Monday, September 30, 2013

Nintex Workflow 2010 - Multiple Dynamic E-Mail Attachments

We have a Nintex Workflow and I need to send a list of documents in a folder to a customer as email attachment. Sound easy !!

Although this could be an easy tasks unfortunately I couldn't find an easy way to do this, because the "Attachment" input element only takes a full path to a single file.

That means you can't loop through a folder and collect all files with a seperator in a variable and give it to the attachment control. Or better would be to collect all paths in the "Collection" variable and give this variable to the attachment control. This also does not work in my Nintex version. Maybe the Nintex guys already added this feature.

The only way I found is to save each file in a separate variable. So I had to make a restrictions like, "I've maximum 5 attachments".

So in my Workflow I've 3 variables (you can have more):

  • fileAttachPath: Contains the path to the folder like http://sharepoint/sites/projectX/docs
  • fileAttach1: is initialized with fileAttachPath (single line of text)
  • fileAttach2: is initialized with fileAttachPath (single line of text)
  • filesCollection: a collection variable to hold the files
  • fileToAttach: Need when we loop throught filesCollection (single line of text variable)

We initialize the variables fileAttach1/2 with fileAttachPath because an empty variable would cause an error in the email attachment. See http://connect.nintex.com/forums/thread/18201.aspx.



Now we have a list lookup task which loops through the list and writes the names of the files into a collection variable



Now comes a tricky one. We have to write the attachments into the variables. We need "For Each" task. We loop through the "filesCollection" variables and write each item into the filesToAttach variable.



Next we use "Run If" task to fill our variables. We begin with the highest variable, im my case "fileAttach2".
The condition is:

  • Check if fileToAttach = fileAttachPath and
  • fileAttach1 is not fileAttachPath

If we had more than 2 attachments we would check the prior one.



If this is successfull fill the variable fileAttach2 with the path to the file:




The last check is the one for the variable fileAttach1.



Now we can add the variables as attachments to any email in Nintex.

Here a look to the workflow diagram  (It's in german but it should be clear)



If someone found a better way, comments are appreciated.



Monday, September 23, 2013

Ways to redirect http requests in SharePoint

The are multiple szenarios when you need a redirect in SharePoint:

  • You've migrated SharePoint into a new URL (e.g http://sp2010 to http://sp2013)
  • You moved a site collection
  • You moved a sub site into a new site collection

If you want to redirect in SharePoint without custom solutions you have the following options:


Redirect with XML Viewer or Content Editor WebPart


You can redirect with JavaScript by inserting a WebPart into the site and adding simple JavaScript. This is easy to configure but redirects only the site in which the WebPart sits (Probably the Mainsite). If users have saved links the their document libraries this method fails, unless you put the same WebPart into the document library.

So go to the site you want to redirect and the XML Viewer WebPart. Open the WebPart settings and add the following line of code:

<script type="text/javascript">
location.href = "<<the url you want to redirect>>";
</script>



One drawback of this way to redirect is that you don't have an easy on/off button to activate this redirect. Once the WebPart is saved you can't edit the site anymore, because you're always redirected.

If you want to edit the site you'v to remove the WebPart by typing the URL and adding the querystring ?contents=1 to the site url like http://sharepoint/myproject?contents=1

You can also use the html meta tag "refresh" to redirect your page. Here is an example with a popup indicating that the page has moved and the meta tag.


IIS HTTP Redirect


This is a feature in IIS which have to be enabled. It is used to redirect all requests to another destination. This fits perfect when you have upgraded SharePoint.

Check within the Server Manager in the Web Server Role if this feature is already installed. Otherwise you can install it also from the command line:

dism /online /Enable-Feature /FeatureName:IIS-HttpRedirect

After you've enabled this feature you'll see it in the IIS Manager.


The HTTP Redirect Feature


Click on your SharePoint webapplication and then on "Http Redirect" to open the feature. 

In this example we migrated to from SharePoint 2010 to SharePoint 2013. The new url ist http://sp2013. So when someone enters the url http://sp2010/sites/myproject he should be redirected to  http://sp2013/sites/myproject.

Configure Redirect

If you want to do more granular redirect like including query parameter you can use parameter like $V and $Q. 

When you edit and apply redirect clear your browser cache for testing.

References:


Redirect with URL Rewrite 2.0 Module


The URL Rewrite 2.0 Module is not a feature in IIS 7.5. It has to be downloaded and installed from here. Once installed you can see the module within you features in IIS.

URL Rewrite module


This is a very might tool as you can use wildcard and regular expression. This means that you can also redirect untill item level in SharePoint.

In the following example I moved a site "projectx" under http://sharepoint into an own sitecollection http://sharepoint/sites/projectx.

Open the URL Rewrite Module in IIS within your webapplication. Click on "Add Rule" on the right pane. Add a name for your rule like "Redirect Projectx into Sitecollection"

The pattern must be server relative. Check Using "Wildcards" as we use asterisks "*" within the pattern.

In the action pane select "redirect" and the type the url you want to redirect. I added also "{R:1}" at the end of the redirect. This is a parameter which delivers all the right side from the pattern. So you can redirect all request under http://sharepoint/projects/xxxxx to http://sharepoint/sites/projectx/xxxxx.

You can see the parameters when you test the pattern by clicking "Test Pattern".


The rewrite module provide lots of more options for a granular redirect. You can check within the link I've provided in the reference.

Please not that when you apply a rule the web.config of the webapplication is changed and you get an IIS refresh. Another thing to pay attention is a when you change a rule, the rule is applied when you disable and enable it again.

References:

Thursday, September 19, 2013

Move a SharePoint Team Wiki Library between SharePoint Sites or Site Collections

In this post I describe how you can move a team wiki from one site to another. The difficulties here are:

  • The "Save as Template" option in the list settings does not exist. This is because this type of list is deprecated. In SharePoint 2010 you should use Enterprise Wikis.
  • After copying the list, the links in the wikis site must be adapted to the new site.

Ways to copy the Wiki Library

The are three "easy" ways to copy the Wiki Library from one site to the other. The "Save As Template" Link does not exist in the Library Settings but the option still exists.

SharePoint Designer

Open the site with the SharePoint Designer. Click on your wiki and go the the library settings. You'll find a "Save as template" button there.

"Save as Template" option in SharePoint Designer


SharePoint UI

You can use the standard "Save As Template" UI by modifying the URL

Go to the Wiki Library. Normally you browse the first wiki site, but go to the wiki library page by typing  /wikilibname/Forms/AllPages.aspx or by the Ribbon

"All Pages" in a wiki site
Now go the library settings.
Wiki Library Settings

Here change the url. Replace "listedit.aspx" by "savetmpl.aspx" in the browser and you will land at the "Save As Template" Site.
"Save as Template" form

PowerShell

You can also save a wiki template with PowerShell. This option is interesting for farm admins. Open the SharePoint Management Shell and type the following:


After the command ends succesfully you should have a template in your list templates gallery.


Download & Copy Template

Now download the template from the list templates gallery in the old site and upload it to your new site into the list templates gallery.


"List templates" gallery in your site collection

Upload a new template into your gallery


Create a new library from your template. Go to "View All Site Content" then "Create" and select your new template.

Adapt Wiki Links

To adapt the links within your wiki content either you do it manually or by PowerShell. If you haave the option to use PowerShell I can offer the following script.

Adapt the variables for your environment and start the script within the SharePoint Management Console. This script will loop through all your wiki items and adapt the links within your content.

 <# PowerShell Script to adapt Team Wiki Content Links after migration #>  
 # Setup Basic sites and pages  
 $teamWikiWebUrl = "http://sharepoint/sites/newweb"  
 $wikiListName  = "Applications Wiki"  
 $oldLink     = "/sites/oldweb/team%20wiki/"  
 $newLink    = "/sites/newweb/team%20wiki/"  
 try   
 {  
   Write-Host -foregroundcolor green " >> STARTING ..."  
   Write-Host -foregroundcolor green "`t1. Getting Wiki List."  
   $wikiWeb  = Get-SPWeb $teamWikiWebUrl  
   $wiki     = $wikiWeb.Lists[$wikiListName]  
   Write-Host -foregroundcolor green "`t2. Start adapting links."  
   foreach ($wikiItem in $wiki.Items) {                  
     # Replace the links in the new content  
     Write-Host -foregroundcolor green "`t`t>>Adapting links in: " $wikiItem.Name  
     $content   = $wikiItem["ows_WikiField"]  
     $newContent = $content -replace $oldLink, $newLink    
     # Add new content to the page  
     $wikiItem["ows_WikiField"] = $newContent  
     $wikiItem.Update()      
   }  
   Write-Host -foregroundcolor green  "`t3. All Links adapted !!"    
   $wikiWeb.Dispose()  
 }  
 catch [System.Exception] {  
   Write-Host "Error while adapting Wiki links...`n" -foregroundcolor red  
   $_.Exception.ToString();    
   exit  
 }   

Thursday, September 12, 2013

How to create a FAQ in SharePoint with OOB features

There are lots of stuff in the web around how to create a FAQ in SharePoint.

In this post I'll show you some tricks what you can do with additional OOB SharePoint tools to have also some search and some style within your FAQ to impress your customer. It also shows you the power of SharePoint as a developer platform.

The requirements for our FAQ are:
  • Easy to maintain
  • Easy to search content
  • Add documents as links in FAQ
  • Grouped FAQ content
  • Export FAQ when needed into a report

To understand this post you should have some basic know-how about:
  • SharePoint Lists
  • SharePoint List Views
  • HTML, CSS
  • JavaScript (SharePoint Client API)
  • SharePoint WebParts
  • CAML
or you just follow the instructions.

The following image shows us the final page.

Final FAQ Pag

Our FAQ page has a FAQ list and a document library. This is useful if you want to link on some documents in your FAQ. The page has a list WebPart with a special grouped view and also2 quick searches for documents and for the FAQ list.

I also use a category for my FAQ. To manage the category I've an own list "Basic themes", where the FAQ owner can manage his categories.


Steps to create the FAQ



1. STEP : Create a site

Create a own site collection for your FAQ. Use the blank site template.

2. STEP : Create the Basic Themes List

Use the "Custom List Template" to create the Basic themes List. Name the list "Basicthemes". This list
will be referenced by the FAQ list for categorize the FAQ. This list does need nothing but a title. This titles will categorize our FAQ.

3. STEP : Create a document library

Create a standard document library and call it "Documents". This library will contain documents referenced by the FAQ.

4. STEP : Create the FAQ List

Use the "Custom List Template" to create the FAQ List. Name the list "FAQ".

Our FAQ needs now some fields. You can easily add more fields if you want but for my FAQ I took the following fields:

Field nameField type
TitleSingle line of text
ThemeLookup to Basic Themes List on Title
QuestionMultiple line of text
AnswerMultiple line of text

5. STEP: Create a group view

Go to the FAQ list and create a new view. Call it "WebPart View". Use following settings

View option Option values
Fields : Question, Answer
Group by : Theme than title
Format: Magazine

6. STEP: Create an asset library

Create a document or asset library and name it "Site Assets". This library will contain some scripts we need for the site.

7. STEP Upload Quick Search Scripts

Upload the following scripts to the "Site Assets" library. Adapt the list/site names in the scripts with the names of your list/site.

Search FAQ Script

Script to search FAQ content. Copy the code into a .txt file and upload it to the "Site Assets" library.
Adapt the list url and site url settings in the code. Check in the "BAR.CreateXML" function that the field names are set correct like in your library.

This script contains also some css to make the list view more readable.

 <style type="text/css">  
 #SearchResults ul { padding-left:10px }  
 #SearchResults li { margin-bottom:5px }  
 #SearchResults span { color:#666 }  
 .biggySearcher div.s4-search { width:100%; height:30px }  
 .biggySearcher .ms-sbplain { width:150px }  
 .ms-listviewtable tbody tr+tr+tr .ms-rtestate-field {  
  font-weight:bold;  
  font-size: 14px;  
  background-color:#eee;  
  padding:2px 0;  
  border-bottom:1px solid #de3400  
 }  
 .ms-listviewtable tbody tr+tr+tr+tr .ms-rtestate-field {  
  font-weight:normal;  
  font-size: inherit;  
  background-color:#fff  
 }  
 </style>  
 <script type="text/javascript">  
 if (typeof BAR == "undefined") {  
   BAR = {};    
 }  
 BAR.siteUrl   = "/sites/claims";  
 BAR.listName  = "FAQ"; // Displayname  
 BAR.listUrl    = BAR.siteUrl + '/Lists/faq'  
 BAR.resultItems = null;

 BAR.StartQuery = function() {  
   var text = document.getElementById("QueryText").value;  
   text = text.replace(/^\s+|\s+$/g, ""); // Trim  
   var result = BAR.CreateQuery(text);  
   BAR.QueryList(result);  
 }  

 BAR.CreateQuery = function( pSearchText )   
 {  
   var result = "";  
   var searchWords = pSearchText.split(" ");  
   for (var i=0;i<searchWords.length;i++){  
     if (searchWords[i] != "") {  
       if (i>0) result = "<And>" +result;  
       result += BAR.CreateXML(searchWords[i]);  
       if (i>0) result += "</And>";  
     }  
   }  
   return result;  
 }  

 BAR.CreateXML = function(pSearchText) {  
 return ' <Or>'  
   + ' <Or>'  
   + '  <Contains>'  
   + '    <FieldRef Name="Title" />'  
   + '    <Value Type="Text">'+ pSearchText +'</Value>'  
   + '  </Contains>'  
   + '  <Contains>'  
   + '    <FieldRef Name="Question" />'  
   + '    <Value Type="Note">'+ pSearchText +'</Value>'  
   + '  </Contains>'  
   + ' </Or>'  
   + '  <Contains>'  
   + '    <FieldRef Name="Answer" />'  
   + '    <Value Type="Note">'+ pSearchText +'</Value>'  
   + '  </Contains>'  
   + ' </Or>'  
 }

 BAR.QueryList = function(pSearchXML){  
   var context = new SP.ClientContext(BAR.siteUrl);  
   var oList = context.get_web().get_lists().getByTitle(BAR.listName);  
   var camlQuery = new SP.CamlQuery();  
   camlQuery.set_viewXml("<View><Query>"  
   + '<Where>'  
   + pSearchXML  
   + '</Where>'  
   + '</Query><RowLimit>10</RowLimit></View>');  
   BAR.resultItems = oList.getItems(camlQuery);  
   context.load(BAR.resultItems, 'Include(Id, Title, Frage, Basisthema)');  
   context.executeQueryAsync(  
     Function.createDelegate(this, BAR.onQuerySucceeded),   
     Function.createDelegate(this, BAR.onQueryFailed)  
   );  
 }

 BAR.onQuerySucceeded = function(sender, args) {  
   var resultHTML = [];  
   var listItemEnumerator = BAR.resultItems.getEnumerator();  
   var i = 0;    
   while (listItemEnumerator.moveNext()) {  
     i++;  
     var oListItem = listItemEnumerator.get_current();      
     var id  = oListItem.get_id();  
     var theme = oListItem.get_item('Basisthema').get_lookupValue();  
     var title = oListItem.get_item('Title');  
     var quest = oListItem.get_item('Frage').substr(0,120);  
     resultHTML.push('<li>');      
     resultHTML.push('<a href="javascript:EditItem2(null,\''+ BAR.listUrl +'/DispForm.aspx?ID='+ id +'\')">' + title + '</a>');  
     resultHTML.push('<br/>('+ theme +')');  
     resultHTML.push('<br/><span>'+ quest +' ...</span>');  
     resultHTML.push('</li>');  
   }  
   if (i == 0)  
     resultHTML.push('<li><span>No FAQ entry found.</span></li>');  
   document.getElementById("SearchResults").innerHTML = resultHTML.join("");  
 }

 BAR.onQueryFailed = function(sender, args) {  
   alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());  
 }  
 </script>  

 <div class="biggySearcher">  
   <div class="s4-search">      
     <table class="ms-sbtable s4-search"><tr>  
     <td class="ms-sbcell">  
       <input type="text" class="ms-sbplain" id="QueryText" />  
     </td>  
     <td class="ms-sbgo ms-sbcell">  
       <img src="/_layouts/images/gosearch15.png" alt="Search" class="srch-gosearchimg"   
         onmouseout="this.src='\u002f_layouts\u002fimages\u002fgosearch15.png'"   
         onmouseover="this.src='\u002f_layouts\u002fimages\u002fgosearchhover15.png'" title="Search"  
         onclick="BAR.StartQuery()" />  
     </td>  
     </tr></table>  
   </div>  
   <ul id="SearchResults"></ul>  
 </div>  

Script to search the document library

The second script is to set a search. The search results appear in a dialog box. I find this very handy. Copy now the following script into a .txt file and upload it also to your site assets library. Adapt the list and site parameters in the script to search the correct site and library .

 <script type="text/javascript">  
 if (typeof BAR == "undefined") {  
   BAR = {};    
 }  
 BAR.siteUrl   = '/sites/myfaq';  
 BAR.documentLibUrl = 'http://sharepoint/sites/myfaq/documents'  
 BAR.QueryDocs = function() {    
   var text = document.getElementById("QueryTextDocs").value;  
   text = text.replace(/^\s+|\s+$/g, ""); // Trim  
   text = "*" + text + "*"    
   EditItem2(null, BAR.siteUrl + '/_layouts/OSSSearchResults.aspx?k=' + text + '&cs=This%20List&u=' + encodeURIComponent(BAR.documentLibUrl) );  
 }  
 </script>  
 <div class="biggySearcher">  
   <div class="s4-search">  
     <table class="ms-sbtable s4-search"><tr>  
     <td class="ms-sbcell">  
       <input type="text" class="ms-sbplain" id="QueryTextDocs" />  
     </td>  
     <td class="ms-sbgo ms-sbcell">  
       <img src="/_layouts/images/gosearch15.png" alt="Search" class="srch-gosearchimg"   
         onmouseout="this.src='\u002f_layouts\u002fimages\u002fgosearch15.png'"   
         onmouseover="this.src='\u002f_layouts\u002fimages\u002fgosearchhover15.png'" title="Search"  
         onclick="BAR.QueryDocs()" />  
     </td>  
     </tr></table>  
   </div>    
 </div>  

8. STEP: Insert the FAQ List into your page

Goto your home site and click on edit. Add your FAQ list to the middle WebPart section. Click on "Add WebPart" and choose the FAQ list. Choose the WebPart View from the WebPart settings.

9. STEP: Insert FAQ Document Search

Goto your home site and click on edit. Add a "Content Viewer WebPart" to the right WebPart section. Go to the WebPart settings and put into the top text box the path to the uploaded txt file, which contains the documents query.

10. STEP: Insert FAQ Quick Search

Do the same for the FAQ query file.

11. STEP: Adding category filter WebPart

In this step we want to add the category filter:


Go to your home site and click on edit.Click on "Add WebPart" on the right section and choose the "HtmlFormsWebPart" from "Forms" category.

The cool thing about the html forms WebPart is that you can connect to other WebParts with it.

Open the settings of this WebPart and click on "Sourcecode editor". Put the following source code in it. Check if the list name in the code "Basicthemes" matches your list name, else change it to your list name.

This code loads the entries from the "Basicthemes" list into a dropdown menu, which can be used for filtering.

 <div onkeydown="javascript:if (event.keyCode == 13) _SFSUBMIT_" style="margin-bottom:5px">  
   <select id="selThemes" name="T1"></select>  
   <input type="button" value="Filter" onclick="javascript:_SFSUBMIT_"/>  
 </div>  
 <span style="height:11px;width:11px;position:relative;display:inline-block;overflow:hidden;">  
   <img style="position:absolute;border:0;left:0;top:-584px" src="/_layouts/images/fgimg.png" />  
 </span>  
 <a href="javascript:location.href=location.href">Reset Filter</a>  
 <script type="text/javascript">  
  function LoadSelectThemes() {  
   var context = SP.ClientContext.get_current();  
   var list = context.get_web().get_lists().getByTitle('Basicthemes');  
   var camlQuery = SP.CamlQuery.createAllItemsQuery();  
   var items = list.getItems(camlQuery);    
   context.load(items, 'Include(Title, Id)');  
   context.executeQueryAsync(  
     function(){        
       var listItemEnumerator = items.getEnumerator();  
       var oSelect = document.getElementById("selThemes");  
       while (listItemEnumerator.moveNext()) {          
         var oListItem = listItemEnumerator.get_current();  
         var title = oListItem.get_item('Title');          
         var option = document.createElement("option");  
         option.innerHTML = title;  
         option.value = title;  
         if (oSelect != null){            
           oSelect.appendChild(option);  
         }  
       }  
     },  
     function(sender, args) {  
       alert('List Data fetch failed. ' + args.get_message() + 'n' + args.get_stackTrace());  
     }  
   );  
  }  
  window.onload = function(){ ExecuteOrDelayUntilScriptLoaded(ViewItem, "sp.js"); };
 </script>  

12. STEP: Connecting the filter

In this step we connect the Form WebPart with the list view WebPart on the site. Edit you home site. Go to your WebPart Menu of your HTML Form WebPart and click on connections and choose FAQ.


In the upcoming Window choose :
  • Source field = T1
  • Consumer field = Themes (The Lookup field)


Click on "Save" and save your WebPart Settings. The connection should be etablished now.

13. STEP: Testing

Most things should be straight forward. The HTMLFormWebPart caused me sometimes that the whole site stopped working because of wrong JavaScript code. In this case remove the WebPart by adding ?contents=1 to the URL. You will be redirected to the WebPart maintanance page.
Other problems can be that the list or site names are wrong in the scripts. Pay attention that you change every setting. Search & replace is a good tactic in such a case. You can also edit the .txt file in the site assets library in Explorer View directly in NotePad++. So don't have to down- and upload everytime.

SharePoint Installation Claims Error: Failed to create instance of cookie value handler type / handler object

I got this error while installing SharePoint 2013 with AutoSPInstaller.

STS Call Claims Windows: Failed to get cookie value handler type / object

Opening the logs I saw following error messages:


This error was also caused when I tried to use Get-SPSite -Limit All

As the error says that it is a claims problem I first checked the SharePoint Security Token Service but the web service was fine. When I browse the web service, the page is rendered without errors:


After:
  • Check if Windows Web Services Pool has started
  • Check if "Claims To Windows Token Service" has started
  • Check if IIS checkbox "enable for 32bit Applications" is checked
  • Provisioning the Security Service again
  • Check if the "Claims To Windows Token Service" is running under Localsystem account
  • Check if in IIS > Windows Authentication "Enable Kernel Mode Authetication" is enabled
  • And Reinstalling SharePoint
I got still the same error.

Then I decided to debug the code and searched after the error message and found out the the error is thrown when an instance of the SPSessionSecurityTokenCookieValue object is made.

When you look at the constructor of this object:



the error is created from one of this calls. I opened a Powershell command and typed:

System.Security.Cryptography.HashAlgorithm]::Create("SHA256")

The result end in an error with something containing FIPS. And here It made click.

We had a GPO enabled, that only FIPS compliant algorithms for encryption should be used. Disable this policy and reboot to solve the problem


Following policies should be also checked:
  • Impersonate a client after authetication
  • Log on as a batch job
  • log on as a service