harbar.net component based software & platform hygiene

Rational Guide to Multi Tenancy with SharePoint 2010, Part Five: Creating Partitioned Service Applications

Print | posted on Monday, September 13, 2010 2:30 AM

 

Introduction

This, the fifth part of the Rational Guide article on multi tenancy will walk through the creation of the partitioned service applications and the starting of their related service instances 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 (this article)
  6. Provisioning Tenants
  7. Testing the Functionality

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!


Before we get started

In the previous part we created the SharePoint Farm, created the core Service Applications and created and configured a Web Application and Managed Paths for hosting our tenant Site Collections. Now we are going to create the partitioned Service Applications and their related Service Instances.

There are many approaches to this particular area of SharePoint configuration with PowerShell. You will have no doubt seen other’s scripts in this arena on the Interwebz already. It is important to understand that there is no single best way. For this article I have chosen an approach which firstly keeps things deliberately simple (or as simple as it can be) to focus on the specifics for multi tenant, but also which in my opinion is a reasonable approach to doing things.

I will start with the “simple” service applications, namely Managed Metadata, Business Data Connectivity, Secure Store and Word Automation. These are simple to create and start. Then I move on to the more complex ones, namely Search and User Profiles. By using this order, you can guarantee every dependency is taken care of, and it also makes it easier to isolate and potential problems. Of course you may choose to do things in a different order dependant upon your requirements.

However you will note that not all Service Applications are created equal, and neither are their associated PowerShell cmdlets. I will point out the eccentricities as we go. Also I will stress once again, that for this scenario we are a single server farm and we are using a single (the default) Proxy Group. You will need to modify these scripts to deploy into a real farm of more than one machine, or using other Proxy Groups.

For the Service Instance work, I use a variable called $xxxxxInstanceName where xxxxx is the identifier for a given app. Don’t change this value, these are the product names used. In the real world we would include code to check that the service instance is started before continuing, but again this is omitted for the sake of simplicity.

 

OK, let’s get to it. Before we start, we need to grab the application pool we created earlier in which to host the service application endpoints:

# app pool
$saAppPoolName = "SharePoint Web Services Default"
$appPoolUserName = "SHAREPOINT\spservices"

# Gets app pool or quits
Write-Host "Getting Application Pool..."
$saAppPool = Get-SPServiceApplicationPool -Identity $saAppPoolName -EA 0
if($saAppPool -eq $null)
{
    Write-Host "Cannot find the Application Pool $appPoolName, please ensure it exists before continuing."
    Exit -1	
}

 


Managed Metadata

The Managed Metadata Service Application is pretty straightforward. We simply include the –PartitionMode switch into the commands to create both the Service Application and it’s Proxy. Note that we use the –DefaultProxyGroup switch on the Proxy. Also note the comments for the other MMS specific options on the Proxy, you can set those as you need. Once we are done with those, we simply start the Metadata Web Service service instance.

# MMS specifics
$mmsInstanceName = "MetadataWebServiceInstance"
$mmsName = "Hosting Farm Tenant Managed Metadata Service"
$mmsDBName = "HostingFarm_Metadata"

<# Sets up Managed Metadata service instance & service app and proxy 
    -ContentTypePushdownEnabled, -DefaultKeywordTaxonomy, -DefaultSiteCollectionTaxonomy can be configured on the Proxy
    -ContentTypeSyndicationEnabled is always true in parition mode
    -DefaultKeywordTaxonomy is true if not specified
#>
Write-Host "Creating $mmsName Application & proxy..."
$mms = New-SPMetadataServiceApplication -PartitionMode -Name $mmsName -ApplicationPool $saAppPoolName -DatabaseName $mmsDBName
$proxy = New-SPMetadataServiceApplicationProxy -PartitionMode -Name "$mmsName Proxy" -ServiceApplication $mms -DefaultProxyGroup
Write-Host "Starting the $mmsInstanceName..."
Get-SPServiceInstance | where{$_.GetType().Name -eq $mmsInstanceName} | Start-SPServiceInstance
Write-Host "MMS Done!"

Business Data Connectivity

BDC is similar, but has a couple of gotchas! This time, when we create the Service Application, this will also create a proxy and add it to the default proxy group. There is a bug with the cmdlet for creating the BDC proxy – this only works with the PartitionMode switch if used to create a connection to a published service app. Yup! nasty.

# BDC specifics
$bdcInstanceName = "Business Data Connectivity Service"
$bdcName = "Hosting Farm Tenant Business Data Connectivity Service"
$bdcDBName = "HostingFarm_BDC"

<# Sets up Business Data Connectivity Service Application and Proxy and Service Instance
       This command automatically creates a proxy and adds it to the default proxy group - therefore we cannot change it's name
       We can't use New-SPBusinessDataCatalogServiceApplicationProxy as the -PartitionMode param only works for creating a proxy connection to a published app
#>
Write-Host "Creating $bdcInstanceName Application and Proxy..."
$bdc = New-SPBusinessDataCatalogServiceApplication -PartitionMode -Name $bdcName -ApplicationPool $saAppPoolName -DatabaseName $bdcDBName
Write-Host "Starting the $bdcInstanceName Instance..."
Get-SPServiceInstance | where-object {$_.TypeName -eq $bdcInstanceName} | Start-SPServiceInstance
Write-Host " BDC Done!"

Secure Store

The Secure Store service has some of it’s own wackiness, it’s quite comical. It’s proxy creation cmdlet doesn't have a –PartitionMode switch at all, but it will work without one. Be advised thou that you cannot create a proxy connection to a published partitioned Secure Store service app. Don’t you just love the consistency : )!

# SSS Specifics
$sssInstanceName = "Secure Store Service"
$sssName = "Hosting Farm Tenant Secure Store Service"
$sssDBName = "HostingFarm_SecureStore"

<# Sets up Secure Store Service Application & Proxy and Service Instance
        we must set -auditingEnabled param, the others are optional
        -logsizemax is days to keep the logs, default if not set is 0
        -sharing means "can the SA be published?"
        
        New-SPSecureStoreServiceApplicationProxy doens't have a -PartitionMode param!!!!
        It works without one, unless you try to create a proxy connection to a published app, which won't work! FAIL!
#>
Write-Host "Creating $sssName Application & Proxy..."
$sss = New-SPSecureStoreServiceApplication -PartitionMode -Name $sssName -ApplicationPool $saAppPoolName -DatabaseName $sssDBName -auditingEnabled:$true -auditlogmaxsize 30 -Sharing:$false
$proxy = New-SPSecureStoreServiceApplicationProxy -Name "$sssName Proxy" -ServiceApplication $sss -DefaultProxyGroup
Write-Host "Starting the $sssInstanceName Instance..."
$sssInstance = Get-SPServiceInstance | where-object{$_.TypeName -eq $sssInstanceName} | Start-SPServiceInstance
Write-Host "SSS Done!"

Word Automation

Word Automation is very simple. There is no cmdlet for creating a proxy, but we can use the –default parameter to add the one created for us to the Default Proxy Group.

# WAS specifics
$wasInstanceName = "Word Automation Services"
$wasName = "Hosting Farm Tenant Word Automation Service"
$wasDBName = "HostingFarm_WordAutomation"



<# Sets up Word Automation Service Application and Proxy and Service Instance
    there is no New-SPWordConversionServiceApplicationProxy, we can't therefore set it's name
    the -default parameter adds the automatically created proxy to the default proxy group
#>
Write-Host "Creating $wasName Application & Proxy..."
$was = New-SPWordConversionServiceApplication -PartitionMode -Name $wasName -ApplicationPool $saAppPool -DatabaseName $wasDBName -Default
Write-Host "Starting the $wasInstanceName Instance..."
Get-SPServiceInstance | where-object {$_.TypeName -eq $wasInstanceName} | Start-SPServiceInstance
Write-Host "WAS Done!"

Search

We are done with the simple ones, now we need to take a crack at the complex service applications. Search is probably the one with the most moving parts. The script below is my favoured approach to creating it via PowerShell. Whilst we are in a single server environment, I’ve retained the necessary variables for supporting a real farm. Note however thou that there is almost unlimited flexibility with search roles and components now, so you will likely need to tweak further the script for things like additional search components.

The first step is to create the Service Application. This time the switch is -Partitioned (not -PartitionMode)!

I then deliberately do NOT create the proxy until all the various search components are provisioned and online. I’ve found this the safest way.

 

# Search Specifics, we are single server farm
$spServerName = "sph1" 
$AdminServer = $spServerName
$crawlServer = $spServerName
$queryServer = $spServerName
$serviceAppName = "Hosting Farm Tenant SharePoint Server Search Service"
$searchDBName = "HostingFarm_Search"




Write-Host "Creating $serviceAppName Application..."
#note this time it's -Partitioned not -PartitionMode!
$searchApp = New-SPEnterpriseSearchServiceApplication -Partitioned -Name $serviceAppName -ApplicationPool $saAppPool -DatabaseName $searchDBName

Write-Host "Starting the Instance and admin component..."
#turn on the instance
$AdminSearchInstance = Get-SPEnterpriseSearchServiceInstance | where {($_.server) -match $AdminServer} 
$admin = $searchApp | Get-SPEnterpriseSearchAdministrationComponent
                
if($AdminSearchInstance.Status -ne "Online")
{ 
    $AdminSearchInstance.Provision(); sleep 5;
}
$admin | Set-SPEnterpriseSearchAdministrationComponent -SearchServiceInstance $AdminSearchInstance 
        
$admin = ($searchApp | Get-SPEnterpriseSearchAdministrationComponent)
while (-not $admin.Initialized) 
{
  Start-Sleep 10
  $admin = ($searchApp | Get-SPEnterpriseSearchAdministrationComponent)
}
Start-SPEnterpriseSearchQueryAndSiteSettingsServiceInstance $AdminServer
                
# Save these for future removal
Write-Host "Getting topology..."
$InitialCrawlTopology = ($searchApp | Get-SPEnterpriseSearchCrawlTopology)
$InitialQueryTopology = ($searchApp | Get-SPEnterpriseSearchQueryTopology)

# Create crawl topology
Write-Host "Creating Crawl Topology..."
$CrawlTopology = ($searchApp | New-SPEnterpriseSearchCrawlTopology)
$CrawlDatabase = Get-SPEnterpriseSearchCrawlDatabase -SearchApplication $searchApp
            
# Populate crawl topology
Write-Host "Populating Crawl Topology..."
$crawlInstance = Get-SPEnterpriseSearchServiceInstance | where {($_.server) -match $crawlServer}
$CrawlComponent = New-SPEnterpriseSearchCrawlComponent -CrawlTopology $CrawlTopology -CrawlDatabase $CrawlDatabase -SearchServiceInstance $crawlInstance
                
# Activate Crawl Topology
Write-Host "Activating Crawl Topology..."
$CrawlTopology | Set-SPEnterpriseSearchCrawlTopology -Active

$InitialCrawlTopology.CancelActivate()
do {Start-Sleep 5;} while ($InitialCrawlTopology.State -ne "Inactive")
                
# Create query topology
Write-Host "Creating Query topology..."
$QueryTopology = ($searchApp | New-SPEnterpriseSearchQueryTopology -Partitions 1)
            
# Create Property Database
Write-Host "Creating Property Database..."
$PropertyDatabase = Get-SPEnterpriseSearchPropertyDatabase -SearchApplication $searchApp
            
# Map Property store to Crawl store
Write-Host "Mapping Property store to Crawl DB"
$IndexPartition = Get-SPEnterpriseSearchIndexPartition -QueryTopology $QueryTopology
$IndexPartition | Set-SPEnterpriseSearchIndexPartition -PropertyDatabase $PropertyDatabase
            
# Populate Query Topology
Write-Host "Populating Query Topology...."
$QueryInstance = Get-SPEnterpriseSearchServiceInstance | where {($_.server) -match $queryServer}
$QueryComponent = New-SPEnterpriseSearchQueryComponent -QueryTopology $QueryTopology -IndexPartition $IndexPartition -SearchServiceInstance $QueryInstance

# Activate query topology
Write-Host "Activating Query Topology..."
$QueryTopology | Set-SPEnterpriseSearchQueryTopology -Active
do {Start-Sleep 5;} while ($InitialQueryTopology.State -ne "Inactive")

# Cleanup initial topologies
Write-Host "Cleaning up initial topology..."
$InitialCrawlTopology | Remove-SPEnterpriseSearchCrawlTopology -confirm:$false
$InitialQueryTopology | Remove-SPEnterpriseSearchQueryTopology -confirm:$false


# Create proxy and add to Default proxy group
Write-Host "Creating Proxy..."
$searchProxy = New-SPEnterpriseSearchServiceApplicationProxy -Partitioned -Name "$serviceAppName Proxy" -SearchApplication $searchApp
Write-Host "Search Done!"

 


User Profile

User Profile is pretty simple, but there are two PowerShell related gotchas/bugs to be aware of.

First up, I’ll create the Service App and Proxy again in PartitionMode. Then I start the User Profile Service Instance, but NOT the User Profile Synchronization service instance.

# UPA specifics
$upaInstanceName = "User Profile Service"
$upsInstanceName = "User Profile Synchronization Service"
$upaName = "Hosting Farm Tenant User Profile Service"
$upaProfileDBName = "HostingFarm_Profile"
$upaSocialDBName = "HostingFarm_Social"
$upaSyncDBName = "HostingFarm_Sync"



<# Creates UPA Service Application & Proxy, and User Profile Service Instance
     If omitted, -ProfileSyncDBServer, -SocialDBServer & -ProfileDBServer are the SharePoint Default DB Server
     If omitted, -SyncInstanceMachine is the local machine 
#>
Write-Host "Creating $upaName Application & Proxy..."
$upa = New-SPProfileServiceApplication -PartitionMode -Name $upaName -ApplicationPool $saAppPoolName -ProfileDBName $upaProfileDBName -SocialDBName $upaSocialDBName -ProfileSyncDBName $upaSyncDBName
New-SPProfileServiceApplicationProxy -PartitionMode -Name "$upaName Proxy" -ServiceApplication $upa -DefaultProxyGroup
Write-Host "Starting the $upaInstanceName Instance..."
Get-SPServiceInstance | where-object {$_.TypeName -eq $upaInstanceName} | Start-SPServiceInstance

Write-Host " UPA Done!"

Now, there is a bug with the above provisioning of the service application, which results in the wrong default schema for the Farm account on the Sync DB. This only occurs when creating the service app using PowerShell. We MUST fix this up before attempting to start the UPS service instance. If we don’t UPS provisioning will fail.

We MUST make the default schema for the Farm account on this database DBO. We can do that through SQL Server Management Studio, or from T-SQL or through PowerShell:

$upaSyncDBName = "HostingFarm_Sync"

Invoke-sqlcmd -Database $upaSyncDBName -Query "ALTER USER [SHAREPOINT\spfarm] WITH DEFAULT_SCHEMA=dbo;"

 

Please note that the above is an unsupported change to a SharePoint database, and you should not be doing it. To create the UPA with Windows PowerShell and avoid this blocker to starting the UPS service instance, please read Avoiding the Default Schema issue when creating the User Profile Service Application using Windows PowerShell. This post details the problem, and provides two PowerShell scripts – one for non UAC environments, and one for UAC environments.

Ensure that you change the user to match your farm account. Oh, and you must be doing this on the SQL server of course! :)

Now, we can start the UPS service instance, we could do this from Central Administration of course, but there is no CmdLet for doing this. You may see a script on TechNet that does this. But I say, NO NO NO! I hate that script. Mine is much better :) and replicates exactly what clicking Start in the UI does.

Before running this part, you must meet the requirements for UPS service identity. I’ve added a couple of nag prompts. If you haven’t got that stuff set up properly UPS won’t provision. Don’t say I didn’t warn you!

 

 # we need these to start the UPS Service Instance, UPS doens't do managed accounts, and yes the password is in clear text
$farmAccount = "SHAREPOINT\spfarm"
$farmPassword = "Password1"

# UPA/S specifics
$upsInstanceName = "User Profile Synchronization Service"
$upaName = "Hosting Farm Tenant User Profile Service"


$a = Read-Host "Have you fixed the default schema of the Farm Account on the SyncDB?"
$a = Read-Host "Have you added the Farm Account as a local administrator?"

Write-Host "Restarting SPTimerV4..."
restart-service SPTimerV4

Write-Host "Starting the $upsInstanceName Instance..."
$upa = Get-SPServiceApplication | where-object {$_.Name -eq $upaName}
Get-SPServiceInstance | where-object {$_.TypeName -eq $upsInstanceName} | % {
    $_.Status = [Microsoft.SharePoint.Administration.SPObjectStatus]::Provisioning
    $_.IsProvisioned = $false
    $_.UserProfileApplicationGuid = $upa.Id
    $_.Update()
    $upa.SetSynchronizationMachine($_.Server.Address, $_.Id, $farmAccount, $farmPassword) # this causes update conflicts
    Start-SPServiceInstance $_
}



Write-Host "Waiting on $upsInstanceName to provision... We could be some time... Make a cuppa, listen to Run like Hell..."
Write-Host "Baseline time is 270 seconds"
[int]$time = 0
$ups = Get-SPServiceInstance | where-object {$_.TypeName -eq $upsInstanceName}
while(-not ($ups.Status -eq "Online")){
   	sleep 10;
    Write-Host "Still waiting... ($time seconds elapsed)"
    $ups = Get-SPServiceInstance | where-object {$_.TypeName -eq $upsInstanceName}
    $time = $time + 10
  }
Write-Host "$upsInstanceName provisioned, it took $time seconds, resetting IIS..."
iisreset
Write-Host "Done!"
Write-Host "Don't forget to remove the Farm Account from local admins!"
Write-Host "Tenant Farm Created!"

Don’t forget to remove the farm account from local admins at this stage. If UPS takes more than five minutes to provision you have done something wrong with the pre-reqs. For details of those check out the Rational Guide to SharePoint 2010 User Profile Synchronization.

 

 


Summary

Great, so now we have a farm setup and configured with each of the service applications that support it configured for Multi-Tenancy. The next part will cover the configuration of Feature Packs and the provisioning of tenants. Stay Tuned!

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