[UPDATE] Creating Excel UDF’s in C#

UPDATE: See POC example project: http://davecra.com/2014/02/25/codeplex-loading-an-excel-udf-from-vsto/

One of the most common questions I get – and I am getting more often these days – is how to create an Excel User Defined Function (UDF) in Excel with the following criteria:

  1. Using Visual Studio and C# preferably from a Visual Studio Tools for Office (VSTO) add-in.
  2. Be able to access the Excel.Application object
  3. Have it complete register itself without administrative rights.
  4. Not use 3rd party libraries, XLL shims and/or VBA intermixed

The most common request is actually, how to get a VSTO add-in in Excel to register a UDF. Unfortunately, the answer is: you cannot. I welcome anyone who has figured this out – without using an XLL or VBA shim – to let me know how they did it from a VSTO project.

I have tried many different options via separate COM registration, a separate COM class in my project, and finally by asking this question directly to the Microsoft VSTO team. This seems to be a very popular topic on the web these days, so hopefully, they will take notice and build an interface for UDF’s in the next release of VSTO.

With that said, you can still get your VSTO add-in to load your UDF’s, albeit they must be in a separate DLL file. And in order to register on the system without Administrator rights, you will need a VSTO add-in to create an instance, connect to the class and then call the register function (a non-static register function) (see below).

As for gaining access to the Excel Application object – something you get with a VBA UDF – I find a lot of articles out there having users just creating a basic Windows DLL and registering it for Programmability. This is not really enough. To enjoy the full power of Excel in your UDF add-in, you need to register it as an add-in and to do this outside of VSTO means you need to create an COM Extensibility add-in (something gone in Visual Studio 2012).

Finally, the code below will demonstrate how to register the add-in completely in the HKEY_CURRENT_USER key of the registry so there is no need for Administrator rights to deploy the add-in. The requirement will be for the VSTO add-in to call the UDF register function.

Here is the code, with comments:

using System;
using Extensibility;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Excel = Microsoft.Office.Interop.Excel;
using System.Windows.Forms;
using System.Reflection;
using System.Threading;

namespace ExcelFunctions
{
    [GuidAttribute("FF4AF1AD-7E2A-4611-AA6F-47351FF46AFD")]
    public interface IFunctions
    {
        string RANDOMTABLE(int r, int c);
    }

    [GuidAttribute("30A29909-AF27-4814-9CBE-ED6A39A4B9A5"),
    ProgId("ExcelFunctions.Connect"),
    ClassInterface(ClassInterfaceType.AutoDual),
    ComDefaultInterface(typeof(IFunctions))]
    public class Connect : Object, Extensibility.IDTExtensibility2, IFunctions
    {
        static string NAME = "";
        /// <summary>
        /// Generates a random able in the row and cell just
        /// below the current cell - regardless of current
        /// contents
        /// </summary>
        /// <param name="r"></param>
        /// <param name="c"></param>
        /// <returns></returns>
        public string RANDOMTABLE(int r, int c)
        {
            // this gives s a reference to the exact cell with this function
            // what is importat to NOTE is that wihtout passing in a reference
            // to a Range object, I still have access to the full Excel
            // application Object Model becase we are registering this DLL
            // as an IDTExtensibility2 add-in. This interface, as you will see
            // below, provides us with a referene to the Application object
            // on the Connection...
            Excel.Range rng = (Excel.Range)Application.get_Caller(1);
            // now - this is important to note. Excel will not allow you to
            // manipluate other cells or change certain items while you are
            // evaluating a function as part of a claculation event. The
            // best way to describe this is that Excel is in "Edit Cell"
            // mode. Attempting to manipulate the cell or any other cell
            // will throw an exception - so, we spawn a thread and do the 
            // work - later...
            new Thread(() =>
            {
                // In this thread which will not execute until the current
                // calculation chain is complete, we will bild a table full
                // of random values.
                for (int rowCnt = rng.Row + 1; rowCnt <= (rng.Row + r); rowCnt++)
                {
                    for (int colCnt = rng.Column; colCnt < (rng.Column + c); colCnt++)
                    {
                        Excel.Range nextCell = ((Excel.Worksheet)rng.Parent).Cells[rowCnt, colCnt];
                        nextCell.Value2 =  new Random().Next(999).ToString();
                        Marshal.ReleaseComObject(nextCell); // clean
                    }
                }
                // important - release the range to prevent Excel hanging
                // around after the user closes it
                Marshal.FinalReleaseComObject(rng);
            }).Start();

            // simply retun a string to the active cell
            return "RANDOM TABLE";
        }

        #region IDTExtensibility2
        private static Excel.Application Application; // our ref to Excel
        private static object ThisAddIn;
        private static bool fVstoRegister = false;
        public Connect() { }

        /// <summary>
        /// We call this from VSTO so that we can get the DLL
        /// to register itself and load every time
        /// </summary>
        /// <returns></returns>
        public string Register() // exposed to VSTO
        {
            fVstoRegister = true;
            RegisterFunction(typeof(Connect));
            return NAME; // return the name of this instance
        }

        /// <summary>
        /// When we finally do connect and load in Excel we want to get the
        /// reference to the application, so that we can use the application
        /// instace in our UDF as needed
        /// </summary>
        /// <param name="application"></param>
        /// <param name="connectMode"></param>
        /// <param name="addInInst"></param>
        /// <param name="custom"></param>
        public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
        {
            // get a reference to the instance of the add-in
            Application = application as Excel.Application;
            ThisAddIn = addInInst;
        }

        /// <summary>
        /// When we disconnect - remove everything - clean up
        /// </summary>
        /// <param name="disconnectMode"></param>
        /// <param name="custom"></param>
        public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom) 
        {
            // clean up
            Marshal.ReleaseComObject(Application);
            Application = null;
            ThisAddIn = null;
            GC.Collect();
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
        // the following functions are required to be defined, but not needed
        public void OnAddInsUpdate(ref System.Array custom) {}
        public void OnStartupComplete(ref System.Array custom) {}
        public void OnBeginShutdown(ref System.Array custom) { }

        /// <summary>
        /// Registers the COM Automation Add-in in the CURRENT USER context
        /// and then registers it in all versions of Excel on the users system
        /// without the need of administrator permissions
        /// </summary>
        /// <param name="type"></param>
        [ComRegisterFunctionAttribute]
        public static void RegisterFunction(Type type)
        {
            string PATH = System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase.Replace("\\", "/");
            string ASSM = Assembly.GetExecutingAssembly().FullName;
            int startPos = ASSM.ToLower().IndexOf("version=") + "version=".Length;
            int len = ASSM.ToLower().IndexOf(",", startPos) - startPos;
            string VER = ASSM.Substring(startPos, len);
            string GUID = "{" + type.GUID.ToString().ToUpper() + "}";
            NAME = type.Namespace + "." + type.Name; // global
            string BASE = @"Classes\" + NAME;
            string CLSID = @"Classes\CLSID\" + GUID;

            // open the key
            RegistryKey CU = Registry.CurrentUser.OpenSubKey("Software", true);

            // is this version registred?
            RegistryKey key = CU.OpenSubKey(CLSID + @"\InprocServer32\" + VER);
            if (key == null)
            {
                // The version of this class currently being registered DOES NOT
                // exist in the registry - so we will now register it

                // BASE KEY
                // HKEY_CURRENT_USER\CLASSES\{NAME}
                key = CU.CreateSubKey(BASE);
                key.SetValue("", NAME);

                // HKEY_CURRENT_USER\CLASSES\{NAME}\CLSID}
                key = CU.CreateSubKey(BASE + @"\CLSID");
                key.SetValue("", GUID);

                // CLSID
                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}
                key = CU.CreateSubKey(CLSID);
                key.SetValue("", NAME);

                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}\Implemented Categories
                key = CU.CreateSubKey(CLSID + @"\Implemented Categories").CreateSubKey("{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}");

                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}\InProcServer32
                key = CU.CreateSubKey(CLSID + @"\InprocServer32");
                key.SetValue("", @"c:\Windows\SysWow64\mscoree.dll");
                key.SetValue("ThreadingModel", "Both");
                key.SetValue("Class", NAME);
                key.SetValue("CodeBase", PATH);
                key.SetValue("Assembly", ASSM);
                key.SetValue("RuntimeVersion", "v4.0.30319");

                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}\InProcServer32\{VERSION}
                key = CU.CreateSubKey(CLSID + @"\InprocServer32\" + VER);
                key.SetValue("Class", NAME);
                key.SetValue("CodeBase", PATH);
                key.SetValue("Assembly", ASSM);
                key.SetValue("RuntimeVersion", "v4.0.30319");

                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}\ProgId
                key = CU.CreateSubKey(CLSID + @"\ProgId");
                key.SetValue("", NAME);

                // HKEY_CURRENT_USER\CLASSES\CLSID\{GUID}\Progammable
                key = CU.CreateSubKey(CLSID + @"\Programmable");

                // now register the addin in the addins sub keys for each version of Office
                foreach (string keyName in Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Office\").GetSubKeyNames())
                {
                    if (IsVersionNum(keyName))
                    {
                        // and now set it to a loaded state by adding it to the options key
                        key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Office\" + keyName + @"\Excel\Options", true);
                        if (key != null)
                        {
                            // loop though all the names and count how many have the name OPEN#
                            int openCnt = 0;
                            foreach (string optionKeyName in key.GetValueNames())
                                if (optionKeyName.StartsWith("OPEN"))
                                    openCnt++;
                            // Add the open key
                            key.SetValue("OPEN" + (openCnt == 0 ? "" : openCnt.ToString()), "/A " + NAME);
                        }
                    }
                }
                if (!fVstoRegister)
                {
                    // all done - this just helps to assure REGASM is complete
                    // this is not needed, but is useful for troubleshooting
                    MessageBox.Show("Registered " + NAME + ".");
                }
            }
        }

        /// <summary>
        /// Unregisters the add-in, by removing all the keys
        /// </summary>
        /// <param name="type"></param>
        [ComUnregisterFunctionAttribute]
        public static void UnregisterFunction(Type type)
        {
            string GUID = "{" + type.GUID.ToString().ToUpper() + "}";
            string NAME = type.Namespace + "." + type.Name;
            string BASE = @"Classes\" + NAME;
            string CLSID = @"Classes\CLSID\" + GUID;
            // open the key
            RegistryKey CU = Registry.CurrentUser.OpenSubKey("Software", true);
            // DELETE BASE KEY
            // HKEY_CURRENT_USER\CLASSES\{NAME}
            try
            {
                CU.DeleteSubKeyTree(BASE);
            }
            catch { }
            // HKEY_CURRENT_USER\CLASSES\{NAME}\CLSID}
            try
            {
                CU.DeleteSubKeyTree(CLSID);
            }
            catch { }
            // now un-register the addin in the addins sub keys for Office
            // here we just make sure to remove it from allversions of Office
            foreach(string keyName in Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Office\").GetSubKeyNames())
            {
                if(IsVersionNum(keyName))
                {
                    RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Office\" + keyName + @"\Excel\Add-in Manager", true);
                    if (key != null)
                    {
                        try
                        {
                            key.DeleteValue(NAME);
                        }
                        catch { }
                    }
                    key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Office\" + keyName + @"\Excel\Options", true);
                    if (key == null)
                        continue;
                    foreach (string valueName in key.GetValueNames())
                    {
                        if (valueName.StartsWith("OPEN"))
                        {
                            if (key.GetValue(valueName).ToString().Contains(NAME))
                            {
                                try
                                {
                                    key.DeleteValue(valueName);
                                }
                                catch { }
                            }
                        }
                    }
                }
            }
            MessageBox.Show("Unregistered " + NAME + "!");
        }

        /// <summary>
        /// HELPER FUNCTION
        /// This assists is in determining if the subkey string we are passed
        /// is of the type like:
        ///     8.0
        ///     11.0
        ///     14.0
        ///     15.0
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static bool IsVersionNum(string s)
        {
            int idx = s.IndexOf(".");
            if (idx >= 0 && s.EndsWith("0") && int.Parse(s.Substring(0, idx)) > 0)
                return true;
            else
                return false;
        }
        #endregion
    }
}

Finally, from your VSTO add-in, you will want to write code like this:

ExcelFunctions.Connect functionsAddinRef = null;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    // get a new reference to out UDF Project
    functionsAddinRef = new ExcelFunctions.Connect();
    // first register it
    string name = functionsAddinRef.Register();
    // then install it
    Application.AddIns.Add(name).Installed = true;
}

When your VSTO add-in loads, your UDF will Register and Load as well. This this example, once loaded in Excel, simply enter the following formula in an empty cell and press enter:

=RANDOMTABLE(4,4)

You will get a table like this:

image

39 thoughts on “[UPDATE] Creating Excel UDF’s in C#”

  1. Hi, I was wondering if it possible to post a sample project. I’m having a little problem following the instructions.

    Thanks

      1. This would be awesome! I cannot seem to get this to work. Having a sample project should show me what I am doing wrong.

  2. I think you need to add a workbook before, as explained in here
    http://support.microsoft.com/kb/280290/en-us

    Otherwise you get :
    “Run-time error ‘1004’: Add method of addins class failed.”

    That said, there are still some other problems as RANDOMTABLE is not available as a UDF after that.

    I can not believe that after 10 years of engineering one has to dig through a pile of hacks and tricks to simply program office. this is a utter and absolute shame.

    1. I have expressed your sentiment to those with ears for these types of feedback and at this point we can only hope that a future version of VSTO will support UDF’s directly.

      As for the problem you are seeing with the KB Article – I will look into this and see what need to be done. Generally, I have never had the issue before, but I have seen some projects (added to XLSTART) that essentially kill the open Workbook before add-ins and such are loaded. So this may be the problem.

    2. I’m having the same issue. I get it to register fine but cannot use the UDF. When I look inside the excel Addins, I see the addin under inactive addins. When I go to activate it, it’s check box is already checked. No matter what I do I cannot get the addin to show as active.

  3. I understand the functions need to be in a separate project and referenced. I created this new project with the functions as a standard class library. Is this correct?

  4. I was wondering, and let me not sugar coat it here, could you possibly have formatted this in worse colors? 🙂

  5. Hi,

    I see that in this post in this region
    // In this thread which will not execute until the current
    // calculation chain is complete, we will bild a table full
    // of random values.

    where we populate our cells one-by-one underneath our cell that is calling the function. I was wondering if there is a way to do this that allows us to access an excel range and output the array directly into it (ideally increasing run time).

    Thanks!!!

    1. Excel.Range rangeCaller = (Excel.Range)Application.get_Caller();
      Excel.Worksheet workSheet = ((Excel.Worksheet)_rangeCaller.Parent);
      workSheet.get_Range(“A1:B2”).FormulaArray = youtFormulaHere;

  6. Hi, I’m getting the error “Add method of addins class failed” was there ever a solution to this issue?

  7. I’m developing an Excel Ribbon AddIn. Are there any special considerations for working in a UDF with my Ribbon project?

    1. If your Ribbon Project is a VSTO project – then you only need to make sure that the UDF is created in a separate COM project. The reason is that COM is the only way to expose the attributes needed for Excel to find the object with your UDF’s. VSTO does not directly provide for this mechanism. See how I implemented it here: https://excelvstoudfexample.codeplex.com/

  8. Hello – I am using VS2017. The code compiles fine. I see the addins in the Addin area and the COM area. I cannot see the functions however – typing =ADD for instance doesn’t show me the udf. I have also looking through the functions menu – nothing in there.

    What else can i do? I am getting this “warning” – is this causing the issue?

    Severity Code Description Project File Line Suppression State
    Warning CS1762 A reference was created to embedded interop assembly ‘Microsoft.Office.Tools.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ because of an indirect reference to that assembly created by assembly ‘Microsoft.Office.Tools.Excel, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’. Consider changing the ‘Embed Interop Types’ property on either assembly. ExcelAddin C:\Users\mstamper\Downloads\ExcelVSTOUDFExample2\sourceCode\sourceCode\ExcelFunctions\ExcelAddin\CSC 1 Active

    1. The UDF created using this method does not appear in any list or menu and will not auto-fill. This is because the UDF is not registered in the function list. The only way to get this to work is to create a VBA Shim or an XLL. However, it should still run when you enter it in manually and calculate. If your code is not running then there might be something wrong with the function registration. The warning you are getting is not related, as this is common for non-embedded interop types.

      1. Hi – thanks for taking the time to reply. I am getting the ExcelFunctions.connect successfully registers when I build the solution. I have entered each of the commands in your sample code into an empty spreadsheet but I get “#NAME?” in the resulting cell for each command. I have not changed anything in your code except for the GUID values in the Connect.cs values.

        I’m using Excel 2013 64bit and Windows 10 Enterprise 64-bit. I have set the project for “AnyCPU” and the only warning I am getting is what I posted previously, and as you suggest it is probably not contributing to the functions failing to register. I realize you may not have access to my versions of Office and Windows which may make it difficult to help me debug the problem. Any advice you may have would be appreciated!

      2. I ran into the same problem. I launched VS2017 as Administrator so the Project.dll would register and then the add-ins loaded and everything was there, but when I tried to execute the functions like =RANDOMTABLE(4,4), I was getting a #NAME?. However, I closed VS2017 and launched Excel regularly (non-admin) and then everything worked. Just to verify, can you try that if you have not already:

        1) Launch Excel normally
        2) Verify the ExcelFunction.Connect is still in the Add-ins list
        3) In an empty cell, type =RANDOMTABLE(4,4)

        Does that work for you?

      3. Also… I think there is something in Excel that prevents the code from running when Excel is launched as Administrator. Not sure why that is, but definitely running outside of elevated seemed to resolve the issue for me.

  9. Hi, I have the same error as TokyoMike. I tried just like you described, I have the add ins registered and loaded in the excel add ins list but still I can’t get the function work.

    1. If you add a Message Box in the add-in load, do you get it to appear when the add-in loads? If not, it is not registered properly. Something is missing in the registry, blocking you from making the changes, etc. It is also key to run it at least one time as Administrator from Visual Studio to get it to properly register.

      Also, it might be an error in the code somewhere too. Did you modify the code any? Try pulling down a fresh example, make no changes and then compile it and run it (one time as administrator). If that still does not work can you create a basic VSTO or COM add-in and see if those load and run fine on the system. If those don’t there is something fundamentally wrong on the system. If a simple COM/VSTO add-in is loading fine, just this sample/example fails, then I am at a loss as to what might be the root of the issue. I would suggest try from another machine maybe.

      Let me know.

  10. Workbook open is important before adding add-in, so I recommend to add following line before
    “Application.AddIns.Add(name).Installed = true;” because my excel 2019 is starting without any opened workbook:

    ****
    Application.Workbooks.Add(System.Type.Missing);
    ****

    Second thing is the randomtable, which gives me sometimes an exception. I prevent this not using the thread, so I use instead
    *****
    Excel.Range rng = (Excel.Range)Application.get_Caller(1);

    Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
    {
    //your code here
    }));
    *****

  11. Workbook open is important before adding add-in, so I recommend to add following line before
    “Application.AddIns.Add(name).Installed = true;” because my excel 2019 is starting without any opened workbook:

    ****
    Application.Workbooks.Add(System.Type.Missing);
    ****

    Second thing is the randomtable, which gives me sometimes an exception. I prevent this not using the thread, so I use instead
    *****
    Excel.Range rng = (Excel.Range)Application.get_Caller(1);

    Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
    {
    //your code here
    }));
    *****

  12. I found out, if you change the assembly version of ExcelFunctions, then it’s not working, because missing registry entry “InprocServer32” which is set to the correct version. Registering is not fired because “fFound” is true…

    I added to ThisAddIn.cs

    if (!fFound)
    {
    //your code
    } else {
    //note I have 64bit Windows, and compiled it to x86, therefore I need the Wow6432Node
    // is this version registered?
    using (var view32 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser,
    RegistryView.Registry32))
    {
    using (var clsid32 = view32.OpenSubKey(@”Software\Classes\CLSID\”, false))
    {
    // actually accessing Wow6432Node
    RegistryKey key = clsid32.OpenSubKey(“{” + GUID + “}” + @”\InprocServer32\” + functionsAddinRef.MyVersion());
    if (key == null)
    {
    functionsAddinRef.Register();
    }
    }
    }
    }

    and to Connect.cs
    public string MyVersion()
    {
    string ASSM = Assembly.GetExecutingAssembly().FullName;
    int startPos = ASSM.ToLower().IndexOf(“version=”) + “version=”.Length;
    int len = ASSM.ToLower().IndexOf(“,”, startPos) – startPos;
    return ASSM.Substring(startPos, len);
    }

    1. Thanks for the follow up and findings. At the time I wrote this year’s ago, 32 bit was all there was. So this makes sense that code like this is needed. I will work on updating this post with that information.

      Also, have you looked at the new Custom Functions capability with the Javascript API? In many ways it is easier to implement and is supported on several platforms not just Windows.

  13. Thank you for your great blog. So I was able to create an add-in with udf…

    Yes I looked for the javascript api, but I have restrictions in the company, so this is not an option for me. And on the other hand it seems it will take a lot of reading, cause everything is different (no c# but html, css, javascript, …). I was able to create a simple hello world with it, but to transfer my add-in to this would take a lot of time 🙁

  14. It is really nice article. I can’t see the udf name with arguments in the formula bar when typing in the cell. Help me how to do that.

    1. The only limitation to the method prescribed in this entry is that you cannot get the arguments in the formula bar. This is because the only place that arguments can be provided is via the means outlined in the XLL documentation (https://docs.microsoft.com/en-us/office/client-developer/excel/creating-xlls). I have seen some people define their functions in VBA via an XLAM, but then call the C# library (defined in this article) behind the scenes as an alternative.

  15. I have implemented the code as suggested by mcpatric. but still it is not working.
    i still getting this error
    =RANDOMTABLE(4,4), result is #NAME?.

    please suggest what i am doing wrong.

    I am working on:
    1. OS = Windows 10 64Bit
    2. excel = Microsoft Office Professional Plus 2013
    3. Visual Studio = Microsoft Visual Studio Professional Plus 2013
    4. .net = .net Framework 4.5

    when i open excel and check wheather my plugins are loaded successfully or not:

    1. ribon > devloper > Add-Ins: what I found that ExcelFunctions.connect is added in the list with a checked box. but as i click on it, a messag popup that:
    “cannot find addin ”. delete from list” (yesy/no).

    2. ribon > devloper > COM Add-Ins: what I found that ExcelAddin is added in the list with a checked box and showing the right location bellow (D:\..\..\..\ExcelAddin.vsto|visolocal).
    but when i remove it and try to add ExcelAddin.vsto manully message popups
    ” ….\…\ExcelAddin.vsto is not a valid Office AddIn.”

    what am i am missing, please suggest?

    1. For your issues, it is difficult to troubleshoot with what is provided, but I will answer what these mean:
      1) The error you are getting, and asking you to delete, is because the file is no longer in that location, or you do not have permission to it in that location. I find a quick test would be to place the XLAM in the XLSTART folder and try not loading it as an add-in if your issue persists.
      2) You cannot load a VSTO file from the add-ins dialog. You must register it by double-clicking on the VSTO file in Windows Explorer. It sounds like you might be new to VSTO, so I would suggest reading up on how to build, register and install your add-ins here:
      https://docs.microsoft.com/en-us/visualstudio/vsto/getting-started-programming-vsto-add-ins?view=vs-2019
      https://docs.microsoft.com/en-us/visualstudio/vsto/create-vsto-add-ins-for-office-by-using-visual-studio?view=vs-2019
      https://docs.microsoft.com/en-us/visualstudio/vsto/deploying-an-office-solution-by-using-clickonce?view=vs-2019

  16. The reason that some of the users see the add-in is enabled but it is not working is because of the “c:\Windows\SysWow64\mscoree.dll” address. It is supposed to be “mscoree.dll” for a 64-bit Office. You can manually change it from the registry “Computer\HKEY_CLASSES_ROOT\CLSID\{30A29909-AF27-4814-9CBE-ED6A39A4B9A5}\InprocServer32”.

Leave a Reply to MetaCancel reply