Run custom tool on each resx file when building

Visual Studio automatically creates a Resource.Designer.cs code file from a Resource.resx file. It does this when you save the resx file from within Visual Studio. This creates a problem if the resx file is changed outside of Visual Studio, or you don't want to add the *.Designer.cs files to version control.

You can update the Designer.cs file for one resx file by right clicking on it in Visual Studio, and selecting "Run custom tool". This calls the ResXFileCodeGenerator which generates the Designer.cs file. However, we want to do this for all resx files within the project, just before building.

Running something before building

This can be done by running a command before the build, by using a macro or by using an addin.

To run a command before the build, right click the project, select Properties, Build Events. Here you see the Pre-build event command line, in which you can enter a command to run before building the project.

To run a macro before the build, you can use the events described in the EnvironmentEvents macro. In the macro, you can run the custom tool on all resx files.

Creating an add-in

To create an addin, create a new addin project in Visual Studio. You can find this project type under Other Project Types, Extensibility. This will create some example code for your addin.

To hook into the build event, use the following code:

public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
	_applicationObject = (DTE2)application;
	_addInInstance = (AddIn)addInInst;

	EnvDTE.Events events = _applicationObject.Events;
	buildEvents = (EnvDTE.BuildEvents)events.BuildEvents;
	buildEvents.OnBuildBegin += OnBuildBegin;
}

protected void OnBuildBegin(vsBuildScope Scope, vsBuildAction Action)
{
	...
}

To obtain information on the solution and projects, use the _applicationObject object.
To run the custom tool, call the VSProjectItem.RunCustomTool() method. The VSProjectItem class exists in the VSLangProj assembly and namespace.

foreach (ProjectItem file in items)
{
	if (file.Object != null && file.Object is VSProjectItem)
	{
		var projectItem = (VSProjectItem)file.Object;
		if (projectItem.ProjectItem.Name.EndsWith(".resx"))
		{
			projectItem.RunCustomTool();
		}
	}
}

You can install your addin by copying the .AddIn and .dll files to C:\Users\YourUsername\Documents\Visual Studio 2010\Addins, or any other path displayed under Environment, Add-in/Macros Security in the Options dialog. Your addin will then appear in the dialog under Tools, Add-in Manager.

Code for add-in

using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using VSLangProj;

namespace MyProjectResourcesBuilder
{
    public class Connect : IDTExtensibility2
    {
        public Connect()
        { }

        public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
        {
            _applicationObject = (DTE2)application;
            _addInInstance = (AddIn)addInInst;

            EnvDTE.Events events = _applicationObject.Events;
            buildEvents = (EnvDTE.BuildEvents)events.BuildEvents;
            buildEvents.OnBuildBegin += OnBuildBegin;
        }

        protected void OnBuildBegin(vsBuildScope Scope, vsBuildAction Action)
        {
            var projects = (object[]) _applicationObject.ActiveSolutionProjects;
            foreach (Project project in projects)
            {
                if (project.Name == "MyProjectName")
                {
                    IterateProjectFiles((ProjectItems)project.ProjectItems);
                }
            }
        }

        private void IterateProjectFiles(ProjectItems items)
        {
            foreach (ProjectItem file in items)
            {
                if (file.Object != null && file.Object is VSProjectItem)
                {
                    var projectItem = (VSProjectItem)file.Object;
                    if (projectItem.ProjectItem.Name.EndsWith(".resx"))
                    {
                        projectItem.RunCustomTool();
                    }
                }
            }
        }

        public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
        {
            buildEvents.OnBuildBegin -= OnBuildBegin;
        }

        public void OnAddInsUpdate(ref Array custom)
        { }

        public void OnStartupComplete(ref Array custom)
        { }

        public void OnBeginShutdown(ref Array custom)
        { }
        
        private DTE2 _applicationObject;
        private AddIn _addInInstance;
        private BuildEvents buildEvents;
    }
}