Thursday, 31 January 2019

Assigning Microsoft Flow approvals to SharePoint Groups

One of the still missing (at time of writing!) features in Flow is the ability to assign approvals to a SharePoint group. This is a common scenario if you’re coming from a SharePoint background, or are working with a business process setup around a SharePoint site with lists and so on. This post will explain how to retrieve members from a SharePoint group and assign them to an approval task in Flow.

Some more background

In traditional SharePoint workflows (on-prem, hybrid or in the cloud), workflow tasks are often assigned to SharePoint groups. This means we can avoid hard coding individuals into the process and make things easier to manage when people come and go from the company.
Currently, Flow doesn’t let you assign directly to anything other than a named email. However, we do have the ability to assign the approval action to a string variable… so all we need is a list of approver emails in a variable. I also want the users to come from one of my SharePoint groups. Should be easy right? Kind of!
The SharePoint REST API provides a nice endpoint to retrieve the users from within a specific group. This endpoint is available at /_api/web/sitegroups/getbyname(‘Group display name’)/users
So, to get those users within a Flow, we need to hit that endpoint. Historically it used to be quite tricky to make SharePoint REST API calls in Flow. To call back to SharePoint using the HTTP request action, a lot of extra leg work was needed to get proper access – registering an app in Azure, granting permissions to your tenancy… thankfully, we no longer need to do this with the ‘Send an HTTP request to SharePoint’ action. You don’t need to do any app registrations or even pass form digests when doing POST requests.
Enough background info, here’s how to do it!

How to do it

Step 1 – Have your SharePoint site ready. Create a list of the items you want to request approval for. You’ll also need the name of the SharePoint group in the site that you wish to use for assigning the approval task(s)
Step 2 – Create your Flow from blank (go to flow.microsoft.com in your desired tenancy, then go to My Flows then ‘Create from blank’)
Step 3 – For the trigger, I’ve gone with ‘SharePoint – for a selected item’ as my Flow will be triggered via the item context menu in my SharePoint list. You could also go for ‘SharePoint – when an item is created’ or the most appropriate trigger, depending on your approval process. If choosing ‘for a selected item’, don’t forget to configure any additional users for the Flow and consider the connection credentials. Make sure you select your list. E.g.:
Process Screenshot 1
Step 4 – Add a ‘Send an HTTP request to SharePoint’ action and configure like this to query the SharePoint API for your specified group’s users (NB if you’re making use of run-only users in Flow and using those user’s credentials, I think this may mean the running user needs access to view the members in the SharePoint group chosen or this might fail. For Flows running on creation/modified you’re fine, assuming the Flow is created with an account with access to your SharePoint groups):
Process Screenshot 2
Step 5 – Now, we need a string variable for where we will store our approver email addresses. Add a ‘Variables – Initialize variable’ action and configure like this:
Process Screenshot 3
Step 6 – Now we need to do the fun bit, which is parsing the response from the SharePoint REST API call. To do this, add a ‘Data Operations – Parse JSON’ action. The ‘Content’ is the ‘Body’ from the ‘Send an HTTP request to SharePoint’ action earlier:
Process Screenshot 4
Now for the fun bit! We need to tell Flow what schema the response is, so that we can use the SharePoint group response data properly within our Flow (and actually read the email address(es) from the response). Without doing this, we’ve just got a bunch of text and Flow can’t read specific values. To do this, we can provide an example response from the API call by clicking the ‘Use sample payload to generate schema’ link at the bottom.
Within this popup, you can paste in an example JSON response from SharePoint when you hit the endpoint at /_api/web/sitegroups/getbyname(‘Group display name’)/users. An entire example response can be pasted in, exactly as it gets returned with all the users and other values inside it. Flow is very clever and just strips out all the values themselves and leaves the schema! You can use this approach for any SharePoint REST API call. You can get one of these responses by submitting the SharePoint REST API call by using common tools such as Postman.
To save time here though, here’s the full schema you need for this scenario. Just paste this directly into the ‘schema’ section in the action and ignore the ‘Use sample payload…’ link.

{
    "type": "object",
    "properties": {
        "d": {
            "type": "object",
            "properties": {
                "results": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "__metadata": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string"
                                    },
                                    "uri": {
                                        "type": "string"
                                    },
                                    "type": {
                                        "type": "string"
                                    }
                                }
                            },
                            "Alerts": {
                                "type": "object",
                                "properties": {
                                    "__deferred": {
                                        "type": "object",
                                        "properties": {
                                            "uri": {
                                                "type": "string"
                                            }
                                        }
                                    }
                                }
                            },
                            "Groups": {
                                "type": "object",
                                "properties": {
                                    "__deferred": {
                                        "type": "object",
                                        "properties": {
                                            "uri": {
                                                "type": "string"
                                            }
                                        }
                                    }
                                }
                            },
                            "Id": {
                                "type": "integer"
                            },
                            "IsHiddenInUI": {
                                "type": "boolean"
                            },
                            "LoginName": {
                                "type": "string"
                            },
                            "Title": {
                                "type": "string"
                            },
                            "PrincipalType": {
                                "type": "integer"
                            },
                            "Email": {
                                "type": "string"
                            },
                            "IsEmailAuthenticationGuestUser": {
                                "type": "boolean"
                            },
                            "IsShareByEmailGuestUser": {
                                "type": "boolean"
                            },
                            "IsSiteAdmin": {
                                "type": "boolean"
                            },
                            "UserId": {
                                "type": "object",
                                "properties": {
                                    "__metadata": {
                                        "type": "object",
                                        "properties": {
                                            "type": {
                                                "type": "string"
                                            }
                                        }
                                    },
                                    "NameId": {
                                        "type": "string"
                                    },
                                    "NameIdIssuer": {
                                        "type": "string"
                                    }
                                }
                            }
                        },
                        "required": [
                            "__metadata",
                            "Alerts",
                            "Groups",
                            "Id",
                            "IsHiddenInUI",
                            "LoginName",
                            "Title",
                            "PrincipalType",
                            "Email",
                            "IsEmailAuthenticationGuestUser",
                            "IsShareByEmailGuestUser",
                            "IsSiteAdmin",
                            "UserId"
                        ]
                    }
                }
            }
        }
    }
}
The completed action should look something like this:
Process Screenshot 5
Step 7 – Now we’ve got the data in a format Flow can work with, we can loop through our users and append them into a semi-colon separated string of email addresses. To loop the results, click ‘New step’ then go to ‘More’ and choose ‘Add an apply to each’:
Process Screenshot 6
Step 8 – Add the ‘results’ from the previous ‘Parse JSON’ action to the apply to each action:
Process Screenshot 7
Step 9 – Within the ‘Apply to each’ action, click ‘Add an action’ and add a ‘Variables – Append to string variable’ action. Within this, we want to set the ‘approverEmails’ variable created earlier. The value will be the ‘Email’ from the ‘Parse JSON’ action, plus also add semi-colon immediately after this (important, so our email addresses are separated properly!):
Process Screenshot 8
As we parsed our JSON nicely, we can just add the email here and because we’re in an ‘apply to each’, Flow will loop through every user returned and append the email address to our approverEmails variable. Don’t forget that semi-colon!
Step 10 – Phew. Finally, we’ve now got all the users from the group into a semi-colon separated variable in our Flow. We can now go ahead and create the approvals action. Add the action and configure as per your requirements. For the ‘Assigned To’, you need to click the ‘See more’ link underneath the ‘Variables’ section under Dynamic content:
Process Screenshot 9
Then choose our ‘approverEmails’ variable:
Process Screenshot 10
Step 11 – The finished Flow should look like this:
Process Screenshot 11
Step 12 – Now all that’s left to do is name, save, and run the Flow! I setup a group called ‘My SP group’ in my site  to use in the Flow and added the following users:
Process Screenshot 12
When I run the Flow, the tasks get sent out as below (check your Approvals > Sent section within Flow to see everybody the task went to):
Process Screenshot 13
If you look at the Flow run itself, you can check through the actions to see the data returned from the SharePoint call to get the group members. You can even click through the ‘apply to each’ to see all the emails (4 in my case) which were appended to the approverEmails variable:
Process Screenshot 14

Thursday, 17 January 2019

PDF generate in SharePoint Online

You would try to use jspdf 1.2.60 as I found the author wrapped the jspdf in a anonymous function from v 1.3.0 above seems caused the error(ignore the html tag, as I tested in SPFx first).
Here is my sample test code in react script editor web part in modern page.

<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.2.60/jspdf.min.js"></script>
    <script type="text/javascript">        
        function SaveAsPDF() {
            var doc = new jsPDF();
            var specialElementHandlers = {
                '#editor': function (element, renderer) {
                    return true;
                }
            };
            doc.fromHTML($('div[class^="spFxJspdf"]').html(), 15, 15, {
                'width': 170,
                'elementHandlers': specialElementHandlers
            });
            doc.save('testfile.pdf');
        }
    </script>
    <div class="spFxJspdf_c9184c61">
        <div class="container_c9184c61">
            <div class="row_c9184c61">
                <div class="column_c9184c61">
                    <span class="title_c9184c61">Welcome to SharePoint!</span>
                    <p class="subTitle_c9184c61">Customize SharePoint experiences using Web Parts.</p>
                    <p class="description_c9184c61">SPFxJSPDF</p>
                    <a href="https://aka.ms/spfx" class="button_c9184c61">
                        <span class="label_c9184c61">Learn more</span>
                    </a>
                </div>
            </div>
        </div>
        <input type="button" id="btn_pdf" onclick="SaveAsPDF()" value="SavePDF">
    </div>

Monday, 14 January 2019

Restore files from recycle bin by Powershell Command



Restore files from recycle bin by Powershell Command 

“SharePoint Online Management Shell” is require to run the final code, so please download from here  https://www.microsoft.com/en-us/download/details.aspx?id=35588

  1. Open this link -> https://github.com/sharepoint/pnp-powershell/releases
  2. Download and install "SharePointPnPPowerShellOnline.msi"
  3. After installed .msi file, Open -> "SharePoint Online Management Shell"
  4. Run-> Connect-PnPOnline -Url https://***********.sharepoint.com/sites/TICS
  5. Enter admin User id and password (like Utpal@*******.onmicrosoft.com and ********)
  6. Get user wise documents detail in bin, replace email id of user and change csv file save path ->  Get-PnPRecycleBinItem -firststage | ? {($_.DeletedByEmail -eq 'Praveen@******.onmicrosoft.com')} | Export-Csv c:\temp\RecycleBinFiles.csv
  7. Replace email id user whom documents need to store and run -> Get-PnPRecycleBinItem -firststage | ? {($_.DeletedByEmail -eq 'Praveen@*******.onmicrosoft.com')} | Restore-PnpRecycleBinItem -Force
  8. After finished the  step 7 command, verify the recycle bin documents run step 6 again.

PeoplePicker in Provider Hosted App

Adding a Peoplepicker control to a SharePoint app is a very common requirement in any SharePoint App project. Until SharePoint 2010 this was a very easy task as Microsoft provided a people picker control which can be used in service side code.
SharePoint from 2013 onward shifted the focus shifted client side coding and hence building this control was not easily available. There are some resources available on MSDN which helps to build this control
For SharePoint hosted app:
For provide hosted app:
In this article I want to showcase one more option for building this control using Kendo control which can be used easily for people picker action. HTML dropdown control can also be used for this and can be tried.
We will be using SP.Utilities.Utility.SearchPrincipals for searching user and returning a list of users which can be bound to a dropdown control to simulate a people picker control.
The detailed steps are as below,
  1. Create a MVC based provider hosted app for SharePoint 2013.
  2. Add kendo.all.min.js in the scripts folder for reference,
  3. Add a input control to your view as below,
    1. <input name="Users" id="users" /> 
  4. Add a js file named peoplepicker.js to your solution in scripts folder. Here we will write all code for binding the control. This can be any existing file as per project structure.
  5. Add reference to below assemblies in HomeController,
    1. using System.Runtime.Serialization;  
    2. using Microsoft.SharePoint.Client.Utilities;  
  6. Add below method in Homecontroller
    1. public JsonResult searchPerson(string Id)  
    2. {  
    3.     List < SPUser > userlist = new List < SPUser > ();  
    4.     var searchUsers = new PeoplePicker();  
    5.     userlist = searchUsers.SearchPeople(Id, System.Web.HttpContext.Current.Session["SPHostUrl"].ToString(), System.Web.HttpContext.Current.Session["AccessToken"].ToString());  
    6.     return Json(userlist, JsonRequestBehavior.AllowGet);  
    7. }  
    This method assumes that SPHostURL and Access token are stored in session. The searchpeople method can return a List of SPUser which can have predefined attributes which we will be binding to dropdown control.
  7. The SearchPeople method can be build using Searchprinciples as below,
    1. public List < SPUser > SearchPeople(string searchText, string hostWeb, string contextToken)  
    2.   
    3. {  
    4.   
    5.     try {  
    6.   
    7.         List < SPUser > people = new List < SPUser > ();  
    8.   
    9.         using(SP.ClientContext ctx = TokenHelper.GetClientContextWithAccessToken(hostWeb, contextToken))  
    10.   
    11.         {  
    12.   
    13.             SP.Web web = ctx.Web;  
    14.   
    15.             ctx.Load(web);  
    16.   
    17.             var results = SP.Utilities.Utility.SearchPrincipals(ctx, web, searchText,  
    18.   
    19.                 SP.Utilities.PrincipalType.User, SP.Utilities.PrincipalSource.All, null, 10);  
    20.   
    21.             ctx.ExecuteQuery();  
    22.   
    23.             foreach(var user in results)  
    24.   
    25.             {  
    26.   
    27.                 SPUser uUser = new SPUser(user);  
    28.   
    29.                 people.Add(uUser);  
    30.   
    31.             }  
    32.   
    33.         }  
    34.   
    35.         return people;  
    36.   
    37.     } catch (Exception ex)  
    38.   
    39.     {  
    40.   
    41.         throw ex;  
    42.   
    43.     }  
    44.   
  8. Once we get the result from the above method we can bind the list of users to Kendo dropdown by below syntax,
  1. <script>  
  2.     $(document).ready(function() {  
  3.   
  4.         $("#users").kendoDropDownList({  
  5.   
  6.             dataTextField: "ContactName",  
  7.   
  8.             dataValueField: "CustomerID",  
  9.   
  10.             headerTemplate: '<div class="dropdown-header k-widget k-header">' +  
  11.   
  12.                 '<span>Photo</span>' +  
  13.   
  14.                 '<span>Contact info</span>' +  
  15.   
  16.                 '</div>',  
  17.   
  18.             valueTemplate: '<span class="selected-value" style="background-image: url(\'../content/web/Customers/#:data.CustomerID#.jpg\')"></span><span>#:data.ContactName#</span>',  
  19.   
  20.             template: '<span class="k-state-default" style="background-image: url(\'../content/web/Customers/#:data.CustomerID#.jpg\')"></span>' +  
  21.   
  22.                 '<span class="k-state-default"><h3>#: data.ContactName #</h3></span>',  
  23.   
  24.             dataSource: {  
  25.   
  26.                 transport: {  
  27.   
  28.                     read: function(e) {  
  29.   
  30.                         $http({  
  31.   
  32.                             url: '/PHA/Home/searchPerson/' + e.data.filter.filters[0].value, // pass search text value here  
  33.   
  34.                             method: "GET"  
  35.   
  36.                         }).then(function(data) {  
  37.   
  38.                             var users = [];  
  39.   
  40.                             angular.forEach(data.data, function(item) {  
  41.   
  42.                                 users.push({  
  43.   
  44.                                     Login: item.CustomerID,  
  45.   
  46.                                     Name: item.ContactName,  
  47.   
  48.                                     Email: item.CustomerEmail  
  49.   
  50.                                 });  
  51.   
  52.                             });  
  53.   
  54.                             e.success(users);  
  55.   
  56.                         }, function(error) {  
  57.   
  58.                             console.log(error);  
  59.   
  60.                         });  
  61.                     }  
  62.                 }  
  63.             }  
  64.         });  
  65.   
  66.         var dropdownlist = $("#users").data("kendoDropDownList");  
  67.   
  68.     });  
  69. </script>