Content Migration using Web Service and Client Side Object Model

Most of our intranet sites have been moved from SharePoint 2007 to SharePoint 2013, like Team sites and MySite. The only one left is the our corporate portal site, which serves the entire company.


In MOSS 2007, we implemented many Farm solutions, such as, custom web parts, list event receivers, workflow and custom site template. Those are the bits that make upgrade challenging. For team sites, we re-complied the Farm solution (there is only one Farm solution for Team sites) and deployed to SharePoint 2013. Then take the content database to SharePoint 2010 first, then SharePoint 2013. However, for our corporate portal site, which is heavily customized, we don’t want to follow the same road-map since we try to avoid Farm solutions and convert everything to App Model. Business also want to split this one site collection to 3 new site collections in SharePoint 2013.


I developed a custom migration tool in a short period. It will allow site admin to migrate the MOSS 2007 site to SharePoint 2013 remotely as well as converting custom web parts to App parts and updating the links in the list items.


SharePoint 2007 SharePoint 2013

For MOSS 2007, I’m using Web Services like /_vti_bin/Webs.asmx, /_vti_bin/Lists.asmx,  /_vti_bin/Views.asmx and /_vti_bin/WebPartPages.asmx. There is also a custom web service developed due to the limitation of OOTB web service. For SharePoint 2013, Web Services are used as well as Client Side Object Model. Using this custom migration tool, site admins can move a site/list to anywhere they want.


Create Query Rule and Prompted Results SharePoint 2013

string siteUrl = "";
string resultSourceName = "Local SharePoint Results";
using (SPSite site = new SPSite(siteUrl))
  using(SPWeb web = site.OpenWeb("/en"))

    SPServiceContext context = SPServiceContext.GetContext(SPServiceApplicationProxyGroup.Default,

    SearchServiceApplicationProxy searchAppProxy = context.GetDefaultProxy(typeof(SearchServiceApplicationProxy))
                                       as SearchServiceApplicationProxy;

    SearchServiceApplicationInfo ssai = searchAppProxy.GetSearchServiceApplicationInfo();

    if (searchAppProxy != null)
      Guid searchAppId = searchAppProxy.GetSearchServiceApplicationInfo().SearchServiceApplicationId;
      // Get the application itself
      SearchServiceApplication searchApp = SearchService.Service.SearchApplications.GetValue<SearchServiceApplication>(searchAppId);

      QueryRuleManager queryRuleManager = new QueryRuleManager(searchApp);
      // SPWeb Level
      SearchObjectOwner searchOwner = new SearchObjectOwner(SearchObjectLevel.SPWeb, web);

      SearchObjectFilter searchFilter = new SearchObjectFilter(searchOwner);

      FederationManager federManager = new FederationManager(searchApp);

      Source resultSource = federManager.GetSourceByName(resultSourceName, searchOwner);

      QueryRuleCollection qrCollection = queryRuleManager.GetQueryRules(searchFilter);

      CreateQueryRule(queryRuleManager, qrCollection, searchFilter);

private void CreateQueryRule(QueryRuleManager queryRuleManager, QueryRuleCollection qrCollection, SearchObjectFilter searchFilter)
     QueryRule queryRule = qrCollection.CreateQueryRule("QueryRuleName", null, null, true);
     // Query Matches Keyword Exactly
     List<string> keyWords = new List<string>();
     KeywordCondition kwCondition = queryRule.QueryConditions.CreateKeywordCondition(keyWords, true);

     // Promopted Results
     Microsoft.Office.Server.Search.Query.Rules.BestBetCollection bestbets = queryRuleManager.GetBestBets(searchFilter);
     QueryAction queryAction = queryRule.CreateQueryAction(QueryActionType.AssignBestBet);
     Microsoft.Office.Server.Search.Query.Rules.BestBet bestBet = bestbets.CreateBestBet("Resutl1", null, "Description", true);
     bestBet = bestbets.CreateBestBet("Result2", new Uri(""), "Description", true);

     RegularExpressionCondition pnCondition = queryRule.QueryConditions.CreateRegularExpressionCondition("pattern", true);



Configure Site Navigation via CSOM

We would like to configure site navigation when a new site got created using CSOM. 
Here is the code I figured it out.
using(ClientContext ctx = new ClientContext(siteUrl)
   Web web = ctx.Web;
   TaxonomySession taxonomySession = TaxonomySession.GetTaxonomySession(ctx);
   WebNavigationSettings webNavSettings = new WebNavigationSettings(ctx, web);
   webNavSettings.CurrentNavigation.Source = StandardNavigationSource.PortalProvider;
   webNavSettings.CreateFriendlyUrlsForNewPages = false;

   // show only current site's navigation items in current navigation
   web.AllProperties["__InheritCurrentNavigation"] = "False";
   web.AllProperties["__NavigationOrderingMethod"] = "2";
   web.AllProperties["__NavigationShowSiblings"] = "True";
   web.AllProperties["__CurrentNavigationIncludeTypes"] = "3";
   web.AllProperties["__CurrentDynamicChildLimit"] = "100";

   // show subwebs and pages in navigation
   web.AllProperties["__IncludeSubSitesInNavigation"] = "True";
   web.AllProperties["__IncludePagesInNavigation"] = "True";



SP2013 Search REST API Error: Method not found

We have a large SharePoint 2013 Farm and recently encountered a strange issue on some front end servers.

The REST API of Search return 400 Bad Request Error with even a simple query like “test”.

After googling, we found a quick fix but don’t understand how it make this trick.
You have to run Install-SPApplicationContent on the servers that have this issue.

Setup IIS App Pool and deploy codes by PowerShell

$iisSiteLocation = "D:\InetPub\wwwroot\iis\VirtualDirectories\" + $iisFolder"
$iisSite = "IIS:\Sites\My_Apps_Site (443)"

$NewAppPoolName = "My_Apps_Site_Service_AppPool"

CopyFilesWithWebConfigToIISLocation $sourceLocation $iisSiteLocation $appFolderName

function CreateApplicationPool([string] $AppPoolName){

$NewPool = Get-Item IIS:\AppPools\"$AppPoolName" -ErrorAction SilentlyContinue

Write-Host $AppPoolName

if($NewPool -eq $null) {
Write-Host "Creating Application Pool..."
New-WebAppPool -Name $AppPoolName
$NewPool = Get-Item IIS:\AppPools\"$AppPoolName"
$cred = Get-Credential
$NewPool.ProcessModel.Username = $cred.GetNetworkCredential().Domain +"\"+ $cred.GetNetworkCredential().UserName
$NewPool.ProcessModel.Password = $cred.GetNetworkCredential().Password
$NewPool.ProcessModel.IdentityType = 3
$NewPool.ManagedRuntimeVersion = "v4.0"
$NewPool | Set-Item
Else {
Write-Host "Application Pool already exists."


function CopyFilesWithWebConfigToIISLocation ([string] $sourceDir, [string] $destDir, [string] $appFolderName) {
$sourceFileLocation = $sourceDir + "\" + $appFolderName
$destFileLocation = $destDir + "\" + $appFolderName

write-host "Copy files from " $sourceFileLocation " to " $destFileLocation
Copy-Item $sourceFileLocation $destDir -recurse -force
Copy-Item "$sourceFileLocation\WebConfig\PROD\web.config" $destFileLocation -force}

Managed Metadata Service issue caused by SharePoint Feb 2015 CU

After applying the Feb CU to SharePoint environment, we experienced issues below:

(1) On the My Profile Edit page where all the term set properties are getting the following error:

There was a problem retrieving data for this field. Updating values in this field is disabled temporarily. You can still update values in other fields.

(2) Also, if you’re trying to enter a hashtag when posting a newsfeed, you’ll get

The tagging service is currently unavailable, Please try again later.

To Fix the issues, you have to do the following:

1. Open a Command Prompt window using Farm account and at the command prompt, type:

cd /d %commonprogramfiles%\Microsoft Shared\Web server extensions\15\BIN

2. Fix the permissions on the server by entering the following command:

psconfig -cmd secureresources

3. Restart IIS and make the changes by entering the following command at the command prompt:
iisreset /noforce


Extend the Newsfeed Functionality of SharePoint 2013

Requirement: Provide ability to “one-click” post content to newsfeed or site feeds that users have access to.



  • SharePoint Provider-Hosted App
    • default.aspx – a custom page to allow users entering content and post to their newsfeed or site feed.
    • postnewsfeed
    • Custom Action – add “Post” to ECB menu of target lists
      <?xml version="1.0" encoding="utf-8"?>
      <Elements xmlns="">
       <CustomAction Id="c72a6c2f-b61e-41bc-90ec-4d7000aa4a64.PostToNewsfeed"
          <UrlAction Url="~remoteAppUrl/Pages/Default.aspx?{StandardTokens}&amp;SPListItemId={ItemId}&amp;SPListId={ListId}" />
  • JQuery UI AutoComplete
    • Generating mention and hashtags via JSOMmention
/// <summary>
/// WebMethod to search people for metion
/// </summary>
/// <param name="spHostUrl"></param>
/// <param name="term"></param>
/// <returns>People Search result in an array contains AccountName and PreferredName</returns>
public static string[] SearchPeople(string spHostUrl, string term)

   List<string> searchResults = new List<string>();

     using (var ctx = new ClientContext(spHostUrl))
       if (ctx != null && term != null)
         KeywordQuery query = new KeywordQuery(ctx);
         query.QueryText = "(PreferredName:" + term + "* AND LastName:" + term + "*)";
         query.SourceId = new Guid("B09A7990-05EA-4AF9-81EF-EDFAB16C4E31"); // this is the People result source
         query.RowLimit = 10;
         query.TrimDuplicates = true;
         query.EnableSorting = true;
         query.SortList.Add("LastName", Microsoft.SharePoint.Client.Search.Query.SortDirection.Ascending);
         SearchExecutor searchExecutor = new SearchExecutor(ctx);

         ClientResult<ResultTableCollection> results = searchExecutor.ExecuteQuery(query);

         #region loop through results
         if (results.Value.Count > 0)
           for (int i = 0; i < results.Value[0].ResultRows.Count(); i++)
             var resultRow = results.Value[0].ResultRows.ElementAt(i);
             string name = resultRow["PreferredName"].ToString();
             string accountName = resultRow["AccountName"].ToString();
             searchResults.Add(name + "|" + accountName);
   catch (Exception ex)

   return searchResults.ToArray();
/// <summary>
/// WCF Service method to retrieve Hashtags
/// </summary>
/// <param name="spHostUrl"></param>
/// <param name="term"></param>
/// <returns></returns>

public string[] SearchHashTag(string spHostUrl, string term)
   List<string> searchResults = new List<string>();
     using (var ctx = TokenHelper.GetS2SClientContextWithWindowsIdentity(new Uri(spHostUrl), null))
       if (ctx != null && term != null)
         TaxonomySession taxonomySession = TaxonomySession.GetTaxonomySession(ctx);
         TermStore termStore = taxonomySession.GetDefaultSiteCollectionTermStore();
         TermSetCollection termSetCollection = termStore.GetTermSetsByName("Hashtags", 1033);
         TermSet termSet = termSetCollection.GetByName("Hashtags");

         TermCollection termCollection = termSet.GetAllTerms();

         #region loop through results
         if (termCollection.Count > 0)
           var terms = termCollection.GetEnumerator();
           while (terms.MoveNext())

   catch(Exception ex)

   return searchResults.ToArray();