Monday, September 16, 2013

Fixing missing Hidden and Sealed attributes in Site columns

After a recent upgrade to sharepoint 2010, we were no longer able to create new site templates. Creating a template would fail with an error:

Unexpected System.InvalidOperationException: Error copying temporary solution file to solutions gallery: _catalogs/solutions/TestProduction.wsp at Microsoft.SharePoint.SPSolutionExporter.ExportWebToGallery

Digging a little deeper into the ULS logs, I was able to find a better explaination of the error:

SharePoint Foundation General 9fjj Monitorable SPSolutionExporter: Microsoft.SharePoint.SPException: Feature definition with Id 10a563f6-0afb-4d38-9ddc-241c61694ac6 failed validation,
file 'TestProductionListInstances\ElementsFields.xml', line 39, character 167:
The 'Hidden' attribute is invalid - The value '' is invalid according to its datatype 'http://schemas.microsoft.com/sharepoint/:TRUEFALSE' - The Enumeration constraint failed. at Microsoft.SharePoint.Administration.SPSolutionPackage.SolutionFile.FeatureXmlValidationCallBack

Something was wrong with our columns. The "Hidden" parameter was blank instead of true or false. Looking into the database confirmed the error. The xml schema of the column definition had : Hidden="" instead of Hidden="FALSE". I also noticed that "Sealed" was also blank, and would cause the same error when trying to save the site as a template. The error describes the elementfields.xml file. This file is part of the site template WSP file. To view the file, go to the solutions gallery and download the Site template WSP that gets created. Change the extension to a CAB and open it. You will find the ElementFields.xml file and can use the line and character from the ULS log error to find the field that causes the problem. In my case, it was about 40 differnet fields.

These were errors on the site columns, many but not all of them being custom columns we created. Sealed and Hidden are not things that are easily changed through the UI, so I had to use Powershell to update the columns. I probably could have used SP Manager too, but since there were 40+ columns duplicated over a few different site collections, it was easier to use powershell.

Updating the Sealed column is fairly easiy since it is a property on a column that can be set directly. When trying to change the 'Hidden' property, I would get the following error: PS C:\> $f = $web.Fields["Meeting Location"]
PS C:\> $f.Hidden = $false
Exception setting "Hidden": "Cannot change Hidden attribute for this field"
At line:1 char:4
+ $f. <<<< Hidden = $false
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException

So, if I can't change the field, how do I change it?. The answer is to change the xml schema. sharepoint will let you change the schema directly and skip around whatever check it has in place.


$field = $Web.fields[$i]
[XML]$schema = $field.schemaxml
$field.schemaxml = $schema.Innerxml
$field.update()

The next problem, how do you know what fields need to be changed? If you just check for $field.hidden -eq "" or something like that it will not work. It will tell you that the field is "FALSE" even when it is blank! So, we have to look at the xml:
[XML]$schema = $field.schemaxml
if ( $schema.InnerXML.Contains("Hidden=""""")){
do something}

All together the script looks like this:

$site = Get-SPSite "http://YourSiteCollectionURL"
$Web =$site.RootWeb

for($i = $Web.Fields.count -1; $i -ge 0; $i--)
{
$field = $Web.fields[$i]
[XML]$schema = $field.schemaxml
if ( $schema.InnerXML.Contains("Hidden=""""")){
$schema.field.Hidden = "FALSE"
$field.schemaxml = $schema.Innerxml
Write-Host $field "HIDDEN: " $schema.field.Hidden
$field.update()
}
if ( $schema.InnerXML.Contains("Sealed=""""")){
Write-Host $field "Sealed: " $schema.field.Sealed
$field.Sealed = "FALSE"
$field.update()
}
if ( $schema.InnerXML.Contains("Required=""""")){
Write-Host $field "Required: " $schema.field.Sealed
$field.Required = "FALSE"
$field.update()
}
}


This script will set all the hidden and sealed fields to FALSE for the site columns that have the field as blank. Another way to do this is to modifiy the database directly, but that isn't supported and you can easily break things.

I hope this can help someone else who runs into the same problem. I wasn't able to find a good solution on the web. MSFT support(wipro) did help, but it took them almost 2 months, and 3 different support techs to come up with a script that I then had to extensivly modify to get to actually solve the problem.

5 comments:

Leigh Webber said...

As written, the Sealed and Required attributes don't get handled via xml, like the Hidden attribute does. Also, you have to get the field from the site columns collection fresh each time, or you get an error "The node to be inserted is from a different document context."

Here's a corrected version:

# SiteColumnRepairHiddenSealed
# This script finds and fixes custom site columns whose Hidden or Sealed attribute is set to an empty string ("").
# This violates the xml schema rules, and causes an error when you try to create a site template that uses the columns.

clear-host
Add-PSSnapin Microsoft.SharePoint.PowerShell -erroraction SilentlyContinue
if($env:COMPUTERNAME -eq "ProdServerName") {
$siteUrl = "https://ProdSiteCollection";
} elseif ($env:COMPUTERNAME -eq "DevServerName") {
$siteUrl = "https://DevSiteCollection";
}

$site = Get-SPSite $siteUrl
$Web =$site.RootWeb

for($i = $Web.Fields.count -1; $i -ge 0; $i--){
# Get the field from the site columns
$field = $Web.fields[$i]
[XML]$schema = $field.schemaxml
if ( $schema.InnerXML.Contains("Hidden=""""")){
$schema.field.Hidden = "FALSE"
$field.schemaxml = $schema.Innerxml
Write-Host $field "HIDDEN: " $schema.field.Hidden
$field.update()
}
# Get the field from the site columns again - otherwise error: "The node to be inserted is from a different document context"
$field = $Web.fields[$i]
[XML]$schema = $field.schemaxml
if ( $schema.InnerXML.Contains("Sealed=""""")){
write-host $field.InternalName
$schema.field.Sealed = "FALSE"
$field.schemaxml = $schema.Innerxml
Write-Host $field "Sealed: " $schema.field.Sealed
$field.update()
}
# Get the field from the site columns again
$field = $Web.fields[$i]
[XML]$schema = $field.schemaxml
if ( $schema.InnerXML.Contains("Required=""""")){
write-host $field.InternalName
$schema.field.Required = "FALSE"
$field.schemaxml = $schema.Innerxml
Write-Host $field "Required: " $schema.field.Sealed
$field.update()
}
}
$web.dispose()
$site.dispose()

Jeff Lester said...

Ah thanks,
My script was the result of building up a few different peices. I didn't realize running it all at once would be an issue.

Deepak Koduri said...

Great Post. Saved my Day .

Thanks a lot Jeff Lester ... :)

Jade Graham said...

Thanks for taking the time to discuss this, I feel strongly about it and love learning more on this topic. SharePoint Intranet

mohit arora said...

script worked as it is expected, thanks for the help.
it resolved my problem and fixed the save site as template issue.