The problem you're encountering is that the Excel Function Wizard will call the function repeatedly whilst you are entering parameter values.
To circumvent this, your function needs to detect the presence of the Function Wizard and proceed accordingly.
I have some C++ code that does this robustly in production. Hopefully you can port this to C#. It uses the Windows API. You need to take care that the Function Wizard is pertinent to a particular Excel session; taking special care of Excel2013.
typedef struct _EnumStruct
{
bool wizard;
DWORD pid;
} EnumStruct, FAR* LPEnumStruct;
BOOL CALLBACK EnumProc(HWND hwnd, LPEnumStruct pEnum)
{
static const char szFunctionWizardClass[] = "bosa_sdm_XL";
static const char szFunctionWizardCaption[] = "Function Arguments";
char szClass[sizeof(szFunctionWizardClass)];
char szCaption[sizeof(szFunctionWizardCaption)];
if (GetClassName(hwnd, (LPSTR)szClass, sizeof(szFunctionWizardClass))){
if (CompareString(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE, (LPSTR)szClass, (lstrlen((LPSTR)szClass) > lstrlen(szFunctionWizardClass)) ? lstrlen(szFunctionWizardClass) : -1, szFunctionWizardClass, -1) == CSTR_EQUAL){
// Do the process IDs match? (The former way of checking parent windows doesn't work in Excel2013).
DWORD pid = NULL;
GetWindowThreadProcessId(hwnd, &pid);
if (pid == pEnum->pid){
// Check the window caption
if (::GetWindowText(hwnd, szCaption, sizeof(szFunctionWizardCaption))){
if (CompareString(MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT), NORM_IGNORECASE, (LPSTR)szCaption, (lstrlen((LPSTR)szCaption) > lstrlen(szFunctionWizardCaption)) ? lstrlen(szFunctionWizardCaption) : -1, szFunctionWizardCaption, -1) == CSTR_EQUAL){
pEnum->wizard = TRUE;
return FALSE;
}
}
}
}
}
// Continue the enumeration
return TRUE;
}
bool Excel12::calledFromFunctionWizard()
{
EnumStruct enm;
enm.wizard = FALSE;
enm.pid = GetProcessId(GetCurrentProcess());
EnumWindows((WNDENUMPROC)EnumProc, (LPARAM)((LPEnumStruct)&enm));
return enm.wizard;
}