Mathias Osterkamp

Specialist – focus development Microsoft technology stack

SPFX 2019 Custom Installer

How create a custom installer for your SFPX solution

What we create

In SharePoint SPFX solution development you have most time the requirement to do some installation tasks. You have multiple solutions for that problem. First - most content can be deployed via XML manifest files to your website. This helps you with the most basic stuff, but if you like to do some more modifications, for example set special permissions on lists or deploy webparts to your page, it have to be done extra.

To reach this advanced installation we use an application customizer. This customizer is running once and just executed directly after install your app. Inside the customizer you can run further code to perform every action you like.

How it looks

It is starting with the basic app installation. So select your app from “Add an app” dialog.

AppInstallation

After finished installation, refresh your page and you will get a short install notice. You could also set up some configuration wizard or deny the installation for some reason.

AppInstallation

After successful installation you will get a dialog message and everything is done.

How it is build

You start with an default application customizer. You can add this via yeoman generator.

On the onInit function we call our install and remove the customizer after successful installation.

public async onInit(): Promise<void> {
    Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);
    //do this only as site admin
    if (this.context.pageContext.legacyPageContext.isSiteAdmin) {
      try {
        //do installation
        await this.install();
        //if everything fine remove customizer
        await this.removeCustomizer(this.componentId);
        Log.info(LOG_SOURCE, "Removed customizer", this.context.serviceScope);
        await Dialog.alert(`Success. Refresh page!`);
      } catch (err) {
        console.log(err);
        Log.error(LOG_SOURCE, err, this.context.serviceScope);
        await Dialog.alert(`Error`);
      }
    }
    return Promise.resolve();
  }

The install function contains all your install logic you need. Be sure that you do not forget to handle errors and maybe redo the installation until everything is fine.

private async install(): Promise<void> {
    await Dialog.alert(`Do some install stuff.`);
    return;
  }

To finish our installation we need to remove our customizer from the custom actions.

protected removeCustomizer(componentId: string) {
    try {
      this._getdigest().then((digrestJson) => {
        console.log(digrestJson);
        const digest = digrestJson.FormDigestValue;
        const headers = {
          "X-RequestDigest": digest,
          "content-type": "application/json;odata=verbose",
        };
        const spOpts: ISPHttpClientOptions = {};
        this.context.spHttpClient
          .get(
            this.context.pageContext.web.absoluteUrl +
              `/_api/web/UserCustomActions`,
            SPHttpClient.configurations.v1,
            spOpts
          )
          .then((response: SPHttpClientResponse) => {
            response.json().then((responseJSON: any) => {
              console.log(responseJSON);

              responseJSON.value.forEach((element) => {
                //Match custom action
                if (element.ClientSideComponentId == componentId) {
                  //found custom action, call REST API to delete object
                  this.context.spHttpClient
                    .post(
                      this.context.pageContext.web.absoluteUrl +
                        "/_api/web/UserCustomActions(@v0)/deleteObject()?@v0=guid'" +
                        element.Id +
                        "'",
                      SPHttpClient.configurations.v1,
                      spOpts
                    )
                    .then((response: SPHttpClientResponse) => {
                      console.log(
                        "I think I just deleted a custom action via REST API---"
                      );
                    });
                }
              });
            });
          });
      });
    } catch (error) {
      console.error(error);
    }
  }

To get everything running you also need to setup your package-solution with the feature and add your customizer as custom action. Hint: This works only for apps installed on a website.

"skipFeatureDeployment": false,
  "features": [
    {
      "title": "Application Extension - Deployment of custom action.",
      "description": "Deploys a custom action with ClientSideComponentId association",
      "id": "a4b2f6ab-5e1b-4b6c-aa40-f4a708929633",
      "version": "1.0.0.0",
      "assets": {
        "elementManifests": [
          "elements.xml",
          "CustomInstallerApplicationCustomizer.xml"
        ]
      }
    }
  ]

To provision your custom action you add the CustomInstallerApplicationCustomizer.xml on the assets folder.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <CustomAction Title="CustomInstallerApplicationCustomizer" Location="ClientSideExtension.ApplicationCustomizer" ClientSideComponentId="6cc39d9d-d730-4352-8212-7108749e04f9">
    </CustomAction>
</Elements>

Thats everything to go! Smart hint, it deploys automatically a feature (like shown in solution config). You can manual deactivate this feature on your web settings and after reactivation the installer runs again.

Full code here.