From 6cbe86073132c7ff2c5469495c056d830819d17a Mon Sep 17 00:00:00 2001 From: Torsten Date: Wed, 15 Apr 2026 13:26:45 +0200 Subject: [PATCH] version 1.0 --- Start-SPMigration.ps1 | 464 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) diff --git a/Start-SPMigration.ps1 b/Start-SPMigration.ps1 index e69de29..dce9baa 100644 --- a/Start-SPMigration.ps1 +++ b/Start-SPMigration.ps1 @@ -0,0 +1,464 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$WebUrl, + + [Parameter(Mandatory = $true)] + [string]$OutputPath, + + [switch]$IncludeHiddenLibraries +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Initialize-SharePointPowerShell { + if (-not (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue)) { + Add-PSSnapin Microsoft.SharePoint.PowerShell + } +} + +function Ensure-Directory { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + if (-not [System.IO.Directory]::Exists($Path)) { + [System.IO.Directory]::CreateDirectory($Path) | Out-Null + } +} + +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 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 + ) + + if (-not $Item.Fields.ContainsField($InternalName)) { + return $null + } + + return Get-FieldTextValue -Field $Item.Fields[$InternalName] -RawValue $Item[$InternalName] +} + +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 + $fieldMetadata = @() + + if ($null -ne $item) { + foreach ($field in $item.Fields) { + $rawValue = $item[$field.InternalName] + + $fieldMetadata += [PSCustomObject]@{ + InternalName = $field.InternalName + Title = $field.Title + TypeAsString = $field.TypeAsString + TextValue = Get-FieldTextValue -Field $field -RawValue $rawValue + Value = Convert-SPFieldValue $rawValue + } + } + } + + 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) { + [PSCustomObject]@{ + Id = $item.ID + UniqueId = $item.UniqueId.ToString() + ContentTypeId = $item.ContentTypeId.ToString() + ContentTypeName = $item.ContentType.Name + 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" + Fields = $fieldMetadata + } + } + 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 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 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]$MetadataRootPath, + + [Parameter(Mandatory = $true)] + [string]$ManifestPath + ) + + $relativeSegments = Get-RelativeFilePathSegments -List $List -File $File + $relativeDirectorySegments = @() + + if ($relativeSegments.Count -gt 1) { + $relativeDirectorySegments = $relativeSegments[0..($relativeSegments.Count - 2)] + } + + $safeLibraryName = Get-SafePathSegment $List.Title + $fileDirectory = Combine-PathSegments -BasePath ([System.IO.Path]::Combine($FilesRootPath, $safeLibraryName)) -Segments $relativeDirectorySegments + $metadataDirectory = Combine-PathSegments -BasePath ([System.IO.Path]::Combine($MetadataRootPath, $safeLibraryName)) -Segments $relativeDirectorySegments + + Ensure-Directory -Path $fileDirectory + Ensure-Directory -Path $metadataDirectory + + $fileName = $relativeSegments[-1] + $localFilePath = [System.IO.Path]::Combine($fileDirectory, $fileName) + $metadataPath = [System.IO.Path]::Combine($metadataDirectory, "$fileName.metadata.json") + + [System.IO.File]::WriteAllBytes($localFilePath, $File.OpenBinary()) + + $metadata = Get-FileMetadataObject -Web $Web -List $List -File $File + Write-MetadataJson -MetadataObject $metadata -Path $metadataPath + + $manifestRow = [PSCustomObject]@{ + WebUrl = $Web.Url + LibraryTitle = $List.Title + FileName = $File.Name + FileServerRelativeUrl = $File.ServerRelativeUrl + FileUniqueId = $File.UniqueId.ToString() + FileLength = $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]$MetadataRootPath, + + [Parameter(Mandatory = $true)] + [string]$ManifestPath, + + [ref]$FileCount + ) + + foreach ($file in $Folder.Files) { + Export-SPFile -Web $Web -List $List -File $file -FilesRootPath $FilesRootPath -MetadataRootPath $MetadataRootPath -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 -MetadataRootPath $MetadataRootPath -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]$MetadataRootPath, + + [Parameter(Mandatory = $true)] + [string]$ManifestPath + ) + + Write-Host ("Exportiere Bibliothek: {0}" -f $List.Title) + $fileCount = 0 + + Export-SPFolder -Web $Web -List $List -Folder $List.RootFolder -FilesRootPath $FilesRootPath -MetadataRootPath $MetadataRootPath -ManifestPath $ManifestPath -FileCount ([ref]$fileCount) + + Write-Host (" Dateien exportiert: {0}" -f $fileCount) +} + +Initialize-SharePointPowerShell + +$resolvedOutputPath = [System.IO.Path]::GetFullPath($OutputPath) +$filesRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Files") +$metadataRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Metadata") +$manifestPath = [System.IO.Path]::Combine($resolvedOutputPath, "manifest.csv") + +Ensure-Directory -Path $resolvedOutputPath +Ensure-Directory -Path $filesRootPath +Ensure-Directory -Path $metadataRootPath + +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 $_.IsCatalog + } + ) + + if ($documentLibraries.Count -eq 0) { + Write-Warning ("Keine Dokumentbibliotheken im Web gefunden: {0}" -f $WebUrl) + return + } + + Write-Host ("Gefundene Bibliotheken: {0}" -f $documentLibraries.Count) + + foreach ($library in $documentLibraries) { + Export-SPDocumentLibrary -Web $web -List $library -FilesRootPath $filesRootPath -MetadataRootPath $metadataRootPath -ManifestPath $manifestPath + } + + Write-Host ("Export abgeschlossen. Ausgabe: {0}" -f $resolvedOutputPath) +} +finally { + if ($null -ne $web) { + $web.Dispose() + } +}