Wednesday, June 21, 2017

Get folder of list item using javascript object model in Sharepoint

Suppose that we need to get folder (SP.Folder) where specific list item (SP.ListItem) is located. The following code shows how to do that:

   1: var ctx = SP.ClientContext.get_current();
   2: var file = item.get_file();
   3: ctx.load(file);
   4: ctx.executeQueryAsync(
   5:     Function.createDelegate(this, function (sender, args) {                                
   6:         var folderUrl = file.get_serverRelativeUrl().substring(0,
   7:             file.get_serverRelativeUrl().lastIndexOf("/"));
   8:         var folder = ctx.get_web().getFolderByServerRelativeUrl(folderUrl);
   9:         ctx.load(folder);
  10:         ctx.executeQueryAsync(
  11:             Function.createDelegate(this, function (sender, args) {
  12:                 ...
  13:             }),
  14:             Function.createDelegate(this, function (sender, args) {
  15:                 console.log(args.get_message());
  16:             }));
  17:     }),
  18:     Function.createDelegate(this, function (sender, args) {
  19:         console.log(args.get_message());
  20:     }));

Here at first we load file (SP.File) (lines 2-3), then get relative url (lines 6-7) and by this relative url get folder (lines 8-9). JSOM documentation says that there is SP.ListItem.folder property available, but it always returned error for some reason. May be it will be fixed in future updates.

Monday, June 19, 2017

Problem with Sharepoint NTLM authentication and nginx proxy

Some time ago we faced with the following problem: on-premise Sharepoint 2013 site has 2 authentication zones: Default and Custom. Default authentication zone uses NTLM authentication while Custom uses FBA. Both zones have own host headers (e.g. windows.example.com for Default zone and fba.example.com for Custom). Both host headers were specified in Alternate access mappings of appropriate web application in Sharepoint central administration.

For accessing Sharepoint site remotely nginx was used as reverse proxy between client and internal Sharepoint farm. With this configuration FBA url worked both from within Sharepoint farm (from RDP session) and remotely while Windows url worked only from Sharepoint server and only if we bypass nginx by specifying windows.example.com in hosts file and pointing it to 127.0.0.1 (self IP address). All attempts to login through nginx failed with 401 Unauthorized (and I mean login using custom host header. Logins from RDP session via serve’s name worked).

Investigation showed that nginx doesn’t works well with NTLM authentication (see e.g. How to enable windows authentication through a reverse proxy), so at the end we got rid from nginx in between and configured access to Sharepoint via IP table. If you have solution which works with nginx please share it. Anyway I hope that this information will be helpful.

Monday, June 5, 2017

Perform CAML queries with managed metadata fields to Sharepoint lists via javascript object model

In this post I will show how to perform CAML queries which contain conditions with managed metadata (taxonomy) fields via javascript object model (JSOM). Suppose that we have custom list with 2 fields:

  1. Location – managed metadata
  2. Path – single line text which contains url for specific location

We need to query this list using location value and get path for this specific location. Here is the javascript code which can be used for that:

   1: SP.SOD.executeFunc("sp.js", "SP.ClientContext", function () {
   2:     try {
   3:         if (typeof (location) == "undefined" || location == null ||
   4:             location == "") {
   5:             return;
   6:         }
   7:  
   8:         var ctx = new SP.ClientContext("http://example.com");
   9:         var list = ctx.get_web().get_lists().getByTitle("Locations");
  10:         var query = new SP.CamlQuery();
  11:         query.set_viewXml(
  12:             '<View>' +
  13:                 '<Query>' +
  14:                     '<Where>' +
  15:                         '<Contains>' +
  16:                           '<FieldRef Name=\'Location\'/>' +
  17:                           '<Value Type=\'Text\'>' + location + '</Value>' +
  18:                         '</Contains>' +
  19:                     '</Where>' +
  20:                 '</Query>' +
  21:                 '<RowLimit>1</RowLimit>' +
  22:             '</View>');
  23:  
  24:         var items = list.getItems(query);
  25:         ctx.load(items, 'Include(Path)');
  26:         ctx.executeQueryAsync(
  27:             Function.createDelegate(this, function (sender, args) {
  28:                 var path = "";
  29:                 var enumerator = items.getEnumerator();
  30:                 while (enumerator.moveNext()) {
  31:                     var item = enumerator.get_current();
  32:                     path = item.get_item("Path").get_url();
  33:                     break;
  34:                 }
  35:  
  36:                 console.log("Path: " + path);
  37:             }),
  38:             Function.createDelegate(this, function (sender, args) {
  39:                 console.log(args.get_message());
  40:             }));
  41:  
  42:     } catch (ex) {
  43:         console.log(ex.message);
  44:     }
  45: });

Code is self-descriptive so I won’t detailed explain what it does. The only moment to notice is that in order to get list item by taxonomy value in Location field we use Contains operator and pass term label to the query (lines 12-22). After that we just iterate through returned items (in this example we set RowLimit to 1, but in your scenarios you can of course get many items) and read Path field value. In order to be able to access Path field we included it to result set (line 25).

Monday, May 22, 2017

Set owners and members for existing Azure AD groups via MS Graph client library

In one of my previous posts I showed how to create new Azure AD group and add owners via MS Graph client library: Create Azure AD group and set group owner using Microsoft Graph Client library. In this post I will also show how to set owners for existing groups, which will also include deleting of previous owners. The same approach may be used for adding and deleting group members (instead of Owners use Members property of Group class). In order to synchronize Azure AD group owners use the following method:

   1: public static void SetAzureGroupOwners(Group group, List<User> newOwners)
   2: {
   3:     if (group == null || newOwners == null || newOwners.Count == 0)
   4:     {
   5:         return;
   6:     }
   7:  
   8:     var graph = new GraphServiceClient(new AzureAuthenticationProvider());
   9:  
  10:     var existingOwners = GetAzureGroupOwners(group.Id);
  11:     foreach (User newOwner in newOwners)
  12:     {
  13:         if (!existingOwners.Any(u => string.Compare(u.UserPrincipalName,
  14:             newOwner.UserPrincipalName, true) == 0))
  15:         {
  16:             var result = graph.Groups[group.Id].Owners.
  17:                 References.Request().AddAsync(newOwner);
  18:             result.Wait();
  19:             while (result.Status == TaskStatus.WaitingForActivation)
  20:             {
  21:                 Thread.Sleep(1000);
  22:             }
  23:         }
  24:     }
  25:  
  26:     existingOwners = GetAzureGroupOwners(group.Id);
  27:     foreach (User existingOwner in existingOwners)
  28:     {
  29:         if (!newOwners.Any(u => string.Compare(u.UserPrincipalName,
  30:             existingOwner.UserPrincipalName, true) == 0))
  31:         {
  32:             var result = graph.Groups[group.Id].Owners[existingOwner.Id].
  33:                 Reference.Request().DeleteAsync();
  34:             result.Wait();
  35:             while (result.Status == TaskStatus.WaitingForActivation)
  36:             {
  37:                 Thread.Sleep(1000);
  38:             }
  39:         }
  40:     }
  41: }

In this method we use helper function GetAzureGroupOwners:

   1: public static List<User> GetAzureGroupOwners(string groupId)
   2: {
   3:     try
   4:     {
   5:         var graph = new GraphServiceClient(new AzureAuthenticationProvider());
   6:         var result = new List<User>();
   7:         var owners = graph.Groups[groupId].Owners.Request().GetAsync();
   8:         while (owners.Result.Count > 0)
   9:         {
  10:             foreach (var owner in owners.Result)
  11:             {
  12:                 if (!(owner is User))
  13:                 {
  14:                     continue;
  15:                 }
  16:  
  17:                 result.Add((User)owner);
  18:             }
  19:  
  20:             if (owners.Result.NextPageRequest != null)
  21:             {
  22:                 owners = owners.Result.NextPageRequest.GetAsync();
  23:             }
  24:             else
  25:             {
  26:                 break;
  27:             }
  28:         }
  29:         return result;
  30:     }
  31:     catch (Exception x)
  32:     {
  33:         Console.WriteLine("Error occured when getting Azure " +
  34:             "group's owners: '{0}'\n{1}", x.Message, x.StackTrace);
  35:         return new List<User>();
  36:     }
  37: }

and AzureAuthenticationProvider from previous post Work with Azure AD via Microsoft Graph API.

Method SetAzureGroupOwners() receives reference to the Azure AD group and list of users which should be set as owners. Please note that we are talking here about replacing existing owners on new ones, i.e. we need not only to add new owners but also delete those existing owners which don’t exist in this list. So at first we add new owners (lines 10-24) and then remove missing existing owners (lines 26-40). At the end group will only have those owners which are passed in the list to the function.

Tuesday, May 9, 2017

Update Azure AD group via MS Graph client library

In my previous posts I showed several scenarios of using MS Graph client library, see:

Work with Azure AD via Microsoft Graph API
Create Azure AD group and set group owner using Microsoft Graph Client library
Retrieve paginated data from Azure AD via Microsoft Graph Client library

In this article we will continue to get familiar with the MS Graph client library and see how to update Azure AD group programmatically. Examples in this post will use the same AzureAuthenticationProvider class for authenticating against Azure AD as in examples provided above so I won’t duplicate it here.

Here is how we can rename Azure AD group programmatically using MS Graph client library:

   1: renameGroup("oldGroupName", "newGroupName");
   2:  
   3: ...
   4:  
   5: static void renameGroup(string oldName, string newName)
   6: {
   7:     var graph = new GraphServiceClient(new AzureAuthenticationProvider());
   8:     var group = getGroup(oldName);
   9:     group.DisplayName = newName;
  10:  
  11:     var groupReq = new GroupRequest(graph.Groups[group.Id].Request().RequestUrl,
  12:         graph, new List<Option>());
  13:     var result = groupReq.UpdateAsync(group);
  14:  
  15:     do
  16:     {
  17:         Console.WriteLine("Result status: {0}", result.Status);
  18:         Thread.Sleep(5000);
  19:     } while (result.Status == TaskStatus.WaitingForActivation);
  20: }
  21:  
  22: static Group getGroup(string name)
  23: {
  24:     var graph = new GraphServiceClient(new AzureAuthenticationProvider());
  25:     try
  26:     {
  27:         var groups = graph.Groups.Request().GetAsync();
  28:         int requestNumber = 1;
  29:         while (groups.Result.Count > 0)
  30:         {
  31:             foreach (var g in groups.Result)
  32:             {
  33:                 if (string.Compare(g.DisplayName, name, true) == 0)
  34:                 {
  35:                     return g;
  36:                 }
  37:             }
  38:  
  39:             if (groups.Result.NextPageRequest != null)
  40:             {
  41:                 groups = groups.Result.NextPageRequest.GetAsync();
  42:             }
  43:             else
  44:             {
  45:                 break;
  46:             }
  47:         }
  48:         return null;
  49:     }
  50:     catch (ServiceException x)
  51:     {
  52:         Console.WriteLine("Exception occured: {0}", x.Error);
  53:         return null;
  54:     }
  55: }

At first in method retrieveGroup() we get reference on the Group object and update group’s DisplayName property (lines 7-9). Then we create GroupRequest object and call it’s UpdateAsync method (lines 11-13) and wait until request will be processed (lines 15-19). After that group will appear in Azure portal with new name. But note that if group was already used in Sharepoint Online site (e.g. for granting permissions on some site) changes won’t be synced here automatically – you will need to sync user profiles and then update user data in User information list.