Refactor migration process handling and improve error logging in Start-SPMigrationGUI.ps1

This commit is contained in:
Torsten Brendgen
2026-06-29 17:02:36 +02:00
parent 369bd284a1
commit bbe83e55ad

View File

@@ -799,14 +799,15 @@ $script:CurrentMappingMeta = [ordered]@{
SourceWebUrl = "" SourceWebUrl = ""
} }
$script:IsMigrationRunning = $false $script:IsMigrationRunning = $false
$script:MigrationPowerShell = $null $script:MigrationProcess = $null
$script:MigrationRunspace = $null $script:MigrationOutputPath = ""
$script:MigrationAsyncResult = $null $script:MigrationParameterPath = ""
$script:MigrationOutput = $null $script:MigrationWrapperPath = ""
$script:MigrationTimer = $null $script:MigrationTimer = $null
$script:MigrationStreamPositions = @{} $script:MigrationStreamPositions = @{}
$script:MigrationCompletedHandler = $null $script:MigrationCompletedHandler = $null
$script:MigrationActionLabel = "" $script:MigrationActionLabel = ""
$script:LastMigrationErrorMessage = ""
function Get-ObjectPropertyValue { function Get-ObjectPropertyValue {
param( param(
@@ -1048,89 +1049,146 @@ function Set-MigrationUiBusy {
function Reset-MigrationStreamPositions { function Reset-MigrationStreamPositions {
$script:MigrationStreamPositions = @{ $script:MigrationStreamPositions = @{
Output = 0 Output = [long]0
Error = 0
Warning = 0
Verbose = 0
Debug = 0
Information = 0
} }
} }
function Write-NewPowerShellRecords { function Get-MigrationHostPath {
param( $windowsPowerShellCandidates = @(
$Collection, (Join-Path -Path $env:WINDIR -ChildPath "Sysnative\WindowsPowerShell\v1.0\powershell.exe"),
(Join-Path -Path $env:WINDIR -ChildPath "System32\WindowsPowerShell\v1.0\powershell.exe")
[Parameter(Mandatory = $true)]
[string]$PositionKey,
[ValidateSet("INFO", "WARN", "ERROR")]
[string]$Level = "INFO"
) )
if ($null -eq $Collection -or -not $script:MigrationStreamPositions.ContainsKey($PositionKey)) { foreach ($candidate in $windowsPowerShellCandidates) {
if ([System.IO.File]::Exists($candidate)) {
return $candidate
}
}
$currentProcessPath = (Get-Process -Id $PID).Path
if (-not [string]::IsNullOrWhiteSpace($currentProcessPath) -and [System.IO.File]::Exists($currentProcessPath)) {
return $currentProcessPath
}
throw "Kein PowerShell-Host fuer die Migration gefunden."
}
function Get-SingleQuotedPowerShellLiteral {
param(
[Parameter(Mandatory = $true)]
[string]$Value
)
return "'" + $Value.Replace("'", "''") + "'"
}
function Write-NewFileRecords {
param(
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$PositionKey
)
if ([string]::IsNullOrWhiteSpace($Path) -or -not [System.IO.File]::Exists($Path) -or -not $script:MigrationStreamPositions.ContainsKey($PositionKey)) {
return return
} }
while ($script:MigrationStreamPositions[$PositionKey] -lt $Collection.Count) { $fileStream = $null
$index = [int]$script:MigrationStreamPositions[$PositionKey] $reader = $null
$script:MigrationStreamPositions[$PositionKey] = $index + 1 $text = $null
try { try {
$record = $Collection[$index] $fileStream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
[void]$fileStream.Seek([long]$script:MigrationStreamPositions[$PositionKey], [System.IO.SeekOrigin]::Begin)
$reader = New-Object System.IO.StreamReader($fileStream, [System.Text.UTF8Encoding]::new($false), $true)
$text = $reader.ReadToEnd()
$script:MigrationStreamPositions[$PositionKey] = $fileStream.Position
}
catch {
return
}
finally {
if ($null -ne $reader) {
$reader.Dispose()
} }
catch { elseif ($null -ne $fileStream) {
return $fileStream.Dispose()
}
}
if ([string]::IsNullOrEmpty($text)) {
return
}
foreach ($line in ($text -split "`r?`n")) {
if ([string]::IsNullOrWhiteSpace($line)) {
continue
} }
$text = Convert-RecordToLogText -Record $record $level = "INFO"
if (-not [string]::IsNullOrWhiteSpace($text)) { $message = $line
Write-UILog -Message $text -Level $Level if ($line -match '^\[(INFO|WARN|ERROR)\]\s*(.*)$') {
$level = $matches[1]
$message = $matches[2]
} }
if ([string]::IsNullOrWhiteSpace($message)) {
continue
}
if ($level -eq "ERROR") {
$script:LastMigrationErrorMessage = $message
}
Write-UILog -Message $message -Level $level
} }
} }
function Write-NewMigrationPowerShellRecords { function Write-NewMigrationProcessRecords {
if ($null -eq $script:MigrationPowerShell) { Write-NewFileRecords -Path $script:MigrationOutputPath -PositionKey "Output"
return
}
Write-NewPowerShellRecords -Collection $script:MigrationOutput -PositionKey "Output" -Level "INFO"
Write-NewPowerShellRecords -Collection $script:MigrationPowerShell.Streams.Warning -PositionKey "Warning" -Level "WARN"
Write-NewPowerShellRecords -Collection $script:MigrationPowerShell.Streams.Error -PositionKey "Error" -Level "ERROR"
Write-NewPowerShellRecords -Collection $script:MigrationPowerShell.Streams.Verbose -PositionKey "Verbose" -Level "INFO"
Write-NewPowerShellRecords -Collection $script:MigrationPowerShell.Streams.Debug -PositionKey "Debug" -Level "INFO"
Write-NewPowerShellRecords -Collection $script:MigrationPowerShell.Streams.Information -PositionKey "Information" -Level "INFO"
} }
function Clear-MigrationRunState { function Clear-MigrationRunState {
if ($null -ne $script:MigrationPowerShell) { if ($null -ne $script:MigrationProcess) {
$script:MigrationPowerShell.Dispose()
}
if ($null -ne $script:MigrationRunspace) {
$script:MigrationRunspace.Dispose()
}
if ($null -ne $script:MigrationOutput) {
try { try {
$script:MigrationOutput.Dispose() if (-not $script:MigrationProcess.HasExited) {
$script:MigrationProcess.Kill()
$script:MigrationProcess.WaitForExit()
}
}
catch {
}
$script:MigrationProcess.Dispose()
}
foreach ($path in @($script:MigrationWrapperPath, $script:MigrationParameterPath, $script:MigrationOutputPath)) {
if ([string]::IsNullOrWhiteSpace($path)) {
continue
}
try {
if (Test-Path -LiteralPath $path) {
Remove-Item -LiteralPath $path -Force -ErrorAction Stop
}
} }
catch { catch {
} }
} }
$script:MigrationPowerShell = $null $script:MigrationProcess = $null
$script:MigrationRunspace = $null $script:MigrationOutputPath = ""
$script:MigrationAsyncResult = $null $script:MigrationParameterPath = ""
$script:MigrationOutput = $null $script:MigrationWrapperPath = ""
$script:MigrationCompletedHandler = $null $script:MigrationCompletedHandler = $null
$script:MigrationActionLabel = "" $script:MigrationActionLabel = ""
$script:LastMigrationErrorMessage = ""
Reset-MigrationStreamPositions Reset-MigrationStreamPositions
} }
function Complete-MigrationScriptAsync { function Complete-MigrationScriptAsync {
if ($null -eq $script:MigrationPowerShell -or $null -eq $script:MigrationAsyncResult) { if ($null -eq $script:MigrationProcess) {
return return
} }
@@ -1139,17 +1197,23 @@ function Complete-MigrationScriptAsync {
$success = $false $success = $false
$errorMessage = $null $errorMessage = $null
Write-NewMigrationPowerShellRecords Write-NewMigrationProcessRecords
if (-not $script:MigrationProcess.HasExited) {
return
}
try { try {
[void]$script:MigrationPowerShell.EndInvoke($script:MigrationAsyncResult) if ($script:MigrationProcess.ExitCode -eq 0) {
Write-NewMigrationPowerShellRecords $success = $true
$success = $true }
} else {
catch { $errorMessage = $script:LastMigrationErrorMessage
Write-NewMigrationPowerShellRecords if ([string]::IsNullOrWhiteSpace($errorMessage)) {
$errorMessage = $_.Exception.Message $errorMessage = "Der Migrationsprozess wurde mit ExitCode $($script:MigrationProcess.ExitCode) beendet."
Write-UILog -Message $errorMessage -Level "ERROR" Write-UILog -Message $errorMessage -Level "ERROR"
}
}
} }
finally { finally {
Clear-MigrationRunState Clear-MigrationRunState
@@ -1182,9 +1246,9 @@ function Ensure-MigrationTimer {
$script:MigrationTimer = New-Object System.Windows.Forms.Timer $script:MigrationTimer = New-Object System.Windows.Forms.Timer
$script:MigrationTimer.Interval = 250 $script:MigrationTimer.Interval = 250
$script:MigrationTimer.Add_Tick({ $script:MigrationTimer.Add_Tick({
Write-NewMigrationPowerShellRecords Write-NewMigrationProcessRecords
if ($null -eq $script:MigrationAsyncResult -or -not $script:MigrationAsyncResult.IsCompleted) { if ($null -eq $script:MigrationProcess -or -not $script:MigrationProcess.HasExited) {
return return
} }
@@ -1213,28 +1277,99 @@ function Invoke-MigrationScript {
} }
Reset-MigrationStreamPositions Reset-MigrationStreamPositions
$script:MigrationRunspace = [runspacefactory]::CreateRunspace() $script:LastMigrationErrorMessage = ""
$script:MigrationRunspace.ApartmentState = [System.Threading.ApartmentState]::STA
$script:MigrationRunspace.ThreadOptions = [System.Management.Automation.Runspaces.PSThreadOptions]::ReuseThread
$script:MigrationRunspace.Open()
$script:MigrationPowerShell = [powershell]::Create()
$script:MigrationPowerShell.Runspace = $script:MigrationRunspace
$script:MigrationOutput = [System.Management.Automation.PSDataCollection[psobject]]::new()
$script:MigrationCompletedHandler = $OnCompleted $script:MigrationCompletedHandler = $OnCompleted
$script:MigrationActionLabel = $ActionLabel $script:MigrationActionLabel = $ActionLabel
$tempDirectory = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "StartSPMigrationGui"
if (-not [System.IO.Directory]::Exists($tempDirectory)) {
[System.IO.Directory]::CreateDirectory($tempDirectory) | Out-Null
}
$runId = [guid]::NewGuid().ToString("N")
$parameterPath = Join-Path -Path $tempDirectory -ChildPath ("migration-parameters-{0}.clixml" -f $runId)
$outputPath = Join-Path -Path $tempDirectory -ChildPath ("migration-output-{0}.log" -f $runId)
$wrapperPath = Join-Path -Path $tempDirectory -ChildPath ("migration-runner-{0}.ps1" -f $runId)
try { try {
[void]$script:MigrationPowerShell.AddCommand($script:MigrationScriptPath) Export-Clixml -LiteralPath $parameterPath -InputObject $Parameters
[void]$script:MigrationPowerShell.AddParameters($Parameters) [System.IO.File]::WriteAllText($outputPath, "", [System.Text.UTF8Encoding]::new($false))
$wrapperContent = @"
`$ErrorActionPreference = 'Stop'
`$parameters = Import-Clixml -LiteralPath $(Get-SingleQuotedPowerShellLiteral -Value $parameterPath)
`$logPath = $(Get-SingleQuotedPowerShellLiteral -Value $outputPath)
`$logDirectory = [System.IO.Path]::GetDirectoryName(`$logPath)
if (-not [System.IO.Directory]::Exists(`$logDirectory)) {
[System.IO.Directory]::CreateDirectory(`$logDirectory) | Out-Null
}
`$script:LogWriter = [System.IO.StreamWriter]::new(`$logPath, `$true, [System.Text.UTF8Encoding]::new(`$false))
`$script:LogWriter.AutoFlush = `$true
function Write-RunnerLog {
param(
[Parameter(Mandatory = `$true)]
[ValidateSet('INFO', 'WARN', 'ERROR')]
[string]`$Level,
[string]`$Message
)
if ([string]::IsNullOrWhiteSpace(`$Message)) {
return
}
`$script:LogWriter.WriteLine(('[{0}] {1}' -f `$Level, `$Message))
}
try {
& $(Get-SingleQuotedPowerShellLiteral -Value $script:MigrationScriptPath) @parameters *>&1 | ForEach-Object {
`$level = switch (`$_.GetType().FullName) {
'System.Management.Automation.ErrorRecord' { 'ERROR'; break }
'System.Management.Automation.WarningRecord' { 'WARN'; break }
default { 'INFO' }
}
Write-RunnerLog -Level `$level -Message ([string]`$_)
}
exit 0
}
catch {
Write-RunnerLog -Level 'ERROR' -Message `$_.Exception.Message
exit 1
}
finally {
if (`$null -ne `$script:LogWriter) {
`$script:LogWriter.Dispose()
}
}
"@
Set-Content -LiteralPath $wrapperPath -Value $wrapperContent -Encoding UTF8
$hostPath = Get-MigrationHostPath
$processStartInfo = New-Object System.Diagnostics.ProcessStartInfo
$processStartInfo.FileName = $hostPath
$processStartInfo.Arguments = ('-NoProfile -ExecutionPolicy Bypass -File "{0}"' -f $wrapperPath.Replace('"', '""'))
$processStartInfo.WorkingDirectory = Split-Path -Path $script:MigrationScriptPath -Parent
$processStartInfo.UseShellExecute = $false
$processStartInfo.CreateNoWindow = $true
$script:MigrationProcess = New-Object System.Diagnostics.Process
$script:MigrationProcess.StartInfo = $processStartInfo
if (-not $script:MigrationProcess.Start()) {
throw "Der Migrationsprozess konnte nicht gestartet werden."
}
$script:MigrationOutputPath = $outputPath
$script:MigrationParameterPath = $parameterPath
$script:MigrationWrapperPath = $wrapperPath
Set-MigrationUiBusy -IsBusy $true Set-MigrationUiBusy -IsBusy $true
Write-UILog -Message ("Starte {0}..." -f $ActionLabel) Write-UILog -Message ("Starte {0}..." -f $ActionLabel)
$inputCollection = [System.Management.Automation.PSDataCollection[psobject]]::new()
$inputCollection.Complete()
$script:MigrationAsyncResult = $script:MigrationPowerShell.BeginInvoke($inputCollection, $script:MigrationOutput)
Ensure-MigrationTimer Ensure-MigrationTimer
$script:MigrationTimer.Start() $script:MigrationTimer.Start()
} }
@@ -2787,3 +2922,4 @@ if ($NoGui.IsPresent) {
} }
$formBuilder.Show() $formBuilder.Show()