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 = ""
}
$script:IsMigrationRunning = $false
$script:MigrationPowerShell = $null
$script:MigrationRunspace = $null
$script:MigrationAsyncResult = $null
$script:MigrationOutput = $null
$script:MigrationProcess = $null
$script:MigrationOutputPath = ""
$script:MigrationParameterPath = ""
$script:MigrationWrapperPath = ""
$script:MigrationTimer = $null
$script:MigrationStreamPositions = @{}
$script:MigrationCompletedHandler = $null
$script:MigrationActionLabel = ""
$script:LastMigrationErrorMessage = ""
function Get-ObjectPropertyValue {
param(
@@ -1048,89 +1049,146 @@ function Set-MigrationUiBusy {
function Reset-MigrationStreamPositions {
$script:MigrationStreamPositions = @{
Output = 0
Error = 0
Warning = 0
Verbose = 0
Debug = 0
Information = 0
Output = [long]0
}
}
function Write-NewPowerShellRecords {
param(
$Collection,
[Parameter(Mandatory = $true)]
[string]$PositionKey,
[ValidateSet("INFO", "WARN", "ERROR")]
[string]$Level = "INFO"
function Get-MigrationHostPath {
$windowsPowerShellCandidates = @(
(Join-Path -Path $env:WINDIR -ChildPath "Sysnative\WindowsPowerShell\v1.0\powershell.exe"),
(Join-Path -Path $env:WINDIR -ChildPath "System32\WindowsPowerShell\v1.0\powershell.exe")
)
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
}
while ($script:MigrationStreamPositions[$PositionKey] -lt $Collection.Count) {
$index = [int]$script:MigrationStreamPositions[$PositionKey]
$script:MigrationStreamPositions[$PositionKey] = $index + 1
$fileStream = $null
$reader = $null
$text = $null
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
}
$text = Convert-RecordToLogText -Record $record
if (-not [string]::IsNullOrWhiteSpace($text)) {
Write-UILog -Message $text -Level $Level
finally {
if ($null -ne $reader) {
$reader.Dispose()
}
elseif ($null -ne $fileStream) {
$fileStream.Dispose()
}
}
function Write-NewMigrationPowerShellRecords {
if ($null -eq $script:MigrationPowerShell) {
if ([string]::IsNullOrEmpty($text)) {
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"
foreach ($line in ($text -split "`r?`n")) {
if ([string]::IsNullOrWhiteSpace($line)) {
continue
}
$level = "INFO"
$message = $line
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-NewMigrationProcessRecords {
Write-NewFileRecords -Path $script:MigrationOutputPath -PositionKey "Output"
}
function Clear-MigrationRunState {
if ($null -ne $script:MigrationPowerShell) {
$script:MigrationPowerShell.Dispose()
}
if ($null -ne $script:MigrationRunspace) {
$script:MigrationRunspace.Dispose()
}
if ($null -ne $script:MigrationOutput) {
if ($null -ne $script:MigrationProcess) {
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 {
}
}
$script:MigrationPowerShell = $null
$script:MigrationRunspace = $null
$script:MigrationAsyncResult = $null
$script:MigrationOutput = $null
$script:MigrationProcess = $null
$script:MigrationOutputPath = ""
$script:MigrationParameterPath = ""
$script:MigrationWrapperPath = ""
$script:MigrationCompletedHandler = $null
$script:MigrationActionLabel = ""
$script:LastMigrationErrorMessage = ""
Reset-MigrationStreamPositions
}
function Complete-MigrationScriptAsync {
if ($null -eq $script:MigrationPowerShell -or $null -eq $script:MigrationAsyncResult) {
if ($null -eq $script:MigrationProcess) {
return
}
@@ -1139,18 +1197,24 @@ function Complete-MigrationScriptAsync {
$success = $false
$errorMessage = $null
Write-NewMigrationPowerShellRecords
Write-NewMigrationProcessRecords
if (-not $script:MigrationProcess.HasExited) {
return
}
try {
[void]$script:MigrationPowerShell.EndInvoke($script:MigrationAsyncResult)
Write-NewMigrationPowerShellRecords
if ($script:MigrationProcess.ExitCode -eq 0) {
$success = $true
}
catch {
Write-NewMigrationPowerShellRecords
$errorMessage = $_.Exception.Message
else {
$errorMessage = $script:LastMigrationErrorMessage
if ([string]::IsNullOrWhiteSpace($errorMessage)) {
$errorMessage = "Der Migrationsprozess wurde mit ExitCode $($script:MigrationProcess.ExitCode) beendet."
Write-UILog -Message $errorMessage -Level "ERROR"
}
}
}
finally {
Clear-MigrationRunState
Set-MigrationUiBusy -IsBusy $false
@@ -1182,9 +1246,9 @@ function Ensure-MigrationTimer {
$script:MigrationTimer = New-Object System.Windows.Forms.Timer
$script:MigrationTimer.Interval = 250
$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
}
@@ -1213,28 +1277,99 @@ function Invoke-MigrationScript {
}
Reset-MigrationStreamPositions
$script:MigrationRunspace = [runspacefactory]::CreateRunspace()
$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:LastMigrationErrorMessage = ""
$script:MigrationCompletedHandler = $OnCompleted
$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 {
[void]$script:MigrationPowerShell.AddCommand($script:MigrationScriptPath)
[void]$script:MigrationPowerShell.AddParameters($Parameters)
Export-Clixml -LiteralPath $parameterPath -InputObject $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
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
$script:MigrationTimer.Start()
}
@@ -2787,3 +2922,4 @@ if ($NoGui.IsPresent) {
}
$formBuilder.Show()