harbar.net component based software & platform hygiene

Rational Guide to Multi Tenancy with SharePoint 2010, Part Six: Provisioning Tenants

Print | posted on Tuesday, September 14, 2010 10:33 AM



This, the sixth part of the Rational Guide article on multi tenancy, will walk through the creation of feature packs and the provisioning of Tenants for the sample scenario detailed in part three.

If you haven’t checked out the previous parts, I strongly encourage you to review them. I won’t repeat information and I assume you have read the previous parts, which are:

  1. Feature and Capability Overview
  2. Planning your Deployment
  3. Example Scenario and what Multi Tenancy brings to the party
  4. Configuring the base Infrastructure
  5. Creating Partitioned Service Applications
  6. Provisioning Tenants (this article)
  7. Testing the Functionality and other considerations

IMPORTANT: You must have run the scripts in the order presented in this series. If you haven’t run the scripts in part four, you should go do that before proceeding!


So far in the article series we have configured the core infrastructure and created service applications in partitioned mode. Whilst there are a few nuances to those steps which are critical to success, there is nothing clever going on so far, and we have zero functionality. Now we look at the really interesting part of the deployment, actually provisioning tenants and setting up the service applications to work with them.

Creating Feature Packs

For our scenario we wish each of the three tenants to use a different edition (or SKU) of SharePoint 2010. Therefore we need to create our Feature Packs which will encapsulate the SKU features before we provision the tenants.

This work is pretty simple. All we need to do is create a feature pack for each SKU and add to each the necessary features in an additive fashion. Luckily this ‘work’ has been done for us in the Hoster’s Guide. This is a very long script, so I will simply include here an excerpt. Get the full script here.

Add-PSSnapin microsoft.sharepoint.powershell  -ea silentlycontinue

#validate pre-reqs
$app = Get-SPServiceApplication |? {$_.TypeName -eq "Microsoft SharePoint Foundation Subscription Settings Service Application"}
if($app -eq $null){
 "Settings Service Application must already exist"

#Create an alias for Add-SPSiteSubscriptionFeaturePackMember 
Set-Alias AddFeature Add-SPSiteSubscriptionFeaturePackMember

#Foundation Feature Pack
$ffp = New-SPSiteSubscriptionFeaturePack
AddFeature -identity $ffp -FeatureDefinition AdminLinks -ea SilentlyContinue
AddFeature -identity $ffp -FeatureDefinition AnnouncementsList -ea SilentlyContinue

# ... the rest of the features ...

AddFeature -identity $ffp -FeatureDefinition workflowProcessList -ea SilentlyContinue 
AddFeature -identity $ffp -FeatureDefinition XmlFormLibrary -ea SilentlyContinue 

#Standard Feature Pack
$sfp = New-SPSiteSubscriptionFeaturePack
AddFeature -identity $sfp -FeatureDefinition AccSrvRestrictedList -ea SilentlyContinue 
AddFeature -identity $sfp -FeatureDefinition AdminLinks -ea SilentlyContinue         

# ... the rest of the features ...

AddFeature -identity $sfp -FeatureDefinition Workflows -ea SilentlyContinue    
AddFeature -identity $sfp -FeatureDefinition XmlFormLibrary -ea SilentlyContinue

#Enterprise Feature Pack
$efp = New-SPSiteSubscriptionFeaturePack
AddFeature -identity $efp -FeatureDefinition AccSrvMSysAso -ea SilentlyContinue  
AddFeature -identity $efp -FeatureDefinition AccSrvRestrictedList -ea SilentlyContinue

# ... the rest of the features ...

AddFeature -identity $efp -FeatureDefinition Workflows -ea SilentlyContinue        
AddFeature -identity $efp -FeatureDefinition XmlFormLibrary -ea SilentlyContinue

#output IDs to text file
$out = "foundationFeatures = "+$ffp.ID
$out = $out + "standardFeatures = "+$sfp.ID
$out = $out + "enterpriseFeatures = "+$efp.ID
$out | out-File "featurePacks.txt"

All I’ve done here is add a little bit at the end of the script to output the Feature Pack IDs to a text file, as we will need these later when provisioning tenants. Whilst we can add custom properties to feature packs, it is simplest and easiest to work directly with the GUIDs used.

Shoving the GUIDs out to a text file is OK for this simple example scenario which is intended for explanation purposes, but is not much good for the real world. A better approach would be to add them to the Farm’s property bag.

Provisioning Tenants

Now we get to the interesting part. The general idea here is to have a chunk of script that can be run repeatedly which sets up a tenant and all the related configuration.

First up we set up some variables for our hosting web app and the name of the two service apps we’ll be working with. Also we’ll add in the GUIDs for our feature packs:

Add-PSSnapin Microsoft.SharePoint.Powershell -EA 0 

# farm details - update to reflect environment
$hostingMainURL = "http://sph1"
$upaProxyName = "Hosting Farm Tenant User Profile Service Proxy"
$mmsProxyName = "Hosting Farm Tenant Managed Metadata Service Proxy"

# feature packs - update after creating them
$foundationFeatures = "82b4a51b-de9a-42f4-b035-9427fa8ff222"
$standardFeatures = "5906fe7c-cca3-4cc5-a074-4594f1d36933"
$enterpriseFeatures = "107ae862-2fed-4b14-b542-ae9b327f2b71"


Now before we can continue, you must add the user whom is running this script to the Permissions of the User Profile Service Application. This is NOT the Administrators, but the Permissions. How you choose to do that is up to you. If you don’t do this it won’t work. I also strongly recommend you quit your PowerShell host after setting this. This is why I haven’t scripted the permissions within it. I am not going to detail the reasons behind that in this article (it may show up in a separate post, it’s quite a meaty topic!) The script will nag you about this and then immediately afterwards run an IISReset, which is required:

$a = Read-Host "Have you added the user running this script to *permissions* on the UPA?"

Write-Host "Resetting IIS..."

Now we create a PowerShell Function for doing all the real work. As you can see we pass in a number of parameters – these are all the things we need to configure the various options. I’ve built this so as to take in the Customer Name – a nice way to shortcut all the URLs and OUs and such. But of course this is just a sample scenario, in the real world this would need further refinement.

We start by grabbing a reference to our hosting web app and create a new site subscription:

function ProvisionTenant($customerName, $customerTenantAdmin, $customerTenantAdminEmail, $customerFeatures, $hostingMainURL, $upaProxyName, $mmsProxyName, $foundationFeatures)
    Write-Host "Provisioning Tenant..."
    Write-Host "Name: $customerName"
    Write-Host "Admin: $customerTenantAdmin"
    Write-Host "Email: $customerTenantAdminEmail"
    Write-Host "Features: $customerFeatures"
    Write-Host "Foundation: $foundationFeatures"

    # grab the web app
    $webApp = Get-SPWebApplication $hostingMainURL

    # create new Site Subscription
    Write-Host "Creating Site Subcription..."
    $sub = New-SPSiteSubscription

Once we have a Site Subscription, we can configure the feature pack to be used, and the People Picker to only use a particular OU, which we pass in in a DN format:

# assign feature pack and configure the OU to use in the People Picker for the Subscription
    Write-Host "Assiging Feature Pack and configuring People Picker..."
    Set-SPSiteSubscriptionConfig –id $sub -FeaturePack $customerFeatures -UserAccountDirectoryPath "OU=$customerName,OU=Customers,DC=sharepoint,DC=com"

Next up, we create a member site using the Subscription and passing in the web app to the –HostHeaderWebApplication. We must do this first as the other steps require a site at the ‘root’.

    # create the "main" member site (we need a site at the root to use Host Headers and Managed Paths in the following cmdlets)
    Write-Host "Creating Member Site..."
    New-SPSite -url "http://$customerName.sharepoint.com" -SiteSubscription $sub -HostHeaderWebApplication $webApp -owneralias $customerTenantAdmin -owneremail $customerTenantAdminEmail -template sts#0

Then we create the Tenant Admin site note the -AdministrationSiteType parameter:

# create Tenant Admin site 
    Write-Host "Creating Tenant Admin site..."
    New-SPSite -url "http://$customerName.sharepoint.com/admin" -SiteSubscription $sub -HostHeaderWebApplication $webApp -owneralias $customerTenantAdmin -owneremail $customerTenantAdminEmail -template tenantadmin#0 -AdministrationSiteType TenantAdministration

Now we can move on to the configuration of the Service Applications. Remember that our old ‘global’ settings no longer make sense. We have cmdlets for setting these on a per-tenant basis. FIrst we will check to see if the tenant is using the Foundation Feature Pack, and if not configure the SAs. (BDC and SSO have no per tenant config that is set upfront). We also need to create a MySite host site collection before we can configure the UPA:

# everything else needs standard
    if (!($customerFeatures -eq $foundationFeatures))
        Write-Host "Tenant has SharePoint Server features"
        # create a mysite host
        Write-Host "Creating My Site Host..."
        New-SPSite -url "http://$customerName.sharepoint.com/mysites" -SiteSubscription $sub -HostHeaderWebApplication $webApp -owneralias $customerTenantAdmin -owneremail $customerTenantAdminEmail -template SPSMSITEHOST#0

At this stage we can configure the UPA. Note the –SynchronizationOU parameter is a plain string value. We could enter a DN here, but it won’t work properly, so don’t!

        # configure the MySites host, MySites path, Naming Resolution and Profile Sync OU for the Subscription
        Write-Host "Configuring Tenant Profile Config..."
        $upaProxy = Get-SPServiceApplicationProxy | where-object {$_.DisplayName -eq $upaProxyName}
        Add-SPSiteSubscriptionProfileConfig -id $sub -SynchronizationOU $customerName -MySiteHostLocation "http://$customerName.sharepoint.com/mysites" -MySiteManagedPath "/mysites/personal" -SiteNamingConflictResolution "None" -ProfileServiceApplicationProxy $upaProxy

Next up is to create a site collection for the content type hub and configure the MMS service app.

        # create a site for the Content Type Gallery
        Write-Host "Creating Content Type Gallery..."
        New-SPSite -url "http://$customerName.sharepoint.com/cthub" -SiteSubscription $sub -HostHeaderWebApplication $webApp -owneralias $customerTenantAdmin -owneremail $customerTenantAdminEmail -template sts#0

        # configure the Content Type Gallery for the Subscription
        Write-Host "Configuring Tenant Content Type Gallery..."
        $mmsProxy = Get-SPServiceApplicationProxy | where-object {$_.DisplayName -eq $mmsProxyName}
        # ContentTypeHub feature activation may fail - if so activate manually
        Set-SPSiteSubscriptionMetadataConfig -identity $sub -serviceProxy $mmsProxy -huburi "http://$customerName.sharepoint.com/cthub" -SyndicationErrorReportEnabled
        Write-Host "Activating Content Type Hub..."
        Enable-SPFeature -Identity ContentTypeHub -url "http://$customerName.sharepoint.com/cthub"
    Write-Host "Tenant Provisioned!"
    return $sub;

That’s it! Now we can call our function for each tenant to provision them:

# customer details - do this for each tenant
$customerName = "Microsoft"
$customerTenantAdmin = "SHAREPOINT\msadmin"
$customerTenantAdminEmail = "msadmin@sharepoint.com"
$customerFeatures = $enterpriseFeatures

ProvisionTenant $customerName $customerTenantAdmin $customerTenantAdminEmail $customerFeatures $hostingMainURL $upaProxyName $mmsProxyName $foundationFeatures

$customerName = "Oracle"
$customerTenantAdmin = "SHAREPOINT\oadmin"
$customerTenantAdminEmail = "oadmin@sharepoint.com"
$customerFeatures = $standardFeatures

ProvisionTenant $customerName $customerTenantAdmin $customerTenantAdminEmail $customerFeatures $hostingMainURL $upaProxyName $mmsProxyName $foundationFeatures

$customerName = "Apple"
$customerTenantAdmin = "SHAREPOINT\appleadmin"
$customerTenantAdminEmail = "appleadmin@sharepoint.com"
$customerFeatures = $foundationFeatures

ProvisionTenant $customerName $customerTenantAdmin $customerTenantAdminEmail $customerFeatures $hostingMainURL $upaProxyName $mmsProxyName $foundationFeatures

It’s all very simple and reliable. The entire script has been tested over 3,000 times with no failures on a variety of different servers. Of course the scenario is contrived for the purposes of explanation, however with minor tweaks it can be modified to suit.

In the final instalment, I’ll talk through some testing considerations, along with other things that are critical to bear in mind when looking at implementing multi-tenant solutions with SharePoint 2010.

If you want the complete script, it is available on the TechNet Script Center.