VS Project Synch macro

00000000000000000000000000000000
0
Anonymous Sep 26, 2009 at 14:00

Hello everyone :)

In the last months, I have been busy in a less-than-optimally organized C++ project.

This project involved work on a larger codebase that had absolutely no version control system available. All files were lying around in a semi-sorted directory structure on some intranet server. This, of course, made daily synching to this structure quite tedious. In particular, Visual Studio 2003 refuses to import recursive directories, and aborts importing if any files are already present within the solution.

Thus, I wrote the following macro.

It essentially discards all folders and files in selected projects, and rebuilds these from the solution directories.

During this process, new solution folders are created for each on-disk sub directory, so the source files remain nicely sorted in the IDE.

The macro has been tested in Visual Studio 2003, but should hopefully work in other VS versions with slight adaptions.

Note that the
Microsoft.VisualStudio.VCProject
and
Microsoft.VisualStudio.VCProjectEngine
references need to be added to the Visual Studio macro project for the macro to work.

Perhaps one or another might find it helpful, it’s free for any use.

If anybody is interested in more integrated Visual Studio code, I also have a working COM-based C++ version of the macro available.

Cheers,
- Wernaeh

'==========================================================================
' Project generator main module.
'
' Copyright Wernaeh, Juli 2009
' Free for any use.
'
' Contact: wernaeh@gmail.com
'==========================================================================

Option Explicit On 
Option Strict Off

Imports EnvDTE
Imports System.Diagnostics
Imports Microsoft.VisualStudio.VCProject
Imports Microsoft.VisualStudio.VCProjectEngine

Public Module ProjectGen

    '==========================================================================
    ' Main project generation entry point
    '==========================================================================

    Sub regenerateMain()

        '======================================================================
        ' Temporaries
        '======================================================================

        ' Temporary loop variables
        Dim i As Integer
        Dim j As Integer

        ' Array with file extensions to be added
        Dim exts(8) As String
        exts(0) = ".c"
        exts(1) = ".cpp"
        exts(2) = ".h"
        exts(3) = ".def"
        exts(4) = ".ico"
        exts(5) = ".rc"
        exts(6) = ".flex"
        exts(7) = ".bat"


        '======================================================================
        ' Determine project selection
        '======================================================================

        ' Retrieve an array with selected projects
        Dim selectedprojects() As Project
        Dim selectedprojectcount As Integer
        selectedprojectcount = 0

        For i = 1 To DTE.SelectedItems.Count
            If TypeOf DTE.SelectedItems.Item(i).Project Is Project Then
                selectedprojectcount += 1
                Redim Preserve selectedprojects(selectedprojectcount - 1)           
                selectedprojects(selectedprojectcount - 1) = _
                    DTE.SelectedItems.Item(i).Project
            End If
        Next


        ' Notify the user if no projects have been selected
        If selectedprojectcount = 0 Then
            MsgBox("Please select some projects for processing with the" & _
                    vbNewLine & _
                    "project generator within the solution view.", _
                    MsgBoxStyle.OKOnly, _
                    "Please select projects!")
            Return
        End If


        '======================================================================
        ' Handle open documents
        '======================================================================

        ' Prompt the user whether to save all open documents, if there
        ' are any.
        If DTE.Documents.Count > 0 Then
            Dim usersaveall As MsgBoxResult
            usersaveall = _
                MsgBox("The project generator will close any open files." & _
                        vbNewLine & _
                        "Should these be saved now?", _
                        MsgBoxStyle.YesNoCancel, _
                        "Save open files now?")

            If usersaveall = MsgBoxResult.Cancel
                Return
            End If

            If usersaveall = MsgBoxResult.Yes
                DTE.Documents.SaveAll()
            End If

            DTE.Documents.CloseAll(vsSaveChanges.vsSaveChangesNo)
        End If


        '======================================================================
        ' Regenerate projects
        '======================================================================
        
        ' Now, regenerate each project
        For i = 0 To selectedprojectcount - 1

            Dim project As Project
            project = selectedprojects(i)


            '==================================================================
            ' Regeneration prompt
            '==================================================================

            ' First, prompt whether regeneration is desired at all
            ' for a single project.
            Dim userregenerate As MsgBoxResult
            userregenerate = _
                MsgBox("Should the project:" & vbNewLine & _
                       "        " & selectedprojects(i).Name & vbNewLine & _
                       "be regenerated?" & vbNewLine & vbNewLine & _
        "All file links and folders within the project will be removed," & _
                        vbNewLine & _
                       "and regenerated from the on-disk project structure.", _
                       MsgBoxStyle.YesNoCancel, _
                       "Really regenerate project: " & _
                        selectedprojects(i).Name & "?")

            If userregenerate = MsgBoxResult.Cancel
                Return
            End If

            If userregenerate = MsgBoxResult.No
                Goto NextProject
            End If


            '==================================================================
            ' Delete old project items
            '==================================================================

            ' Here, we just need to delete root project items.
            ' Since we do not know how indices change when deleting
            ' items, we do a copy to a save array first.
            Dim items() As ProjectItem
            Dim itemcount As Integer

            itemcount = project.ProjectItems.Count
            Redim items(itemcount - 1)

            For j = 1 To project.ProjectItems.Count
                items(j - 1) = project.ProjectItems.Item(j)             
            Next

            For j = 0 To itemcount - 1
                items(j).Remove()
            Next


            '==================================================================
            ' Regenerate project items from disk
            '==================================================================

            ' First, retrieve the project folder
            Dim projectfolder As String
            projectfolder = _
                Left(project.FullName, InStrRev(project.FullName, "") - 1)

            ' Then, starting with the project folder,
            ' add all files into our project.
            ' Note there is a bug here, we can't use the common DTE interfaces
            ' for managing project folders for VC projects.
            ' Thus, we need to go for the C++ specific project type instead.
            Dim projectvc As VCProject
            projectvc = project.Object

            addItemsInFolder(projectfolder, "", projectvc, nothing, exts, 0)
        
            NextProject:            
        Next


        '==================================================================
        ' Recursively close all treeview items
        '==================================================================

        Dim solutionexplorer As UIHierarchy
        solutionexplorer = _
            DTE.Windows.Item(Constants.vsWindowKindSolutionExplorer).Object

        For i = 1 To solutionexplorer.UIHierarchyItems.Count
            closeTreeViewHelper _
                (solutionexplorer.UIHierarchyItems.Item(i), solutionexplorer, _
                 selectedprojects, selectedprojectcount, 0)
        Next

    End Sub


    '==========================================================================
    ' Directory management helper
    '==========================================================================

    Sub addItemsInFolder(basefolder As String, recursefolder As String, _
                         projectvc As VCProject, _
                         filtervc As VCFilter, exts() As String, _
                         depth As Integer)

        ' Put together base directory for searching,
        ' this needs to be the absolute directory within the
        ' project base directory.
        Dim searchfolder As String

        If recursefolder <> "" Then
            searchfolder = basefolder & "" & recursefolder
        Else
            searchfolder = basefolder
        End If


        ' First, handle files within the folder.
        Dim filename As String
        filename = Dir(searchfolder & "*")


        Do While (filename <> "")

            ' Expand file to relative path within project
            If recursefolder <> "" Then
                filename = recursefolder & "" & filename
            End If

            ' For each found file,
            ' check if it matches the provided extensions filter.
            Dim i As Integer

            For i = 0 To UBound(exts)
                If Right(filename, Len(exts(i))) = exts(i) Then

                    ' File matches, so either add it to the filter
                    ' or the project itself
                    Dim file As VCFile

                    If filtervc Is Nothing Then
                        file = projectvc.AddFile(filename)
                    Else
                        file = filtervc.AddFile(filename)
                    End If

                    Goto filefinished
                End If
            Next

            filefinished:

            filename = Dir()
        Loop


        ' Load all folder directories into some array - 
        ' otherwise recursion won't do, since Dir() does
        ' not have a search handle.
        Dim dirnames() As String
        Dim dirnamecount As Integer
        dirnamecount = 0

        Dim dirname As String
        dirname = Dir(searchfolder & "*", vbDirectory)

        Do While (dirname <> "")
            If (GetAttr(searchfolder & "" & dirname) And vbDirectory) = _
                    vbDirectory Then
                dirnamecount += 1
                Redim Preserve dirnames(dirnamecount - 1)
                dirnames(dirnamecount - 1) = dirname
            End If          

            dirname = Dir()
        Loop


        ' Finally, create items for each folder, and
        ' recurse into subfolders.
        Dim j As Integer
    
        For j = 0 To dirnamecount - 1

            Dim newfilter As VCFilter

            If filtervc Is Nothing Then
                newfilter = projectvc.AddFilter(dirnames(j))
            Else
                newfilter = filtervc.AddFilter(dirnames(j))
            End If

            Dim newrecurse As String

            If recursefolder <> "" Then
                newrecurse = recursefolder & "" & dirnames(j)
            Else
                newrecurse = dirnames(j)
            End If

            addItemsInFolder(basefolder, newrecurse, _
                             projectvc, newfilter, exts, depth + 1)
        Next

    End Sub


    '==========================================================================
    ' Tree view closing helper
    '==========================================================================

    Sub closeTreeViewHelper(hierarchyitem As UIHierarchyItem, _
                            solutionexplorer As UIHierarchy, _
                            selectedprojects() As Project, _
                            selectedprojectcount As Integer, _
                            depth As Integer)

        ' Helper call for closing all but the lowest hierarchy items.
        ' This is a little bit more complicated than one would assume:
        ' First, we need to check if the given item is a folder.
        ' This is done by checking its number of children - empty folders
        ' are ignored.
        ' Then, the folder is first expanded, so we may close children
        ' folders - otherwise closing children doesn't work.
        ' Then, the folder is closed again.
        ' Folder closing is performed by selecting the folder and
        ' then simulating a mouse click event.
        
        ' Test if we are a valid folder - i.e. we have children.
        ' For leaf entries (files), we just abort here.
        If hierarchyitem.UIHierarchyItems.Count = 0 Then
            Return
        End If


        ' On level 1 (projects level), we should ignore all projects
        ' that currently are not selected.
        ' This keeps the update from messing with unrelated projects.
        If depth = 1 Then
            Dim j As Integer

            For j = 0 To selectedprojectcount - 1
                If selectedprojects(j) Is hierarchyitem.Object Then
                    Goto projectselected
                End If          
            Next


            ' Project not selected, so abort
            Return

            projectselected:
        End If


        ' We are a folder, so see if we are expanded,
        ' if we aren't do the expansion now.
        If Not hierarchyitem.UIHierarchyItems.Expanded Then
            hierarchyitem.Select(vsUISelectionType.vsUISelectionTypeSelect)
            solutionexplorer.DoDefaultAction()
        End If


        ' Recurse on all children items
        Dim i As Integer
        For i = 1 To hierarchyitem.UIHierarchyItems.Count
            closeTreeViewHelper _
                (hierarchyitem.UIHierarchyItems.Item(i), solutionexplorer, _
                 selectedprojects, selectedprojectcount, depth + 1)
        Next


        ' Finally, unexpand our object again,
        ' but only if it is not on depth = 1, so root project folders and
        ' files remain visible.
        ' (Depth = 0 -> Solution, Depth = 1 -> Projects)
        If depth > 1 Then
            hierarchyitem.Select(vsUISelectionType.vsUISelectionTypeSelect)
            solutionexplorer.DoDefaultAction()
        End If

    End Sub

End Module

5 Replies

Please log in or register to post a reply.

99f6aeec9715bb034bba93ba2a7eb360
0
Nick 102 Sep 28, 2009 at 07:38

I don’t get it. Shouldn’t you instead just be using version control? TortoiseSVN and TortoiseGit are pretty awesome.

Fd80f81596aa1cf809ceb1c2077e190b
0
rouncer 103 Sep 28, 2009 at 08:22

wow, havent looked at basic in a while.

B7568a7d781a2ebebe3fa176215ae667
0
Wernaeh 101 Sep 30, 2009 at 06:07

I don’t get it. Shouldn’t you instead just be using version control? TortoiseSVN and TortoiseGit are pretty awesome

Ya, it wasn’t my decision not to use any version control - sadly, sometimes the people who decide are not those that should do so…

That’s why I came up with the macro above - the only alternative was that I’d create my own local SVN repository, synch that to the coworkers network project folders on the one side, and to VS on the other.

Hope that makes it a little more clear ;)

Cheers,
- Wernaeh

99f6aeec9715bb034bba93ba2a7eb360
0
Nick 102 Sep 30, 2009 at 09:47

Ok, thanks for clarifying that. Still, it leaves a bad taste in my mouth to suggest a neat solution to a problem that actually really asks for a totally different solution: a hammer to slam on the heads of the people who decided not to use source control. :blush:

I seriously suggest to try and convince every possible coworker of using SVN, and create some momentum to demand server space for it. There’s no excuse for not using version control.

B7568a7d781a2ebebe3fa176215ae667
0
Wernaeh 101 Sep 30, 2009 at 20:36

… great… now I want a hammer ;)

Cheers,
- Wernaeh