Powershell Start and stop additional powershell instance foreach loop - powershell

I have a powershell script I am spawning additional powershell instances to run cmdlets.
I need to be able to Start-Process , a powershell instance, from a nested foreach loop and allow the process to run through and then stop that process when its complete.
foreach($thing in $things)
foreach($stuff in $stuffs)
start-process powershell.exe -nonewwindow | get-cmdichooseasanewcmd | export-csv -path stuff
stop-process same instantiated process
How do I stop the process that is specifically tied to my foreach loop when it is complete with its task?

If you're not doing anything requiring credentials you can use jobs.
$job = Start-Job { # Do stuff }
$job.StopJob()
Anything involving credentials invokes the wrath of double hopping but I don't think it works the way you're running it in your question either so shouldn't be a problem.
Alternatively you can use runspaces but that's a big can of worms. See this link if you want to go there.

Related

Executing additional cmdlets after a Job was fired off

Suppose I have a simple Job that starts off like this:
$job = Start-Job -ArgumentList 1, 2 -ScriptBlock {
$var1 = $args[0]
$var2 = $args[1]
$var3 = $var1 + $var2
Write-Host $var3
}
Now suppose that I want to continue executing the session that is $job and introduce new arguments and simply continue executing inside $job.
From what I understand, that's not how Jobs work in Powershell. Once the commands inside the job were executed, the job is considered to be completed and can no longer be recycled. If my understanding is correct, is there any other way to achieve the effect of effectively having a background task inside your powershell session that you can continue injecting with new commands/variables, without having to create a new job/session/process? For clarity, this is local (on the same machine).
Dennis' helpful answer provides the crucial pointer: use the PowerShell SDK to create an in-process PowerShell instance that you can use for repeated invocation of commands.
The following sample code demonstrates this: It keeps prompting you for a command to execute and uses a single, reusable PowerShell instance to execute it (press Ctrl-C to exit):
$ps = [powershell]::Create()
while ($true) {
$cmd = Read-Host 'Type a command to execute'
# Execute and output the results.
$ps.AddScript($cmd).Invoke()
# Relay errors, if any.
$ps.Streams.Error | Write-Error
# Reset in preparation for the next command.
$ps.Commands.Clear(); $ps.Streams.ClearStreams()
}
I think you would be better of looking into PowerShell runspaces instead that can communicate back and forth with each other since they are threads of the same processes.
Start-Job actually starts a new PowerShell session in a separate isolated process.
See, MS Docs - Start-Job -RunAs32 and MS Scripting - Beginning Use of PowerShell Runspaces

Labeling a process to determine its execution status

I have a script D:\Script.ps1 that runs using a shortcut:
# The shortcut is located in the startup folder $Env:AppData\Microsoft\Windows\Start Menu\Programs\Startup\Script.lnk
# The object in this shortcut description
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File D:\Script.ps1
How can I see in the console that the process Script is working? How can I mark this process in script or in shortcut description so that I can see if it is working or has already completed the task and completed the work? I could track the process using an ID (Get-Process).ID, but I do not know the ID of my process when starting OS. I can’t Get it's Name easy and Set, as, for example, in Start-Job. I can not distinguish it from other running processes powershell via (Get-Process).ProcessName, they all have the ProcessName of powershell. What can I do to distinguish my powershell process from other powershell running ones? Thanks
If you can switch from using a batch file as a wrapper or can tolerate another PowerShell script to launch the real script. You can use Start-Process with the -PassThru parameter combined with Wait-Process to tell you when it's complete.
Something like:
$Process = Start-Process -FilePath C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe -ArgumentList "-command .{read-host} -WindowStyle Normal" -passthru
$Process | Wait-Process
Write-Host "The process has completed."
However, if you need to observe the process completely apart from it there are a couple of other approaches I can think of.
Get-Process doesn't return a command line property but the WMI Win32_Process class does. You can match some aspect of the CommandLine to differentiate & isolate the process running the script of interest. Something like:
Get-CimInstance win32_Process -Filter "CommandLine LIKE '%d:\\script.ps1'"
You can use this directly or to get the PID or any other characteristic to further observe with. You can certainly get more elaborate with the filter.
Note: Typical best practice is to move the filter criteria to the left, but in this case you might think about using a PowerShell | Where-Object{}, as performance probably wouldn't be a concern. It might make it somewhat easier to filter based on CommandLine. I'd only do that if the existing filter was troublesome.
You can also add the command line column in Task Manager to watch just the same, but without scripting etc...
These aren't exactly eloquent solutions, but let me know if it's helpful.

How do I get a Powershell process that was opened by another Powershell process?

I am running multiple PowerShell scripts at once. I would like to be able to wait on certain ones to finish before opening new scripts. Basically, I was thinking if I could find the command line option that ran it something like "powershell.exe -Path "<script dir>" that would do it.
I tried doing a Get-Process | gm to find any parameters that I could call to get that information and I didn't see any (doesn't mean they aren't there) I tried looking through Task Manager to see if I could view something through the gui that I could link to but that didn't help either.
I hope I can get something like
Start-Process -FilePath ".\<script>.ps1" -ArgumentList "<args>"
do
{
sleep 10
}
until ((Get-Process -ProcessName "PowerShell" | where "<paramater>" -EQ ".\<script>")
I need to wait until that process is done but I don't want to put a wait at the end of the Start-Process because after that Start-Process kicks off I need some other items to go to while my .\ is running. I just need it to wait before another section of script kicks off.
Have a look at the "Job" cmdlets https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_jobs?view=powershell-6
And the $PID automatic variable, this will give the process ID of the current PowerShell session.

Launch PowerShell script into new window while passing variables

I've been using the site for a while, searching through the questions and answers, trying to map them to my scenario, but I'm either missing something, or what I'm looking to do isn't possible (at least the way I'm trying to do it), hence I'm hoping for a push in the right direction. Thanks in advance for reading.
I've been working on a fairly sizeable automation project. My main script performs a number of tasks, and generally works well, and reliably. At one stage of the script, I execute another PowerShell script, which was written by another team. I call the script as follows:
.\DeployMySQLProvider.ps1 -AzCredential $asdkCreds `
-VMLocalCredential $vmLocalAdminCreds `
-CloudAdminCredential $cloudAdminCreds
-PrivilegedEndpoint $ERCSip `
-DefaultSSLCertificatePassword $secureVMpwd -AcceptLicense
When I call it this way, from my main script, it works fine, however, this script uses and registers a DLL file during it's deployment, and locks it until the PowerShell window and session is closed. At the end of my main script, I have a cleanup phase, which can't complete it's job because of this locked DLL.
My thoughts therefore, were to launch the 2nd script into a new PowerShell window and session, either using Start-Process or Invoke-Expression, but I just can't seem to get either right. Most of the variables I'm passing through to the 2nd script aren't just strings, which is probably where I'm falling over. They are a mix of usernames and passwords (secure strings) along with $ERCSip which is a string.
Should I be looking at Start-Process / Invoke-Expression, or something else entirely? When I was testing with Start-Process, I had the following defined, but couldn't get the ArgumentList side working correcly for me (blank below):
Start-Process "$pshome\powershell.exe" -PassThru -Wait `
-Verb RunAs -ErrorAction Stop -ArgumentList ""
Any pointers in the right direction would be much appreciated.
Thanks!
I've used something similar to this in my scripting:
$scriptpath="c:\pathto\deploysqlProvider"
$a = "$scriptpath\DeployMySQLProvider.ps1 -AzCredential $asdkCreds `
-VMLocalCredential $vmLocalAdminCreds `
-CloudAdminCredential $cloudAdminCreds
-PrivilegedEndpoint $ERCSip ` "
-DefaultSSLCertificatePassword $secureVMpwd -AcceptLicense
Start-Process -Verb runas -FilePath powershell.exe -ArgumentList $a -wait -PassThru ;
Not sure if you need it to runas admin or not (-verb runas).
I'd suggest you then look for the Powershell process and path. So that if you have to kill this separate process you can.

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

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"?
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."
}
}
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.
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.
The two instances are totally separate, because they are two different processes. Generally, it is not the most efficient way to start a Powershell process for every script run. Depending on the number of scripts and how often you re-run them, it may be affecting your overall performance. If it's not, I would leave everything AS IS.
Another option would be to run in the same runspace (this is a correct word for it), but clean everything up every time. See this answer for a way to do it. Or use below extract:
$sysvars = get-variable | select -Expand name
function remove-uservars {
get-variable |
where {$sysvars -notcontains $_.name} |
remove-variable
}