Thursday, August 27, 2015

One problem with Set-SPUser cmdlet

When you use Set-SPUser cmdlet which sets user properties, e.g. email:

Set-SPUser -Identity "domain\username" -Web http://example.com -Email user@example.com

you may get the following error message:

You must specify a valid user object or user identity

This error may be shown if your web application uses claims-based authentication, but you specified username in command line arguments in regular Windows format: domain\username. In order to avoid this error try to use claims format for username:

Set-SPUser -Identity "i:0#.w|domain\username" -Web http://example.com -Email user@example.com

It should fix the problem.

Tuesday, August 25, 2015

One of the reasons of why sending email via client object model may not work in Sharepoint

Some time ago we faced with the following problem: when send email via CSOM using Utility.SendEmail method we got the following error:

The e-mail message cannot be sent. Make sure the e-mail has a valid recipient.

When Sharepoint processes request from Utility.SendEmail method on server side it uses SPUtility.SendEmail_Client internal method:

   1: internal static void SendEmail_Client(EmailProperties properties)
   2: {
   3:     SPWeb web = SPContext.Current.Web;
   4:     if (web == null)
   5:     {
   6:         throw new SPException(SPResource.GetString("ContextWebNotFound",
   7:             new object[0]));
   8:     }
   9:     if (properties.To.Count == 0)
  10:     {
  11:         throw SPUtility.GetInvalidRecipientsException(web.LanguageCulture);
  12:     }
  13:     web.CheckPermissions(SPBasePermissions.CreateAlerts);
  14:     if (SPAdministrationWebApplication.Local.OutboundMailServiceInstance == null ||
  15:         string.IsNullOrEmpty(SPAdministrationWebApplication.Local.
  16:             OutboundMailServiceInstance.Server.Address))
  17:     {
  18:         throw new ConfigurationErrorsException();
  19:     }
  20:     Encoding encoding = Encoding.UTF8;
  21:     try
  22:     {
  23:         encoding = Encoding.GetEncoding(web.Site.WebApplication.
  24:             OutboundMailCodePage);
  25:     }
  26:     catch (ArgumentException)
  27:     {
  28:         encoding = Encoding.UTF8;
  29:     }
  30:     using (MailMessage mm = new MailMessage())
  31:     {
  32:         SPUtility.ResolveAddressesForEmail(web, properties.To, delegate(MailAddress a)
  33:         {
  34:             mm.To.Add(a);
  35:         });
  36:         SPUtility.ResolveAddressesForEmail(web, properties.CC, delegate(MailAddress a)
  37:         {
  38:             mm.CC.Add(a);
  39:         });
  40:         SPUtility.ResolveAddressesForEmail(web, properties.BCC, delegate(MailAddress a)
  41:         {
  42:             mm.Bcc.Add(a);
  43:         });
  44:         if (mm.To.Count == 0 && mm.CC.Count == 0 && mm.Bcc.Count == 0)
  45:         {
  46:             throw SPUtility.GetInvalidRecipientsException(web.LanguageCulture);
  47:         }
  48:         try
  49:         {
  50:             SPPrincipalInfo sPPrincipalInfo = SPUtility.ResolvePrincipal(web, properties.From,
  51:                 SPPrincipalType.All, SPPrincipalSource.All, null, false);
  52:             if (sPPrincipalInfo != null && !string.IsNullOrEmpty(sPPrincipalInfo.Email))
  53:             {
  54:                 mm.From = new MailAddress(sPPrincipalInfo.Email,
  55:                     (sPPrincipalInfo.DisplayName != null) ?
  56:                         sPPrincipalInfo.DisplayName : "", Encoding.UTF8);
  57:             }
  58:         }
  59:         catch (FormatException)
  60:         {
  61:         }
  62:         if (mm.From == null)
  63:         {
  64:             mm.From = new MailAddress(web.Site.WebApplication.OutboundMailSenderAddress,
  65:                 web.Title, Encoding.UTF8);
  66:         }
  67:         mm.Subject = properties.Subject;
  68:         mm.IsBodyHtml = true;
  69:         mm.BodyEncoding = encoding;
  70:         mm.SubjectEncoding = encoding;
  71:         mm.Body = properties.Body;
  72:         foreach (KeyValuePair<string, string> current in properties.AdditionalHeaders)
  73:         {
  74:             mm.Headers[current.Key] = current.Value;
  75:         }
  76:         mm.Headers["SharePointSiteId"] = web.Site.ID.ToString("B");
  77:         SmtpClient smtpClient = new SmtpClient(SPAdministrationWebApplication.Local.
  78:             OutboundMailServiceInstance.Server.Address);
  79:         smtpClient.Send(mm);
  80:     }
  81: }

As you can see it throws GetInvalidRecipientsException in 2 cases:

  • when To recipients are not specified (line 11)
  • when another internal method SPUtility.ResolveAddressesForEmail method doesn’t calls provided delegates for To, Cc and Bcc recipients and as result appropriate recipients lists are empty (line 46)

In our case To recipient was specified for sure, so the only reason was problems in SPUtility.ResolveAddressesForEmail method: because of some reason it didn’t resolve specified recipient. Let’s see the code of this method:

   1: private static void ResolveAddressesForEmail(SPWeb web, IEnumerable<string> addresses,
   2:     SPUtility.AddressReader func)
   3: {
   4:     if (addresses == null)
   5:     {
   6:         return;
   7:     }
   8:     foreach (string current in addresses)
   9:     {
  10:         if (!string.IsNullOrEmpty(current))
  11:         {
  12:             SPPrincipalInfo sPPrincipalInfo = SPUtility.ResolvePrincipal(web, current,
  13:                 SPPrincipalType.All, SPPrincipalSource.All, null, false);
  14:             if (sPPrincipalInfo == null)
  15:             {
  16:                 ULS.SendTraceTag(3146118u, ULSCat.msoulscat_WSS_General, ULSTraceLevel.Medium,
  17:                     "ResolveAddressesForEmail : No user is resolved from the input '{0}', ignored.",
  18:                         new object[]
  19:                 {
  20:                     current
  21:                 });
  22:             }
  23:             else
  24:             {
  25:                 if (sPPrincipalInfo.PrincipalId <= 0)
  26:                 {
  27:                     ULS.SendTraceTag(3146119u, ULSCat.msoulscat_WSS_General, ULSTraceLevel.Medium,
  28:                     "ResolveAddressesForEmail : Resolved input '{0}' is not registered as the site " +
  29:                     "collection user, ignored. Login '{1}', Email '{2}'", new object[]
  30:                     {
  31:                         current,
  32:                         sPPrincipalInfo.LoginName,
  33:                         sPPrincipalInfo.Email
  34:                     });
  35:                 }
  36:                 else
  37:                 {
  38:                     if (!string.IsNullOrEmpty(sPPrincipalInfo.Email))
  39:                     {
  40:                         try
  41:                         {
  42:                             func(new MailAddress(sPPrincipalInfo.Email, (sPPrincipalInfo.DisplayName != null) ?
  43:                                 sPPrincipalInfo.DisplayName : "", Encoding.UTF8));
  44:                             continue;
  45:                         }
  46:                         catch (FormatException)
  47:                         {
  48:                             continue;
  49:                         }
  50:                     }
  51:                     if (string.IsNullOrEmpty(sPPrincipalInfo.Email) && sPPrincipalInfo.IsSharePointGroup &&
  52:                         web.DoesUserHavePermissions(SPBasePermissions.BrowseUserInfo))
  53:                     {
  54:                         SPGroup byNameNoThrow = web.SiteGroups.GetByNameNoThrow(sPPrincipalInfo.LoginName);
  55:                         if (byNameNoThrow != null)
  56:                         {
  57:                             foreach (SPUser sPUser in byNameNoThrow.Users)
  58:                             {
  59:                                 if (!string.IsNullOrEmpty(sPUser.Email))
  60:                                 {
  61:                                     try
  62:                                     {
  63:                                         func(new MailAddress(sPUser.Email, (sPUser.Name != null) ?
  64:                                             sPUser.Name : "", Encoding.UTF8));
  65:                                     }
  66:                                     catch (FormatException)
  67:                                     {
  68:                                     }
  69:                                 }
  70:                             }
  71:                         }
  72:                     }
  73:                 }
  74:             }
  75:         }
  76:     }
  77: }

As you can see it internally uses SPUtility.ResolvePrincipal method which resolve principals based on provided parameters. I tried to call this method for recipient which we specified for Utility.SendEmail and it returned not-null principal:

   1: var p = SPUtility.ResolvePrincipal(web, "domain\\username", SPPrincipalType.All,
   2:     SPPrincipalSource.All, null, false);

But PrincipalId property of returned object was negative (-1). As result SPUtility.ResolveAddressesForEmail method didn’t use this principal and added the following line to the log (lines 25-35):

ResolveAddressesForEmail : Resolved input 'domain\user' is not registered as the site collection user, ignored.

The problem was in the format which we used for specifying email recipient: we used “domain\user” format, while the correct way is to use claims format “i:0#.w|domain\user”. After changing the code emails start working.

Monday, August 24, 2015

Hide toolbar of XsltListViewWebPart in Sharepoint Online using client side rendering

When you add OTB XsltListView web part on the page by default it shos toolbar over the list content:

image

Of course you may remove this toolbar manually by going to web part properties and setting Toolbar Type to “No toolbar”. But what to do if we need to set it automatically during provisioning without manual work?

If you worked with XslListViewWebPart in Sharepoint 2010 then you probably know that even with server object model it is possible to remove toolbar only using reflection, e.g. like shown here:

   1: try
   2: {
   3:   MethodInfo ensureViewMethod = lvwp.GetType().GetMethod("EnsureView",
   4:         BindingFlags.Instance | BindingFlags.NonPublic);
   5:   object[] ensureViewParams = { };
   6:   ensureViewMethod.Invoke(lvwp, ensureViewParams);
   7:   FieldInfo viewFieldInfo = lvwp.GetType().GetField("view",
   8:     BindingFlags.NonPublic | BindingFlags.Instance);
   9:   SPView view = viewFieldInfo.GetValue(lvwp) as SPView;
  10:   Type[] toolbarMethodParamTypes = { Type.GetType("System.String") };
  11:   MethodInfo setToolbarTypeMethod = view.GetType().GetMethod("SetToolbarType",
  12:     BindingFlags.Instance | BindingFlags.NonPublic, null, toolbarMethodParamTypes, null);
  13:   object[] setToolbarParam = { "None" }; //set the type here
  14:   setToolbarTypeMethod.Invoke(view, setToolbarParam);
  15:   view.Update();
  16: }
  17: catch { }

But how to do the same in Sharepoint Online where we don’t even have server object model (not even mention reflection)? People on the forums tried to do it via client side object model (CSOM), but without luck, because setting View.Toolbar property to <Toolbar Type='None'/> is ignored: see Hiding the toolbar of a Listview webpart through CSOM.

Fortunately in Sharepoint 2013 there is another powerful mechanism which can be used for hiding toolbar of XsltListViewWebPart: client side rendering. Of course it can be used for other purposes as well, but in this article we will use it for hiding toolbar.

First of all we need to define template override in separate js file and upload this file to the Sharepoint doclib:

   1: (function () {
   2:     var overrideCtx = {};
   3:     overrideCtx.Templates = {};
   4:  
   5:     overrideCtx.BaseViewID = 1;
   6:     overrideCtx.ListTemplateType = 100;
   7:     overrideCtx.OnPostRender = postRenderHandler;
   8:  
   9:     SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
  10: })();
  11:  
  12: function postRenderHandler(ctx) {
  13:     jQuery("td.ms-list-addnew").hide();
  14: }

After that we need to set JSLink property of web part pointing to this file. E.g if our file is uploaded to Site assets link will be the following;

   1: <property name='JSLink' type='string'>~site/Site assets/crs-list-view.js</property>

In example above we defined override template with custom post render handler. In the handler we hide DOM element which contains toolbar. After that XsltListViewWebPart will look like this:

image

Hope that this information will help you.