Second Life of a Hungarian SharePoint Geek

February 19, 2015

How to Programmatically “Enable reporting of offensive content” on a community site

Filed under: PowerShell, SP 2013 — Peter Holpar @ 23:10

Recently a question was posted on sharepoint.stackexchange.com about how to “Enable reporting of offensive content” from code on SharePoint 2013 community sites.

image

The Community Settings page (CommunitySettings.aspx) has a code behind class Microsoft.SharePoint.Portal.CommunitySettingsPage. In its BtnSave_Click method it depends on the static EnableDisableAbuseReports method of the internal FunctionalityEnablers class to perform the actions required for reporting of offensive content. Furthermore, it sets the value of the vti_CommunityEnableReportAbuse web property to indicate that reporting is enabled for the community site.

To perform the same actions from PowerShell I wrote this script:

$site = New-Object Microsoft.SharePoint.SPSite("http://YourServer/CommunitySite")
$web = $site.OpenWeb()

# this command has only effect to the check box on the Community Settings page
$web.AllProperties["vti_CommunityEnableReportAbuse"] = "true"
$web.Update()

# the functionality itself is activated by the code below
# get a reference for the Microsoft.SharePoint.Portal assembly
$spPortalAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | ? { $_.Location -ne $Null -And $_.Location.Split(‘\\’)[-1].Equals(‘Microsoft.SharePoint.Portal.dll’) }
$functionalityEnablersType = $spPortalAssembly.GetType("Microsoft.SharePoint.Portal.FunctionalityEnablers")
$mi_EnableDisableAbuseReports = $functionalityEnablersType.GetMethod("EnableDisableAbuseReports")
$mi_EnableDisableAbuseReports.Invoke($null, @($spWeb, $True))

Note: If you use “false” instead of “true” when setting the value of vti_CommunityEnableReportAbuse, and $False instead of $True when invoking the static method in the last line of code, then you can inactivate the reporting for the site.

The alternative solution is to use the server side API from C#:

web.AllProperties["vti_CommunityEnableReportAbuse"] = "true";
web.Update();

// get an assembly reference to "Microsoft.SharePoint.Portal" via an arbitrary public class from the assembly
Assembly spPortalAssembly = typeof(Microsoft.SharePoint.Portal.PortalContext).Assembly;

Type functionalityEnablersType = spPortalAssembly.GetType("Microsoft.SharePoint.Portal.FunctionalityEnablers");
MethodInfo mi_EnableDisableAbuseReports = functionalityEnablersType.GetMethod("EnableDisableAbuseReports");
mi_EnableDisableAbuseReports.Invoke(null, new object[] { web, true });

We can verify the effect of our code by checking the columns in the first view of the Discussions List before enabling the reporting via this PowerShell script:

$list = $web.Lists["Discussions List"]
$list.Views[0].ViewFields

I found these ones:

Threading
CategoriesLookup
Popularity
DescendantLikesCount
DescendantRatingsCount
AuthorReputationLookup
AuthorNumOfRepliesLookup
AuthorNumOfPostsLookup
AuthorNumOfBestResponsesLookup
AuthorLastActivityLookup
AuthorMemberSinceLookup
AuthorMemberStatusIntLookup
AuthorGiftedBadgeLookup
LikesCount
LikedBy

And after we enable reporting. Four further columns should be appended to the list of view fields if the code succeeded:

AbuseReportsCount
AbuseReportsLookup
AbuseReportsReporterLookup
AbuseReportsCommentsLookup

Or we can invoke the static IsReportAbuseEnabled method of the internal Microsoft.SharePoint.Portal.CommunityUtils class to verify if reporting is enabled, just as the OnLoad method of the Microsoft.SharePoint.Portal.CommunitySettingsPage does. You should know however, that this method does not more as simply to check the value of the vti_CommunityEnableReportAbuse web property, so even if it returns true, it does not mean for sure that reporting is really enable. So I prefer checking the columns in view as shown earlier.

The PowerShell version:

$site = New-Object Microsoft.SharePoint.SPSite("http://YourServer/CommunitySite")
$web = $site.OpenWeb()

$communityUtilsType = $spPortalAssembly.GetType("Microsoft.SharePoint.Portal.CommunityUtils")
$mi_IsReportAbuseEnabled = $communityUtilsType.GetMethod("IsReportAbuseEnabled")
$mi_IsReportAbuseEnabled.Invoke($null, @($spWeb))

The C# version:

// get an assembly reference to "Microsoft.SharePoint.Portal" via an arbitrary public class from the assembly
Assembly spPortalAssembly = typeof(Microsoft.SharePoint.Portal.PortalContext).Assembly;

Type communityUtilsType = spPortalAssembly.GetType("Microsoft.SharePoint.Portal.CommunityUtils");
MethodInfo mi_IsReportAbuseEnabled = communityUtilsType.GetMethod("IsReportAbuseEnabled");
mi_IsReportAbuseEnabled.Invoke(null, new object[] { web });

February 18, 2015

Strange (?) Behavior of the Client Object Model You should be Aware of

Filed under: Managed Client OM, PS 2013, SP 2010, SP 2013 — Tags: , , , — Peter Holpar @ 00:28

Note: The code examples in this post are based on the managed client OM (C#), however, you can expect the same behavior when working with the JavaScript / Silverlight versions of the client OM, both for SharePoint 2010 and 2013, furthermore, the behavior I describe applies to the client OM of Project Server 2013 as well.

Assume you have a SharePoint list including two fields, Title is a text column, and User is a field of type People or Group. You would like to update both of these fields via the client object model.

In the first case we simply set fix values for the item with Id=1, the text ‘Test’ for Title and an integer (2) as user Id for the User field.

using (var context = new ClientContext(webUrl))
{
  var web = context.Web;
  var list = web.Lists.GetByTitle("TestList");

  var item = list.GetItemById(1);
  item["Title"] = "Test";

  var userValue = new FieldUserValue();
  userValue.LookupId = 2;
  item["User"] = userValue;

  item.Update();
 
  context.ExecuteQuery();
}

Everything works as expected, both of the fields are updated by the query.

In the second case, we would like to set the same values for the item with Id=2, but in this case we get the user Id from the Id of the current user (that has “accidentally” the value 2 in this case). To get that value, we call the ExecuteQuery in the middle of the code, after setting the Title field, and before setting the User field and before invoking the Update method on the item. Finally we invoke the ExecuteQuery method again.

using (var context = new ClientContext(webUrl))
{
  var web = context.Web;
  var list = web.Lists.GetByTitle("TestList");

  var item = list.GetItemById(2);
  item["Title"] = "Test";

  var userValue = new FieldUserValue();
  var cu = web.CurrentUser;
  context.Load(cu, u => u.Id);
  context.ExecuteQuery();

  userValue.LookupId = cu.Id;
  item["User"] = userValue;

  item.Update();
 
  context.ExecuteQuery();
}

One could expect that both of the fields would be updated, however, the Title field won’t be in fact, and we will understand why, after having a look at the requests (for example, using Fiddler) sent by the client OM.

In the first case, there is a single request that includes setting both of the fields (see the SetFieldValue methods in the screenshot below, one for each field) and invoking the Update method.

image

In the second case there are two requests sent by the client OM, one for each in ExecuteQuery code.

The first request includes a single SetFieldValue method for the Title field, the query for the Id of the current user is not included in the screenshot for the sake of simplicity:

image

The second request includes a single SetFieldValue method for the User field, and invoking the Update method.

image

You can see, that calling ExecuteQuery for the the Id of the current user submits the change of the Title field as well, and thereby clears the “dirty” flag of the field (that means it won’t be re-submitted by the OM once again in later requests), but due to the lack of invoking the Update method in this request, this value change has no effect, it is simply not persisted. In the second query we call the Update method, but in this request only the change in the User field was submitted, it has no effect on the Title field.

Lesson learned: The requests sent by the client OM are independent on the server side, no session state or context is persisted between the requests. Only data changed since the last request or required to be able to perform the current request on the server side are sent by the client OM. You should consider the code blocks between (or before) calling ExecuteQuery as they were separate server side console applications you try to run one after another. In this case you won’t expect either, that setting a field without calling the Update method may have any effect on the persisted value of the field. If you are aware of the client OM architecture internals, this is just the expected behavior. If you are not, you might have such surprises in the future.

February 17, 2015

Unhandled Exception in Gantt Chart View after Editing Calendar Item in Datasheet View

Filed under: Bugs, Calendar, SP 2010 — Tags: , , — Peter Holpar @ 22:48

The other day a user complained that since last week he receives an exception when navigating to a customized Calendar list in SharePoint.

The error details found in ULS logs and displayed on the web UI after turning custom errors off:

Unable to cast object of type ‘System.DBNull’ to type ‘System.String’.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidCastException: Unable to cast object of type ‘System.DBNull’ to type ‘System.String’.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below. 

Stack Trace:
[InvalidCastException: Unable to cast object of type ‘System.DBNull’ to type ‘System.String’.]
   Microsoft.SharePoint.WebControls.GanttV4.<NormalizeDateFields>b__3a(Nullable`1 value, DataRow dr, String col) +114
   Microsoft.SharePoint.WebControls.GanttV4.TransformDataTableColumns(IEnumerable`1 cols, Func`4 transform) +454
   Microsoft.SharePoint.WebControls.GanttV4.GenerateGridSerializer() +62
   Microsoft.SharePoint.WebControls.GanttV4.OnPreRender(EventArgs e) +243
   System.Web.UI.Control.PreRenderRecursiveInternal() +108
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Control.PreRenderRecursiveInternal() +224
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

image

The single reference we found for this error on the web did not helped at all.

The default view for the list was a Gantt chart view that included all events in the list, other views (like All Events) were displayed without any error.

My very first idea was that the Gantt view should have been altered recently that caused the error, however as we checked the last modified date for the view, it turned out that it has not been modified recently.

Next, as we reduced the item count in the view, the error was displayed not immediately, but only after navigating through several pages of events. It clearly indicated that the problem is caused by data corruption in one or more items. Checking the items created or modified last week, we found a single item. Opening the item for edition from the All Events view, and simply saving it without any modifications solved the issue. Comparing the item’s Xml property before and after the save event the most significant difference was that the fAllDayEvent field (ows_fAllDayEvent attribute in the Xml property) was missing in the former one.

Since the All Day Event column was a mandatory one, it was first a surprise that a such event existed. The only possible solution via the UI (we did not assumed that somebody manipulated the items via code) was the All Events view. Since there were a few custom columns inserted to this view, the All Day Event column was simply removed from the view to provide enough space for the new columns. If we switch this view to the Datasheet View, we can enter new items without performing the validation rules or even saving a default value for the All Day Event field, and so the same error can be reproduced.

February 11, 2015

Migrating AngularJS-based SharePoint 2013 Applications to Former SharePoint Versions

Filed under: AngularJS, JSCOM, SP 2010, SP 2013 — Tags: , , , — Peter Holpar @ 23:34

In the past years we created several more or less simple AngularJS-based applications for SharePoint 2013 using REST / OData or the JavaScript Client Object Model (JSCOM). As we found this combo quite powerful and effective, we tried to apply the same technologies in other environments too, where we had to work with SharePoint 2010 (with and even without the visual upgrade). Since the migration into the backward direction was not quite trivial, I thought it might be worth to share my experience.

The sample application that I use in this post to illustrate the steps to fix the errors during the migration is really a simple one: it utilizes AngularJS v1.3.0-beta.6 to display the First Name, Last Name, Business Phone and Email Address fields of items from a standard SharePoint Contacts lists, and lets the items to be ordered as the user clicks on the field names in the header.

For example, we have a Contacts list like this:

image

that will be displayed as it shown below:

Contacts15

The HTML code that we used in a Content Editor web part to define the UI:

  1. <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
  2. <script type="text/javascript" src="/_layouts/15/sp.js"></script>
  3. <script type="text/javascript" src="/_layouts/15/contacts/angular.min.js"></script>
  4. <script type="text/javascript" src="/_layouts/15/contacts/contacts.js"></script>
  5.  
  6. <div ng-app="myApp">
  7.     <div ng-controller="contactsCtrl">
  8.         <table>
  9.             <tr>
  10.                 <td><a href="" ng-click="predicate = 'firstName'; reverse=!reverse">First name</a></td>
  11.                 <td><a href="" ng-click="predicate = 'lastName'; reverse=!reverse">Last name</a></td>
  12.                 <td><a href="" ng-click="predicate = 'eMail'; reverse=!reverse">E-Mail</a></td>
  13.                 <td><a href="" ng-click="predicate = 'workPhone'; reverse=!reverse">Phone</a></td>
  14.             </tr>
  15.                     <tr ng-repeat="contact in contacts | orderBy:predicate:reverse">
  16.                 <td>{{contact.firstName}}</td>
  17.                 <td>{{contact.lastName}}</td>
  18.                 <td><a href="mailto:{{contact.eMail}}">{{contact.eMail}}</a></td>
  19.                 <td>{{contact.workPhone}}</td>
  20.             </tr>
  21.         </table>
  22.     </div>
  23. </div>

The AngularJS controller (contactsCtrl) as well as the custom service (mySharePointService) were defined in the contacts.js. The service access the SharePoint list via JSCOM.

  1. 'use strict';
  2.  
  3. var contactListName = 'Contacts';
  4.  
  5. var myApp = angular.module('myApp', []);
  6.  
  7. myApp.service('mySharePointService', function ($q, $http) {
  8.  
  9.     this.getContacts = function ($scope) {
  10.         var deferred = $q.defer();
  11.  
  12.         var ctx = new SP.ClientContext.get_current();
  13.  
  14.         var web = ctx.get_web();
  15.         var list = web.get_lists().getByTitle(contactListName);
  16.  
  17.         var camlQuery = new SP.CamlQuery();
  18.         camlQuery.set_viewXml('<View><ViewFields><FieldRef Name=\'Title\'/><FieldRef Name=\'FirstName\'/><FieldRef Name=\'Email\'/><FieldRef Name=\'WorkPhone\'/></ViewFields></View>');
  19.         var contacts = list.getItems(camlQuery);
  20.  
  21.         ctx.load(contacts);
  22.  
  23.         ctx.executeQueryAsync(
  24.             function () {
  25.                 deferred.resolve(contacts);
  26.             },
  27.             function (sender, args) {
  28.                 deferred.reject('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
  29.             }
  30.         );
  31.  
  32.         return deferred.promise;
  33.     };
  34.  
  35.  
  36. });
  37.  
  38. myApp.controller('contactsCtrl', function ($scope, mySharePointService) {
  39.     var promiseContacts = mySharePointService.getContacts($scope);
  40.  
  41.     promiseContacts.then(function (contacts) {
  42.         $scope.contacts = [];
  43.         var contactEnumerator = contacts.getEnumerator();
  44.         while (contactEnumerator.moveNext()) {
  45.             var contact = contactEnumerator.get_current();
  46.             $scope.contacts.push({
  47.                 firstName: contact.get_item('FirstName'),
  48.                 lastName: contact.get_item('Title'),
  49.                 workPhone: contact.get_item('WorkPhone'),
  50.                 eMail: contact.get_item('Email')
  51.             });
  52.         }
  53.  
  54.     }, function (errorMsg) {
  55.         console.log("Error: " + errorMsg);
  56.     });
  57. });

When we was to use the same HTML and .js in SP 2010 SP2 with the visual upgrade (that is the standard SP 2010 UI, below is a screenshot of the Contacts list itself), we had to face some issues:

ContactList14

First of all, although the data binding was correct and it has been initialized (the double curly braces disappeared), no data was displayed at all. When checking the console in the IE Developer Tools, we saw error messages like:

The collection has not been initialized. It has not been requested or the request has not been executed. It may need to be explicitly requested.

Remark: In other cases, when we did not work with collections, but object properties, the error was:

The property or field has not been initialized. It has not been requested or the request has not been executed. It may need to be explicitly requested.

As you may know, this error message typically indicates, that the collection / property has not been loaded into the client context. However, when I checked the network traffic between the client and server using Fiddler, I saw that the collection was requested by the client, and the response contains it as well. To make the symptoms even stranger, occasionally the contacts were displayed without any error message. This fact indicated me that it might be a kind of timing issue.

The solution to this issue was really inserting a delay (1 sec in our case) in the controller before we invoke the getContacts method of the mySharePointService service.

setTimeout(function () {
    $scope.getContacts($scope, mySharePointService);
}, 1000);

Second, sorting the items was not working anymore. Although one could see for a moment, that the sort order of the items was changed on clicking on the field name in the headers, but a second later the page reloaded and was displayed with the original sort order.

The solution to this problem was to replace the value of the href attribute of the anchors in the header. The original value was

<a href="" …

The new one, that changes the sort order without reloading the page:

<a href="javascript:;" …

We had a third error as well, that seems to be a consequence of the standard document mode (IE 8) used by the SharePoint 2010 master pages:

SCRIPT5014: Array.prototype.slice: ‘this’ is not a JavaScript object
angular.min.js, line 26 character 36

As we switched the document mode manually (for example, via the IE Developer Tools) the error disappeared. As this error had no negative effect on the functionality, we simply ignored that.

The updated version of the HTML content:

  1. <script type="text/javascript" src="/_layouts/sp.core.js"></script>
  2. <script type="text/javascript" src="/_layouts/sp.runtime.js"></script>
  3. <script type="text/javascript" src="/_layouts/sp.js"></script>
  4. <script type="text/javascript" src="/_layouts/contacts/angular.min.js"></script>
  5. <script type="text/javascript" src="/_layouts/contacts/contacts.js"></script>
  6.  
  7. <div ng-app="myApp">
  8. <div ng-controller="contactsCtrl">
  9.   <table>
  10.     <tr>
  11.         <td><a href="javascript:;" ng-click="predicate = 'firstName'; reverse=!reverse">First name</a></td>
  12.         <td><a href="javascript:;" ng-click="predicate = 'lastName'; reverse=!reverse">Last name</a></td>
  13.         <td><a href="javascript:;" ng-click="predicate = 'eMail'; reverse=!reverse">E-Mail</a></td>
  14.         <td><a href="javascript:;" ng-click="predicate = 'workPhone'; reverse=!reverse">Phone</a></td>
  15.     </tr>
  16.             <tr ng-repeat="contact in contacts | orderBy:predicate:reverse">
  17.         <td>{{contact.firstName}}</td>
  18.         <td>{{contact.lastName}}</td>
  19.         <td><a href="mailto:{{contact.eMail}}">{{contact.eMail}}</a></td>
  20.         <td>{{contact.workPhone}}</td>
  21.     </tr>
  22. </table>
  23. </div>
  24. </div>

The full and updated .js file:

  1. 'use strict';
  2.  
  3. var contactListName = 'Contacts';
  4.  
  5. var myApp = angular.module('myApp', []);
  6.  
  7. myApp.service('mySharePointService', function ($q, $http) {
  8.  
  9.   this.getContacts = function ($scope) {
  10.     var deferred = $q.defer();
  11.  
  12.     var ctx = new SP.ClientContext.get_current();
  13.  
  14.     var web = ctx.get_web();
  15.     var list = web.get_lists().getByTitle(contactListName);
  16.  
  17.     var camlQuery = new SP.CamlQuery();
  18.     camlQuery.set_viewXml('<View><ViewFields><FieldRef Name=\'Title\'/><FieldRef Name=\'FirstName\'/><FieldRef Name=\'Email\'/><FieldRef Name=\'WorkPhone\'/></ViewFields></View>');
  19.     var contacts = list.getItems(camlQuery);
  20.  
  21.     ctx.load(contacts);
  22.  
  23.     ctx.executeQueryAsync(
  24.             function () {
  25.               deferred.resolve(contacts);
  26.             },
  27.             function (sender, args) {
  28.               deferred.reject('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
  29.             }
  30.         );
  31.  
  32.     return deferred.promise;
  33.   };
  34.  
  35. });
  36.  
  37. myApp.controller('contactsCtrl', function ($scope, mySharePointService) {
  38.     
  39.     setTimeout(function () {
  40.       $scope.getContacts($scope, mySharePointService);
  41.     }, 1000);
  42.  
  43.     $scope.getContacts = function ($scope, mySharePointService) {
  44.       var promiseContacts = mySharePointService.getContacts($scope);
  45.  
  46.       promiseContacts.then(function (contacts) {
  47.         $scope.contacts = [];
  48.         var contactEnumerator = contacts.getEnumerator();
  49.         while (contactEnumerator.moveNext()) {
  50.           var contact = contactEnumerator.get_current();
  51.           $scope.contacts.push({
  52.             firstName: contact.get_item('FirstName'),
  53.             lastName: contact.get_item('Title'),
  54.             workPhone: contact.get_item('WorkPhone'),
  55.             eMail: contact.get_item('Email')
  56.           });
  57.         }
  58.  
  59.       }, function (errorMsg) {
  60.         console.log("Error: " + errorMsg);
  61.       });
  62.     };
  63. });

The result as rendered by a Content Editor web part:

Contacts14

In case of SP 2010 SP2 without the visual update (that is the WSS 3.0 / MOSS 2007 UI, see the screenshot of the list below) we had all the above mentioned problems, and the following one:

ContactList12

The data binding was not working at all, see the image below:

NoDataBinding

It turned out to have two causes, both of them was an effect of the standard document mode (quirks mode) used by the WSS 3.0 master pages on working of AngularJS.

To solve these issues, first we had to append id="ng-app" to the HTML DIV we bind the AngularJS application to.

The original version was:

<div ng-app="myApp">

The new one is:

<div ng-app="myApp" id="ng-app">

Second, we had to disable the $sce service of AngularJS in the application if the document is displayed in quirks mode. We can achieve that via the config method of the application:

myApp.config(function ($sceProvider) {
    // Completely disable SCE to support IE7 (quirks mode in SharePoint 2007 / 2010).
    // SCRIPT5022: [$sce:iequirks]
http://errors.angularjs.org/1.3.0-beta.6/$sce/iequirks
    if ((document.documentMode == 5) || (document.documentMode == 7)) {
        // or: if (document.documentMode < 8) {
        $sceProvider.enabled(false);
    }
});

The updated version of the HTML content:

  1. <script type="text/javascript" src="/_layouts/sp.core.js"></script>
  2. <script type="text/javascript" src="/_layouts/sp.runtime.js"></script>
  3. <script type="text/javascript" src="/_layouts/sp.js"></script>
  4. <script type="text/javascript" src="/_layouts/contacts/angular.min.js"></script>
  5. <script type="text/javascript" src="/_layouts/contacts/contacts12.js"></script>
  6.  
  7. <div ng-app="myApp" id="ng-app">
  8. <div ng-controller="contactsCtrl">
  9.   <table>
  10.     <tr>
  11.         <td><a href="javascript:;" ng-click="predicate = 'firstName'; reverse=!reverse">First name</a></td>
  12.         <td><a href="javascript:;" ng-click="predicate = 'lastName'; reverse=!reverse">Last name</a></td>
  13.         <td><a href="javascript:;" ng-click="predicate = 'eMail'; reverse=!reverse">E-Mail</a></td>
  14.         <td><a href="javascript:;" ng-click="predicate = 'workPhone'; reverse=!reverse">Phone</a></td>
  15.     </tr>
  16.             <tr ng-repeat="contact in contacts | orderBy:predicate:reverse">
  17.         <td>{{contact.firstName}}</td>
  18.         <td>{{contact.lastName}}</td>
  19.         <td><a href="mailto:{{contact.eMail}}">{{contact.eMail}}</a></td>
  20.         <td>{{contact.workPhone}}</td>
  21.     </tr>
  22. </table>
  23. </div>
  24. </div>

The full and updated .js file:

  1. 'use strict';
  2.  
  3. var contactListName = 'Contacts';
  4.  
  5. var myApp = angular.module('myApp', []);
  6.  
  7. myApp.service('mySharePointService', function ($q, $http) {
  8.  
  9.   this.getContacts = function ($scope) {
  10.     var deferred = $q.defer();
  11.  
  12.     var ctx = new SP.ClientContext.get_current();
  13.  
  14.     var web = ctx.get_web();
  15.     var list = web.get_lists().getByTitle(contactListName);
  16.  
  17.     var camlQuery = new SP.CamlQuery();
  18.     camlQuery.set_viewXml('<View><ViewFields><FieldRef Name=\'Title\'/><FieldRef Name=\'FirstName\'/><FieldRef Name=\'Email\'/><FieldRef Name=\'WorkPhone\'/></ViewFields></View>');
  19.     var contacts = list.getItems(camlQuery);
  20.  
  21.     ctx.load(contacts);
  22.  
  23.     ctx.executeQueryAsync(
  24.             function () {
  25.               deferred.resolve(contacts);
  26.             },
  27.             function (sender, args) {
  28.               deferred.reject('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
  29.             }
  30.         );
  31.  
  32.     return deferred.promise;
  33.   };
  34.  
  35. });
  36.  
  37. myApp.controller('contactsCtrl', function ($scope, mySharePointService) {
  38.     
  39.     setTimeout(function () {
  40.       $scope.getContacts($scope, mySharePointService);
  41.     }, 1000);
  42.  
  43.     $scope.getContacts = function ($scope, mySharePointService) {
  44.       var promiseContacts = mySharePointService.getContacts($scope);
  45.  
  46.       promiseContacts.then(function (contacts) {
  47.         $scope.contacts = [];
  48.         var contactEnumerator = contacts.getEnumerator();
  49.         while (contactEnumerator.moveNext()) {
  50.           var contact = contactEnumerator.get_current();
  51.           $scope.contacts.push({
  52.             firstName: contact.get_item('FirstName'),
  53.             lastName: contact.get_item('Title'),
  54.             workPhone: contact.get_item('WorkPhone'),
  55.             eMail: contact.get_item('Email')
  56.           });
  57.         }
  58.  
  59.       }, function (errorMsg) {
  60.         console.log("Error: " + errorMsg);
  61.       });
  62.     };
  63.   });
  64.  
  65.   myApp.config(function ($sceProvider) {
  66.     // Completely disable SCE to support IE7 (quirks mode in SharePoint 2007 / 2010).
  67.     // SCRIPT5022: [$sce:iequirks] http://errors.angularjs.org/1.3.0-beta.6/$sce/iequirks
  68.     if ((document.documentMode == 5) || (document.documentMode == 7)) {
  69.       // or: if (document.documentMode < 8) {
  70.       $sceProvider.enabled(false);
  71.     }
  72.   });

The result as rendered by a Content Editor web part:

Contacts12

I assume that this method should work even in case of WSS 3.0 / MOSS 2007, although if you really work with this SharePoint version, you have to obviously re-write the custom mySharePointService service to utilize the SharePoint web services (for example, via the SPServices library) instead of JSCOM.

Note: Another alternative approach to include AngularJS-based content in former SharePoint version is to use an IFRAME (for example, via Page Viewer Web Part) to display a simple HTML document, that you can store either in a document library in SharePoint or in the file system in the _layouts hive. Since in this page you don’t use the master page that enforced the document mode that caused the issues above, you can work without the restrictions or problems we faced when included the AngularJS content in the .aspx page itself.

I hope that the tips and hints in this post help all of you to utilize the really great features of AngularJS even if you have to work with former versions of SharePoint.

February 10, 2015

Further Effects of Running Code in Elevated Privileges Block

Filed under: Permissions, PowerShell, SP 2010, SP 2013 — Tags: , , , — Peter Holpar @ 23:29

A few days ago I already published a blog post about the effects of running your PowerShell code in an elevated privileges block. In the past days I made some further tests to check what kind of effect the permission elevation might have if the code runs out of the SharePoint web application context, for example, in the case of server side console applications, windows services or PowerShell scripts, just to name a few typical cases.

To test the effects, I’ve created a simple console application in C#, and a PowerShell script.

I’ve tested the application / script with two different permissions. In both cases, the user running the application was neither site owner (a.k.a. primary administrator) nor a secondary administrator. In the first case, the user has the Full Control permission level on the root web of the site collection, in the second case the user has no permissions at all.

In C# I’ve defined a CheckIfCurrentUserIsSiteAdmin method as:

  1. private void CheckIfCurrentUserIsSiteAdmin(string url)
  2. {
  3.     using (SPSite site = new SPSite(url))
  4.     {
  5.         using (SPWeb web = site.OpenWeb())
  6.         {
  7.             SPUser currentUser = web.CurrentUser;
  8.             Console.WriteLine("Current user ({0}) is site admin on '{1}': {2}", currentUser.LoginName, url, currentUser.IsSiteAdmin);
  9.             Console.WriteLine("Current user ({0}) is site auditor on '{1}': {2}", currentUser.LoginName, url, currentUser.IsSiteAuditor);
  10.             Console.WriteLine("Effective permissions on web: '{0}'", web.EffectiveBasePermissions);
  11.             try
  12.             {
  13.                 Console.WriteLine("web.UserIsWebAdmin: '{0}'", web.UserIsWebAdmin);
  14.             }
  15.             catch (Exception ex)
  16.             {
  17.                 Console.WriteLine("'web.UserIsWebAdmin' threw an exception: '{0}'", ex.Message);
  18.             }
  19.             try
  20.             {
  21.                 Console.WriteLine("web.UserIsSiteAdmin: '{0}'", web.UserIsSiteAdmin);
  22.             }
  23.             catch (Exception ex)
  24.             {
  25.                 Console.WriteLine("'web.UserIsSiteAdmin' threw an exception: '{0}'", ex.Message);
  26.             }
  27.         }
  28.     }
  29. }

Then called it without and with elevated permissions:

  1. string url = http://YourServer;
  2. Console.WriteLine("Before elevation of privileges");
  3. CheckIfCurrentUserIsSiteAdmin(url);
  4. Console.WriteLine("After elevation of privileges");
  5. SPSecurity.RunWithElevatedPrivileges(
  6.     () =>
  7.         {
  8.             CheckIfCurrentUserIsSiteAdmin(url);
  9.         });

The summary of the result:

If the user executing the application has full permission on the (root) web (Full Control permission level):

Before elevation:
Current user is site admin: False
Effective perm.: FullMask
web.UserIsWebAdmin: True
web.UserIsSiteAdmin: False

After elevation:
Current user is site admin: True
Effective perm.: FullMask
web.UserIsWebAdmin: True
web.UserIsSiteAdmin: True

If the user has no permission on the (root) web:

Before elevation:
Current user is site admin: False
Effective perm.: EmptyMask
web.UserIsWebAdmin: ‘Access denied’ exception when reading the property
web.UserIsSiteAdmin: ‘Access denied’ exception when reading the property

After elevation:
Current user is site admin: True
Effective perm.: FullMask
web.UserIsWebAdmin: True
web.UserIsSiteAdmin: True

In PowerShell I defined a CheckIfCurrentUserIsSiteAdmin method as well, and invoked that without and with elevated right:

function CheckIfCurrentUserIsSiteAdmin($url) {
  $site = Get-SPSite $url 
  $web = $site.RootWeb
  $currentUser = $web.CurrentUser
  Write-Host Current user $currentUser.LoginName is site admin on $url : $currentUser.IsSiteAdmin
  Write-Host Current user $currentUser.LoginName is site auditor on $url : $currentUser.IsSiteAuditor

  Write-Host Effective permissions on web: $web.EffectiveBasePermissions
  Write-Host web.UserIsWebAdmin: $web.UserIsWebAdmin
  Write-Host web.UserIsSiteAdmin: $web.UserIsSiteAdmin
}

$url = "http://YourServer&quot;

Write-Host Before elevation of privileges
CheckIfCurrentUserIsSiteAdmin $url

Write-Host After elevation of privileges
[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges(
  {
    CheckIfCurrentUserIsSiteAdmin $url
  }
)

The results were the same as in the case of the C# console application, except the web.UserIsWebAdmin and web.UserIsSiteAdmin, when calling with no permissions on the web. In case we don’t receive any exception, simply no value (neither True nor False) was returned.

These results show, that any code, let it be a method in a standard SharePoint API, or a custom component, that depends on the above tested properties, behaves differently when using with elevated privileges, even if it is executed from an application external to the SharePoint web application context, that means, even if the identity of the process does not change.

February 9, 2015

“Decoding” SharePoint Error Messages using PowerShell

Filed under: PowerShell, SP 2010, Tips & Tricks — Tags: , , — Peter Holpar @ 22:26

When working with SharePoint errors in ULS logs, you can find the error message near to the stack trace. In case of simple methods the stack trace may be enough to identify the exact conditions under which the exception was thrown. However, if the method is complex, with a lot of conditions and branches, it is not always trivial to find the error source, as we don’t see the exception message itself, as it is stored in language-specific resource files, and you see only a kind of keyword in the code.

For example, let’s see the GetItemById method of the SPList object with this signature:

internal SPListItem GetItemById(string strId, int id, string strRootFolder, bool cacheRowsetAndId, string strViewFields, bool bDatesInUtc)

There is a condition near to the end of the method:

if (this.IsUserInformationList)
{
    throw new ArgumentException(SPResource.GetString("CannotFindUser", new object[0]));
}
throw new ArgumentException(SPResource.GetString("ItemGone", new object[0]));

How could we “decode” this keyword to the real error message? It is easy to achieve using PowerShell.

For example, to get the error message for the “ItemGone”:

[Microsoft.SharePoint.SPResource]::GetString("ItemGone")

is

Item does not exist. It may have been deleted by another user.

Note, that since the second parameter is an empty array, we can simply ignore it when invoking the static GetString method.

If you need the language specific error message (for example, the German one):

$ci = New-Object System.Globalization.CultureInfo("de-de")
[Microsoft.SharePoint.SPResource]::GetString($ci, "ItemGone")

it is

Das Element ist nicht vorhanden. Möglicherweise wurde es von einem anderen Benutzer gelöscht.

Having the error message, it is already obvious most of the time, at which line of code the exception was thrown.

It can also help to translate the localized message to the English one, and use it to look up a solution for the error on the Internet using your favorite search engine, as there are probably more results when you search for the English text.

Setting the Value of an URL Field or other Complex Field Types using PowerShell via the Managed Client Object Model

Filed under: Managed Client OM, PowerShell, SP 2013 — Tags: , , — Peter Holpar @ 00:30

Assume you have a SharePoint list that includes fields of type Hyperlink or Picture (name this field UrlField), Person or Group (field name User) and Lookup (field name Lookup) and you need to set their values remotely using PowerShell via the Managed Client Object Model.

In C# you would set the Hyperlink field using this code:

  1. var siteUrl = "http://YourServer&quot;;
  2.  
  3. using (var context = new ClientContext(siteUrl))
  4. {
  5.     var web = context.Web;
  6.     var list = web.Lists.GetByTitle("YourTestList");
  7.     var item = list.GetItemById(1);
  8.  
  9.     var urlValue = new FieldUrlValue();
  10.     urlValue.Url = "http://www.company.com&quot;;
  11.     urlValue.Description = "Description of the URL";
  12.     item["UrlField"] = urlValue;
  13.  
  14.     item.Update();
  15.     context.ExecuteQuery();
  16. }

You may think, that translating this code to PowerShell is as easy as:

Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"

$siteUrl = "http://YourServer&quot;

$context = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$web = $context.Web
$list = $web.Lists.GetByTitle("YourTestList")
$item = $list.GetItemById(1)

$urlValue = New-Object Microsoft.SharePoint.Client.FieldUrlValue
$urlValue.Url = "http://www.company.com&quot;
$urlValue.Description = "Description of the URL"
$item["UrlField"] = $urlValue

$item.Update()
$context.ExecuteQuery()

However, that approach simply won’t work, as you receive this error:

"Invalid URL: Microsoft.SharePoint.Client.FieldUrlValue" (ErrorCode: -2130575155)

When capturing the network traffic with Fiddler, the C# version sends this for the SetFieldValue method:

image

For the PowerShell code however:

image

You can see, that the first parameter, the field name is the same in both cases, however the second parameter, that should be the value we assign to the field is wrong in the second case. In the C# case it has the correct type (FieldUrlValue , represented by the GUID value in the TypeId), however in PowerShell the type is Unspecified, and the really type is sent as text in this parameter (I assume the ToString() method was called on the type by PowerShell)

Solution: You should explicitly cast the object in the $urlValue variable to the FieldUrlValue type as shown below (the single difference is highlighted in code with bold):

Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"

$siteUrl = "http://YourServer&quot;

$context = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$web = $context.Web
$list = $web.Lists.GetByTitle("YourTestList")
$item = $list.GetItemById(1)

$urlValue = New-Object Microsoft.SharePoint.Client.FieldUrlValue
$urlValue.Url = "http://www.company.com&quot;
$urlValue.Description = "Description of the URL"
$item["UrlField"] = [Microsoft.SharePoint.Client.FieldUrlValue]$urlValue

$item.Update()
$context.ExecuteQuery()

Similarly in case of the other complex field types (only the relevant codes are included).

For the Person or Group field in C# (assuming 2 is the ID of the user you would like to set in the field):

  1. var userValue = new FieldUserValue();
  2. userValue.LookupId = 2;
  3. item["User"] = userValue;

The network trace:

image

PowerShell code, that does not work:

$userValue = New-Object Microsoft.SharePoint.Client.FieldUserValue
$userValue.LookupId = 2
$item["User"] = $userValue

The error you receive:

"Invalid data has been used to update the list item. The field you are trying to update may be read only." (ErrorCode: -2147352571)

The network trace:

image

PowerShell code, that does work:

$userValue = New-Object Microsoft.SharePoint.Client.FieldUserValue
$userValue.LookupId = 2
$item["User"] = [Microsoft.SharePoint.Client.FieldUserValue]$userValue

For the Lookup field in C# (assuming 2 is the ID of the related list item you would like to set in the field):

  1. var lookUpValue = new FieldLookupValue();
  2. lookUpValue.LookupId = 2;
  3. item["Lookup"] = lookUpValue;

The network trace:

image

PowerShell code, that does not work:

$lookUpValue = New-Object Microsoft.SharePoint.Client.FieldLookupValue
$lookUpValue.LookupId = 2
$item["Lookup"] = $lookUpValue

The error you receive:

"Invalid data has been used to update the list item. The field you are trying to update may be read only." (ErrorCode: -2147352571)

The network trace:

image

PowerShell code, that does work:

$lookUpValue = New-Object Microsoft.SharePoint.Client.FieldLookupValue
$lookUpValue.LookupId = 2
$item["Lookup"] = [Microsoft.SharePoint.Client.FieldLookupValue]$lookUpValue

February 7, 2015

Changing Site Collection Administrators via PowerShell without Elevated Permissions

Filed under: PowerShell, Security, SP 2010 — Tags: , , — Peter Holpar @ 22:20

In my recent post I’ve already illustrated how to read and set the primary and secondary site collection administrators (the Owner and SecondaryContact properties of the corresponding SPSite object) via PowerShell. In that samples I’ve used elevated privileges to achieve my goals.

Let’s see if it is there a way without elevating the privileges.

Before PowerShell, before SharePoint 2010, the standard way to display / change the site owner and the secondary owner in command line was the stsadm command, that is still available to us.

For example, we can display this information for all site of a web application using the enumsites operation:

stsadm -o enumsites -url http://mysiteroot

To set the site owner, we can use the siteowner operation with the ownerlogin parameter:

stsadm -o siteowner -url http://mysiteroot/users/user1 -ownerlogin "domain\user1"

For the secondary admin, use the secondarylogin parameter instead of ownerlogin.

Note: You should have local administrator rights on the server where you run this commands, otherwise you receive an “Access denied.” error message. The reason, that for most of the stsadm operation, the following security check is performed in the entry method (public static int Main) of the Microsoft.SharePoint.StsAdmin.SPStsAdmin class:

if (!SPAdministrationServiceUtilities.IsCurrentUserMachineAdmin())
{
  Console.WriteLine(SPResource.GetString("AccessDenied", new object[0]));
  Console.WriteLine();
  return -2147024891;
}

I decided to check, what kind of method stsadm uses to read and change the values. The implementation of the siteowner operation can be found in the Microsoft.SharePoint.StsAdmin.SPSiteOwner class. To access the the Owner and SecondaryContact properties of the SPSite object, the OwnerLoginName and SecondaryContactLoginName properties of the SPSiteAdministration class are used. In the constructor of this class there is a security check that verify if the calling user is a farm administrator:

internal SPSiteAdministration(SPSite site)
{
    if (site == null)
    {
        throw new ArgumentNullException("site");
    }
    this.m_Site = site;
    if (this.m_Site.WebApplication.Farm.CurrentUserIsAdministrator())
    {
        this.m_Site.AdministratorOperationMode = true;
    }

To display the owner and the secondary contact of the site collection, we can use the following PowerShell script:

$url = "http://mysiteroot/users/user1&quot;
$siteAdmin = New-Object Microsoft.SharePoint.Administration.SPSiteAdministration($url)
$siteAdmin.OwnerLoginName
$siteAdmin.SecondaryContactLoginName

Changing these values is so simple as:

$url = "http://mysiteroot/users/user1&quot;
$siteAdmin = New-Object Microsoft.SharePoint.Administration.SPSiteAdministration($url)
$siteAdmin.OwnerLoginName = "domain\user1"
$siteAdmin.SecondaryContactLoginName = "domain\user2"

Note, that we are using the login name as string, and not an SPUser when assigning the values, and there is no need for elevated privileges. The caller must be a farm administrator for the current SharePoint farm, however, as we call this code directly, and not from stsadm, where this is checked, the user should not be a local admin.

I found it a bit inconsistent and disturbing, that we can access (read / set) the same properties via various objects and methods of the base SharePoint library, that perform various permission checks, and so one can avoid the security checks implemented in one of the objects when accessing the very same information via another class.

Changing Site Collection Administrators via PowerShell Using Elevated Permissions

Filed under: PowerShell, Security, SP 2010 — Tags: , , — Peter Holpar @ 05:06

Recently we migrated the users of a SharePoint farm into another domain. As part of the migration the MySites of the users should have been migrated as well. For the user migration we used the Move-SPUser Cmdlet, however it seems to have no effect on the site primary and secondary administrators (that means the Owner and SecondaryContact properties of the corresponding SPSite object). As you might know, each MySite is a site collection in SharePoint, the user the MySite belongs to is by default the primary site collection administrator and there is no secondary admin specified. It is possible to change (“migrate”) the admins via the Central Administration web UI, however having hundreds of users, it was not a viable option to us. But no problem, we can surely change these values via PowerShell as well, couldn’t we? Let’s test it first, and use PowerShell to read the values. We have all of the MySites in a separate web application, so we tried to iterate through all its site collections and dump out the information we need.

These samples assume that the URL of the web application (the MySite root) is http://mysiteroot, and the MySites are under the managed path /users, for example, the URL of the MySite of user1 is http://mysiteroot/users/user1.

Our first try was:

$waUrl = "http://mysiteroot&quot;

$wa = Get-SPWebApplication $waUrl
Get-SPSite -WebApplication $wa -Limit ALL | % {
  Write-Host "Url:" $_.Url
  Write-Host "Primary Administrator:" $_.Owner.LoginName
  Write-Host "Secondary Administrator:" $_.SecondaryContact.LoginName
  Write-Host "—————————–"
}

Surprise! The URLs are dumped out, however the Owner only for the root site and the own MySite of the user who executed the script, the SecondaryContact only for the root (in the MySite of the executing user had no SecondaryContact defined). That means one could access these properties only for the site where one is defined as site collection administrator (either primary or secondary). There is no error message (like Access denied) for the other sites, simply no information about the admins displayed.

However, if we run the code in an elevated privileges block, the Owner and SecondaryContact properties are dumped out as well:

$waUrl = "http://mysiteroot&quot;

[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges (
  {
    $wa = Get-SPWebApplication $waUrl
    Get-SPSite -WebApplication $wa -Limit ALL | % {
      Write-Host "Url:" $_.Url
      Write-Host "Primary Administrator:" $_.Owner.LoginName
      Write-Host "Secondary Administrator:" $_.SecondaryContact.LoginName
      Write-Host "—————————–"
    }
  }
)

Note 1: If you would like to dump out the info without Write-Host in a RunWithElevatedPrivileges code block, you would not see the result in the console, even the Url properties would disappear.

The following sample display no result:

$waUrl = "http://mysiteroot&quot;

[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges (
  {
    $wa = Get-SPWebApplication $waUrl
    Get-SPSite -WebApplication $wa -Limit ALL | % {
      $_.Url
      $_.Owner.LoginName
      $_.SecondaryContact.LoginName
    }
  }
)

Note 2: You don’t need to place all of your code in the elevated block. It is enough to place the code that gets the reference to the web application (site, web, etc.) in this block.

The following code works just as well as the original one with elevated privileges:

$waUrl = "http://mysiteroot&quot;

[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges (
  {
    $wa = Get-SPWebApplication $waUrl
  }
)

Get-SPSite -WebApplication $wa -Limit ALL | % {
  Write-Host "Url:" $_.Url
  Write-Host "Primary Administrator:" $_.Owner.LoginName
  Write-Host "Secondary Administrator:" $_.SecondaryContact.LoginName
  Write-Host "—————————–"
}

After we displayed the information about the site admins, let’s see, how can we change the configuration. First I tried without elevation, although I was already sure, that it won’t perform the requested operation. In the following samples I try to set the Owner property, but in the case of SecondaryContact it would have the same outcome.

$url = "http://mysiteroot/users/user1&quot;
$siteOwnerLogin = "domain\user1" 
$web = Get-SPWeb $url
$user = $web.EnsureUser($siteOwnerLogin)
$site = $web.Site
$site.Owner = $user

The code above does not work. We receive an error message (Attempted to perform an unauthorized operation.), and the site owner is not changed.

However, using the elevated code block will solve the problem again:

$url = "http://mysiteroot/users/user1&quot;
$siteOwnerLogin = "domain\user1"

[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges(
     {
         $web = Get-SPWeb $url
         $user = $web.EnsureUser($siteOwnerLogin)
         $site = $web.Site
         $site.Owner = $user
     }
)

You can find opinions on the web, like this one, that states, that running PowerShell code in elevated block has no effect at all. The samples above demonstrate however, that it really DOES have effect.

But what effect of code elevation is it, that makes it possible to get and set the Owner and SecondaryContact properties in the former samples?

If you have a look at the source code of the Owner (or SecondaryContact) property, for example, using Reflector, you will see, that there is a double permission check, both of them can throw an UnauthorizedAccessException. We can ignore the first one (that is !this.AdministratorOperationMode), while this condition is only checked if there is no CurrentUser for the RootWeb of the site. The second permission check is the important one, that is checked if the CurrentUser is not null, and it is:

(!this.RootWeb.CurrentUser.IsSiteAdmin)

If this condition is true, then an UnauthorizedAccessException is thrown. The setter method of the Owner (or SecondaryContact) property calls first the getter, so the same condition is checked in this case as well. Although the call to the getter is in a try-catch block, the catch is valid only for the type SPException, so it has no effect on the UnauthorizedAccessException thrown by the getter.

Remark: I have to admit, that it is not yet clear to me, why the exception thrown by the getter is not displayed when calling the getter from PowerShell.

Let’s see a further example of code elevation from PowerShell to clear the reason, why the former samples with elevation solved the issue of getting / setting the Owner and SecondaryContact properties:

$url = "http://mysiteroot/users/user1&quot;
$web = Get-SPWeb $url

$userName = $web.CurrentUser.LoginName
$userIsAdmin = $web.CurrentUser.IsSiteAdmin

# elevated privilages
[Microsoft.SharePoint.SPSecurity]::RunWithElevatedPrivileges(
     {
         $web = Get-SPWeb $url
         $userNameElevated = $web.CurrentUser
         $userIsAdminElevated = $web.CurrentUser.IsSiteAdmin
     }
)

Write-Host "Before elevation"
Write-Host "User name: " $userName
Write-Host "User is site admin: " $userIsAdmin
Write-Host "After elevation"
Write-Host "User name: " $userNameElevated
Write-Host "User is site admin: " $userIsAdminElevated

Assuming the executing user has neither owner nor secondary contact for the site collection, the code returns the same user name before and after elevation, however the value of the IsSiteAdmin property is false before the elevation, but true after the elevation. As we have seen from the previous reflectoring, this change is just enough for the getter / setter  methods of the Owner and SecondaryContact properties to work.

In my next post I show you an alternative method that make it possible to read / change these values from PowerShell without any kind of elevation.

February 4, 2015

How to Move all of the Site Collections of a Web Application into a Single Content Database using PowerShell

Filed under: Administration, PowerShell, SP 2010 — Tags: , , — Peter Holpar @ 23:45

Recently we had a task at one of our clients to consolidate all of the site collections of a SharePoint web application into a single content database.

There were around 10 content DBs in this web application, each of them included about 5-10 site collections. The total size of the content DBs was around 1-2 GBs, and they expected no growth in the near future.

To make the farm administration easier we had to move all of the site collections into the first content DB (let’s call it ContentDB1). We achieved this goal via the following PowerShell script:

$webAppUrl = "http://YourSPWebApp&quot;
$destinationDBName = "ContentDB1"

$wa = Get-SPWebApplication $webAppUrl
$destinationDB = Get-SPContentDatabase -WebApplication $wa | ? { $_.Name -eq $destinationDBName }

Get-SPContentDatabase -WebApplication $wa | ? { $_.Name -ne $destinationDBName } | % {
  Get-SPSite -ContentDatabase $_ | Move-SPSite -DestinationDatabase $destinationDB -Confirm:$False
  # we can disable the content DB at the end
  Set-SPContentDatabase $_ -Status Disabled -Confirm:$False
  # or dismount it from SharePoint
  # Dismount-SPContentDatabase $_ -Confirm:$False
  # or remove it from the SharePoint server as well as from SQL server
  # Remove-SPContentDatabase $_ -Confirm:$False -Force
}

As you can see, we iterate through all site collections of all DBs in the web application (except the target content DB, ContentDB1), and move them into the ContentDB1 database using the Move-SPSite CmdLet.

At the end we can either disable the already empty content databases, dismount the from SharePoint, or remove them from the SQL server.

Don’t forget to execute IISRESET after the script to let the configuration changes be reflected in the Central Administration UI.

Older Posts »

The Shocking Blue Green Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 53 other followers