Refactor migration process handling and improve error logging in Start-SPMigrationGUI.ps1
This commit is contained in:
@@ -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 {
|
catch {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
$text = Convert-RecordToLogText -Record $record
|
if ($null -ne $reader) {
|
||||||
if (-not [string]::IsNullOrWhiteSpace($text)) {
|
$reader.Dispose()
|
||||||
Write-UILog -Message $text -Level $Level
|
}
|
||||||
|
elseif ($null -ne $fileStream) {
|
||||||
|
$fileStream.Dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function Write-NewMigrationPowerShellRecords {
|
if ([string]::IsNullOrEmpty($text)) {
|
||||||
if ($null -eq $script:MigrationPowerShell) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-NewPowerShellRecords -Collection $script:MigrationOutput -PositionKey "Output" -Level "INFO"
|
foreach ($line in ($text -split "`r?`n")) {
|
||||||
Write-NewPowerShellRecords -Collection $script:MigrationPowerShell.Streams.Warning -PositionKey "Warning" -Level "WARN"
|
if ([string]::IsNullOrWhiteSpace($line)) {
|
||||||
Write-NewPowerShellRecords -Collection $script:MigrationPowerShell.Streams.Error -PositionKey "Error" -Level "ERROR"
|
continue
|
||||||
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"
|
$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 {
|
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,18 +1197,24 @@ 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
|
||||||
}
|
}
|
||||||
catch {
|
else {
|
||||||
Write-NewMigrationPowerShellRecords
|
$errorMessage = $script:LastMigrationErrorMessage
|
||||||
$errorMessage = $_.Exception.Message
|
if ([string]::IsNullOrWhiteSpace($errorMessage)) {
|
||||||
|
$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
|
||||||
Set-MigrationUiBusy -IsBusy $false
|
Set-MigrationUiBusy -IsBusy $false
|
||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user