Tuesday, October 30, 2012

Restore a previously restored SharePoint Site Collection with Restore-SPSite

In SharePoint you get an error if you try to restore a site collection that war previously restored. I always make a site collection backup of a site while I'm developing directly on a site and if i make any mistakes I restore the back with the Powershell command Restore-SPSite.

Common error messages were:


Restore-SPSite : The operation that you are attempting to perform cannot be completed successfully.  No content databases in the
web application were available to store your site collection.  The existing content databases may have reached the maximum number
 of site collections, or be set to read-only, or be offline, or may already contain a copy of this site collection.  Create anoth
er content database for the Web application and then try the operation again.


Reason

The reason for this problem is that you can't restore sites or web which have the same id. SharePoint don't remove all ids when you delete a site collection. (Don't know why)

Solution

In all other blogs you'll see that you need to create a new content datase or a new webapplication. There is an easier way.

First check if the site is already deleted. When you delete a site collection without PowerShell parameter -GradualDelete $false the site is deleted gradually. That means a timer job will delete the site in near future but the site is marked a deleted.

To check if the site is already deleted use the PowerShell Command "Get-SPDeletedSite".


Now we'll find out for later which content database hosts the sitecollection. Select the "DatabaseId" and type following command:

Get-SPContentDatabase | Where {$_.Id -eq "02cf20b1-b312-4111-98b9-303b8113a71d" }

Pick the name of the content database for later.

If your site is listed there, delete the site now with the command:

Remove-SPDeletedSite -Identity 8489a07c-1b1a-485e-9e7d-b0c4ffbf26b5

The number after Identity is the SiteID.

Now you need to open the "SQL Server Management Studio" to login into the SharePoint Database. We not will change anything here. We use the Studio to find the deleted Site Id.

Click on the Content Database which contains the previously deleted site and open tables. Right click on "dbo.AllWebs" and select "Select Top 100 Rows".



In the table there is a "FullUrl" attribute. Search the url of your deleted site. If you found it copy the Guid in the ID Attribute.


Now open the "SharePoint 2010 Management Console" and enter following stsadm command:

stsadm -o deleteweb -webid <ID of  the Site> -databasename <Database Name> -databaseserver <DB Server>

Example:

stsadm -o deleteweb -webid E3E814F2-8819-4132-854B-7C7548EB2229 -databasename Main_Ct01 -databaseserver NT7262


Now you're able to restore your backuped site collection again.

Thursday, September 27, 2012

Open Document directly in Edit Mode within SharePoint from an Email link

If you send a link to a SharePoint document with Email to another user, the document is opened in read only mode. It is not possible to open it directly in Edit Mode.

Therefore I wrote a little JavaScript which can do that easily.

Steps to implement:

1.) Create a new page on the sitecollection you want to have this ability. Name it OpenDoc.
2.) Add the "XML-Viewer" WebPart from "Content Rollup" category.
3.) Open the WebPart Settings of the XML-Viewer WebPart
4.) Click on XML-Editor Button and add following JavaScript into it:

<script type="text/javascript">
ExecuteOrDelayUntilScriptLoaded(LoadDoc, "sp.js");

function LoadDoc() {
  JSRequest.EnsureSetup(); 
  var file = JSRequest.QueryString["file"];
  if (typeof file != "undefined" && file != "") {    
    var baseUrl= "http://sharepoint/sites/mysite";     
    var host = location.protocol + "//" + location.host;    
    var docUrl = file.replace(host, "");
    var i = file.lastIndexOf("/");
    var listUrl = file.substr(0, i);
    editDocumentWithProgID2(docUrl, '', 'SharePoint.OpenDocuments', '0', baseUrl, '0')
    location.href = listUrl;
  }

}
</script>

5.) Adapt the baseUrl variable to your sitecollection

That's it.


When you want to open a file directly in edit mode copy the link of the file :


To open the file in edit mode you have to create the link manually. DocumentUrl is the copied file url.


  • PageUrl + ? + file= + DocumentUrl


like


  • http://sharepoint/sites/mysite/sitepages/opendoc.aspx?file=http://sharepoint/sites/mysite/docs/lavazza.xls



Thursday, September 20, 2012

Open SharePoint 2010/2013 Display, Edit, New Forms in Modal Dialogs

If you want to open a SP 2010 or in SP 2013 list item in a modal dialog you have two choices. Using the standard JavaScript functions or creating own functions.

How to find which item URL is called


If the dialog box appears you don't have an address bar to pick up the called URL. To see which URL is called when you edit or open a list item use the Internet Explorer Developer Tools.

Open your source list in the Internet Explorer. Press F12 to open the developer tools.



Click on the "Network" tab and then on "Start Capturing"



Now click the link you want to track.
You'll see the requests listed and you can also copy the urls for your need.


SharePoint usually calls the /_layout/listform.aspx with the list parameter. The form then redirects to the appropriate list form.


Method 1: Using core functions


When using SharePoint 2010 core javascript functions to open elements in dialogs mostly you'll need to know :

  • the ID of the list
  • the ID of the Content Type
  • the ID of the list item
  • the Page Type
If you don't enter the ContentTypeID parameter the default content type is selected. This does not matter if your list has only one content type.


Display Item Dialog

PageType = 4 (Display Form)

<a target="_self" href="javascript:EditItem2(null, 'http://sharepoint/yoursite/_layouts/listform.aspx?PageType=4&amp;ListId={60EDAB82-D9EF-4F89-BCEB-D9B287BDCC96}&amp;ID=1&amp;ContentTypeID=0x01020004D023775B015F43930EAF26E5B8AD35')">Urlaub in Jamaica</a>


Edit Item Dialog

PageType = 6 (Edit Form)

<a target="_self" href="javascript:EditItem2(null, 'http://sharepoint/yoursite/_layouts/listform.aspx?PageType=6&amp;ListId={60EDAB82-D9EF-4F89-BCEB-D9B287BDCC96}&amp;ID=1&amp;ContentTypeID=0x01020004D023775B015F43930EAF26E5B8AD35')">Urlaub in Jamaica</a>

New Item Dialog

PageType = 8 (New  Form)


<a href="javascript:NewItem2(null, ''http://sharepoint/yoursite/_layouts/listform.aspx?PageType=8&amp;ListId={xxxxxxxxxx}&amp;RootFolder=')">New Item</a>


Method 2: Using custom functions


You can use the SharePoint Dialog Framework to create your own dialog. You can also use the standard form template links:

  • Display Form: http://sharepoint/sites/test/Lists/Kalender/DispForm.aspx?ID=1
  • Edit Form: http://sharepoint/sites/test/Lists/Kalender/EditForm.aspx?ID=1
  • New Form: http://sharepoint/sites/test/Lists/Kalender/NewForm.aspx


Create a small javascript function which takes the url as parameter and place it somewhere in your file

 function openDialog( pUrl ) {  
   SP.UI.ModalDialog.showModalDialog(   
     {  
       url: pUrl,
       width: 500,  
       height: 500,  
       title: "My Dialog"  
     }  
   );  
 }  


Now you can call one of the forms with:


<a href="javascript:openDialog('http://sharepoint/sites/test/List/Kalender/DispForm.aspx?ID=1')">
  Open Item
</a>


You can certainly adapt the function with e.g. the ID parameter of the item and so on...


 function openCalendarDialog( pID ) {  
   SP.UI.ModalDialog.showModalDialog(   
     {  
       url: 'http://sharepoint/sites/test/List/Calendar/DispForm.aspx?ID='+pID,
       width: 500,  
       height: 500,  
       title: "My Dialog"  
     }  
   );  
 }  

The HTML to call the function would be:

<a href="javascript:openCalendarDialog(1)">
  Open Event
</a>

Friday, August 31, 2012

TFS - ItemNotFoundException on Reporting Server while creating new Team Projects

I had do deal with a problem while creating new Team Projects with Visual Studio 2010. The error message was that an an the item /Tfs2010OlapReportDS was not found.

It seemed that the reports for tfs could not be uploaded due a permission problem. After hours of searching and reinstalling I found the fix for the problem.

The fix was really easy.
  1. Go to your team foundation server 
  2. Open the administration console. 
  3. Click on Reporting
  4. Click on Edit
  5. Click on Reports Tab
  6. Enter the credential for the Reports
  7. Save
  8. Click on Start Jobs




The whole exception message :

TF30162: Task "Populate Reports" from Group "Reporting" failed Exception Type: Microsoft.TeamFoundation.Client.PcwException Exception Message: The Project Creation Wizard encountered an error while creating reports to the SQL Server Reporting Services on http://tfs/ReportServer/ReportService2005.asmx. Exception Details: The Project Creation Wizard encountered a problem while creating reports on the SQL Server Reporting Services on http://tfs/ReportServer/ReportService2005.asmx. The reason for the failure cannot be determined at this time. Because the operation failed, the wizard was not able to finish creating the SQL Server Reporting Services site.

Stack Trace: at Microsoft.VisualStudio.TeamFoundation.RosettaReportUploader.Execute(ProjectCreationContext context, XmlNode taskXml) at Microsoft.VisualStudio.TeamFoundation.ProjectCreationEngine.TaskExecutor.PerformTask(IProjectComponentCreator componentCreator, ProjectCreationContext context, XmlNode taskXml) at Microsoft.VisualStudio.TeamFoundation.ProjectCreationEngine.RunTask(Object taskObj)
-- Inner Exception --
Exception Message: The item '/Tfs2010OlapReportDS'cannot be found. (type ReportingServiceException)
Exception Stack Trace: at Microsoft.TeamFoundation.Client.Reporting.ReportingUtilities.ConvertException(SoapException e) at Microsoft.TeamFoundation.Client.Reporting.ReportingUtilities.HasPermissions(ReportingService proxy, String itemPath, String[] permissions) at Microsoft.TeamFoundation.Client.Reporting.ReportingUtilities.CheckPermissions(ReportingService proxy, String itemPath, String[] requiredPermissions) at Microsoft.TeamFoundation.Client.Reporting.ReportingUploader.Validate() at Microsoft.VisualStudio.TeamFoundation.RosettaReportUploader.Execute(ProjectCreationContext context, XmlNode taskXml)

Inner Exception Details: Exception Message: System.Web.Services.Protocols.SoapException: The item '/Tfs2010OlapReportDS' cannot be found.

Thursday, August 30, 2012

Using SharePoint Metadata Navigation in Enterprise Wikis

I thought it would be great to use the WikiCategories field in each Enterprise Wiki Page as navigation. This works relatively easy. SharePoint 2010 offers the new metadata navigation mechanism. We'll us it to create the page.

1. Goal


2. Create Enterprise Wiki Site

I had a Team Site and I wanted to create an "Enterprise Wiki" subsite. To use the Wiki site template you must first activate the "Publishing Infrastructure" Feature in your sitecollection features.

The create a new subsite with the "Enterprise Wiki" template.

3. Adapt MetaData Field

  1. Click on "Site Actions" menu on the wiki page
  2. Click on pages
  3. Click on Library in the Ribbon bar
  4. Click on "Library Settings"
  5. Click on "Wiki Category" field
  6. Adapt the field with the tags you want. The meta tags must be configured in the central admin within the metadata service.

4. Activate Metadata Feature

Go to your newly create Wiki site.

  1. Click on "Site Actions" menu
  2. Click on "Site Settings"
  3. Click on "Site features"
  4. Activate the "Metadata Navigation and Filtering" feature


5. Add List WebPart

Now edit the start page of your Enterprise Wiki. Add the "Pages" library as WebPart to your page. When you add the webpart to the page the metadata navigation will automatically appear on the left pane.

6. How it Works

What the Metadata Navigation does is to update your URL with the appropriate filter values. Therefore you can use all type of WebParts which can understand url filtering like the content query webpart or the list webpart.

7. Test it

Add tags to some of your wiki pages. Creat or edit a wiki page and add some tags in the taxonomy field on the right side.


The great thing about meta tags is that there is a hierarchie. When you filter a parent tag all pages with child tags will also appear in the results.

Another great thing is that you can use the tags for filtering in the search refinements.


Resources:

Monday, July 30, 2012

SharePoint Use Confirmation for Site Owners

A problem in SharePoint is to keep content up to date. Therefore SharePoint offers the possibility to create use confirmations. The only problem is that the confirmation can only be approved by the site collection administrators. Not in all companies the site collection admins are also the site owners. This can be due to permission restrictions or other actions that a normal site owner should not do.

In this post I'll show you how you can create your own confirmation rules and pages without using visual studio or SharePoint solution files.

Standard Site Use Confirmation

SharePoint offers an out of the box site confirmation mechanism. It send an email to the site collection owner and updates a counter of how many times a mail is sent without receiving a confirmation. The mechanism does not check the real use of the site. It simply send an email after a period of time to check the use.

You can configure it in the Central Administration  under "Application Management". There you'll find the link "Confirm site use and deletion".



Here you can configure after how many days a confirmation mail should be send to the sitecollection owner. Secondly you can also configure after how many times without response the sitecollection should be deleted.

As I am a SharePoint Admin in a company with sites that contain important information, I'm of course not a fan of workflows which automatically delete sites. I'm more interested in which sites are being used permanently and which not.

PowerShell Script

For me as an administrator the easiest way to manipulate things in SharePoint is to create PowerShell Scripts. Because of that I created a simple script which sends confirmation mails to users.

The script is relatively simple. It just loops through all site collections and checks their "CertificationDate" property. This property contains the last date the site has been confirmed.

Within a site collection  it loops through the SharePoint groups to find the site owner group. Then max. 2 site random site owners are requested to confirm the site use by an email. Additionally the SharePoint administrator is informed if the confirmation time exceeds a period you predefined. With that you have the full control over the use process.

Here comes the script. Adapt the variables in the "Variables which can be changed" section. Change the URLs, SMTP Server and Files Paths. Also adapt the name of your owners group in line 76 if you have other group names for your owners.

  1. #############################################
  2. # Loading Microsoft.SharePoint.PowerShell #
  3. #############################################
  4. $snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
  5. if ($snapin -eq $null) {
  6. Add-PSSnapin "Microsoft.SharePoint.Powershell"
  7. }
  8. # >> Get today
  9. $date = Get-Date
  10. # >> Format of the date for the log file
  11. $formatteddate = Get-Date -uformat "%Y_%m_%d_%H_%M"
  12. #############################################
  13. # Variables which can be changed #
  14. #############################################
  15. # >> Confirm mail send after 90 days of last confirmation
  16. $confirmAfterDays = 90
  17. # >> After this amount of days of tolerance the admin is informed
  18. $toleranceDays = 30
  19. # >> Name of the log file
  20. $log = "E:\Appl\SP2010\Scripts\ConfirmUsage_$formatteddate.log"
  21. # >> Name of the log file
  22. $webapplication = "http://sharepoint"
  23. # >> Email of sender
  24. $emailFrom = "sharepoint@mycompany.ch"
  25. # >> Email of admin
  26. $emailAdmin = "sharepoint@mycompany.ch"
  27. # >> Location of the useconfirmation page
  28. $pathPage = "/_layouts/MyCompany.UseConfirmation/useconfirmation.aspx"
  29. # >> SMTP Server
  30. $smtp = new-object Net.Mail.SmtpClient("mail.mycompany.ch")
  31. #############################################
  32. # Start Process #
  33. #############################################
  34. # >> Logging!!
  35. Start-Transcript -path $log
  36. # >> Create the stopwatch
  37. [System.Diagnostics.Stopwatch] $sw;
  38. $sw = New-Object System.Diagnostics.StopWatch
  39. $sw.Start()
  40. Write-Host "Starting User Confirmation Check."
  41. Write-Host "------------------------------------------"
  42. Write-Host ""
  43. try {
  44. $webapp = Get-SPWebApplication $webapplication
  45. $webapp.Sites | foreach {
  46. Write-Host "Checking Site: " $_.Url
  47. $diffDate = ($date - $_.CertificationDate).Days
  48. if ($diffDate -gt $confirmAfterDays ) {
  49. Write-Host " >> Site: " $_.Url " - Last use was for $diffDate days."
  50. $users = @()
  51. #############################################
  52. # Get Site Owners from groups #
  53. #############################################
  54. foreach($group in $_.RootWeb.SiteGroups){
  55. if ( $group.Name -like "* Owners") ) {
  56. foreach( $user in $group.Users){
  57. $users += $user.Email
  58. }
  59. }
  60. }
  61. #############################################
  62. # Select Users #
  63. #############################################
  64. $countUser = $users.Length
  65. $emailTo = ""
  66. if ($countUser -gt 2){
  67. # By more than 2 site owners select random 2
  68. $i1 = Get-Random -minimum 0 -maximum $countUser
  69. $i2 = Get-Random -minimum 0 -maximum $countUser
  70. while ($i1 -eq $i2) { $i2 = Get-Random -minimum 0 -maximum $countUser }
  71. $emailTo = $users[$i1] + ";" + $users[$i2]
  72. }else {
  73. $emailTo = $users -join ";"
  74. }
  75. #############################################
  76. # Send E-Mail to Users #
  77. #############################################
  78. if ($emailTo -ne ""){
  79. $subject = "Confirm SharePoint Web site in use"
  80. $body = "Dear Site Owner, `n"
  81. $body += "Please click on the link " + $_.Url + $pathPage +" to confirm that your site is still in use."
  82. $body += @"
  83. If the site is not being used, you can archive or delete it. Kontakt your SharePoint-Administrator for further process: sharepoint@mycompany.ch
  84. You will receive reminders of this until you confirm the site is in use, or delete it.
  85. Your Sharepoint Team
  86. "@
  87. Write-Host " >> Sending Email To: " $emailTo
  88. $smtp.Send($emailFrom, $emailTo, $subject, $body)
  89. }
  90. #############################################
  91. # Send E-Mail to Admin if site use is not #
  92. # confirmed for x days #
  93. #############################################
  94. if ( ($diffDate - $confirmAfterDays) -gt $toleranceDays ) {
  95. Write-Host " >> Sending Notice To: $emailAdmin that site use is not confirmed since $toleranceDays days."
  96. $subject = "Site Use is not confirmed since $toleranceDays days."
  97. $body = "The site use of '" + $_.Url + "' is not confirmed since $toleranceDays days. Please contact the site owner or archive the site"
  98. $smtp.Send($emailFrom, $emailAdmin, $subject, $body)
  99. }
  100. }
  101. }
  102. }
  103. catch [System.Exception] {
  104. $_.Exception.ToString();
  105. Write-Host "Error while sending confirmation usage mails."
  106. }
  107. finally {
  108. $sw.Stop()
  109. Write-Host "Time Elapsed: " $sw.Elapsed.ToString()
  110. Stop-Transcript
  111. }


Create Task Schedule

The next step is to create a task schedule which activates the script every 2 days.  Therefore go to your Application Server and open the Task Scheduler. Be sure that you are logged as Farm Account on the server.

Create a new folder in the task schedule library. Name it SharePoint.

Creat new folder in Task Scheduler


Now create a new task.

Create a new Task Scheduler Task

Name it "Site Use Confirmation". In the "Action" tab choose :
  • Action: Start a program
  • Program/script: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
  • Add argument: -command "C:\Scripts\SiteUseConfirmation.ps1"

In the "Triggers" tab create a new trigger and set the schedule you want. Save the task by clicking "OK"

Confirmation Page

Finally we need the confirmation page. The standard confirmation page of SharePoint cannot be used because only a sitecollection admin can confirm the use. But we want that everbody can do that.

So I copied the standard confirmation page and modified it for my purposes. Create a new "MyCompany.UseConfirmation" folder within the /14/TEMPLATE/LAYOUTS/ folder. Copy the useconfirmation.aspx file into the new folder.

We have to make some modifications so the new confirmation page works.

1.) Replace the DynamicMasterPageFile and Inherits attributes by  MasterPageFile="/_layouts/v4.master".

2.) Create a Page Load event. Add this piece of code above the first asp:Content element.

  1. <script runat="server">
  2. void Page_Load(object sender, System.EventArgs e)
  3. {
  4. SPSecurity.RunWithElevatedPrivileges(delegate()
  5. {
  6. using(SPSite site = new SPSite(SPContext.Current.Site.ID))
  7. {
  8. site.ConfirmUsage();
  9. Label_UseConfirmation.Text = Label_UseConfirmation.Text.Replace("%1", site.RootWeb.Title);
  10. }
  11. });
  12. }
  13. </script>

This code fragment ensures that everyone can confirm the site use. Now adapt the link in the powershell script to your new confirmation page.

Deployment

You have to copy the confirmation page to all of your frontend servers into the /14/Templates/Layouts/MyCompany.UseConfirmation/ folder.

Monday, July 23, 2012

SharePoint 2013 Developer Training Site

Found this useful site in the microsoft homepage. It contains multiple videos for SharePoint and Office 13 developers:

http://msdn.microsoft.com/en-US/office/apps/fp123626

Tuesday, July 17, 2012

Sorted Dropdown for MatchPoint Form WebPart with Distinct Elements

MatchPoint is a handy framework for creating fast custom solutions. In this post I'll show you how you can bypass some limitations with the Form WebPart.

A customer of mine wanted a sorted select in his form webpart in which he can choose the assigned user of a task list. Something like that:

Example of a sorted Dropdown

1.Attempt - The SPListChoiceProvider

You can do this easily with a ChoiceField in the Form WebPart. As provider (Source of the Dropdown) a SPListChoiceProvider would be a logical choice.

In the SPListChoiceProvider you can select the source list, a key and a value column for the dropdown. Additionally you can also select "UseDistinctValues" to remove duplicate entries.

One drawback or missing feature here is the ability to choose an order for the elements. Another is that you can only choose a single field as key. There is no possibility to combine fields.


2. Attempt - The ExpressionChoiceProvider

My second attempt was to use the ExpressionChoiceProvider. With this provide you can enter a MatchPoint Expression as source. So I tried:

Web.Lists["Jeve Masterplan 2011"].Where("AssignedTo", "!=", null).Select({"AssignedTo"}).OrderBy("AssignedTo", "Ascending").Distinct()

The result was something like that:



The order was correct bu it seems that the Distinct() Method of Linq is not working as expected. I couldn't find any solution here.

3.Attempt - Composite Field

The third attempt was to select a composite field. A composite can be designed as you want. So I selected the composite field gave it the name "fldAssignedTo" and checked the "EnableSelection" checkbox.

As provider I chose the ListDataProvider.

To design the composite I chose the XslTransformer because it's much more powerfull than the PatternTransformer. The Composite field gives you a lot flexibility. So the first thing I made was to map fields, so I can use them in the XSL.

Mappings


Within the Composite Field  you can mark selectable html elements  with the attribute Selectable="1". With Selectable="1" the html elements get clickable and you can connect them to other MatchPoint WebParts. Herefore EnableSelection must be activated on the field properties.

Then I tried the following XSL Code. It creates a simple small div with clickable p elements.

 <xsl:stylesheet version="1.0" .. >  
  <xsl:key name="Persons" match="//Row" use="Person" />  

  <xsl:template match="/">  
   <div style="height:60px; width:200px; overflow:auto">     
    <xsl:for-each select="//Row[generate-id(.) = generate-id(key('Persons', Person))]">  
     <xsl:sort select="Person"/>  
     <xsl:apply-templates select="." />  
    </xsl:for-each>  
   </div>  
  </xsl:template>  

  <xsl:template match="Row">    
   <p Selectable="1"><xsl:value-of select="Person" /></p>   
  </xsl:template>  

 </xsl:stylesheet>  


I used the Muenchian Method to group the the items by Person. So I could remove distinct values. With the sort within the foreach loop I could also sort the selected items.

The cool part was that the elements were now distinct, ordered and selectable. The bad part was, that I connected the form with another webpart and when I selected an element in the form, wrong items were shown in the connected webpart. Without grouping and ordering there were no problems.

Then I look a little into the javascript of the MatchPoint framework and realized that the Composite WebPart creates an array rowIds with the Item IDs of the returned XML. When you change the order of the items in the XML the items and the associated ids in the array are not properly connected anymore.

Row IDs

Another problem is also the Selectable attribute. The Composite is not designed to create a dropdown in your form. Therefore you've other fields. The problem is that the Selectable attribute has no effect on option fields in html. (in Internet Explorer)

 <xsl:stylesheet version="1.0" .. >  
  <xsl:key name="Persons" match="//Row" use="Person" />  
  <xsl:template match="/">  
   <select>     
    <xsl:for-each select="//Row[generate-id(.) = generate-id(key('Persons', Person))]">  
     <xsl:sort select="Person"/>  
     <xsl:apply-templates select="." />  
    </xsl:for-each>  
   </select>  
  </xsl:template>  
  <xsl:template match="Row">    
   <option Selectable="1"><xsl:value-of select="Person" /></option>   
  </xsl:template>  
 </xsl:stylesheet>  


Internally the MatchPoint Framework searches all html elements with the Selectable attribute and adds them a click event. A click event on option element has unfortunately no effects.

I was so close to solve this problem, but none of the above methods could be use

4 Attempt - JavaScript Hacking

This is always the last way I help me out. The theory is to use the underlying MatchPoint Framework and add some more features in the way that I don't change the default behaviour. The decorator pattern is here answer.

My idea was to add a "Changeable" attribute to mark a select box as an item, which has elements connected to other webparts. So I extended the MP.Composite function with my abilities:


 var OriginalMPComposite = MP.CompositeWebPart;  
 MP.CompositeWebPart = function() {  
   OriginalMPComposite.apply(this, arguments);

   this.Setup = function() {       
     this.BindEventHandlers();  
     this.BindChangeEvent();  
   }

   this.BindChangeEvent = function() {  
     this.$container = $(this.Control).find('#RowContainer');  
     if (this.EnableSelection)  
     {  
       var me = this;  
       this.$container.find("[Changeable]").each(function()  
       {   
         var $row = $(this);            
         var rowId = this.options[0].value;    
         $row.change($$.Delegate.Create(me, me.Row_Change));            
       });  
     }  
   }

   this.Row_Change = function(e) {  
     var $row = $(e.currentTarget);  
     var rowId = $row.val();  
     if (this.SelectedRowId == rowId)  
     {  
       this.SelectedRowId = null;  
     }  
     else  
     {  
       this.SelectedRowId = rowId;  
     }  
     var me = this;  
     this.Callback.SelectionChanged(function() { MP.ConnectionManager.NotifyConsumers(me, "SelectedRow"); });   
     RefreshCommandUI();  
   }    
 }  

In this JavaScript I call the original function an extend the main function with the BindChangeEvent and Row_Change methods.

You can add this JavaScript somewhere in the site or directy into the XsltTransformer. Now you have only mark the select with the Changeable attribute and add the Item IDs into the value of the options.

Now add a Form WebPart to your page and select the Composite Field. Choose a provider for your field. Don't forget to check the "EnableSelection" Checkbox on your field.


Now add a XlsTransformer for your field and add two mappings.



Add the following XSLT to your transformer. Change the element names if you used other mapping names.

 <xsl:key name="Persons" match="//Row" use="Person" />  
  <xsl:template match="/">  
   <select Changeable="1">  
    <option value=""></option>  
    <xsl:for-each select="//Row[generate-id(.) = generate-id(key('Persons', Person))]">  
     <xsl:sort select="Person"/>  
     <xsl:apply-templates select="." />  
   </xsl:for-each>  
   </select>  
   <script type="text/javascript">  
    var OriginalMPComposite = MP.CompositeWebPart;  
    MP.CompositeWebPart = function() {  
   OriginalMPComposite.apply(this, arguments)  
   this.Setup = function() {       
      this.BindEventHandlers();  
      this.BindChangeEvent();  
     }  
   this.BindChangeEvent = function() {  
      this.$container = $(this.Control).find('#RowContainer');  
      if (this.EnableSelection)  
      {  
       var me = this;  
       this.$container.find("[Changeable]")  
         .each(function()  
         {                    
           var $row = $(this);            
           var rowId = this.options[0].value;    
           $row.change($$.Delegate.Create(me, me.Row_Change));            
         });  
      }  
     }  
   this.Row_Change = function(e)  
   {  
    var $row = $(e.currentTarget);  
    var rowId = $row.val();  
    if (this.SelectedRowId == rowId)  
    {  
     this.SelectedRowId = null;  
    }  
    else  
    {  
     this.SelectedRowId = rowId;  
    }  
    var me = this;  
    this.Callback.SelectionChanged(function() { MP.ConnectionManager.NotifyConsumers(me, "SelectedRow"); });   
    RefreshCommandUI();  
   }    
 }  
 </script>    
  </xsl:template>  
  <xsl:template match="Row">    
   <option value="{ItemID}"><xsl:value-of select="Person" /></option>   
  </xsl:template>  


Now you have an ordered dropdown with distinct items which can be selected and connected to other webparts. You can also have other text in the option elements or sort in the order and with the field you want.

Hint: 
Because I've a user field  I used the following Expression in connected WebParts in conjunction with the contains operator:

ConnectionData.fldAssignedTo.SelectedRow.ListItem.AssigendTo.ToString().Split({"#"})[1]





Wednesday, July 11, 2012

Localization with SharePoint 2010 and Visual Studio 2010


A good resource management is essential when you working with SharePoint. Most custom solutions are not really localized although it's not so difficult to implement. In this post I'll show you which resource types exists while developing SharePoint solutions.

Resource Types

When you develop SharePoint solutions you'll be creating different types of solution items. This includes:
  • Features
  • WebParts
  • ASPX Pages
  • Custom Code
Each of these three items can output something to the user, so each of them must have localization capabilities.

Resource Locations

You have multiple locations where you can position your resource files. Each location is used by another item and has other code methods.
  1. \14\Template\Features\<Feature Name>\Resources\
  2. \14\Resources\
  3. \14\Config\Resources\
  4. <Virtual Directory>\App_GlobalResources\
In this post you'll understand in which cases which folder is used. Only Nr. 3 (\14\Config\Resources) folder is not used in any of the following examples, because in real life I never use this folder either.

The meaning of the \14\Config\Resources folder is that all resource files within the folder are copied to the <Virtual Directory>\App_GlobalResources\ in following cases

  • You use the stsadm command : stsadm -copyappbincontent. You have to execute this command on all frontend servers
  • When provisioning a new webapplication also all resources files are copied from this folder
Until now I never used this folder. Perhaps you'll find a use case herefore.



Part 1: Localization of Features

Method 1

With Visual Studio 2010 it is more easier to localize features, Click on the feature you want to localize and select "Add Resource Feature".

Add new Feature Resource

Select the invariant culture, A new Resource.resx file is created within your feature folder.

The new Resource in your Feature folder

Open the Resource.rex file and create two entries with keys "FeatureTitle" and "FeatureDesc". Next doubleclick on the Feature Folder to open the Design view.

Modify Feature Settings

Click on Manifest at the bottom. On the next screen expand the "Edit Options". Edit here the title and description properties of the feature by entering a new title and description with reference to the resource file.


That's it. If you want to add a new culture you can't copy the resx file and paste it again. You have to do the first step again by selecting the new culture in the upcoming dropdown box.

Method 2

Another method how you can localize a feature or a webpart is to deploy the resource file into the Resources folder under the 14hive. Right click on your project name and select "Add" >> "SharePoint Mapped Folder".
Add new mapped folder

Select the "Resources" folder in the next dialog and click O.K.

Now add a new resource file to this folder by right-clicking on the folder name and selecting "Add" >> "new Item". Select Resource File in the next dialog and name it Features.resx.

You can tell now your feature to take this resource file as default resource file. To do this double-click on your feature to open its property panel. There you'll find the attribute "Default Resource File"

Set Default Resource File

Part 2: Localization of WebParts

The localization of WebParts is very easy when you've understood the localization of features, To localize a webpart follow the steps of the 2. method from the feature localization. This means
  1. Add a new SharePoint Mapped Folder to the project
  2. Select the "Resources" folder
  3. Add a new resource into this folder
Now you can adress the resource within the webpart settings. Assume we have a resource file called "Features.resx" in the mapped folder with keys WebPartTitle and WebPartDesc , then you can modify your webpart like that:

<?xml version="1.0" encoding="utf-8"?> 
<webParts> 
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3"> 
    <metaData> 
      <type name="Gollum.WebPart, $SharePoint.Project.AssemblyFullName$" /> 
      <importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage> 
    </metaData> 
    <data> 
      <properties> 
        <property name="Title" type="string">$Resources:Features,WebPartTitle</property> 
        <property name="Description" type="string">$Resources:Features,WebPartDesc</property>
      </properties> 
    </data> 
  </webPart> 
</webParts> 

If you want to know how to localize the webpart code, then look at step 4.


Part 3: Localization for ASPX Pages

You often develop custom pages for displaying or managing information. Usually you deploy them to the /_layouts/ folder. This virtual path is mapped to the \14\TEMPLATE\LAYOUTS folder in the server.

When you open an aspx file in this folder you will see that the expression builder "Resources" is mostly used to localize a page.

<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,common_nofieldempty_TEXT%> .. />

This is a common way to seperate code and UI. Within the aspx page you can say which resource should be used by using the expression builder syntax. The markup for using the "Resource" expression is:

<%Resources:*Name of the resource file*, *Key in the resource file*%>

When you have a resource file like Gollum.resx and a key "SayHello" with value "Say hello to the world" then

<%Resources:Gollum,SayHello%> 

would output "Say hello to the world".

Hint: Expressions can only be used within ASP Controls. You cannot place them anywhere in the HTML Code like:
<table>
  <td> <a href="www.xyz.com"><%$Resource:Gollum, Link1%></a> ..

In this case you can use the ASP:HyperLink or ASP:Literal Control.

<table>
  <td> <asp:HyperLink runat="server" NavigateUrl="www.xyz.com" Title="%$Resource:Gollum, Link1%" /> ..


Where I have to put the resx file ?

Assume we have a resx file named Gollum.resx. To be able to use this file with the expression syntax it must be located in the "App_GlobalResources" folder of the current web application in the IIS webserver.

Usually the folder sits in a path something like that:
  • C:\inetpub\wwwroot\wss\VirtualDirectories\80\App_GlobalResources
  • C:\inetpub\wwwroot\wss\VirtualDirectories\sharepoint\App_GlobalResources
  • C:\inetpub\wwwroot\wss\VirtualDirectories\intranet\App_GlobalResources

A resource file within the App_GlobalResources folder is often called a Global Resource, because all pages within a web application can used it.

How do I deploy resource files to App_GlobalResources folder ?

The type of your Visual Studio project must be a "SharePoint Project". Now you click right on your project name and choose "Add" >> "New Item"

Adding a new item

In the next dialog select the "Empty Element" and name it e.g. "GlobalResources"

Adding a new empty module

Now click right on the new "GlobalResources" folder (Module) and add a new resource file. 

Add new item into the new folder
Name it like you want.

Add new resource file

Doubleclick the resx file and open it. Enter some keys and values. Save it afterwards.

Add some resource
In the next step click on the Gollum.resx file to display the properties. Change the "Deployment Type" attribute to "AppGlobalResource". This will force the solution package to deploy the file to the IIS folder App_GlobalResources.
Change Deployment Type


When you select the value "AppGlobalResources" the "Deployment Location" attribute gets activated, where you can select the folder to which the resx file should be deployed. You can select here any folder or leave the standard entry. This has no effect to the expression syntax.

Change Deployment Location
Now add some resources to our page.


We are already done. Deploy the solution and check if everything works fine. When you open the page now error messages hould be shown. Also the resx file should reside within the App_Globalresources folder.

Location of the resx file
Now you can start to localize your page. Make a copy of the Gollum.resx file and add it to the GlobalResource Module folder. Rename it for your desired language:
  • Gollum.tr-TR.resx (example for Turkish)
  • Gollum.en-US.resx (example for American english).
  • Gollum.de-DE.resx (example for German)

Part 4: Reading resource by code


Reading Global Resources by code

To read a global resource (resource files in App_GlobalResources) folder by code use the following syntax:

  • HttpContext.GetGlobalResourceObject("Gollum","Page_Title")
  • HttpContext.GetGlobalResourceObject("Gollum","Page_Title",CultureInfo.CurrentUICulture)

Reading Resources in 14Hive/Resource folder

To read resources within the Resource folder for example in webparts, you can use the helper method

uint lang = SPContext.Current.Web != null ? SPContext.Current.Web.Language : 1033;
lblHeaderTitle.Text = SPUtility.GetLocalizedString("$Resources:Features", "WebPartTitle",lang)


Final Words

Before you start creating a SharePoint Project you should think about of which items your project will consist and which type of resource handling is appropriate.

As an example you have a project with multiple pages and features. But one of these pages is used in the Central Administration site and the other on you normal SharePoint web application.

To use Global Resources you had to deploy your solution to all webapplications, because the Central Admin has its own web application with its own App_GlobalResource folder. Perhaps it would make more sense to deploy it into the 14Hive/Resource folder and use code to localize in this case to prevent multiple deployments.



Wednesday, June 20, 2012

Colorful SharePoint Calender

Never knew that you can combine multiple calenders in SharePoint 2010 to a single calender view. The function is called overlaying. Moreover you can define a color for each view, all OOB. I found this great post how you can colorize your SharePoint calender:

One drawback, which happened to me after overlaying was that the overlayed calender items could not be opened in the new SP 2010 Dialog style but only as new browser window.



Tip: If you have embeded the calender as webpart somewhere in your site, you have to actualize the view in the WebPart settings to view the colored calender.

Friday, June 8, 2012

Replace row numbers from copied code

This has nothing to do with SharePoint but I wanted to share the trick. When you copy code from some pages sometimes you have to copy the ugly row numbers, like

1: function XYZ
2:{
3:     var a = b;

You can delete these numbers easily with NotePad++ or another higher TextEditor. I use NotePad++ so I'll show you have I do that with NP.


  1. Copy the code into the editor
  2. Open the "Replace" dialog
  3. Check "Regular Expression"
  4. Enter the regular expression in "Find What" = \d*: (This can be any other regex)
  5. Enter nothing into "Replace String"




Thursday, June 7, 2012

Check Ports for Extranet SharePoint Farms with PowerShell

When you create a SharePoint Farm within an extranet you usually have to check ports to other servers within other security layers behind firewalls. Here is a simple script for a quick check. Copy this script to every SharePoint Server and modify the ip addresses. Run the script on the server to see if a port is blocked.


<#
These values can be modified
Enter the IPS of the server
Ports from http://technet.microsoft.com/en-us/library/cc262849.aspx
#>
$SERVER_APP = "xxx.xxx.xxx.xxx"
$SERVER_WEBAPPS = @("xxx.xxx.xxx.xxx", "xxx.xxx.xxx.xxx")
$SERVER_DB = "xxx.xxx.xxx.xxx"
$SERVER_AD = "xxx.xxx.xxx.xxx"
$SERVER_DNS = "xxx.xxx.xxx.xxx"
$SERVER_SMTP = "xxx.xxx.xxx.xxx"
$CLIENT = "xxx.xxx.xxx.xxx" #IP of a client which should access SharePoint

$USE_KERBEROS = $false
$USE_NETBIOS = $false
$USE_SMTP = $true

# bi = bidirectional
# out = outbound
$CONNECTIONS = @(
  #SQL
  ( "out", $SERVER_APP, $SERVER_DB, "1435", "SQL" ),
  ( "out", $SERVER_WEBAPPS, $SERVER_DB, "1435", "SQL" ),  
  
  #Service Applications
  ( "bi", $SERVER_WEBAPPS[0], $SERVER_WEBAPPS[1], "32843,32844", "Service Applications" )  
  
  #HTTP
  ( "bi", $CLIENT, $SERVER_WEBAPPS, "80,443", "HTTP, HTTPS" ),      
  ( "bi", $SERVER_WEBAPPS, $SERVER_APP, "80,443", "HTTP, HTTPS" ),    

  #SMB
  ( "bi", $SERVER_WEBAPPS, $SERVER_APP, "445", "SMB" ),      
  
  #LDAP
  ( "out", $SERVER_APP, $SERVER_AD, "389, 636" , "LDAP, LDAPS"),
  
  #DNS
  ( "out", $SERVER_APP, $SERVER_DNS, "53", "DNS" )
)

#SMTP ?
if ($USE_SMTP -eq $true) {  
  $CONNECTIONS += ,( "bi", $SERVER_WEBAPPS, $SERVER_SMTP, "25", "SMTP" )
}

#KERBEROS ?
if ($USE_KERBEROS -eq $true) {  
  $CONNECTIONS += ,@( "bi", $SERVER_WEBAPPS, $SERVER_APP, "88,464", "Kerberos")
}

#NETBIOS ?
if ($USE_NETBIOS -eq $true) {  
  $CONNECTIONS += ,@( "bi", $SERVER_WEBAPPS, $SERVER_APP, "137,138,139", "NetBios")
}


<#
---------------------------
Do not touch these ones
---------------------------
#>
$LOCAL_IP = (Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0]

Function PingPort {
  $ip = $args[0] 
  $port = [int]$args[1]
  
  $ErrorActionPreference = "SilentlyContinue"
  $socket = new-object System.Net.Sockets.TcpClient($ip, $port)
  if ($socket –eq $null) {    
    $false
  } else {
    $socket = $null
    $true
  }
}

foreach ($conn in $CONNECTIONS) {  
  if ($conn[0] -eq "bi") {    
    $CONNECTIONS += ,@( "out", $conn[2], $conn[1], $conn[3], $conn[4] )
  }
}

foreach ($conn in $CONNECTIONS) {  
  if ( $conn[1] -is [System.Array] ) { $servers1 = $conn[1] }else{ $servers1 = @($conn[1]) }
  if ( $conn[2] -is [System.Array] ) { $servers2 = $conn[2] }else{ $servers2 = @($conn[2]) }    
  $ports = $conn[3] -split ","  
  $desc = $conn[4]
  
  foreach( $port in $ports) {
    foreach( $server1 in $servers1) {    
      foreach( $server2 in $servers2) {            
        if ($LOCAL_IP -eq $server1) {
          Write-Host "`nTesting Connection:"
          Write-Host $server1 " -> " $server2 " -> Port:" $port " [" $desc "]" -foregroundcolor yellow
          $pinged = PingPort $server2 $port
          if ( $pinged -eq $true ){
            Write-Host "Connection O.K." -foregroundcolor green
          }else{
            Write-Host "Port closed." -foregroundcolor red
          }
        }
      }
    }
  }
}   



Thursday, May 24, 2012

Synch User Fields when copying lists from one sitecollection to another

If you copy a list from one sitecollection to another one with a list template and the copied list has user fields than you'll have other users in the destionation list.

This occures because SharePoint saves all user in a own UserInfo table for each sitecollection. A user in SiteCollection 1 can have the ID 5 and in another the ID 3.

As the SPUserField is a LookUp Field to the User Info List the name of the User in the destination list can change.

Two little PowerShell Scripts can help here. The first one reads the user info for a field in the source list and creates a csv with the information. The second one reads the csv and updates the destination list. Copy the code to two .ps1 files.

Open the SharePoint 2010 Management Shell and run the "Dump user Info" script to dump the infos to a .csv file. Afterwards run the second script to update the destination list.


Dump User Info

 # Adapt these values  
 $web = Get-SPWeb "http://sharepoint/sites/sourcelist/"   
 $list = $web.Lists["SourceList"]  
 $field = "UserField"  
 $csv = "C:\temp\user.csv"  
 # ---------------------  
 $userInfos = @()  
 foreach($item in $list.Items) {  
   $userFld = [Microsoft.SharePoint.SPFieldUser] $item.Fields.GetField($field)    
   if ($null -ne $userFld) {          
     if ($null -ne $item[$field]) {  
       $fieldVal = new-object Microsoft.SharePoint.SPFieldUserValue($web, $item[$field].ToString())      
       if ($null -ne $fieldVal){  
         $user = $fieldVal.User        
         $userInfos += New-Object PSObject -Property @{  
           Title = $item.Title  
           Login = $user.LoginName  
         }  
       }  
     }  
   }else{  
     Write-Host "Field: " $field " not found in list!" -foregroundcolor red  
   }  
 }  
 $userinfos | Export-CSV $csv -NoTypeInformation  
 Write-Host "END - Script"  

UpdateUser Info

 $web = Get-SPWeb "http://sharepoint/sites/io/"   
 $list = $web.Lists["Services"]  
 $field = "Verantwortlich_x0020__x0028_ad_x"  
 $items = $list.Items  
 $csv = "C:\temp\user.csv "  
 $log = "C:\temp\usersynch.log"  
 Start-Transcript -path $log   
 $import = Import-Csv $csv  
 foreach ($obj in $import)  
 {   
   Write-Host "Searching for " $obj.Title -foregroundcolor blue  
   $items | Where-Object { $_.Title -eq $obj.Title} | foreach {  
     Write-Host " >> Updating item to new user: " $obj.Login -foregroundcolor green  
     try  
     {  
       $_[$field] = $web.EnsureUser($obj.Login).ID  
       $_.Update()      
     } catch [Microsoft.SharePoint.SPException] {  
       Write-Host " >> Error: could not find user: " $obj.Login -foregroundcolor red            
     }   
   }  
   Write-Host ""  
 }  
 Write-Host "End Synch Process"  
 Stop-Transcript  

Tuesday, May 8, 2012

Copy SharePoint Lists between sites with different language templates

An easy way to copy a list with content between to sites is to create a list template, upload it in the other site to the list template gallery and create a new list with the uploaded template.

The problem starts when the site templates have different languages. For example you create a list template in a site with english site template and you upload it to a site with a german site template, you'll not see the template in the create page.

A way to change this ist to modify the.stp file, which is created when you make the list template. SO the first steps are:
  1. Create a list template. You can do this in the list settings. Just click "Save list as template" and give in the the filename and the template name.
  2. Download the created template to a local folder.

Extract the template file

Now we will modify the .stp file. An .stp file is nothing more than a microsoft cabinet file. Therefore rename the file from "mylist.stp" to "mylist.cab". You can now open the file and see all the compressed files inside.

The next step is to make a local folder e.g "C:\temp\mylist" where we'll copy all files within the cab file. So open  the cap file and drag all files inside to the mylist folder. As it's a cab file you can't copy the files, you have the drag them with the mouse.

Afterwards open the manifest.xml in the mylists folder and change the language code within the xml to the destination language code. (See Language Codes )

<Language>1031</Language>

Create a new template

In this step we create the stp again. Unfortunately it is not so easy as it seems to be. First of all we have to create a .ddf file. Create a new file in the mylist folder and rename it to "definition.ddf".

Open the .ddf file and paste the fallowing code inside:

; DIAMOND Directive File (.ddf)
.OPTION EXPLICIT
; Generate errors on variable typos
.Set CabinetNameTemplate=mylist.stp
.Set Cabinet=on
.Set Compress=on

;The files specified below are stored, compressed, in the cabinet file
30000000.000
manifest.xml

In the last section write down all the files which should be within the new .stp file. These should be all except the .ddf file.

You can also change the CabinetTemplateName. This is the name of the file, but this is not important for us.

Create cabinet

Open the DOS command promp and go to the mylists folder. Type the following command:

makecab /f definition.ddf



The result should end in a mylist.stp file within a subfolder. You can now go and upload the new .stp file to the list gallery of your destination site.

The list template should be finally visible in the list create dialog.

Monday, May 7, 2012

Item-Level Permission for a SharePoint Document Library

The SharePoint Document Library doesn't have the option in his UI-Settings to change the item-level permisssions like normal SharePoint lists.

This means that you can't create a document library in which only user who created the documents can only see their documents and no one else.

You can simulate the behaviour by changing the permissions on each single document but this could be expensive and could also cause performance issues on  large libraries.

The better option is the activate the item level security by PowerShell. It seems that Microsoft  only disabled the UI Option but with PowerShell you can reactivate it. I don't know why Microsoft disabled this option but in my tests this method worked very well.

 $web = Get-SPWeb http://YourSite/  
 $list = $web.Lists["Your Document Library Name"]  
 $list.ReadSecurity = 2  
 $list.Update()  
 $web.Dispose()  

What the values for ReadSecurity property means can be seen here.

There is also a property for the writesecurity, that you can use to enable users to edit only their own items.

Sources