Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Visual Studio extension to update project properties

0.00/5 (No votes)
4 Aug 2015 1  
Plugin for Visual Studio 2012 that will batch-update the properties of all projects in a solution.

Introduction

This is a simple little plugin (.vsix) for Visual Studio 2012 that will batch-update the properties of all projects in a solution and is a useful introduction to writing extensions for Visual Studio.

Contents

  1. Background
  2. Using the Demo
  3. Using the Code
  4. UpdateProjectProperties.vsct
  5. UpdateProjectPropertiesPackage
    1. The Initialize Method
    2. The AdviseSolutionEvents Method
    3. The MenuItemCallback Method
  6. fUpdateSettings
    1. The SetProperty Method
  7. Points of Interest
  8. History

Background

Ever get frustrated when you make changes to a solution and have to go through each project one-by-one to change the version number, or you created an app for a client and have to change the company name & copyright info for each project one-by-one? I certainly do, it's just a tedious waste of time that needlessly eats up time before you can click 'Build'.

Using the Demo

To install the demo unzip the demo and open a command-prompt in the C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE folder. Now type: vsixinstaller "full path to vsix file". This will install the extension into Visual Studio (If Visual Studio is open while you install you may have to restart it to use the extension).

The extension creates a button in the Solution Explorer's toolbar:

When you click the button you'll get a simple screen where you can enter property values and set them: just enter a value and click 'Set' next to the value to apply it to all the projects in the solution.

Using the code

Before you can create an extension for Visual Studio you have to install the Visual Studio SDK. This will add a 'Visual Studio Package' template under Extensibility when you create a new project. Visual Studio creates a whole bunch of files when you run the Visual Studio Package Wizard. There are only 2 that we're really insterested in: the UpdateProjectProperties.vsct file and the UpdateProjectPropertiesPackage.cs file. Apart from customizing these two files all I needed to do was add a form (fUpdateSettings.cs) to provide a UI and update the selected properties.

UpdateProjectProperties.vsct

Command-table files (.vsct) describes the set of commands that are contained in a VSPackage. In our case we need to add a button with an icon to the Solution Explorer's toolbar.

First we create a menu group for the button and tell Visual Studio that the group should be added to the Solution Explorer's toolbar:

<group guid="guidUpdateProjectPropertiesCmdSet" id="SolutionToolbarGroup" priority="0xF000">
  <parent guid="guidSHLMainMenu" id="IDM_VS_TOOL_PROJWIN">
</parent>

The Parent element is what tells VS where to add the group, IDM_VS_TOOL_PROJWIN is the constant for the Solution Explorer's toolbar.

Next we define an image for the button:

<!--The bitmaps section is used to define the bitmaps that are used for the commands.-->
<Bitmaps>
  <!--  The bitmap id is defined in a way that is a little bit different from the others: 
        the declaration starts with a guid for the bitmap strip, then there is the resource id of the 
        bitmap strip containing the bitmaps and then there are the numeric ids of the elements used 
        inside a button definition. An important aspect of this declaration is that the element id 
        must be the actual index (1-based) of the bitmap inside the bitmap strip. -->
  <Bitmap guid="guidImages" href="Resources\drawing4.png" usedList="bmpPic1"/>
</Bitmaps>

The href points to an image file included in the project as an embedded resource and usedList points to a a symbol defined in the <Symbols> section of the file.

Next we define the actual button:

<Buttons>
  <!--To define a menu group you have to specify its ID, the parent menu and its display priority. 
	  The command is visible and enabled by default. If you need to change the visibility, status, etc, you can use
	  the CommandFlag node.
	  You can add more than one CommandFlag node e.g.:
		  <CommandFlag>DefaultInvisible</CommandFlag>

We define the button and tell Visual Studio that it's parent is the menu group we defined earlier, what image to use for the button and what the text of the button should be.

Finally we put it all together in the <Symbols> section of the file which defines the Guids and IDs that are used by the other elements in the vsct file:

<Symbols>
	<!-- This is the package guid. -->
	<GuidSymbol name="guidUpdateProjectPropertiesPkg" value="{d5729cc0-a507-4be2-8cb2-48001eec2e43}" />
	<!-- This is the guid used to group the menu commands together -->
	<GuidSymbol name="guidUpdateProjectPropertiesCmdSet" value="{76f8f297-6941-406e-aa6f-6e4eb84e6881}">
	  <IDSymbol name="SolutionToolbarGroup" value="0x0190" />
	  <IDSymbol name="cmdUpdateProjectProperties" value="0x0100" />
	</GuidSymbol>

Note that in the bitmap section where we list bmpPic1 the value is set to 1. This tells Visual Studio which image in the bitmap strip to use. Each image is 16x16 pixels and this value is the index (1-based) of the image to use. In this project the bitmap strip only has 1 image so we use 1 as the index.

UpdateProjectPropertiesPackage

This class is the core of any extension. The Visual Studio Package Wizard creates the basics of this file (always named [ProjectName]Package.cs) which you then need to customize to implement your extension. The first thing we had to do for this extension is make the class inherit the IVsSolutionEvents3 interface so that we can listen for the OnAfterOpenSolution and OnBeforeCloseSolution events to know when to enable or disable the button. To tell Visual Studio to notify our class of solution events we will call the AdviseSolutionEvents method of the IVsSolution object, more about this later.  The interface has quite a few methods but all we need to do in the ones we're not interested in is ensure that we return VSConstants.S_OK to tell VS that we handled the event:

public int OnBeforeOpeningChildren(IVsHierarchy pHierarchy)
{
    return VSConstants.S_OK;
}

In the 2 mehods we need to enable/disable the button (OnAfterOpenSolution and OnBeforeCloseSolution) we just set the button's enabled property:

/// <summary>
/// Enable the button when a solution has been opened.
/// </summary>
/// <param name="pUnkReserved"></param>
/// <param name="fNewSolution"></param>
/// <returns></returns>
public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
{
    if (_menuItem != null && _vsSolution != null)
    {
	_menuItem.Enabled = true;
    }
    return VSConstants.S_OK;
}

The Initialize Method:

The Initialize() method is called by Visual Studio to initialize our extension. Anything you need to do to configure your extension that requires access to the Visual Studio api must be done in this method. The constructor of your extension may be called by Visual Studio before the api becomes available so nothing in the constructor can depend on any Visual Studio objects or services.

/// <summary>
/// Initialization of the package; this method is called right after the package is sited, so this is the place
/// where you can put all the initialization code that rely on services provided by VisualStudio.
/// </summary>
protected override void Initialize()
{
    base.Initialize();

    // Add our command handlers for menu (commands must exist in the .vsct file)
    OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
    if ( null != mcs )
    {
	// Create the command for the menu item.
	CommandID menuCommandID = new CommandID(
                                                GuidList.guidUpdateProjectPropertiesCmdSet, 
                                                (int)PkgCmdIDList.cmdUpdateProjectProperties
                                               );
	_menuItem = new OleMenuCommand(MenuItemCallback, menuCommandID);
	_menuItem.Enabled = false;
	mcs.AddCommand(_menuItem);

	_dte = GetService(typeof(SDTE)) as DTE2;
	// Hook the class up to receive solution event so we can enable/disable the button.
	AdviseSolutionEvents();
    }
}

Our implementation of the method is quite simple:

  1. We get a reference to the OleMenuCommandService so we can add our command to it.
  2. We create a CommandID (menuCommandID) which references the button we defined in the .vsct file using the menu group's Guid (GuidList.guidUpdateProjectPropertiesCmdSet) and the id of the button (PkgCmdIDList.cmdUpdateProjectProperties).
  3. We create an OleMenuCommand to represent our button. In the constructor we pass the method to call when the button is clicked (MenuItemCallback) and the CommandID (menuCommandID) we just created for the button.
  4. We add our OleMenuCommand the the OleMenuCommandService.
  5. We call the method which will enroll our class for notification of Solution events (AdviseSolutionEvents).

The AdviseSolutionEvents Method:

This method enrolls our class for notification of solution events.

/// <summary>
/// Subscribes to solution events.
/// </summary>
private void AdviseSolutionEvents()
{
    UnadviseSolutionEvents();

    _vsSolution = this.GetService(typeof(SVsSolution)) as IVsSolution;

    if (_vsSolution != null)
    {
	_vsSolution.AdviseSolutionEvents(this, out _hSolutionEvents);
    }
}
  1. First we call the UnadviseSolutionEvents method to make sure we're only subscribed once. UnadviseSolutionEvents is just the reverse of this method so there's no need to go into it in detail.
  2. We get a reference to the solution service (IVsSolution).
  3. We call the service's AdviseSolutionEvents method to hook up our class so that it will receive notification of solution events.

The MenuItemCallback Method:

This method is called by Visual Studio when our button is clicked and is pretty self-explanatory. All we're doing is calling a helper method to get a list of all the projects and passing this to the form we'll use do do the real work:

/// <summary>
/// This function is the callback used to execute a command when the a menu item is clicked.
/// See the Initialize method to see how the menu item is associated to this function using
/// the OleMenuCommandService service and the MenuCommand class.
/// </summary>
private void MenuItemCallback(object sender, EventArgs e)
{
    if (_vsSolution != null)
    {
	// Get the list of projects in the solution and open the form:
	using (fUpdateSettings frm = new fUpdateSettings(new List<Project>(GetProjects(_vsSolution))))
	{
	    frm.ShowDialog();
	}
    }
}

I won't go into the helper methods here, just have a look at the source code. All they basically do is go through the solution's project hierarchy and extract the list of projects.

fUpdateSettings

This form is where we allow the user to enter property values and set them. All the methods are pretty straightforward so I'll only go into the method we're using to update the project properties. The form's constructor takes a list of projects in the current solution that we retrieved in our UpdateProjectPropertiesPackage class before opening the form.

The SetProperty Method:

In this method we simply iterate through the list of projects and then through each property in each project and set the one we want to set:

/// <summary>
/// Sets the value of a property in all projects of open solution.
/// </summary>
/// <param name="propertyName">Name of the property to set.</param>
/// <param name="value">New value of the property.</param>
private void SetProperty(string propertyName, string value)
{
    try
    {
	if (_projects.Count > 0)
	{
	    //Go through each project in the Solution:
	    foreach (Project prj in _projects)
	    {
		//Check each property:
		foreach (Property prop in prj.Properties)
		{
		    //Set the property:
		    if (prop.Name == propertyName)
		    {
			prop.Value = value;
		    }
		}
	    }
	}
    }
    catch (Exception ex)
    {
	MessageBox.Show(string.Format("An error occurred trying to set the {0}.\r\n{1}", propertyName, ex.Message), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

 

Points of Interest

The only real catch with all of this is to make sure that the object you use to subscribe to solution events is not garbage-collected by Visual Studio before you need it. Visual Studio has a nasty tendency to dispose of things unless it is directly holding a reference to it. Others will tell you you can create an object to implement the IVsSolutionEvents3 interface and simply hold it in a class-level field and it will get notified of solution events, in practise I've found that Visual Studio will forget about event-subscribers even if they're held in a class-level field. This is why I implemented the IVsSolutionEvents3 interface directly in the package class (UpdateProjectPropertiesPackage).

History

  1. 2015-08-02: First Version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here