1

I have the code below that I have been working on and I have an issue, I can not get it to send the variable message to the computer, if I take out the variable it works but that is not what I was trying to accomplish with it.

Function Send-PopupMessage {
    #Requires -Version 2.0 
    [CmdletBinding()]  
    Param(
        [Parameter(Mandatory = $true)]
        [String]$ComputerName,
        [Parameter(Mandatory = $true)]
        [String]$Message
    )

    Invoke-Command -ComputerName $ComputerName -Scriptblock {
        $CmdMessage = "msg.exe * $Message"
        Write-Host $CmdMessage
        $CmdMessage | Invoke-Expression
    }
}

This is not the same as the question linked because I am in a session to another computer using PSWA so I am not able to start another session from this. Also even when I changed my code to be more like the one in the "Duplicate" question I am still getting the same results that the cmd being sent to the other computer is

msg.exe * '' instead of msg.exe * 'Test Message'

Community
  • 1
  • 1
Luke
  • 647
  • 1
  • 8
  • 22

1 Answers1

2

PowerShell script blocks are not lexical closures by default. The scriptblock passed to Invoke-Command does not save the current value of the $Message parameter when being run on the other computer.

When the block is run in the remote session, it is using the current value of $Message in that session. Because that variable is most likely $null, the message is omitted from your command.

Use the $using:variable syntax described in this question to capture the value of $Message.

Invoke-Command -ComputerName $ComputerName -Scriptblock {
    $CmdMessage = "msg.exe * $using:Message"
    Write-Host $CmdMessage
    $CmdMessage | Invoke-Expression
}

The $using:variable syntax only works when invoking a block on a remote computer. If you need to capture variables in a scriptblock for local execution, instead call GetNewClosure() on the ScriptBlock.

$Message = "Hey there."
$closure = {
    $CmdMessage = "msg.exe * $Message"
    Write-Host $CmdMessage
    $CmdMessage | Invoke-Expression
}.GetNewClosure()

$Message = $null
Invoke-Command -Scriptblock $closure
Community
  • 1
  • 1
Ryan Bemrose
  • 9,018
  • 1
  • 41
  • 54
  • While your explanation is correct, I don't think your solution works: assuming that your local machine is set up for remoting, contrast the following commands (only the last one works): `$foo = 'bar'; icm -computername . { $foo }` vs. `$foo = 'bar'; icm -computername . { $foo }.GetNewClosure()` vs. `$foo = 'bar'; icm -computername . { $using:foo }` – mklement0 Jul 30 '16 at 05:01
  • AFAIK, for remote invocation `Invoke-Command` is ignore any module `ScriptBlock` is attached to, so `GetNewClosure()` useless here. – user4003407 Jul 30 '16 at 05:02
  • 1
    Edited the answer to include the `$using:var` syntax, though I agree that probably makes this a dupe. Thanks @mklement0 – Ryan Bemrose Jul 30 '16 at 05:33
  • If you don't use `-ComputerName` with `Invoke-Command`, local execution in the current scope is performed, where local variables are directly accessible - no need for `.GetNewClosure()`. Compare `$foo = 'bar'; icm { $foo }`, which is effectively the same as `$foo = 'bar'; & { $foo }`. I'm unclear on when `.GetNewClosure()` is actually needed. – mklement0 Jul 30 '16 at 13:27
  • GetNewClosure saves the state of the variables when it is called. In the example above, the `$Message` variable is set to null, but the scriptblock invocation still uses its old value. I have used this to save variable state for later execution. – Ryan Bemrose Jul 30 '16 at 16:39
  • That worked like a charm, and yes I think you are right that my question might be a duplicate, but thank you for taking the time to explain the answer! – Luke Aug 01 '16 at 14:35