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

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'.

Related

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

Cannot bind parameter to argument 'command' because it is null. Powershell

I had a function similar to below code. It receives command and the command arguments. I had to run this command in background and collect the output. But that last statement is bugging me with this error
Error:
Cannot bind argument to parameter 'Command' because it is null.
+ CategoryInfo : InvalidData: (:) [Invoke-Expression], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.InvokeExpre
ssionCommand
+ PSComputerName : localhost
Code:
$cmd = 'Get-content'
$Arg = 'path to file'
$sb = "$cmd $Arg -ErrorVariable e -ErrorAction Stop"
invoke-Expression $sb #This printsoutput
$job = Start-job -ScriptBlock {Invoke-Expression $sb}
wait-job -id $job.Id
$job | Receive-job #this should print output but throwing error
I am pretty sure last line is the one throwing the error.
Another alternative to get the $sb into the scope of the scriptblock besides -argumentlist is to use the $using: scope. (PowerShell 3+)
$cmd = 'Get-content'
$Arg = 'path to file'
$sb = "$cmd $Arg -ErrorVariable e -ErrorAction Stop"
$job = Start-job -ScriptBlock {Invoke-Expression $using:sb}
wait-job -id $job.Id
$job | Receive-job
The issue here is that you are not actually giving Invoke-Expression a command.
Whenever you create a new context ( in this case, a job ) you loose your access to the parent session's environment. In your case $sb is currently null.
The way that you manage this is to pass the values as arguments via the -ArgumentList parameter of Start-Job:
start-job -ScriptBlock {} -ArgumentList
In order to facilitate handing $sb to the ScriptBlock you would do this:
$sb = "$cmd $Arg -ErrorVariable e -ErrorAction Stop"
$job = start-job -ScriptBlock { Param([string]$sb)
Invoke-Expression $sb
} -ArgumentList $sb
This can be confusing so this is the same code written with more friendly names:
$OuterSB = "$cmd $Arg -ErrorVariable e -ErrorAction Stop"
$job = start-job -ScriptBlock { Param([string]$InnerSB)
Invoke-Expression $InnerSB
} -ArgumentList $OuterSB
I got the same error but when I run PowerShell using administrator privileges, I don't get this error. Good luck!
I also got the same error when running command with user without proper authorizations.
This in deed can be related to that.
For example my code:
$allParams = #(10, "Some Process")
Start-Job -ScriptBlock $restartProcessRobot -ArgumentList $allParams
<#
#descr
Function that restarts given proces -DisplayName after given number of seconds.
#param1 Integer number of seconds after which the process should be restarted.
#param2 String display name of process(es) that must be affected. Wild cards accepted.
#>
$restartServiceAfter = {
param($seconds, $processName)
Start-Sleep $seconds
Restart-Service -DisplayName $processName -Force
}
And user is not authorized to restart services.

Trying to pass variable to a PowerShell function which is then run as a Start-Job

I am trying to pass the values of a variable (or more but for testing just the one right now) from a function to a Powershell Start-Job.
This works if using the invoke-expression but I have to get rid of the | out-string if passing to the start-job.
Works:
$thecomp = "127.0.0.1"
$func = {$compr = $thecomp; function TestConnection {Test-Connection -ComputerName $compr} } | Out-String
invoke-expression $func
TestConnection
 
This does not work and notice the difference in the $func line:
$thecomp = "127.0.0.1"
$func = {$compr = $thecomp; function TestConnection {Test-Connection -ComputerName $compr} }
Start-Job -Name testjob -InitializationScript $func -ScriptBlock {TestConnection} -ArgumentList $compr,$thecomp | Out-Null
Wait-Job -Name testjob | Out-Null
Receive-Job -Name testjob
Remove-Job *
I get the same error whether this is the local machine or a remote machine:
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.
+ CategoryInfo : InvalidData: (:) [Test-Connection], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.TestConnectionCommand
+ PSComputerName : localhost
I've tried a number of different things including using -ArgumentList and other things. I'm really at a loss as to what I am missing. If I request the variables $compr and $thecomp in the ISE after running the above, I get the IP but it is not passing to the function that's within the Start-Job.
I got it working using a different method. This is what I am doing that works.
$system = '127.0.0.1'
$func = {
param ([string]$system)
Process {Test-Connection -ComputerName $system }
}
Start-Job -Name testjob -ScriptBlock $func -ArgumentList $system | Out-Null
Wait-Job -Name testjob | Out-Null
Receive-Job -Name testjob
Remove-Job *
It seems that the function is no longer explicitly named but is rather called or used with the variable defining it.
This also works and coincides better with what i was originally trying to do.
$system = '127.0.0.1'
$func = {function TestConnection ($system) {Test-Connection -ComputerName $system } } # | Out-String
Start-Job -Name testjob -InitializationScript $func -ScriptBlock {TestConnection $args} -ArgumentList $system | Out-Null

Getting output from a job

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

Variables in Start-Job do not get evaluated

My Powershell code doesn't evaluate the $agent variable:
foreach ($agent in $agentcomputers) {
Write-Output 'Starting agent on '$agent
# psexc to start the agent
Start-Job -ScriptBlock {& psexec $agent c:\grinder\examples\startAgent.cmd}
}
This link is similar to my problem, except I'm not calling an external Powershell script.
I tried adding that in, using $args[0] for $agent, and adding the -ArgumentList parameters, but that didn't work.
Edits/Replies
$agentcomputers is just a list of computer names - each on its own line:
$agentcomputers = Get-Content c:\grinder-dist\agent-computers.txt
I have also tried this - and $args[0] doesn't evaluate:
Start-Job -ScriptBlock {& psexec $args[0] c:\grinder\examples\startAgent.cmd} -ArgumentList #($agent)
Here are 3 different ways I would do it.
First, all aligned and pretty.
$agents = Get-Content c:\grinder-dist\agent-computers.txt
$jobs = {
Param($agent)
write-host "Starting agent on" $agent
& psexec \\$agent c:\grinder\examples\startAgent.cmd
}
foreach($agent in $agents) {
Start-Job -ScriptBlock $jobs -argumentlist $agent | Out-Null
}
Get-Job | Wait-Job | Receive-Job
Or you could just put it all on one line without creating any variables.
(Get-Content c:\grinder-dist\agent-computers.txt) | %{ Start-Job -ScriptBlock { param($_) write-host "Starting agent on" $_; & psexec \\$_ c:\grinder\examples\startAgent.cmd } -argumentlist $_ | Out-Null }
Get-Job | Wait-Job | Receive-Job
And in this final example, you could manage how many threads are run concurrently by doing it this way.
$MaxThreads = 5
$agents = Get-Content c:\grinder-dist\agent-computers.txt
$jobs = {
Param($agent)
write-host "Starting agent on" $agent
& psexec \\$agent c:\grinder\examples\startAgent.cmd
}
foreach($agent in $agents) {
Start-Job -ScriptBlock $jobs -argumentlist $agent | Out-Null
While($(Get-Job -State 'Running').Count -ge $MaxThreads) {
sleep 10
}
Get-Job | Wait-Job | Receive-Job
}
Here is the solution. As Andy said, I needed to use $args array with the -ArgumentList parameter. This other thread was helpful: Powershell: passing parameters to a job
foreach($agent in $agentcomputers){
$agentslash = "\\"+$agent
$args = ($agentslash,"c:\grinder\examples\startAgent.cmd")
Write-Output 'Starting agent on '$agent
#psexc to start the agent
$ScriptBlock = {& 'psexec' #args }
Start-Job -ScriptBlock $ScriptBlock -ArgumentList $args
}