Thursday, 4 April 2013

Rolling out SharePoint 2013 apps to the enterprise - tenant scope and PowerShell installs

Let’s say you’ve decided to make some custom functionality available to your users as a SharePoint 2013 app. You’ve decided that this will be published to the appropriate internal SharePoint App Catalog (as opposed to being available in the public SharePoint Store). In a large company, it’s probably not realistic to expect all users to install such internal apps, even if we want to make the functionality available to everyone.

The answer *might* be to use one of the options for deploying the app at “tenant scope”. This works for both on-premises deployments and Office 365/SharePoint Online. This article looks at these options, but just as a reminder of where we are in the overall series, here's the table of contents:

  1. SharePoint 2013 apps – architecture, capability and UX considerations
  2. Getting started – creating lists, content types, fields etc. within a SharePoint app (provisioning)
  3. Working with data in the app web, and why you should
  4. Access end-user data (in the host web) from a SharePoint 2013 app
  5. Rolling out SharePoint 2013 apps to the enterprise - tenant scope and PowerShell installs [this article]
  6. Azure is the new SharePoint ‘_layouts’ directory
  7. “Host web apps” – provisioning files (e.g. master pages) to the host web
  8. “Host web apps” – provisioning fields and content types
  9. Working with web parts within a SharePoint app
  10. Using app parts (ClientWebPart) to bring app elements into the host web
  11. Using permission and capability requests
Microsoft even provide a nice admin interface for tenant-scope installs, which offers the following deployment options:

  • Deployment to named site collections
  • Deployment to all sites under a particular managed path
  • Deployment to all sites created from a certain site template

I said *might*. When deploying apps in this way, there is a crucial detail to be aware of – something which could either be highly-desirable or a deal-breaker. Essentially, all installed instances of the app *share* a single app web. This means any lists or libraries you provision as part of your app, are shared amongst all the webs where the app is installed. As I say, this could be exactly how you want your app to operate, or completely against it’s design.

The process for using this model is as follows:

  1. Upload app to App Catalog.
  2. Install the app to the App Catalog site collection – yes, for this scenario we actually have to install the app there (for reasons which will become clear later).
  3. Go to the Site Contents page and find the app. On the callout menu here (and only here), you’ll have an action labelled ‘DEPLOYMENT’:

    Deployment action in App Catalog 
  4. Click the ‘DEPLOYMENT’ button to go to the admin page.

On this page, I see the 3 options for deployment – individual site collections:

Deploying app to named site collections

..or deploying to all sites under a Managed Path, or all sites created from a certain template:

Deploying app using managed path or site template

Once you’ve selected appropriate options and hit OK, after a few minutes time you’ll find your app has landed in the target locations:

App deployed to target siteAs you can probably guess, it’s a timer job which performs the deployment. This is the App Installation Service timer job, set to run every 5 mins by default.

Important aspects of tenant-scoped app installations

As I highlighted before, all app instances share a single app web. The way this works is that the instance which is installed to the App Catalog site is used for all instances – effectively the user is always redirected to this URL on the app domain, regardless of which site they entered the app from:

Tenant scope app running in app catalog site

There are also important by-products of this:

  • Users in sites where the app is installed cannot remove the app – regardless of their permissions there
    • This makes sense because they would effectively be uninstalling the single instance used by everyone
  • Individual “usages” of the app do not get reported by Get-SPAppInstance – only the instance in the App Catalog site gets returned (N.B. applicable to on-premises SP2013 only)

Any changes to the app (e.g. app upgrades, app removal) need to be managed at the instance which is installed to the App Catalog site.

But I want something else - to roll out my app in bulk, but it NOT to be “single instance”

Then in this case, PowerShell is your friend – this is a two step process to install a new app:

For a full list of cmdlets around SharePoint 2013 apps, see App Management Service cmdlets in SharePoint 2013 on TechNet. Corey Roth also has a post on some of these cmdlets.

If you’re on Office 365 (with it’s limited set of PowerShell cmdlets, as of April 2013), you’ll need to accomplish the same thing using one of the client APIs. I’m starting to form the view that .NET CSOM code “wrapped” with PowerShell is a good way to do this – since it can then be integrated with TFS automated builds etc.

Happy app installing!

11 comments:

Jarno Leikas said...

I've understood that this "batch installation" also has limitations as to what kinds of artifacts the app is allowed to install (see http://msdn.microsoft.com/en-us/library/fp179896.aspx), e.g. app parts and custom actions can not be provisioned to host webs.

Also, we've had some issues with app permissions when using the Install-SPApp cmdlet. Namely, the app needs to be trusted manually at the host web in order to make it work. Have you experienced similar issues?

Chris O'Brien said...

@Jarno,

Agree on both of those points. On the first, I'd like to think that a future update *may* change this - I can't really think of any technical reason why it has to be that way.

On the second however, obviously Install-SPApp is effectively skipping that step of the app installer agreeing to the app's Permission Requests. It still needs to happen at some point. It's an interesting question as to whether this can also be accomplished with PowerShell - I haven't yet looked.

Obviously all this doesn't apply to tenant-scope apps, because the person who installed it to the App Catalog site agreed to the Permission Requests at that time.

Cheers,

Chris.

Parham said...

Hi Chris,

I have installed my custom app, which is a On-Prom Provider-Hosted app, in my On-Prom app catalog, but I can't deploy my app in the desirable site collections and receive an error saying "The entered site collection is invalid."!!

Have you experienced this issue before? Do have any suggestions?

Cheers,
Parr

Chris O'Brien said...

@Parham,

This happens when you're trying to specify a site collection which is NOT associated to the App Catalog you're working with. Remember that App Catalogs are associated at the web application level.

Effectively you're going "across the boundary", and will need to either change the App Catalog or site collection being specified.

HTH,

Chris.

Anonymous said...

Hi,

I ran into the same issue about the manual trust like Jarno. Is there a way to set an app trustable by powershell?

Anonymous said...

Hey Chris,

"If you’re on Office 365 (with it’s limited set of PowerShell cmdlets, as of April 2013), you’ll need to accomplish the same thing using one of the client APIs. I’m starting to form the view that .NET CSOM code “wrapped” with PowerShell is a good way to do this – since it can then be integrated with TFS automated builds etc."

How would you do this in office365?

Chris O'Brien said...

@Anonymous,

Effectively your PowerShell is calling one of the client APIs rather than the server API. You'll need the client API bits on the box which runs the PowerShell.

Here's an example by Anders Rask which is used to import SP2013 search configuration (managed properties etc.) - http://andersrask.sharepointspace.com/Lists/Posts/Post.aspx?ID=42

HTH,

Chris.

David Morrison said...

I have not found any way to trust the app via built in SharePoint PowerShell cmdlets.. BUT, I wrote a 'workaround' to trust it that works good enough for me. Basically I am using the PowerShell Internet Explorer automation to do it.

Here is my sample code, you'll need to modify a few things etc to get it work, but hopefully is a good starting point. I am using this as part of my TFS Build process that builds the app, installs the app, and now trusts it automatically. I used https://officesharepointci.codeplex.com/ for this and then modified it to add this:


Write-Host "Trying to trust the app..."

$authorizeURL = "$($web.Url.TrimEnd('/'))/_layouts/15/appinv.aspx?AppInstanceId={$($appInstance.Id)}"
Write-Host "Loading url:" $authorizeURL

$oIE = New-Object -com internetexplorer.application

try
{
$oIE.visible=$true
$oIE.navigate2($authorizeURL)

sleep -Seconds 1
while ($oIE.busy) {
sleep -milliseconds 50
}

Write-Host "Loaded Page:" $oIE.Document.Title

sleep -seconds 5
$button = $oIE.Document.getElementById("ctl00_PlaceHolderMain_BtnAllow")
if ($button -eq $null)
{
Write-Host "Could not find button to press"
Write-Host $oIE.Document.documentElement.outerText
}
else
{
Write-Host "Found [Trust It] button, clicking..."
}

$button.click() #click the button, even if it errors (this will give TFS Build a Red error to show)

sleep -Seconds 1
while ($oIE.busy) {
sleep -milliseconds 50
}
Write-Host "Now we're on page:" $oIE.Document.Title " - " $oIE.LocationURL

#if the button press was successful, we should now be on the Site Settings page..
if ($oIE.Document.title -like "*trust*")
{
Write-Host "Error: " $oIE.Document.body.getElementsByClassName("ms-error").item().InnerText
throw ("Error Trusting App:" + $oIE.Document.body.getElementsByClassName("ms-error").item().InnerText)
}
else
{
Write-Host "App was trusted successfully!"
}
}
finally
{
$oIE.Quit()
}

Chris O'Brien said...

@David,

NICE work! Good thinking to use IE automation, especially as part of a build process.

Thanks for sharing!

Chris.

Jarno Leikas said...

@David,

The solution worked perfectly. Using the IE screen didn't even occur to me!

There's also a way to automate app part installations. Normally you can't export an app part, and I suspect they set the export mode to "Do not allow" because the .webpart file contains a GUID that refers to the host web (which is obviously always different).

What you can do is export the app part (just allow exporting it first from the web part's settings). If you open the .webpart file, you'll notice there's a property element with name 'ProductWebId' in the file. This contains the GUID to the host web that the original app part was exported from, and this needs to be replaced run-time when importing the .webpart file.

So, just read the .webpart file into memory, and before adding that file to the web, modify the ProductWebId element's value to reflect the ID of the new host web. Then you can add it programmatically like any other exported web part.

I'm pretty sure you can add the ClientWebPart completely programmatically too, but I haven't tried that yet.

But thanks again David, with the following steps, I can automate app and app part installations now:
1) Upload the app to app catalog
2) Install the app to a web (using your approach)
3) Install exported app parts to a page by changing the ProductWebId guid

Jarno

Chris O'Brien said...

@Jarno,

Also cool. So are you doing this with CSOM or some other remote code?

Thanks,

Chris.