pnp / pnpjs

Fluent JavaScript API for SharePoint and Microsoft Graph REST APIs
https://pnp.github.io/pnpjs/
Other
764 stars 304 forks source link

How to correctly install apps to multiple sites from tenant app catalog #3143

Closed jhholm closed 1 month ago

jhholm commented 1 month ago

What version of PnPjs library you are using

4.x

Minor Version Number

4.5.0

Target environment

All

Additional environment details

Technically I'm using SP Editor PnP JS console, but authentication shouldn't matter as this is more of a documentation / architecture question.

Question/Request

I've been using SP Editor Chrome extension and it's PnP JS console for years to do tasks that you would normally do with PnP PowerShell. One quite common task is to install applications on sites as I find it easier to work with TypeScript than PowerShell. Recently SP Editor was updated from PnPJS 2.x to use PnPJS 4.x and I'm having a struggle to migrate my ALM scripts.

I want to install certain apps on sites. I have the list of sites as a string array. Below is the pseudo code that shows my issue. I am not using site collection app catalogs, and the application that I want to install is deployed on the tenant app catalog, but needs installation per site. As far as I can see this is not possible from a Web object.

Not possible from Web object

const appId = "HEREISMYAPPIDTHATIHARDCODED"
const sites = ["https://contoso.sharepoint.com/sites/site1", "https://contoso.sharepoint.com/sites/sites2"]; //List of sites
for (const site of sites) { //Loop through the sites and install an app if not installed
  const web = Web([sp.web, site]); //Instantiate a new Web object
  const catalog = web.appcatalog; //I can only get the site collection app catalog from a Web object
  const app = catalog.getAppById(appId);
  const appData = await app();
  if (!appData.InstalledVersion) { //If application is not installed, install
    await app.install();
  }
}

So as you might see from the code, this doesn't work. I can only get the site collection app catalog, not the tenant app catalog. Thus I am unable to install tenant deployed apps starting from a Web object.

Works from SPFI object

const appId = "HEREISMYAPPIDTHATIHARDCODED"
const sites = ["https://contoso.sharepoint.com/sites/site1", "https://contoso.sharepoint.com/sites/sites2"]; //List of sites
for (const site of sites) { //Loop through the sites and install an app if not installed
  const _sp = spfi().using(SPBrowser({ baseUrl: site })); //Instantiate a new SPFI object, instead of a Web
  const app = _sp.tenantAppcatalog.getAppById(appId); //Now I can get the tenantAppcatalog, and also the Web object if needed
  const app = catalog.getAppById(appId);
  const appData = await app();
  if (!appData.InstalledVersion) { //If application is not installed, install
    await app.install();
  }
}

Alternatively I could also use https://pnp.github.io/pnpjs/concepts/calling-other-endpoints/ if needed.

Is this functionality/architecture intentional, or am I missing something? As far as I remember v2 did allow getting either tenant or site collection app catalog from the Web object. Technically both site collection and tenant app catalog APIs are under /_api/web.

juliemturner commented 1 month ago

Have you reviewed the docs for v4? https://pnp.github.io/pnpjs/sp/alm/

I think I'm just confused what your qeustions is... you can't use the sp object?

But yes, the architecture change for getting the tenant app catalog did change and it was intentional, but this change was made in v3 when we went to the new internals.

jhholm commented 1 month ago

I reviewed the docs, the changelog and I also noticed the change was done for version 3.

I was not sure if this was intentional as you used to be able to do all or most Web related actions via the Web object. I understand the idea that for deployment and you are technically working against the tenant, but installation is done for the specific web. I think this is more of an opionated question and how SharePoint APIs are structured.

So this was more of a question that am I understanding things correctly and for ALM I just need to instantiate a new SP object instead of a Web object.

As this is intentional and instantiating a new SP object per site you want to install an app is the way to go, I guess this question can be closed.

juliemturner commented 1 month ago

You can't get a Web object without an SP object which has always been the case. If you look at the docs for Web you'll see that you have to use the sp object to get one since v3, so I think that's the confusion. The sp constant in v2 was just that a global constant. In v3+ the sp object is a factory interface that you create and can have multiple instances. So, all that said we had to rearchitect how to get the tenant app catalog and so now you can use that sp object to get it, which quite frankly makes more sense. I did test the code in SP Editor and it worked just fine.

jhholm commented 1 month ago

Yeah. The code works fine. I just needed to change my mindset. Closing as answered.

github-actions[bot] commented 1 month ago

This issue is locked for inactivity or age. If you have a related issue please open a new issue and reference this one. Closed issues are not tracked.