Files
Start-SPMigration/Start-SPMigration.ps1
Torsten Brendgen 68d7ca6755 Refactor metadata column mappings in README and PowerShell script
- Updated README to reflect changes in metadata column mappings structure, separating system and custom columns.
- Modified PowerShell script to support new metadata column mapping structure.
- Introduced functions to handle taxonomy fields and improved error handling during item saving.
- Enhanced import functionality to manage existing files and handle required fields more effectively.
2026-04-16 13:11:23 +02:00

2857 lines
82 KiB
PowerShell

[CmdletBinding()]
param(
[Alias("WebUrl")]
[string]$SourceUrl,
[string]$OutputPath = (Join-Path -Path (Get-Location).Path -ChildPath "SPMigrationOutput"),
[switch]$IncludeHiddenLibraries,
[switch]$IncludeHiddenLists,
[Alias("TargetWebUrl")]
[string]$TargetUrl,
[Alias("MappingCsvPath")]
[string]$MappingTable,
[switch]$Export,
[switch]$Import,
[switch]$ImportFiles,
[switch]$ImportLists,
[Alias("OverwriteFiles")]
[switch]$Overwrite
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$script:SkippedImportFieldWarnings = @{}
function Initialize-SharePointPowerShell {
if($null -eq $(Get-Module SharePointServer)) {
Import-Module SharePointServer
}else{
Write-Host "SharePoint Server PowerShell-Modul bereits geladen."
}
try {
[void][System.Reflection.Assembly]::Load("Microsoft.SharePoint.Taxonomy")
}
catch {
try {
Add-Type -AssemblyName "Microsoft.SharePoint.Taxonomy"
}
catch {
}
}
}
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]$MappingTablePath,
[switch]$ImportFiles,
[switch]$ImportLists,
[switch]$Overwrite
)
$resolvedInputPath = [System.IO.Path]::GetFullPath($InputPath)
$filesRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Files")
$listsRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Lists")
$resolvedMappingTablePath = [System.IO.Path]::GetFullPath($MappingTablePath)
$migrationMappingTable = Get-MigrationMappingTable -Path $resolvedMappingTablePath
$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 -MigrationMappingTable $migrationMappingTable -Overwrite:$Overwrite
}
if ($shouldImportLists) {
Import-SPLists -Web $targetWeb -ListsRootPath $listsRootPath -MigrationMappingTable $migrationMappingTable
}
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 New-MigrationContainerMappingEntry {
param(
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List
)
return [PSCustomObject]@{
ObjectType = $ObjectType
SourceTitle = $List.Title
TargetTitle = $List.Title
BaseType = $List.BaseType.ToString()
BaseTemplate = [int]$List.BaseTemplate
RootFolderUrl = $List.RootFolder.ServerRelativeUrl
Hidden = [bool]$List.Hidden
}
}
function Test-SPFieldSupportedForImport {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPField]$Field
)
$unsupportedInternalNames = @(
"AccessPolicy",
"AppAuthor",
"AppEditor",
"Attachments",
"BannerUrl",
"BaseName",
"Author",
"ComplianceAssetId",
"ContentType",
"ContentTypeId",
"ContentVersion",
"Created",
"Created_x0020_Date",
"DocIcon",
"Duration",
"Edit",
"Editor",
"EncodedAbsUrl",
"EventCanceled",
"EventType",
"FileDirRef",
"File_x0020_Type",
"FileLeafRef",
"FileRef",
"FolderChildCount",
"FreeBusy",
"FSObjType",
"Geolocation",
"GUID",
"HTML_x0020_File_x0020_Type",
"ID",
"InstanceID",
"ItemChildCount",
"Last_x0020_Modified",
"LinkFilename",
"LinkFilename2",
"LinkFilenameNoMenu",
"LinkTitle",
"LinkTitle2",
"LinkTitleNoMenu",
"MasterSeriesItemID",
"MetaInfo",
"Modified",
"NoExecute",
"Order",
"OriginatorId",
"Overbook",
"owshiddenversion",
"Participants",
"PermMask",
"ProgId",
"RecurrenceData",
"RecurrenceID",
"Restricted",
"ScopeId",
"SelectTitle",
"ServerUrl",
"SMLastModifiedDate",
"SMTotalFileCount",
"SMTotalFileStreamSize",
"SMTotalSize",
"SortBehavior",
"SyncClientId",
"TimeZone",
"UID",
"UniqueId",
"webPartRteVersion",
"WorkflowInstanceID",
"WorkflowVersion",
"Workspace",
"WorkspaceLink",
"XMLTZone",
"_Level",
"_ModerationStatus",
"_UIVersion",
"_UIVersionString"
)
$unsupportedFieldTypes = @(
"Attachments",
"Computed"
)
if ($Field.ReadOnlyField) {
return $false
}
if ([string]$Field.InternalName -like "_*") {
return $false
}
if ($unsupportedFieldTypes -contains [string]$Field.TypeAsString) {
return $false
}
return -not ($unsupportedInternalNames -contains [string]$Field.InternalName)
}
function Resolve-SPFieldById {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFieldCollection]$Fields,
[Parameter(Mandatory = $true)]
[Guid]$FieldId
)
try {
return $Fields[$FieldId]
}
catch {
}
foreach ($candidateField in $Fields) {
if ($candidateField.Id -eq $FieldId) {
return $candidateField
}
}
return $null
}
function Get-SPFieldSupportingInternalNames {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPField]$Field,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFieldCollection]$Fields
)
$supportingInternalNames = @()
$typeName = [string]$Field.GetType().FullName
if ($typeName -like "*TaxonomyField*") {
try {
$textFieldId = [Guid]$Field.TextField
if ($textFieldId -ne [Guid]::Empty) {
$supportingField = Resolve-SPFieldById -Fields $Fields -FieldId $textFieldId
if ($null -ne $supportingField -and -not [string]::IsNullOrWhiteSpace([string]$supportingField.InternalName)) {
$supportingInternalNames += [string]$supportingField.InternalName
}
}
}
catch {
}
}
$internalNamePrefix = "{0}_" -f $Field.InternalName
foreach ($candidateField in $Fields) {
if ($candidateField.Id -eq $Field.Id) {
continue
}
if (-not [bool]$candidateField.Hidden) {
continue
}
if ([string]$candidateField.InternalName -like ($internalNamePrefix + "*")) {
$supportingInternalNames += [string]$candidateField.InternalName
}
}
return @($supportingInternalNames | Sort-Object -Unique)
}
function Test-SPFieldShownInMappingTable {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPField]$Field,
[hashtable]$SupportingFieldLookup = @{}
)
$fieldKey = ([string]$Field.InternalName).ToLowerInvariant()
if ($SupportingFieldLookup.ContainsKey($fieldKey)) {
return $false
}
return $true
}
function Test-SPFieldIsSystemColumn {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPField]$Field
)
try {
if ([bool]$Field.FromBaseType) {
return $true
}
}
catch {
}
try {
if (-not [bool]$Field.CanBeDeleted) {
return $true
}
}
catch {
}
if ([bool]$Field.Hidden) {
return $true
}
if ([string]$Field.InternalName -like "_*") {
return $true
}
if (-not (Test-SPFieldSupportedForImport -Field $Field)) {
return $true
}
return $false
}
function Write-SkippedImportFieldWarning {
param(
[Parameter(Mandatory = $true)]
[string]$FieldInternalName,
[Parameter(Mandatory = $true)]
[string]$Message
)
$warningKey = $FieldInternalName.ToLowerInvariant()
if ($script:SkippedImportFieldWarnings.ContainsKey($warningKey)) {
return
}
$script:SkippedImportFieldWarnings[$warningKey] = $true
Write-Warning $Message
}
function New-MigrationFieldMappingEntries {
param(
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List
)
$rows = @()
$supportingFieldLookup = @{}
foreach ($field in @($List.Fields)) {
foreach ($supportingInternalName in @(Get-SPFieldSupportingInternalNames -Field $field -Fields $List.Fields)) {
if (-not [string]::IsNullOrWhiteSpace([string]$supportingInternalName)) {
$supportingFieldLookup[[string]$supportingInternalName.ToLowerInvariant()] = $true
}
}
}
foreach ($field in @($List.Fields | Sort-Object -Property InternalName)) {
if (-not (Test-SPFieldShownInMappingTable -Field $field -SupportingFieldLookup $supportingFieldLookup)) {
continue
}
$rows += [PSCustomObject]@{
ObjectType = $ObjectType
ContainerSourceTitle = $List.Title
SourceInternalName = $field.InternalName
SourceCanonicalInternalName = $field.InternalName
SourceSupportingInternalNames = @(Get-SPFieldSupportingInternalNames -Field $field -Fields $List.Fields)
TargetInternalName = $field.InternalName
DisplayName = $field.Title
TypeAsString = $field.TypeAsString
Hidden = [bool]$field.Hidden
ReadOnly = [bool]$field.ReadOnlyField
Sealed = [bool]$field.Sealed
IsSystemColumn = (Test-SPFieldIsSystemColumn -Field $field)
ImportSupported = (Test-SPFieldSupportedForImport -Field $field)
}
}
return $rows
}
function New-MigrationMappingTable {
param(
[Parameter(Mandatory = $true)]
[string]$SourceWebUrl,
[Microsoft.SharePoint.SPList[]]$DocumentLibraries = @(),
[Microsoft.SharePoint.SPList[]]$Lists = @()
)
$libraryMappings = @()
$listMappings = @()
$systemColumnMappings = @()
$customColumnMappings = @()
foreach ($library in @($DocumentLibraries)) {
$libraryMappings += New-MigrationContainerMappingEntry -ObjectType "DocumentLibrary" -List $library
$fieldMappings = @(New-MigrationFieldMappingEntries -ObjectType "DocumentLibrary" -List $library)
$systemColumnMappings += @($fieldMappings | Where-Object { [bool](Get-ObjectPropertyValue -Object $_ -PropertyName "IsSystemColumn" -DefaultValue $false) })
$customColumnMappings += @($fieldMappings | Where-Object { -not [bool](Get-ObjectPropertyValue -Object $_ -PropertyName "IsSystemColumn" -DefaultValue $false) })
}
foreach ($list in @($Lists)) {
$listMappings += New-MigrationContainerMappingEntry -ObjectType "List" -List $list
$fieldMappings = @(New-MigrationFieldMappingEntries -ObjectType "List" -List $list)
$systemColumnMappings += @($fieldMappings | Where-Object { [bool](Get-ObjectPropertyValue -Object $_ -PropertyName "IsSystemColumn" -DefaultValue $false) })
$customColumnMappings += @($fieldMappings | Where-Object { -not [bool](Get-ObjectPropertyValue -Object $_ -PropertyName "IsSystemColumn" -DefaultValue $false) })
}
return [PSCustomObject]@{
SchemaVersion = 1
GeneratedAtUtc = [System.DateTime]::UtcNow.ToString("o")
SourceWebUrl = $SourceWebUrl
LibraryMappings = $libraryMappings
ListMappings = $listMappings
MetadataColumnMappings = [PSCustomObject]@{
SystemColumns = $systemColumnMappings
CustomColumns = $customColumnMappings
}
}
}
function Write-MigrationMappingTable {
param(
[Parameter(Mandatory = $true)]
$MappingTable,
[Parameter(Mandatory = $true)]
[string]$Path
)
$json = $MappingTable | ConvertTo-Json -Depth 10
[System.IO.File]::WriteAllText($Path, $json, [System.Text.Encoding]::UTF8)
}
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 Get-MigrationMappingTable {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not [System.IO.File]::Exists($Path)) {
throw ("MappingTable nicht gefunden: {0}" -f $Path)
}
$extension = [System.IO.Path]::GetExtension($Path)
if ($extension -ieq ".json") {
$mappingTable = Read-JsonFile -Path $Path
}
elseif ($extension -ieq ".csv") {
$fieldMapping = Get-FieldMapping -Path $Path
$metadataColumnMappings = @()
foreach ($sourceInternalName in $fieldMapping.Keys) {
$metadataColumnMappings += [PSCustomObject]@{
ObjectType = "*"
ContainerSourceTitle = "*"
SourceInternalName = [string]$sourceInternalName
SourceCanonicalInternalName = [string]$sourceInternalName
SourceSupportingInternalNames = @()
TargetInternalName = [string]$fieldMapping[$sourceInternalName]
DisplayName = ""
TypeAsString = ""
Hidden = $false
ReadOnly = $false
Sealed = $false
IsSystemColumn = $false
ImportSupported = $true
}
}
$mappingTable = [PSCustomObject]@{
SchemaVersion = 1
GeneratedAtUtc = [System.DateTime]::UtcNow.ToString("o")
SourceWebUrl = ""
LibraryMappings = @()
ListMappings = @()
MetadataColumnMappings = [PSCustomObject]@{
SystemColumns = @()
CustomColumns = $metadataColumnMappings
}
}
}
else {
throw "MappingTable muss eine JSON- oder CSV-Datei sein."
}
if ($null -eq $mappingTable) {
throw "MappingTable konnte nicht gelesen werden."
}
return $mappingTable
}
function Get-ContainerMappingTableEntryKey {
param(
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
return ("{0}|{1}" -f $ObjectType.Trim(), $SourceTitle.Trim()).ToLowerInvariant()
}
function Get-MetadataColumnMappingRows {
param(
[Parameter(Mandatory = $true)]
$MigrationMappingTable
)
$metadataColumnMappings = Get-ObjectPropertyValue -Object $MigrationMappingTable -PropertyName "MetadataColumnMappings" -DefaultValue @()
if ($null -eq $metadataColumnMappings) {
return @()
}
if ($null -ne $metadataColumnMappings.PSObject.Properties["SystemColumns"] -or $null -ne $metadataColumnMappings.PSObject.Properties["CustomColumns"]) {
return @(
@(Get-ObjectPropertyValue -Object $metadataColumnMappings -PropertyName "SystemColumns" -DefaultValue @()) +
@(Get-ObjectPropertyValue -Object $metadataColumnMappings -PropertyName "CustomColumns" -DefaultValue @())
)
}
return @($metadataColumnMappings)
}
function Test-MetadataColumnMappingRowImportSupported {
param(
[Parameter(Mandatory = $true)]
$Row
)
if (Test-ObjectHasProperty -Object $Row -PropertyName "ImportSupported") {
return [bool](Get-ObjectPropertyValue -Object $Row -PropertyName "ImportSupported" -DefaultValue $false)
}
$sourceInternalName = [string](Get-ObjectPropertyValue -Object $Row -PropertyName "SourceInternalName")
$typeAsString = [string](Get-ObjectPropertyValue -Object $Row -PropertyName "TypeAsString")
$readOnly = [bool](Get-ObjectPropertyValue -Object $Row -PropertyName "ReadOnly" -DefaultValue $false)
$unsupportedInternalNames = @(
"AccessPolicy",
"AppAuthor",
"AppEditor",
"Attachments",
"BannerUrl",
"BaseName",
"Author",
"ComplianceAssetId",
"ContentType",
"ContentTypeId",
"ContentVersion",
"Created",
"Created_x0020_Date",
"DocIcon",
"Duration",
"Edit",
"Editor",
"EncodedAbsUrl",
"EventCanceled",
"EventType",
"FileDirRef",
"File_x0020_Type",
"FileLeafRef",
"FileRef",
"FolderChildCount",
"FreeBusy",
"FSObjType",
"Geolocation",
"GUID",
"HTML_x0020_File_x0020_Type",
"ID",
"InstanceID",
"ItemChildCount",
"Last_x0020_Modified",
"LinkFilename",
"LinkFilename2",
"LinkFilenameNoMenu",
"LinkTitle",
"LinkTitle2",
"LinkTitleNoMenu",
"MasterSeriesItemID",
"MetaInfo",
"Modified",
"NoExecute",
"Order",
"OriginatorId",
"Overbook",
"owshiddenversion",
"Participants",
"PermMask",
"ProgId",
"RecurrenceData",
"RecurrenceID",
"Restricted",
"ScopeId",
"SelectTitle",
"ServerUrl",
"SMLastModifiedDate",
"SMTotalFileCount",
"SMTotalFileStreamSize",
"SMTotalSize",
"SortBehavior",
"SyncClientId",
"TimeZone",
"UID",
"UniqueId",
"webPartRteVersion",
"WorkflowInstanceID",
"WorkflowVersion",
"Workspace",
"WorkspaceLink",
"XMLTZone",
"_Level",
"_ModerationStatus",
"_UIVersion",
"_UIVersionString"
)
if ($readOnly) {
return $false
}
if ([string]::IsNullOrWhiteSpace($sourceInternalName)) {
return $false
}
if ([string]$sourceInternalName -like "_*") {
return $false
}
if ($typeAsString -in @("Attachments", "Computed")) {
return $false
}
return -not ($unsupportedInternalNames -contains $sourceInternalName)
}
function Get-ContainerMappingMap {
param(
[Parameter(Mandatory = $true)]
$MigrationMappingTable
)
$containerMap = @{}
$rows = @(
@(Get-ObjectPropertyValue -Object $MigrationMappingTable -PropertyName "LibraryMappings" -DefaultValue @()) +
@(Get-ObjectPropertyValue -Object $MigrationMappingTable -PropertyName "ListMappings" -DefaultValue @())
)
foreach ($row in $rows) {
$objectType = [string](Get-ObjectPropertyValue -Object $row -PropertyName "ObjectType")
$sourceTitle = [string](Get-ObjectPropertyValue -Object $row -PropertyName "SourceTitle")
if ([string]::IsNullOrWhiteSpace($objectType) -or [string]::IsNullOrWhiteSpace($sourceTitle)) {
continue
}
$key = Get-ContainerMappingTableEntryKey -ObjectType $objectType -SourceTitle $sourceTitle
$containerMap[$key] = $row
}
return $containerMap
}
function Get-TargetContainerTitleFromMappingTable {
param(
[Parameter(Mandatory = $true)]
[hashtable]$ContainerMappingMap,
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
$key = Get-ContainerMappingTableEntryKey -ObjectType $ObjectType -SourceTitle $SourceTitle
$entry = $ContainerMappingMap[$key]
if ($null -eq $entry) {
return $SourceTitle
}
$targetTitle = [string](Get-ObjectPropertyValue -Object $entry -PropertyName "TargetTitle")
if ([string]::IsNullOrWhiteSpace($targetTitle)) {
return $SourceTitle
}
return $targetTitle.Trim()
}
function Test-ContainerHasExplicitTargetMappingInTable {
param(
[Parameter(Mandatory = $true)]
[hashtable]$ContainerMappingMap,
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
$key = Get-ContainerMappingTableEntryKey -ObjectType $ObjectType -SourceTitle $SourceTitle
$entry = $ContainerMappingMap[$key]
if ($null -eq $entry) {
return $false
}
$targetTitle = [string](Get-ObjectPropertyValue -Object $entry -PropertyName "TargetTitle")
return -not [string]::IsNullOrWhiteSpace($targetTitle)
}
function Get-FieldMappingForContainer {
param(
[Parameter(Mandatory = $true)]
$MigrationMappingTable,
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
$rows = @(Get-MetadataColumnMappingRows -MigrationMappingTable $MigrationMappingTable)
$mapping = [ordered]@{}
$mappingPriority = @{}
foreach ($row in $rows) {
$rowObjectType = [string](Get-ObjectPropertyValue -Object $row -PropertyName "ObjectType")
$rowContainerSourceTitle = [string](Get-ObjectPropertyValue -Object $row -PropertyName "ContainerSourceTitle")
$sourceInternalName = [string](Get-ObjectPropertyValue -Object $row -PropertyName "SourceInternalName")
$sourceCanonicalInternalName = [string](Get-ObjectPropertyValue -Object $row -PropertyName "SourceCanonicalInternalName")
$targetInternalName = [string](Get-ObjectPropertyValue -Object $row -PropertyName "TargetInternalName")
$isHiddenRow = [bool](Get-ObjectPropertyValue -Object $row -PropertyName "Hidden" -DefaultValue $false)
if ([string]::IsNullOrWhiteSpace($sourceInternalName) -or [string]::IsNullOrWhiteSpace($targetInternalName)) {
continue
}
$objectTypeMatches = ($rowObjectType -eq "*") -or ($rowObjectType -eq $ObjectType)
$containerMatches = [string]::IsNullOrWhiteSpace($rowContainerSourceTitle) -or ($rowContainerSourceTitle -eq "*") -or ($rowContainerSourceTitle -eq $SourceTitle)
if (-not $objectTypeMatches -or -not $containerMatches) {
continue
}
if (-not (Test-MetadataColumnMappingRowImportSupported -Row $row)) {
continue
}
$normalizedSourceInternalName = $sourceCanonicalInternalName
if ([string]::IsNullOrWhiteSpace($normalizedSourceInternalName)) {
$normalizedSourceInternalName = $sourceInternalName
}
if ($isHiddenRow -and $sourceInternalName -match "^(.*)_\d+$") {
$normalizedSourceInternalName = $matches[1]
}
if ([string]::IsNullOrWhiteSpace($normalizedSourceInternalName)) {
continue
}
$priority = if ($isHiddenRow) { 1 } else { 2 }
$normalizedSourceInternalName = $normalizedSourceInternalName.Trim()
if ($mappingPriority.ContainsKey($normalizedSourceInternalName) -and $mappingPriority[$normalizedSourceInternalName] -gt $priority) {
continue
}
$mapping[$normalizedSourceInternalName] = $targetInternalName.Trim()
$mappingPriority[$normalizedSourceInternalName] = $priority
}
return $mapping
}
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 Convert-ToTaxonomyTermEntries {
param(
$RawValue,
[string]$TextValue
)
$termEntries = @()
$rawTerms = @($RawValue)
if (($rawTerms.Length -eq 1) -and $null -eq $rawTerms[0]) {
$rawTerms = @()
}
foreach ($rawTerm in $rawTerms) {
$termGuid = [string](Get-ObjectPropertyValue -Object $rawTerm -PropertyName "TermGuid")
$label = [string](Get-ObjectPropertyValue -Object $rawTerm -PropertyName "Label")
if ([string]::IsNullOrWhiteSpace($termGuid) -and ($rawTerm -is [string])) {
$rawTermString = [string]$rawTerm
if ($rawTermString -match "^(.*)\|([0-9a-fA-F-]{36})$") {
$label = $matches[1]
$termGuid = $matches[2]
}
else {
$parsedGuid = [Guid]::Empty
if ([guid]::TryParse($rawTermString, [ref]$parsedGuid)) {
$termGuid = $rawTermString
}
}
}
if ([string]::IsNullOrWhiteSpace($label) -and -not [string]::IsNullOrWhiteSpace($TextValue) -and $rawTerms.Length -le 1) {
$label = $TextValue
}
if ([string]::IsNullOrWhiteSpace($termGuid)) {
continue
}
if ([string]::IsNullOrWhiteSpace($label)) {
$label = $termGuid
}
$termEntries += [PSCustomObject]@{
Label = $label
TermGuid = $termGuid
}
}
if ($termEntries.Count -eq 0 -and -not [string]::IsNullOrWhiteSpace($TextValue)) {
$segments = @($TextValue -split ";#")
foreach ($segment in $segments) {
if ([string]::IsNullOrWhiteSpace([string]$segment)) {
continue
}
if ([string]$segment -match "^(.*)\|([0-9a-fA-F-]{36})$") {
$termEntries += [PSCustomObject]@{
Label = $matches[1]
TermGuid = $matches[2]
}
}
}
}
return @($termEntries)
}
function New-TaxonomyFieldValueObject {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPField]$Field,
[Parameter(Mandatory = $true)]
$TermEntry
)
$label = [string](Get-ObjectPropertyValue -Object $TermEntry -PropertyName "Label")
$termGuid = [string](Get-ObjectPropertyValue -Object $TermEntry -PropertyName "TermGuid")
$labelGuidPair = "{0}|{1}" -f $label, $termGuid
$taxonomyField = [Microsoft.SharePoint.Taxonomy.TaxonomyField]$Field
$taxonomyValue = $null
try {
$taxonomyValue = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue($taxonomyField)
}
catch {
}
if ($null -eq $taxonomyValue) {
try {
$taxonomyValue = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue
}
catch {
}
}
if ($null -eq $taxonomyValue) {
throw ("Konnte kein TaxonomyFieldValue fuer Feld '{0}' erstellen." -f $Field.InternalName)
}
$isPopulated = $false
try {
$taxonomyValue.PopulateFromLabelGuidPair($labelGuidPair)
$isPopulated = $true
}
catch {
}
if (-not $isPopulated) {
try {
$taxonomyValue.Label = $label
$taxonomyValue.TermGuid = $termGuid
$taxonomyValue.WssId = -1
$isPopulated = $true
}
catch {
}
}
if (-not $isPopulated) {
throw ("Konnte TaxonomyFieldValue fuer Feld '{0}' nicht befuellen." -f $Field.InternalName)
}
return $taxonomyValue
}
function Set-SPTaxonomyFieldValue {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPField]$Field,
$RawValue,
[string]$TextValue
)
$taxonomyField = [Microsoft.SharePoint.Taxonomy.TaxonomyField]$Field
$termEntries = @(Convert-ToTaxonomyTermEntries -RawValue $RawValue -TextValue $TextValue)
if ($termEntries.Count -eq 0) {
$Item[$Field.InternalName] = $null
return
}
if ([string]$Field.TypeAsString -eq "TaxonomyFieldTypeMulti") {
$labelGuidPairs = @()
foreach ($termEntry in $termEntries) {
$label = [string](Get-ObjectPropertyValue -Object $termEntry -PropertyName "Label")
$termGuid = [string](Get-ObjectPropertyValue -Object $termEntry -PropertyName "TermGuid")
$labelGuidPairs += ("{0}|{1}" -f $label, $termGuid)
}
$pairString = $labelGuidPairs -join ";#"
$taxonomyCollection = $null
try {
$taxonomyCollection = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection($Item.Web, "", $taxonomyField)
}
catch {
}
if ($null -eq $taxonomyCollection) {
try {
$taxonomyCollection = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection($Item.Web, $pairString, $taxonomyField)
}
catch {
}
}
if ($null -eq $taxonomyCollection) {
throw ("Konnte keine TaxonomyFieldValueCollection fuer Feld '{0}' erstellen." -f $Field.InternalName)
}
$collectionIsPopulated = $false
try {
$taxonomyCollection.PopulateFromLabelGuidPairs($pairString)
$collectionIsPopulated = $true
}
catch {
}
if (-not $collectionIsPopulated) {
try {
foreach ($termEntry in $termEntries) {
[void]$taxonomyCollection.Add((New-TaxonomyFieldValueObject -Field $Field -TermEntry $termEntry))
}
$collectionIsPopulated = $true
}
catch {
}
}
if (-not $collectionIsPopulated) {
throw ("Konnte die TaxonomyFieldValueCollection fuer Feld '{0}' nicht befuellen." -f $Field.InternalName)
}
try {
$taxonomyField.SetFieldValueByValueCollection($Item, $taxonomyCollection)
return
}
catch {
}
try {
$taxonomyField.SetFieldValue($Item, $taxonomyCollection)
return
}
catch {
}
$Item[$Field.InternalName] = $taxonomyCollection
return
}
$taxonomyValue = New-TaxonomyFieldValueObject -Field $Field -TermEntry $termEntries[0]
try {
$taxonomyField.SetFieldValueByValue($Item, $taxonomyValue)
return
}
catch {
}
try {
$taxonomyField.SetFieldValue($Item, $taxonomyValue)
return
}
catch {
}
$Item[$Field.InternalName] = $taxonomyValue
}
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-SkippedImportFieldWarning -FieldInternalName $TargetInternalName -Message ("Zielfeld nicht gefunden: {0}" -f $TargetInternalName)
return
}
if (-not (Test-SPFieldSupportedForImport -Field $field)) {
Write-SkippedImportFieldWarning -FieldInternalName $TargetInternalName -Message ("Zielfeld wird nicht importiert und wird uebersprungen: {0}" -f $TargetInternalName)
return
}
try {
switch ($field.TypeAsString) {
"TaxonomyFieldType" {
Set-SPTaxonomyFieldValue -Item $Item -Field $field -RawValue $RawValue -TextValue $TextValue
return
}
"TaxonomyFieldTypeMulti" {
Set-SPTaxonomyFieldValue -Item $Item -Field $field -RawValue $RawValue -TextValue $TextValue
return
}
"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 {
}
}
Write-SkippedImportFieldWarning -FieldInternalName $TargetInternalName -Message ("Konnte Feld '{0}' nicht setzen und ueberspringe es. {1}" -f $TargetInternalName, $_.Exception.Message)
return
}
}
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 -and $sourceInternalName -match "^(.*)_\d+$") {
$canonicalSourceInternalName = $matches[1]
$hasRawValue = Test-ObjectHasProperty -Object $SourceFieldValues -PropertyName $canonicalSourceInternalName
$hasTextValue = Test-ObjectHasProperty -Object $SourceFieldTextValues -PropertyName $canonicalSourceInternalName
if ($hasRawValue -or $hasTextValue) {
$sourceInternalName = $canonicalSourceInternalName
}
}
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 Get-SPItemFieldRawValue {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPField]$Field
)
try {
return $Item[$Field.InternalName]
}
catch {
try {
return $Item[$Field.Id]
}
catch {
return $null
}
}
}
function Test-SPValuePresent {
param(
$Value
)
if ($null -eq $Value) {
return $false
}
if ($Value -is [string]) {
return -not [string]::IsNullOrWhiteSpace($Value)
}
if ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])) {
return @($Value).Count -gt 0
}
return $true
}
function Get-SPItemMissingRequiredFields {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item
)
$missingFields = @()
foreach ($field in $Item.Fields) {
if (-not $field.Required) {
continue
}
if ($field.TypeAsString -eq "Computed" -or $field.TypeAsString -eq "Attachments") {
continue
}
$rawValue = Get-SPItemFieldRawValue -Item $Item -Field $field
if (-not (Test-SPValuePresent -Value $rawValue)) {
$missingFields += $field.InternalName
}
}
return $missingFields
}
function Save-SPListItem {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPListItem]$Item,
[string]$Context = ""
)
$missingRequiredFields = @(Get-SPItemMissingRequiredFields -Item $Item)
if ($missingRequiredFields.Count -gt 0) {
$message = "Pflichtfelder ohne Wert: {0}" -f ($missingRequiredFields -join ", ")
if (-not [string]::IsNullOrWhiteSpace($Context)) {
$message = "{0}. {1}" -f $Context, $message
}
throw $message
}
$saveErrors = @()
try {
$Item.UpdateOverwriteVersion()
return
}
catch {
$saveErrors += ("UpdateOverwriteVersion: {0}" -f $_.Exception.Message)
}
try {
$Item.SystemUpdate($false)
return
}
catch {
$saveErrors += ("SystemUpdate(false): {0}" -f $_.Exception.Message)
}
try {
$Item.Update()
return
}
catch {
$saveErrors += ("Update: {0}" -f $_.Exception.Message)
}
$missingRequiredFields = @(Get-SPItemMissingRequiredFields -Item $Item)
$messageParts = @()
if (-not [string]::IsNullOrWhiteSpace($Context)) {
$messageParts += $Context
}
if ($missingRequiredFields.Count -gt 0) {
$messageParts += ("Pflichtfelder ohne Wert: {0}" -f ($missingRequiredFields -join ", "))
}
if ($saveErrors.Count -gt 0) {
$messageParts += ("Save-Versuche fehlgeschlagen: {0}" -f ($saveErrors -join " | "))
}
throw ("Konnte Listenelement nicht speichern. {0}" -f ($messageParts -join ". "))
}
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 Get-SPFolderFileByName {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFolder]$Folder,
[Parameter(Mandatory = $true)]
[string]$FileName
)
foreach ($existingFile in $Folder.Files) {
if ($existingFile.Name -eq $FileName) {
return $existingFile
}
}
return $null
}
function Update-SPExistingFileAsNewVersion {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFile]$ExistingFile,
[Parameter(Mandatory = $true)]
[byte[]]$FileBytes
)
$requiresCheckout = $false
try {
$requiresCheckout = [bool]$ExistingFile.ParentFolder.DocumentLibrary.ForceCheckout
}
catch {
$requiresCheckout = $false
}
if ($requiresCheckout -and $ExistingFile.CheckOutType -eq [Microsoft.SharePoint.SPFile+SPCheckOutType]::None) {
$ExistingFile.CheckOut()
}
$existingFile.SaveBinary($FileBytes)
if ($ExistingFile.CheckOutType -ne [Microsoft.SharePoint.SPFile+SPCheckOutType]::None) {
$checkInType = [Microsoft.SharePoint.SPCheckinType]::MajorCheckIn
try {
if ([bool]$ExistingFile.ParentFolder.DocumentLibrary.EnableMinorVersions) {
$checkInType = [Microsoft.SharePoint.SPCheckinType]::MinorCheckIn
}
}
catch {
}
$ExistingFile.CheckIn("Imported by Start-SPMigration", $checkInType)
}
return $ExistingFile
}
function Import-SPFileToFolder {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPFolder]$TargetFolder,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$TargetLibrary,
[Parameter(Mandatory = $true)]
[string]$FileName,
[Parameter(Mandatory = $true)]
[byte[]]$FileBytes,
[switch]$Overwrite
)
$existingFile = Get-SPFolderFileByName -Folder $TargetFolder -FileName $FileName
if ($null -eq $existingFile) {
return $TargetFolder.Files.Add($FileName, $FileBytes, $false)
}
if ($Overwrite.IsPresent) {
return $TargetFolder.Files.Add($FileName, $FileBytes, $true)
}
if (-not [bool]$TargetLibrary.EnableVersioning) {
Write-Warning ("Datei uebersprungen, da sie bereits existiert und in der Zielbibliothek keine Versionierung aktiv ist: {0}/{1}" -f $TargetLibrary.Title, $FileName)
return $null
}
Write-Host ("Datei existiert bereits. Erzeuge neue Version: {0}/{1}" -f $TargetLibrary.Title, $FileName)
return Update-SPExistingFileAsNewVersion -ExistingFile $existingFile -FileBytes $FileBytes
}
function Import-SPDocumentLibraries {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[string]$FilesRootPath,
[Parameter(Mandatory = $true)]
$MigrationMappingTable,
[switch]$Overwrite
)
if (-not [System.IO.Directory]::Exists($FilesRootPath)) {
Write-Warning ("Kein Files-Ordner gefunden: {0}" -f $FilesRootPath)
return
}
$containerMappingMap = Get-ContainerMappingMap -MigrationMappingTable $MigrationMappingTable
$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-TargetContainerTitleFromMappingTable -ContainerMappingMap $containerMappingMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
$targetLibrary = $Web.Lists.TryGetList($targetLibraryTitle)
if ($null -eq $targetLibrary) {
if (Test-ContainerHasExplicitTargetMappingInTable -ContainerMappingMap $containerMappingMap -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 in der MappingTable gepflegt. Bitte Bibliothek manuell anlegen oder TargetTitle in der MappingTable 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 = Import-SPFileToFolder -TargetFolder $targetFolder -TargetLibrary $targetLibrary -FileName $fileName -FileBytes $fileBytes -Overwrite:$Overwrite
if ($null -eq $spFile) {
continue
}
$spItem = $spFile.Item
if ($null -ne $itemMetadata) {
try {
$fieldMapping = Get-FieldMappingForContainer -MigrationMappingTable $MigrationMappingTable -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
Apply-FieldMappingToItem -Item $spItem -FieldMapping $fieldMapping -SourceFieldValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldValues") -SourceFieldTextValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldTextValues")
Save-SPListItem -Item $spItem -Context ("Bibliothek '{0}', Datei '{1}'" -f $targetLibrary.Title, $fileName)
}
catch {
Write-Warning ("Metadaten fuer Datei konnten nicht gespeichert werden: {0}" -f $_.Exception.Message)
}
}
}
}
function Import-SPLists {
param(
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPWeb]$Web,
[Parameter(Mandatory = $true)]
[string]$ListsRootPath,
[Parameter(Mandatory = $true)]
$MigrationMappingTable
)
if (-not [System.IO.Directory]::Exists($ListsRootPath)) {
Write-Warning ("Kein Lists-Ordner gefunden: {0}" -f $ListsRootPath)
return
}
$containerMappingMap = Get-ContainerMappingMap -MigrationMappingTable $MigrationMappingTable
$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-TargetContainerTitleFromMappingTable -ContainerMappingMap $containerMappingMap -ObjectType "List" -SourceTitle $sourceListTitle
$targetList = $Web.Lists.TryGetList($targetListTitle)
if ($null -eq $targetList) {
if (Test-ContainerHasExplicitTargetMappingInTable -ContainerMappingMap $containerMappingMap -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 in der MappingTable gepflegt. Bitte Liste manuell anlegen oder TargetTitle in der MappingTable 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)
$fieldMapping = Get-FieldMappingForContainer -MigrationMappingTable $MigrationMappingTable -ObjectType "List" -SourceTitle $sourceListTitle
foreach ($sourceItem in $items) {
$sourceItemId = [string](Get-ObjectPropertyValue -Object $sourceItem -PropertyName "Id")
$sourceItemTitle = [string](Get-ObjectPropertyValue -Object $sourceItem -PropertyName "Title")
$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"))
}
try {
$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 -Context ("Liste '{0}', Quell-ItemId '{1}', Titel '{2}'" -f $targetListTitle, $sourceItemId, $sourceItemTitle)
}
catch {
Write-Warning ("Listenelement wurde uebersprungen: {0}" -f $_.Exception.Message)
continue
}
}
}
}
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")
$mappingTablePath = if ([string]::IsNullOrWhiteSpace($MappingTable)) {
[System.IO.Path]::Combine($resolvedOutputPath, "MappingTable.json")
}
else {
[System.IO.Path]::GetFullPath($MappingTable)
}
$shouldExport = $Export.IsPresent
$shouldImport = $Import.IsPresent
if (-not $shouldExport -and -not $shouldImport) {
$shouldExport = -not [string]::IsNullOrWhiteSpace($SourceUrl)
$shouldImport = -not [string]::IsNullOrWhiteSpace($TargetUrl)
}
if (-not $shouldExport -and -not $shouldImport) {
throw "Bitte mindestens -Export oder -Import angeben."
}
if ($shouldImport -and [string]::IsNullOrWhiteSpace($TargetUrl)) {
throw "Fuer den Import muss TargetUrl angegeben werden."
}
if ($shouldImport -and -not $shouldExport) {
if (-not [System.IO.File]::Exists($mappingTablePath)) {
throw ("MappingTable nicht gefunden: {0}" -f $mappingTablePath)
}
Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetUrl -MappingTablePath $mappingTablePath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -Overwrite:$Overwrite
return
}
if ([string]::IsNullOrWhiteSpace($SourceUrl)) {
throw "Fuer den Export muss SourceUrl 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 $SourceUrl -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)
$migrationMappingTable = New-MigrationMappingTable -SourceWebUrl $web.Url -DocumentLibraries $documentLibraries -Lists $lists
Write-MigrationMappingTable -MappingTable $migrationMappingTable -Path $mappingTablePath
Write-Host ("MappingTable geschrieben: {0}" -f $mappingTablePath)
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 $SourceUrl)
return
}
Write-Host ("Export abgeschlossen. Ausgabe: {0}" -f $resolvedOutputPath)
if (-not $shouldImport) {
Write-Host "Import wurde nicht angefordert. Export ist abgeschlossen."
return
}
Write-Host ("Starte Import nach Export. Zielweb: {0}" -f $TargetUrl)
Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetUrl -MappingTablePath $mappingTablePath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -Overwrite:$Overwrite
}
finally {
if ($null -ne $web) {
$web.Dispose()
}
}