The question isn't entirely clear what you need, depending on how we understand the use of the -Wait
argument and whether we read the second bullet point as the desired specification or part of the problem to solve.
Regardless of which interpretation, rather than using Set-Location
+ cd
+ start
I would use absolute paths and run in place, like this:
if ($runfolder1 -eq "y")
{
& "$PSScriptRoot\$folder1\code1.ps1"
}
You can test this will give the correct result by putting two scripts in a folder:
child.ps1
Start-Sleep 5
Write-Host "Child"
main.ps1
& "$PSScriptRoot\child.ps1"
Write-Host "Main"
On my system, I run main.ps1
and after 5 seconds I see first "Child" and then "Main".
Of course, some scripts are written assuming they are launched from the script's local directory. This is generally a mistake to do, but it doesn't change that it happens. And so you might also need to change the directory. But even then, I'd use Set-Location
commands for the absolute path, and I'd still use the & "absolute\path"
syntax to start the script.
And this is enough already to solve the simpler interpretation.
But since the question is unclear, I could also read it that you want scripts within a folder to all run at the same time (asynchronous per script file), without waiting for individual scripts in the folder to finish before starting the next script, but then you do want to wait for all scripts in a folder to finish before moving on to the next folder (synchronous per folder).
If so, it's a little more complicated. However, you can do it using Start-Job
+ Wait-Job
. Here's alternative main_alt.ps1
to demonstrate:
main_alt.ps1
$j = Start-Job -FilePath "$PSScriptRoot\child.ps1"
Write-Host "Main"
$null = Wait-Job -job $j
Receive-Job $j
On my system this shows "Main" right away and "Child" after a 5 second wait.
So if you have several scripts in a folder and want to run them all run at the same time without waiting, but then wait for that folder to finish before moving on to another folder, you could start each script as a job and save the job to an array. Then wait on each item in the array before moving to the next folder:
if ($runfolder1 -eq "y")
{
Set-Location -Path "$PSScriptRoot\$folder1"
$scripts = (Get-ChildItem -Path "$PSScriptRoot\$folder1\*.ps1").Name
[array] $jobs = foreach ($script in $scripts) {
Start-Job -FilePath "$PSScriptRoot\$folder1\$script"
}
foreach($j in $jobs) {
$null = Wait-Job -job $j
}
}
It doesn't matter here the jobs might finish out of order. The actual Wait-Job
process is still okay if the job had finished some time before, so it's okay to check them in any order.
Finally, we can abstract this to avoid code repetition. For the basic version:
$folders = @(
@{'folder' = "Folder_1"
'run' = "n"},
@{'folder' = "Folder_2"
'run' = "n"},
@{'folder' = "Folder_3"
'run' = "y"},
@{'folder' = "Folder_4"
'run' = "y"}
)
$initialLocation = Get-Location
$folders | ?{ $_.run -eq "y" } | %{
$path = "$PSScriptRoot\$($_.folder)"
Set-Location -Path $path
$scripts = (Get-ChildItem -Path "$path\*.ps1").Name
foreach ($script in $scripts) {
$ "$path\$script"
}
}
Set-Location $initialLocation
And the more complex/asynchronous option:
$folders = @(
@{'folder' = "Folder_1"
'run' = "n"},
@{'folder' = "Folder_2"
'run' = "n"},
@{'folder' = "Folder_3"
'run' = "y"},
@{'folder' = "Folder_4"
'run' = "y"}
)
$initialLocation = Get-Location
$folders | ?{ $_.run -eq "y" } | %{
$path = "$PSScriptRoot\$($_.folder)"
Set-Location -Path $path
$scripts = (Get-ChildItem -Path "$path\*.ps1").Name
[array] $jobs = foreach ($script in $scripts) {
Start-Job -FilePath "$path\$script"
}
foreach($j in $jobs) {
$null = Wait-Job -job $j
# Optionally add Receive-Job here
}
}
Set-Location $initialLocation
And now even the more complicated option is significantly less code than the original question.
Just for fun, if I were building this I'd expect scripts in specific folders might be related, and therefore potentially need to be synchronous, but individual folders are unrelated, and therefore should be able to run at the same time.
That would look something like this:
$folders = @(
@{'folder' = "Folder_1"
'run' = "n"},
@{'folder' = "Folder_2"
'run' = "y"},
@{'folder' = "Folder_3"
'run' = "y"},
@{'folder' = "Folder_4"
'run' = "y"}
)
[array] $jobs = foreach($f in ($folders | ?{ $_.run -eq "y" })) {
# Powershell 7 and later should use -WorkingDirectory instead, but this works in both 5.1 and 7+
Start-Job -ArgumentList "$PSScriptRoot\$($f.folder)" -ScriptBlock {
$path = $args[0]
# set-location is independent within each job
Set-Location -Path $path
$scripts = (Get-ChildItem -Path "$path\*.ps1").Name
foreach ($script in $scripts) {
$ "$path\$script"
}
}
}
foreach($j in $jobs)
{
$null = wait-job $j
# Optional Receive-Job here
}
As a final revision, given the above scenario I'd also want to get the folder information and y/n decisions from data, rather than in the code. I'd probably use get-content
for the folder names, and then assume "yes" to running each folder with an excludeFolders.txt
file for exceptions, where any folder I should not run is listed there by name on a line by itself:
$folders = (get-childitem $PSScriptRoot -Directory).Name
$exclusions = Get-content "$PSScriptRoot\excludeFolders.txt"
[array] $jobs = foreach($f in ($folders | ?{ $_ -notin $exclusions })) {
# Powershell 7 and later should use -WorkingDirectory instead, but this works in both 5.1 and 7+
Start-Job -ArgumentList "$PSScriptRoot\$f" -ScriptBlock {
$path = $args[0]
Set-Location -Path $path
$scripts = (Get-ChildItem -Path "$path\*.ps1").Name
foreach ($script in $scripts) {
& "$path\$script"
}
}
}
foreach($j in $jobs)
{
"$($j.Name):$($j.State)"
$null = wait-job $j
Receive-Job $j
}
Now just add a little logging and we have beginning of a decent jobbing system. Ideas for future features is converting the text file so each folder can have a cron-like scheduling syntax, and limiting the number of folders to process at one time (maybe with a queue or producer/consumer process). But that's getting too far afield of the original.
Get-Help about_Thread_Jobs
andGet-Help about_Jobs
for details.