See the question and my original answer on StackOverflow

I don't know of a free Visual Studio extension that would do it, but here is a C# sample of an Addin that demonstrate how to hook into Visual Studio global remove & rename file management. It's based on the IVsTrackProjectDocumentsEvents2 interface.

You would have to extend the two OnQueryxxx methods to suit your needs.

using System;
using System.Diagnostics;
using EnvDTE;
using EnvDTE80;
using Extensibility;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

namespace MyAddin1
{
    public class Connect : IDTExtensibility2, IVsTrackProjectDocumentsEvents2
    {
        private uint _trackerCookie;

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

            // the Addin project needs assembly references to Microsoft.VisualStudio.Shell, Microsoft.VisualStudio.Shell.Interop && Microsoft.VisualStudio.OLE.Interop
            // any version should do
            ServiceProvider sp = new ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)_applicationObject);
            IVsTrackProjectDocuments2 tracker = (IVsTrackProjectDocuments2)sp.GetService(typeof(SVsTrackProjectDocuments));

            // ask VS to notify us of files & directories changes
            tracker.AdviseTrackProjectDocumentsEvents(this, out _trackerCookie);
        }

        public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
        {
            if (_trackerCookie != 0)
            {
                // we quit, tell VS not to notify us anymore
                ServiceProvider sp = new ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)_applicationObject);
                IVsTrackProjectDocuments2 tracker = (IVsTrackProjectDocuments2)sp.GetService(typeof(SVsTrackProjectDocuments));
                tracker.UnadviseTrackProjectDocumentsEvents(_trackerCookie);
                _trackerCookie = 0;
            }
        }

        public int OnQueryRenameFiles(IVsProject pProject, int cFiles, string[] rgszMkOldNames, string[] rgszMkNewNames, VSQUERYRENAMEFILEFLAGS[] rgFlags, VSQUERYRENAMEFILERESULTS[] pSummaryResult, VSQUERYRENAMEFILERESULTS[] rgResults)
        {
            Trace.WriteLine("OnQueryRenameFiles pProject:" + pProject + " old[0]:" + rgszMkOldNames[0] + " new[0]:" + rgszMkNewNames[0]);

            // TODO: implement this (I have assumed cFiles is 1 here)
            if (!NotRenameOk(old[0], new[0])
            {
                rgResults[0] = VSQUERYRENAMEFILERESULTS.VSQUERYRENAMEFILERESULTS_RenameNotOK; // nope, it's not ok
            }
            return 0;
        }

        public int OnQueryRemoveFiles(IVsProject pProject, int cFiles, string[] rgpszMkDocuments, VSQUERYREMOVEFILEFLAGS[] rgFlags, VSQUERYREMOVEFILERESULTS[] pSummaryResult, VSQUERYREMOVEFILERESULTS[] rgResults)
        {
            Trace.WriteLine("OnQueryRemoveFiles pProject:" + pProject + " file[0]:" + rgpszMkDocuments[0]);

            // TODO: needs to be implemented, use rgResults to tell VS if it's ok or not
            return 0;
        }

        // other IVsTrackProjectDocumentsEvents2 methods implementation omitted for brevity...