diff --git a/Start-SPMigrationGUI.ps1 b/Start-SPMigrationGUI.ps1 index 084c91b..b3f570a 100644 --- a/Start-SPMigrationGUI.ps1 +++ b/Start-SPMigrationGUI.ps1 @@ -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] + try { + $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 { - return + elseif ($null -ne $fileStream) { + $fileStream.Dispose() + } + } + + if ([string]::IsNullOrEmpty($text)) { + return + } + + foreach ($line in ($text -split "`r?`n")) { + if ([string]::IsNullOrWhiteSpace($line)) { + continue } - $text = Convert-RecordToLogText -Record $record - if (-not [string]::IsNullOrWhiteSpace($text)) { - Write-UILog -Message $text -Level $Level + $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-NewMigrationPowerShellRecords { - if ($null -eq $script:MigrationPowerShell) { - 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 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,17 +1197,23 @@ 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 - $success = $true - } - catch { - Write-NewMigrationPowerShellRecords - $errorMessage = $_.Exception.Message - Write-UILog -Message $errorMessage -Level "ERROR" + if ($script:MigrationProcess.ExitCode -eq 0) { + $success = $true + } + 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 @@ -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() +