Visual Studio macro to group files

Visual Studio has the concept of grouping files together a feature used mainly from code generation tools to groups generated files under the main file, but this feature can be used even for your class, as shown in Figure1.

image

Figure 1: Two code files nested inside program.cs

There are a lot of reasons to group files togheter, you can use this technique for group partial classes definition or you can simply want to group logically related files, etc etc. The annoying stuff is that there is no menu in Visual Studio that permits you to obtain this result, but you can solve everything with a  macro. Just open the Visual Studio Macro IDE and insert the following macro.

   1: Sub GroupFileTogether()

   2:  

   3:     Dim lvProcesses As New ListView

   4:     For I As Int32 = 1 To DTE.SelectedItems.Count

   5:         Dim item As EnvDTE.SelectedItem = DTE.SelectedItems.Item(I)

   6:         Dim lvi As New ListViewItem

   7:         lvi.Tag = item

   8:         lvi.Text = item.Name

   9:         lvProcesses.Items.Add(lvi)

  10:     Next

  11:  

  12:     If lvProcesses.Items.Count < 2 Then

  13:         Return

  14:     End If

  15:  

  16:  

  17:     Dim frm As New Form

  18:     Dim btn As New Button

  19:     btn.Text = "OK"

  20:     btn.DialogResult = DialogResult.OK

  21:     frm.Controls.Add(btn)

  22:     frm.Width = 300

  23:     frm.Text = "Choose the file to be used as root"

  24:     btn.Dock = DockStyle.Bottom

  25:     frm.Controls.Add(lvProcesses)

  26:     lvProcesses.Dock = DockStyle.Fill

  27:     lvProcesses.View = View.Details

  28:     lvProcesses.Columns.Add("Name", 300, HorizontalAlignment.Left)

  29:     lvProcesses.FullRowSelect = True

  30:  

  31:     If frm.ShowDialog() = DialogResult.OK Then

  32:         Dim selected As EnvDTE.SelectedItem = lvProcesses.SelectedItems.Item(0).Tag

  33:  

  34:         For I As Int32 = 0 To lvProcesses.Items.Count - 1

  35:             Dim item As EnvDTE.SelectedItem = lvProcesses.Items.Item(I).Tag

  36:             If item.Name <> selected.Name Then

  37:                 selected.ProjectItem.ProjectItems.AddFromFile( _

  38:                     item.ProjectItem.FileNames(0))

  39:             End If

  40:         Next

  41:     End If

  42: End Sub

This is far from being called production code ready, but it does its dirty work, just assign a shortcut to this macro from the Tools->Customize menu (I’ve used CTRL+G, CTRL+R), then you should select all the files you want to group together and press the shortcut, the macro will show you a dialog that permits you to choose which is the file to be used as root, as shown in Figure2

image

Figure 2: The macro is showing you the list of selected files to choose the root file

Just select Program.cs and press ok and the files will be grouped together as shown in Figure1.

Gian Maria

Macro to attach to local IIS take 3

This is the third post on the series “create a macro to Attach to Local IIS”. The last modification I want to implement is the ability to list all the w3wp.exe active processes, if more than one process is present, I want it to show a list of all IIS processes and let the user choose the list of processes to attach to. Clearly if I have no w3wp.exe active processes, or I have only one, there is no need to bother the user to choose the single available process.

Since I need to show a “user interface” from a macro and I’m not allowed to insert form into Visual Studio Macros Editor, I should create a Windows Forms programmatically. This is quite annoying, but creating simple interfaces without a designer is quite simple, so it is the right approach to maintain the code in a simple macro. First of all I find all IIS processes and add info about these processes inside a ListView.

   1: Dim dbg2 As EnvDTE80.Debugger2 = DTE.Debugger

   2: Dim trans As EnvDTE80.Transport = dbg2.Transports.Item("Default")

   3: Dim processes As EnvDTE.Processes = dbg2.GetProcesses(trans, "")

   4: Dim lvProcesses As New ListView

   5: For Each proc As EnvDTE80.Process2 In processes

   6:     If (proc.Name.EndsWith("w3wp.exe")) Then

   7:         Dim lvi As New ListViewItem

   8:         lvi.Tag = proc

   9:         Dim engineList As String

  10:         lvi.Text = proc.ProcessID

  11:         lvi.SubItems.Add(proc.UserName)

  12:         lvProcesses.Items.Add(lvi)

  13:     End If

  14: Next

To keep a reference to the original EnvDTE80.Process2 object I add the reference to the Tag property of the ListViewItem. Now that I have a ListView populated with all IIS processes, I need to do some check to verify if we have processes available.

   1: If lvProcesses.Items.Count = 0 Then

   2:     Return

   3: End If

   4:  

   5: If lvProcesses.Items.Count = 1 Then

   6:     Dim proc As EnvDTE80.Process2 = lvProcesses.Items(0).Tag

   7:     proc.Attach2()

   8:     Return

   9: End If

As you can see the code is really simple, if I have no valid worker process, I simply return, but if I have a single IIS worker process available, I’ll attach the debugger to it without requiring user input. If I have more than one worker process available I need the user to select the list of the processes he want to attach to.

   1: Dim frm As New Form

   2: Dim btn As New Button

   3: btn.Text = "OK"

   4: btn.DialogResult = DialogResult.OK

   5: frm.Controls.Add(btn)

   6: frm.Width = 700

   7: frm.Text = "Choose IIS worker process to debug"

   8: btn.Dock = DockStyle.Bottom

   9: frm.Controls.Add(lvProcesses)

  10: lvProcesses.Dock = DockStyle.Fill

  11: lvProcesses.View = View.Details

  12: lvProcesses.Columns.Add("ProcessId", 100, HorizontalAlignment.Left)

  13: lvProcesses.Columns.Add("User", 300, HorizontalAlignment.Left)

  14: 'lvProcesses.Columns.Add("Type", 300, HorizontalAlignment.Left)

  15: lvProcesses.FullRowSelect = True

  16:  

  17: If frm.ShowDialog() = DialogResult.OK Then

  18:     For Each fitem As ListViewItem In lvProcesses.SelectedItems

  19:         Dim proc As EnvDTE80.Process2 = fitem.Tag

  20:         proc.Attach2()

  21:     Next

  22: End If

The code is really simple, because it is used only to create a valid Windows Forms interface to show the ListView with Processes info and an OK Button at the bottom to permit to the user to confirm the selection. Now you can start multiple IIS worker process, press the shortcut assigned to this macro and here is the result.

image

Figure 1: The list of all available IIS processes

The User Interface is minimal, I show the process Id and the user that is actually running the process; this last information is really important, because I usually use a different user for each different product I’m working to, so if I need to attach to all IIS processes of a certain product, I immediately can identify all IIS processes that are running with that credential and attach the debugger to the right list of processes.

When the user press OK button, I can iterate through all selected items of the ListView, cast the Tag property to EnvDTE80.Process2 and attach the debugger to the process. As you can see I use the overload version of the Attach2() process that accepts no parameter; this permits me to avoid to specify if I want to debug asp.net 2.0 or 4.0 code, and I can let the debugger choose the right one. Remember that you cannot debug at the same time ASP.NET 4.0 and earlier version, so you could not choose two worker process that are running two different version of the framework.

This post shows clearly that Visual Studio Macro are powerful, yet very simple to use and they can save you huge amount of time automating recurring operations.

Alk.

A file with the complete macro can be downloaded from here. (http://www.codewrecks.com/files/attachtoiis.txt)

Attach to local IIS Macro evolution

I’ve already blogged about the creation of a macro that permits you to attach to IIS worker process pressing a key combination. This approach does not work if you have multiple w3wp.exe processes that are actually running into your machine, because the macro only attach to the very first of them.

A simple and stupid solution is to attach to every w3wp.exe processes that is actually running in the system :), it is somewhat a “patch” solution, but it is really easy to implement, because it requires only a minor modification of the code generated by the Macro Recorder.

   1: Sub AttachToIISProcess()

   2:      Try

   3:          Dim dbg2 As EnvDTE80.Debugger2 = DTE.Debugger

   4:          Dim trans As EnvDTE80.Transport = dbg2.Transports.Item("Default")

   5:          Dim dbgeng(2) As EnvDTE80.Engine

   6:          dbgeng(0) = trans.Engines.Item("T-SQL")

   7:          dbgeng(1) = trans.Engines.Item("Managed (v2.0, v1.1, v1.0)")

   8:          Dim processes As EnvDTE.Processes = dbg2.GetProcesses(trans, "")

   9:          For Each proc As EnvDTE80.Process2 In processes

  10:              If (proc.Name.EndsWith("w3wp.exe")) Then

  11:                  proc.Attach2(dbgeng)

  12:              End If

  13:          Next

  14:      Catch ex As System.Exception

  15:          MsgBox(ex.Message)

  16:      End Try

  17:  End Sub

The change is in lines 8-13, in the first version I use the Item Index property of the EnvDTE.Processes object to grab the first instance of the process named w3wp.exe, in this smarter version I iterate through each process, and attach to every process that ends with the string w3wp.exe. With this little modification I’m able to attach to every IIS process, a capability that is useful when you are debugging multiple sites / services and you need to use multiple worker processes.

Alk.

Visual studio Macro to the rescue

If you heavily work with branches, one of the most frustrating error you can do is modify the wrong branch. I have a project composed by several solution each one containing different UI Projects, each one using WCF as back end but released as separate software. Whenever we do a release of a new version of one of the UI we create a branch, so we can support hotfix, SP, etc.

Figure 1: A branching strategy taken from the branchingguidance

With this scenario I’m prone to this error:

Someone call me telling to create an hotfix for the UI XYZ, I open the solution from Branch\Releases\XYZ\R3_0 to open the third (is the latest) release of the XYZ UI, I do the hotfix, test it, run all the test etc etc, then other people of the team call me asking for modification in other part, so I open the same solution from a different branch (say the trunk or the Feature BLABLA) and do some modification. Now I have two Visual Studio opened, with the very same solution, sometimes I got confused and I modify the wrong line of code… too bad, because this can cause a really bad problem. The problem arise from having more than one instance of Visual Studio opened with solutions of same name, just from different branch Sad smile and you can become confused.

This is expecially true when you use HG or subversion or any other VCS tools that are not integrated in Visual Studio.

A simple solution is creating a Visual Studio Macro that uses a Regex to parse the fullpath of the solution file, searching for a specific folder structure that identify a Branch. Here is the full code

   1: Declare Auto Function SetWindowText Lib "user32" (ByVal hWnd As System.IntPtr, _

   2: ByVal lpstring As String) As Boolean

   3:  

   4:  

   5: Private Sub showTitle(ByVal title As String)

   6:     SetWindowText(New System.IntPtr(DTE.MainWindow.HWnd), title & " - " & DTE.Name)

   7: End Sub

   8:  

   9:  

  10: Private Sub SolutionEvents_Opened() Handles SolutionEvents.Opened

  11:     Dim m As Match = Regex.Match( _

  12:         DTE.Solution.FullName, _

  13:         "Branch.*\\(?<project>.*)\\(?<branch>.*)\\(?<sln>.*)\.sln", _

  14:         RegexOptions.IgnoreCase)

  15:     If (m.Success) Then

  16:         Dim project As String = m.Groups("project").Value

  17:         Dim version As String = m.Groups("branch").Value

  18:         Dim sln As String = m.Groups("sln").Value

  19:         showTitle(String.Format("BRANCH [{0}] - Project {1} - {2}", _

  20:                     version, project, sln))

  21:  

  22:     End If

  23: End Sub

You need to paste this code in the Macros editor, opened from Tools –> Macros –> Macros IDE

SNAGHTML1889083

Figure 2: Open the Macro editor

From the opened editor you just double click on MyMacros, expand the EnvironmentEvents, and you can add your code to every handler supported from Visual Studio.

SNAGHTML18c25a1

Figure 3: You can handle standard events from the IDE, such as when a solution is opened.

If you look at the code, I’ve simply put a regex that permits me to parse the typical branch path structure I have on my projects, where I have Branch\someothertext\nameoftheproject\branchnumber\solutionfile.sln. When I open the trunk version I got only the solution name on the title bar.

image

Figure 4: the solution is opened from the trunk (main) because only the name of the solution is shown in title bar

I’ve opened the same solution from a release of a specific UI and I got

image

Figure 5: The same solution is opened from a branch folder, now title bar shows me that I’m working on branch

I immediately visualize that this solution is a branch, the version number is R3_0 (Release three point zero), the project released is ZZZZManager that resides on the XXXXXWeb-Vs2010 solution ;) now it is really difficult for me and other people to write code on a wrong branch, because all the informations I need are on the title bar.

Using title of the windows gives also the exceptional advantage of having these information in Windows 7 tray bar

image

Figure 6: Using the title bar, I got the very same information when I need to maximize a windows from Windows 7 tray bar

So you can immediately know witch is the right windows to use. Moreover developing such a macro is a matter of minutes, because you can simply add the handler and some code inside the macro editor, Then save the macro, close and reopen visual studio, open again the macro editor and activate the debugger

image 

Figure 7: I can even debug the macro, an invaluable option when you are experimenting on your code.

Now you can put breakpoint around the code, and debugging your macro to tune it.

Alk.