diff --git a/Robocopy镜像文件小程序/MirrorFiles.ps1 b/Robocopy镜像文件小程序/MirrorFiles v0.1.ps1 similarity index 97% rename from Robocopy镜像文件小程序/MirrorFiles.ps1 rename to Robocopy镜像文件小程序/MirrorFiles v0.1.ps1 index 8b0309f..5626ac2 100644 --- a/Robocopy镜像文件小程序/MirrorFiles.ps1 +++ b/Robocopy镜像文件小程序/MirrorFiles v0.1.ps1 @@ -1,49 +1,49 @@ -param( - [Parameter(Mandatory=$true)] - [string]$SourcePath, - - [Parameter(Mandatory=$true)] - [string]$TargetPath -) - -try { - # Validate paths - if (-not (Test-Path $SourcePath)) { - throw "Source path does not exist: $SourcePath" - } - if (-not (Test-Path $TargetPath)) { - throw "Target path does not exist: $TargetPath" - } - - # Create log file path - $logFile = "$PSScriptRoot\MirrorLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" - - Write-Host "[$(Get-Date)] Starting mirror operation..." - Write-Host "Source: $SourcePath" - Write-Host "Target: $TargetPath" - Write-Host "Log file: $logFile" - - # Robocopy parameters: - # /MIR = Mirror mode (purge files in destination not in source) - # /NP = No progress percentage - # /NDL = No directory logging - # /LOG+: Append to log file - # /TEE = Output to console and log file - # /R:5 = 5 retries on failed files - # /W:5 = 5 second wait between retries - $robocopyArgs = @($SourcePath, $TargetPath, "/MIR", "/NP", "/NDL", "/LOG+:$logFile", "/TEE", "/R:5", "/W:5") - - $process = Start-Process robocopy -ArgumentList $robocopyArgs -NoNewWindow -PassThru -Wait - - # Robocopy exit codes: https://ss64.com/nt/robocopy-exit.html - if ($process.ExitCode -ge 8) { - throw "Robocopy failed with exit code $($process.ExitCode)" - } - - Write-Host "[$(Get-Date)] Mirror operation completed successfully" -ForegroundColor Green - Write-Host "Total processing time: $($process.ExitTime - $process.StartTime)" -} -catch { - Write-Host "[ERROR] $_" -ForegroundColor Red - exit 1 +param( + [Parameter(Mandatory=$true)] + [string]$SourcePath, + + [Parameter(Mandatory=$true)] + [string]$TargetPath +) + +try { + # Validate paths + if (-not (Test-Path $SourcePath)) { + throw "Source path does not exist: $SourcePath" + } + if (-not (Test-Path $TargetPath)) { + throw "Target path does not exist: $TargetPath" + } + + # Create log file path + $logFile = "$PSScriptRoot\MirrorLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" + + Write-Host "[$(Get-Date)] Starting mirror operation..." + Write-Host "Source: $SourcePath" + Write-Host "Target: $TargetPath" + Write-Host "Log file: $logFile" + + # Robocopy parameters: + # /MIR = Mirror mode (purge files in destination not in source) + # /NP = No progress percentage + # /NDL = No directory logging + # /LOG+: Append to log file + # /TEE = Output to console and log file + # /R:5 = 5 retries on failed files + # /W:5 = 5 second wait between retries + $robocopyArgs = @($SourcePath, $TargetPath, "/MIR", "/NP", "/NDL", "/LOG+:$logFile", "/TEE", "/R:5", "/W:5") + + $process = Start-Process robocopy -ArgumentList $robocopyArgs -NoNewWindow -PassThru -Wait + + # Robocopy exit codes: https://ss64.com/nt/robocopy-exit.html + if ($process.ExitCode -ge 8) { + throw "Robocopy failed with exit code $($process.ExitCode)" + } + + Write-Host "[$(Get-Date)] Mirror operation completed successfully" -ForegroundColor Green + Write-Host "Total processing time: $($process.ExitTime - $process.StartTime)" +} +catch { + Write-Host "[ERROR] $_" -ForegroundColor Red + exit 1 } \ No newline at end of file diff --git a/Robocopy镜像文件小程序/MirrorFiles v1.1.ps1 b/Robocopy镜像文件小程序/MirrorFiles v0.9.ps1 similarity index 97% rename from Robocopy镜像文件小程序/MirrorFiles v1.1.ps1 rename to Robocopy镜像文件小程序/MirrorFiles v0.9.ps1 index 3e95ac7..54217b6 100644 --- a/Robocopy镜像文件小程序/MirrorFiles v1.1.ps1 +++ b/Robocopy镜像文件小程序/MirrorFiles v0.9.ps1 @@ -1,138 +1,138 @@ -param( - [string]$SourcePath, - [string]$TargetPath -) - -Add-Type -AssemblyName System.Windows.Forms -Add-Type -AssemblyName System.Drawing - -function Show-GUI { - $form = New-Object System.Windows.Forms.Form - $form.Text = "Folder Mirror Tool" - $form.Size = New-Object System.Drawing.Size(600,400) - $form.StartPosition = "CenterScreen" - - # Source Controls - $lblSource = New-Object Windows.Forms.Label - $lblSource.Location = New-Object Drawing.Point(10,10) - $lblSource.Text = "Source Path:" - $txtSource = New-Object Windows.Forms.TextBox - $txtSource.Location = New-Object Drawing.Point(120,10) - $txtSource.Size = New-Object Drawing.Size(350,20) - $btnSource = New-Object Windows.Forms.Button - $btnSource.Location = New-Object Drawing.Point(480,10) - $btnSource.Text = "Browse" - $btnSource.Add_Click({ - $folder = New-Object Windows.Forms.FolderBrowserDialog - if($folder.ShowDialog() -eq "OK") { $txtSource.Text = $folder.SelectedPath } - }) - - # Target Controls - $lblTarget = New-Object Windows.Forms.Label - $lblTarget.Location = New-Object Drawing.Point(10,40) - $lblTarget.Text = "Target Path:" - $txtTarget = New-Object Windows.Forms.TextBox - $txtTarget.Location = New-Object Drawing.Point(120,40) - $txtTarget.Size = New-Object Drawing.Size(350,20) - $btnTarget = New-Object Windows.Forms.Button - $btnTarget.Location = New-Object Drawing.Point(480,40) - $btnTarget.Text = "Browse" - $btnTarget.Add_Click({ - $folder = New-Object Windows.Forms.FolderBrowserDialog - if($folder.ShowDialog() -eq "OK") { $txtTarget.Text = $folder.SelectedPath } - }) - - # Log Box - $txtLog = New-Object Windows.Forms.RichTextBox - $txtLog.Location = New-Object Drawing.Point(10,70) - $txtLog.Size = New-Object Drawing.Size(560,280) - $txtLog.ReadOnly = $true - - # Start Button - $btnStart = New-Object Windows.Forms.Button - $btnStart.Location = New-Object Drawing.Point(480,350) - $btnStart.Size = New-Object Drawing.Size(90,30) - $btnStart.Text = "Start Mirror" - $btnStart.Add_Click({ - $script:SourcePath = $txtSource.Text - $script:TargetPath = $txtTarget.Text - try { - # Reuse existing validation and mirror logic - if (-not (Test-Path $SourcePath)) { throw "Source path invalid" } - if (-not (Test-Path $TargetPath)) { throw "Target path invalid" } - - $txtLog.Text += "Starting mirror operation...`n" - $global:logFile = "$PSScriptRoot\MirrorLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" - - Write-Host "Source: $SourcePath" - Write-Host "Target: $TargetPath" - Write-Host "Log file: $logFile" - - # Robocopy parameters: - # /MIR = Mirror mode (purge files in destination not in source) - # /NP = No progress percentage - # /NDL = No directory logging - # /LOG+: Append to log file - # /TEE = Output to console and log file - # /R:5 = 5 retries on failed files - # /W:5 = 5 second wait between retries - $robocopyArgs = @($SourcePath, $TargetPath, "/MIR", "/NP", "/NDL", "/LOG+:$logFile", "/TEE", "/R:5", "/W:5") - $process = Start-Process robocopy -ArgumentList $robocopyArgs -NoNewWindow -PassThru -Wait - - if ($process.ExitCode -ge 8) { - throw "Robocopy failed with exit code $($process.ExitCode)" - } - - $txtLog.Text += "Operation completed successfully!`n" - } - catch { - $txtLog.Text += "[ERROR] $_`n" - [Windows.Forms.MessageBox]::Show($_.Exception.Message, "Error") - } - }) - - # Add controls to form - $form.Controls.AddRange(@($lblSource, $txtSource, $btnSource, $lblTarget, $txtTarget, $btnTarget, $txtLog, $btnStart)) - [void]$form.ShowDialog() -} - -# Show GUI if no parameters provided -if(-not $PSBoundParameters.ContainsKey('SourcePath') -or - -not $PSBoundParameters.ContainsKey('TargetPath')) { - Show-GUI -} -else { - try { - if (-not (Test-Path $SourcePath)) { throw "Source path invalid" } - if (-not (Test-Path $TargetPath)) { throw "Target path invalid" } - - $logFile = "$PSScriptRoot\MirrorLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" - Write-Host "[$(Get-Date)] Starting mirror operation..." - - Write-Host "Source: $SourcePath" - Write-Host "Target: $TargetPath" - Write-Host "Log file: $logFile" - - # Robocopy parameters: - # /MIR = Mirror mode (purge files in destination not in source) - # /NP = No progress percentage - # /NDL = No directory logging - # /LOG+: Append to log file - # /TEE = Output to console and log file - # /R:5 = 5 retries on failed files - # /W:5 = 5 second wait between retries - $robocopyArgs = @($SourcePath, $TargetPath, "/MIR", "/NP", "/NDL", "/LOG+:$logFile", "/TEE", "/R:5", "/W:5") - $process = Start-Process robocopy -ArgumentList $robocopyArgs -NoNewWindow -PassThru -Wait - - if ($process.ExitCode -ge 8) { - throw "Robocopy failed with exit code $($process.ExitCode)" - } - - Write-Host "[$(Get-Date)] Mirror operation completed successfully" -ForegroundColor Green - Write-Host "Total processing time: $($process.ExitTime - $process.StartTime)" - } - catch { - Write-Host "[ERROR] $_" -ForegroundColor Red - exit 1 - } +param( + [string]$SourcePath, + [string]$TargetPath +) + +Add-Type -AssemblyName System.Windows.Forms +Add-Type -AssemblyName System.Drawing + +function Show-GUI { + $form = New-Object System.Windows.Forms.Form + $form.Text = "Folder Mirror Tool" + $form.Size = New-Object System.Drawing.Size(600,400) + $form.StartPosition = "CenterScreen" + + # Source Controls + $lblSource = New-Object Windows.Forms.Label + $lblSource.Location = New-Object Drawing.Point(10,10) + $lblSource.Text = "Source Path:" + $txtSource = New-Object Windows.Forms.TextBox + $txtSource.Location = New-Object Drawing.Point(120,10) + $txtSource.Size = New-Object Drawing.Size(350,20) + $btnSource = New-Object Windows.Forms.Button + $btnSource.Location = New-Object Drawing.Point(480,10) + $btnSource.Text = "Browse" + $btnSource.Add_Click({ + $folder = New-Object Windows.Forms.FolderBrowserDialog + if($folder.ShowDialog() -eq "OK") { $txtSource.Text = $folder.SelectedPath } + }) + + # Target Controls + $lblTarget = New-Object Windows.Forms.Label + $lblTarget.Location = New-Object Drawing.Point(10,40) + $lblTarget.Text = "Target Path:" + $txtTarget = New-Object Windows.Forms.TextBox + $txtTarget.Location = New-Object Drawing.Point(120,40) + $txtTarget.Size = New-Object Drawing.Size(350,20) + $btnTarget = New-Object Windows.Forms.Button + $btnTarget.Location = New-Object Drawing.Point(480,40) + $btnTarget.Text = "Browse" + $btnTarget.Add_Click({ + $folder = New-Object Windows.Forms.FolderBrowserDialog + if($folder.ShowDialog() -eq "OK") { $txtTarget.Text = $folder.SelectedPath } + }) + + # Log Box + $txtLog = New-Object Windows.Forms.RichTextBox + $txtLog.Location = New-Object Drawing.Point(10,70) + $txtLog.Size = New-Object Drawing.Size(560,280) + $txtLog.ReadOnly = $true + + # Start Button + $btnStart = New-Object Windows.Forms.Button + $btnStart.Location = New-Object Drawing.Point(480,350) + $btnStart.Size = New-Object Drawing.Size(90,30) + $btnStart.Text = "Start Mirror" + $btnStart.Add_Click({ + $script:SourcePath = $txtSource.Text + $script:TargetPath = $txtTarget.Text + try { + # Reuse existing validation and mirror logic + if (-not (Test-Path $SourcePath)) { throw "Source path invalid" } + if (-not (Test-Path $TargetPath)) { throw "Target path invalid" } + + $txtLog.Text += "Starting mirror operation...`n" + $global:logFile = "$PSScriptRoot\MirrorLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" + + Write-Host "Source: $SourcePath" + Write-Host "Target: $TargetPath" + Write-Host "Log file: $logFile" + + # Robocopy parameters: + # /MIR = Mirror mode (purge files in destination not in source) + # /NP = No progress percentage + # /NDL = No directory logging + # /LOG+: Append to log file + # /TEE = Output to console and log file + # /R:5 = 5 retries on failed files + # /W:5 = 5 second wait between retries + $robocopyArgs = @($SourcePath, $TargetPath, "/MIR", "/NP", "/NDL", "/LOG+:$logFile", "/TEE", "/R:5", "/W:5") + $process = Start-Process robocopy -ArgumentList $robocopyArgs -NoNewWindow -PassThru -Wait + + if ($process.ExitCode -ge 8) { + throw "Robocopy failed with exit code $($process.ExitCode)" + } + + $txtLog.Text += "Operation completed successfully!`n" + } + catch { + $txtLog.Text += "[ERROR] $_`n" + [Windows.Forms.MessageBox]::Show($_.Exception.Message, "Error") + } + }) + + # Add controls to form + $form.Controls.AddRange(@($lblSource, $txtSource, $btnSource, $lblTarget, $txtTarget, $btnTarget, $txtLog, $btnStart)) + [void]$form.ShowDialog() +} + +# Show GUI if no parameters provided +if(-not $PSBoundParameters.ContainsKey('SourcePath') -or + -not $PSBoundParameters.ContainsKey('TargetPath')) { + Show-GUI +} +else { + try { + if (-not (Test-Path $SourcePath)) { throw "Source path invalid" } + if (-not (Test-Path $TargetPath)) { throw "Target path invalid" } + + $logFile = "$PSScriptRoot\MirrorLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" + Write-Host "[$(Get-Date)] Starting mirror operation..." + + Write-Host "Source: $SourcePath" + Write-Host "Target: $TargetPath" + Write-Host "Log file: $logFile" + + # Robocopy parameters: + # /MIR = Mirror mode (purge files in destination not in source) + # /NP = No progress percentage + # /NDL = No directory logging + # /LOG+: Append to log file + # /TEE = Output to console and log file + # /R:5 = 5 retries on failed files + # /W:5 = 5 second wait between retries + $robocopyArgs = @($SourcePath, $TargetPath, "/MIR", "/NP", "/NDL", "/LOG+:$logFile", "/TEE", "/R:5", "/W:5") + $process = Start-Process robocopy -ArgumentList $robocopyArgs -NoNewWindow -PassThru -Wait + + if ($process.ExitCode -ge 8) { + throw "Robocopy failed with exit code $($process.ExitCode)" + } + + Write-Host "[$(Get-Date)] Mirror operation completed successfully" -ForegroundColor Green + Write-Host "Total processing time: $($process.ExitTime - $process.StartTime)" + } + catch { + Write-Host "[ERROR] $_" -ForegroundColor Red + exit 1 + } } \ No newline at end of file diff --git a/Robocopy镜像文件小程序/MirrorFiles v1.2.ps1 b/Robocopy镜像文件小程序/MirrorFiles v1.2.ps1 index 96bcea6..f5b1429 100644 --- a/Robocopy镜像文件小程序/MirrorFiles v1.2.ps1 +++ b/Robocopy镜像文件小程序/MirrorFiles v1.2.ps1 @@ -9,9 +9,9 @@ Add-Type -AssemblyName System.Drawing function Show-GUI { $form = New-Object System.Windows.Forms.Form $form.Text = "Folder Mirror Tool" - $form.Size = New-Object System.Drawing.Size(600,450) # Increased height + $form.Size = New-Object System.Drawing.Size(600,450) $form.StartPosition = "CenterScreen" - $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog # Disable resizing + $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog # Source Controls $lblSource = New-Object Windows.Forms.Label @@ -44,68 +44,175 @@ function Show-GUI { }) # Log Box - $txtLog = New-Object Windows.Forms.RichTextBox - $txtLog.Location = New-Object Drawing.Point(10,70) - $txtLog.Size = New-Object Drawing.Size(560,300) # Increased height - $txtLog.ReadOnly = $true + $script:txtLogGui = New-Object Windows.Forms.RichTextBox + $script:txtLogGui.Location = New-Object Drawing.Point(10,70) + $script:txtLogGui.Size = New-Object Drawing.Size(560,300) + $script:txtLogGui.ReadOnly = $true + $script:txtLogGui.Font = New-Object System.Drawing.Font("Consolas", 8) + $script:txtLogGui.WordWrap = $false + $script:txtLogGui.ScrollBars = [System.Windows.Forms.RichTextBoxScrollBars]::Both # Start Button - $btnStart = New-Object Windows.Forms.Button - $btnStart.Location = New-Object Drawing.Point(480,370) # Adjusted position - $btnStart.Size = New-Object Drawing.Size(90,30) - $btnStart.Text = "Start Mirror" - $btnStart.Add_Click({ - $script:SourcePath = $txtSource.Text - $script:TargetPath = $txtTarget.Text + $script:btnStartGui = New-Object Windows.Forms.Button + $script:btnStartGui.Location = New-Object Drawing.Point(480,380) + $script:btnStartGui.Size = New-Object Drawing.Size(90,30) + $script:btnStartGui.Text = "Start Mirror" + $script:btnStartGui.Add_Click({ + $currentSourcePath = $txtSource.Text + $currentTargetPath = $txtTarget.Text + try { - # Reuse existing validation and mirror logic - if (-not (Test-Path $SourcePath)) { throw "Source path invalid" } - if (-not (Test-Path $TargetPath)) { throw "Target path invalid" } - - $txtLog.Text += "Starting mirror operation...`n" - $global:logFile = "$PSScriptRoot\MirrorLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" - - Write-Host "Source: $SourcePath" - Write-Host "Target: $TargetPath" - Write-Host "Log file: $logFile" - - # Robocopy parameters: - # /MIR = Mirror mode (purge files in destination not in source) - # /NP = No progress percentage - # /NDL = No directory logging - # /LOG+: Append to log file - # /TEE = Output to console and log file - # /R:5 = 5 retries on failed files - # /W:5 = 5 second wait between retries - $robocopyArgs = @($SourcePath, $TargetPath, "/MIR", "/NP", "/NDL", "/LOG+:$logFile", "/TEE", "/R:5", "/W:5") - $process = Start-Process robocopy -ArgumentList $robocopyArgs -NoNewWindow -PassThru -Wait - - if ($process.ExitCode -ge 8) { - throw "Robocopy failed with exit code $($process.ExitCode)" + if (-not (Test-Path $currentSourcePath -PathType Container)) { throw "Source path invalid or not a folder." } + if (-not (Test-Path $currentTargetPath -PathType Container)) { + try { + $script:txtLogGui.AppendText("Target path does not exist. Attempting to create: $currentTargetPath`n") + $null = New-Item -ItemType Directory -Path $currentTargetPath -Force -ErrorAction Stop + $script:txtLogGui.AppendText("Created target directory: $currentTargetPath`n") + } catch { + throw "Target path invalid or not a folder, and could not be created: $($_.Exception.Message)" + } } - $txtLog.Text += "Operation completed successfully!`n" + $script:txtLogGui.Clear() + $script:txtLogGui.AppendText("Starting mirror operation...`n") + $script:txtLogGui.AppendText("Source: $currentSourcePath`n") + $script:txtLogGui.AppendText("Target: $currentTargetPath`n") + + $global:logFile = "$PSScriptRoot\MirrorLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" + $script:txtLogGui.AppendText("Detailed log will also be saved to: $global:logFile`n`n") + + # Robocopy parameters for GUI (Verbose, but no per-file % progress to keep GUI clean) + $robocopyArgs = @( + "`"$currentSourcePath`"", + "`"$currentTargetPath`"", + "/MIR", # Mirror mode + "/V", # Verbose output (shows skipped files) + "/FP", # Include Full Pathname of files in output + "/BYTES", # Print sizes as bytes + "/NP", # No Progress percentage (to keep GUI log cleaner) + # Removed /NDL (No Directory List) -> will show directory list + # Removed /NJH (No Job Header) -> will show job header + # Removed /NJS (No Job Summary) -> will show job summary + "/LOG+:`"$global:logFile`"", + "/R:5", # 5 retries on failed files + "/W:5" # 5 second wait between retries + ) + + $script:btnStartGui.Enabled = $false + + $process = New-Object System.Diagnostics.Process + $process.StartInfo.FileName = "robocopy.exe" + $process.StartInfo.Arguments = $robocopyArgs -join " " + $process.StartInfo.UseShellExecute = $false + $process.StartInfo.RedirectStandardOutput = $true + $process.StartInfo.RedirectStandardError = $true + $process.StartInfo.CreateNoWindow = $true + $process.EnableRaisingEvents = $true + + $process.add_OutputDataReceived({ + if (-not [string]::IsNullOrEmpty($_.Data)) { + $script:txtLogGui.BeginInvoke([Action[string]]{ + param($line) + $script:txtLogGui.AppendText("$line`r`n") + $script:txtLogGui.ScrollToCaret() + }, $_.Data) + } + }) + + $process.add_ErrorDataReceived({ + if (-not [string]::IsNullOrEmpty($_.Data)) { + $script:txtLogGui.BeginInvoke([Action[string]]{ + param($line) + # Robocopy often uses stderr for summary or non-critical info too, + # so not always coloring it as a hard error. + # Let Robocopy's output speak for itself mostly. + $script:txtLogGui.AppendText("[STDERR] $line`r`n") + $script:txtLogGui.ScrollToCaret() + }, $_.Data) + } + }) + + $process.add_Exited({ + $exitCode = $process.ExitCode + $script:txtLogGui.BeginInvoke([Action]{ + $script:txtLogGui.AppendText("`n------------------------------------`n") + $script:txtLogGui.AppendText("Robocopy process finished.`n") + $script:txtLogGui.AppendText("Robocopy Exit Code: $exitCode`n") + + if ($exitCode -ge 8) { + $script:txtLogGui.SelectionStart = $script:txtLogGui.TextLength + $script:txtLogGui.SelectionLength = 0 + $script:txtLogGui.SelectionColor = [System.Drawing.Color]::Red + $script:txtLogGui.AppendText("Robocopy reported errors or critical failures (Code >= 8). Check log for details.`n") + $script:txtLogGui.SelectionColor = $script:txtLogGui.ForeColor + [Windows.Forms.MessageBox]::Show("Robocopy completed with errors or critical failures (Exit Code: $exitCode). Check Robocopy output and the log file for details.", "Robocopy Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) + } elseif ($exitCode -eq 0) { + $script:txtLogGui.AppendText("Robocopy: No files were copied. No failure was encountered. No files were mismatched. The files already exist in the destination directory; therefore, the copy operation was skipped.`n") + } elseif ($exitCode -gt 0 -and $exitCode -lt 8) { + $script:txtLogGui.SelectionStart = $script:txtLogGui.TextLength + $script:txtLogGui.SelectionLength = 0 + $script:txtLogGui.SelectionColor = [System.Drawing.Color]::DarkGreen + $script:txtLogGui.AppendText("Robocopy completed. Some files may have been copied, or extra/mismatched files detected (Code $exitCode). This is generally considered successful or informational. Review output for details.`n") + $script:txtLogGui.SelectionColor = $script:txtLogGui.ForeColor + } + $script:txtLogGui.AppendText("Log file: $global:logFile`n") + $script:txtLogGui.ScrollToCaret() + $script:btnStartGui.Enabled = $true + }) + $process.Close() + $process.Dispose() + }) + + $process.Start() | Out-Null + $process.BeginOutputReadLine() + $process.BeginErrorReadLine() } catch { - $txtLog.Text += "[ERROR] $_`n" - [Windows.Forms.MessageBox]::Show($_.Exception.Message, "Error") + $errorMessage = $_.Exception.Message + if ($_.Exception.InnerException) { $errorMessage += " Inner: " + $_.Exception.InnerException.Message } + $script:txtLogGui.AppendText("[SCRIPT ERROR] $errorMessage`n") + [Windows.Forms.MessageBox]::Show($errorMessage, "Script Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) + if ($script:btnStartGui) {$script:btnStartGui.Enabled = $true} } }) - # Add controls to form - $form.Controls.AddRange(@($lblSource, $txtSource, $btnSource, $lblTarget, $txtTarget, $btnTarget, $txtLog, $btnStart)) + $form.Controls.AddRange(@( + $lblSource, $txtSource, $btnSource, + $lblTarget, $txtTarget, $btnTarget, + $script:txtLogGui, + $script:btnStartGui + )) + + if ($SourcePath) { $txtSource.Text = $SourcePath } + if ($TargetPath) { $txtTarget.Text = $TargetPath } + + $form.Add_Shown({ + # If parameters were passed and GUI is shown, optionally auto-start + # For now, we require manual click even if params are pre-filled. + # if ($txtSource.Text -and $txtTarget.Text) { $script:btnStartGui.PerformClick() } + }) + [void]$form.ShowDialog() } -# Show GUI if no parameters provided -if(-not $PSBoundParameters.ContainsKey('SourcePath') -or - -not $PSBoundParameters.ContainsKey('TargetPath')) { - Show-GUI +# --- Main script execution logic --- + +if (-not $PSBoundParameters.ContainsKey('SourcePath') -or -not $PSBoundParameters.ContainsKey('TargetPath')) { + Show-GUI # Pass potentially existing parameters for pre-filling } else { + $cliErrorOccurred = $false try { - if (-not (Test-Path $SourcePath)) { throw "Source path invalid" } - if (-not (Test-Path $TargetPath)) { throw "Target path invalid" } + if (-not (Test-Path $SourcePath -PathType Container)) { throw "Source path '$SourcePath' invalid or not a folder." } + if (-not (Test-Path $TargetPath -PathType Container)) { + Write-Host "Target path '$TargetPath' does not exist. Attempting to create..." -ForegroundColor Yellow + try { + $null = New-Item -ItemType Directory -Path $TargetPath -Force -ErrorAction Stop + Write-Host "Created target directory: $TargetPath" -ForegroundColor Green + } catch { + throw "Target path '$TargetPath' invalid or not a folder, and could not be created: $($_.Exception.Message)" + } + } $logFile = "$PSScriptRoot\MirrorLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" Write-Host "[$(Get-Date)] Starting mirror operation..." @@ -114,26 +221,58 @@ else { Write-Host "Target: $TargetPath" Write-Host "Log file: $logFile" - # Robocopy parameters: - # /MIR = Mirror mode (purge files in destination not in source) - # /NP = No progress percentage - # /NDL = No directory logging - # /LOG+: Append to log file - # /TEE = Output to console and log file - # /R:5 = 5 retries on failed files - # /W:5 = 5 second wait between retries - $robocopyArgs = @($SourcePath, $TargetPath, "/MIR", "/NP", "/NDL", "/LOG+:$logFile", "/TEE", "/R:5", "/W:5") - $process = Start-Process robocopy -ArgumentList $robocopyArgs -NoNewWindow -PassThru -Wait + # Robocopy parameters for CLI (Fully verbose, including progress) + $robocopyArgs = @( + "`"$SourcePath`"", + "`"$TargetPath`"", + "/MIR", # Mirror mode + "/V", # Verbose output (shows skipped files) + "/FP", # Include Full Pathname of files in output + "/BYTES", # Print sizes as bytes + # Removed /NP -> will show progress percentage + # Removed /NDL -> will show directory list + # Removed /NJH -> will show job header + # Removed /NJS -> will show job summary + "/TEE", # Output to console AND log file + "/LOG+:`"$logFile`"", + "/R:5", + "/W:5" + ) + Write-Host "Executing: robocopy $($robocopyArgs -join " ")" + Write-Host "------------------------------------ ROBOCOPY OUTPUT START ------------------------------------" + $process = Start-Process robocopy -ArgumentList $robocopyArgs -NoNewWindow -PassThru -Wait + Write-Host "------------------------------------ ROBOCOPY OUTPUT END --------------------------------------" + Write-Host "Robocopy Exit Code: $($process.ExitCode)" + + if ($process.ExitCode -ge 8) { - throw "Robocopy failed with exit code $($process.ExitCode)" + throw "Robocopy reported errors or critical failures with exit code $($process.ExitCode). Check output above and log: $logFile" + } elseif ($process.ExitCode -eq 0) { + Write-Host "[$(Get-Date)] Robocopy: No files were copied. No failure was encountered. No files were mismatched. (Exit Code: 0)" -ForegroundColor Cyan + } else { # 1 to 7 + Write-Host "[$(Get-Date)] Robocopy completed. Some files may have been copied, or extra/mismatched files detected (Exit Code: $($process.ExitCode)). This is generally considered successful or informational. Review output for details." -ForegroundColor Cyan } - Write-Host "[$(Get-Date)] Mirror operation completed successfully" -ForegroundColor Green - Write-Host "Total processing time: $($process.ExitTime - $process.StartTime)" + Write-Host "[$(Get-Date)] Mirror operation main phase completed." -ForegroundColor Green + if ($process.ExitTime -and $process.StartTime) { + Write-Host "Total Robocopy processing time: $($process.ExitTime - $process.StartTime)" + } } catch { - Write-Host "[ERROR] $_" -ForegroundColor Red - exit 1 + Write-Host "[SCRIPT ERROR] $($_.Exception.Message)" -ForegroundColor Red + if ($_.Exception.InnerException) { Write-Host "[INNER SCRIPT ERROR] $($_.Exception.InnerException.Message)" -ForegroundColor Red } + $cliErrorOccurred = $true + } + finally { + if ($cliErrorOccurred) { + Write-Host "Operation finished with errors." -ForegroundColor Red + } else { + Write-Host "Operation finished." -ForegroundColor Green + } + Read-Host "Press Enter to exit..." + if ($cliErrorOccurred) { + if ($Host.Name -eq "ConsoleHost") { exit 1 } # Exit with error code for automation + } } } \ No newline at end of file