Sunday, September 23, 2012

Importing a SharePoint 2010 .CMP file and the “String was not recognized as a valid DateTime” Error

There are plenty of excellent posts out there on how to do a SharePoint 2010 granular backup and restore operation on a document library using PowerShell. A good overview is provided in this blog post on MSDN: SharePoint 2010 Granular Backup-Restore Part 1. I use this process to deploy a set of custom dashboards that I’ve created.

I've put together a couple of PowerShell functions to export (i.e. backup) and import (i.e. restore) a .CMP file (again, plenty of great examples out there), and they work great. However, I ran into a problem when importing one of my document libraries, which lead me to discover an error, similar to the following, in the corresponding log file:

[9/19/2012 1:21:50 PM] [ListItem] [MyDashboard.aspx] Progress: Importing
[9/19/2012 1:21:50 PM] [ListItem] [MyDashboard.aspx] Verbose: List URL: /aSiteCollection/aSite/DashboardPages
[9/19/2012 1:21:50 PM] [ListItem] [MyDashboard.aspx] Error: String was not recognized as a valid DateTime.
[9/19/2012 1:21:50 PM] [ListItem] [MyDashboard.aspx] Debug: at System.DateTimeParse.Parse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles)
   at System.String.System.IConvertible.ToDateTime(IFormatProvider provider)
   at ...

After a little Googling, I discovered this forum post on TechNet which identified the problem and a workaround: String was not recognized as a valid DateTime on import of site

The problem turns out to be in the Manifest.xml file located within the .CMP. The actual line in the .XML causing the problem contains an erroneous year (i.e. 60354):

<Property Name="vti_syncwith_smartbidev\:80/ymus" Type="Time" Value="29 May 60354 05:36:10 -0000" Access="ReadWrite"/>

And now for the purpose of this post: the following PowerShell script basically renames the .CMP file to a .CAB, extracts the files into a folder, searches and replaces the erroneous date in the Manifest.xml and imports it using import-spweb with the NoFileCompression switch.
function ImportCAB {
        [Parameter(mandatory=$true)] [string]$webUrl,
        [Parameter(mandatory=$true)] [string]$fileName

        $cabFile = $fileName.Replace(".cmp", ".cab")

        if(!(test-path $fileName)) 
            write-host "$fileName does not exist. Exiting..."

        write-host "Copying $fileName to CAB file"
        copy-item $fileName $cabFile
        write-host "Copied $fileName to CAB file"

        $cabFolder = "$(split-path $fileName -Parent)\$((Get-Item -path $fileName).basename)"
        if(test-path $cabFolder) { remove-item $cabFolder -recurse }
        New-Item -ItemType directory -Path $cabFolder
        # Creating CAB Files with Windows PowerShell
        $comObject = "Shell.Application" 
        write-host "Creating $comObject" 
        $shell = New-Object -Comobject $comObject 
        if(!$?) { $(Throw "unable to create $comObject object")} 
        write-host "Creating CAB object for $cabFile"
        $sourceCab = $shell.Namespace($cabFile).items()
        write-host "Creating destination folder object for $cabFolder" 
        $DestinationFolder = $shell.Namespace($cabFolder)
        write-host "Expanding $cabFile to $cabFolder" 
        # Search Manifest.xml for the following and replace "60354" with a valid year
        $findText = "Value=`"29 May 60354 05:36:10 -0000`""
        $replaceText = "Value=`"29 May 2012 05:36:10 -0000`""
        (Get-Content "$($cabFolder)\Manifest.xml") | 
            Foreach-Object {$_ -replace $findText, $replaceText} | 
            Set-Content "$($cabFolder)\Manifest.xml" -encoding UTF8

        import-spweb -identity $webUrl -path $cabFolder -force -UpdateVersions 2 -nofilecompression #-whatif
    catch [Exception]
        write-host $_.Exception.ToString() -ForegroundColor Green

This works rather well, but does not address the real issue of how to fix the source so that the export produces a valid .CMP file in the first place. In theory, I can import this back into my development machine and fix the issue (try at your own risk).