Customizing SharePoint 2013 personal sites using SP Apps with remote provisioning


Remote provisioning is model, where we use the SharePoint App model to customize or provision SharePoint sites or elements to sites. This pattern was briefly explained in my previous post, where personal site customization was also mentioned as one of the options. Check more details on the model from following post.

[Updated on 9th of September with link to download the video for offline viewing] 

In this post, we’ll concentrate on actual details on how the personal my site (SkyDrive Pro) branding can be changed on on-premises or in Office365 using remote provisioning model. Pattern provides us capability to change the branding elements in the sites without need to deploy any customizations to SharePoint in farm level. Lack of the need to manipulate customizations in farm level is obviously beneficial from operation and maintenance perspective. One of the key benefits also on this model is that we can use exact same approach in both on-premises and in Office365, which has positive impact on our development efforts and we don’t have to consider different patterns based on used platform.

I’m briefly going to explain the model with video on demonstrating the required code and approach. You can also download example code, which is provide hosted SP app for modifying the personal site user experience. As always, these demos are more a proof of concept code than ready to be used in production, so please use it as reference, but don’t install that directly to your environment.

Introduction to model

Like with previous SharePoint versions, personal sites (or my sites) consists actually from two different core pieces. These are the my site host and the actual personal site, which are both their own site collections. My site host is one site collect, which has most of the dynamic capabilities from end user perspective, like social feeds and user profile edit. All the capabilities on my site host are dynamic in that sense that shown content is dependent on who is actually accessing the page. Personal site is then actually separate site collection to manage the storage requirements independently per user, since by separating personal my site to own site collection, SharePoint can use normal quota templates to manage the storage and individual site collections can be assign to different databases in SQL Server level.

Personal site creation works in a way that when user first accesses the my site host site, actual personal site (SkyDrive Pro) creation is scheduled as timer job task. Actual site collection creation will be done then by a separate timer job running in web application level, based on these queued requests from end users.

SharePoint 2013 includes new Social CSOM API, which can be used to provision the personal my site or also to access the personal my site remotely, which is actually the key for changing the branding elements in the site. By using this API set, you can built a pretty simple provider hosted application, which will actually modify personal sites or SkyDrive Pro experience based on company branding. After code has been written, it’s just ensuring that it’s executed for the users properly, by placing app part to for example public my site host page or just as well you could place the same app part to front page of the intranet, to ensure that branding is applied or updated when end users access the SharePoint.

 

image

 

Let’s open up the numbers one by one.

  1. Provider hosted app is deployed to SharePoint with app part. App part is installed to same context where the user profile service is running, so that personal site location can be resolved by the social API. When user accesses the page with app part from provider hosted app, we also load the context of the provider hosted app and execute the page in browser.
  2. Actual code is running in IIS as ASP.net application and in page code behind, we use SharePoint 2013 Social API to resolve the user’s personal my site or we could also create it.
  3. Actual branding can be applied using theming or custom master pages. Themes are more recommended approach with planned backwards compatibility in future, since master pages will have more significant impact from maintenance perspective. Themes are more cost friendly and provide nice capabilities for branding changes. We don’t recommend too highly customized personal sites, but if those are required, you can upload and apply custom master page to your personal site as well.
  4. When user moves to personal my site side, we can see the applied branding.

Key benefits of the model is that all customizations and code is actually running out side of the SharePoint context and therefore we can easily modify and change that based on changing requirements without impacting the other SharePoint functionalities. Even personal sites could be still created, but they just would not be branded, if for some reason our provider hosted application would not be available.

This pattern is providing us finally alternative approach compared to the classic my site or personal site customization options using feature stapling, meaning that we can finally move forward from the model which Steve Peschka originally explained in SharePoint blog back in 2007.

 

Analyses of the required code

Required code is actually pretty simple due the exposed user profile API set. We essentially use the client side API to get our hands to root web of the personal site root web and start applying required changes. Key part is following few lines, which we can use for resolving the instance to personal SkyDrive Pro site collection.

   // Get user profile
   ProfileLoader loader = Microsoft.SharePoint.Client.UserProfiles.ProfileLoader.GetProfileLoader(clientContext);
   UserProfile profile = loader.GetUserProfile(); ;
   Microsoft.SharePoint.Client.Site personalSite = profile.PersonalSite;

   clientContext.Load(personalSite);
   clientContext.ExecuteQuery();

After above lines, it’s just matter of actually applying theme or uploading required artefacts to the site collection, like master pages and css files. Social API can be also used to actually create the personal my sites in the context of the user. This would be beneficial in cases where you’d like to completely override the personal site creation process, but since we can however automate the branding elements to be be applied to my sites for example from front page of the intranet, there’s would really not be a exact need to do so.

 

Demonstration of the branding handling for personal sites (SkyDrive Pro)

In this video, we’ll demonstrate the code usage in practice. Demo has been done using on-premises SharePoint farm, but same code actually also works as such with Office365.

Video is available for download from my SkyDrive if needed for offline purposes.

 

Summary

SharePoint 2013 provides the new Social CSOM APIs, which enables us pretty interesting capabilities also from personal site branding perspective. Like mentioned previously in this post, key benefit of this pattern is the fact that you can use the same code structure or approach in both on-premises and in Office365. Approach is pretty different compared to traditional farm or full trust solutions, but this way we don’t actually need to install any feature framework features to SharePoint servers and even if we would update our code, there would not be any downtime or other issues caused  by solution package deployments.

Cloud application model or SP Apps pattern provides interesting alternatives with clear benefits compared to the old patterns. Approaches are dramatically different than what they used to be in previous versions, but like always with IT, end result counts and if we can achieve similar end results from end user perspective, with smaller footprint on the actual SharePoint farm, all parties will win. Business owners will love the availability increase for SharePoint services, IT administrators will love that there’s no code deployed to their farm, developers will love the fact that they can write the same once for asp.net and it will work in all locations… and end user will love the awesome capabilities of SharePoint, like always.

 

References

Few useful links and recourses

Comments (16)

  1. mark says:

    Great post, thank you

  2. Joseph Saad says:

    I am impressed on how simple and effective that is. Thank you Vesa for an Excellent Post. I can also confirm that this worked fine in SPOL.

  3. JosephSaad says:

    I transformed part of the code to PowerShell using CSOM just as a POC. It is still recommended (or actually the only way) that you run this in the user context as Vesa describes in the article and video.

    PowerShell is not optimum as there are many challenges that cannot be address due to nature of "Personal Sites" SharePoint Online:

    1) Not having a single account that has enough privileges across all Personal sites complicated with the fact that in SharePoint Online, you cannot have a user policy across the web application.

    2) Difficulty in enumerating the Personal site collections (Get-SPOSite does not help).

    This code is provided as-is with no warranties.

    Careful with this code, as the theme seem to brick the private personal site.

    #Code

    $password = convertto-securestring -AsPlainText -Force -String  "****1234"

    # Load the SharePoint 2013 .NET Framework Client Object Model libraries.

    #

    Add-Type –Path "C:Program FilesSharePoint Online Management ShellMicrosoft.Online.SharePoint.PowerShellMicrosoft.SharePoint.Client.dll"

    Add-Type –Path "C:Program FilesSharePoint Online Management ShellMicrosoft.Online.SharePoint.PowerShellMicrosoft.SharePoint.Client.Runtime.dll"

    add-type -Path "C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions15ISAPIMicrosoft.SharePoint.Client.UserProfiles.dll"

    $siteUrl = “https://tenant.sharepoint.com/

    $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)

    #Not needed if running from within an App and the user context.

    $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials("SharePoint.Demo03@tenant.onmicrosoft.com", $password)

    $ctx.Credentials = $credentials

    $loader = [Microsoft.SharePoint.Client.UserProfiles.ProfileLoader]::GetProfileLoader($ctx)

    $ctx.load($loader)

    $ctx.ExecuteQuery()

    $profile1 = $loader.GetUserProfile()

    $ctx.load($profile1)

    $ctx.ExecuteQuery()

    if ($profile1.PersonalSiteInstantiationState -eq "Uninitialized")

    {

       #Avoid using Sync Method for creating the Personal Site

       $profile1.CreatePersonalSiteEnque($true);

       $ctx.ExecuteQuery();

       Write-Host ("No my site exists. Currently provisioning…");

    }

    $personalSite = $profile1.PersonalSite

    $ctx.load($personalsite)

    $ctx.ExecuteQuery()

    $personalSite.ServerObjectIsNull

    $personalSite.Url

    $Subctx = New-Object Microsoft.SharePoint.Client.ClientContext($personalSite.Url)

    $subctx.Credentials = $credentials

    $web = $subCtx.Web

    $Subctx.load($web)

    $Subctx.ExecuteQuery()

    #Heads up

    #$palette = $web.ServerRelativeUrl+"/_catalogs/theme/15/palette008.spcolor"

    #$fontscheme = $web.ServerRelativeUrl+"/_catalogs/theme/15/fontscheme003.spfont"

    #This line does not work from PowerShell

    #$web.ApplyTheme($palette, $fontscheme, $null, $false)

    #Change of Master page is not recommended, but can be easily reverted for test purposes.

    #$web.CustomMasterUrl = "/_catalogs/masterpage/seattle.master"

    #$web.CustomMasterUrl = "/personal/sharepoint_demo01_tenant_onmicrosoft_com/_catalogs/masterpage/mysite15.master"

    $web.Update()

    $Subctx.ExecuteQuery()    

  4. JosephSaad says:

    I transformed part of the code to PowerShell using CSOM just as a POC. It is still recommended (or actually the only way) that you run this in the user context as Vesa describes in the article and video.

    PowerShell is not optimum as there are many challenges that cannot be address due to nature of "Personal Sites" SharePoint Online:

    1) Not having a single account that has enough privileges across all Personal sites complicated with the fact that in SharePoint Online, you cannot have a user policy across the web application.

    2) Difficulty in enumerating the Personal site collections (Get-SPOSite does not help).

    This code is provided as-is with no warranties.

    Careful with this code, as the theme seem to brick the private personal site.

    #Code

    $password = convertto-securestring -AsPlainText -Force -String  "****1234"

    # Load the SharePoint 2013 .NET Framework Client Object Model libraries.

    #

    Add-Type –Path "C:Program FilesSharePoint Online Management ShellMicrosoft.Online.SharePoint.PowerShellMicrosoft.SharePoint.Client.dll"

    Add-Type –Path "C:Program FilesSharePoint Online Management ShellMicrosoft.Online.SharePoint.PowerShellMicrosoft.SharePoint.Client.Runtime.dll"

    add-type -Path "C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions15ISAPIMicrosoft.SharePoint.Client.UserProfiles.dll"

    $siteUrl = “https://tenant.sharepoint.com/

    $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)

    #Not needed if running from within an App and the user context.

    $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials("SharePoint.Demo03@tenant.onmicrosoft.com", $password)

    $ctx.Credentials = $credentials

    $loader = [Microsoft.SharePoint.Client.UserProfiles.ProfileLoader]::GetProfileLoader($ctx)

    $ctx.load($loader)

    $ctx.ExecuteQuery()

    $profile1 = $loader.GetUserProfile()

    $ctx.load($profile1)

    $ctx.ExecuteQuery()

    if ($profile1.PersonalSiteInstantiationState -eq "Uninitialized")

    {

       #Avoid using Sync Method for creating the Personal Site

       $profile1.CreatePersonalSiteEnque($true);

       $ctx.ExecuteQuery();

       Write-Host ("No my site exists. Currently provisioning…");

    }

    $personalSite = $profile1.PersonalSite

    $ctx.load($personalsite)

    $ctx.ExecuteQuery()

    $personalSite.ServerObjectIsNull

    $personalSite.Url

    $Subctx = New-Object Microsoft.SharePoint.Client.ClientContext($personalSite.Url)

    $subctx.Credentials = $credentials

    $web = $subCtx.Web

    $Subctx.load($web)

    $Subctx.ExecuteQuery()

    #Heads up

    #$palette = $web.ServerRelativeUrl+"/_catalogs/theme/15/palette008.spcolor"

    #$fontscheme = $web.ServerRelativeUrl+"/_catalogs/theme/15/fontscheme003.spfont"

    #This line does not work from PowerShell

    #$web.ApplyTheme($palette, $fontscheme, $null, $false)

    #Change of Master page is not recommended, but can be easily reverted for test purposes.

    #$web.CustomMasterUrl = "/_catalogs/masterpage/seattle.master"

    #$web.CustomMasterUrl = "/personal/sharepoint_demo01_tenant_onmicrosoft_com/_catalogs/masterpage/mysite15.master"

    $web.Update()

    $Subctx.ExecuteQuery()    

  5. Sonja Madsen, SONJASAPPS says:

    I chose another approach. I made a magic web part that can hide QuickLaunch. I plan to made another that can do the branding: Read it here sp2013.blogspot.com/…/sharepoint-mysite-hide-navigation-blog.html

    or watch the video at http://www.youtube.com/watch

  6. Terry Hagan says:

    Is there a particular reason for making this a Provider Hosted app? Could it also be an Auto Hosted app?

    thanks

    Terry

  7. sonofthesun says:

    Hi Terry,

    it could be just as well auto hosted app. Reason why I did provider hosted was to give me one instance to maintain and configure cross my tenants. Auto hosted out result multiple different instances, which I could certainly also version using native versioning tools, but thought that from code maintenance perspective provider hosted would make more sense.

    You could actually do something similar also just by using SP Hosted apps… APIs are there for JavaScript as well.

  8. Sean T Douglas says:

    Joseph,

    You can use the applytheme method for the web object in PowerShell by passing in Null values instead of trying to use the $NULL variable.  I have been creating a script to automate the provisioning and branding of Team Sites using the same method.  Here is the line of code you need:

    $web.ApplyTheme($palette, $fontscheme, [NullString]::Value, $false)

    The Null value is passed using the [NullString]::Value statement.

    You can also substitute a null value for the the fontscheme parameter as well.

  9. Josh Petryk says:

    I am getting 403 errors when it gets to your ExecuteQuery statements. I don't see where authentication happens in your source code, where are you getting ACS tokens for the client contexts?

  10. sonofthesun says:

    Hi Josh,

    Detailed answer highly depends on your hosting Platform and are you using S2S or ACS for providing the tokens… if you're using Office365, please use the updated sample available from blogs.msdn.com/…/office365-apply-automatically-custom-branding-to-personal-site-skydrive-pro.aspx

  11. JoshPetryk says:

    Thanks for the quick response! I was able to get this somewhat working. Will definitely check out your new post. Yes, we are on Office365.

  12. Dylan says:

    I got the ProfileLoader API working, but how do I get the personal site for other users in the site, using, e.g., the global administrator credentials?

  13. Dylan says:

    Actually, you can use UserProfiles.PeopleManager.GetPropertiesFor( ).PersonalUrl to get the personal URL for each user, just make sure you use the full claims LoginName of each user (which you can get from context.Web.SiteUsers).

  14. Stephanie says:

    Will this work if you don't have oAuth set up?

  15. sonofthesun says:

    Hi Stephanie,

    this particular version would actually work without oAuth, since the authentication in this version of the code is done directly using particular user identity without oAuth.

  16. Stephanie says:

    Hi Vesa,

    Sorry I meant to come back and say I figured it out, I accidentally downloaded the wrong code set from your onedrive which caused a bit of confusion for me.

    Thanks for the response.