Scott Hanselman

Introducing Workspace Reloader - A Visual Studio AddIn to save your open files across project reloads

May 2, '12 Comments [27] Posted in Open Source | VS2010
Sponsored By

Works on my machineA while back my buddy Sam Saffron (from Stack Overflow and Mini Profiler) complained to me on Skype that he was finding it very irritating that every time he updated his project outside of Visual Studio he would be prompted to "Reload Project" and would lose all his open files because Visual Studio would close them.

This apparently is becoming kind of an issue at Stack Overflow. Since they use distributed source control and often have a dozen or more folks all coding inside the same project they are integrating all the time. They'll be deep into something, update their project to test it and all their open windows close.

It's a weird Visual Studio behavior that I've never understood. Visual Studio saves all your open files and window positions when you close the IDE and restores them when you open your solution. But when you open a project then right click and "Unload Project" you'll lose all your windows. I've reported it as a bug and it's also been voted up at User Voice, visited as a Question at StackOverflow, and a few folks have tweeted about it (The SO guys with their thumbs on the scale, no doubt) and been bugging some folks but then I got the idea to just fix it myself. It'd be a good chance to write my first Visual Studio Add-In, see if this is even possible, and fix an irritant at the same time.

DOWNLOAD: Workspace Reloader Visual Studio Add-in - "This package will reload the code files you had open when your project file was modified and unloaded then reloaded"

Warranty: To be clear this is the smallest of extensions. It only listens to two events and it's only 12k so you have no reason that I know of to be afraid of it. Plus, it works on my machine so you've got that going for you.

Creating a Visual Studio Extension

Developing Visual Studio Extensions requires some patience. It's gotten a lot better with Visual Studio 2010 but back in the 2003-2005 days it was really hairy. There's a number of different kinds of things you can extend. You can add menus, add tool bars, commands, new templates, new kinds of designers and visualizers, as well as use just the shell to create your own IDE.

I wanted to create an add-in with Zero UI. I had no need for buttons or menus, I just wanted to listen to events and act on them. I downloaded the Visual Studio 2010 SDK after reading this blog on extending Visual Studio 2010. Make sure you get the right version. I have Visual Studio 2010 SP1 so I needed the updated Visual Studio 2010 SP1 SDK.

File | New Project | Visual Studio Package

I made a new Visual Studio Package. This builds into a VSIX (which is just a ZIP file - isn't everything?). A VSIX has a manifest (which his just XML - isn't everything?) that you can edit in a GUI or as a text file.

I want my VSIX package to work on Visual Studio 11 Beta as well as Visual Studio 2010  so I added to the SupportedProducts node like this. VSIXs other than templates aren't supported in Express (I keep pushing, though):

<SupportedProducts>
<VisualStudio Version="10.0">
<Edition>Ultimate</Edition>
<Edition>Premium</Edition>
<Edition>Pro</Edition>
</VisualStudio>
<VisualStudio Version="11.0">
<Edition>Ultimate</Edition>
<Edition>Premium</Edition>
<Edition>Pro</Edition>
</VisualStudio>
</SupportedProducts>

I also setup the name, version and description in this file. 

I need to decide when my package is going to get loaded. You can add one or more ProvideAutoLoad attributes to a Package class from the VSConstants class. A number of blogs posts say you need to hard code a GUID like this, but they are mistaken. There are constants available.

[ProvideAutoLoad("{ADFC4E64-0397-11D1-9F4E-00A0C911004F}")]

I can have my package automatically load in situations like these:

  • NoSolution   
  • SolutionExists
  • SolutionHasMultipleProjects   
  • SolutionHasSingleProject
  • SolutionBuilding
  • SolutionExistsAndNotBuildingAndNotDebugging
  • SolutionOrProjectUpgrading
  • FullScreenMode

For my package, I need it loaded whenever a "Solution Exists," so I'll use this Constant (in lieu of a hard coded GUID):

[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string)]

Next, I wanted to listen to events from the Solution like the unloading and loading of Projects. I started with the IVsSolutionsEvents interface that includes OnBefore, OnAfter and OnQuery for basically everything. Elisha has a simple listener wrapper as an answer on StackOverflow that I modified.

The SolutionEventsListener uses the very useful Package.GetGlobalService to get hold of the solution.

IVsSolution solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution;
if (solution != null)
{
solution.AdviseSolutionEvents(this, out solutionEventsCookie);
}

We then sign up to hear about things that might happen to the Solution using the IVsSolutionEvents interfaces and making them look like friendly events.

public event Action OnAfterOpenProject;
public event Action OnQueryUnloadProject;

int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
{
OnAfterOpenProject();
return VSConstants.S_OK;
}

int IVsSolutionEvents.OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
{
OnQueryUnloadProject();
return VSConstants.S_OK;
}

I want to hear about things just before Unload happens and then act on them After projects Open. I'll save the Document Windows. There's an interface that manages Documents and Windows for the Shell called, confusingly enough IVsUIShellDocumentWindowMgr

I save the windows just before the unload and reopen them just after the project opens. Unfortunately these are COM interfaces so I had to pass in not an IStream but an OLE.IStream so while the ReopenDocumentWindows is easy below...

listener.OnQueryUnloadProject += () =>
{
comStream = SaveDocumentWindowPositions(winmgr);
};
listener.OnAfterOpenProject += () => {
int hr = winmgr.ReopenDocumentWindows(comStream);
comStream = null;
};

The SaveDocumentWindowPositions is more complex, but basically "make a memory stream, save the documents, and seek back to the beginning of the stream."

private IStream SaveDocumentWindowPositions(IVsUIShellDocumentWindowMgr windowsMgr)
{
if (windowsMgr == null)
{
return null;
}
IStream stream;
NativeMethods.CreateStreamOnHGlobal(IntPtr.Zero, true, out stream);
if (stream == null)
{
return null;
}
int hr = windowsMgr.SaveDocumentWindowPositions(0, stream);
if (hr != VSConstants.S_OK)
{
return null;
}

// Move to the beginning of the stream with all this COM fake number crap
LARGE_INTEGER l = new LARGE_INTEGER();
ULARGE_INTEGER[] ul = new ULARGE_INTEGER[1];
ul[0] = new ULARGE_INTEGER();
l.QuadPart = 0;
//Seek to the beginning of the stream
stream.Seek(l, 0, ul);
return stream;
}

If this does it's job you'll never know it's there. You can test it by installing Workspace Reloader, opening a project and opening a few code files. Now, edit the CSProj as a text file (maybe add a space somewhere) and save it. Visual Studio should prompt you to Reload the Project. Workspace Reloader should keep your files and windows open.

I hope this helps a few people. The source is here.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Wednesday, May 02, 2012 6:45:12 AM UTC
Big thanks from me and the rest of the Stack Overflow team!!!
Sam
Wednesday, May 02, 2012 7:00:34 AM UTC
I just blogged about a week ago about the ISolutionEvents interface including a reference to Elisha's post!
Wednesday, May 02, 2012 7:06:52 AM UTC
Manuel - Excellent post! Great minds think alike!
Scott Hanselman
Wednesday, May 02, 2012 7:48:45 AM UTC
Thankyou Scott! For this beautiful Visual Studio Extension.
Wednesday, May 02, 2012 8:11:16 AM UTC
Dams you Mr Hanselman - you make things look too easy :)
Wednesday, May 02, 2012 8:16:24 AM UTC
Thank you! I'll try it now :p
Wednesday, May 02, 2012 12:39:00 PM UTC
Thanks Scott.

Based on my lacking knowledge, I am under impression that comStream = null does not do the trick and a Marshal.ReleaseComObject() is required. Is that true?
Wednesday, May 02, 2012 12:40:32 PM UTC
Also I do not see a release of the stream before comStream = null; is that not necessary?
Wednesday, May 02, 2012 12:58:56 PM UTC
You are awesome for writing this! Thank you! :D
Wednesday, May 02, 2012 1:11:02 PM UTC
It appears that VS2011 beta does this natively. I've been running it for awhile now and my open documents (including pinned tabs) are always maintained after a project reload.
Wednesday, May 02, 2012 2:26:14 PM UTC
Hey Scott -- the Visual Studio Gallery is all kinds of busted right now. Clicking almost anywhere just returns the following error, and trying to click on the feedback link results in an endless loop of redirection between social.msdn.com and Live Login:

If you see this page, it is because an error occurred in the system while trying to process your request. We apologize for the inconvenience. This error has been reported to our team for analysis. Error Log Reference #6e83f298-a4c0-45ad-9258-7fbc87b9cfa1

Wednesday, May 02, 2012 2:58:58 PM UTC
Excellent! Now that is a quality plugin.
Wednesday, May 02, 2012 5:51:12 PM UTC
Losing my open project files when reloading the project file has been irritating for way too long. Thanks for taking the time to figure out how to prevent the annoyance with this great VS AddIn.
Wednesday, May 02, 2012 6:58:28 PM UTC
Ali Kheyrollahi - I was told that setting it the ReleaseComObject will be called by the GC.

Joshua - No, I don't think so. I just confirmed that here. I'm talking about a reload while the project is open. Open a project, edit the csproj and return to VS. VS closes the open documents on reload.

Aaron - I will tell them now.
Wednesday, May 02, 2012 8:48:57 PM UTC
Awesome nice work, will save everyone a lot of time I bet.

Now we just need a plug in to fix the "Cannot create/shadow copy 'VariousFileName' when that file already exists." error. LOL
Jeff Stan
Wednesday, May 02, 2012 9:19:59 PM UTC
If you code in a git shop and lose your files after every rebase or, gross, have to close/re-open VS each time.....this is the greatest thing.
Wednesday, May 02, 2012 9:27:23 PM UTC
Jeff - what issue Is that?
Scott Hanselman
Thursday, May 03, 2012 12:41:16 PM UTC
You inspired me to rewrite my old "addin" to a shiny new "extension". It was not that difficult afteral (since I already had replaced my "solution events" with the technique described above).
Thursday, May 03, 2012 1:08:17 PM UTC
It is something that only happens occasionally and really hard to detail because it’s not like it is something that happens because of a certain pattern (or at least one that I found). I only said that half joking though and didn’t mean to take away from the cool plugin you created.

Just so I don’t leave you hanging, you can check out this:
http://stackoverflow.com/questions/1007200/asp-net-cannot-create-shadow-copy
Jeff Stan
Thursday, May 03, 2012 5:27:22 PM UTC
Works like a dream.

I was complaining about this very thing to a co-worker a few weeks ago and he told me I should write an extension. Yay for procrastination!
Friday, May 04, 2012 1:20:13 PM UTC
That's a pretty cool extension!!

I've got a sort-of related question: every so often I'll try to open a solution and VS 2010 will hang while loading Intellisense so that I have to kill it via Task Manager. The next attempt to open the same solution results in a message telling that 'Document load is being skipped' and then everything is hunky dory. Is there a way to force VS to always skip the document load phase ie: always open the solution without any opened documents?
Friday, May 04, 2012 5:50:42 PM UTC
Scott - Great post, even though I don't do any C#, I found it interesting to see your explanation of the code. I just have a question about the code after:
// Move to the beginning of the stream with all this COM fake number crap


Is that one of those tricks you just know from being an experienced Windows Developer? It seems like some esoteric code.
Friday, May 04, 2012 7:16:58 PM UTC
Great extension Scott. I noticed the source is on GitHub. Question for you since you are a Microsoft guy. Why choose GitHub over Microsoft's CodePlex? :)

Great icon for the extension too!
Monday, July 09, 2012 3:35:45 PM UTC
Is there some sort of event/hook that happens before showing "File Modification Detected" dialog ?
Sunday, September 30, 2012 11:54:07 AM UTC
Hi Scott,
First, thanks for this excellent post.
I need to create an extension with UI but I'm unable to figure it out. I want to integrate a project with windows forms. It is basically a code generator. There are some inputs in the UI and the generated code must be added as a .cs file into the current working project.
I created a visual studio add-in project and linked the windows form by calling it in Execute(). The build was successful, but when run and click the icon in the toolbar menu, nothing happens! It does'nt even reach the breakpoint placed at the beginning of Execute()!
Your guidance will be much appreciated.

Thanks!
Sanjay
Saturday, December 22, 2012 7:26:00 AM UTC
@Sanjay

I faced the same issue. Your form should be inside the addin project itself. When you try to open a form in a different project(within the same solution), the form doesn't load.

Kindly help!
Sanjay
Wednesday, March 06, 2013 6:24:01 PM UTC
Our big tutorial: Developing extension packages for Visual Studio 2005/2008/2010/2012 - http://www.viva64.com/en/b/0165/
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.