Thursday, August 7, 2014

Reactivate Web-scoped features from PowerShell using client object model in Sharepoint Online

Suppose that you have sandbox solution which you install on Sharepoint Online site with custom features. Some of these features has Site scope and activated automatically when solution is activated in solutions gallery (it works by design). Another features have Web scope and activated on particular web site. If you added reference to your features to custom web template, these features will be automatically activated when you will create site based on this web template.

However there is interesting behavior of Web-scoped features provisioned from sandbox solution: when you deactivate solution in solutions gallery, these features become deactivated as well. You may check it with the following PowerShell script:

Get-SPWeb http://example.com -Limit ALL | %{ Get-SPFeature -Web $_ } | Sort DisplayName -Unique | FT DisplayName,Id

It may introduce problems because some functionality may not work after that. In order to fix it you will most probably need to reactivate these features on all sites where they were activated before solution update. In order to do it on Sharepoint Online we need to use client object model:

   1: function Activate-Feature-On-All-Subsites-Impl($ctx, $web, $templateToMatch,
   2: $featureId)
   3: {
   4:     $allProperties = $web.AllProperties
   5:     $ctx.Load($allProperties)
   6:     $ctx.ExecuteQuery()
   7:  
   8:     $wt = $allProperties["webtemplateid"]
   9:  
  10:     Write-Host "Check site" $web.Url "with template" $wt -foregroundcolor green
  11:     if ($wt -eq $templateToMatch)
  12:     {
  13:         $features = $web.Features
  14:         $ctx.Load($features)
  15:         $ctx.ExecuteQuery()
  16:  
  17:         $id = new-object System.Guid $featureId
  18:         $features.Add($id, $true,
  19: [Microsoft.SharePoint.Client.FeatureDefinitionScope]::Site)
  20:         $ctx.ExecuteQuery()
  21:     }
  22:  
  23:     $subWebs = $web.Webs
  24:     $ctx.Load($subWebs)
  25:     $ctx.ExecuteQuery()
  26:     $subWebs | ForEach-Object { Activate-Feature-On-All-Subsites-Impl $ctx $_
  27: $templateToMatch $featureId }
  28: }

This script assumes that that custom web template id is stored in “webtemplateid” property in SPWeb property bag. You may add it by the following feature element to the feature which will be added to particular onet.xml:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <PropertyBag ParentType="Web">
   4:     <Property Name="webtemplateid" Type="string" Value="WTWorkspaceSmallProject" />
   5:   </PropertyBag>
   6: </Elements>

Function goes through all sub sites and if site’s template matches passed template, it will reactivate feature there. The most tricky moment here is that although your features have Web scope, you need to pass FeatureDefinitionScope.Site enum value to FeatureCollection.Add method. If you will try to pass FeatureDefinitionScope.Web there, it will throw exception:

Exception calling "ExecuteQuery" with "0" argument(s): "Feature with Id … is not installed in this farm, and cannot be added to this scope."

In Sharepoint 2010 there was a bug that FeatureDefinitionScope enum didn’t have Web value. It seems like that even it is added in Sharepoint 2013 not all bugs are still fixed here and you need to pass Site when activate features with Web scope.

With above function you may activate features with Web scope on all sub sites like that:

   1: $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteURL)
   2: $ctx.AuthenticationMode =
   3: [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
   4: $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
   5: $credentials =
   6: New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username,
   7: $securePassword)
   8: $ctx.Credentials = $credentials
   9:  
  10: $web = $ctx.Web
  11: $site = $ctx.Site
  12: $ctx.Load($web)
  13: $ctx.Load($site)
  14: $ctx.ExecuteQuery()
  15:  
  16: Activate-Feature-On-All-Subsites-Impl $ctx $ctx.Web "MyTemplate" $featureId

Hope that this information will be useful.

No comments:

Post a Comment