Content Migration - Export and Import
- Tim Wheeler
Script
Name | Description | Last Modified Date | Download |
---|---|---|---|
WebExport.ps1 | Exports existing content. | 31/01/2012 | |
WebImport.ps1 | Imports content. Creates sub webs. Fixes taxonomy field ids. | 31/01/2012 | WebImport.ps1 |
This script has not been tested with SharePoint 2013
WebExport.ps1
This script will enumerate all the sub webs within a site collection. It will export individual libaries into seperate .cmp export files. It will also generate a manifest.xml file which contains all required information to import into another environment using the WebImport.ps1.
Script
#Script: WebExport.ps1 #Source: https://codemonkeysoftware.atlassian.net param ( $SiteCollectionUrl = $(throw "Please specify parameter SiteCollectionUrl"), $exportFolder = "C:\ContentExport", $RootWebLibraries = "PublishingImages,Documents,SiteCollectionDocuments,SiteCollectionImages,Pages", $SubWebLibraries = "Calendar,Documents,Images,Pages", $deleteExportFolder = $true ) if($RootWebLibraries -ne $null) { $RootWebItemsToExport = $RootWebLibraries.Split(',') } if($SubWebLibraries -ne $null) { $SubWebItemsToExport = $SubWebLibraries.Split(',') } #region IO Functions function Get-ScriptDirectory { $Invocation = (Get-Variable MyInvocation -Scope 1).Value Split-Path $Invocation.MyCommand.Path } function Set-Directory($location) { #[Environment]::CurrentDirectory = Get-ScriptDirectory [IO.Directory]::SetCurrentDirectory((Convert-Path($location))) } #endregion #region SharePoint Snappin Setup cls $snapin="Microsoft.SharePoint.PowerShell" if (get-pssnapin $snapin -ea "silentlycontinue") { write-host -f Green "PSsnapin $snapin is loaded" } else { if (get-pssnapin $snapin -registered -ea "silentlycontinue") { write-host -f Green "PSsnapin $snapin is registered" Add-PSSnapin $snapin write-host -f Green "PSsnapin $snapin is loaded" } else { write-host -f Red "PSSnapin $snapin not found" } } #endregion #region Host Feedback Helpers $logBuffer = New-Object System.Text.StringBuilder function WriteToLog($msg, $msg2) { [Void] $logBuffer.AppendLine(($msg + " " + $msg2)) } function WriteErrorToLog($msg, $msg2) { [Void] $logBuffer.AppendLine() [Void] $logBuffer.AppendLine("ERROR:") [Void] $logBuffer.AppendLine(($msg + " " + $msg2)) [Void] $logBuffer.AppendLine() } function WriteSuccess($msg, $msg2) { Write-Host -ForegroundColor Green $msg $msg2 WriteToLog $msg $msg2 } function WriteFailure($msg, $msg2) { Write-Host -ForegroundColor Red "ERROR: " $msg $msg2 WriteErrorToLog ($msg, $msg2) } function WriteInfo($msg, $msg2) { Write-Host " -" $msg $msg2 WriteToLog $msg $msg2 } function WriteWarning($msg, $msg2) { Write-Warning (" -" + $msg + " " + $msg2) } function WriteInfoNoLine($msg, $msg2) { Write-Host " -" $msg $msg2 -NoNewline WriteToLog $msg $msg2 } function Verify-Admin { ## Check if the script is running under elevated administrator privileges $CurrentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() $CurrentPrincipal = New-Object System.Security.Principal.WindowsPrincipal($CurrentIdentity) if (!$CurrentPrincipal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Host -ForegroundColor Red "- This script should be executed under elevated administrative privileges." Write-Host -ForegroundColor Red "- Use ""Run as Administrator"" to execute PowerShell before running this script." #Read-Host "Press [Enter] key to exit" break } } Verify-Admin #endregion function AddNode([xml] $manifest, $parent, $name, $value) { $node = $manifest.CreateElement($name) $node.InnerText = $value return $parent.AppendChild($node) } function AddAttribute([xml] $manifest, $parent, $name, $value) { $node = $manifest.CreateAttribute($name) $node.Value = $value $parent.Attributes.Append($node) | Out-Null } function GetList($web, $listUrl) { try { $list = $null $list = $web.GetList($listUrl) if($list -ne $null) {return $list} } catch{ } try { $list = $null $list = $web.Lists.TryGetList($listUrl.Replace("/","")) if($list -ne $null) {return $list} } catch{ } return $null } function ExportWeb($web, $items, $exportFolder, [xml] $manifest, $manifestParent) { Write-Host "Processing Web: " $web.Url foreach($item in $items) { if([string]::IsNullOrEmpty($item)) { continue } $exportFileName = "{0}.{1}.{2}.cmp" -f $exportSetName, $web.Id, $item $exportFileName = $exportFileName.Replace("/","") $exportFullPath = "{0}\{1}" -f $exportFolder, $exportFileName try { $list = GetList $web $item # if($list -eq $null) # { # Write-Host ("No file {1} at {0}" -f $web.Url, $item) # } if($list -ne $null) { if($web.ServerRelativeUrl.EndsWith("/")) { $url = "{0}{1}" -f $web.ServerRelativeUrl, $list.RootFolder } else { $url = "{0}/{1}" -f $web.ServerRelativeUrl, $list.RootFolder } Write-Host "Exporting Item: $url" $web | Export-SPWeb -ItemUrl $url -Path $exportFullPath -HaltOnError -IncludeUserSecurity:$false -IncludeVersions:CurrentVersion -Force if($?) { WriteSuccess "Created content package $exportFileName" $node = AddNode $manifest $manifestParent "ContentFile" $exportFileName AddAttribute $manifest $node "Url" $web.ServerRelativeUrl AddAttribute $manifest $node "Title" $web.Title AddAttribute $manifest $node "WebTemplate" $web.WebTemplate AddAttribute $manifest $node "Configuration" $web.Configuration AddAttribute $manifest $node "Description" $web.Description } } } catch { Write-Warning $_ } } } function Export($siteCol, $exportSetName, $exportFolder, $RootWebItemsToExport, $SubWebItemsToExport) { if(![System.IO.Directory]::Exists($exportFolder)) { [System.IO.Directory]::CreateDirectory($exportFolder) } [Environment]::CurrentDirectory = $exportFolder Set-Directory $exportFolder [xml] $manifest = New-Object xml $rootNode = $manifest.CreateNode([System.Xml.XmlNodeType]::Element, "Content", "") $manifest.AppendChild($rootNode) | Out-Null if($siteCol -eq $null) { throw "Unable to locat site collection $SiteToExportUrl" } foreach($web in $siteCol.AllWebs) { try { if($web.IsRootWeb) { ExportWeb $web $RootWebItemsToExport $exportFolder $manifest $rootNode } else { ExportWeb $web $SubWebItemsToExport $exportFolder $manifest $rootNode } } finally { Invoke-Command -ScriptBlock { $web.Dispose() } -ErrorAction SilentlyContinue } } #Save data $manifestFileName = "{0}\{1}.xml" -f $exportFolder, $exportSetName $manifest.Save($manifestFileName) WriteSuccess "Manifest file written to $manifestFileName" } if($deleteExportFolder) { #Delete existing items remove-item -path "C:\ContentExport\*.*" -Force -ErrorAction:SilentlyContinue } $siteCol = get-spsite -Limit All | where-object {$_.Url -ieq $SiteCollectionUrl} if($siteCol -ne $null) { Export $siteCol $siteCol.RootWeb.Title $exportFolder $RootWebItemsToExport $SubWebItemsToExport remove-item -path "C:\ContentExport\*.log" -Force } else { throw 'Unable to locate site ' + $SiteCollectionUrl }
Usage
.\WebExport.ps1 http://myportal.url C:\ContentExport #Exports using defaults #or .\WebExport.ps1 http://myportal.url C:\ContentExport "SiteCollectionDocuments,SiteCollectionImages" "Documents" #Exports 2 libraries from the rootweb and 1 library from each sub web if they exist
WebImport.ps1
This script reads in an xml file that is generated by the WebExport.ps1 script. This manifest contains information about all the packages to import and some values about each SPWeb. If the SPWeb has not yet been created in the destination site collection this script will create it prior to importing the individual libraries.
This script will also clean up taxonomy fields that have been migrated from another environment.
For this to work you must have valid content types in the site collection. (The values from the content type fields will be used to refresh the list instance fields)
Script
#Script: WebImport.ps1 #Source: https://codemonkeysoftware.atlassian.net #notes: Run this script in Administrator mode. # This script will import content packages gerneated by WebExport.ps1 # Example invocation: http://portal.com "My Portal.xml" C:\ContentExport param ( [string]$destinationSiteUrl = $(Read-Host -Prompt "Please enter the URL of the destination site eg; http://myserver"), [string]$importManifest = $(Read-Host -Prompt "Please enter the content set name (The content manifest xml file) eg; MySet.xml"), [string]$importFolder = "C:\ContentExport" ) $contentSetName = $importManifest #region IO Functions function Get-ScriptDirectory { $Invocation = (Get-Variable MyInvocation -Scope 1).Value Split-Path $Invocation.MyCommand.Path } function Set-Directory($location) { #[Environment]::CurrentDirectory = Get-ScriptDirectory [IO.Directory]::SetCurrentDirectory((Convert-Path($location))) } #endregion #region SharePoint Snappin Setup $snapin="Microsoft.SharePoint.PowerShell" if (get-pssnapin $snapin -ea "silentlycontinue") { write-host -f Green "PSsnapin $snapin is loaded" } else { if (get-pssnapin $snapin -registered -ea "silentlycontinue") { write-host -f Green "PSsnapin $snapin is registered" Add-PSSnapin $snapin write-host -f Green "PSsnapin $snapin is loaded" } else { write-host -f Red "PSSnapin $snapin not found" } } #endregion #region Host Feedback Helpers function WriteSuccess($msg, $msg2) { Write-Host -ForegroundColor Green $msg $msg2 } function WriteFailure($msg, $msg2) { Write-Host -ForegroundColor Red "ERROR: " $msg $msg2 } function WriteInfo($msg, $msg2) { Write-Host " -" $msg $msg2 } function WriteWarning($msg, $msg2) { Write-Warning (" -" + $msg + " " + $msg2) } function WriteInfoNoLine($msg, $msg2) { Write-Host " -" $msg $msg2 -NoNewline } #endregion #region Xml Content Manifest function LoadConfiguration($configFile) { if(![IO.File]::Exists($configFile)) { throw "Could not find configuration file: $configFile. Script Terminated." } WriteInfo "Found configuration file: $configFile" try { $config = [xml] (get-content -Path $configFile) WriteSuccess "Content Manifest Loaded Successfully $configFile" return $config.Content } catch { WriteFailure "Manifest file found but could not be loaded. May not be valid XML" throw } } #endregion #region Data Clean Up # When content in imported into a new environment the Term Store ID is kept on the #taxonomy fields. This causes a problem as the new term store has a different id. #These functions clean up the taxonomy list which has bad entries following an import #and modify all the items and resets the correct values. function CleanTaxonomyList($site) { Write-Host "Cleaning /Lists/TaxonomyHiddenList" $web = $site.RootWeb $taxlist = $web.GetList("Lists/TaxonomyHiddenList") for($count = $taxlist.Items.Count - 1; $count -ge 0; $count--) { $item = $taxlist.Items[$count] if([string]::IsNullOrEmpty($item.Title)) { $item.Delete() if($?) { #Write-Host "Deleted Item with bad title" } else { Write-Host -ForegroundColor Red "Failed to deleted Item with bad title" } } } } #This function will reset the term store id if it is wrong, and will fix the WssId. #If the correct term cannot be located, the default one will be used. function ResetTaxonomyDefaults($site, [Microsoft.SharePoint.Publishing.PublishingWeb] $web) { $txs = New-Object "Microsoft.SharePoint.Taxonomy.TaxonomySession" -ArgumentList $site $pages = $web.GetPublishingPages() foreach($page in $pages) { Write-Host -ForegroundColor Cyan "Checking publishing page " $page.Title if($page.ListItem.File.CheckOutStatus -ne "None") { $page.CheckIn("Checked in by data clean process"); } $page.CheckOut(); foreach ($field in $page.ListItem.Fields) { if($field.GetType().Name -eq "TaxonomyField") { $taxField = [Microsoft.SharePoint.Taxonomy.TaxonomyField] $field #Write-Host "Found field to update:" $taxField.Title $currentValue = $page.ListItem.Properties[$taxField.InternalName] #Write-Host "Current Value is" $currentValue $templateField = $page.ListItem.ParentList.ParentWeb.Site.RootWeb.Fields[$field.Id] $defaultValue = $templateField.DefaultValue $termStore = $txs.TermStores[$templateField.SspId] $termSet = $termStore.GetTermSet($templateField.TermSetId) if($taxField.SspId -ne $templateField.SspId) { # Write-Host "TaxField SspId is not correct, updating" $taxField.SspId = $templateField.SspId $taxField.Update() } if($taxField.TermSetId -ne $templateField.TermSetId) { # Write-Host "TaxField TermSetId is not correct, updating" $taxField.TermSetId = $templateField.TermSetId $taxField.Update() } $fieldValue = $templateField.GetFieldValue($currentValue) if(($fieldValue.GetType().Name -eq "TaxonomyFieldValue") -and ($fieldValue -eq $null -or [string]::IsNullOrEmpty($fieldValue.TermGuid))) { $fieldValue = $templateField.GetFieldValue($defaultValue) } if(($fieldValue.GetType().Name -ne "TaxonomyFieldValue") -and ($fieldValue[0] -eq $null -or [string]::IsNullOrEmpty($fieldValue[0].TermGuid))) { $fieldValue = $templateField.GetFieldValue($defaultValue) } if($fieldValue.GetType().Name -eq "TaxonomyFieldValue") { try { $term = $termSet.GetTerm($fieldValue.TermGuid) } catch { Write-Host -red ("Failed to update field {0} for page {1} in web {2}" -f $taxField.InternalName, $page.Title, $web.Url) } } else { try { $term = $termSet.GetTerm($fieldValue[0].TermGuid) } catch { Write-Host -red ("Failed to update field {0} for page {1} in web {2}" -f $taxField.InternalName, $page.Title, $web.Url) } } $taxField.SetFieldValue($page.ListItem, $term) } } $page.ListItem.Update() $page.CheckIn("Data clean process") } } function CleanSiteCollection ($siteColUrl) { [Microsoft.SharePoint.SPSite] $site = get-spsite -Limit ALL | where-object {$_.Url -ieq $siteColUrl} CleanTaxonomyList $site $site | Get-SPWeb -limit all | ForEach-Object { #Check to see if site is a publishing site if ([Microsoft.SharePoint.Publishing.PublishingWeb]::IsPublishingWeb($_)) { Write-Host "Cleaning pages in `"$($_.Title)`" site." #Get the Publishing Web and pages within it $publishingWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($_) ResetTaxonomyDefaults $site $publishingWeb } $_.Dispose() } $site.Dispose() } #endregion function Import($siteColUrl, $manifest, $importFolder) { $siteCol = get-spsite -Limit ALL | where-object {$_.Url -ieq $siteColUrl} if($siteCol -eq $null) { throw "Unable to locate " + $siteColUrl } $manifest = LoadConfiguration ($importFolder + "\" + $manifest) $importFiles = $manifest.ContentFile foreach($node in $importFiles) { $filename = ($importFolder + "\" + $node.InnerText) $url = $node.Url $fullUrl = ($siteCol.Url + $url) $web = $siteCol | Get-SPWeb -Limit ALL | Where-Object {$_.ServerRelativeUrl -ieq $url} if($web -eq $null) { WriteInfo "Creating new web at url $url" try { $template = Get-SPWebTemplate ($node.WebTemplate + "#" + $node.Configuration) if($template -eq $null) { throw "Template " + ($node.WebTemplate + "#" + $node.Configuration) + " does not exist in this farm" } $web = New-SPWeb -Template $template -Url $fullUrl -Name $node.Title -Description $node.Description } catch { WriteFailure "Failed to import content at location $url - " $_ continue } WriteInfo "New web created at url $url" } try { $web | Import-SPWeb -Path $filename -Force -ActivateSolutions:$false -IncludeUserSecurity:$true -UpdateVersions:Overwrite WriteSuccess "Imported $filename to " $web.Url } catch { WriteFailure "Error importing file $filename to web $web for site $siteCol" $_ } finally { $web.Dispose() } } } Import $destinationSiteUrl $contentSetName $importFolder CleanSiteCollection $destinationSiteUrl
Usage
.\WebImport.ps1 http://destinationportal "Your Manifest.xml" c:\contentexport #reads the manfiest from the contentexport folder and imports the files to the destination site collection
CodeMonkey Software is a division of JCHMedia www.jchmedia.com