0

I have a series of 15 batch files I need to run as part of a powershell script. I currently have a working version of this, but I execute each batch file as below:

Start-Process -FilePath "Script1.bat" -WorkingDirectory "C:\Root" -Wait
Start-Process -FilePath "Script2.bat" -WorkingDirectory "C:\Root" -Wait
Start-Process -FilePath "Script3.bat" -WorkingDirectory "C:\Root" -Wait

and so on.

The batch files are lumping thousands of SQL scripts into one big script, and some of them are much slower than others (the SQL populates 5 different databases and 1 is much larger than the other 4)

I'm trying to improve the speed at which the 15 scripts run, and I thought a good idea would be to run the batch files in parallel so that all of the SQL files are created at the same time, rather than in sequence. To do this I removed the "-Wait"s, and can see all of the command line windows opening simultaneously.

The problem I'm having is that after I have created the SQL files, I'm using Copy-Item to move the scripts to the desired location. Because the Copy-Item commands are no longer waiting for the batch files to execute, it's attempting to copy the SQL files from the folder they are created in before the batch files have finished creating them.

So basically I'm trying to come up with a way to "Wait" for the collection of batch files to run so I can ensure the batch files have finished running before I start copying the files. I've looked online for ages, and have tried using powershell Jobs with the "Wait-Job" command, but the wait only waits until every batch file has been executed, and not until they have been completed. Does anyone have any ideas on how this can be achieved?

Squashman
  • 13,649
  • 5
  • 27
  • 36
Joe Farmer
  • 23
  • 3
  • I know how to do this if you were doing it all in a batch file. [How to wait all batch files to finish before exiting?](https://stackoverflow.com/questions/33584587/how-to-wait-all-batch-files-to-finish-before-exiting) – Squashman Dec 14 '18 at 17:49
  • Well, if they all take the same time to execute, add the `-Wait` in the last one. If not, in the two last, and so on. This is a quite simple way. – double-beep Dec 14 '18 at 17:51
  • 1
    the 1st thing that comes to mind for me is to have your BAT file write a "finished._BAT_SomeName.txt` file when it finishes. then look for that file. [*grin*] ///// the next would be to look at putting it into a PoSh job and use `Wait-Job | Receive-Job` to know when the BAT file finishes. ///// last, convert the BAT files to standard PoSh scriptblocks and use jobs with those.. – Lee_Dailey Dec 14 '18 at 17:51
  • I have an example script that does this to run unit tests. All of the unit test.dll are started as separate processes and scripts waits for them all to finish. Will try to post it tonite as an answer if someone doesn't beat me to it. – No Refunds No Returns Dec 14 '18 at 20:39
  • @palansen's answer is essentially my technique. – No Refunds No Returns Dec 15 '18 at 02:30

2 Answers2

0

I'd use an event here.

Instead of triggering the batch with what you have try the following:

$bat3 = Start-Process -FilePath "Script3.bat" -WorkingDirectory "C:\Root" -Wait -PassThru

Notice, you're saving the process object to a variable and you've added a -PassThru parameter (so an object is returned by the commandlet). Next, create a scriptblock with whatever you want to happen when the batch script finishes:

$things-todo-when-batchfile-completes = {
    Copy-Item -Path ... -Destination ...
    Get-EventSubscriber | Unregister-Event
}

Make sure to end the block with Get-EventSubscriber | Unregister-Event. Next, create an event handler:

$job = Register-ObjectEvent -InputObject $bat3 `
-eventname exited `
-SourceIdentifier Batch3Handler `
-Action $things-todo-when-batchfile-completes

Now, when the task finishes running the handler will execute the actions in $things-todo-when-batchfile-completes.

This should let you kick off the batch files, and when they finish running, you execute the file copy you were after.

Microsoft has a blog post on the technique in the piece Weekend Scripter: Playing with PowerShell Processes and Events.

Adam
  • 3,891
  • 3
  • 19
  • 42
0

I'm thinking: put all the process objects in an array... and wait for all of them to have exited...

$p = 1..15 | ForEach-Object {
    Start-Process -FilePath "Script$_.bat" -WorkingDirectory "C:\Root" -PassThru
}

while(($p.HasExited -eq $false).Count) {
    Start-Sleep -Milliseconds 100
}
Palansen
  • 311
  • 2
  • 7
  • This was what I needed. I had to alter it slightly as there are actually 3 differently named scripts in 5 different directories, but I just put the full file path of each into an array instead of 1..15. Then in place of "Script$_.bat" I just used $_ and used Split-Path to pick the filename and working directory from the object in the array, Like so: `$Loop = $BatchFiles | ForEach-Object { Start-Process -FilePath (Split-Path -Path $_ -Leaf -Resolve) -WorkingDirectory (Split-Path -Path $_ -Parent) -PassThru }` – Joe Farmer Dec 19 '18 at 10:07