“This file cannot be saved because some properties are missing or invalid”

Error


This file cannot be saved because some properties are missing or invalid.
Use the Document Information Panel to provide the correct property values. Errors for required properties are marked with a red asterisk, and errors for invalid properties are marked with a red dashed border.

Only specific pattern allowed. Only data in the following pattern is allowed: ‘.*\S.*’

Case

There is a document library in SharePoint 2010 site. Users are using Office 2010 32-bit.Saving an open document from client computers doesn’t work for all the client PCs. On some PCs saving the document fails and this error is shown: “This file cannot be saved because some properties are missing or invalid. Use the Document Information Panel to provide the correct property values. Errors for required properties are marked with a red asterisk, and errors for invalid properties are marked with a red dashed border”.
In the Document Information Panel there are no properties marked with red and everything looks fine.

Investigation

There are two cases I discovered that experience this problem and both are caused from the same bug in Office.

Case 1:

Labels are enabled for the content type in the document library and the label specified contains line breaks, for example {Field1}\n{Field2}.
The error occurs even without inserting the label in the document’s content. After removing the line breaks from the label the documents can be saved.

Case 2:

There were some hidden columns in the document library’s content type. I made them Optional and opened a document from a PC that is having the problem. In the Document Information Panel there was one property marked with a red dashed border.

This file cannot be saved because some properties are missing or invalid.

Right clicking and selecting “Full error description…” popped-up the following message: “Only specific pattern allowed. Only data in the following pattern is allowed: ‘.*\S.*’”.

“Only specific pattern allowed.

It turns out that the validation is not allowing line breaks in the property. After removing the line breaks I was able to save the document.

The question now is why the problem was present only on some of the PCs. Checking the Office build version discovered that the problem was present only on client PCs with Office 2010 14.0.7106.5003. Client PCs with Office 2010 without service packs (14.0.476.1000) are not having this problem. This is a bug introduced with Office 2010 updates. Microsoft is aware of this bug and they are working on it and will be fixed with the next Office 2010 updates. It’s been few months since then (October 2013) and the issue is still present. Hopefully they will release the hotfix soon.

Solution

(Bug fixed. Check UPDATE 24/03/2014 at the end of this post.)

Because this is a bug in Office 2010, as a temporary “solution” I uninstalled Office 2010 14.0.7106.5003 and installed a clean Office 2010 14.0.476.1000 without service packs. I’ll stick with 14.0.476.1000 until Microsoft fixes this bug. Hopefully it won’t take too long until they release the hotfix.

UPDATE 22/01/2014:
To find out which update is the problem I performed the following:

-Installed clean Office 2010 (32 bit) 14.0.476.100 – The issue is not present

-Then Installed Service Pack 2 14.0.7015.1000 –The issue is not present

-Then installed the updates after SP2 one by one until the problem started.

Discovered that KB2826026 (08/10/2013) is the update that has introduced this bug.

-To make sure that this is the only problematic update I uninstalled KB2826026 and installed the other latest updates (except KB2826026) and the issue is not present.

Microsoft hasn’t released the fix for this bug yet. Uninstalling KB2826026 solves the problem temporarily until the hotfix is released.

UPDATE 25/02/2014:
“The KB2826026 has been replaced by the KB2837583 (released 11/02/2014) but Microsoft did not remove the bug brought by the first KB. So you have to uninstall the KB2837583 otherwise you will have this bug again …” – visitor’s comment (Nicolas).

In my case uninstalling KB2826026 (now KB2837583) fixed the problem. If in your case that doesn’t solve the problem then try by uninstalling other updates released after September 2013. Check the comments below.

UPDATE 12/03/2014:

KB2837583 (released 11/02/2014) is now replaced with KB2878225(release 11/03/2014) but the bug is still not fixed. So this update (KB2878225) must be uninstalled too.

UPDATE 24/03/2014:

Good news! The bug is fixed with hotfix KB2878228. You have to download and install it manually because its not included with the automatic updates.

How to catch ‘Access Denied’ exception with try-catch in SharePoint

Problem:

Can’t catch ‘Access Denied” exception in try-catch block.

Case:

When the code tries to access an object that it has no permissions to access ‘access denied’ exception is thrown and by default it is handled by SharePoint by redirecting to ‘Access Denied’ page. That page doesn’t show much information for troubleshooting.
If you put the code that causes ‘access denied’ exception in try-catch block you will not be able to catch that exception because SharePoint has already handled it.

Solution:

To change this default behavior SPSecurity.CatchAccessDeniedException should be set to FALSE before your try-catch block.

bool originalState = SPSecurity.CatchAccessDeniedException;
SPSecurity.CatchAccessDeniedException = false;
try
{
	//your code that throws Access Denied exception
}
catch
{
	//get more info about the exception 
}
finally
{
	SPSecurity.CatchAccessDeniedException = originalState;
}

Change the URL of a SharePoint list or library

Below are listed three different ways to change the URL of an existing list or library.

1. With PowerShell

Changing the URL is possible by moving the root folder to a new URL. Here is a PowerShell script to change the URL of an existing library:

Add-PSSnapin Microsoft.SharePoint.PowerShell –erroraction SilentlyContinue

$libOriginalUrl = "/Lists/YourLibName1";
$libNewUrl = "/YourLibName2";
$web = Get-SPWeb -Identity http://....

$lib = $web.GetList($web.Url + $libOriginalUrl)
$rootFolder = $lib.RootFolder;
$rootFolder.MoveTo($web.Url + $libNewUrl)

2. With SharePoint Designer

Open the site in SharePoint Designer and in All Files in Site Objects right click the list and click “Rename” to rename it or drag and drop to move it to another folder. For example a list can be moved outside “/Lists” and the Url will change from “webUrl/Lists/List1” to “webUrl/List1”.
All Files in SharePoint Designer

3. With Windows Explorer

Its the same thing as with SharePoint Designer done in Windows Explorer without opening SharePoint Designer. To get to all files and folders of the site open a document library with Windows Explorer by clicking “Open in Explorer” in the Ribbon. Then in Windows Explorer move up in the folders hierarchy to show all the files and folders in the root of the site. You can rename a list/library and you can drag and drop the list’s root folder to add/remove parts of the Url as in the example with SharePoint Designer above. Because standard lists don’t have the option in the Ribbon “Open in Explorer” you can use any document library just to get to the folders of the site as explained in the previous paragraph and then find your list and rename/move it.
Site files in Explorer

Note:

Hyperlinks in Quick Launch will continue to point to the old URL of the list so after changing the URL you need to update the quick launch to reflect the new list name and URL.

Multi lookup fields in CAML queries. <Eq> vs <Contains>.

Correct query with <Eq>:

	<Where><Eq><FieldRef Name=\"MultiLookupFieldName\" LookupId=\"TRUE\"/><Value Type=\"LookupMulti\">" + id + "</Value></Eq></Where>

Problematic query with <Contains>:

"<Where><Contains><FieldRef Name=\"MultiLookupFieldName\" LookupId=\"TRUE\"/><Value Type=\"LookupMulti\">" + id + "</Value></Contains></Where>"

Below is a short explanation and comparison between the two queries above.

When using CAML query for querying a list with multi-lookup field it may seem logical to use <CONTAINS> to get all the items that contain a specific value in the multi lookup field.
That query would be:

"<Where><Contains><FieldRef Name=\"MultiLookupFieldName\" LookupId=\"TRUE\"/><Value Type=\"LookupMulti\">" + id + "</Value></Contains></Where>"

The problem with this query is that it will return wrong results as explained below with one example.
For demonstration purposes let’s say we have two lists, List1 and List2, and List1 has a multi lookup column that points to List2.

These two lists contain the following items:

Items in List1:
  • Item1:
        Title: Item1
        MultiLookupField: i1
  • Item2:
        Title: Item2
        MultiLookupField: i1,i21
  • Item3:
        Title: Item3
        MultiLookupField: i2,i21
    • Items in List2:
      • Item1:
            Title: i1
            ID: 1
      • Item2:
            Title i2
            ID: 2
      • Item3:
            Title: i21
            ID: 21

      A CAML query with <CONTAINS> to get all items in List1 that have i1 in their multi lookup field would look like below:

      	<Where><Contains><FieldRef Name=\"MultiLookupFieldName\" LookupId=\"TRUE\"/><Value Type=\"LookupMulti\">" + 1 + "</Value></Contains></Where>
      

      This will return Item1, Item2 and Item3. It should only return Item1 and Item2 but because Item3 has i21 which has ID=21 it is included because 21 contains “1”.

      To avoid this problem <EQ> should be used instead of <CONTAINS>.

      	<Where><Eq><FieldRef Name=\"MultiLookupFieldName\" LookupId=\"TRUE\"/><Value Type=\"LookupMulti\">" + id + "</Value></Eq></Where>
      

      By reading the query you may think that it will return only the items that have ONLY one value in the multi lookup field equal to value specified in the query but the true behaviour is that it will return all the items that CONTAIN the specified item even if they have other items selected in the multi lookup field.
      For the lists in the example above if for example the query is as below

      	<Where><Eq><FieldRef Name=\"MultiLookupFieldName\" LookupId=\"TRUE\"/><Value Type=\"LookupMulti\">1</Value></Eq></Where>
      

      it will return not just Item1 that has only i1 in the multi lookup field but it will also return Item2 because it contains i1 even although i1 is not the only item Item2 contains in the multi lookup field (it also contains i21).

      Conclusion:

      Use <EQ> instead of <CONTAINS> when writing a query that checks a multi lookup field.
      Be aware that when checking a multi lookup field by Id (with LookupId=TRUE) a query with <EQ> returns results that you would normally expect from a query with <CONTAINS> while a query with <CONTAINS> returns wrong results and is not very usable in this case.

      Code example with <EQ>:

      	string query = "<Where><Eq><FieldRef Name=\"MultiLookupFieldName\" LookupId=\"TRUE\"/><Value Type=\"LookupMulti\">" + id + "</Value></Eq></Where>";
      	SPQuery spQuery = new SPQuery();
      	spQuery.Query = query;
      	SPListItemCollection items = list.GetItems(spQuery);
      


Workflow progress column missing in a document library

Problem:

Workflow progress column is missing even though workflow instances have been started for many documents.

Case:

There is a document library with a workflow associated with its content type. Even though there have been workflow instaces started there is no workflow progress column created for the list.
The missing column doesn’t affect the work of the workflow itself. The workflow can be started, completed and it is working without problems, only the progress column is not available.

Elaboration:

Workflow progress column should be created when the first workflow instance is stared on a document. It turns out that if for some reason has failed to be created when the first workflow instance is started then it will not be created with next new instances of the workflow even if the problem that caused the failure the first time is fixed.

In my case the workflow progress column is not created because of the lookup threshold. Workflow progress column is lookup column. If the default view has more lookup columns than the lookup threshold (by default 8) then the creation of the workflow progress column fails.

By removing lookup columns form the view or increasing the lookup threshold in Central Administration then start another workflow instance the problem was not fixed. I had to remove the original workflow and add another identical workflow to the list. When starting the first instance of the new workflow the workflow progress column was created successfully.

Solution:

To fix the problem of missing workflow status column follow the steps below:

  1. Remove lookup columns for the list’s default view so its number is below the lookup threshold for that web application, or increase the lookup threshold for that web application in Central Administration.
  2. Remove the workflow from the content type when there are no running instances
    Important: With this action workflow history is lost for this workflow
  3. Add new workflow to the content type. Start a workflow instance on a document so the workflow progress column is created.

Effects of temporarily disabling versioning for a SharePoint document library

Suppose we have a document library that has major and minor versioning enabled. We want to know what happens if we disable versioning and re-enable it again, and what is lost/retained from the items versions history.

Case:

There is a document library with major and minor versioning enabled, requires check out for editing document and there are workflows associated with the content types. We’ll observe what happens with the version history when the versioning is disabled and re-enabled. When the versioning is disabled the document library contains number of documents in minor and major versions, with version history, some document are in checked out state and there are document with workflows in process.

Test Results

After re-enabled the versioning the version history of the documents is preserved but any change done while the versioning was disabled has increased the major version of the document (document is published).

Example: Before disabling the versioning for the document library one document is in version 0,2 with version history 0.1, 0.2. After the versioning is disabled the item is modified. After re-enabling the versioning the change done while the versioning was disabled has increased the major version of the document (document published), so the versioning history now is 0.1, 0.2, 1.0 instead of 0.1, 0.2, 0.3 how would have been if the versioning was not disabled when the change has happened.

Every document that was not modified while the versioning was disabled has the full version history back and unaffected from the disable-enable versioning process.

Conclusion

If minor versions are enabled for a document library then temporarily disabling versioning will affect the version history if changes are made while the versioning is disabled. The problem is that every change in that period will publish the document to the next major version.

Running code on SharePoint server using application page

When we need to work with SharePoint object model for maintenance reasons or simple tasks we need a way to execute code on a server machine. Example of such situations are: when we want to check the properties of an item, list or web that are not accessible using the UI, or looping through all the items in a list to set a particular field to a specific value etc.
In such cases there are different options to work with SharePoint object model on a server machine where there is no Visual Studio installed:

  1. PowerShell script
  2. Windows application. For example console or WinForms application developed on Development environment by referencing Microsoft.SharePoint.dll
  3. Application page with inline code

This post is a quick how-to guide on using application page to run inline C# code on a server.
This post is not a guide how to develop application pages for a SharePoint solution. In that case you would write the code in the page’s code behind and pack everything in a solution package.

Step 1

Create a file with extension .aspx (in this guide we will name it appPage1.aspx)

Step 2

Copy and paste the following content to appPage1.aspx based on the SharePoint version you need the page for.

For Microsoft Office SharePoint Server 2007 (MOSS 2007):

<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C" %>
<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.Utilities" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormSection" Src="~/_controltemplates/InputFormSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" Src="~/_controltemplates/InputFormControl.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" Src="~/_controltemplates/ButtonSection.ascx" %>
<%@ Register TagPrefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
        <%
            try
            {
                SPWeb web = SPContext.Current.Web;
		        Response.Write(web.Title);
                //your code here
            }                
            catch (Exception ex)
            {
                Response.Write("<br/><br/>" + ex.ToString());
            }

        %>
</asp:Content>

<asp:Content ID="PageTitle" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
</asp:Content>

<asp:Content ID="PageTitleInTitleArea" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server" >
</asp:Content>

For SharePoint 2010:

<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" AutoEventWireup="true" DynamicMasterPageFile="~masterurl/default.master" Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase" %>

<asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
        <%
            try
            {
                SPWeb web = SPContext.Current.Web;
		        Response.Write(web.Title);
                //your code here
            }                
            catch (Exception ex)
            {
                Response.Write("<br/><br/>" + ex.ToString());
            }

        %>
</asp:Content>

<asp:Content ID="PageTitle" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
</asp:Content>

<asp:Content ID="PageTitleInTitleArea" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server" >
</asp:Content>

For SharePoint 2013

<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%> 
<%@ Page Language="C#" MasterPageFile="minimal.master"  %> 
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %> 
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 
<%@ Import Namespace="Microsoft.SharePoint" %> 
<%@ Assembly Name="Microsoft.Web.CommandUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> 
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="wssuc" TagName="LinkSection" src="~/_controltemplates/15/LinkSection.ascx" %>

<asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
        <%
            try
           {
                SPWeb web = SPContext.Current.Web;
		        Response.Write(web.Title);
                //your code here
            }              
            catch (Exception ex)
            {
                Response.Write("<br/><br/>" + ex.ToString());
            }

        %>
</asp:Content>

<asp:Content ID="PageTitle" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
</asp:Content>

<asp:Content ID="PageTitleInTitleArea" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server" >
</asp:Content>

Step 3

Write your code in the space specified with “//your code here” in the aspx page.
If you need you can reference your assemblies in the page. The referenced assemblies must be available on the server where this page will be used.

Step 4

Copy the file to the LAYOUTS folder in SharePoint root directory.
The path to the layouts folder is:

For MOSS 2007:

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\Template\Layouts

For SharePoint 2010

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Template\Layouts

For SharePoint 2013

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\Template\Layouts

Step 5

Open the browser and navigate to appPage1.aspx.
The Url is:

For MOSS 2007 and SharePoint 2010:

yourSiteUrl/_layouts/appPage1.aspx

For SharePoint 2013:

yourSiteUrl/_layouts/15/appPage1.aspx

The code will run in the context of the web where you open the page and in the context of the logged in user. You can elevate the privilages of the logged in user with SPSecurity.RunWithElevatedPrivilages and instantiate new SPSite to run the code in the context of system account.

Content Approval implementation options in SharePoint

There are different options to implement item approval functionality for SharePoint list items. Below are listed some option starting from the simplest, out-of-the-box features to workflows developed in Visual Studio.

1. Set ‘Require content approval’ in list’s Versioning settings.

This is the simplest way to have content approval functionality in SharePoint and it doesn’t require workflow.
To enable Content Approval go to list’s Settings -> Versioning settings and set ‘Require content approval for submitted items?’ to Yes.
Enable versioning as per your requirements.
Workflow Settings

2. Approval Workflow.

This is achieved by using out-of-the-box workflow ‘Approval – SharePoint 2010’ workflow and involves task generation and options to Approve/Reject/Request changes/Reassign tasks)
To use this workflow follow the steps below:
1. Make sure the site collection feature ‘Workflows’ is activated
2. Go to List Settings -> Workflow Settings and click ‘Add’ to add new workflow. Choose Approval – SharePoint 2010, specify Name, choose Task List, History List and specify the Start Options.
Require Content Approval screen
Click Next and specify the other settings (Approvers, task duration, enable content approval etc.)
After adding the workflow it will be available for the list items and it will start automatically or can be started manually depending on the workflow settings.

3. Create custom workflow using SharePoint Designer

With SharePoint Designer can be created custom workflows with a relatively easy way and without coding. SharePoint Designer has improved noticeably with every major version and there is a lot of flexibility and has a quite long list of actions to use when creating workflows for different business processes including content approval processes.

4. Creating workflows using Visual Studio

This is the most advanced way of creating custom workflows. It has full flexibility to implement any functionality and has the advantage of building workflows for easy deployment in different environments.

Conclusion

There is no strict rule when to use which option. Its best to evaluate every option starting from the easiest one (option 1) until the most appropriate option is chosen considering the requirements and the available resources (time and people)

Site collection backup/restore vs. site export/import with focus on running workflows, version history and auditing

Custom SharePoint solutions usually contain custom lists with custom event receivers, custom workflows, maintain version history, use auditing etc. For a site with lists that have those customization it’s important to make sure that the backup/restore process will not lose any of the running workflows, workflow tasks and their status, items version history (major and minor versions), registered audit events etc.

Regarding audit events, in fact they are saved in Audit table in the content database and site backup/restore or export/import will not affect that table but can make other changes(for example new site GUID) which will result in invalid rows in that table (one of the columns is Site ID (GUID)).

Database attach is always the best way to avoid any problems of this kind but in some cases for various reasons very frequent backup of the content database is not preferred and backup of a single site collection (SPSite) or single site (SPWeb) is required. Another reason for testing site export/import is that I’ve seen administrators using export/import for migration to another location and as a more frequently running backup plan and I was asked is it safe to do that for custom solution.

Test case

I tested site collection backup/restore and site export/import in SharePoint 2010. In the site there was a document library with versioning (major and minor) enabled, auditing was enabled and there were custom workflows associated with the list. In the time of back up there were documents in minor and major version. Also some of the documents had workflows in running state. After the backup some versions from items versions history were deleted, running workflows were completed and workflows on other items were started. Also some of the documents were deleted and new documents created. After that the restore/import was performed and the restored/imported site was compared with the original state (state when the site was backed up/exported). Import of the exported site was tested by importing on the same location and also importing on another sub site.

Below are the commands for backup/restore and export/import and the results after restore/import. The focus is on running workflows, version history and registered auditing events.

Site collection backup

PowerShell command to backup a site collection

        Backup-SPSite -Identity "site collection url" -Path "backup file path"

More information for Backup-SPSite: http://technet.microsoft.com/en-us/library/ff607901.aspx

Site collection restore

PowerShell command to restore a site collection

	Restore-SPSite -Identity "site collection url" -Path "backup file path"

More information for Restore-SPSite: http://technet.microsoft.com/en-us/library/ff607788.aspx

Restore results:

The results are the same when restoring on the same location or another location, in the same web application or another web application.

Results:

  • The restored site collection has new site ID (GUID).
  • All items versions are restored successfully with the original correct datetime value for every version.
  • All items workflows are restored successfully
  • Audit events are lost. This happens because of the new site ID (GUID). This can be fixed by updating Audit table in the content database. If the site collection is restored in the same location or another location that uses the same content database then rows in Audit table should be updated by writing the new Site ID in the rows that have the old Site ID. If the restore is done on another location that uses another content database then new rows must be created in the Audit table in the new content database by reading the old audit events from the Audit table in the old content database.

Site Export

PowerShell command to export SPWeb:

	Export-SPWeb [-Identity] "site url or GUID" -Path "backup file path"

More information for Export-SPWeb: http://technet.microsoft.com/en-us/library/ff607895.aspx
Important: Export-SPWeb does not export the running workflows.

Site import

Powershell command to import SPWeb:

	Import-SPWeb [-Identity] "site url or GUID" -Path "backup file path"

More information for Import-SPWeb: http://technet.microsoft.com/en-us/library/ff607613.aspx

Import in the same location

If there are event receivers running custom code in the lists it’s recommended to remove all of them before import to avoid any failure caused by the event receiver logic. Depending on the logic of the event receivers import may completely fail or the imported web may have wrong items version history and other problems. After import event receivers can be added back. For removing/adding event receivers with PowerShell check one of my older posts Add, Modify or Delete List Event Receivers with PowerShell

Import results:

  • Import operation does not import the workflows (as expected, because they don’t get exported with Export-SPWeb).
  • Import does not affect the currently running workflows but import of the document versions fails for those documents with running workflow.
  • Versions are imported only for document with no workflows in running state when importing.
  • Versions date and time are lost. All versions have datetime value of the time when the import has been performed.
  • Audit events are not affected by the import, so all events are retained including those registered between export and import

Import in another sub site

The following must be done before importing the exported site:

  • Create new site using the same template and same language as the exported site
  • As explained above, removing event receiver is strongly recommended

Import results:

  • All running workflows are lost
  • All custom site properties and custom item properties are lost (properties in SPWeb.Properties and SPItem.Properties hashtables)
  • All workflow associations are lost
  • Document versions are imported but date and time for the versions are lost. All versions have datetime value of the time when the import has been performed.
  • Audit events are lost
  • Workflow tasks are lost

Conclusion:

Database attach of a backed up content database is superior for custom SharePoint solutions that contain custom workflows, custom web and item properties, custom event receivers, version history etc. To avoid big content database and long backup times its better the custom solutions to be installed in a site collection that has its own content database instead of sharing one content database with other site collections. That way backup/restore space and time will stay in normal and manageable boundaries. In that case you can stick to database backup as the best backup/restore plan for highly customized and big SharePoint solution.

Site collection backup/restore is better than SPWeb export/import but not as trouble free as database backup/restore plan. The problems are caused by the new Site ID (GUID) generated. That causes lose of audit events, or more precisely additional work to correct the Audit table after the restore.

Site export/import is almost useless for custom solutions that contain custom workflows, event receivers, custom web and item properties, custom event receivers, version history etc. It has problems even if it’s used for simple sites that use only SharePoint out of the box functionality if it has lists with workflows and versioning enabled.

Get Timer Job Status programmatically

The status of a running timer job can checked programmatically with SPRunningJob.Status. The status can be in one of the following states: Scheduled , Initialized , Succeeded, Failed, Retry, Aborted, Pausing, Paused.
If a job definition is not running in a given moment then we can’t get it with SPWebApplication.RunningJobs. In that case we have to get the timer job’s history entries and check with what status has the job ended when it’s been executed.

Below is the code to get the status of a timer job with a given name. If the job is not running then it checks the last timer job history entry. One minute schedule is assumed for this example. Modify it as per your time job schedule.

using Microsoft.SharePoint.Administration;

private SPRunningJobStatus CheckTimerJobsStatus(SPWebApplication webApp, String timerJobName)
{
	SPRunningJobStatus status;
	
	try
	{
		SPRunningJob runningJob = null;
		SPRunningJobCollection jobs = webApp.RunningJobs;
		foreach (SPRunningJob rJob in jobs)
		{
			if (runningJob.JobDefinitionTitle == timerJobName)
			{
				runningJob = rJob;
			}
		}

		if (runningJob != null)
		{
			status = runningJob.Status;
		}
		else
		{
			SPJobDefinitionCollection jobDefs = webApp.JobDefinitions;
			SPJobHistory lastJob = GetLastJobFromHistory(jobDefs, timerJobName);
			if (lastJob != null)
			{
				status = lastJob.Status;
			}
		}
	}
	catch (Exception ex)
	{
		//Hand the exception as fit, depending where you run this code
	}

    return status;
}
		
private SPJobHistory GetLastJobFromHistory(SPJobDefinitionCollection jobDefs, string jobName)
{
	SPJobHistory lastJob = null;
	foreach (SPJobDefinition jobDef in jobDefs)
	{
		if (jobDef.DisplayName == jobName)
		{
			IEnumerable<SPJobHistory> jobHistory = jobDef.HistoryEntries;

			foreach (SPJobHistory jobHistoryItem in jobHistory)
			{
				if (jobHistoryItem.StartTime - jobDef.LastRunTime < new TimeSpan(0, 1, 0))
				{
					lastJob = jobHistoryItem;
					break;
				}
			}
		}
	}
	return lastJob;
}

The account that executes this code should have enough rights on the configuration database otherwise you will get the following error

The EXECUTE permission was denied on the object ‘proc_GetTimerRunningJobs’, database ‘SharePointConfigDBName’, schema ‘dbo’

The farm account already have the required permissions but you may get this error if you run this with another account, even if you run it with elevated privileges.
For example if you run this code in an application page with RunWithElevatedPrivileges block you may get this error because RunWithElevatedPrivileges elevates the current account to the application pool account, but that account may not have enough rights to execute those SQL procedures.
In that case you have two options: 1. impersonate the farm account or 2. go to the configuration database and grand the application pool account EXECUTE permission for the procedures required for this code to run. EXECUTE permission is needed for the following stored procedures in the configuration database:

  • proc_GetTimerRunningJobs
  • proc_GetTimerJobHistory
  • proc_GetTimerJobLastRunTime

Because we should not directly modify the content and configuration databases its better to impersonate the farm account instead of touching the configuration database.

Get, Add or Remove SharePoint groups and users programmatically

Below are some methods to get, add or remove SharePoint groups and group users.

Get site groups

public List<String> GetSiteGroups(string SiteURL)
{
	List<String> groups = new List<String>();
	SPSecurity.RunWithElevatedPrivileges(delegate()
	{
		using (SPSite site = new SPSite(SiteURL))
		{
			using (SPWeb web = site.OpenWeb())
			{
				SPGroupCollection sitegroups = web.SiteGroups;
				foreach (SPGroup item in sitegroups)
				{
					groups.Add(item.Name);
				}
			}
		}
	});

	return groups;
}

Ensure group

This method ensures that a group exists and if it doesn’t it creates a group with the specified name and owner (given as loginname)

public void EnsureSPGroup(string spGroup, string spGroupOwner, string SiteURL)
{
	SPSecurity.RunWithElevatedPrivileges(delegate()
	{
		using (SPSite site = new SPSite(SiteURL))
		{
			using (SPWeb web = site.OpenWeb())
			{
				try
				{
					SPGroup group = web.SiteGroups[spGroup];
				}
				catch (SPException ex)
				{
					SPUser owner = web.EnsureUser(spGroupOwner);
					web.SiteGroups.Add(spGroup, owner, null, string.Empty);
				}
			}
		}
	});
}

Get group members

This method returns all users members of a group specified with the group ID.

public List<String> getGroupUsers(int groupID, string SiteURL)
{
	List<String> groupMembers = new List<String>();
	SPSecurity.RunWithElevatedPrivileges(delegate()
	{
		using (SPSite site = new SPSite(SiteURL))
		{
			using (SPWeb web = site.OpenWeb())
			{
				SPGroup group = web.SiteGroups.GetByID(groupID);
				foreach (SPUser user in group.Users)
				{
					groupMembers.Add(user.LoginName);
				}
			}
		}
	});

	return groupMembers;
}

This method returns all users members of a group specified with the group name.

public List<String> getGroupUsers(string groupName, string SiteURL)
{
	List<String> groupMembers = new List<String>();
	SPSecurity.RunWithElevatedPrivileges(delegate()
	{
		using (SPSite site = new SPSite(SiteURL))
		{
			using (SPWeb web = site.OpenWeb())
			{
				SPGroup group = web.SiteGroups[groupName];
				foreach (SPUser user in group.Users)
				{
					groupMembers.Add(user.LoginName);
				}
			}
		}
	});

	return groupMembers;
}

Add user to a group

This method adds a user specified by login name to a group specified with the group ID

public void AddUser(String userLoginName, int groupID, string SiteURL)
{
	SPSecurity.RunWithElevatedPrivileges(delegate()
	{
		using (SPSite site = new SPSite(SiteURL))
		{
			using (SPWeb web = site.OpenWeb())
			{
				SPGroup group = web.SiteGroups.GetByID(groupID);
				SPUser userToAdd = web.EnsureUser(userLoginName);
				group.AddUser(userToAdd);
				group.Update();
			}
		}
	});
}

This method adds a user specified my login name to a group specified with the group name.

public void AddUser(String userLoginName, string groupName, string SiteURL)
{
	SPSecurity.RunWithElevatedPrivileges(delegate()
	{
		using (SPSite site = new SPSite(SiteURL))
		{
			using (SPWeb web = site.OpenWeb())
			{
				try
				{
					SPGroup group = web.SiteGroups[groupName];
					SPUser userToAdd = web.EnsureUser(userLoginName);
					group.AddUser(userToAdd);
					group.Update();
				}
				catch (Exception ex)
				{
					//handle the exception
				}
			}
		}
	});
}

Remove user from a group

This method removes a user from a group specified with the group ID.

public void RemoveUser(String userLoginName, int groupID, string SiteURL)
{
	SPSecurity.RunWithElevatedPrivileges(delegate()
	{
		using (SPSite site = new SPSite(SiteURL))
		{
			using (SPWeb web = site.OpenWeb())
			{
				SPGroup group = web.SiteGroups.GetByID(groupID);
				SPUser userToRemove = web.EnsureUser(userLoginName);
				group.RemoveUser(userToRemove);
				group.Update();
			}
		}
	});
}

This method removes a user from a group specified with the group name.

public void RemoveUser(String userLoginName, string groupName, string SiteURL)
{
	SPSecurity.RunWithElevatedPrivileges(delegate()
	{
		using (SPSite site = new SPSite(SiteURL))
		{
			using (SPWeb web = site.OpenWeb())
			{
				SPGroup group = web.SiteGroups[groupName];
				SPUser userToRemove = web.EnsureUser(userLoginName);
				group.RemoveUser(userToRemove);
				group.Update();
			}
		}
	});
}

For getting Active Directory groups and users check my other post Get Active Directory Groups and Users programmatically

User profiles synchronization job not synchronizing

Problem

User profiles synchronization job is not synchronizing the user profiles from AD to SharePoint User Profiles.

Solution

Check the permissions of the account that is specified in the synchronization connection. This account must be in the Domain Administrators group or have the following permissions granted explicitly:

  • Replicating Directory Changes
  • Replicating Directory Changes All
  • Replicating Directory Changes In Filtered Set
  • Replication synchronization

Follow the steps below to grand permissions to a user account

  1. Open the Active Directory Users and Computers snap-in
  2. On the View menu, click Advanced Features.
  3. Right-click your domain -> Properties.
  4. On the Security tab, if the desired user account is not listed, click Add to add the user account
  5. After adding the user select the permissions listed above in the post.
  6. Click Apply, OK.

After granting the required permission go to User Profiles Service Application and start user profile synchronization and after it’s done check the results.

Hide or change quick launch’s width with CSS

Hide quick launch

To hide the quick launch the following CSS can be used:

	<style type="text/css">
	.s4-ca {
	 margin-left: 0px;
	}
	#s4-leftpanel {
	 display: none;
	}
	</style>

Change quick launch’s width

Quick launch’s width can be changed with the following CSS:

	<style type="text/css">
	.s4-ca {
	margin-left: 200px;
	}
	body #s4-leftpanel {
				   width: 200px;
	float:left;
	}
	</style>

The CSS can be added to a content editor web part to apply the change to a particular page or can be added to the master page to apply it for the whole site.

Another way to apply it to one site it to create a css file (let’s name it MyCss.css) and paste the css code above. Then upload the file to the site’s style library. Then set the site’s AlternateCssUrl property to the uploaded css file. That can be done using the following PowerShell script:

	Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
	$web=get-spweb http://weburl
	$site.AlternateCssUrl="http://sitecollectionurl/Style%20Library/MyCss.css"
	$web.Update();

Site columns missing (probably deleted)

Problem

Site columns missing from site collection columns gallery and from every content type that was using them. Possibly deleted by site collection administrator.

Case

I had a site collection scoped feature which deploys site columns and content types. There was a document library with multiple content types that inherit from the site content type deployed with that feature. For some reason some of the columns were missing from the site content type. As a result all the children content types were missing those columns and, of course because of that they were not available in the document library. It was surprising that they were not event in the site columns gallery. Probably somebody has deleted them from the site columns gallery.

Investigation

I returned the site collection columns by deactivating and reactivate the site collection feature that has originally deployed those columns. This action returned the columns back and they also showed back in the content type without explicitly re-adding them.
Now the problem was that I was not able to push the changes down to the content types that inherit from this site content type. I tried with UI and also with code by calling SPContentType.Update(true, true) but without success. Finally I found a workaround by adding a new content type inheriting from this content type. After creating the new content type all the missing fields showed back in all the content types and as a result in the document library.

Solution

  1. Deactivate and reactivate the site collection feature that has originally deployed the missing site columns.
  2. Add new content type that inherits from the site content type that contains the missing columns to the document library temporarily.
  3. Delete the newly added content type.
%d bloggers like this: