Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call a PowerShell script in a new, clean PowerShell instance (from within another script)

Tags:

powershell

I have many scripts. After making changes, I like to run them all to see if I broke anything. I wrote a script to loop through each, running it on fresh data.

Inside my loop I'm currently running powershell.exe -command <path to script>. I don't know if that's the best way to do this, or if the two instances are totally separate from each other.

What's the preferred way to run a script in a clean instance of PowerShell? Or should I be saying "session"?

like image 789
Vimes Avatar asked Oct 27 '12 00:10

Vimes


3 Answers

Using powershell.exe seems to be a good approach but with its pros and cons, of course.

Pros:

  • Each script is invoked in a separate clean session.
  • Even crashes do not stop the whole testing process.

Cons:

  • Invoking powershell.exe is somewhat slow.
  • Testing depends on exit codes but 0 does not always mean success.

None of the cons is mentioned is a question as a potential problem.

The demo script is below. It has been tested with PS v2 and v3. Script names may include special characters like spaces, apostrophes, brackets, backticks, dollars. One mentioned in comments requirement is ability to get script paths in their code. With the proposed approach scripts can get their own path as $MyInvocation.MyCommand.Path

# make a script list, use the full paths or explicit relative paths
$scripts = @(
    '.\test1.ps1' # good name
    '.\test 2.ps1' # with a space
    ".\test '3'.ps1" # with apostrophes
    ".\test [4].ps1" # with brackets
    '.\test `5`.ps1' # with backticks
    '.\test $6.ps1' # with a dollar
    '.\test ''3'' [4] `5` $6.ps1' # all specials
)

# process each script in the list
foreach($script in $scripts) {
    # make a command; mind &, ' around the path, and escaping '
    $command = "& '" + $script.Replace("'", "''") + "'"

    # invoke the command, i.e. the script in a separate process
    powershell.exe -command $command

    # check for the exit code (assuming 0 is for success)
    if ($LastExitCode) {
        # in this demo just write a warning
        Write-Warning "Script $script failed."
    }
    else {
        Write-Host "Script $script succeeded."
    }
}
like image 88
Roman Kuzmin Avatar answered Sep 30 '22 20:09

Roman Kuzmin


If you're on PowerShell 2.0 or higher, you can use jobs to do this. Each job runs in a separate PowerShell process e.g.:

$scripts = ".\script1.ps1", ".\script2.ps1"

$jobs = @()
foreach ($script in $scripts)
{
    $jobs += Start-Job -FilePath $script
}

Wait-Job $jobs

foreach ($job in $jobs)
{
    "*" * 60
    "Status of '$($job.Command)' is $($job.State)"
    "Script output:"
    Receive-Job $job
}

Also, check out the PowerShell Community Extensions. It has a Test-Script command that can detect syntax errors in a script file. Of course, it won't catch runtime errors.

like image 43
Keith Hill Avatar answered Sep 30 '22 20:09

Keith Hill


One tip for PowerShell V3 users: we (the PowerShell team) added a new API on the Runspace class called ResetRunspace(). This API resets the global variable table back to the initial state for that runspace (as well as cleaning up a few other things). What it doesn't do is clean out function definitions, types and format files or unload modules. This allows the API to be much faster. Also note that the Runspace has to have been created using an InitialSessionState object, not a RunspaceConfiguration instance. ResetRunspace() was added as part of the Workflow feature in V3 to support parallel execution efficiently in a script.

like image 27
Bruce Payette Avatar answered Sep 30 '22 19:09

Bruce Payette