Visual Studio macro: Find files that aren't in

2019-01-11 15:18发布

问题:

I'd like to write a macro to crawl through the files in my project directory and find files that aren't included in the project.

In playing around with the DTE object, I see that the Project object has ProjectItems; if a ProjectItem represents a directory, then it has its own ProjectItems collection. This gives me all files that are included in the project.

So I could crawl recursively through each ProjectItems collection, and for each ProjectItem that's a directory, check to see if there are files in the file system that don't have a corresponding ProjectItem. This seems clumsy, though.

Any ideas of a simpler way to approach this?

回答1:

Here is the C# version of your code:

public static void IncludeNewFiles()
{
    int count = 0;
    EnvDTE80.DTE2 dte2;
    List<string> newfiles;

    dte2 = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.10.0");

    foreach (Project project in dte2.Solution.Projects)
    {
        if (project.UniqueName.EndsWith(".csproj"))
        {
            newfiles = GetFilesNotInProject(project);

            foreach (var file in newfiles)
                project.ProjectItems.AddFromFile(file);

            count += newfiles.Count;
        }
    }
    dte2.StatusBar.Text = String.Format("{0} new file{1} included in the project.", count, (count == 1 ? "" : "s"));
}

public static List<string> GetAllProjectFiles(ProjectItems projectItems, string extension)
{
    List<string> returnValue = new List<string>();

    foreach(ProjectItem projectItem in projectItems)
    {
        for (short i = 1; i <= projectItems.Count; i++)
        {
            string fileName = projectItem.FileNames[i];
            if (Path.GetExtension(fileName).ToLower() == extension)
                returnValue.Add(fileName);
        }
        returnValue.AddRange(GetAllProjectFiles(projectItem.ProjectItems, extension));        
    }

    return returnValue;
}

public static List<string> GetFilesNotInProject(Project project)
{
    List<string> returnValue = new List<string>();
    string startPath = Path.GetDirectoryName(project.FullName);
    List<string> projectFiles = GetAllProjectFiles(project.ProjectItems, ".cs");

    foreach (var file in Directory.GetFiles(startPath, "*.cs", SearchOption.AllDirectories))
        if (!projectFiles.Contains(file)) returnValue.Add(file);

    return returnValue;
}


回答2:

Thanks to @JaredPar and @lpthnc for pointing me in the right direction. I ended up using an approach very similar to what @JaredPar outlines above. Here's my working macro FWIW.

Imports System.IO
Imports System.Collections.Generic
Imports EnvDTE

Public Module Main

    Sub IncludeNewFiles()
        Dim Count As Integer = 0
        For Each Project As Project In DTE.Solution.Projects
            If Project.UniqueName.EndsWith(".vbproj") Then
                Dim NewFiles As List(Of String) = GetFilesNotInProject(Project)
                For Each File In NewFiles
                    Project.ProjectItems.AddFromFile(File)
                Next
                Count += NewFiles.Count
            End If
        Next
        DTE.StatusBar.Text = String.Format("{0} new file{1} included in the project.", Count, If(Count = 1, "", "s"))
    End Sub

    Private Function GetAllProjectFiles(ByVal ProjectItems As ProjectItems, ByVal Extension As String) As List(Of String)
        GetAllProjectFiles = New List(Of String)
        For Each ProjectItem As ProjectItem In ProjectItems
            For i As Integer = 1 To ProjectItem.FileCount
                Dim FileName As String = ProjectItem.FileNames(i)
                If Path.GetExtension(fileName).ToLower = Extension Then
                    GetAllProjectFiles.Add(fileName)
                End If
            Next
            GetAllProjectFiles.AddRange(GetAllProjectFiles(ProjectItem.ProjectItems, Extension))
        Next
    End Function

    Private Function GetFilesNotInProject(ByVal Project As Project) As List(Of String)
        Dim StartPath As String = Path.GetDirectoryName(Project.FullName)
        Dim ProjectFiles As List(Of String) = GetAllProjectFiles(Project.ProjectItems, ".vb")
        GetFilesNotInProject = New List(Of String)
        For Each file In Directory.GetFiles(StartPath, "*.vb", SearchOption.AllDirectories)
            If Not ProjectFiles.Contains(file) Then GetFilesNotInProject.Add(file)
        Next
    End Function

End Module


回答3:

The approach I would take is to

  1. Enumerate the file system looking for all files
  2. Check and see if the given file has an associated project item.

Here is a quick bit of sample code

Function ContainsItem(p as Project, fileName as String) As Boolean
  Try
    Return p.ProjectItems.Item(fileName)
  Catch ex As ArgumentException
    Return False
  End Try
End Function

Function CotainsItem(dte as DTE, fileName as String) As Boolean
  For Each p As Project in dte.Solution.Projects
    Return ContainsItem(p, fileName)
  Next
End Function

Function GetFilesNotInProject(dte as DTE, startPath as String) as List(Of String)
  Dim list As New List(Of String)
  Dim files = Directory.GetFiles(startPath, "*.cs", SearchOPtions.AllDirectories)
  For Each file in files 
    If Not ContainsItem(dte, file) Then
      list.Add(file)
    End If
  Next
  Return list
End Function


回答4:

I'd go with PowerShell. The PowerShell script in my other post will do this for you. The script will get the list of included files from the project file and compare that against the files on disk. You will get the set of files that are on disk but not included in the project. You can either delete them or pend them as deletes for TFS.

https://stackoverflow.com/a/23420956/846428