Thursday, December 17, 2015

SharePoint 2010 workflow emails to groups not working, strange characters in "to" field

SharePoint workflow notification emails are a useful way of communicating document and item status changes to users involved in that document/items lifecycle.  You can also associate groups with the workflow notification to send emails to more than one user.  This is what I was attempting to do.




I was working on a complex workflow when the notifications suddenly appeared to have stopped working.  The workflow would fire and correctly set variables, but never send the email notification.  If I added myself to the workflow notification email, it worked, but there were strange characters such as "1" or "#" in the to field.



Turns out, the fix was very simple, but I lost an afternoon trying to figure it out so wanted to post it here.


It appeared at first glance that the notifications that worked and the ones that didn't were identical, set up as follows:



Even if you looked at the workflow email both cases appeared identical:





So, after a good deal of head scratching and some fruitless internet searches, I found the difference between the notifications that worked and the ones that didn't.

By default, when you use a group in the "to" field of the workflow email, it is configured as follows:




However, for groups, this doesn't work!  Instead, set the return field as "
Email addresses, Semicolon Delimited" for the group notification to work:



Now my workflow notifications against a group work perfectly.

Like so many things in SharePoint, its a small issue, but difficult to track down if you aren't sure what is causing it.  Hopefully this will save you some time!







Thursday, September 3, 2015

Document Content Type lookup site column change not reflected in Document Libraries

We have discovered a glitch with SharePoint that prevents the usage of a modified column in a content type in document libraries that use that content type.


If you have an existing site column used by your content type in SharePoint, you may wish to update it.  In our case, we wanted a lookup column to also return the ID of a record so that we could use that ID in filters within the hosting document libraries.


We navigated to site settings, clicked site columns, and located the lookup column that was associated with our content type.  We added a checkbox beside the "Add a column to show each of these additional fields - ID" value.  And clicked OK.


When we looked at the content type itself, you cannot see the ID values reflected here.  This is normal. The Content Type page does not reflect additional fields for lookup values.


However, we could also not see the ID field in our document libraries that used this content type.  Even if we created a new document library, and added the content type, the ID field was still not available.


As a last resort, we DELETED the site column from the content type that had the lookup.  We then re-added the same column that was just deleted from.


Now, when I navigate to any document library that uses the lookup column, I can also see the ID value for that lookup column.  This can now be used for view and web part filters to restrict document records to those that just match the lookup value.

Tuesday, May 26, 2015

Adding a User to Multiple Groups

In SharePoint 2013 using JQuery and the People Picker Control

The Problem

You have a SharePoint site with a large number of groups.  In our case, the groups are there to support highly granular document notifications, where a specific group needs to be notified when certain document groups are modified.
If you use the default SharePoint client interface, adding a user to multiple groups can be cumbersome and time consuming as you are only able to do it one at a time.  Of course, there are powershell and server side ways of doing this, but we needed a client interface that would allow us to select a user, then select multiple groups to either add or remove. 
This solution provides that interface.

Resources

This post is based on a number of resources.
 
Blog post that describes groups components of solution:

 
Microsoft Documentation on People Picker Control:
 
Stack Exchange Question and answer about adding a people picker to a custom web part:

 

Functionality

 
STEP ONE:
User Navigates to page with Picker and groups controls embedded.  All controls are empty (no values).

 
STEP TWO:
User enters name or email address in people picker.  People picker resolves user.

 
STEP THREE:
User clicks “Get User’s Groups” and groups controls are populated.  Control on left marked “Available Groups” contains all groups from current site that user is NOT a member of.  Control on right marked “Assigned Groups” contains all groups the identified user currently belongs to.

 
STEP FOUR:
User can select as many groups as needed from available groups.  Clicking the double arrow button pointing right results in user being added to those groups.

 
STEP FIVE:
User can select as many groups to remove the member from as they wish on the right site control, clicking the double arrow button pointing left results in users being removed from all groups specified.

 

Script Libraries

Depending on your approach to hosting the page, you may need to add these script libraries at the top of your HTML.  If you added these components to a web part page, these libraries are included.  If you create a standalone page, you will need to add these references:
<SharePoint:ScriptLink name="clienttemplates.js" runat="server" LoadAfterUI="true" Localizable="false" />
    <SharePoint:ScriptLink name="clientforms.js" runat="server" LoadAfterUI="true" Localizable="false" />
    <SharePoint:ScriptLink name="clientpeoplepicker.js" runat="server" LoadAfterUI="true" Localizable="false" />
    <SharePoint:ScriptLink name="autofill.js" runat="server" LoadAfterUI="true" Localizable="false" />
    <SharePoint:ScriptLink name="sp.js" runat="server" LoadAfterUI="true" Localizable="false" />
    <SharePoint:ScriptLink name="sp.runtime.js" runat="server" LoadAfterUI="true" Localizable="false" />
    <SharePoint:ScriptLink name="sp.core.js" runat="server" LoadAfterUI="true" Localizable="false" />
 

 

JQuery Libraries

You will need to have the two JQuery Libraries imported on your page to provide functionality.  I have my libraries hosted in the local site assets folder, so my links look like this:
These links are pasted into the “PlaceHolderAdditionalPageHead” on my page, place them as appropriate for your context.
<script src="../SiteAssets/jquery-1.11.3.js" type="text/javascript"></script>
<script src="../SiteAssets/jquery.SPServices-2014.02.js" type="text/javascript"></script>

 

HTML Markup

The HTML markup used on the SharePoint page can be inserted into a content editor webpart, or embedded through designer if you so choose.  I chose the latter option and added to the ContentPlacedholderMain section of my SharePoint page.
The HTML Markup is as follows:
<table align="center">
              <tr>
                        <td colspan="3" align="right">
                                    <table align="Center">
                                                <tr>
                                                            <td>User:&nbsp;<div id="peoplePickerDiv"></div></td>
                                                            <td><br/><input type="button" value="Get User's Groups" onclick="getUserInfo()"></input></td>
                                                </tr>
                                    </table>
                        </td>
              </tr>
              <tr><td><br/><br/></td></tr>
              <tr> 
                <th class='ms-vh2'>Available Groups</th> 
                <th></th> 
                <th class='ms-vh2'>Assigned Groups</th> 
              </tr> 
              <tr> 
                <td class='ms-vb2'> 
                  <select id="my_SPGroupsAvailable" style="width:400px;height:500px;" multiple="multiple"></select> 
                </td> 
                <td> 
                  <button id="my_AddGroupsToUser" type="button" style="width:80px;" onclick="AddGroupsToUser()">
                                    &gt;&gt;</button><br/><br/> 
                  <button id="my_RemoveGroupsFromUser" type="button" style="width:80px;" onclick="RemoveGroupsFromUser()">
                                    &lt;&lt;</button></td> 
                <td class='ms-vb2'> 
                  <select id="my_SPGroupsAssigned" style="width:400px;height:500px;" multiple="multiple"></select> 
                </td> 
              </tr> 
            </table>
 
The people picker is identified by the peoplePickerDiv, Available Groups and Assigned groups divs contain the two group collections for the chosen user.

 

Javascript

The javascript that supports the functionality described is pasted below.  Examine the comments for details on functionality.
// Run your custom code when the DOM is ready.
                        $(document).ready(function () {
                            // Comment out the below line to ensure JS loading
                            //alert("Document Ready Function Run.");
                            // set up the people picker ready for use using control div
                            initializePeoplePicker('peoplePickerDiv');
                            //
                        });
                       
                        // PEOPLE PICKER JAVASCRIPT
                       
                        // Render and initialize the client-side People Picker.
                        function initializePeoplePicker(peoplePickerElementId) {
                                    // Create a schema to store picker properties, and set the properties.
                            var schema = {};
                            schema['PrincipalAccountType'] = 'User,DL,SecGroup,SPGroup';
                            schema['SearchPrincipalSource'] = 15;
                            schema['ResolvePrincipalSource'] = 15;
                            schema['AllowMultipleValues'] = false;
                            schema['MaximumEntitySuggestions'] = 50;
                            schema['Width'] = '280px';
                       
                            // Render and initialize the picker.
                            // Pass the ID of the DOM element that contains the picker, an array of initial
                            // PickerEntity objects to set the picker value, and a schema that defines
                            // picker properties.
                            this.SPClientPeoplePicker_InitStandaloneControlWrapper(peoplePickerElementId, null, schema);
                        }
                       
                        // Query the picker for user information.
                        function getUserInfo() {
                                    //alert("Get user info clicked");
                            // Get the people picker object from the page.
                            var peoplePicker = this.SPClientPeoplePicker.SPClientPeoplePickerDict.peoplePickerDiv_TopSpan;
                       
                            // Get information about all users.  Only use first user entered.
                            // control settings prevent entry of multiple users (schema['AllowMultipleValues'] = false;)
                            // this approach left here to allow further customizing
                            var users = peoplePicker.GetAllUserInfo();
                       
                                    // if the user array has an element
                                    if(users.length > 0) {
                                                RefreshGroupLists(users[0].Key);
                                    } else {
                                                alert("No user entered.  Enter name or email address into user field.");
                                    }
                        }
                        // END OF PEOPLE PICKER JAVASCRIPT
                       
                        // START OF GROUPS CONTROLS JAVASCRIPT
                        // Populate groups lists based on loginName of user identified in people picker
                        function RefreshGroupLists(loginName){
                          var strHTMLAvailable = "";
                          var strHTMLAssigned = "";
                          var arrOptionsAssigned = new Array();
                          var intOpts = 0;
                          var booMatch;
                          var booErr = "false";
                          $("#my_SPGroupsAssigned").html("");
                          $("#my_SPGroupsAvailable").html("");
                          if($("#my_SiteUser").attr("value") == 0){
                            alert("You must select a user");
                            return;
                          }
                          // Populate the groups that the current user belongs to.
                          // populate the array that determines which groups are available (arrOptionsAssigned)
                          $().SPServices({ 
                           operation: "GetGroupCollectionFromUser", 
                                 userLoginName: loginName, 
                                 async: false, 
                                 completefunc: function(xData, Status)
                                 {
                                   $(xData.responseXML).find("errorstring").each(function() {
                                       alert("User not found");
                                       booErr = "true";

                                       return;
                                    });
                                    $(xData.responseXML).find("Group").each(function() {
                                        strHTMLAssigned += "<option value='" + $(this).attr("Name") + "'>" + $(this).attr("Name") + "</option>";
                                        arrOptionsAssigned[intOpts] = $(this).attr("Name");
                                        intOpts = intOpts + 1;

                                    });
                                    $("#my_SPGroupsAssigned").append(strHTMLAssigned); 
                           }
                          });
                          //Populate available site groups using current sites group collection, do not use groups identified by strOptionsAssigned array
                          if(booErr == "false"){
                            $().SPServices({ 
                                operation: "GetGroupCollectionFromSite", 
                                async: false, 
                                completefunc: function(xData, Status) { 
                                  $(xData.responseXML).find("Group").each(function() {
                                    booMatch = "false"; 
                                    for (var i=0;i<=arrOptionsAssigned.length;i++){ 
                                      if($(this).attr("Name") == arrOptionsAssigned[i]){ 
                                        booMatch = "true"; 
                                        break; 
                                      } 
                                    } 
                                    if(booMatch == "false"){ 
                                      strHTMLAvailable += "<option value='" + $(this).attr("Name") + "'>" + $(this).attr("Name") + "</option>"; 
                                    } 
                                  }); 
                                  $("#my_SPGroupsAvailable").append(strHTMLAvailable); 
                                }
                            }); 
                          }
                        } 
                       
                        // Take people picker identied user, iterate through groups array adding user to each group
                        function AddGroupsToUser(){
                          var i;  
                          //alert("add groups to user fired");
                          var peoplePicker = this.SPClientPeoplePicker.SPClientPeoplePickerDict.peoplePickerDiv_TopSpan;
                          var users = peoplePicker.GetAllUserInfo();
                          if($("#my_SPGroupsAvailable").val() == null){ 
                            alert("You haven't selected any groups to add"); 
                            return; 
                          } 
                          else{ 
                            var arrGroups = $("#my_SPGroupsAvailable").val();
                            for (i=0;i<arrGroups.length;i++){ 
                              $().SPServices({ 
                                  operation: "AddUserToGroup", 
                                  groupName: arrGroups[i], 

                                  userLoginName: users[0].Key, 
                                  async: false, 
                                  completefunc: null 
                              }); 
                            }
                            // repopulate page with updated groups
                            RefreshGroupLists(users[0].Key); 
                          } 
                        }   
                        // Take people picker identified user and remove from groups array removing user from each group
                        function RemoveGroupsFromUser(){ 
                          var i;  
                          //alert("remove groups from user fired");
                          var peoplePicker = this.SPClientPeoplePicker.SPClientPeoplePickerDict.peoplePickerDiv_TopSpan;
                          var users = peoplePicker.GetAllUserInfo();
                          if($("#my_SPGroupsAssigned").val() == null){
                            alert("You haven't selected any groups to remove"); 
                            return; 
                          } 
                          else{ 
                            var arrGroups = $("#my_SPGroupsAssigned").val(); 
                            for (i=0;i<arrGroups.length;i++){ 
                              $().SPServices({ 
                                  operation: "RemoveUserFromGroup", 
                                  groupName: arrGroups[i], 
                                  userLoginName: users[0].Key,
                                  async: false, 
                                  completefunc: null 
                              });
                            }
                            RefreshGroupLists(users[0].Key);

                          }
                        }