Files
Start-SPMigration/Start-SPMigration.ps1

1638 lines
47 KiB
PowerShell

[CmdletBinding()]
param(
[string]$WebUrl,
[Parameter(Mandatory = $true)]
[string]$OutputPath,
[switch]$IncludeHiddenLibraries,
[switch]$IncludeHiddenLists,
[string]$TargetWebUrl,
[string]$MappingCsvPath,
[switch]$ImportFiles,
[switch]$ImportLists,
[switch]$OverwriteFiles,
[switch]$ExportOnly,
[switch]$ImportOnly
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Initialize-SharePointPowerShell {
Import-Module SharePointServer
}
function Ensure-Directory {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not [System.IO.Directory]::Exists($Path)) {
[System.IO.Directory]::CreateDirectory($Path) | Out-Null
}
}
function Invoke-MigrationImport {
param(
[Parameter(Mandatory = $true)]
[string]$InputPath,
[Parameter(Mandatory = $true)]
[string]$TargetWebUrl,
[Parameter(Mandatory = $true)]
[string]$MappingCsvPath,
[switch]$ImportFiles,
[switch]$ImportLists,
[switch]$OverwriteFiles
)
$resolvedInputPath = [System.IO.Path]::GetFullPath($InputPath)
$filesRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Files")
$listsRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Lists")
$manifestPath = [System.IO.Path]::Combine($resolvedInputPath, "manifest.csv")
$fieldMapping = Get-FieldMapping -Path $MappingCsvPath
$containerManifestMap = Get-ContainerManifestMap -ManifestRows (Read-ManifestRows -ManifestPath $manifestPath)
if ($fieldMapping.Count -eq 0) {
throw "Die Mapping-CSV enthaelt keine gueltigen Zuordnungen."
}
$shouldImportFiles = $ImportFiles.IsPresent
$shouldImportLists = $ImportLists.IsPresent
if (-not $shouldImportFiles -and -not $shouldImportLists) {
$shouldImportFiles = $true
$shouldImportLists = $true
}
$targetWeb = $null
try {
$targetWeb = Get-SPWeb -Identity $TargetWebUrl -ErrorAction Stop
if ($shouldImportFiles) {
Import-SPDocumentLibraries -Web $targetWeb -FilesRootPath $filesRootPath -FieldMapping $fieldMapping -ContainerManifestMap $containerManifestMap -OverwriteFiles:$OverwriteFiles
}
if ($shouldImportLists) {
Import-SPLists -Web $targetWeb -ListsRootPath $listsRootPath -FieldMapping $fieldMapping -ContainerManifestMap $containerManifestMap
}
Write-Host ("Import abgeschlossen. Quelle: {0}" -f $resolvedInputPath)
}
finally {
if ($null -ne $targetWeb) {
$targetWeb.Dispose()
}
}
}
function Get-SafePathSegment {
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
$invalidChars = [System.IO.Path]::GetInvalidFileNameChars()
$safeName = $Name
foreach ($char in $invalidChars) {
$safeName = $safeName.Replace($char, "_")
}
$safeName = $safeName.Trim()
if ([string]::IsNullOrWhiteSpace($safeName)) {
return "_"
}
return $safeName
}
function Test-IsCatalogList {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List
)
try {
return [bool]$List.IsCatalog
}
catch {
return $false
}
}
function Get-SafeCollectionCount {
param(
$Value
)
if ($null -eq $Value) {
return 0
}
try {
return [int]$Value.Count
}
catch {
}
try {
return [int]$Value.Length
}
catch {
}
return @($Value).Length
}
function Combine-PathSegments {
param(
[Parameter(Mandatory = $true)]
[string]$BasePath,
[string[]]$Segments = @()
)
$combinedPath = $BasePath
foreach ($segment in $Segments) {
if (-not [string]::IsNullOrWhiteSpace($segment)) {
$combinedPath = [System.IO.Path]::Combine($combinedPath, $segment)
}
}
return $combinedPath
}
function Convert-SPFieldValue {
param(
[Parameter(ValueFromPipeline = $true)]
$Value
)
if ($null -eq $Value) {
return $null
}
if ($Value -is [System.DateTime]) {
return $Value.ToString("o")
}
if ($Value -is [Microsoft.SharePoint.SPFieldUserValue]) {
return @{
LookupId = $Value.LookupId
LookupValue = $Value.LookupValue
Email = $Value.User.Email
LoginName = $Value.User.LoginName
}
}
if ($Value -is [Microsoft.SharePoint.SPFieldUserValueCollection]) {
return @($Value | ForEach-Object { Convert-SPFieldValue $_ })
}
if ($Value -is [Microsoft.SharePoint.SPFieldLookupValue]) {
return @{
LookupId = $Value.LookupId
LookupValue = $Value.LookupValue
}
}
if ($Value -is [Microsoft.SharePoint.SPFieldLookupValueCollection]) {
return @($Value | ForEach-Object { Convert-SPFieldValue $_ })
}
if ($Value -is [Microsoft.SharePoint.SPFieldUrlValue]) {
return @{
Url = $Value.Url
Description = $Value.Description
}
}
if ($Value -is [System.Guid]) {
return $Value.ToString()
}
if ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])) {
return @($Value | ForEach-Object { Convert-SPFieldValue $_ })
}
$typeName = $Value.GetType().FullName
if ($typeName -like "*TaxonomyFieldValue") {
return @{
Label = $Value.Label
TermGuid = $Value.TermGuid
WssId = $Value.WssId
}
}
if ($typeName -like "*TaxonomyFieldValueCollection") {
return @($Value | ForEach-Object {
@{
Label = $_.Label
TermGuid = $_.TermGuid
WssId = $_.WssId
}
})
}
return $Value
}
function Get-FieldTextValue {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPField]$Field,
$RawValue
)
try {
return $Field.GetFieldValueAsText($RawValue)
}
catch {
return $null
}
}
function Get-ItemFieldTextValue {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item,
[Parameter(Mandatory = $true)]
[string]$InternalName
)
$field = $null
try {
$field = $Item.Fields.GetFieldByInternalName($InternalName)
}
catch {
$field = $null
}
if ($null -eq $field) {
try {
$field = $Item.Fields.GetFieldByStaticName($InternalName)
}
catch {
$field = $null
}
}
if ($null -eq $field) {
return $null
}
$rawValue = $null
try {
$rawValue = $Item[$field.InternalName]
}
catch {
try {
$rawValue = $Item[$field.Id]
}
catch {
$rawValue = $null
}
}
return Get-FieldTextValue -Field $field -RawValue $rawValue
}
function Get-ListItemFieldMetadata {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item
)
$fieldMetadata = @()
foreach ($field in $Item.Fields) {
$rawValue = $null
try {
$rawValue = $Item[$field.InternalName]
}
catch {
try {
$rawValue = $Item[$field.Id]
}
catch {
$rawValue = $null
}
}
$fieldMetadata += [PSCustomObject]@{
InternalName = $field.InternalName
TypeAsString = $field.TypeAsString
TextValue = Get-FieldTextValue -Field $field -RawValue $rawValue
Value = Convert-SPFieldValue $rawValue
}
}
return $fieldMetadata
}
function Get-ListItemFieldValueMap {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item,
[switch]$AsText
)
$fieldValues = [ordered]@{}
foreach ($field in $Item.Fields) {
$rawValue = $null
try {
$rawValue = $Item[$field.InternalName]
}
catch {
try {
$rawValue = $Item[$field.Id]
}
catch {
$rawValue = $null
}
}
if ($AsText) {
$fieldValues[$field.InternalName] = Get-FieldTextValue -Field $field -RawValue $rawValue
}
else {
$fieldValues[$field.InternalName] = Convert-SPFieldValue $rawValue
}
}
return [PSCustomObject]$fieldValues
}
function Get-ListItemAttachmentMetadata {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item
)
$attachments = @()
$attachmentCollection = $Item.Attachments
if ((Get-SafeCollectionCount $attachmentCollection) -eq 0) {
return $attachments
}
foreach ($attachmentName in @($attachmentCollection)) {
$attachments += [PSCustomObject]@{
FileName = $attachmentName
Url = $attachmentCollection.UrlPrefix + $attachmentName
}
}
return $attachments
}
function Get-ListItemMetadataObject {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item
)
$attachmentCollection = $Item.Attachments
return [PSCustomObject]@{
Id = $Item.ID
UniqueId = $Item.UniqueId.ToString()
Title = if ($Item.Fields.ContainsField("Title")) { $Item["Title"] } else { $null }
ContentTypeId = $Item.ContentTypeId.ToString()
FileSystemObjectType = $Item.FileSystemObjectType.ToString()
DisplayName = $Item.DisplayName
Name = if ($Item.Name) { $Item.Name } else { $null }
Url = if ($Item.Url) { $Item.Url } else { $null }
ServerRelativeUrl = if ($Item.Folder) { $Item.Folder.ServerRelativeUrl } else { $null }
Created = if ($null -ne $Item["Created"]) { ([datetime]$Item["Created"]).ToString("o") } else { $null }
Modified = if ($null -ne $Item["Modified"]) { ([datetime]$Item["Modified"]).ToString("o") } else { $null }
CreatedBy = Get-ItemFieldTextValue -Item $Item -InternalName "Author"
ModifiedBy = Get-ItemFieldTextValue -Item $Item -InternalName "Editor"
HasAttachments = (Get-SafeCollectionCount $attachmentCollection) -gt 0
Attachments = Get-ListItemAttachmentMetadata -Item $Item
FieldValues = Get-ListItemFieldValueMap -Item $Item
FieldTextValues = Get-ListItemFieldValueMap -Item $Item -AsText
Fields = Get-ListItemFieldMetadata -Item $Item
}
}
function Get-FileMetadataObject {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFile]$File
)
$item = $File.Item
return [PSCustomObject]@{
ExportedAtUtc = [System.DateTime]::UtcNow.ToString("o")
Web = [PSCustomObject]@{
Title = $Web.Title
Url = $Web.Url
ServerRelativeUrl = $Web.ServerRelativeUrl
Id = $Web.ID.ToString()
}
Library = [PSCustomObject]@{
Title = $List.Title
Id = $List.ID.ToString()
RootFolderUrl = $List.RootFolder.ServerRelativeUrl
BaseTemplate = [int]$List.BaseTemplate
}
File = [PSCustomObject]@{
Name = $File.Name
Url = $File.Url
ServerRelativeUrl = $File.ServerRelativeUrl
UniqueId = $File.UniqueId.ToString()
Length = $File.Length
TimeCreated = $File.TimeCreated.ToString("o")
TimeLastModified = $File.TimeLastModified.ToString("o")
CheckOutType = $File.CheckOutType.ToString()
Level = $File.Level.ToString()
MajorVersion = $File.MajorVersion
MinorVersion = $File.MinorVersion
}
Item = if ($null -ne $item) {
Get-ListItemMetadataObject -Item $item
}
else {
$null
}
}
}
function Write-MetadataJson {
param(
[Parameter(Mandatory = $true)]
$MetadataObject,
[Parameter(Mandatory = $true)]
[string]$Path
)
$json = $MetadataObject | ConvertTo-Json -Depth 10
[System.IO.File]::WriteAllText($Path, $json, [System.Text.Encoding]::UTF8)
}
function Write-ManifestRow {
param(
[Parameter(Mandatory = $true)]
[pscustomobject]$Row,
[Parameter(Mandatory = $true)]
[string]$ManifestPath
)
$append = [System.IO.File]::Exists($ManifestPath)
$Row | Export-Csv -Path $ManifestPath -NoTypeInformation -Delimiter ";" -Encoding UTF8 -Append:$append
}
function New-ManifestRow {
param(
[string]$RecordType = "",
[string]$ObjectType = "",
[string]$SourceWebUrl = "",
[string]$SourceTitle = "",
[string]$TargetTitle = "",
[string]$BaseType = "",
[string]$BaseTemplate = "",
[string]$RootFolderUrl = "",
[string]$SourceServerRelativeUrl = "",
[string]$FileName = "",
[string]$FileUniqueId = "",
[string]$FileLength = "",
[string]$LocalFilePath = "",
[string]$LocalMetadataPath = "",
[string]$ExportedAtUtc = ""
)
return [PSCustomObject]@{
RecordType = $RecordType
ObjectType = $ObjectType
SourceWebUrl = $SourceWebUrl
SourceTitle = $SourceTitle
TargetTitle = $TargetTitle
BaseType = $BaseType
BaseTemplate = $BaseTemplate
RootFolderUrl = $RootFolderUrl
SourceServerRelativeUrl = $SourceServerRelativeUrl
FileName = $FileName
FileUniqueId = $FileUniqueId
FileLength = $FileLength
LocalFilePath = $LocalFilePath
LocalMetadataPath = $LocalMetadataPath
ExportedAtUtc = $ExportedAtUtc
}
}
function Write-ContainerManifestRow {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List,
[Parameter(Mandatory = $true)]
[string]$ManifestPath
)
$objectType = if ($List.BaseType -eq [Microsoft.SharePoint.SPBaseType]::DocumentLibrary) { "DocumentLibrary" } else { "List" }
$manifestRow = New-ManifestRow `
-RecordType "Container" `
-ObjectType $objectType `
-SourceWebUrl $Web.Url `
-SourceTitle $List.Title `
-TargetTitle "" `
-BaseType $List.BaseType.ToString() `
-BaseTemplate ([string][int]$List.BaseTemplate) `
-RootFolderUrl $List.RootFolder.ServerRelativeUrl `
-SourceServerRelativeUrl $List.RootFolder.ServerRelativeUrl `
-ExportedAtUtc ([System.DateTime]::UtcNow.ToString("o"))
Write-ManifestRow -Row $manifestRow -ManifestPath $ManifestPath
}
function Get-RelativeFilePathSegments {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFile]$File
)
$rootUrl = $List.RootFolder.ServerRelativeUrl.TrimEnd("/")
$fileUrl = $File.ServerRelativeUrl
$relativeUrl = $fileUrl.Substring($rootUrl.Length).TrimStart("/")
return @($relativeUrl.Split("/") | ForEach-Object { Get-SafePathSegment $_ })
}
function Get-SPListItemsRecursive {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List
)
$query = New-Object Microsoft.SharePoint.SPQuery
$query.Query = ""
$query.Folder = $List.RootFolder
$query.ViewAttributes = "Scope='RecursiveAll'"
$query.RowLimit = 0
$query.ViewFieldsOnly = $false
return @($List.GetItems($query))
}
function Export-SPFile {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFile]$File,
[Parameter(Mandatory = $true)]
[string]$FilesRootPath,
[Parameter(Mandatory = $true)]
[string]$ManifestPath
)
$relativeSegments = @(Get-RelativeFilePathSegments -List $List -File $File)
$relativeDirectorySegments = @()
if ($relativeSegments.Length -gt 1) {
$relativeDirectorySegments = $relativeSegments[0..($relativeSegments.Length - 2)]
}
$safeLibraryName = Get-SafePathSegment $List.Title
$fileDirectory = Combine-PathSegments -BasePath ([System.IO.Path]::Combine($FilesRootPath, $safeLibraryName)) -Segments $relativeDirectorySegments
Ensure-Directory -Path $fileDirectory
if ($relativeSegments.Length -eq 0) {
throw ("Konnte keinen relativen Dateipfad fuer '{0}' ermitteln." -f $File.ServerRelativeUrl)
}
$fileName = $relativeSegments[-1]
$localFilePath = [System.IO.Path]::Combine($fileDirectory, $fileName)
$metadataPath = [System.IO.Path]::Combine($fileDirectory, "$fileName.properties.json")
[System.IO.File]::WriteAllBytes($localFilePath, $File.OpenBinary())
$metadata = Get-FileMetadataObject -Web $Web -List $List -File $File
Write-MetadataJson -MetadataObject $metadata -Path $metadataPath
$manifestRow = New-ManifestRow `
-RecordType "File" `
-ObjectType "DocumentLibrary" `
-SourceWebUrl $Web.Url `
-SourceTitle $List.Title `
-TargetTitle "" `
-BaseType $List.BaseType.ToString() `
-BaseTemplate ([string][int]$List.BaseTemplate) `
-RootFolderUrl $List.RootFolder.ServerRelativeUrl `
-SourceServerRelativeUrl $File.ServerRelativeUrl `
-FileName $File.Name `
-FileUniqueId $File.UniqueId.ToString() `
-FileLength ([string]$File.Length) `
-LocalFilePath $localFilePath `
-LocalMetadataPath $metadataPath `
-ExportedAtUtc ([System.DateTime]::UtcNow.ToString("o"))
Write-ManifestRow -Row $manifestRow -ManifestPath $ManifestPath
}
function Export-SPFolder {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFolder]$Folder,
[Parameter(Mandatory = $true)]
[string]$FilesRootPath,
[Parameter(Mandatory = $true)]
[string]$ManifestPath,
[ref]$FileCount
)
foreach ($file in $Folder.Files) {
Export-SPFile -Web $Web -List $List -File $file -FilesRootPath $FilesRootPath -ManifestPath $ManifestPath
$FileCount.Value++
}
foreach ($subFolder in $Folder.SubFolders) {
if ($subFolder.Name -eq "Forms") {
continue
}
Export-SPFolder -Web $Web -List $List -Folder $subFolder -FilesRootPath $FilesRootPath -ManifestPath $ManifestPath -FileCount $FileCount
}
}
function Export-SPDocumentLibrary {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List,
[Parameter(Mandatory = $true)]
[string]$FilesRootPath,
[Parameter(Mandatory = $true)]
[string]$ManifestPath
)
Write-Host ("Exportiere Bibliothek: {0}" -f $List.Title)
Write-ContainerManifestRow -Web $Web -List $List -ManifestPath $ManifestPath
$fileCount = 0
Export-SPFolder -Web $Web -List $List -Folder $List.RootFolder -FilesRootPath $FilesRootPath -ManifestPath $ManifestPath -FileCount ([ref]$fileCount)
Write-Host (" Dateien exportiert: {0}" -f $fileCount)
}
function Export-SPList {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List,
[Parameter(Mandatory = $true)]
[string]$ListsRootPath,
[Parameter(Mandatory = $true)]
[string]$ManifestPath
)
Write-Host ("Exportiere Liste: {0}" -f $List.Title)
Write-ContainerManifestRow -Web $Web -List $List -ManifestPath $ManifestPath
$safeListName = Get-SafePathSegment $List.Title
$listPath = [System.IO.Path]::Combine($ListsRootPath, "$safeListName.json")
$items = @()
foreach ($item in @(Get-SPListItemsRecursive -List $List)) {
$items += Get-ListItemMetadataObject -Item $item
}
$listExport = [PSCustomObject]@{
ExportedAtUtc = [System.DateTime]::UtcNow.ToString("o")
Web = [PSCustomObject]@{
Title = $Web.Title
Url = $Web.Url
ServerRelativeUrl = $Web.ServerRelativeUrl
Id = $Web.ID.ToString()
}
List = [PSCustomObject]@{
Title = $List.Title
Id = $List.ID.ToString()
BaseType = $List.BaseType.ToString()
BaseTemplate = [int]$List.BaseTemplate
DefaultViewUrl = $List.DefaultViewUrl
RootFolderUrl = $List.RootFolder.ServerRelativeUrl
ItemCount = $List.ItemCount
Hidden = $List.Hidden
}
Items = $items
}
Write-MetadataJson -MetadataObject $listExport -Path $listPath
Write-Host (" Listeneintraege exportiert: {0}" -f $items.Length)
}
function Get-ObjectPropertyValue {
param(
$Object,
[Parameter(Mandatory = $true)]
[string]$PropertyName,
$DefaultValue = $null
)
if ($null -eq $Object) {
return $DefaultValue
}
$property = $Object.PSObject.Properties[$PropertyName]
if ($null -eq $property) {
return $DefaultValue
}
return $property.Value
}
function Read-ManifestRows {
param(
[Parameter(Mandatory = $true)]
[string]$ManifestPath
)
if (-not [System.IO.File]::Exists($ManifestPath)) {
return @()
}
return @(Import-Csv -Path $ManifestPath -Delimiter ";")
}
function Get-ContainerManifestEntryKey {
param(
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
return ("{0}|{1}" -f $ObjectType.Trim(), $SourceTitle.Trim()).ToLowerInvariant()
}
function Get-ContainerManifestMap {
param(
[Parameter(Mandatory = $true)]
[object[]]$ManifestRows
)
$containerMap = @{}
foreach ($row in $ManifestRows) {
$recordType = [string](Get-ObjectPropertyValue -Object $row -PropertyName "RecordType")
$sourceTitle = [string](Get-ObjectPropertyValue -Object $row -PropertyName "SourceTitle")
$objectType = [string](Get-ObjectPropertyValue -Object $row -PropertyName "ObjectType")
if ($recordType -ne "Container" -or [string]::IsNullOrWhiteSpace($sourceTitle) -or [string]::IsNullOrWhiteSpace($objectType)) {
continue
}
$key = Get-ContainerManifestEntryKey -ObjectType $objectType -SourceTitle $sourceTitle
$containerMap[$key] = $row
}
return $containerMap
}
function Get-TargetContainerTitle {
param(
[Parameter(Mandatory = $true)]
[hashtable]$ContainerManifestMap,
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
$key = Get-ContainerManifestEntryKey -ObjectType $ObjectType -SourceTitle $SourceTitle
$manifestEntry = $ContainerManifestMap[$key]
if ($null -eq $manifestEntry) {
return $SourceTitle
}
$targetTitle = [string](Get-ObjectPropertyValue -Object $manifestEntry -PropertyName "TargetTitle")
if ([string]::IsNullOrWhiteSpace($targetTitle)) {
return $SourceTitle
}
return $targetTitle.Trim()
}
function Test-ContainerHasExplicitTargetMapping {
param(
[Parameter(Mandatory = $true)]
[hashtable]$ContainerManifestMap,
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
$key = Get-ContainerManifestEntryKey -ObjectType $ObjectType -SourceTitle $SourceTitle
$manifestEntry = $ContainerManifestMap[$key]
if ($null -eq $manifestEntry) {
return $false
}
$targetTitle = [string](Get-ObjectPropertyValue -Object $manifestEntry -PropertyName "TargetTitle")
return -not [string]::IsNullOrWhiteSpace($targetTitle)
}
function Test-ObjectHasProperty {
param(
$Object,
[Parameter(Mandatory = $true)]
[string]$PropertyName
)
if ($null -eq $Object) {
return $false
}
return $null -ne $Object.PSObject.Properties[$PropertyName]
}
function Read-JsonFile {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
return Get-Content -Path $Path -Raw -Encoding UTF8 | ConvertFrom-Json
}
function Resolve-SPField {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFieldCollection]$Fields,
[Parameter(Mandatory = $true)]
[string]$InternalName
)
try {
return $Fields.GetFieldByInternalName($InternalName)
}
catch {
}
try {
return $Fields.GetFieldByStaticName($InternalName)
}
catch {
}
foreach ($field in $Fields) {
if ($field.InternalName -eq $InternalName -or $field.StaticName -eq $InternalName) {
return $field
}
}
return $null
}
function Get-FieldMapping {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
$rows = Import-Csv -Path $Path -Delimiter ";"
$mapping = [ordered]@{}
foreach ($row in $rows) {
$sourceInternalName = [string](Get-ObjectPropertyValue -Object $row -PropertyName "SourceInternalName")
$targetInternalName = [string](Get-ObjectPropertyValue -Object $row -PropertyName "TargetInternalName")
if ([string]::IsNullOrWhiteSpace($sourceInternalName) -or [string]::IsNullOrWhiteSpace($targetInternalName)) {
continue
}
$mapping[$sourceInternalName.Trim()] = $targetInternalName.Trim()
}
return $mapping
}
function Convert-ToBoolean {
param(
$RawValue,
$TextValue
)
if ($RawValue -is [bool]) {
return $RawValue
}
$candidate = $RawValue
if ($null -eq $candidate -or [string]::IsNullOrWhiteSpace([string]$candidate)) {
$candidate = $TextValue
}
if ($null -eq $candidate -or [string]::IsNullOrWhiteSpace([string]$candidate)) {
return $null
}
return [System.Convert]::ToBoolean($candidate)
}
function Convert-ToInt32 {
param(
$RawValue,
$TextValue
)
$candidate = $RawValue
if ($null -eq $candidate -or [string]::IsNullOrWhiteSpace([string]$candidate)) {
$candidate = $TextValue
}
if ($null -eq $candidate -or [string]::IsNullOrWhiteSpace([string]$candidate)) {
return $null
}
return [int]$candidate
}
function Convert-ToDouble {
param(
$RawValue,
$TextValue
)
$candidate = $RawValue
if ($null -eq $candidate -or [string]::IsNullOrWhiteSpace([string]$candidate)) {
$candidate = $TextValue
}
if ($null -eq $candidate -or [string]::IsNullOrWhiteSpace([string]$candidate)) {
return $null
}
return [double]::Parse(
[string]$candidate,
[System.Globalization.NumberStyles]::Any,
[System.Globalization.CultureInfo]::InvariantCulture
)
}
function Convert-ToDateTimeValue {
param(
$RawValue,
$TextValue
)
$candidate = $RawValue
if ($null -eq $candidate -or [string]::IsNullOrWhiteSpace([string]$candidate)) {
$candidate = $TextValue
}
if ($null -eq $candidate -or [string]::IsNullOrWhiteSpace([string]$candidate)) {
return $null
}
return [datetime]::Parse(
[string]$candidate,
[System.Globalization.CultureInfo]::InvariantCulture,
[System.Globalization.DateTimeStyles]::RoundtripKind
)
}
function Resolve-SPUser {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
$RawUserValue,
[string]$TextValue
)
$candidates = @()
if ($null -ne $RawUserValue) {
$loginName = Get-ObjectPropertyValue -Object $RawUserValue -PropertyName "LoginName"
$email = Get-ObjectPropertyValue -Object $RawUserValue -PropertyName "Email"
$lookupValue = Get-ObjectPropertyValue -Object $RawUserValue -PropertyName "LookupValue"
if (-not [string]::IsNullOrWhiteSpace([string]$loginName)) {
$candidates += [string]$loginName
}
if (-not [string]::IsNullOrWhiteSpace([string]$email)) {
$candidates += [string]$email
}
if (-not [string]::IsNullOrWhiteSpace([string]$lookupValue)) {
$candidates += [string]$lookupValue
}
}
if (-not [string]::IsNullOrWhiteSpace([string]$TextValue)) {
$candidates += [string]$TextValue
}
foreach ($candidate in $candidates | Select-Object -Unique) {
try {
return $Web.EnsureUser($candidate)
}
catch {
}
}
return $null
}
function Convert-ToUserFieldValue {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item,
$RawValue,
[string]$TextValue
)
$user = Resolve-SPUser -Web $Item.Web -RawUserValue $RawValue -TextValue $TextValue
if ($null -eq $user) {
return $null
}
return New-Object Microsoft.SharePoint.SPFieldUserValue($Item.Web, $user.ID, $user.LoginName)
}
function Convert-ToUserMultiFieldValue {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item,
$RawValue,
[string]$TextValue
)
$collection = New-Object Microsoft.SharePoint.SPFieldUserValueCollection
$rawUsers = @($RawValue)
if (($rawUsers.Length -eq 1) -and $null -eq $rawUsers[0] -and -not [string]::IsNullOrWhiteSpace($TextValue)) {
$rawUsers = @($TextValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries))
}
foreach ($rawUser in $rawUsers) {
$userValue = Convert-ToUserFieldValue -Item $Item -RawValue $rawUser -TextValue ([string]$rawUser)
if ($null -ne $userValue) {
[void]$collection.Add($userValue)
}
}
return $collection
}
function Convert-ToUrlFieldValue {
param(
$RawValue,
[string]$TextValue
)
$url = Get-ObjectPropertyValue -Object $RawValue -PropertyName "Url"
$description = Get-ObjectPropertyValue -Object $RawValue -PropertyName "Description"
if (-not [string]::IsNullOrWhiteSpace([string]$url)) {
$fieldValue = New-Object Microsoft.SharePoint.SPFieldUrlValue
$fieldValue.Url = [string]$url
$fieldValue.Description = [string]$description
return $fieldValue
}
if (-not [string]::IsNullOrWhiteSpace([string]$TextValue)) {
$fieldValue = New-Object Microsoft.SharePoint.SPFieldUrlValue
try {
$fieldValue.FromString([string]$TextValue)
return $fieldValue
}
catch {
}
}
return $null
}
function Set-SPItemFieldValue {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item,
[Parameter(Mandatory = $true)]
[string]$TargetInternalName,
$RawValue,
[string]$TextValue
)
$field = Resolve-SPField -Fields $Item.Fields -InternalName $TargetInternalName
if ($null -eq $field) {
Write-Warning ("Zielfeld nicht gefunden: {0}" -f $TargetInternalName)
return
}
if ($field.ReadOnlyField -or $field.Sealed) {
Write-Warning ("Zielfeld ist schreibgeschuetzt und wird uebersprungen: {0}" -f $TargetInternalName)
return
}
try {
switch ($field.TypeAsString) {
"Boolean" {
$Item[$field.InternalName] = Convert-ToBoolean -RawValue $RawValue -TextValue $TextValue
return
}
"Integer" {
$Item[$field.InternalName] = Convert-ToInt32 -RawValue $RawValue -TextValue $TextValue
return
}
"Counter" {
return
}
"Number" {
$Item[$field.InternalName] = Convert-ToDouble -RawValue $RawValue -TextValue $TextValue
return
}
"Currency" {
$Item[$field.InternalName] = Convert-ToDouble -RawValue $RawValue -TextValue $TextValue
return
}
"DateTime" {
$Item[$field.InternalName] = Convert-ToDateTimeValue -RawValue $RawValue -TextValue $TextValue
return
}
"URL" {
$Item[$field.InternalName] = Convert-ToUrlFieldValue -RawValue $RawValue -TextValue $TextValue
return
}
"User" {
$Item[$field.InternalName] = Convert-ToUserFieldValue -Item $Item -RawValue $RawValue -TextValue $TextValue
return
}
"UserMulti" {
$Item[$field.InternalName] = Convert-ToUserMultiFieldValue -Item $Item -RawValue $RawValue -TextValue $TextValue
return
}
default {
if ($null -eq $RawValue) {
$Item[$field.InternalName] = $TextValue
}
else {
$Item[$field.InternalName] = $RawValue
}
return
}
}
}
catch {
if (-not [string]::IsNullOrWhiteSpace($TextValue)) {
try {
$field.ParseAndSetValue($Item, $TextValue)
return
}
catch {
}
}
throw ("Konnte Feld '{0}' nicht setzen. {1}" -f $TargetInternalName, $_.Exception.Message)
}
}
function Apply-FieldMappingToItem {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item,
[Parameter(Mandatory = $true)]
[System.Collections.IDictionary]$FieldMapping,
$SourceFieldValues,
$SourceFieldTextValues
)
foreach ($sourceInternalName in $FieldMapping.Keys) {
$targetInternalName = [string]$FieldMapping[$sourceInternalName]
$hasRawValue = Test-ObjectHasProperty -Object $SourceFieldValues -PropertyName $sourceInternalName
$hasTextValue = Test-ObjectHasProperty -Object $SourceFieldTextValues -PropertyName $sourceInternalName
if (-not $hasRawValue -and -not $hasTextValue) {
continue
}
$rawValue = if ($hasRawValue) {
Get-ObjectPropertyValue -Object $SourceFieldValues -PropertyName $sourceInternalName
}
else {
$null
}
$textValue = if ($hasTextValue) {
[string](Get-ObjectPropertyValue -Object $SourceFieldTextValues -PropertyName $sourceInternalName)
}
else {
$null
}
Set-SPItemFieldValue -Item $Item -TargetInternalName $targetInternalName -RawValue $rawValue -TextValue $textValue
}
}
function Save-SPListItem {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item
)
try {
$Item.UpdateOverwriteVersion()
return
}
catch {
}
$Item.Update()
}
function Get-RelativeSegmentsFromMetadataPath {
param(
[Parameter(Mandatory = $true)]
[string]$MetadataFilePath,
[Parameter(Mandatory = $true)]
[string]$FilesRootPath
)
$relativePath = $MetadataFilePath.Substring($FilesRootPath.Length).TrimStart("\")
return @($relativePath.Split("\") | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
}
function Ensure-SPFolderHierarchy {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List,
[string[]]$FolderSegments = @()
)
$currentFolder = $List.RootFolder
foreach ($folderSegment in $FolderSegments) {
$nextFolder = $null
foreach ($existingFolder in $currentFolder.SubFolders) {
if ($existingFolder.Name -eq $folderSegment) {
$nextFolder = $existingFolder
break
}
}
if ($null -eq $nextFolder) {
$nextFolder = $currentFolder.SubFolders.Add($folderSegment)
}
$currentFolder = $nextFolder
}
return $currentFolder
}
function Import-SPDocumentLibraries {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[string]$FilesRootPath,
[Parameter(Mandatory = $true)]
[System.Collections.IDictionary]$FieldMapping,
[Parameter(Mandatory = $true)]
[hashtable]$ContainerManifestMap,
[switch]$OverwriteFiles
)
if (-not [System.IO.Directory]::Exists($FilesRootPath)) {
Write-Warning ("Kein Files-Ordner gefunden: {0}" -f $FilesRootPath)
return
}
$metadataFiles = @(Get-ChildItem -Path $FilesRootPath -Recurse -File -Filter *.properties.json)
Write-Host ("Gefundene Datei-Metadaten: {0}" -f $metadataFiles.Length)
foreach ($metadataFile in $metadataFiles) {
$sourceFilePath = $metadataFile.FullName.Substring(0, $metadataFile.FullName.Length - ".properties.json".Length)
if (-not [System.IO.File]::Exists($sourceFilePath)) {
Write-Warning ("Quelldatei zur Metadatei nicht gefunden: {0}" -f $metadataFile.FullName)
continue
}
$metadata = Read-JsonFile -Path $metadataFile.FullName
$libraryMetadata = Get-ObjectPropertyValue -Object $metadata -PropertyName "Library"
$itemMetadata = Get-ObjectPropertyValue -Object $metadata -PropertyName "Item"
$sourceLibraryTitle = [string](Get-ObjectPropertyValue -Object $libraryMetadata -PropertyName "Title")
if ([string]::IsNullOrWhiteSpace($sourceLibraryTitle)) {
Write-Warning ("Bibliothekstitel fehlt in: {0}" -f $metadataFile.FullName)
continue
}
$targetLibraryTitle = Get-TargetContainerTitle -ContainerManifestMap $ContainerManifestMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
$targetLibrary = $Web.Lists.TryGetList($targetLibraryTitle)
if ($null -eq $targetLibrary) {
if (Test-ContainerHasExplicitTargetMapping -ContainerManifestMap $ContainerManifestMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle) {
Write-Warning ("Zielbibliothek '{0}' fuer Quellbibliothek '{1}' nicht gefunden. Bitte diese Bibliothek im Ziel manuell anlegen." -f $targetLibraryTitle, $sourceLibraryTitle)
}
else {
Write-Warning ("Zielbibliothek '{0}' nicht gefunden und kein TargetTitle im manifest.csv gepflegt. Bitte Bibliothek manuell anlegen oder TargetTitle im Manifest setzen." -f $sourceLibraryTitle)
}
continue
}
if ($targetLibrary.BaseType -ne [Microsoft.SharePoint.SPBaseType]::DocumentLibrary) {
Write-Warning ("Zielliste ist keine Dokumentbibliothek: {0}" -f $targetLibraryTitle)
continue
}
$relativeSegments = @(Get-RelativeSegmentsFromMetadataPath -MetadataFilePath $metadataFile.FullName -FilesRootPath $FilesRootPath)
if ($relativeSegments.Length -lt 2) {
Write-Warning ("Dateipfad konnte nicht relativ zur Bibliothek ermittelt werden: {0}" -f $metadataFile.FullName)
continue
}
$folderSegments = @()
if ($relativeSegments.Length -gt 2) {
$folderSegments = $relativeSegments[1..($relativeSegments.Length - 2)]
}
$targetFolder = Ensure-SPFolderHierarchy -List $targetLibrary -FolderSegments $folderSegments
$fileName = [System.IO.Path]::GetFileName($sourceFilePath)
$fileBytes = [System.IO.File]::ReadAllBytes($sourceFilePath)
Write-Host ("Importiere Datei: {0} -> {1}" -f $sourceFilePath, $targetLibrary.Title)
$spFile = $targetFolder.Files.Add($fileName, $fileBytes, $OverwriteFiles.IsPresent)
$spItem = $spFile.Item
if ($null -ne $itemMetadata) {
Apply-FieldMappingToItem -Item $spItem -FieldMapping $FieldMapping -SourceFieldValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldValues") -SourceFieldTextValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldTextValues")
Save-SPListItem -Item $spItem
}
}
}
function Import-SPLists {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[string]$ListsRootPath,
[Parameter(Mandatory = $true)]
[System.Collections.IDictionary]$FieldMapping,
[Parameter(Mandatory = $true)]
[hashtable]$ContainerManifestMap
)
if (-not [System.IO.Directory]::Exists($ListsRootPath)) {
Write-Warning ("Kein Lists-Ordner gefunden: {0}" -f $ListsRootPath)
return
}
$listFiles = @(Get-ChildItem -Path $ListsRootPath -File -Filter *.json)
Write-Host ("Gefundene Listen-JSONs: {0}" -f $listFiles.Length)
foreach ($listFile in $listFiles) {
$listExport = Read-JsonFile -Path $listFile.FullName
$listMetadata = Get-ObjectPropertyValue -Object $listExport -PropertyName "List"
$items = @(Get-ObjectPropertyValue -Object $listExport -PropertyName "Items" -DefaultValue @())
$sourceListTitle = [string](Get-ObjectPropertyValue -Object $listMetadata -PropertyName "Title")
if ([string]::IsNullOrWhiteSpace($sourceListTitle)) {
Write-Warning ("Listentitel fehlt in: {0}" -f $listFile.FullName)
continue
}
$targetListTitle = Get-TargetContainerTitle -ContainerManifestMap $ContainerManifestMap -ObjectType "List" -SourceTitle $sourceListTitle
$targetList = $Web.Lists.TryGetList($targetListTitle)
if ($null -eq $targetList) {
if (Test-ContainerHasExplicitTargetMapping -ContainerManifestMap $ContainerManifestMap -ObjectType "List" -SourceTitle $sourceListTitle) {
Write-Warning ("Zielliste '{0}' fuer Quellliste '{1}' nicht gefunden. Bitte diese Liste im Ziel manuell anlegen." -f $targetListTitle, $sourceListTitle)
}
else {
Write-Warning ("Zielliste '{0}' nicht gefunden und kein TargetTitle im manifest.csv gepflegt. Bitte Liste manuell anlegen oder TargetTitle im Manifest setzen." -f $sourceListTitle)
}
continue
}
if ($targetList.BaseType -eq [Microsoft.SharePoint.SPBaseType]::DocumentLibrary) {
Write-Warning ("Liste ist eine Dokumentbibliothek und wird im Listenimport uebersprungen: {0}" -f $targetListTitle)
continue
}
Write-Host ("Importiere Liste: {0}" -f $targetListTitle)
foreach ($sourceItem in $items) {
$fileSystemObjectType = [string](Get-ObjectPropertyValue -Object $sourceItem -PropertyName "FileSystemObjectType")
if ($fileSystemObjectType -eq "Folder") {
Write-Warning ("Ordner in normalen Listen werden in dieser Version uebersprungen: {0}" -f $targetListTitle)
continue
}
if ((Get-ObjectPropertyValue -Object $sourceItem -PropertyName "HasAttachments" -DefaultValue $false)) {
Write-Warning ("Listenanlagen wurden im Export nicht physisch gesichert und werden uebersprungen. Liste: {0}, ItemId: {1}" -f $targetListTitle, (Get-ObjectPropertyValue -Object $sourceItem -PropertyName "Id"))
}
$targetItem = $targetList.Items.Add()
Apply-FieldMappingToItem -Item $targetItem -FieldMapping $FieldMapping -SourceFieldValues (Get-ObjectPropertyValue -Object $sourceItem -PropertyName "FieldValues") -SourceFieldTextValues (Get-ObjectPropertyValue -Object $sourceItem -PropertyName "FieldTextValues")
Save-SPListItem -Item $targetItem
}
}
}
Initialize-SharePointPowerShell
$resolvedOutputPath = [System.IO.Path]::GetFullPath($OutputPath)
$filesRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Files")
$listsRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Lists")
$manifestPath = [System.IO.Path]::Combine($resolvedOutputPath, "manifest.csv")
$importConfigurationProvided =
(-not [string]::IsNullOrWhiteSpace($TargetWebUrl)) -or
(-not [string]::IsNullOrWhiteSpace($MappingCsvPath)) -or
$ImportFiles -or
$ImportLists -or
$OverwriteFiles
if ($ExportOnly -and $ImportOnly) {
throw "ExportOnly und ImportOnly koennen nicht gleichzeitig gesetzt werden."
}
if ($ImportOnly) {
if ([string]::IsNullOrWhiteSpace($TargetWebUrl) -or [string]::IsNullOrWhiteSpace($MappingCsvPath)) {
throw "Fuer ImportOnly muessen TargetWebUrl und MappingCsvPath angegeben werden."
}
Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetWebUrl -MappingCsvPath $MappingCsvPath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -OverwriteFiles:$OverwriteFiles
return
}
if ([string]::IsNullOrWhiteSpace($WebUrl)) {
throw "Fuer den Export muss WebUrl angegeben werden."
}
Ensure-Directory -Path $resolvedOutputPath
Ensure-Directory -Path $filesRootPath
Ensure-Directory -Path $listsRootPath
if ([System.IO.File]::Exists($manifestPath)) {
[System.IO.File]::Delete($manifestPath)
}
$web = $null
try {
$web = Get-SPWeb -Identity $WebUrl -ErrorAction Stop
$documentLibraries = @(
$web.Lists | Where-Object {
$_.BaseType -eq [Microsoft.SharePoint.SPBaseType]::DocumentLibrary -and
($IncludeHiddenLibraries -or -not $_.Hidden) -and
-not (Test-IsCatalogList -List $_)
}
)
$lists = @(
$web.Lists | Where-Object {
$_.BaseType -ne [Microsoft.SharePoint.SPBaseType]::DocumentLibrary -and
($IncludeHiddenLists -or -not $_.Hidden) -and
-not (Test-IsCatalogList -List $_)
}
)
Write-Host ("Gefundene Bibliotheken: {0}" -f $documentLibraries.Length)
Write-Host ("Gefundene Listen: {0}" -f $lists.Length)
foreach ($library in $documentLibraries) {
Export-SPDocumentLibrary -Web $web -List $library -FilesRootPath $filesRootPath -ManifestPath $manifestPath
}
foreach ($list in $lists) {
Export-SPList -Web $web -List $list -ListsRootPath $listsRootPath -ManifestPath $manifestPath
}
if ($documentLibraries.Length -eq 0 -and $lists.Length -eq 0) {
Write-Warning ("Keine exportierbaren Bibliotheken oder Listen im Web gefunden: {0}" -f $WebUrl)
return
}
Write-Host ("Export abgeschlossen. Ausgabe: {0}" -f $resolvedOutputPath)
if ($ExportOnly) {
Write-Host "ExportOnly ist gesetzt. Import wird uebersprungen."
return
}
if (-not $importConfigurationProvided) {
return
}
if ([string]::IsNullOrWhiteSpace($TargetWebUrl) -or [string]::IsNullOrWhiteSpace($MappingCsvPath)) {
throw "Fuer den automatischen Import muessen TargetWebUrl und MappingCsvPath angegeben werden."
}
Write-Host ("Starte Import nach Export. Zielweb: {0}" -f $TargetWebUrl)
Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetWebUrl -MappingCsvPath $MappingCsvPath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -OverwriteFiles:$OverwriteFiles
}
finally {
if ($null -ne $web) {
$web.Dispose()
}
}