Powershell: Don't wait on function return - powershell

I have a Powershell script where I am invoking a function called generate_html.
Is there a way to call the function and not wait on its return before proceeding to the next function call?
I would prefer not to have to resort to breaking it out into multiple scripts and running those scripts at same time.
function generate_html($var)
{
...
}
generate_html team1
generate_html team2

You can start a method as an asynchronous job, using the Start-Job cmdlet.
So, you would do something like this:
function generate_html($var)
{
#snipped
}
Start-Job -ScriptBlock { generate_html team1 }
If you need to monitor if the job is finished, either store the output of Start-Job or use Get-Job to get all jobs started in the current session, pulling out the one you want.
Once you have that, check the State property to see if the value is Finished
EDIT:
Actually, my mistake, the job will be in another thread, so functions defined or not available (not in a module that powershell knows about) in the thread from which you start your job are not available.
Try declaring your function as a scriptblock:
$generateHtml = {
param($var)
#snipped
}
Start-Job -ScriptBlock $generateHtml -ArgumentList team1
Start-Job -ScriptBlock $generateHtml -ArgumentList team2

For me, the issue was my function would create a Windows Forms GUI, and stopped the remaining script from running until I closed out the Windows Forms GUI. I found the solution, but for a different command, here: Using Invoke-Command -ScriptBlock on a function with arguments.
Within the -ScriptBlock parameter, use ${function:YOUR_FUNCTION}
function generate_html($var) {
# code
}
Start-Job -ScriptBlock ${function:generate_html} -ArgumentList "team1"
Start-Job -ScriptBlock ${function:generate_html} -ArgumentList "team2"
Also, if you don't want console output from Start-Job, just pipe it to Out-Null:
Start-Job -ScriptBlock ${function:generate_html} -ArgumentList "team1" | Out-Null
Start-Job -ScriptBlock ${function:generate_html} -ArgumentList "team2" | Out-Null

Related

Start-Process inside Invoke-Command closes immediately unless -wait switch, but how can I continue script?

I'm trying to remotely run this Windows Update Assistance Installer .exe and I notice that the .exe closes immediately unless I use the -wait command. However, if I use the -wait command I can't continue my foreach loop for the other computers since it takes hours for an install to finish. If I take the -wait command out, I think it launches then closes immediately.
$computers | % {
{more code...}
Invoke-Command -Session $Session -ScriptBlock {
$msbuild = "C:\windows\temp\Windows10Upgrade9252(21H2).exe"
$Args = '/quietinstall /skipeula /auto upgrade /copylogs'
Start-Process -FilePath $msbuild -ArgumentList $args -Wait
}
}
Run in parallel (within -throttlelimit) with $computers or $sessions as the computername/sessionname. Can also use -asjob. $args might be reserved already. Start-process won't return text output or the exit code without further action, which I've added. You might use scheduled tasks instead.
$sessions = new-pssession $computers
Invoke-Command $sessions {
$msbuild = "C:\windows\temp\Windows10Upgrade9252(21H2).exe"
$myargs = '/quietinstall /skipeula /auto upgrade /copylogs'
$p = start-process -wait $msbuild $myargs -passthru
[pscustomobject]#{exitcode=$p.exitcode]
} # -asjob
exitcode PSComputerName RunspaceId
-------- -------------- ----------
0 localhost 197d26f5-754b-49d3-baf4-2ca8fccacd4c
Other ways to wait for a program to finish: How to tell PowerShell to wait for each command to end before starting the next?

Powershell - capture output from Invoke-Command running exe on remote machine

I need to configure the auditing policy on a collection of remote servers. I'm trying to use the Invoke-Command commandlet to run auditpol.exe on each server. The issue is that I can't seem to capture any output from the auditpol command.
I tried the obvious (assigning the result of Invoke-Command to a string):
> $command = "Start-Process -FilePath `"auditpol.exe`" -ArgumentList `"/set`", `"/subcategory`", `"```"File System```"`", `"/success:enable`""
> $command
"auditpol.exe" -ArgumentList "/set", "/subcategory", "`"File System`"", "/success:enable"
> $out = Invoke-Command -ComputerName MyServer -ScriptBlock {$command}
> $out
>
But $out is empty.
I also tried the method detailed in this MSDN blog using Wait-Job and Receive-Job. The results are somewhat promising, but inconclusive:
> $command = "Start-Process -FilePath `"auditpol.exe`" -ArgumentList `"/set`", `"/subcategory`", `"```"File System```"`", `"/success:enable`""
> $command
"auditpol.exe" -ArgumentList "/set", "/subcategory", "`"File System`"", "/success:enable"
> $job = Invoke-Command -ComputerName MyServer -ScriptBlock {$command} -AsJob
> Wait-Job $job
Id Name State HasMoreData Location Command
-- ---- ----- ----------- -------- -------
3 Job3 Completed True MyServer $command
> $output = Receive-Job $job
> $output
>
I was hoping that I would be able to capture the actual output from auditpol.exe using Receive-Job, but as indicated above, that doesn't seem to be the case.
I do get some information from Wait-Job. According to the Microsoft documentation of Wait-Job State=Completed should indicate that the operation was successful, but I'm not entirely convinced that it really has visibility into whether or not the auditpol operation was successful or not. Any advice would be greatly appreciated!
To run a console program synchronously and with its stdout and stderr output available for capture invoke it directly - do not use Start-Process (whether you run that program locally or remotely, via Invoke-Command):
$out = Invoke-Command -ComputerName MyServer -ScriptBlock {
auditpol.exe /set /subcategory 'File System' /success:enable
}
If you also want to capture stderr output, append 2>&1 to the auditpol.exe call.
If your script block is stored in local variable $command (as a [scriptblock] instance, not as a string), simply pass it directly to -ScriptBlock:
# Create a script block (a piece of code that can be executed on demand later)
# and store it in a (local) variable.
# Note that if you were to use any variable references inside the block,
# they would refer to variables on the remote machine if the block were to be
# executed remotely.
$command = { auditpol.exe /set /subcategory 'File System' /success:enable }
# Pass the script block to Invoke-Command for remote execution.
$out = Invoke-Command -ComputerName MyServer -ScriptBlock $command
As for what you tried:
$out = Invoke-Command -ComputerName MyServer -ScriptBlock {$command}
You're passing a script-block literal ({ ... }) that, when it is executed on the target computer, references a variable named $command.
Generally, simply referencing a variable outputs its value - it doesn't execute anything.
More importantly, however, $command is a local variable, which the remotely executing script block cannot see, so referencing the there-uninitialized $command variable will effectively yield $null.
In short: your Invoke-Command call does nothing and returns $null.

Powershell build step, fire and forget?

I am running the following powershell command in a build step using TFS 2018.
Start-Job -ScriptBlock {
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
}
Since I don't want the script to affect the build step it should simply fire and forget the script. Hence I am using Start-Job. But it seems that once the step is done the process is killed. Is there a way to maintain the process lifetime even though the build step is done or the build process is finished?
Additional information... the powershell script should run on the remote server. The script itself triggers an .exe with parameters.
To simply fire and forget, invoke the script with Invoke-Command -AsJob:
Invoke-Command -AsJob -FilePath \\MyServer\run.ps1 -ComputerName MyServer -Args arg1, arg2
Start-Sleep 1 # !! Seemingly, this is necessary, as #doorman has discovered.
This should kick off the script remotely, asynchronously, with a job getting created in the local session to monitor its execution.
Caveat: The use of Start-Sleep - possibly with a longer wait time -
is seemingly necessary in order for the remote process to be created before the calling script exits, but such a solution may not be fully robust, as there is no guaranteed timing.
Since you're not planning to monitor the remote execution, the local session terminating - and along with it the monitoring job - should't matter.
When do you want the script to stop running? You could use a do-while loop and come up with a <condition> that meets your needs.
Start-Job -ScriptBlock {
do{
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
Start-Sleep 2
}while(<condition>)
}
Alternatively, you could use the condition $true so it executes forever. You will have to stop the job later in the script when you no longer need it.
$job = Start-Job -ScriptBlock {
do{
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
Start-Sleep 2
}while($true)
}
Stop-Job $job
Remove-Job $job
I've added a Start-Sleep 2 so it doesn't lock up your CPU as no idea what the script is doing - remove if not required.
Why not something like this:
Invoke-Command -Filepath \\MyServer\Run.ps1 -Computername MyServer -Argumentlist Arg1,Arg2 -AsJob
$JobCount = (get-job).Count
Do
{
Start-Sleep -Seconds 1
$totalJobCompleted = (get-job | Where-Object {$_.state -eq "Completed"} | Where-Object {$_.Command -like "NAMEOFCOMMAND*"}).count
}
Until($totalJobCompleted -ge $JobCount)
#doorman -
PowerShell is natively a single threaded application. In almost all cases, this is a huge benefit. Even forcing multiple threads, you can see the child threads are always dependent on the main thread. If this wasn't the case, it would be very easy to create memory leaks. This is almost always a good thing as when you close the main thread, .Net will clean up all the other threads you may have forgotten about for you. You just happened to run across a case where this behaviour is not beneficial to your situation.
There are a few ways to tackle the issue, but the easiest is probably to use the good ol' command prompt to launch an independent new instance not based at all on your original script. To do this, you can use invoke-expression in conjunction with 'cmd /c'. See Below:
invoke-expression 'cmd /c start powershell -NoProfile -windowstyle hidden -Command {
$i = 0
while ($true) {
if($i -gt 30) {
break
}
else {
$i | Out-File C:\Temp\IndependentSessionTest.txt -Append
Start-Sleep -Seconds 1
$i++
}
}
}
'
This will start a new session, run the script you want, not show a window and not use your powershell profile when the script gets run. You will be able to see that even if you kill the original PowerShell session, this one will keep running. You can verify this by looking at the IndependentSessionTest.txt file after you close the main powershell window and see that the file keeps getting updated numbers.
Hopefully this points you in the right direction.
Here's some source links:
PowerShell launch script in new instance
How to run a PowerShell script without displaying a window?

New-PSSession in Job blocks Job

I need to start PSSession in job. But I can't use Invoke-Command -AsJob due product architecure.
I tried something like:
$block {
"Job started"
$session = New-PSSession -computername "host-name"
"Session started"
}
$job = Start-Job -ScriptBlock $block
Wait-Job -Job $job -Timeout 10 | Out-Null
Receive-Job -Job $job
(I don't have PS on system I post this question, so I could make mistake in syntax, skip it, please)
But, instead of creating remote session job became blocked forever on New-PSSession line.
Wait-Job will exit by timeout, and Recieve-Job will return only "Job started".
I tried:
I ensure that remoting in main thread is working well, and Invoke-Command -AsJob is working too.
Invoke-Command and other PSSession-based cmdlets have same behavior — blocks job execution.
Create PSSession in main thread and then transfer session object or object.Id as -InputArguments for job
Result: PSSession created in main thread can't be used in child Job, no matter, was it transfered or found inside job`s scriptblock by Get-PSSession
Thanks in advance!

Background job does not seem to run

I have function in a powershell 2.0 script that I am launching as a bkground job
Start-Job -ScriptBlock {CopyDataToServer($uploadSessionGuid)} -Name $uploadSessionGuid
Then at the end of the script I have
Wait-Job -State Running -Timeout $LogCopyTimeout
Event though the job is showing as Running and then completed, nothing is copied to the server.
How can I debug this?
Roman is right about CopyDataToServer and $uploadSessionGuid probably not being defined in the runspace the job executes in (upvoted his answer). BTW I believe it is better to wait on a specific job object than for any job in the running state e.g.:
$job = Start-Job {param($path, $guid) . $path\lib.ps1; CopyDataToServer $guid} `
-arg $pwd,$uploadSessionGuid
Wait-Job $job
Receive-Job $job
Note that you can use the -ArgumentList parameter to pass in parameters to your scriptblock. While you can access these arguments in your scriptblock via $args, I prefer using a param block and naming the args. This example also shows how you can pass in the path to a PowerShell script containing the function CopyDataToServer which gets dot sourced into the job's runspace.
The script block {CopyDataToServer($uploadSessionGuid)} is invoked in a new runspace where the command CopyDataToServer or the variable $uploadSessionGuid might be not available. To check this instead of your job run this at first:
Start-Job -ScriptBlock {
Get-Command CopyDataToServer
Get-Variable uploadSessionGuid
}
Wait-Job -State Running
Get-Job | Receive-Job
If the job returns the command and the variable then the problem is elsewhere and debugging is not over. But if it fails or gets wrong results then this is the problem to be fixed (to make the command available and/or use a different way to supply the parameter).