0

Please forgive me if this is a relatively simple fix, I am new to PowerShell scripting and could not find any answer that would work for my case.

What I am trying to do:

I have a "Main PowerShell Script" that I want to use to run which will run some other and different PowerShell script (code1.ps1, code2.ps2.. etc.) stored in multiple subfolder (Folder_1, Folder_2, etc.).

  • The main PowerShell script will decide which folder I want to go and run the specific code in that folder.

  • if I am running all 4 codes as listed below, each code will only execute after the previous folder's code has been completed.

Problem I am facing:

I have used the -Wait command at the end of PowerShell code execution, but this seems to keep that specific code in pause even after the code has finished running. I have to manually close the window (that is invoked by the code1.ps1 and similar) and then the next folder's code2.ps1 will execute.

I have also tried to use -NoExit, but for some reason it gives me the following error message:

Start-Process : A parameter cannot be found that matches parameter name 'NoExit'.

The code I am trying to use-

#------------------Main Powershell Script-------------------------
#-------------------------------------------
#-------- Setting Up Main Directory  -------
#-------------------------------------------
$currentdirectory=$PSScriptRoot
Set-Location -Path $currentdirectory
#-------------------------------------------
#-------- Folder Run Declaration  ----------
#-------------------------------------------
# "y" means yes, "n" means no
$runfolder1="n"
$runfolder2="n"
$runfolder3="y"
$runfolder4="y"

#-------------------------------------------
#-------- Folder Name Declaration  ---------
#-------------------------------------------

$folder1="Folder_1"
$folder2="Folder_2"
$folder3="Folder_3"
$folder4="Folder_4"

#-------------------------------------------
#-------- Main Code  ---------
#-------------------------------------------

if ($runfolder1 -eq "y") 
{
    cd $folder1
    & start powershell {.\code1.ps1} -Wait
    cd ..
 }


 if ($runfolder2 -eq "y") 
{
    cd $folder2
    & start powershell {.\code2.ps1} -Wait
    cd ..
 }


 if ($runfolder3 -eq "y") 
{
    cd $folder3
    & start powershell {.\code3.ps1} -Wait 
    cd ..
 }

 if ($runfolder4 -eq "y") 
{
    cd $folder4
    & start powershell {.\code4.ps1} -Wait
    cd ..
 }
2
  • 1
    please add a focused, answerable question to your post Commented Oct 3 at 19:20
  • have you taken a look at jobs? that can run your code in parallel if you choose to. take a look at Get-Help about_Thread_Jobs and Get-Help about_Jobs for details. Commented Oct 3 at 19:48

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Given that the OP's question is unclear, it's also unclear which part of your answer solved their problem. All aspects of your answer differ from the OP's attempt in important ways: - Running a script by its full path from whatever the current dir. is isn't necessarily the same as invoking it from the dir. it lives in. - Direct invocation of a script (any console-subsystem application) runs in the current console, whereas Start-Process launches in a new window by default. - Running scripts as jobs runs them invisibly and requires subsequent collection of output via Receive-Job.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.