Showing posts with label Sharepoint 2010. Show all posts
Showing posts with label Sharepoint 2010. Show all posts

Friday, 17 February 2012

Search result URLs not correctly mapped to the current zone when the content is crawled in a different farm

This one is just a nice-to-know. I didn't know about this setting, so I'll briefly document it here for future reference.

Situation
The set-up consists of a typical enterprise services farm (http://technet.microsoft.com/en-us/library/cc560988.aspx#Enterprise):

  • Farm A hosts the collaboration web-applications, in which the users upload their documents and other content.
  • Farm B is a remote services farm and hosts the cross-farm service applications. This includes all of our search service application(s). These service applications are published and then consumed on Farm A. 
Farm B crawls farm A using the web-application's default zone url, e.g. http://sps2010. When a user performs a search in farm A on the default zone, all of his search results link back to the default zone. So far so good.

The problem
Now lets say we add a new Intranet AAM zone on farm A's web-applications, which points to http://sps2010intra. When a user performs a search from that zone, all of his search results will again refer to the default zone!

The explanation
This is actually pretty logical: the content is crawled on farm B, using the default zone's URL.
In a single farm setup, the search service application will have access to the crawled web-application's alternate access mappings and will take those into account when a query is done on the crawled content.
Here however, the search service application is hosted on a different farm (farm B) and that search service application doesn't know how the alternate access mapping is set up on farm A!

The solution
When you google this problem, you will get a lot of posts saying that you need to use the Server Name Mapping setting on your search service application. There's a good overview of this setting here.

Personally I would not recommend using that approach, as it only works for one AAM zone.  There's no way to map multiple zones (intranet AND internet for instance) using the server name mapping route.

A much more flexible solution is to mimic the crawled content farm's alternate access mappings on the remote services farm. If you did set-up server name mappings to fix this: delete them and recrawl! ;)
  1. Open the central admin site on your remote services farm (farm B).
  2. Click on Application Management and select Configure alternate access mappings (in the web applications section).
  3. Now click on Map to external resource in the toolbar. This little bugger allows you to create an alternate access mapping for resources that aren't in the farm.
  4. Give it a meaningful name, like Collab {Name of the crawled webApp}. Enter the crawled web-application's default zone URL in the URL protocol, host and port field.
  5. Click on Save to create the external resource AAM. Now you can set this resource up as if it were a web-application on your remote services farm!
  6. Use Edit Public URLs and Add Internal URLs to configure the external resource's AAM just like it is set up on your crawled farm (farm A).
That's it ... no need to recrawl or anything. If all was done well, search results on the content farm (Farm A) will now be correctly translated according to their zone, even while the content was indexed on another farm.





SP2010 - Search Crawl not working on remote services farm


A quick post on two issues I've encountered lately. Both are related to SharePoint 2010 Search crawls that suddenly stopped working.
I'm just going to post the symptoms and what worked as solution for me.

The SharePoint set-up consists of two farms:
  • Farm A hosts the collaboration web-applications, in which the users upload their documents
  • Farm B hosts most of the service applications, including the search service application(s). These search service applications crawl content from Farm A. Furthermore, these service applications are consumed on Farm A (so people on that farm can search the crawled content).

Issue #1
The crawl log was filled with the following errors when crawling farm A:
The SharePoint item being crawled returned an error when requesting data from the web service. ( Error from SharePoint site: Value does not fall within the expected range. ).

Solution
Apparently there was an issue with the web-apps that were being crawled. In order to get them crawled again, I
  1. Detached the content databases linked to the web-apps on farm A
  2. Removed the web-app (make sure to delete the related IIS site)
  3. Recreate the web-app
  4. Attach the old content db again
Simple as that.

Issue #2
Some weeks later, my crawl log got filled with this lovely message:
The SharePoint item being crawled returned an error when requesting the data from the web service. ( Error from SharePoint site: *** Could not find part of the path 'c:\TEMP\gthrsvc_OSearch14\{random-text}.tmp' 

Solution
Despite the error, that path was indeed present on all my machines in farm B. I tried fiddling with the permissions on that folder, but nothing helped.

In the end I just:
  1. Removed the consumed search service applications on farm A.
  2. Removed the search service application(s) on farm B
  3. Recreated the search service application(s) on farm B. Luckily for me, I had already scripted this procedure in PowerShell.
  4. Publishd the service apps again on farm B and consume them on farm A.
  5. Recrawled all content
Conclusion: if some part of SharePoint is fucked up, you can usually fix it by removing & recreating it :)

Friday, 19 August 2011

SP 2010 user profile service app - ILM Database could not be created

This week I've been battling with the following error during a SharePoint 2010 user profile service app setup: ILM Database could not be created.

I had created some powershell scripts to set up the user profile service applications automatically on multiple farms. These scripts worked like a charm on most farms, except one.
The service application itself was correctly created, but when the script tried to start the User Profile Sync Service it failed. The service itself briefly went to a Started state, but reverted back to its Stopped state after a few minutes.

A glance in the event logs revealed a number of warnings, that were logged at the time the service started:

Warning 1
ILM SQL Configuration file could not be read: 
Error Parcing the SQL File: C:\Program Files\Microsoft Office Servers\14.0\Sql\StoredProcedures.sql  

Exception: Conversion of one or more characters from XML to target collation impossible

Warning 2
ILM Database could not be created: Error sent to Windows Event Log running : 
-- Proc Name:	GetObjectDisplayNames
-- Purpose:		to get display names for objects
-- Input:		@ObjectIDs, which we suppose to take in an array of Guids, in the format of:
--				(?B553ECA-F359-11DB-AABA-D0C055D89593? ... , �A5B106DC-F359-11DB-9C44-1EC155D89593?
-- Exception:
--				If failed to find certain object id, SQL error occurs
CREATE PROCEDURE [dbo].[GetObjectDisplayNames]
@ObjectIDs     NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Sql	NVARCHAR(MAX)
SET @Sql =
N' SELECT ValueString FROM ObjectValues ' +
N' WHERE AttributeName = N''DisplayName'' ' +
N' AND ObjectID IN ' + @ObjectIDs
EXEC sp_executesql @Sql
END
: Conversion of one or more characters from XML to target collation impossible

These warnings repeated themselves a few times and then it said: Sync service stopped succesfully. Just brilliant, ain't it?
As I couldn't find any clues online, I presumed I was the first to get this kind of exotic crap.
So in the next days I tried to fix it ... to no avail however. I tried:
  • rerunning the script (who knows, maybe it was a one-time glitch)
  • installing the user profile service app manually
  • reinstalling sharepoint completely
  • installing SP1
  • formatting and reinstalling all servers in that $*@! farm

But finally I found a workaround. It involves tampering with some out-of-the-box installation files, so it will void your warranty/support from Microsoft. Proceed with caution! ;)

Solution
I noticed there was a strange token in the message displayed in the second warning in the event-log.

Could it be that easy? Was this token the source of all that "Conversion of one or more characters from XML to target collation impossible" crap?
I was pretty desperate at that time, so I was willing to give it a shot. Here's what I did:

  1. Open an explorer window and go to C:\Program Files\Microsoft Office Servers\14.0\Sql
  2. Locate the file StoredProcedures.sql, this contains the stored proc that was mentioned in the error.
  3. Take a backup of this file as we'll have to modify it. Always take a backup if you're going to tinker with ootb SharePoint files ;)
  4. Open the file in notepad and look for the entry CREATE PROCEDURE [dbo].[GetObjectDisplayNames]
  5. If you scroll up a bit, you should notice a line mentioning "... , ’A5B106DC-F359-11DB-9C44-1EC155D89593?"
  6. Now replace that sign in front of the A5B106DC-F359-11DB-9C44-1EC155D89593 guid with a ? char.
  7. Save the file again

After that I reran the script and a miracle happened (*insert praise for whatever deity you believe in*): everything worked like a charm. The sync service started properly after a few minutes and the user profile service app synced properly.

I'm still not sure why this error only occurred on one farm, while all the others installed without any hassle. All I know is that this fixed it for me. Feel free to leave your ideas on why it works in the comments :)


So there you have it, another dirty fix for a SharePoint 2010 issue. Maybe I should rename this blog to Dirty solutions for exotic sharepoint issues ... :/

Note
Either way, I've also opened a case with Microsoft Support themselves, so hopefully they'll fix this in a future update or provide a better solution. Once I get more info from them, I'll let you know.

Wednesday, 20 July 2011

Update - Creating SharePoint 2010 user profile exclusion filters using Powershell

This post is actually a follow-up on my previous post on user profile exclusion filters.

A quick recap of that post:
  • I needed to script the creation of a user profile service application, along with some profile exclusion filters.
  • Unfortunately I couldn't find any existing reference on script the creation of user profile exclusion filters (only guides on how to do this in the central admin pages).
  • Took a peek at the code SharePoint uses to create exclusion filters in central admin, only to see they use internal/hidden classes & methods to create these filters.
  • In the end I couldn't find any public API/powershell cmdlet to create these filters, so ended up writing my own workaround: a small .NET 3.5 project that will invoke the internal/hidden SharePoint 2010 methods using reflection.

Still, for me there was something missing: an easy way to use this workaround in PowerShell. Cause we all know: Powershell rocks! :p

First attempt
Originally I wanted to translate the complete .NET 3.5 workaround into one (or more) ps1 scripts. This would eliminate all need for external assemblies and is just easier to read/fix should something happen.
I managed to translate 99% of the .NET code, which was actually easier than I thought it would be. It was a nice exercise in the usage of reflection and generics within PowerShell 2, so I might make a specific blogpost on this subject later on :)

However, 99% is not 100%. I just couldn't get the final step working: how to assign the FilterSet objects (the actual filters) to the user profile connection.
Internally SharePoint 2010 uses the SetExclusionFilters method - something I had to call using reflection. This worked like a charm from within a .NET project, but I couldn't get PowerShell to cast the objects to the correct internal classes (despite the fact that Get-Member did mention it was casted correctly).
As a result, I kept getting the following error ... no matter what I tried

Exception calling "Invoke" with "2" argument(s): "Parameter count mismatch."
At Functions.ps1:96 char:35
+ $setExclusionFiltersMI.Invoke <<<< ($connection, @($filterSetList)) + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : DotNetMethodException
When I find the time, I'll post the ps1 scripts too ... maybe one of you can solve this mystery.

First Second attempt
Soooo translating it all into ps1 scripts didn't work, but no worries! Time for a workaround .. for a workaround?!
This time I just wrote another .NET project, which defines some custom cmdlets. These cmdlets will then invoke the .NET code that I described in my previous post. On the bright side: the cmdlets can be easily used from within PowerShall and it actually works! :)
(But you're still stuck with a bunch of .NET assemblies that you cannot change on the fly ... can't have it all, I guess.)

Before I start a lengthy description of the cmdlets I developed, I'll post provide a quick overview. Use these links if you can't be a***d / bothered to read manuals :)

The project defines these cmdlets
  • Get-FilterSetCondition
  • Get-FilterSet
  • Set-ExclusionFilter
  • Get-FilterAttributes
  • Get-FilterSetConditionOperators
  • Get-FilterSetOperators

Get-FilterSetCondition
Use this cmdlet to create a new condition for your exclusion filter. These conditions are internally stored as FilterSetCondition objects, hence the naming. You can find an example of this in Example1.ps1 and Example2.ps1
Syntax:
Get-FilterSetCondition -Connection $connection `
                       -ConditionType "user" `
                       -Attribute "userAccountControl" `
                       -Operator "Bit_on" `
                       -Value "2"
Parameters:
  • Connection
    The connection on which you want to create an exclusion filter (Microsoft.Office.Server.UserProfiles.Connection object)
  • ConditionType
    Whether this condition applies to a user-related attribute or a group-related attribute (just like you have separate windows in the UI to specify user and group filters)
  • Attribute
    The attribute on which you want to filter, e.g. company or userAccountControl. Use the Get-FilterAttributes cmdlet to get an overview of all available attributes.
  • Operator
    The operator that you want to use in this condition, e.g. "Bit_on" or "Equals". Use the Get-FilterSetConditionOperators cmdlet to get an overview of all available operators.
  • Value
    The string-value you want to use in this condition.

Get-FilterSet
Use this cmdlet to create a new exclusion filter, based on conditions you've created using the Get-FilterSetCondition cmdlet. Do not mix user-based conditions with group-based conditions though! You can find an example of this in Example1.ps1 and Example2.ps1
Syntax:
Get-FilterSet -FilterType "user" `
              -Operator "or" `
              -Conditions $userConditions
Parameters:
  • FilterType
    Whether this filter contains user-related conditions or a group-related condition
  • Operator
    The operator that you want to use to combine the conditions. Either supply "or" or "and" as value.
  • Conditions
    An array of FilterSetCondition objects (which you created using the Get-FilterSetCondition cmdlet)

Set-ExclusionFilter
Use this cmdlet to assign exclusion filters (created using the Get-FilterSet cmdlet) to an existing user profile connection. You can either assign a user-based exclusion filter, a group-based exclusion filter or both.
You cannot however assign multiple user-based exclusion filters or multiple group-based exclusion filters. You'll have to combine these into a single user-based exclusion filter or a single group-based exclusion filter. An example of this cmdlet can be found in Example1.ps1 and Example2.ps1
Syntax:
Set-ExclusionFilter -Connection $connection `
                    -UserFilter $userFilter `
                    -GroupFilter $groupFilter
Parameters:
  • Connection
    A reference to a user profile connection (Microsoft.Office.Server.UserProfiles.Connection object)
  • UserFilter
    The user-related exclusion filter you want to set on the connection. Don't supply this parameter if you don't want to set a user-related exclusion filter.
  • GroupFilter
    The group-related exclusion filter you want to set on the connection. Don't supply this parameter if you don't want to set a group-related exclusion filter.

Get-FilterAttributes
Use this cmdlet to get a list of all available user-related or group-related attributes on a given user profile connection. Each of the returned strings can be used in the Get-FilterSetCondition cmdlet to create a condition on. You can find an example of this in Example3.ps1
Syntax:
Get-FilterAttributes -Connection $connection -User
Get-FilterAttributes -Connection $connection -Group
Parameters:
  • Connection
    A reference to a user profile connection (Microsoft.Office.Server.UserProfiles.Connection object)
  • User
    A switch parameter. Supply this if you want to list all user-related attributes on the connection. Cannot be used in combination with the -Group switch
  • Group
    A switch parameter. Supply this if you want to list all group-related attributes on the connection. Cannot be used in combination with the -User switch

Get-FilterSetConditionOperators
Lists all available operators that you can use in Get-FilterSetCondition

Get-FilterSetOperators
Lists all available operators that you can use in Get-FilterSet


That's brilliant ... but how do I use this in SharePoint's management shell?!
  1. Download either the complete package or the standalone archive. Look in the download section for a link ...
    • The standalone package only contains the necessary assemblies and some example scripts.
    • The complete package also contains the source-code I used, so you could make your own modifications. The source-code is located in the src folder, but I've also copied the necessary assemblies and examples in the distribution folder for easy access
  2. Whatever you downloaded, copy the VNTG.UserProfileFilters.PowerShell.dll and VNTG.UserProfileFilters.dll assemblies to a folder on your SharePoint 2010 server.
  3. Open a new SharePoint 2010 Management Shell on that server
  4. Load the cmdlets using Import-Module ".\VNTG.UserProfileFilters.PowerShell.dll"
  5. That's it! You can now use the cmdlets to create exclusion filters. Don't forget to check out the Example*.ps1 scripts that are also present in the downloaded packages. These scripts provide you with some real-life scenarios to create exclusion filters using the new cmdlets.


Disclaimer and downloads
Like I mentioned in the first part of this post: the workaround is based on reflection, in order to call the internal classes/methods that SharePoint 2010 uses in its Central Admin pages.
I'm sure they're hidden for a reason, so this workaround is definately not officially supported!

USE IT AT YOUR OWN RISK!
Neither I nor my bosses at Ventigrate can be held reliable if something goes wrong!
(The download is hosted at their codeplex project, so they forced me to add this disclaimer ... :p)

If you're interested in the complete source: use this download. This zip-archive contains the source of both the .NET workaround mentioned in the first part, as well as the powershell cmdlets. It also comes with additional examples to clarify its usage.

If you're not interested in any source code, but just want to use this workaround in your SharePoint 2010 Management Shell: use this download. This archive only contains the necessary .NET assemblies and some example ps1 scripts on how to use the cmdlets in your Powershell 2 console.


Examples
I'll list the same example that was mentioned in my original post - only this time using the custom cmdlets. This example is also available as Example1.ps1 in the downloads.
This code will create a user-attribute based exclusion filter and a group-attribute based exclusion filter and assign it to a user profile connection.
  • The user-attrib exclusion filter will filter on "userAccountControl bit on equals 2" or "company equals ventigrate"
  • The group-attrib exclusion filter will filter on "name equals testgroup"
Check the Example2.ps1 script if you want an example that only adds a user-attribute based filter.

#region STEP 1: load the necessary references
#--------------------------------------------
# Load the sharepoint powershell cmdlets
Add-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue | Out-Null

# Load the custom userprofile cmdlet dll
Import-Module ".\VNTG.UserProfileFilters.PowerShell.dll"
#endregion

#region STEP 2: get the user profile connection on which you want to create exclusion filters
#--------------------------------------------------------------------------------------------
# Get a reference to the service application called 'User Profile Service Application' (very original)
# and a connection called Example 1.
# Change these parameters to whatever user profile service app & connection you want to change on 
# your environment...
$serviceAppName = "User Profile Service Application"
$connectionName = "Example 1"

$profileApp = Get-SPServiceApplication | ? {$_.DisplayName -eq $serviceAppName}
$profileContext = [Microsoft.SharePoint.SPServiceContext]::GetContext(
          $profileApp.ServiceApplicationProxyGroup, 
          [Microsoft.SharePoint.SPSiteSubscriptionIdentifier]::Default)
          
# Get a new UserProfileConfigManager
$configManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($profileContext)

# Get the connection called "Example 1" in the service application
$connection = $configManager.ConnectionManager | ? {$_.DisplayName -eq $connectionName}

#endregion

#region STEP 3: create the conditions and bind them into an user-filter and a group-filter
#-----------------------------------------------------------------------------------------
# Execute Create-FilterSetCondition to create 2 user-related conditions and 1 group-related condition
# - userCondition1 = userAccountControl bit on equals 2
# - userCondition2 = company equals ventigrate
# - groupCondition = name equals testgroup
$userCondition1 = Get-FilterSetCondition -Connection $connection -ConditionType "user" -Attribute "userAccountControl" -Operator "Bit_on" -Value "2"
$userCondition2 = Get-FilterSetCondition -Connection $connection -ConditionType "user" -Attribute "company" -Operator "Equals" -Value "ventigrate"
$groupCondition = Get-FilterSetCondition -Connection $connection -ConditionType "group" -Attribute "name" -Operator "Equals" -Value "testgroup"

# Combine the user-related conditions in a single user FilterSet, using an OR operand
$userConditions = @($userCondition1, $userCondition2)
$userFilter = Get-FilterSet -FilterType "user" -Operator "or" -Conditions $userConditions

# "Combine" the group condition in another group FilterSet, using an AND operand (though it doesn't matter as it's a single condition ;))
$groupConditions = @($groupCondition)
$groupFilter = Get-FilterSet -FilterType "group" -Operator "and" -Conditions $groupConditions

#endregion

#region STEP 4: assign the filters to the connection
#---------------------------------------------------
# Assign the newly created filters to the connection
Set-ExclusionFilter -Connection $connection -UserFilter $userFilter -GroupFilter $groupFilter

#endregion

I think this code is pretty self-explaining (lots of comments in there).
As always: leave a comment if something isn't 100% clear ... or if you just want to post some constructive feedback :)

Sunday, 22 August 2010

How to create a BCS lookup field in a content type

Hellloooooo fellow frustrated SharePoint devs!
(Well I assume you are, otherwise you wouldn't be interested in this ...)

I'm currently working on a little project/proof-of-concept which involves heavy usage of the new SharePoint 2010 BCS functionalities. I've already come across a few bumps in the road for which I couldn't immediately find some useful info, so I guess this will be the first of many posts on this subject.

Today I'll start with the following issue: how to use a BCS lookup field in a content type.
Normally this shouldn't be to hard to do:
  1. Create a site column "A"
  2. Create a content type (on site collection level) "CT"
  3. Add a reference to column "A" in content type "CT"
However, no matter how hard I looked, I just couldn't find an option to create a BCS lookup field as site column. Fyi: a BCS lookup field is called External Data field in the UI.
Sure, you can easily create such field on a list level, but not on a site level.


Adding a list BCS lookup. Notice the External Data type


Trying to add a site BCS lookup. No external data type here ... :(



So after googling around without any result, I found it was time to fumble around with my trusty Powershell and see how these list fields are defined.
Apparently the field's SchemaXml is:
<field
type="BusinessData"
displayname="bcs_field"
required="FALSE"
enforceuniquevalues="FALSE"
id="{GUID}"
sourceid="{GUID}"
staticname="bcs_field"
baserenderingtype="Text"
name="bcs_field"
colname="nvarchar11"
rowordinal="0"
version="3"
group=""
systeminstance="SPSDEV1"
entitynamespace="SPSDEV1.BCS"
entityname="dbo_Persons"
bdcfield="Names"
profile=""
hasactions="True"
addfieldoption="AddToAllContentTypes, AddFieldToDefaultView">
</field>
Notable properties are:
  • SystemInstanceName: this is the BCS instance name.
    You can find this value via Central Admin > Application Management > Manage Service Applications > Business Data Connectivity Services. Then select External Systems and select the system's name. This should result in an URL like http://centraladmin/_admin/BDC/ViewBDCLobSystemInstances.aspx?AppId=...

  • EntityNamespace: BCS entity's namespace
  • EntityName: BCS entity you're referencing to
  • BdcFieldName: the BCS field you want to display in the column
Those last three properties' values can be found in the External Content Types overview of Central Admin > Application Management > Manage Service Applications > Business Data Connectivity Services





Armed with this info, I cooked up this piece of code which will create a BCS lookup site column. Of course, you can then reuse this column in any content type.
private string CreateBCSLookupField(SPWeb targetWeb,
string lookupFieldName, string groupName,
string systemInstanceName, string entityNamespace,
string entityName, string entityFieldName, bool hasActions)
{
SPBusinessDataField lookupField =
targetWeb.Fields.CreateNewField("BusinessData", lookupFieldName) as SPBusinessDataField;
lookupField.Group = groupName;
lookupField.SystemInstanceName = systemInstanceName;
lookupField.EntityNamespace = entityNamespace;
lookupField.EntityName = entityName;
lookupField.HasActions = hasActions;
lookupField.BdcFieldName = entityFieldName;
return targetWeb.Fields.Add(lookupField);
} 

*UPDATE*
Okay, apparently there's still something fishy going on behind the scene. I've been using this code for a while now and it seems there's an inconsistency when you create multiple BCS site columns in a row.
Lets say you create 3 columns (A, B, C) with this code.
  • First test:
    A was ok, B too, but C resulted in an error about an improperly configured lookup.
  • Second test (same code, same setup):
    A was ok, B impropertly configged and C was ok
  • Third test (again same setup):
    A failed, B was ok and C failed too
To be honest, I really don't know why it sometimes fails.  It's the same frikking code, but somehow SharePoint sometimes decides to funk things up.
Funking things up apparently comes down to bad property values.  I compared the schema xml's of properly created BCS fields and those that weren't: the correct fields had the same XML as I posted before, while the erronous fields had this xml
<Field  
Type="BusinessData"  
DisplayName="bcs_field"  
Required="FALSE"  
EnforceUniqueValues="FALSE"  
Group="BCS Test"  
ID="{GUID}"  
SourceID="{GUID}"  
StaticName="bcs_field"  
Name="bcs_field"  
BaseRenderingType="Text"  
ColName="nvarchar11"  
RowOrdinal="0" />
As you can see, this xml doesn't have the BCS related properties ... but why?!
Currently I only have one "workaround":
  1. Create BCS field
  2. Check if the resulting fields schema xml contains a reference to SystemInstance (or another BCS related property)
  3. If it doesn't, remove the field and try again.  Usually it'll then be created properly
  4. Rince and repeat if it still doesn't have a correct schema xml (of course, limit the number of repeats to prevent endless loops)
I know, it's definately not kosher ... but I just don't have a clue at the moment on what's causing these missing properties.
Any ideas are welcome :)

Wednesday, 11 August 2010

Querying SharePoint 2010 SPSite from within console application results in FileNotFoundException

Tried to connect to a SharePoint 2010 sitecollection via a console application today, but it kept failing with an FileNotFoundException.
Code:

using(SPSite site = new SPSite("http://sps2010"){
//FileNotFoundException
}

Just for future reference:
  1. make sure your application is using the .NET 3.5 framework
  2. ensure it's compiled as 64 bit

As you'll have guessed by now: my app was using the visual studio 2010 default settings of .NET framework 4.0, compiled as x86 ... which obviously conflicts with the Microsoft.SharePoint assemblies (that are built on 3.5 and as 64bit.) :)

Thursday, 24 June 2010

Importing SharePoint 2007 list templates (STP) into SharePoint 2010

Damn, only my second post and I'm already covering *shiver* SharePoint.

Anyway, I've been working on migrating an existing WSS 3.0 customisation to a new SPF 2010 environment today. Most of the stuff was pretty straightforward (some webparts, webservices, a few event handlers, ...), but there was one thing that didn't work out as planned.

My predecessor relied on list templates stored in STP files to deploy the custom list templates and create some lists instances based on them.

(This deployment involved manual uploading & creating of lists, maybe I'll do a quick overview on how you can automate this in the coming days... pretty easy stuff tho).


Now, while STP site templates are no longer supported, STP list templates
should still work in SharePoint 2010. Unfortunately things were a bit trickier than I expected them to be.

As a test I started with uploading one of the original STP's to the template catalog (
_catalogs/lt). No problem so far, but when I tried to create a list based off that template I got the following error:
Error
Microsoft SharePoint Foundation version 3 templates are not supported in this version of the product
Correlation ID: {random guid}
Date and Time: ....

Dang, that didn't look to good. Googling around didn't help either, as they all suggested on installing the original solution in a 2007 environment, upgrading the environment to 2010 & then exporting the list again as a template.

As this wasn't an option, I decided to delve deeper into the STP file. As you might know, these STP files are just renamed CAB files. This means you can safely rename them again to .CAB and extract their contents ... which I did.

Doing so revealed that my STP/CAB file only contained a single file: manifest.xml. This file contains the definition of your list: its fields, contenttypes, views, ...
To have some data to compare to, I created a new, custom list in SPF2010 and exported it as an STP template. Once again I renamed the file to .CAB and viewed its contents ... and there was much rejoicing.

It turned out that the SPF2010 STP had the same content as the WSS 3.0 STP: just a single manifest.xml. A quick diff between the two
manifest.xml files revealed that they're both quite similar and that there's a ProductVersion element in both files.
In the WSS 3.0 version, this element has a value of 3 ... but in the SPF2010 version, it has a value of 4. Could it be solved this easily?

Guess it was. I changed the value of the
ProductVersion element in the original manifest.xml to 4 and repackaged it into a CAB. Renamed the CAB to STP and uploaded it again in SPF2010 and behold ... no more errors when creating lists from that template!

Summary
So if you need to migrate list templates STP's from SharePoint 2007 to 2010, you could try the following:

  1. Rename the original .STP to .CAB
  2. Extract its manifest.xml to a local folder (lets call it {workingfolder})
  3. Search for the ProductVersion element. This should have a value of 3
  4. Change its value to 4
  5. Repackage the manifest.xml into a .CAB. I've done this by using makecab.exe in the C:\Windows\System32 folder
    Syntax
    : makecab.exe {workingfolder}\manifest.xml {workingfolder}\{template-name}.cab
  6. Change the generated cabinet's extension from .CAB back to .STP and upload it into the _catalogs/lt
Update
For those looking to migrate a 2007 doc lib with content: apparently a fellow SharePoint-victim has written a powershell script for this - http://www.heyweb.net/2011/06/converting-sharepoint-2007-document-library-templates-for-sharepoint-2010/
Kudos to Peter! :)