Find Out if an Extension is Installed, part 2

“Hi, this is Library, is Extension 1 home? What about Extension 2, are they home too?”

In my previous post on this topic, I explained how you can use the NAV App Installed App system table to see if a specific extension is installed. I later found out though that the ability to access that table may not always be available in the Dynamics 365 for Financials platform, so back to square one. I want a stable long-lasting solution.

Alas…events!

First…some background on why I need this functionality. I’m developing 2 extensions that share a common library, but I want to develop these extensions so that they are independent, meaning that a customer is able to install only one of the extensions if they choose to, and not be forced to install both. I also need certain features in each extension to act differently depending on if the other extension is installed or not. I know….never simple. 🙂

For those that are not aware, when you submit an extension to AppSource, you are able to submit along with it a library extension. Multiple extensions can be dependent on the same library, which makes it easy to deliver foundation type functions that are shared amongst your extensions. The library extension will not be seen in AppSource, but it will be automatically installed when any of the dependent extensions are installed.

What this also means is that when I am developing in the context of one of my “functional” extensions, I am able to directly reference objects that are contained in my library because the library is guaranteed to exist when the functional extension is installed. What I cannot do though is the opposite, because although the library extension knows that at least one functional extension was installed, it does not know directly which one it was. Make sense!? Clear as mud I know. 🙂

In the example below, let’s assume that I have a “Library” extension, and two functional extensions, brilliantly named “Extension 1” and “Extension 2”.

First, I need to create a codeunit in my library extension that will act as the “extension handler” so to speak. In other words it will house the functions required to determine what extensions are installed. In my library codeunit, I’ll add the following functions:

CheckIsExtensionInstalled(ExtensionAppID : GUID) : Boolean
IsInstalled := FALSE;
IsExtensionInstalled(ExtensionAppID,IsInstalled);
EXIT(IsInstalled);

GetExtension1AppID() : GUID
EXIT('743ba26c-1f75-4a2b-9973-a0b77d2c77d3');

GetExtension2AppID() : GUID
EXIT('a618dfa7-3cec-463c-83f7-7d8f6f6d699b');

LOCAL [IntegrationEvent] IsExtensionInstalled(ExtensionAppID : GUID;VAR IsInstalled : Boolean)

The above functions allow me to call into the codeunit to determine if either of my functional extensions are installed. The GUIDs that I used are samples above, but you should use the same GUID that is used in the corresponding extension manifest file.

The piece that makes this all possible is the published event IsExtensionInstalled. What I can do now is from within each functional extension, I can subscribe to that event so that each extension can basically give “answer” the library when it asks if it’s installed.

To do that, I create 2 more codeunits, one in each functional extension. These codeunits will contain a subscriber to the event that we published in the library codeunit. This way, if the extension is installed, its subscriber will respond to the event and let the library know that it is installed. If the extension is not installed then there won’t be anyone home to answer that call.

Extension 1

LOCAL [EventSubscriber] OnCheckIsExtensionInstalled(ExtensionAppID : GUID;VAR IsInstalled : Boolean)
IF ExtensionAppID = ExtensionHandler.GetExtension1AppID THEN
  IsInstalled := TRUE;

Extension 2

LOCAL [EventSubscriber] OnCheckIsExtensionInstalled(ExtensionAppID : GUID;VAR IsInstalled : Boolean)
IF ExtensionAppID = ExtensionHandler.GetExtension2AppID THEN
  IsInstalled := TRUE;

So how do we use all of this? Easy, of course. The example below shows code that you could use from either of the functional extensions, so that your extensions can act differently depending on what other extensions are installed.

IF LibraryCodeunit.CheckIsExtensionInstalled(LibraryCodeunit.GetExtension1AppID) THEN
  MESSAGE('Extension 1 is installed.');

IF LibraryCodeunit.CheckIsExtensionInstalled(LibraryCodeunit.GetExtension2AppID) THEN
  MESSAGE('Extension 2 is installed.');

There, easy right? If you want to try it out yourself, you can grab the above example on GitHub here.

Now, while you can do this if you own all of the source code for each extension, you cannot use this solution to determine if “any old random extension” is installed, as you need to add the subscriber function to the functional extension code. But, if you are developing out multiple extensions and if you need to know which of them are installed, then this solution will work wonderfully!

Happy coding!

Advertisements

Find Out if an Extension is Installed

EDIT: I’ve just been informed that users will not have access to the “NAV App Installed App” system table in the near future, so while the following will work for a typical NAV 2017 installation, it will not work for a Dynamics 365 extension.

EDIT2: I’ve posted a much better solution here!!

Having already created and published one extension, I am now in the process of creating a second extension, and in some cases, I want my extensions to act differently depending on if the other extension is installed or not. I could easily make each extension dependent on each other so that I know they’re always both installed, but where’s the fun in that!!??

You can do this by using the NAV App Installed App system table (2000000153). This table lists all of the extensions that are installed within the tenant.

Now, before I go further, I will mention that there is a function named IsInstalled in Codeunit 2500 (NavExtensionInstallationMgmt) that you could call, but this function accepts a Package ID, which is different than the Application ID of the extension. I prefer to use the Application ID, as I am in control of what that value is through the extension packaging process, and I like that control. So……because of this, I am not going to use the built-in function.

Here’s what you need to do:

First, create a local function that returns true/false depending on if a given Application ID is installed or not.

LOCAL CheckIfExtensionInstalled(AppID : GUID) : Boolean
EXIT(NAVAppInstalledApp.GET(AppID));

Next, we need to write local functions to return our extension Application IDs. You can get the GUID from the extension manifest file when you build the NAVX file. Repeat this process and create a function for each extension that you want to see if it’s installed.

LOCAL GetExtension1AppID() : GUID
//-- AppID: GUID
EVALUATE(AppID,'e7deb9a9-6727-4157-838e-bcf4a0853942');
EXIT(AppID);

Finally…….create a global function that will check for each extension. We will call this function from throughout our application wherever we need our application to act accordingly.

Extension1IsInstalled() : Boolean
EXIT(CheckIfExtensionInstalled(GetExtension1AppID));

Now, all we need to do is use the functionality in our code, such as the following example:

IF Extension1IsInstalled THEN BEGIN
  //-- do something
END ELSE BEGIN
  //-- do something else
END;

One thing to note, you will probably also need to give your codeunit (or whatever object you added the above functions to) permissions to read the NAV App Installed App table as the average user does not typically have this permission.

Determining the right client

In certain scenarios, it may be important to know what client that the user is working in. Perhaps you have an action that doesn’t work in the Web Client, or maybe you have some code that interacts with a device camera that only works in the Device Client. Or maybe you want to send a link to your users that allows them to open a record, and we want to make sure the link is for the correct client?

First, I want to be clear that what I am describing here is based on the Dynamics NAV 2017 platform. Yes, this means the Dynamics 365 for Financials platform falls under this umbrella as well. Perhaps bits and pieces are supported on older platforms, but my intention is to never look backwards and keep this train moving forward!

Now to it…

There are some system variables that we need to get familiar with:

  • CLIENTTYPE
    • an option variable that contains the various client types:
      • Windows
      • Web
      • SOAP
      • OData
      • NAS
      • Background
      • Management
      • Tablet
      • Phone
      • Desktop
      • ODataV4
  • CURRENTCLIENTTYPE
    • Returns the client type that is currently executing the code.
  • DEFAULTCLIENTTYPE
    • Returns the default client type, as defined in the Dynamics NAV Server configuration. For more information click here.

Example 1: Show an action only in the Windows Client.

  • In the Development Environment, design the page that contains the action.
  • Make sure that the action does something. If it does not do anything, then it will always be hidden. For testing purposes you can add the following code to the onAction trigger for the action:
MESSAGE('A message for testing');
  • Add a global Boolean variable named ‘showMyAction’:

globalvariable

  • Assign the global variable to the Visible property of the action that you wish to conditional show/hide (you can show this property as a column in the Action Designer now!!):

actiondesigner

  • Add the following code to the onOpenPage trigger:
showMyAction := CURRENTCLIENTTYPE = CLIENTTYPE::Windows;
  • Close, save and compile your page.
  • If you run the page in the Web Client for example, you will not see the action on the ribbon of the page. Running the page from the Windows Client will show the action.

Example 2: Create a url that takes a user to the first customer record.

  • In the Development Environment, create a new codeunit.
  • In the onRun trigger, create the following local variables:

capture-globalvariables

  • In the onRun trigger, add the following code:
Customer.FINDFIRST;
url := GETURL(DEFAULTCLIENTTYPE,COMPANYNAME,OBJECTTYPE::Page,PAGE::"Customer Card",Customer);
MESSAGE(url);
  • Close, save and compile the codeunit.
  • From the Development Environment run the codeunit. A message should appear on screen with the url that was generated. In the example below, my default client was set to be the Web Client.
http://localhost:8080/DynamicsNAV100/WebClient?company=CRONUS%20USA&page=21&bookmark=27%3bEgAAAAJ7CDAAMQAxADIAMQAyADEAMg%3d%3d

Hopefully you get a sense now of how you can condition your code based on the different Dynamics NAV client types.

That’s all for today!