File Migration (Robocopy) w/ Statistics
DISCLAIMER: The information in this guide is provided "as is" without any guarantee of completeness, accuracy, timeliness, or of the results obtained from the use of this information. The author assumes no responsibility for any errors or omissions in the content. It is meant for general information purposes only and should not be used as a substitute for professional advice. The author is not responsible for any damages caused by the use of this information. By using this guide, you agree to hold the author harmless from any and all claims, damages, or expenses that may arise from your use of the information.
Introduction
This script was originally written to process Robocopy command (.CMD) files sequentially, reporting statistics on each Robocopy job. Statistics include: Start Time, End Time, Total Process Duration, and any call errors when running the .CMD file. This information helps gauge migration and sync times for migration cut overs.
I've made every effort to design this PowerShell script in a manner that would require no modifications. The script will launch all included .CMD files relative to that path of the script. Simply place all .CMD files that need to be processed within the same folder as the script.
Additional Information:
- The PowerShell script will launch all included .CMD files that are included in the script's location/path.
- The PowerShell script will create .log files that are generated in the script's location/path.
- PowerShell script activity log naming convention:
<PowerShell Script Name>.log - Command (.CMD) process log naming convention:
<CMD Script Name>.log
- PowerShell script activity log naming convention:
Requirements
- Windows Machine w/ Administrative Rights.
- PowerShell.
Instructions
- Copy the following PowerShell script to the source machine (not the destination). Name it "migration.ps1":
# Script Variables - DO NOT TOUCH! $scriptPath = $PSScriptRoot $scriptName = ($MyInvocation.MyCommand.Name) -replace '\.[^.]+$' $scriptLog = "$scriptPath\$scriptName.log" # Get a list of .cmd file(s) to process. $cmdList = Get-ChildItem -Path $scriptPath -Filter "*.cmd" # Check if any .cmd file(s) exist. if $TRUE continue with script, else notify user and exit script. if ($cmdList.count -gt 0) { # Output an ordered list of .cmd file(s) to be processed. "FILE(S) TO BE PROCESSED:" | Tee-Object -Append -FilePath $scriptLog $cmdCount = 1 foreach ($cmd in $cmdList) { "$cmdCount - $cmd" | Tee-Object -Append -FilePath $scriptLog $cmdCount++ } # Create a TimeSpan object to be used later in the script for calculating total duration. $totalDuration = New-TimeSpan ### START OF .CMD PROCESSING ### # Process each .cmd file. foreach ($cmd in $cmdList) { # Visual seperator. "--------------------------------------------------------------------------------" | Tee-Object -Append -FilePath $scriptLog # Get the current time (for start time). Format it to match dd/mm/yy hh:mm:ss tt. Output to console and respective .log file. $startTime = Get-Date $startTimeFormatted = ($startTime).ToString("dd/mm/yy hh:mm:ss tt" ) "$($startTimeFormatted): Starting $cmd" | Tee-Object -Append -FilePath $scriptLog $fileLog = "$scriptPath\" + ($cmd.Name -replace '\.[^.]+$') + ".log" try { # Call the .cmd file for processing. & $cmd.FullName 2>&1 | Tee-Object -Append -FilePath $fileLog # Output script/call related errors for the .cmd file. if ($LASTEXITCODE -ne 0 -or $Error[0]) { Write-Output "Failed to execute $($cmd.FullName). Exit code: $LASTEXITCODE, Error: $($Error[0])" } } catch { $_.Exception.Message | Tee-Object -Append -FilePath $scriptLog } # $fileLogContent = Get-Content -Path $fileLog # $fileLogContent | ForEach-Object { # if ($_ -match "access is denied") { # Write-Output $_ # } # } # Get the current time (for end time). Format it to match dd/mm/yy hh:mm:ss tt. Output to console and respective .log file. $endTime = Get-Date $endTimeFormatted = ($endTime).ToString("dd/mm/yy hh:mm:ss tt" ) "$($endTimeFormatted): Finished $cmd" | Tee-Object -Append -FilePath $scriptLog # Calculate processing duration. Format it to display days, hours, minutes, seconds, and miliseconds. Output to console and respective .log file. $duration = $endTime - $startTime $durationFormatted = "{0:dd} days, {0:hh} hours, {0:mm} minutes, {0:ss} seconds, {0:fff} milliseconds" -f $duration "PROCESS DURATION: $durationFormatted" | Tee-Object -Append -FilePath $scriptLog # Add $duration to the $totalDuration variable, which is a TimeSpan object containing the sum of all durations ($totalDuration). $totalDuration += $duration ### END OF .CMD PROCESSING ### } } else { Write-Host "No .cmd files found in the directory." Read-Host "Press Enter to exit..." } # Visual seperator. "--------------------------------------------------------------------------------" | Tee-Object -Append -FilePath $scriptLog # Calculate total processing duration. Output to console and respective .log file. $totalProcessDurationFormatted = "{0:dd} days, {0:hh} hours, {0:mm} minutes, {0:ss} seconds, {0:fff} milliseconds" -f $totalDuration "TOTAL PROCESS DURATION $totalProcessDurationFormatted" | Tee-Object -Append -FilePath $scriptLog - Copy Robocopy related .cmd file(s) to the script location on the source machine.
- In an elevated PowerShell prompt, run migration.ps1:
powershell.exe C:\path\to\script\migration.ps1
Sources
- N/A
KB Change/Issue Log
2023/06/14 - General Notes for Possible Future Changes.
Issue
The script could have an option to parse the .cmd logs provided by Robocopy to provide error, changed files, new files, etc. for the scripts activity log, the same log that provides the .cmd process duration's.
Log file handling also needs improvement to consolidate log generation between the PowerShell script and Robocopy.
Solution
This has not been implemented yet and the changes will remain pending, until needed. However, the following steps are something to consider if this feature is needed:
- At the end of each processed .cmd file, parse the .cmd log file for errors, changed files, new files, and assign it to an array. It may be best to leave the parsing to a dedicated function.
- Just a thought, but errors, changed files, and new files could be assigned to separate arrays too, which would lead to better count statistics.
- A switch statement may be best to use within a function to parse everything in one go, instead of reparsing for each type (e.g., errors, changed files, new files, etc.).
- Or pull just stick with the separate Robocopy (cmd .log) statistics.
Log file handling may need improvement:
- Weigh the pros and cons of appending to the same log vs generating a new log for each script execution.
- Right now both the script and the Robocopy command arguments are creating logs. Compare the logs to utilize only one of them.
KB Meta
| Page Includes | @9#bkmrk-callout-danger-NoResponsibilityDisclaimer-5wod5ufe |