A while back, I wrote some code to copy some files off of a Windows 2012 R2 Server to a removable USB drive for data archival and off-site storage. I set up the copy process in a Scheduled Task to synchronize the data using Robocopy. However, I realized the scheduled task is not able to stop the Robocopy.exe process. This is very strange behavior as one would think that the spawning process, PowerShell.exe, would end all sub-processes when it’s killed by the ‘End Task’ button. This is not the case. The Robocopy process continues to copy data even if you attempt to end the task.
I need it to stop when I press the “End Task” button. Suppose I want to yank the drive off the server? Suppose, I want to stop it, modify some data, then start it again. I’ll end up with 2 Robocopy processes copying the same data to the same drive, thrashing that poor little guy to death. Imagine a runaway process copying gigabytes of data with no easy way to stop it.
Of course I could kill the process using Task Manager, but this means I have to remember to do that. Also, how is anyone else supposed to come in behind me and manage this process?
It needs to work as expected. Since killing a parent process does not kill the child processes, we can leverage this to create a process monitor function that spawns an intermediary. This process would monitor the parent process and the child process. If the parent disappears, then it will be responsible for disposing of the child process and itself. Likewise, if the child process disappears, it will just end itself as its work is complete. One final responsibility, it should monitor the exit code of the child process to return that to the parent process so it can know what happened.
In short, here’s a play-by-play.
– Create an object to represent the parent.
– Create another object representing the child.
– Store both Process IDs.
– Spawn the process monitor code as a new process.
– We’ll pass the Process IDs of both the parent and the child wait on it synchronously.
– If the child exits, kill the monitor, Pass the Error code back.
– If the parent exits, dispose of the child process, and kill the monitor.
Function Start-Robo ([String]$RoboArgs) { #Task Scheduler doesn't kill all spawned processes, thus if a Robo is in progress, #it only kills Powershell.exe not Robocopy.exe if you 'end' the task prematurely. #If it ends naturally all processes do stop. #This function handles killing the robocopy process if the Scheduled Task is ended. $CurrProc = [System.Diagnostics.Process]::GetCurrentProcess() $RoboProc = Start-Process -PassThru Robocopy -argumentlist $RoboArgs $CurrProcID = $CurrProc.ID $RoboProcID = $RoboProc.Id Write-Host "Spawning Monitor..." Start-Process -Wait -FilePath PowerShell -ArgumentList "-Command &{ Write-Host 'Process Monitor....SpawnID: $CurrProcID RoboID: $RoboProcID' `$CurrProcSub = Get-Process -id $CurrProcID `$RoboProcSub = Get-Process -id $RoboProcID if (!`$CurrProcSub -or !`$RoboProcSub) {Exit} While (!(`$RoboProcSub.hasExited)) { if (!(`$CurrProcSub.hasExited)) { Start-Sleep -Seconds 3 } Else { Stop-Process -Id `$RoboProcSub.ID -Force } } } " Write-Host "Robo Completed" $ExitCode = [INT] $RoboProcSub.ExitCode Return $ExitCode }
So after placing that function in your script, you can use it like this:
Start-Robo -RoboArgs "C:\data D:\data /J /MIR /W:2 /R:1 /TEE /Log+:C:\Logs\YourFavoriteLog.Log"
Of course, the above example is just a mishmash of various command-line arguments for Robocopy – feel free to substitute your own as you see fit. It’s always recommended to set some variables first as follows:
$Log = "C:\Logs\MyLog.Log" $Srouce = "C:\Data" $Dest = "D:\Data" Start-Robo -RoboArgs "$Source $Dest /J /MIR /W:2 /R:1 /TEE /Log+:$Log"