SharePoint: Removing a BDC model via PowerShell

May 7th, 2012 No comments

    In one of the previous posts I showed how to import a BDC model; the current one is about removing. The following script allows to remove a bdc model by its name:

$model = Get-SPBusinessDataCatalogMetadataObject 
    -Name "yourModelName" -BDCObjectType Model -ServiceContext "http://yourWebAppUrl"
Remove-SPBusinessDataCatalogModel -identity $model -Confirm:$false

Note: here command line parameters are wrapped to the next lines for readability only. In SharePoint 2010 Management Shell, each command and its parameters should be in the same line.

The Get-SPBusinessDataCatalogMetadataObject command gets a Model object by its name and saves reference to it in the $model variable. The Model object is relevant to your web application accessible through the url http://yourWebAppUrl. As its name implies, the Remove-SPBusinessDataCatalogModel method removes the received model using the $model variable. -Confirm:$false allows to skip an YES/NO confirmation arising right before a model is deleted indeed. Remember that the operation isn’t reversible, use the -Confirm:$false with precaution.

If you need to remove all bdc models, you can use the script as follows:

$metaStore = Get-SPBusinessDataCatalogMetadataObject
                -BdcObjectType Catalog -ServiceContext "http://yourWebAppUrl"
foreach ($model in $metaStore.GetModels("*")) { 
		Remove-SPBusinessDataCatalogModel –Identity $model -Confirm:$false
}

Note: here command line parameters are wrapped to the next lines for readability only. In SharePoint 2010 Management Shell, each command and its parameters should be in the same line.

SharePoint: “Value does not fall within the expected range” exception in SPFieldMap.GetColumnNumber

May 4th, 2012 No comments

    If you get the exception shown below, you likely need to increase the List View Lookup Threshold.

[ArgumentException: Value does not fall within the expected range.]
   Microsoft.SharePoint.SPFieldMap.GetColumnNumber(String strFieldName, Boolean bThrow) +23672719
   Microsoft.SharePoint.SPListItemCollection.GetColumnNumber(String groupName, Boolean bThrowException) +174
   Microsoft.SharePoint.SPListItemCollection.GetRawValue(String fieldname, Int32 iIndex, Boolean bThrow) +44
   Microsoft.SharePoint.SPListItem.GetValue(SPField fld, Int32 columnNumber, Boolean bRaw, Boolean bThrowException) +26792787
   Microsoft.SharePoint.SPListItem.GetValue(String strName, Boolean bThrowException) +77
   Microsoft.SharePoint.SPListItem.get_Item(String fieldName) +12
   ...

Go to Central Administration, click on Application Management, then click on Manage web applications, select an application throwing the exception (in my case it’s SharePoint – 80), in the upper Ribbon click on arrow below the General Settings group to display a drop-down menu, click on Resource Throttling.

General Settings - Resource Throttling

Increase a value of the List View Lookup Threshold. The default value is 8. I set to 20, and the exception is gone.

List View Lookup Threshold

SharePoint: Deploying a BDC model via PowerShell

May 3rd, 2012 No comments

    In SharePoint 2010 a BDC model can be easily deployed using PowerShell. Launch SharePoint 2010 Management Shell (click on Start, then All Programs -> Microsoft SharePoint 2010 Products -> SharePoint 2010 Management Shell) and execute the following script:

$metaStore = Get-SPBusinessDataCatalogMetadataObject 
                -BdcObjectType "Catalog" -ServiceContext "http://yourWebAppUrl"
Import-SPBusinessDataCatalogModel 
                -Path "c:\folder\subfolder\yourfile.bdcm" -Identity $metaStore -Force

Note: here command line parameters are wrapped to the next lines for readability only. In SharePoint 2010 Management Shell, each command and its parameters should be in the same line.

The first command, Get-SPBusinessDataCatalogMetadataObject, returns and saves in the $metaStore variable a Catalog metadata object relevant to your web application accessible through the url http://yourWebAppUrl. The second command, Import-SPBusinessDataCatalogModel, imports a Business Data Connectivity Model defined in a file, path of which is indicated by the -Path key. The model is to be added to the Catalog referenced by the $metaStore and overwrites the existent version (if any) due to the -Force key.

Despite the -Force key you can get the following error:

Error: Cannot merge contents of LobSystem (External System) with Name 'Products' 
as it appears to be different from a preexisting LobSystem in the current load context.

What you need to do in this case is remove the previous version of the model.

SharePoint: Workflow + List Item Edit Form = Value cannot be null Exception

April 25th, 2012 No comments

    After migration of a SharePoint 2007 application to SharePoint 2010 we encountered an unhandled exception occurring arbitrarily when a list item is opening for editing. The exception looks as follows:

Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: s

Source Error: 
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below. 

Stack Trace: 

[ArgumentNullException: Value cannot be null. Parameter name: s]
   System.IO.StringReader..ctor(String s) +10151478
   Microsoft.SharePoint.Publishing.Internal.WorkflowUtilities.FlattenXmlToHashtable(String strXml) +117
   Microsoft.SharePoint.Publishing.Internal.WorkflowUtilities.DoesWorkflowCancelWhenItemEdited(String associationXml) +12
   Microsoft.SharePoint.Publishing.WebControls.ConsoleDataSource.EnsurePageNotInLockingWorkflowIfInEditMode() +207
   Microsoft.SharePoint.Publishing.WebControls.ConsoleDataSource.LoadDataSource() +199
   Microsoft.SharePoint.Publishing.WebControls.ConsoleDataSource.OnLoad(EventArgs e) +98
   Microsoft.SharePoint.Publishing.WebControls.XmlConsoleDataSource.OnLoad(EventArgs e) +201
   Microsoft.SharePoint.Publishing.WebControls.PublishingSiteActionsMenuCustomizer.OnLoad(EventArgs e) +186
   System.Web.UI.Control.LoadRecursive() +66
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Control.LoadRecursive() +191
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2428

Having analyzed the stack trace we found out that the issue is, evidently, caused by workflow infrastructure. In the SharePoint application we have both handmade custom workflows and the ones that were made in SharePoint Designer (so called SPD Workflows). It’s interesting that the exception was rising for both workflow types. After some investigation using .Net Reflector, we discovered that the AssociationData xml-element is a cause of our troubles. The AssociationData is an element of the Workflow Definition Schema and specifies any custom data to pass to the workflow association form. Additionally, we use the association data when starting workflow on list items through the code. The workflow infrastructure, in SharePoint 2010, supposes that the AssociationData element is a valid xml string. So, if it’s empty or contains any data without xml-tags, the exception is thrown as opposite to SP 2007.

The solution consists of two steps. Firstly, for every Workflow Definition in your project you need to set <AssociationData> so that it contains a valid xml. Regardless of whether association data is actually used or not, <AssociationData> has to be presented in Workflow Definition and contain at least a fake valid xml. For example, I set dummy <Data />. In case you deploy a custom workflow through a SharePoint Feature, the Workflow Definition is usually located in Elements.xml file. If <AssociationData> element doesn’t exist within <Workflow> tag, add it at the same level as <MetaData>. The resultant workflow definition should look like:

<?xml version="1.0" encoding="utf-8" ?> 
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Workflow ...>
    ...
    <AssociationData>
      <Data />
    </AssociationData>
    <MetaData>
      ...
    </MetaData>
    ...
  </Workflow>
</Elements>

The described first step of the solution affects only new applications created after the alterations have been applied to Workflow Definitions. Even if a SharePoint Feature containing a custom workflow is reactivated, changes are only applied to workflow instances that start after the workflow association is modified. But for live applications created before or migrated from previous version of SharePoint (like in my case), the first step is useless. Additionally, you may have the SPD workflow which doesn’t include usual Workflow Definition at all. So, the second step is a necessary and sufficient part of the solution.

The second step is get all SPWorkflowAssociation objects and adjust their AssociationData properties programmatically. I’ve developed a few methods allowing to achieve this goal. See the listing below:

using System;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;

...

/// <summary>
/// Adjusts AssociationData properties of all Workflow Associations related to the passed Web, its child Lists and Content Types
/// </summary>
public static void AdjustAllWorkflowAssociations(SPWeb web)
{
    AdjustWebWorkflowAssociation(web);

    for (int i = 0; i < web.ContentTypes.Count; i ++)
        AdjustContentTypeWorkflowAssociation(web.ContentTypes[i]);

    for (int i = 0; i < web.Lists.Count; i ++)
    {
        AdjustListWorkflowAssociation(web.Lists[i]);
        for (int j = 0; j < web.Lists[i].ContentTypes.Count; j ++)
            AdjustContentTypeWorkflowAssociation(web.Lists[i].ContentTypes[j]);
    }
}

/// <summary>
/// Adjusts AssociationData properties of all Workflow Associations related to the passed List
/// </summary>
public static void AdjustListWorkflowAssociation(SPList list)
{
    AdjustAssociationData(list.WorkflowAssociations);
}

/// <summary>
/// Adjusts AssociationData properties of all Workflow Associations related to the passed Web
/// </summary>
public static void AdjustWebWorkflowAssociation(SPWeb web)
{
    AdjustAssociationData(web.WorkflowAssociations);
}

/// <summary>
/// Adjusts AssociationData properties of all Workflow Associations related to the passed Content Type
/// </summary>
public static void AdjustContentTypeWorkflowAssociation(SPContentType contentType)
{
    AdjustAssociationData(contentType.WorkflowAssociations);
}

/// <summary>
/// Adjusts AssociationData properties of all Workflow Associations in the passed collection
/// </summary>
public static void AdjustAssociationData(SPWorkflowAssociationCollection collection)
{
    for (int i = 0; i < collection.Count; i ++)
        AdjustAssociationData(collection[i], collection);
}

/// <summary>
/// Sets AssociationData property if it's not valid
/// </summary>
public static void AdjustAssociationData(SPWorkflowAssociation workflowAssociation, SPWorkflowAssociationCollection collection)
{
    if (!IsValidXml(workflowAssociation.AssociationData))
    {
        string newValue = string.IsNullOrEmpty(workflowAssociation.AssociationData)
                                ? "<Data />"
                                : string.Format("<Data>{0}</Data>", workflowAssociation.AssociationData);
        workflowAssociation.AssociationData = newValue;
        collection.Update(workflowAssociation);
    }
}

/// <summary>
/// Checks if the passed string is a valid xml
/// </summary>
public static bool IsValidXml(string str)
{
    if (!string.IsNullOrEmpty(str))
    {
        try
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(str);
            return true;
        }
        catch {}
    }
    return false;
}

The basic method in the set is AdjustAssociationData, which examines AssociationData property of a passed SPWorkflowAssociation object and then applies a valid xml-value to the property if it’s necessary. As we know, workflows can be associated with webs, lists or content types. The AdjustWebWorkflowAssociation, AdjustListWorkflowAssociation and AdjustContentTypeWorkflowAssociation methods adjust AssociationData of a passed SPWeb, SPList or SPContentType object respectively. Finally, the AdjustAllWorkflowAssociations tries to adjust all available workflow associations that can be reached through a passed SPWeb instance.

Note that I everywhere used the for-statement instead of foreach, because otherwise the exception “Collection was modified; enumeration operation may not execute” will be thrown.

Below is the piece of code from the simple console application, which I used against our problem SharePoint application:

static void Main(string[] args)
{
    try
    {
        AppArguments arguments = new AppArguments(args);

        if(string.IsNullOrEmpty(arguments.Url))
        {
            Console.WriteLine("Invalid application URL!");
            return;
        }

        using (SPSite spSite = new SPSite(arguments.Url))
            using (SPWeb spWeb = spSite.OpenWeb())
                AdjustAllWorkflowAssociations(spWeb);

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

Where the AppArguments is a class derived from InputArguments. InputArguments is described here – Simple Command Line Arguments Parser.

public class AppArguments : InputArguments
{
    public string Url
    {
        get { return GetValue("url"); }
    }

    public AppArguments(string[] args) : base(args)
    {
    }
}

As for our SharePoint application, it has only workflows associated with lists. So, after executing of the console app described above, the exception in question is gone. I hope it’ll help somebody as well.

The full version of the console app you can download here (Visual Studio 2010 solution). To execute it use the command line with parameters as follows:

AdjustAssociationData.exe -url "http://yourservername/yourapppath"

SharePoint: User from trusted domain doesn’t see search result

April 23rd, 2012 No comments

    Our production SharePoint Farm and customer users are deployed in one domain, let’s call it Root, while the development and quality control farms are in a child domain, let’s name it Child.Root. The Child.Root domain trust to the parent Root domain, while the Root domain knows nothing about the Child.Root. So, we have a one way trust domains configuration where the Child.Root trusts Root, but not vice versa. Under such conditions we have faced the issue with SharePoint Search when an user from the trusted parent Root domain gets zero results, executing a search query on the Child.Root.

The solution was borrowed from the Microsoft Knowledge Base ArticleUnable to Perform a query on a One-Way trust Domains Scenario when an User from the trusted domain performs the query and the SSA Application Pool account is from the Trustee Domain. The topology described in the KB article presumes that domains with a one way trust relationship are in two separate forests. Despite the fact that in our case both domains are in one forest, the solution works great, though.

So, you need to follow these steps:

  1. Launch SharePoint 2010 Management Shell (click on Start, then All Programs -> Microsoft SharePoint 2010 Products -> SharePoint 2010 Management Shell);
  2. Type $searchapp = Get-SPEnterpriseSearchServiceApplication and press Enter;
  3. Type $searchapp.SetProperty(“ForceClaimACLs”,1) and press Enter. Don’t wait for any confirmation, you won’t see it;
    Set ForceClaimACLs property
  4. Restart a full crawl through Central Administration (click on Start, then All Programs -> Microsoft SharePoint 2010 Products -> SharePoint 2010 Central Administration, then go to Application Management -> Manage Service Applications -> Search Service Application, then in Crawling section click on Content Sources, open context menu for the Content Source you want to re-crawl, for example, Local SharePoint sites and click Start Full Crawl);
    Start Full Crawl

After the SetProperty() command has set value of the ForceClaimACLs parameter in the search administration database to 1, ACLs are stored as Claims instead of NT tokens. Note, however, that you needn’t switch other SharePoint applications (different from the Search Service Application) to Claims based authentication. Also, keep in mind that this is a one-way change, so you won’t be able to reverse it back to classic mode.

After the full crawl is performed, users see search results, regardless from which domain they are logged in.