Sunday, December 28, 2014

Maintain multiple copies of same document effectively using “Manage Copies” feature

Sometimes we need to have copies of same document in multiple SharePoint sites. Let’s assume that each site should contain important documents as shown below.

image

Above setup prompts us following questions

Q1. Is it a good practice to upload documents manually in each site?

Q2. What if we have thousands of sites?

Q3. What if those documents need to be updated regularly?

Above valid questions leads us to keep those documents centrally and publish copies to whatever locations they need to be.

We can achieve above task using SharePoint search, but with few limitations

  • Original location of documents are visible to end users
  • Difficult to check amount of copies (in published sites) from the original document location
  • Difficult to publish a particular version only to a selected sites
  • and many more…

How can we overcome above limitations?

Instead of using search, we can use out of the box and heavily underrated “Manage Copies” feature.

Following are the steps to publish and share documents using “Manage Copies” feature

1. Store documents in a document library

image

2. Select the document and click on “Manage Copies” icon

image

3. Click on “New Copy” link and provide the URL of the destination site

image

3. I prefer to click on “Update Copies” link to copy documents instantly

image

3. We can check destination libraries to see the success

image

As shown above we can maintain multiple copies of the same documents, where we can store the original document centrally.

As we have now solved half of the problem, let’s focus on how to update copies of a particular document.

Let’s say that there is an amendment to the leave policy and we have thousands of project sites those having copies of that leave policy. How can we reflect the change to those sites? It’s very easy. Following are the steps

1. Check Out the document in the original document library

image

2. Do the change to the document

image

3. Save the document and Check In. Remember to select “Update copies of this document with the new version” option

image

4. This will prompt another window showing all the copies of that document. If we don’t want the update to reflect on a particular site we can leave it by not checking the checkbox.

image

5. We can check destination libraries to see the success

image

In this article I explained the usage of “Manage Copies” feature to effectively manage copies of important documents.

Hope this helps someone Smile

Monday, December 15, 2014

Install SQL Server Reporting Services in multi server SharePoint 2013 farm with Windows 2012 R2 and SQL Server 2014 hosted in Azure VMs

Recently I installed SQL Server Reporting Services in SharePoint integrated mode in following multi server environment. I used Windows Server 2012 R2 for the operating system and SQL Server 2014 as the database server. My server farm is hosted in Azure VMs.
image
So following are the steps I used to install and configure SSRS

1. Installation of Reporting Services in SharePoint mode and Reporting Services add-in for SharePoint in Application server
  • Download SQL Server 2014 image in application server and start the setup
  • Select Reporting Services – SharePoint and Reporting Services Add-in for SharePoint
  • Complete the installation
rs

2. Installation of Reporting Reporting Services add-in for SharePoint in WFE server
  • Download or copy SQL Server 2014 image in WFE server and start the setup
  • Select Reporting Services Add-in for SharePoint
 image
  • Complete the installation
3. Register SQL Server Reporting Services service in SharePoint 2013
  • We have to execute following two commands in both servers
Install-SPRSService
Install-SPRSServiceProxy
4. Start SQL Server Reporting Services service in App server
image

5. Create new SQL Server Reporting Services service application in Central Administration
image
That’s all we need to do.

Caution !!

Prior to the above successful configuration, I tried the same using Reporting Services Add-in utility downloaded from Microsoft.

Unfortunately it got the SQL Server Reporting Services Service stuck in Starting
Capture
Finally I was able to complete it after downloading the SQL Server image to my server to do the configuration as I mentioned above

Monday, November 17, 2014

SharePoint Demonstration and Presentations at Gartner Symposium - Gold Coast Australia

I was lucky to be a part of Sri Lankan pavilion in Gartner Symposium 2014. I presented and demonstrated SharePoint 2013 and Office 365 capabilities. Following were the most looked after business requirement

  • More user friendly Wiki sites
  • SharePoint Apps
  • Migration to cloud
  • Business process automation

IMG_0633IMG_0642

IMG_0640 

IMG_0654

Wednesday, October 22, 2014

SharePoint 2013 on Azure–Solving “Error while enabling Windows feature : NetFx3” in Windows Server 2012 R2

When we deploy SharePoint 2013 in Windows Azure, there are few obstacles we need to tackle. I had following conditions in my environment
  • Windows Server 2012 R2 Datacenter image as the VM image
  • Virtual machines are located in Azure Virtual Network
In this article series I will discuss some issues we faced and solutions we used to overcome them
In this post I will explain the first issue we came across.
The issue occurred while we installing SQL Server, where the installation process failed to install dot net framework 3.5. The actual error message was as below
“Error while enabling Windows feature : NetFx3, Error Code : -2146498298 , Please try enabling Windows feature : NetFx3 from Windows management tools and then run setup again. For more information on how to enable Windows features , see http://go.microsoft.com/fwlink/?linkid=227143
As certain sources mentioned, I used “Add Roles and Features Wizard” and specified alternate source path to pic relevant files from Windows image. But it still prompted me following error.
image
I tried the same task using command line, but the operation failed after 62% or 66%
After hours of struggling, following are the remedies I used to arrive at a solution

Remedy 1 : Disable IP6, and Provide Google DNS as an alternative DNS
image   image

Remedy 2 : Download the latest Windows Server 2012 R2 Data Center Image
From this link you can download the trial image.

Remedy 3 : Configure group policy “Specify settings for optional component installation and component repair
When this policy is enabled, a network location (for example, a file server) can be specified for both repair of the feature file store, and to enable features that have their payload removed. The Alternate source file path can point to a \sources\sxs folder or a Windows image (WIM) file using the WIM: prefix
To enable the policy, navigate to Computer Configuration\Administrative Templates\System in Group Policy Editor
image
image
After enabling the policy, run “gpupdate” to refresh the policy

Remedy 4 : Execute following command to Activate dot net 3.5
Open a PowerShell windows as the administrator and execute following command to install dot net 3.5 with required prerequisites. I extracted the downloaded image in a drive so I provided that location as the source
  1. Dism /online /enable-feature /featurename:NetFx3 /All /Source:E:\sources\sxs /LimitAccess
Above remedies allowed me to install dot net 3.5 and continue the SQL Server installation without further issues.

Monday, October 6, 2014

Awarded Microsoft MVP for SharePoint Server

On 1st of October 2014 I was honored with the MVP (Most Valuable Professional) award for SharePoint Server.

download

It was a dream come true and thanks Microsoft for recognizing the work our community have done.

I take this opportunity to thank my colleagues who helped me to achieve this specially Prabath Fonseka, Wellington Perera, Katherine Chen and Sri Lankan SharePoint community.

It's been a long and arduous journey so far, but the real challenge is just beginning.

Thursday, September 25, 2014

Resolve “The property does not exist on type 'SP.ListItem'. Make sure to only use property names that are defined by the type” error when posting field values using SharePoint REST api

When I tried to update field data of a document in a document library using REST api I got Bad Request (400) error message. Surprisingly this was the same code which is working on my office 365 environment. Following is the full error message

The property 'ContentTypeId' does not exist on type 'SP.ListItem'. Make sure to only use property names that are defined by the type

Following is the code I used to update the document

  1. var updateUrl = "http://sp13/sites/dev/_api/Web/GetFileByServerRelativeUrl('/Sites/dev/Lists/DOA/Test/CSM.docx')/ListItemAllFields";
  2.  
  3. $.ajax(
  4. {
  5.    'url': updateUrl,
  6.    'method': 'POST',
  7.    'data': JSON.stringify({
  8.        '__metadata': { 'type': 'SP.ListItem' },
  9.        'ContentTypeId': '0x0101005C9775F3130F7A498C5C8CF69C1750AE03'
  10.    }),
  11.    'headers': {
  12.        'accept': 'application/json;odata=verbose',
  13.        'content-type': 'application/json;odata=verbose',
  14.        'X-RequestDigest': $('#__REQUESTDIGEST').val(),
  15.        'X-Http-Method': 'MERGE',
  16.        "If-Match": "*"
  17.    },
  18.    'success': function (data) {
  19.        alert('success');
  20.    },
  21.    'error': function (err) {
  22.        alert('error');
  23.    }
  24.   }
  25. );

The reason for above error is that the type attribute is not compatible in this scenario. I need to provide the exact entity name instead of generic SP.ListItem.

How can I find the entity name for the list? it is very easy. I just need to provide following request.

http://sp13/Sites/dev/_api/lists/getbytitle('DOA')

Following is the entity name that I should use in my code

image

Following is the modified code that works on my on premises environment as well

  1. var updateUrl = "http://sp13/sites/dev/_api/Web/GetFileByServerRelativeUrl('/Sites/dev/Lists/DOA/Test/CSM.docx')/ListItemAllFields";
  2.  
  3. $.ajax(
  4. {
  5.    'url': updateUrl,
  6.    'method': 'POST',
  7.    'data': JSON.stringify({
  8.        '__metadata': { 'type': 'SP.Data.DOAListItem' },
  9.        'ContentTypeId': '0x0101005C9775F3130F7A498C5C8CF69C1750AE03'
  10.    }),
  11.    'headers': {
  12.        'accept': 'application/json;odata=verbose',
  13.        'content-type': 'application/json;odata=verbose',
  14.        'X-RequestDigest': $('#__REQUESTDIGEST').val(),
  15.        'X-Http-Method': 'MERGE',
  16.        "If-Match": "*"
  17.    },
  18.    'success': function (data) {
  19.        alert('success');
  20.    },
  21.    'error': function (err) {
  22.        alert('error');
  23.    }
  24.   }
  25. );

Friday, September 19, 2014

Get “Multiple lines of text” field value using SharePoint 2013 REST services

If we get list item from SharePoint REST services, we may get unexpected results for “Multiple lines of text” fields. Let’s take the scenario given below. I used following service to get results.
https://sp13/sites/dev/_api/web/lists/getbytitle('r')/items(4)
Instead of the field value we may get a escaped html string like below.
image
The reason for this behavior is that the column is enabled for rich text. If we want to keep the rich text input, we may need to escape the string using RegEx for some JavaScript functionality. For an example we can use following script to display expected comment in a textbox.
tmpComment = data.d.AGMComment != null ? data.d.AGMComment.replace("'", "") : "";
$('#txtApprovalComments').val($(tmpComment).text());
But if we don’t need the rich text behavior we can simply make the column to accept only plain text.
image
After modifying column settings as shown above, following will be the output from the same service call
image

Wednesday, September 17, 2014

SharePoint 2013 Visual Studio based workflow - customize task approval email to include link to respective task

In SharePoint 2013 workflow architecture is drastically changed when compared with SharePoint 2010. Some new activities like “HttpSend” are introduced to the platform and some activities are improved a lot. On the other hand some scenarios became very hard to implement.

Let’s assume that we have a SharePoint 2013 Visual Studio based workflow and need to assign a task to a user. We expect the notification email to contain the link to respective task. isn’t it ?

In SharePoint 2010 it was very easy to include the task url in the mail. But that is not the case anymore. It’s not possible to include the task url in the default mail. Instead it is sent like below which is not user friendly.

image

In this post I’ll show a workaround to resolve the problem. Following are the steps we need to perform

1. Add “SingleTask” activity and configure necessary parameters

image

2. Modify WaitForTaskCompletion property to false

This will avoid the “SingleTask” activity to wait until the approval. Furthermore change “WaiveAssignmentEmail” property value to “Yes” which avoids the notification email.

image

3. Compose an email manually

In this email I’ll include a link to assigned task. To get the guid of task list, I will use “GetTaskListId” activity. Furthermore I’ll get the task id from “SingleTask” activity output.

From those elements I’ll construct the url of the assigned task

e.g.: “http://sp13/sites/dev/DOAApprovalForm.aspx?List="+taskListId.ToString()+"&ID="+taskItemId+"

Then I’ll construct the email body as I wish

  1. "<html><body style='font-size:11pt;font-family:Segoe UI Light,sans-serif;color:#444444;'><div>Contract comment is : " + contractComment+ " </br>Please approve this <a href="+siteUrl + "SitePages/DOAApprovalForm.aspx?List="+taskListId.ToString()+"&ID="+taskItemId+">Task</a></div></body></html>"

 

4. Handle Approve or Reject actions using a “Pick” activity

We will use “WaitForFieldChange” activity to pause the workflow until user presses Approve or Reject buttons. Since there are two possible values in the pausing field we need to use a “Pick” activity

image 

5. As mentioned earlier, we will use “WaitForFieldChange” activities on both branches, configured for “TaskOutcome” field.

image

For the first “WaitForFieldChange” activity, the FieldValue is set to “Approved” and for the other one it is “Rejected”

6. I’ll update the taskOutcome with respective values to continue the workflow

image

This will allow us to modify email as we wish and pause the workflow until the task is approved or rejected

image

Tuesday, September 2, 2014

Open a div in SharePoint modal dialog

Sometimes we need to load a div in modal popup instead of a page. Following is an example code that we can use in such scenarios.

In this scenario I use a modal popup to load file upload control in the same page.

  1. function Opendialog(ctType) {
  2.   SP.SOD.executeOrDelayUntilScriptLoaded(function () {
  3.     var element = document.createElement('div');
  4.     element.innerHTML = '<input type="file" id="documentUpload" accept="*" /><input id="btnUpload" type="button" value="Upload" onclick="UploadFile(\'' + ctType + '\')"; />';
  5.  
  6.     SP.UI.ModalDialog.showModalDialog({
  7.      html: element,
  8.      title: 'Document Upload',
  9.      allowMaximize: false,
  10.      showClose: true,
  11.      dialogReturnValueCallback : Function.createDelegate(null, CloseCallback),
  12.      autoSize: true
  13.     });
  14.    }, "SP.js");
  15. }
  16.  
  17. function CloseCallback(result, target) {
  18.    location.reload(true);
  19. }

Wednesday, August 20, 2014

SharePoint 2013 - Generate document with data using server object model

When we provide a content management solution with SharePoint, often we need to generate reports or documents using existing data. To generate reports we can use SSRS with “SharePoint List” as the data source. But, how can we generate a word document in a given template?

In this article I will show how to generate a word document with data using SharePoint server object model. To explain the concept I will implement following scenario.

  • Generate contract approval summary document from a template
  • Fill approvers section from list fields

Following are the steps we need to follow.

1. Create a content type and associate with list fields

I’ve created the content type with necessary fields in visual studio and deployed to the farm.

image

2. Associate the content type to a document library and go to advanced settings to change the document template

we will navigate to advanced settings and upload an empty word document (e.g.: template.docx)

image

3. Click on edit template and modify the document to build the template

image

To add dynamic content, we will use quick parts section of the document. As you can see, the content type fields are available in the quick parts section. we will embed those fields to our document accordingly

image

image

Then we will save changes

4. Use following code to create document

  1. var web = SPContext.Current.Web;
  2. web.AllowUnsafeUpdates = true;
  3.  
  4. var tplurl = listItem.ParentList.ContentTypes["Contract Approval Summary"].DocumentTemplateUrl;
  5. byte[] stream = web.GetFile(tplurl).OpenBinary();
  6.  
  7.  
  8. var itemProperties = new Hashtable();
  9. itemProperties.Add("AGMName", agmName);
  10. itemProperties.Add("RegionalControllerName", regionalName);
  11. itemProperties.Add("RVPName", rvpName);
  12.  
  13. listItem.ParentList.RootFolder.Files.Add(listItem.DisplayName + " Approval Summary.docx", stream, itemProperties, true);
  14. web.AllowUnsafeUpdates = false;

That will create the document with required fields.Those fields will be embedded to the document as per the template

Sunday, August 3, 2014

Programmatically add tasks to timeline in SharePoint 2013 Tasks list

In a previous post I described about the new Timeline feature in SharePoint 2013. But tasks are not added to the timeline automatically, which is a huge drawback. As a result we need to create the task and it to the timeline manually as below.

image

From this post I will explain how to add tasks to timeline automatically. There are two options to derive at a solution. They are,

  • Using JavaScripts
  • Using an Event Receiver

In this article I will show a way to implement the task using Item Added event receiver configured to Tasks list type. Following are two logical steps included in the solution.

1. Set required properties in the tasks list

In the RootFolder of a particular Tasks list, there are three properties relevant to the Timeline control. When we create the list, those properties are not automatically set. As a result we have to set those three properties, namely,

  • TimeLineDefaultView
  • TimeLineAllViews
  • TimeLine_TimeLine

2. Get xml document in TimeLine_TimeLine property, and add required nodes

We have to add xml nodes to “tskSet” and “mlSet” sections to represent the task which is to be displayed in Timeline control.

Following is the sample code I used to execute above steps

  1. var web = properties.Web as SPWeb;
  2. var doc = new XmlDocument();
  3. var taskList = web.Lists[properties.ListId];
  4. var root = taskList.RootFolder;
  5.  
  6. if (root.Properties["Timeline_Timeline"] == null)
  7. {
  8. //Add timeline related properties
  9. root.SetProperty("TimelineDefaultView", "Timeline");
  10. root.SetProperty("TimelineAllViews", "Timeline");
  11. //Add default xml to Timeline_Timeline property
  12. root.SetProperty("Timeline_Timeline",@"<TLViewData><fmtSet><fmt id='0' clr='FFEE2222' thm='0001' t1='0' t2='1' type='0' /><fmt id='1' clr='FFEE2222' thm='0001' t1='2' t2='3' type='1' /><fmt id='2' clr='FFEE2222' thm='0001' t1='4' t2='5' type='2' /><fmt id='3' clr='FFEE2222' thm='0001' t1='6' t2='7' type='3' /></fmtSet><fltSet><ft id='{00000000-0000-0000-0000-000000000000}' uid='4294967295' uidSrc='1' onTL='0' fmt='1' y='4294967282' x='0' h='20' /></fltSet><tskSet><t id='{00000000-0000-0000-0000-000000000000}' uid='4294967295' uidSrc='1' onTL='0' fmt='0' ch='4294967295' /></tskSet><options dateFormat='255' panZoomT='9' ProjSummFmt='3' showDates='1' showProjSummDates='0' showToday='1' showTS='1' timelineHeight='133' timelineWidth='-1' timescaleT='8' todayT='10' /><mlSet>
  13. <m id='{00000000-0000-0000-0000-000000000000}' uid='4294967295' uidSrc='1' onTL='0' fmt='2' y='35' x='0' /></mlSet><txtSet><style id='0' type='0' clr='FFEE2222' thm='0001' sz='8' font='Segoe UI' bold='0' ital='0' und='0' strk='0' /><style id='1' type='1' clr='FFEE2222' thm='0001' sz='8' font='Segoe UI' bold='0' ital='0' und='0' strk='0' /><style id='2' type='2' clr='FF999999' thm='0001' sz='8' font='Segoe UI' bold='0' ital='0' und='0' strk='0' /><style id='3' type='3' clr='FFB3B3B3' thm='0001' sz='8' font='Segoe UI Light' bold='0' ital='0' und='0' strk='0' /><style id='4' type='4' clr='FF525051' thm='0001' sz='10' font='Segoe UI' bold='0' ital='0' und='0' strk='0' /><style id='5' type='5' clr='FFB3B3B3' thm='0001' sz='8' font='Segoe UI Light' bold='0' ital='0' und='0' strk='0' /><style id='6' type='6' clr='FF999999' thm='0001' sz='9' font='Segoe UI' bold='0' ital='0' und='0' strk='0' /><style id='7' type='7' clr='FF999999' thm='0001' sz='8' font='Segoe UI' bold='0' ital='0' und='0' strk='0' /><style id='8' type='8' clr='FF999999' thm='0001' sz='8' font='Segoe UI' bold='0' ital='0' und='0' strk='0' /><style id='9' type='9' clr='FFFFA614' thm='0001' sz='8' font='Segoe UI Semibold' bold='1' ital='0' und='0' strk='0' /><style id='10' type='10' clr='FFFFA72B' thm='0001' sz='10' font='Segoe UI Semibold' bold='0' ital='0' und='0' strk='0' /></txtSet></TLViewData>");
  14. root.Update();
  15. }
  16.  
  17. doc.LoadXml(root.Properties["Timeline_Timeline"].ToString());
  18.  
  19. //Create required xml nodes
  20. var tElement = doc.CreateElement("t");
  21. tElement.SetAttribute("id", "{" + properties.ListItemUniqueId.ToString() + "}");
  22. tElement.SetAttribute("uid", properties.ListItemId.ToString());
  23. tElement.SetAttribute("uidSrc", "1");
  24. tElement.SetAttribute("onTL", "1");
  25. tElement.SetAttribute("fmt", "0");
  26. tElement.SetAttribute("ch", "4294967295");
  27.  
  28. var mElement = doc.CreateElement("m");
  29. mElement.SetAttribute("id", "{" + properties.ListItemUniqueId.ToString() + "}");
  30. mElement.SetAttribute("uid", properties.ListItemId.ToString());
  31. mElement.SetAttribute("uidSrc", "1");
  32. mElement.SetAttribute("onTL", "1");
  33. mElement.SetAttribute("fmt", "2");
  34. mElement.SetAttribute("y", "35");
  35. mElement.SetAttribute("x", "0");
  36.  
  37. //Add created nodes to xml document
  38. doc.SelectNodes("/TLViewData/tskSet/t")[doc.SelectNodes("/TLViewData/tskSet/t").Count - 1].AppendChild(tElement);
  39. doc.SelectNodes("/TLViewData/mlSet/m")[doc.SelectNodes("/TLViewData/mlSet/m").Count - 1].AppendChild(mElement);
  40. root.SetProperty("Timeline_Timeline", doc.OuterXml);
  41. root.Update();
  42.  
  43. base.ItemAdded(properties);

After deploying the solution, tasks are added to the timeline automatically.

Thursday, July 31, 2014

Get the display name of a value in person of group field from SharePoint 2013 "_api/web/lists" service

Let’s assume that we need to access a SharePoint list using built in web services. To do the task we can use a service like below.
http://sp13/sites/dev/_api/web/lists/getbytitle('Regions')/items 
What happen if we have a “Person or Group” column in the list? We would get a result like below.

image

As you can see we get only the User Id instead of other properties like login name or display name.
To get those properties, we just need to expand respective properties as shown below.
http://sp13/sites/dev/_api/web/lists/getbytitle('Regions')/items?$select=Area_x0020_General_x0020_Manager/Name,Area_x0020_General_x0020_Manager/Title&$expand=Area_x0020_General_x0020_Manager/Id
Following is the result I got, which is what I expected

image

Friday, July 25, 2014

Cross site collection data access via HTTP web service for a SharePoint 2013 designer workflow. Resolving Unauthorized exception

Recently I created a SharePoint 2013 approval workflow where I had to take approvers from a list located at a different SharePoint site collection.

In this example I have two site collections named Config and Project where I have created the workflow in the Project site collection using SharePoint Designer. In the Config site collection I have a list called Approvers where I store relevant information regarding approvers.

Following diagram shows the scenario I described.

image
To get relevant information from Config site collection, I will use SharePoint api web services. For an example I could use following service to return all items in that list.
http://sp13/Sites/config/_api/web/lists/getbytitle('Approvers')/items
Calling a HTTP web service through a SharePoint 2013 Designer based workflow is straight forward. I have explained that in this blog post.

Although I have followed the steps correctly, I was getting an “Unauthorized” exception all the time. I’ve given necessary permissions to the list and web application but still I was getting the same error.

Later I figured out that we need to provide permissions to the workflow explicitly if we need to access resources beyond the current site. Following are the steps I followed to configure permissions to the workflow

1. Activate “Workflows can use app permissions” site feature in the site where I create the workflow (http://sp13/Sites/project )
image
2. Navigate to Site Settings –> Site app permissions where you can see an item called workflow
image
3. Copy the client section of the App Identifier as shown below
image
4.Navigate to <site url>/_layouts/15/appinv.aspx to configure permissions. Then add the client section of the App Identifier in the App Id section and click Lookup to populate content.
image
5. Then we need to specify App’s Permission Request XML
<AppPermissionRequests>
    <AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" />
</AppPermissionRequests>
Since we are accessing beyond the current site collection, we will use “http://sharepoint/content/tenant” as the scope. More information on app permission and available scopes can be found on this article.

6. Finally we will trust the workflow
image
This will allow our workflow to access resources beyond the current site collection.