StudioShell 1.5 is Available

§ February 6, 2013 14:07 by beefarino |

I’m happy to say that a new release of StudioShell is up, along with a new version of the StudioShell.Beta nuget package!

The big things in this release:

  1. Support for Visual Studio 2012
  2. Support for PowerShell 3.0
  3. Support for use in the ISE

In addition there are tons of itty bitty bug fixes and incremental improvements. 

This release is still classified as a beta.  I want to collect usage information from VS2012 and/or PowerShell 3 users before marking the release as stable, and there are some documentation gaps that need to be filled.  That said, the 1.5 release is still preferred over the existing 1.2 and 1.3.1 packages for stability and features.

Oh, and I went ahead and started pushing the StudioShell.Contrib project.  There isn’t must up there - at the moment it contains a few “helper functions” that I commonly use, and I will shortly push some Psake-related functions along with some contribution guidelines.  My hope is that others will fork the project and contribute their own pieces.

Enjoy!



StudioShell Update and a Related Effort

§ January 27, 2013 18:05 by beefarino |

Software and computing_Console_128I’m prepping a release of StudioShell by the end of January.  This release will include overdue support for Visual Studio 2012, and contain a ton of small fixes (and a few rather large ones).

As I wrap up this 1.5 beta release, I'm vacillating with certain new features I'm not sure everyone would want.  For instance, in my local StudioShell profile script, I've added menu items for projects, code elements, and files that allow me to quickly mount the studioshell prompt to that location.  E.g., right-click on a class, select "Mount this Class", and StudioShell magically does a push-location to the class location in the code model tree.  Similarly, right-click on the class, select “Mount this File Location,” and StudioShell changes location to the file system folder containing the code file.

There’s a ton of these little things floating about.  While these are useful, I don't think they necessarily need to be made a part of StudioShell.  I think they deserve their own place in the community.  So I’m creating a new StudioShell.Contrib project to collect these little helpers.  I’m not sure where this project will be hosted yet – either alongside StudioShell on CodePlex or perhaps on GitHub - your input is welcome.



StudioShell 1.2 Release

§ February 23, 2012 10:24 by beefarino |

image_thumb1_thumbA bit late to the blog, but I’m happy to say that StudioShell 1.2 has been released! This release has been a long time coming, due largely to my lack of focus and a rather ambitious feature set.

This post is a breakdown of the key elements in the 1.2 release. I’ll elaborate more on some of these items in future posts.

NuGet Support

While I prefer the StudioShell console experience over NuGet, NuGet has a user base that is 170 times larger than StudioShell. It’s silly not to do everything I can to support that environment – both in terms of enabling StudioShell features in the NuGet host and in leveraging the NuGet distribution mechanism.

In the previous releases, the majority of StudioShell features were available from the NuGet console; however, some of the more “luxurious” features wouldn’t work – for instance, you could add menu commands from the NuGet console:

1 PM> new-item dte:/commandbars/menubar/help ` 2 -name clickthis -value {"hello world!" | out-host}

The “clickthis” menu item would appear, but it wouldn’t do anything when you clicked on it. The 1.2 release addresses these shortcomings – you can now expect full support for StudioShell functionality from the NuGet package manager console.

In addition, you can now install StudioShell via NuGet. Just search the public repository for the “StudioShell” package:

1 PM> install-package StudioShell

PowerShell Support

One of the most frequent requests I receive is to use StudioShell from the standard PowerShell console. This would be a great enabler for automated build scenarios and software factories.

This is now possible with the 1.2 release. You can pull StudioShell into your standard PowerShell console just like any other module:

1 > import-module StudioShell

This results in a DTE: drive that is attached to a new instance of Visual Studio that you can use to load solutions, edit projects, create code, etc.

Experimental SSMS Support

A friend of mine is interested in using StudioShell in other Visual Studio shell applications – specifically in BIDS. We haven’t gotten that far, but the StudioShell 1.2 MSI installer does allow you to integrate with SQL Server Management Studio 2012 (Denali).

Not all of the DTE: drive is available – in particular the project and code models don’t seem to be supported in SSMS. At the same time, there is still a lot of useful functionality available. E.g., the debugger hive is fully functional, allowing you to spelunk stacks, locals, etc. during a SQL debugging session.

This feature should be considered highly volatile until we can get some people using it regularly and reporting the issues. Use at your own risk.

DTE: Drive Topology

In the past year I’ve learned quite a bit about designing useful PowerShell providers. I’ve applied some of this knowledge to the StudioShell PSDTE provider by changing the layout of some of the drive nodes.

In previous releases the code model and project model occupied a shared space on the DTE: drive. This made sense from an object-model perspective – projects contain files, files contain code. Unfortunately it made for some slow pipelines; for instance, applying global code changes:

1 ls dte:/solution -rec | ` 2 where {$_ -match 'codeclass' } | ` 3 set-itemproperty -name Comment -value (get-date)

would force iteration of all project items, properties, and code. Very slow operation during which the shell and Visual Studio remain unavailable. The new drive topology moves the code model to a peer of the project model:

1 ls dte:/solution/codemodel -rec | ` 2 where {$_ -match 'codeclass' } | ` 3 set-itemproperty -name Comment -value (get-date)

effectively isolating code from projects and enabling faster “bulk” operations.

These changes are breaking for existing scripts that rely on the old path topology, but 1.2 includes a simple way to revert the drive topology to the previous version when you need to:

1 > use-pathTopologyVersion 1.0

In a nutshell, this command tells the PSDTE provider to use the drive topology that existed in the specified version of StudioShell. If you have a script that relies on these old paths, just add a call to use-pathTopologyVersion to the top of the script and StudioShell will re-enable the old paths. When you’re done, you can bounce back to the current topology just as easily:

1 > use-pathTopologyVersion -current

Cleaner Codebase

StudioShell started as a very … organic project. It grew as I needed it to grow, and it still does. As a friend once understated, “it’s a bit monolithic.” The code needed some serious refactoring before any of the previous features could be realized.

A quick shout-out to two vendors who proved key in this process: NDepend was essential in teasing apart concerns, and PostSharp greatly simplified several feature implementations.

Bug Fixes

Of course this release contains plenty of fixes as well. A special thank-you to everyone who supports this project by submitting issues and discussing features. Keep ‘em coming.

Looking Ahead – looking for help

So what’s next for StudioShell? First and foremost is a more aggressive release pipeline. With 1.2 comes a PSake build, the next step is to incorporate automated deployments to NuGet and CodePlex. I don’t want to wait another 6 months to package a release.

I’m also looking at expanding the DTE drive topology to include some other Visual Studio services. More on this later.

I’d also like some help getting StudioShell usable in other shells, especially the SQL family of Visual Studio shells. If you have the inkling to help, please get in touch.



Generate Monkey Code Smartly with StudioShell

§ October 28, 2011 01:09 by beefarino |

There is no shortage of code generation techniques available in Visual Studio.  There are even some neat PowerShell solutions that are based on the DTE.  Let me show you an approach I’ve used a few times – it’s a fairly special case, but it’s one of those problems that requires a high volume of monkey code that I frankly don’t want to spend a lot of time on.  It also demonstrates a few automation techniques you may not have considered before – such as driving other Visual Studio extensions like ReSharper.  My hope is that it gives you some fodder for exploring your own creative automation solutions with StudioShell.

The scenario: I need to take a COM interface and wrap it in a .NET class.  Why?  Long story for another post, but it has to do with getting PowerShell format definitions working for said COM interfaces. 

imageFor example, I’ve recently been working on a PowerShell Provider around the Windows Task Scheduler.  The Task Scheduler API is COM-based and consists of a handful of about 40 interfaces that need wrapping to work in my provider framework.

My standard workflow to create one of these wrappers is fairly wrote at this point:

  1. Define the wrapper class in C#;
  2. Add a private field holding a reference to the COM interface;
  3. Initialize the field in the wrapper class constructor;
  4. Use ReSharper’s Generate Delegating Members code generation feature to recreate the COM interface implementation in C#.

Not hard, but lots of easy that needs to get done.  First I’ll show how I accomplish this manually, then at how StudioShell can take the pain out of this boring work…

Manual Workflow

Here is how I would implement the wrapper for the simple IAction interface in the Task Scheduler COM type library.  The COM interface is defined as follows in C# code:

1 public interface IAction 2 { 3 string Id 4 { 5 get; 6 set; 7 } 8 9 _TASK_ACTION_TYPE Type 10 { 11 get; 12 } 13 }

Step 1: Define the wrapper class in C#

The C# wrapper classes I create are usually named after the interfaces by replacing the “I” in the interface name with “Shell”.  So, IAction becomes “ShellAction” in implementation:

1 public class ShellAction 2 { 3 }

Step 2: Add a private field holding the COM reference

With a basic class structure in place, I add a private field holding the IAction reference:

1 using TaskScheduler; 2 3 public class ShellAction 4 { 5 IAction _item; 6 }

Step 3: Initialize the COM reference in the class constructor

The IAction _item field needs to be initialized in the class constructor:

1 public class ShellAction 2 { 3 IAction _item; 4 5 public ShellAction(IAction item) 6 { 7 _item = item; 8 } 9 }

And to be honest, I don’t write this code – I use ReSharper to generate it for me.  I hit [alt]-[insert] and ReSharper pops up a list of code generations it can perform on the class, and I select Generate Constructor, and it does the magic for me.

imageStep 4: Generate delegating members using ReSharper

Once the basic class and field code is in place, I turn again to my second-favorite Visual Studio extension.  ReSharper ships with a badass function called “Generate Delegating Members” that does exactly what the name implies – it can generate method and property calls that forward to a class member.

Using a dialog to allow me to select what members I want to create delegating calls for, and once I click Finish to run this little gem, the class is code complete:

1 public class ShellAction 2 { 3 IAction _item; 4 5 public ShellAction(IAction item) 6 { 7 _item = item; 8 } 9 10 public string Id 11 { 12 get { return _item.Id; } 13 set { _item.Id = value; } 14 } 15 16 public _TASK_ACTION_TYPE Type 17 { 18 get { return _item.Type; } 19 } 20 }

Rocket science it ain’t.  Still, I have about 39 more of these things to create.  Time to automate.  Heh.  Poetry.

Automated Workflow

In a nutshell, I want to use StudioShell to drive this four step process for each interface in the Task Scheduler type library.  So, the first thing I need is a list of every interface I can find in that type library.  This is pretty easy to do since PowerShell has access to the entire .NET underpinnings:

1 §> $t = [appdomain]::currentdomain.getassemblies()` 2 | where { $_.fullname -match 'tasksch' } ` 3 | %{ $_.gettypes() } ` 4 | where { ` 5 $_.isinterface -and $_.name -match 'i' ` 6 }


This pipeline looks complicated, but it’s pretty simple when you break it down.  In English, it reads as “From all loaded assemblies (line 1) whose fullname contains the string ‘tasksch’ (line 2) get the public types (line 3) that are interfaces and start with the letter ‘I’ (line 5).”  The goal is to get the public interfaces from the Task Scheduler type library.  The results are stored in the variable $t, which contains what we expect:

1 § >$t 2 3 IsPublic IsSerial Name 4 -------- -------- ---- 5 True False ITaskFolderCollection 6 True False ITaskFolder 7 True False IRegisteredTask 8 True False IRunningTask 9 ...

Cool.  Now we have the list of interfaces we need to wrap.  Let’s start hacking.

Step 1: Define the wrapper class in C# ( 40 times )

To keep things simple, I’m going to dump all of these generated classes into a single new code file.  I can always break them out later if I choose to.  I navigate StudioShell to the project I’m working in:

§ >cd DTE:\solution\projects\scheduledtasks

and create a new code file:

§ >new-item -type codefile -name test.cs

Then I navigate StudioShell into the code model for this new empty file:

§ >cd ./test.cs/codemodel

Time to bring the awesome.  I want a new public class for each interface in $t, and I want the class name to be the same of the interface with the initial 'I’ replaced with “Shell”:

§ >$t | foreach { new-item -type class ` -name ($_.name -replace '^I','Shell') ` -access public } Location: dte:\...\test.cs\codemodel File: test.cs Code Container: Available Operations: d+ < Kind Name ---- ---- vsCMElementClass ShellTaskFolderCollection vsCMElementClass ShellTaskFolder vsCMElementClass ShellRegisteredTask ...

Taking a quick peek at the code now in test.cs, it would seem I have my 40 classes declared:

public class ShellTaskFolderCollection { } public class ShellTaskFolder { } public class ShellRegisteredTask { } // ...

Step 2: Add a private field holding the COM reference ( 40 times )

Next item we need is the private COM reference in each class.  I can accomplish this using the standard new-item cmdlet to create private member variables inside of each class:

1 § >$t | foreach { ` 2 $c = $_.name -replace '^I','Shell'; ` 3 new-item -path $c ` 4 -name _item ` 5 -membertype $_.name ` 6 -type variable } 7 8 Location: dte:\...\test.cs\codemodel\ 9 File: test.cs 10 Code Container: Class ShellTaskFolderCollection 11 Available Operations: d+ < 12 13 14 Kind Name 15 ---- ---- 16 vsCMElementVariable _item 17 18 ...

For each interface in $t, I derive the class name using the same replace logic I used in step 1; I store this class name in the variable $c (line 2).  My current location is the code model for the test.cs file, so I can use this class name as a relative path to the new-item cmdlet to create class members.  In lines 3-6, I do just that to create a private field named “_item.”  The type of field _item is set to the name of the Task Scheduler interface being processed.

Huh, looking at the code file I can see I need to add a using statement to let the compiler resolve those TaskScheduler type library references; I’ll just do that by hand to get it done:

using TaskScheduler; public class ShellTaskFolderCollection { ITaskFolderCollection _item; } public class ShellTaskFolder { ITaskFolder _item; } public class ShellRegisteredTask { IRegisteredTask _item; } //...

So far so good.

Step 3: Initialize the COM reference in the class constructor ( 40 times )

Now things get interesting.  I need to add a constructor that initializes the value of the COM reference.  I used ReSharper to do this before, but there’s no way to automate ReSharper is there?  Um, yeah, there is.

All Visual Studio extensions (and Visual Studio itself) expose handles to their functionality in the form of Command objects.  StudioShell dutifully exposes these commands as part of the DTE: drive.  If we can find the command, we can pass it to the standard invoke-item cmdlet to make it run.

So let’s find the command.  Knowing that it’s a ReSharper command, and that it has something to do with generating constructors, I can narrow my search using intuition:

§ >ls dte:/commands | where {` $_.name -match 'resharper' ` -and ` $_.name -match 'constructor' } Location: dte:\commands Available Operations: d+ < Name ---- ReSharper.ReSharper_Constructor2FactoryMethodAction ReSharper_Generate_Constructor

Hmmm, I’m not a betting man, but I’d still put money that the ReSharper_Generate_Constructor command is the one I’m after.

Now, a little Visual Studio SDK sidebar – when a command executes, it uses the context of the current user activity.  In the case of this particular ReSharper command, it will only execute successfully if I’m currently in a code editor window working on a class.  Moreover, I need to move the cursor to the class I want the command to operate on.  Starting to sound like more trouble than its worth?

Fear not grasshopper – StudioShell has your back yet again!  One of the newer features of StudioShell is the ability to “navigate” to specific code model elements using the invoke-item cmdlet.  This is a recent feature that will be available in the upcoming 1.2 release.

So back on track, we want to invoke the ReSharper_Generate_Constructor command on each of our classes in test.cs.  So, that’s what we do:

1 § >ls -recurse | where {$_ -match 'class' } | foreach { 2 $_ |invoke-item; 3 invoke-item ` 4 dte:/commands/ReSharper_Generate_Constructor 5 }

imageLine 1 isolates the classes in test.cs; each class code item is first “invoked,” causing the containing document to activate and the cursor to move to the class code (line 2), then the ReSharper Generate Constructor command is invoked (lines 3-4). 

The result – the ReSharper Generate Constructor dialog is opened once for each class.  Unfortunately there is no way I know of to automatically select what members to initialize and then dismiss the dialog.  I’m sure there is, and I’d love to hear about it in the comments.  But right now I’d rather be more done than smart.  So, I hit [space]-[enter] 40 times like a schlep.

And the code looks right:

1 using TaskScheduler; 2 3 public class ShellTaskFolderCollection 4 { 5 ITaskFolderCollection _item; 6 7 public ShellTaskFolderCollection(ITaskFolderCollection item) 8 { 9 _item = item; 10 } 11 } 12 13 public class ShellTaskFolder 14 { 15 ITaskFolder _item; 16 17 public ShellTaskFolder(ITaskFolder item) 18 { 19 _item = item; 20 } 21 } 22 23 public class ShellRegisteredTask 24 { 25 IRegisteredTask _item; 26 27 public ShellRegisteredTask(IRegisteredTask item) 28 { 29 _item = item; 30 } 31 } 32 33 // ...

Step 4: Generate delegating members using ReSharper ( 40 times )

One last step and I’m done.  I need to run the ReSharper Generate Delegating Members command on each class.  This isn’t that different from what I had to do in step 3, and since I’ve taken the mystique out of automating ReSharper I know exactly what to do.

First, find the command.  I’ll use the same intuition that worked for me in step 3:

1 § >ls dte:/commands | where { ` 2 $_.name -match 'resharper' ` 3 -and ` 4 $_.name -match 'delegat' } 5 6 7 Location: PSDTE::dte:\commands 8 Available Operations: d+ < 9 10 Name 11 ---- 12 ReSharper_Generate_Delegating

Well lookey what we have here…  Hello there ReSharper_Generate_Delegating command!  Prepare to be automated!

This command works like the ReSharper_Generate_Constructor command – input focus has to be in a class code element in a text editor window.  But after solving step 3 this is no issue.  In fact, the solution for step 4 looks almost identical to step 3:

1 § >ls -recurse | where {$_ -match 'class' } | foreach { 2 $_ |invoke-item; 3 invoke-item ` 4 dte:/commands/ReSharper_Generate_Delegating 5 }

The only thing I had to change was the name of the command.

Again I get about 40 dialogs, one for each class, asking me what fields I want to create delegating members for, and as before I just type [space]-[enter] until the UIs disappear.

The code looks accurate:

1 using TaskScheduler; 2 3 public class ShellTaskFolderCollection 4 { 5 ITaskFolderCollection _item; 6 7 public ShellTaskFolderCollection(ITaskFolderCollection item) 8 { 9 _item = item; 10 } 11 12 public IEnumerator GetEnumerator() 13 { 14 return _item.GetEnumerator(); 15 } 16 17 public int Count 18 { 19 get { return _item.Count; } 20 } 21 22 public ITaskFolder this[object index] 23 { 24 get { return _item[index]; } 25 } 26 } 27 28 public class ShellTaskFolder 29 { 30 ITaskFolder _item; 31 32 public ShellTaskFolder(ITaskFolder item) 33 { 34 _item = item; 35 } 36 37 public ITaskFolder GetFolder(string Path) 38 { 39 return _item.GetFolder(Path); 40 } 41 42 public ITaskFolderCollection GetFolders(int flags) 43 { 44 return _item.GetFolders(flags); 45 } 46 47 // ... 48 } 49 50 public class ShellRegisteredTask 51 { 52 IRegisteredTask _item; 53 54 public ShellRegisteredTask(IRegisteredTask item) 55 { 56 _item = item; 57 } 58 59 public IRunningTask Run(object @params) 60 { 61 return _item.Run(@params); 62 } 63 64 public IRunningTask RunEx(object @params, int flags, int sessionID, string user) 65 { 66 return _item.RunEx(@params, flags, sessionID, user); 67 } 68 69 //... 70 } 71 72 // ...

 

… and it even builds (read: SHIP IT!!).  2500+ lines of monkey code created in 5 lines of powershell, all in all about 10 minutes of exploring. 

Hindsight

This little demo show some pretty powerful automation techniques – honestly I find the notion of automating other automation tools compelling.  A few things I’d like you to keep in mind as you explore your own automations:

  1. I’m automating my workflow, not my coding.  That is, my focus is on replicating the activities I do manually in Visual Studio to yield the code I want in the end.  In this case that means driving another automation tool.  I’m not looking for fancy or even “best practice” with this, I’m looking for done. 
  2. I’m not plotting a general solution I plan to reuse.  Sure I’ll employ some of the techniques again, but it’s actually easier (for me at least) to treat each new workflow as a new automation opportunity.  Moreover, trying to generalize this solution now would be a waste of time, since experience tells me that the next situation will be just different enough to force me to change my approach.
  3. I don’t focus on automation alone – that is, when I encounter a simple snag – such as the missing “using TaskScheduler;” statement in the code file, I just fix it by hand and move on instead of revisiting my automation technique to make it work perfectly.
  4. I’m automating something I’ve done manually several times.  So I have clear expectations of what needs doing and the path to follow.  My rule of thumb is not to automate until I can recite the steps I’ve taken before my memory.

Enjoy!