Getting output from a job - powershell

I'm failing miserably to get any output from jobs - what is wrong with the following code?
$test = {
DIR
}
$mjob = Start-Job -ScriptBlock {$test}
while (Get-Job -State Running){}
Receive-Job -Job $mjob -OutVariable $otest
Write-Host($otest)

When you use -OutVariable supply only the name of the variable e.g.:
... -OutVariable otest
unless $otest happens to contain the name of the variable you want to save the output to.
A few other suggestions. $test represents a scriptblock so you don't need to put {} around it. And rather than wait using a while loop, just use Wait-Job e.g.:
$test = { get-childitem }
$job = Start-Job $test
Wait-Job $job
Receive-Job $job -OutVariable otest
$otest

You can use the pipeline to wait for the job to finish and then receive its result. Make sure to remove the braces when you pass a scriptblock to the ScriptBlock parameter, otherwise you're creating a nested scriptblock:
$test = { DIR }
Start-Job -ScriptBlock $test | Wait-Job | Receive-Job

Related

Powershell Execute program in parallel and wait end of execution

I need to execute a program (.exe) in a powershell script in a foreach loop, I need to wait the end of execution before doing some other tasks.
I tried this solution, the program is launched but it's closing immediately
$jobArray = New-Object -TypeName System.Collections.ArrayList
ForEach ($item in Get-Content C:\items.txt) {
$job = Start-Job -ScriptBlock {Start-Process "C:\Development\Console.exe" -ArgumentList /count, /Id:$item, /verbose }
$jobArray.Add($job)
}
Write-Verbose "started" -Verbose
#Wait for all jobs
ForEach ($job in $jobArray) {
$job | Wait-Job
}
A process already is like a job, it runs in parallel. In fact, PowerShell jobs are also just processes, so you currently start a process just to start another process.
Use Start-Process -PassThru without -Wait to capture a process object on which you can wait using Wait-Process.
$processes = ForEach ($item in Get-Content C:\items.txt) {
Start-Process -PassThru -FilePath 'C:\Development\Console.exe' -ArgumentList '/count', "/Id:$item", '/verbose'
}
Write-Verbose "started" -Verbose
# Wait for all processes
$processes | Wait-Process
As another optimization, most of the times you use a foreach or ForEach-Object loop you don't need to use ArrayList explicitly. Just assign the loop statement or pipeline to a variable and let PowerShell create an array for you in an efficient way.

Invoke-Command one to Many without timeouts

I am trying to run series of commands on remote Windows Servers.
Some of these servers are up and some of them not. To some I have access and to some of them I do not have access.
Options to address this:
I can go with:
$Computername = PC1, PC2, PC3
Invoke-Command -ComputerName $computerName -FilePath .\script
Now what bugs me is that the invoke-command timeout takes too long and cannot made shorter.
So what I was thinking about is to use PowerShell 7 Parallel loops + Invoke-Command + Powershell Jobs:
$server_list = Import-Csv .\server-list.csv
$list = $server_list.Name
$my_array = $list | Foreach-Object -ThrottleLimit 32 -Parallel {
$x = start-job -name $_ -ScriptBlock { invoke-command -ComputerName $_ -FilePath .\script.ps1}
$x | Wait-Job -Timeout 5
$x | Where-Object {$_.State -ne "Completed"} | Stop-Job
$zz = Receive-Job -Name $_
$zz
}
$my_array
Error I am getting is:
Invoke-Command: Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
Invoke-Command: Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
Invoke-Command: Cannot validate argument on parameter 'ComputerName'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
Any idea how to address this?
This is how you can apply the logic you're looking for without so much overhead, you only really need a loop to enumerate each computer and Start-ThreadJob which comes pre-installed in PowerShell Core.
As for the error, you can use the $using: scope modifier when referring to variable defined in the local scope. Example 9 from the docs has a good explanation on this.
$jobs = foreach($computer in (Import-Csv .\server-list.csv).Name) {
Start-ThreadJob {
Invoke-Command -ComputerName $using:computer -FilePath .\script.ps1
} -ThrottleLimit 32
}
# Wait 5 seconds for them
$jobs | Wait-Job -Timeout 5
# Split the array between Completed and the other states // Running, Failed, etc.
$completed, $failed = $jobs.Where({ $_.State -eq 'Completed' }, 'Split')
# get the output from the completed ones
$completed | Receive-Job -Wait -AutoRemoveJob
# These maybe you want to output to console before removing?
$failed | Stop-Job -PassThru | Remove-Job

Script-Block parsing issues

I am running some code that is read from an encrypted file and converted into a ScriptBlock. The code will be a full complex script, but for simplicity let's assume it is the following:
"$(date) Agent started." | Out-File -FilePath 'C:\TMP\test_agent.log' -Append
while($true) {
'$(date) Will check back in 30 seconds...' | Out-File -FilePath 'C:\TMP\test_agent.log' -Append
Start-Sleep -Seconds 30
}
Below is the simple code that launches it, and it works just fine (the $sStr variable contains the above script as a string):
$job = Start-Job -ScriptBlock {
$sb = $executioncontext.invokecommand.NewScriptBlock($args[0])
Invoke-Command -ScriptBlock $sb
} -ArgumentList $sStr | Wait-Job -Timeout 1 | Receive-Job
Again, this works fine. However, I need this to run as a new PowerShell process. But when I try the below the ScriptBlock is not parsed properly and I get errors. Here is the modified launcher:
$job = Start-Job -ScriptBlock {
$sb = $executioncontext.invokecommand.NewScriptBlock($args[0])
powershell "Invoke-Command -ScriptBlock $sb"
} -ArgumentList $sStr | Wait-Job -Timeout 1 | Receive-Job
How to start a new powershell process (and kill the parent) so that the ScriptBlock is correctly parsed and executed?
Thanks!
Maybe you could solve your problem with a PowerShell background job, which can be created via Start-Job. Start-Job also has a -ScriptBlock parameter.
Example 7 of Get-Job shows you how to check if the job is already completed, and how you can retrieve the results of the job.
Hope that helps.

How do I send a module to a Powershell Start-Job?

I send in a module to Start-Job but it seems to be empty.
Why isn't it filled with my start method?
$m = New-Module -ScriptBlock{
function start(){
"started"
};
} -AsCustomObject
$m.start()
Start-Job -ScriptBlock{
$module = $args[0]
"<argsO:$module>"
} -ArgumentList $m
I run the above and then:
PS C:\temp> job 510 | Receive-Job -Keep
to get:
<argsO:>
Alas, the $module seems to be empty.
Your $module variable is being populated, it just doesn't produce any output. You can check by putting $module | gm inside your Start-Job and you will see that it is a PSCustomObject:
Start-Job -ScriptBlock{
$module = $args[0]
$module | gm
} -ArgumentList $m
Get-Job | Wait-Job | Receive-Job
Unfortunately what you're trying to do won't work because the object gets deserialized when its passed as an argument and as a result it loses its methods, so attempting to call .start() within the job returns:
[Deserialized.System.Management.Automation.PSCustomObject] does not
contain a method named 'start'.

start-job to run script parallelly

Please help , really very much worried
How to transform the below script using start-job , I have 6 Modules to compare , but sequentially it's taking too much time I am trying to adopt start-job option so that I can run this compare parallelly or in background
Tried this -
Start-Job -Name "Comparecontrol" -filepath $ExecuteSbtWithDcmDm -ArgumentList $CompareControl,"",$false,$false | Out-Null
echolog $THISSCRIPT $DCM_UPDATE_LOG_FILE $LLINFO "Finished Control Master Comparison
Main Script
The general flow would be something like this:
$jobs = #()
$jobs += Start-Job -scriptblock {...}
...
$jobs += Start-Job -scriptblock {...}
Wait-Job $jobs
$results = Receive-Job $jobs
You can use a job name as an alternative to storing the job instance returned by Start-Job e.g.:
$jobName = 'CompareControl'
foreach ($script in $scripts)
{
Start-Job -Name $jobName-scriptblock {&$script} -ArgumentList ...
}
Wait-Job -Name $jobName
$results = Receive-Job -Name $jobName