When I first heard that macros were being dropped from Dev 11 I was gobsmacked. (I just love that world!) While the macro story up to Visual Studio 2010 wasn’t great because we couldn’t write macros in our .NET language of choice, I expected the situation would start getting better and was saddened when the fix was to remove the easy customizability all together.
People at Microsoft said that the code behind macros was too brittle to update and their telemetry said macros are feature no one was using. My theory is the reason people skipped macros are because you had to do it in a different language. (VB people: I’m not criticizing VB but most of the developers who would write macros are C#/C++ developers). By dropping macros, the argument was that there was still a valid way to extend the development environment with .VSX files. That’s great but like I really want to install an SDK, write a VSX and have Dev 11 debugging Dev 11 just to enumerate my projects and add a new define to debug build configurations.
Readers of this blog know I’ve written many macros (examples here) that make debugging in particular easier. I use my macros on a daily basis so was not looking forward to porting them to an extension and all the junk that entails. As I was navigating the file system using the now built in NuGet Package Manager Console window, why not use PowerShell as the macro language? While this works, I’ll still be pestering Microsoft a lot during the Dev 12 development cycle for a real macro system that lets me program in any .NET language.
To use PowerShell as the macro language, you need access to the Visual Studio automation model. In macros and extensions the root of life is the DTE interface. A quick look at the variables defined in the Package Manager Console shows a $dte variable so once you have that, you’ve got everything! That’s nice when things are easy.
My goal was to port all my macros over to PowerShell and you can see the results of my porting efforts at the bottom of this blog entry. For the most part, it was simply a matter of porting VB to PowerShell, which wasn’t too onerous if you know PowerShell. The main drawback I encountered is that to debug any functions you write you have to use the old PowerShell 1.0 way of debugging with Set-PSDebug and all the fun of command line debugging. It’s a bit painful, but it does work.
There were two areas where I got stumped during the porting. The first was in converting interfaces. When accessing the breakpoints in PowerShell, you use $dte.Debugger.Breakpoints. However, that returns the original IBreakpoints interface defined back in Visual Studio 2003. However, if you want to access the Filterby property, you need the IBreakpoints2 interface defined in Visual Studio 2005. As PowerShell’s COM implementation only works with IDispatch, and not explicit interface members, I was not sure I could get everything to work if I couldn’t convert. Fortunately, the NuGet developers already thought of this and provided the Get-Interface cmdlet that allows easy conversion. The following snippet shows converting an IBreakpoints interface into the IBreakpoints2 interface.
The second problem in porting I encountered was trying to use a Visual Studio method like $dte.Breakpoints.Add, which takes many optional and conflicting parameters. That’s easy to do in VB, but PowerShell sort of likes you to use all the parameters to a COM method. A bit of careful web searching lead me to a great solution by Jason Archer where he uses some seriously ninja PowerShell tricks to solve the problem. If you want to learn some advanced PowerShell, look at Invoke-NamedParameter function.
By using the Package Manager Console, I thought packaging up my cmdlets as a NuGet package would be the way to go. The only problem is that a NuGet package is designed to be used with a solution, which is the correct approach, but I wanted my cmdlets to be available at all times. To that end I chose to go with a PowerShell module, which works just fine with the Package Manager Console.
You can put the files in the download anywhere you want, but if you would like to include them in the module path, use Install-Module.PS1 to do the installation.
It looks like there’s a bug in the NuGet Package Manager Console because the $ENV:PSModulePath does not include the normal user’s documents directory in the default path. Consequently, to run Install-Module.PS1 you will need to run Dev 11 elevated to do the installation as the NuGet modules directory is in Program Files.
If you’re still using Visual Studio 2010 and you want to try out my cmdlets, please do as they all work provided you installed NuGet. If you have any questions or ideas for other macros, please don’t hesitate to let me know.