Generate multiple scriptblock through the pipeline in PowerShell - powershell

Basically I want to make the following concise using the pipeline:
{Remove-Job 27}, {Remove-Job 29} | % { Invoke-Command -Session $s -ScriptBlock $_; };
To something like (conceptually)
#(27, 29) | % {Remove-Job $_;} | % { Invoke-Command -Session $s -ScriptBlock $_; };
What would be a good way to do this?

Your second example isn't quite right, because you have to emit a ScriptBlock in order to call Invoke-Command on it. Therefore, rather than actually calling Remove-Job on the local machine, you'd want to pass it as a ScriptBlock to the remote computer. Here is the most concise way I can think of at the moment, to achieve what you're after:
27, 29 | % { Invoke-Command -Session $s -ScriptBlock { Remove-Job -Id $args[0]; } -ArgumentList $_; };
Even though you didn't explicitly come out and display the code, it's obvious that you are pre-creating the PowerShell Session (aka. PSSession) object, prior to calling Invoke-Command. You can also simplify things by not pre-creating the PSSession, and simply using Invoke-Command with the -ComputerName parameter. Here is an example:
$ComputerList = #('server01.contoso.com', 'server02.contoso.com', 'server03.contoso.com');
Invoke-Command -ComputerName $ComputerList -ScriptBlock { Remove-Job -Id $args[0]; } -ArgumentList 27,29;
Note: I also moved the Job IDs directly into -ArgumentList, rather than piping them in. I generally try to avoid using the PowerShell pipeline, unless it really makes sense (eg. taking advantage of Where-Object, Get-Member, or Select-Object). Everyone has a different approach.

Any reason this wouldn't work?
Invoke-Command -Session $s -ScriptBlock { 27,29 |% {Remove-Job -Id $_ } }

Another approach would be to use variable expansion, and the Create() method on the ScriptBlock type.
27, 29 | % { Invoke-Command -Session $s -ScriptBlock $([ScriptBlock]::Create("Remove-Job -Id $_")) };
I'm not sure that is any more concise than Trevor's approach.

The Id parameter of the Remove-Job cmdlet accepts an array of ID's so you could speicfy them like so:
Invoke-Command -Session $s -ScriptBlock { Remove-Job -Id 27,29 }

Related

How to mock a job in Pester?

We're trying to assess if Invoke-Command has been called exactly one time.
Script.ps1
$job = Invoke-Command -ScriptBlock {'test'} -ComputerName localhost -AsJob
$job | Wait-Job
Script.Tests.ps1
BeforeAll {
$testScript = $PSCommandPath.Replace('.Tests.ps1', '.ps1')
Mock Invoke-Command
}
Describe 'Test' {
It 'should be green' {
. $testScript
Should -Invoke Invoke-Command -Times 1 -Exactly -Scope It
}
}
The problem is mocking the job object in Pester. When the job is not mocked, Wait-Job will throw an error that it didn't receive a job object.
How is it possible to mock a PowerShell job object in Pester?
One solution might be to have the Mock of Invoke-Command still create a legitimate job, but executing some script/code that you deem safe for the purpose of testing.
To do this, you need to first put the Invoke-Command cmdlet in a variable so that you can use it via that variable (because a Mock can't directly call its own command).
For example:
$InvokeCommand = Get-Command Invoke-Command
Mock Invoke-Command {
& $InvokeCommand -ScriptBlock {'some safe alternative code'} -ComputerName localhost -AsJob
}

Getting command output from Invoke-Command

I'm trying to use Invoke-Command to run a command in PowerShell remotely on a number of machines, and capture their output from it, but I'm not getting any output from it. I suspect it's from how I'm using Start-Process, but I'm not sure.
$RunCommand = {
Start-Process "$env:ProgramFiles\Some Program\someprogram.exe" -ArgumentList "-SignatureUpdate"
}
$comp_list = #(Get-Content "c:\temp\comp_list.txt")
$cred = Get-Credential
$jobs = Invoke-Command -Credential $cred -Computer $comp_list -ScriptBlock $RunCommand -AsJob
Wait-Job $jobs
$r = Receive-Job $jobs
$r | % { $_ > c:\temp\$($_.PScomputerName).output }
Is there a better way to invoke a command using environment variables like that?
I've found that Start-Process is probably not the best way to capture the output from a binary unless the binary itself passes on .NET objects. What I did find that works better for this is the call operator:
$RunCommand = {
$exe = "$env:ProgramFiles\Some Program\someprogram.exe"
& $exe -SignatureUpdate
}
I was still not getting the output I was expecting. More specifically, I was only getting the last line of output from the command instead of the entire thing. Eventually it dawned on me that all the previous lines were being overwritten, and so I changed the last line to this:
$r | % { $_ >> c:\temp\$($_.PScomputerName).output }
Note: I changed > to >> for appending to the file.

Splitting out an array from a splatted object

Okay, so I've got a bit of code which calls another PS script from within a PS script, and passes in a couple of parameters: Invoke-Command -ScriptBlock { param($script,$a1,$a2) &$script #($a1,$a2) } -ArgumentList #($scriptToRun,$p1,$p2) -ComputerName localhost -Credential $cred
The problem I'm experiencing however, is the receiving script is getting both $a1 & $a2 combined together in $args[0]. What I can't figure out as yet, is how do I split out the two array elements again?
Alternatively, how can I get them to pass correctly without the #()?
Splatting isn't required here, so simply remove the #():
Invoke-Command -ScriptBlock {
param($script,$a1,$a2)
& $script $a1 $a2
} -ArgumentList $scriptToRun,$p1,$p2 ...
The default behavior is to assign each argument from the argument list to the corresponding positional parameter in the parameter definition of the script block.
Or, if you want to use splatting, you need to do it like this:
Invoke-Command -ScriptBlock {
param($script,$params)
& $script #params
} -ArgumentList $scriptToRun,#($p1,$p2) ...

Invoke-Command with dynamic function name

I found this awesome post: Using Invoke-Command -ScriptBlock on a function with arguments
I'm trying to make the function call (${function:Foo}) dynamic, as in I want to pass the function name.
I tried this:
$name = "Foo"
Invoke-Command -ScriptBlock ${function:$name}
but that fails. I also tried various escape sequences, but just can't get the function name to be dynamic.
EDIT: For clarity I am adding a small test script. Of course the desired result is to call the ExternalFunction.
Function ExternalFunction()
{
write-host "I was called externally"
}
Function InternalFunction()
{
Param ([parameter(Mandatory=$true)][string]$FunctionName)
#working: Invoke-Command -ScriptBlock ${function:ExternalFunction}
#not working: Invoke-Command -ScriptBlock ${invoke-expression $FunctionName}
if (Test-Path Function:\$FunctionName) {
#working,but how to use it in ScriptBlock?
}
}
InternalFunction -FunctionName "ExternalFunction"
Alternate solution:
function foo {'I am foo!'}
$name = 'foo'
$sb = (get-command $name -CommandType Function).ScriptBlock
invoke-command -scriptblock $sb
I am foo!
as simple as :
invoke-expression $name
or if you want to keep invoke-commande for remoting for example
Invoke-Command -ScriptBlock { invoke-expression $name}
You could try the following. It tests if the name specified is a valid function before it attempts to run it:
$myfuncnamevar = "Foo"
Invoke-Command -ScriptBlock {
param($name)
if (Test-Path Function:\$name) {
#Function exists = run it
& $name
}
} -ArgumentList $myfuncnamevar

How to capture the Return Value of a ScriptBlock invoked with Powershell's Invoke-Command

My question is very similar to this one, except I'm trying to capture the return code of a ScriptBlock using Invoke-Command (so I can't use the -FilePath option). Here's my code:
Invoke-Command -computername $server {\\fileserver\script.cmd $args} -ArgumentList $args
exit $LASTEXITCODE
The problem is that Invoke-Command doesn't capture the return code of script.cmd, so I have no way of knowing if it failed or not. I need to be able to know if script.cmd failed.
I tried using a New-PSSession as well (which lets me see script.cmd's return code on the remote server) but I can't find any way to pass it back to my calling Powershell script to actually DO anything about the failure.
$remotesession = new-pssession -computername localhost
invoke-command -ScriptBlock { cmd /c exit 2} -Session $remotesession
$remotelastexitcode = invoke-command -ScriptBlock { $lastexitcode} -Session $remotesession
$remotelastexitcode # will return 2 in this example
Create a new session using new-pssession
Invoke your scripblock in this session
Fetch the lastexitcode from this session
$script = {
# Call exe and combine all output streams so nothing is missed
$output = ping badhostname *>&1
# Save lastexitcode right after call to exe completes
$exitCode = $LASTEXITCODE
# Return the output and the exitcode using a hashtable
New-Object -TypeName PSCustomObject -Property #{Host=$env:computername; Output=$output; ExitCode=$exitCode}
}
# Capture the results from the remote computers
$results = Invoke-Command -ComputerName host1, host2 -ScriptBlock $script
$results | select Host, Output, ExitCode | Format-List
Host : HOST1
Output : Ping request could not find host badhostname. Please check the name and try again
ExitCode : 1
Host : HOST2
Output : Ping request could not find host badhostname. Please check the name and try again.
ExitCode : 1
I have been using another method lately to solve this problem. The various outputs that come from the script running on the remote computer are an array.
$result = Invoke-Command -ComputerName SERVER01 -ScriptBlock {
ping BADHOSTNAME
$lastexitcode
}
exit $result | Select-Object -Last 1
The $result variable will contain an array of the ping output message and the $lastexitcode. If the exit code from the remote script is output last then it can be fetched from the complete result without parsing.
To get the rest of the output before the exit code it's just:
$result | Select-Object -First $(result.Count-1)
#jon Z's answer is good, but this is simpler:
$remotelastexitcode = invoke-command -computername localhost -ScriptBlock {
cmd /c exit 2; $lastexitcode}
Of course if your command produces output you'll have to suppress it or parse it to get the exit code, in which case #jon Z's answer may be better.
It is better to use return instead of exit.
For example:
$result = Invoke-Command -ComputerName SERVER01 -ScriptBlock {
return "SERVER01"
}
$result