excelexcel-formulaoffice-jsoffice-addinsexcel-web-addins

Office Excel AddIn Custom functions are not being called on the Windows Excel App


We've created a custom function called TS_BIND that is working fine on Mac and Web but not on Windows. On Windows, any function we create is not called internally when the user tries to use them. We use a shared runtime for this add-in.


functions.ts

/* eslint-disable @typescript-eslint/no-explicit-any */
/** global clearInterval, console, CustomFunctions, setInterval **/

interface IRangeData {
  index: number;
  address: string;
  value: any;
}

/**
 * Two-way data binding between two cells. Select two cells that need to have the same value, this function will keep them in sync. Alter the value of one cell and the other cell will be updated with the same value.
 * @customfunction BIND
 * @param {any} cell1 The first cell to bind
 * @param {any} cell2 The second cell to bind
 * @returns {any} The updated value
 * @requiresAddress
 * @requiresParameterAddresses
 */
export function bind(cell1: any, cell2: any, invocation: CustomFunctions.Invocation): any {
  Excel.run(async (context) => {
    let range1: IRangeData;
    let range2: IRangeData;

    let tsCache = context.workbook.worksheets.getItemOrNullObject("ts-bind-cache");
    await context.sync();
    if (tsCache.isNullObject) tsCache = context.workbook.worksheets.add("ts-bind-cache");
    tsCache.visibility = "Hidden";

    const addressesRange = tsCache.getRange("A1").getResizedRange(999, 1);
    addressesRange.load("values");
    await context.sync();

    const addresses = addressesRange.values.filter((address) => address[0] !== null && address[0] !== "");

    const sheetCellCode1 = invocation.parameterAddresses[0]?.replace(/['+]/g, "");
    const sheetCellCode2 = invocation.parameterAddresses[1]?.replace(/['+]/g, "");

    addresses.forEach((address, index) => {
      if (address[0] === sheetCellCode1) {
        range1 = {
          index,
          address: address[0],
          value: address[1],
        };
      }
      if (address[0] === sheetCellCode2) {
        range2 = {
          index,
          address: address[0],
          value: address[1],
        };
      }
    });

    if (!range1?.address) {
      const cacheRange1 = tsCache.getRange(`A${addresses.length + 1}:B${addresses.length + 1}`);
      cacheRange1.values = [[sheetCellCode1, cell1]];
      await context.sync();
    }

    if (!range2?.address) {
      const cacheRange2 = tsCache.getRange(`A${addresses.length + 2}:B${addresses.length + 2}`);
      cacheRange2.values = [[sheetCellCode2, cell2]];
      await context.sync();
    }

    if (cell1 !== cell2) {
      if (range1?.value !== cell1) {
        const sheetName = sheetCellCode2.split("!")[0];
        const sheet = context.workbook.worksheets.getItemOrNullObject(sheetName);
        await context.sync();

        if (!sheet.isNullObject) {
          const range = sheet.getRange(sheetCellCode2.split("!")[1]);
          range.values = [[cell1]];
          await context.sync();

          if (range1) {
            const cacheRange1 = tsCache.getRange(`B${range1.index + 1}`);
            cacheRange1.values = [[cell1]];
          }
          if (range2) {
            const cacheRange2 = tsCache.getRange(`B${range2.index + 1}`);
            cacheRange2.values = [[cell1]];
          }
          await context.sync();
        }
      }

      if (range2?.value !== cell2) {
        const sheetName = sheetCellCode1.split("!")[0];
        const sheet = context.workbook.worksheets.getItemOrNullObject(sheetName);
        await context.sync();

        if (!sheet.isNullObject) {
          const range = sheet.getRange(sheetCellCode1.split("!")[1]);
          range.values = [[cell2]];
          await context.sync();

          if (range1) {
            const cacheRange1 = tsCache.getRange(`B${range1.index + 1}`);
            cacheRange1.values = [[cell2]];
          }
          if (range2) {
            const cacheRange2 = tsCache.getRange(`B${range2.index + 1}`);
            cacheRange2.values = [[cell2]];
          }
          await context.sync();
        }
      }
    }
  });

  return cell1;
}

manifest.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="TaskPaneApp">
  <Id>749ebdde-13cf-459b-829b-89c11cfa2b23</Id>
  <Version>1.1.1.0</Version>
  <ProviderName>TermSheet LLC</ProviderName>
  <DefaultLocale>en-US</DefaultLocale>
  <DisplayName DefaultValue="TermSheet for Excel"/>
  <Description DefaultValue="TermSheet Add-In for Microsoft Excel."/>
  <IconUrl DefaultValue="https://localhost:3000/assets/icon-32.png"/>
  <HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/>
  <SupportUrl DefaultValue="https://termsheet.com/"/>
  <AppDomains>
    <AppDomain>https://termsheet.com</AppDomain>
  </AppDomains>
  <Hosts>
    <Host Name="Workbook"/>
  </Hosts>
  <Requirements>
    <Sets DefaultMinVersion="1.1">
      <Set Name="SharedRuntime" MinVersion="1.1"/>
    </Sets>
  </Requirements>
  <DefaultSettings>
    <SourceLocation DefaultValue="https://localhost:3000/taskpane.html"/>
  </DefaultSettings>
  <Permissions>ReadWriteDocument</Permissions>
  <VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0">
    <Hosts>
      <Host xsi:type="Workbook">
        <Runtimes>
          <Runtime resid="Taskpane.Url" lifetime="long"/>
        </Runtimes>
        <AllFormFactors>
          <ExtensionPoint xsi:type="CustomFunctions">
            <Script>
              <SourceLocation resid="Functions.Script.Url"/>
            </Script>
            <Page>
              <SourceLocation resid="Functions.Page.Url"/>
            </Page>
            <Metadata>
              <SourceLocation resid="Functions.Metadata.Url"/>
            </Metadata>
            <Namespace resid="Functions.Namespace"/>
          </ExtensionPoint>
        </AllFormFactors>
        <DesktopFormFactor>
          <GetStarted>
            <Title resid="GetStarted.Title"/>
            <Description resid="GetStarted.Description"/>
            <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/>
          </GetStarted>
          <FunctionFile resid="Taskpane.Url"/>
          <ExtensionPoint xsi:type="PrimaryCommandSurface">
            <OfficeTab id="TabHome">
              <Group id="CommandsGroup">
                <Label resid="CommandsGroup.Label"/>
                <Icon>
                  <bt:Image size="16" resid="Icon.16x16"/>
                  <bt:Image size="32" resid="Icon.32x32"/>
                  <bt:Image size="80" resid="Icon.80x80"/>
                </Icon>
                <Control xsi:type="Button" id="TaskpaneButton">
                  <Label resid="TaskpaneButton.Label"/>
                  <Supertip>
                    <Title resid="TaskpaneButton.Label"/>
                    <Description resid="TaskpaneButton.Tooltip"/>
                  </Supertip>
                  <Icon>
                    <bt:Image size="16" resid="Icon.16x16"/>
                    <bt:Image size="32" resid="Icon.32x32"/>
                    <bt:Image size="80" resid="Icon.80x80"/>
                  </Icon>
                  <Action xsi:type="ShowTaskpane">
                    <TaskpaneId>ButtonId1</TaskpaneId>
                    <SourceLocation resid="Taskpane.Url"/>
                  </Action>
                </Control>
              </Group>
            </OfficeTab>
          </ExtensionPoint>
        </DesktopFormFactor>
      </Host>
    </Hosts>
    <Resources>
      <bt:Images>
        <bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/assets/icon-16.png"/>
        <bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/assets/icon-32.png"/>
        <bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/assets/icon-80.png"/>
      </bt:Images>
      <bt:Urls>
        <bt:Url id="Functions.Script.Url" DefaultValue="https://localhost:3000/functions.js"/>
        <bt:Url id="Functions.Metadata.Url" DefaultValue="https://localhost:3000/functions.json"/>
        <bt:Url id="Functions.Page.Url" DefaultValue="https://localhost:3000/taskpane.html"/>
        <bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://go.microsoft.com/fwlink/?LinkId=276812"/>
        <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html"/>
      </bt:Urls>
      <bt:ShortStrings>
        <bt:String id="Functions.Namespace" DefaultValue="TS"/>
        <bt:String id="GetStarted.Title" DefaultValue="Get started with TermSheet!"/>
        <bt:String id="CommandsGroup.Label" DefaultValue="TermSheet for Excel"/>
        <bt:String id="TaskpaneButton.Label" DefaultValue="TermSheet for Excel"/>
      </bt:ShortStrings>
      <bt:LongStrings>
        <bt:String id="GetStarted.Description" DefaultValue="Manage your TermSheet worksheets."/>
        <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Click to Open TermSheet"/>
      </bt:LongStrings>
    </Resources>
  </VersionOverrides>
</OfficeApp>

We're getting this error below and ended up discovering that the custom functions are not called on Windows Excel App, at least for this add-in.

TermSheet for Excel Add-In - Custom Function - TS_BIND


Solution

  • That was the solution from the Microsoft team, and it worked for me. Thanks a lot, guys, for hopping in.

    Hello Marcos,

    Good day!

    After closely working with our engineering team please find the update. In the parsing of manifest, Runtimes will be parsed first, and its unique identifier is resid. In your manifest, the sharedruntime's resid is "Taskpane.Url"(). When Excel installs the Custom Function (this happens after Runtimes is parsed), it will read the resid in CustomFunctionExtensionPoint's page item, and then find the runtime corresponding to the resid in the parsed Runtimes. In your scenario, Excel cannot find a runtime with resid "Function.Page.Url", so it recognized your custom function as non-sharedruntime by default. By explaining this specific process, I want you to inform that in the parsing of manifest, Excel will match the custom function and runtime by resid , not the specific value of this resid in "<bt:Urls>" tag. This matching rule also applies to other resid. Please modify the resid from Functions.Page.Url to Taskpane.Url.

    If the issue still exists, please feel free to reply back to the email I will be happy to assist you further on the request.

    Regards, Karasi Jayakrishna (He/Him) Partner Frontline Advocate (Microsoft Commercial Marketplace) For Microsoft Customer Service & Support