Programmatically installing an app from the app catalog

After spending a fair bit of time googling for a way to programatically add / install an SPFx (webpart) app that was already uploaded to the tenant app catalog to a subsite in a random site collection, I was about to give up. A few of the things I had looked at but didn’t work for me included:

  • Using web.LoadAndInstallApp(appstream) because it won’t install the globally available version of the app in the tenant catalog. Instead you’re uploading the app’s sppkg file each time
  • Creating an PnP provisioning template e.g. with PowerShell using Get-PnPProvisiongTemplate but unfortunately, when you inspect the template you’ll notice that for subsites there is no element and even if you manually add it, the app won’t get installed (there are several issues for this e.g. (this one)[https://github.com/SharePoint/PnP-Sites-Core/issues/1668]
  • Trying to mimic the behavior found in Microsoft’s own classes as desribed (here)[https://rasper87.wordpress.com/2018/04/26/provision-spfx-web-parts-to-classic-sites-part-3-install-spfx-web-part-to-sharepoint-site-web/]

REST to the RESCUE

Luckily I then found the following Microsoft page https://docs.microsoft.com/en-us/sharepoint/dev/apis/alm-api-for-spfx-add-ins, detailing the latest about the ALM API for spfx add-ins. After a bit fiddling around with PnP PowerShell to obtain an access token …

Connect-PnPOnline https://xxx.sharepoint.com/sites/app-xxx -AppId 8370c46b-5103-45fc-aad9-xxxxxxxxxxxx -AppSecret xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
Get-PnPAppAuthAccessToken

I managed to call the following API successfully

url: /_api/web/tenantappcatalog/AvailableApps/GetById('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx')/Install
method: POST
Authorization: Bearer <access token>
X-RequestDigest: <form digest>
Accept: 'application/json;odata=nometadata'

And added the following extension method to my C# project

private static async Task<string> InstallApp(this Web web, string siteUrl, string clientId, string clientSecret, string appId, bool wait = false)
{
    var authManager = new AuthenticationManager();
    var clientContext = authManager.GetAppOnlyAuthenticatedContext(siteUrl, clientId, clientSecret);
    var bearerToken = clientContext.GetAccessToken();

    var site = clientContext.Site;
    clientContext.Load(site);
    clientContext.ExecuteQuery();

    var client = new HttpClient();
    var data = new StringContent(string.Empty);

    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);

    var response = await client.PostAsync($"{web.Url}/_api/web/tenantappcatalog/AvailableApps/GetById('{appId}')/Install", data).ConfigureAwait(false);

    string responseBody = await response.Content.ReadAsStringAsync();
    return responseBody ;
}

You May Also Like

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: