Have you ever tried adding a new reference to lots of projects in a very large application? If you have, you know this already and if you haven’t, let me tell you: it gets old really fast… Open project, right click the project, click on “Add Reference…” and so on. As an inherently lazy demographic, this simply is not an options for us developers. So what do we do? We automate it with a VisualStudio macro.
First of all: yes, its VB – deal with it.
You can find the Macros IDE in the Tools menu of VS under Macros. There are some samples that come with VS and there is some code out there on the web, but what I want to show you is how you can automate project reference handling.
DTE.Solution.Projects
gives you a collection of project objects in your current solution. You can add projects with
DTE.Solution.AddFromFile(projectFilePath)
but to add a reference to a project, you need to get a handle on a VSProject while the projects in DTE.Solution.Projects are of type Project. To get a VSProject from a Project, simply do this
Dim vsProject = CType(DTE.Solution.Projects.Item(1).Object, VSProject)
Now you can add a reference to this project by doing this
‘ add project reference to otherProject (of type Project)
vsProject.References.AddProject(otherProject)
‘ add file reference to assemblyPath
vsProject.References.Add(assemblyPath)
To get a better picture of how you could use that. Here is some code of mine that I used to add a file reference to a bunch of projects. The macro first asks the user to select a target assembly and then a file containing a list of folder names (separated by newlines). For every entry in the folder list, the macro searches for a VB or C# project in that folder or a parent folder and if the project hasn’t already been processed, adds the project to the solution and adds the file reference.
Public Module References
Public Sub CustomAddFileReference()
Dim log As TextWriter = New StringWriter()
If (DTE.Solution.Count <> 0) Then
MsgBox("Please close solution first")
Return
End If
DTE.Solution.Create("C:\", "automagic")
Dim openFileDialog As Forms.FileDialog = New Forms.OpenFileDialog
Dim winptr As WinWrapper = New WinWrapper
Dim target As String
Dim projects As Dictionary(Of String, Project) = New Dictionary(Of String, Project)()
openFileDialog.Filter = "Assemblies (*.dll)|*.dll|All files (*.*)|*.*"
openFileDialog.FilterIndex = 2
openFileDialog.Title = "Select Target Assembly"
If openFileDialog.ShowDialog(winptr) = Forms.DialogResult.OK Then
target = openFileDialog.FileName
End If
openFileDialog.Filter = "All files (*.*)|*.*"
openFileDialog.FilterIndex = 1
openFileDialog.Title = "Select Project List"
If openFileDialog.ShowDialog(winptr) = Forms.DialogResult.OK Then
Dim reader As StreamReader = File.OpenText(openFileDialog.FileName)
While (Not reader.EndOfStream)
Dim line As String = reader.ReadLine().Trim()
Dim projFile = FindProjectFile(line)
If (Not projFile Is Nothing AndAlso Not projects.ContainsKey(projFile)) Then
log.WriteLine(projFile)
Try
Dim source As Project = DTE.Solution.AddFromFile(projFile)
projects.Add(projFile, source)
Dim vsProject = CType(source.Object, VSProject)
vsProject.References.Add(target)
Catch ex As Exception
log.WriteLine(ex.ToString())
End Try
End If
End While
reader.Dispose()
End If
Dim outputPane As OutputWindowPane = GetOutputWindowPane("Macros")
outputPane.OutputString(log.ToString())
End Sub
Private Function FindProjectFile(ByVal directory As String) As String
Dim path = directory
Dim project = Nothing
Dim ext = Nothing
While (project Is Nothing)
For Each child As String In System.IO.Directory.GetFiles(path)
ext = System.IO.Path.GetExtension(child)
If (ext = ".vbproj" OrElse ext = ".csproj") Then
Return child
End If
Next
path = System.IO.Path.GetFullPath(System.IO.Path.Combine(path, ".."))
End While
Return Nothing
End Function
Private Function GetOutputWindowPane(ByVal Name As String, Optional ByVal show As Boolean = True) As OutputWindowPane
Dim window As Window
Dim outputWindow As OutputWindow
Dim outputWindowPane As OutputWindowPane
window = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput)
If show Then window.Visible = True
outputWindow = window.Object
Try
outputWindowPane = outputWindow.OutputWindowPanes.Item(Name)
Catch e As System.Exception
outputWindowPane = outputWindow.OutputWindowPanes.Add(Name)
End Try
outputWindowPane.Activate()
Return outputWindowPane
End Function
End Module
'' This class is used to set the proper parent to any UI that you may display from within a macro.
'' See the AddClassicComRef macro for an example of how this is used
Public Class WinWrapper
Implements System.Windows.Forms.IWin32Window
Overridable ReadOnly Property Handle() As System.IntPtr Implements System.Windows.Forms.IWin32Window.Handle
Get
Dim iptr As New System.IntPtr(DTE.MainWindow.HWnd)
Return iptr
End Get
End Property
End Class