Parameter passing

I have a function that takes 3 parameters. I am trying to use the parameters in an Invoke-Command statement, but the final parameter is not being recognized and I can’t figure out why.

function hide-update{
[cmdletbinding(SupportsShouldProcess=$true)]
param(
[parameter(mandatory=$true)][string]$computer,
[parameter(mandatory=$true)][string]$jobName,
[parameter(mandatory=$true)][string]$filter
)

if ($PSCmdlet.ShouldProcess(“$computer”)) {
Invoke-Command -ComputerName $computer {Register-ScheduledJob -Name $using:jobName -ScriptBlock {Hide-WUUpdate -Title $using:filter `
-confirm:$false} -ScheduledJobOption @{RunElevated=$true} -RunNow}
}

}

First of all I can say the function does work. The $computer and $jobName parameter work as they should. The $filter parameter is not being recognized inside of the Register-ScheduledJob -ScriptBlock {}. Because this parameter is not being recognized the result of running this function hides all of the updates as if it ignores the filter. I am using PowerShell 4.0. I did verify that the Invoke-Command works correctly when not using it in this function.

The $using:<variable name> is for a concept called Remote Variables. Remote Variables are (as far as I know) only available for remoting, e.g. working with sessions. If you want to read more about remote variables you can run the command

Get-Help about_remote_variables

The Register-ScheduledJob function does not use sessions for its work. IF you want to pass parameters to it you should use the -ArgumentList parameter to pass parameters to the Register-ScheduledJob function. An example of this (written in this text editor and thus not tested at all, but I believe it should work fine) would be:

Register-ScheduledJob -Name $using:jobName -ScriptBlock { PARAM ($FilterParameter) Hide-WUUpdate -Title $FilterParameter -Confirm:$false } -ArgumentList $filter -ScheduledJobOption @{ RunElevated = $true } -RunNow }

$using should be working. I think the problem with your $filter is that it’s embedded in a script block. PowerShell probably just doesn’t parse inside that.

Since I find myself coming to different conclusions than Don Jones, I’m guessing I have just misunderstood something or have missed something completely and I’m not testing what I should be testing. If someone sees an error in how I’m using Remote Variables or something else amiss in the tests, please correct me. However, from my naive, simple tests, it seems as though PowerShell does parse the scriptblock correctly, but using Remote Variables does not work with Register-ScheduledJob.

Invalid Remote Variable Name - Fails parsing and thus never invokes the command on the “remote” machine.

I get an error message if I use an invalid variable name like the following script (it’s probably unnecessarily large since I’ve been reusing the same test-method, just altered it a little for the other two tests):

1.  function Test-ScheduledJobWithInvalidRemoteVariableName
2.  {
3.      PARAM (
4.          $MyParam
5.      )
6.      
7.      Invoke-Command -ComputerName localhost -ScriptBlock {
8.          # First just plainly remove the job if it exists
9.          Get-ScheduledJob -Name "Test-ScheduledJobWithInvalidRemoteVariableName" -EA SilentlyContinue | Unregister-ScheduledJob
10.         
11.         $arguments = @{
12.             Name = "Test-ScheduledJobWithInvalidRemoteVariableName";
13.             ScheduledJobOption = @{ RunElevated = $true };
14.             RunNow = $True;
15.             ScriptBlock = { $Using:NonExistingRemoteVariableName | Out-File C:\InvalidRemoteVariableValue.txt };
16.         }
17. 
18.         Register-ScheduledJob @arguments
19.     }
20. }
21.
22. Test-ScheduledJobWithInvalidRemoteVariableName -MyParam "The invalid remote param value"

On line 15 we can see $Using:NonExistingRemoteVariableName which, of course, does not exist in the local scope. Running this code gives the following exception, making it seem as though PowerShell parses the scriptblock.

Invoke-Command : The value of the using variable '$using:NonExistingRemoteVariableName' can
not be retrieved because it has not been set in the local session.
At line:7 char:5
+     Invoke-Command -ComputerName localhost -ScriptBlock {
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Invoke-Command], RuntimeException
    + FullyQualifiedErrorId : UsingVariableIsUndefined,Microsoft.PowerShell.Commands.Invok 
   eCommandCommand

Also, it does not run any of the code inside the scriptblock passed to Invoke-Command, since it states that the script is using an invalid remote variable (error id: UsingVariableIsUndefined).

Scheduled Job with Remote Variable - Registers the job but the Remote Variable does not have any value

I don’t get any error message if, on the other hand, I use a correctly named local variable, the MyParam variable, as in the following code:

1.  function Test-ScheduledJobWithRemoteVariable
2.  {
3.      PARAM (
4.          $MyParam
5.      )
6.      
7.      Invoke-Command -ComputerName localhost -ScriptBlock {
8.          # First just plainly remove the job if it exists
9.          Get-ScheduledJob -Name "Test-ScheduledJobWithArgumentList" -EA SilentlyContinue | Unregister-ScheduledJob
10.         
11.         $arguments = @{
12.             Name = "Test-ScheduledJobWithArgumentList";
13.             ScheduledJobOption = @{ RunElevated = $true };
14.             RunNow = $True;
15.             ScriptBlock = { $Using:MyParam | Out-File C:\RemoteVariableValue.txt };
16.         }
17. 
18.         Register-ScheduledJob @arguments
19.     }
20. }
21.
22. Test-ScheduledJobWithRemoteVariable -MyParam "The remote param value"

(The only difference from the last code is the function name, the job name and the variable name used on line 15.)

Since the $Using:MyParam exists in the local scope, PowerShell accepts this script as valid. However, the C:\RemoteVariableValue.txt which was created will be an empty file.

Scheduled Job with ArgumentList - Correctly passes the expected parameter value to the Scheduled Job.

The third case, using -ArgumentList to pass the value of the variable to the scheduled job seems to work fine.

1.  function Test-ScheduledJobWithArgumentList
2.  {
3.      PARAM (
4.          $MyParam
5.      )
6.      
7.      Invoke-Command -ComputerName localhost -ScriptBlock {
8.          # First just plainly remove the job if it exists
9.          Get-ScheduledJob -Name "ScheduledJobWithArgumentList" -EA SilentlyContinue | Unregister-ScheduledJob
10.         
11.         $arguments = @{
12.             Name = "ScheduledJobWithArgumentList";
13.             ScheduledJobOption = @{ RunElevated = $true };
14.             RunNow = $True;
15.             ArgumentList = $Using:MyParam;
16.             ScriptBlock = { PARAM ($ArgumentPassed) $ArgumentPassed | Out-File C:\ArgumentListValue.txt }
17.         }
18. 
19.         Register-ScheduledJob @arguments
20.     } 
21. }
22. 
23. Test-ScheduledJobWithArgumentList -MyParam "The ArgumentList param value"

(The only difference from the last code is the function name, the job name, the scriptblock on line 16 and the argument ArgumentList was added on line 15.)

The C:\ArgumentListValue.txt file will have the correct “The ArgumentList param value” text as content.

Conclusion/Summary

The only way I get to work is using -ArgumentList to pass the value. So I’m probably doing something wrong when I try to use the $Using scope to use the parameter in the scheduled job, or using Remote Variables doesn’t work with ScheduledJobs. If someone finds an error in the above tests or reasoning, could you please point me to the error of my reasoning or perhaps a resource which explains Remote Variables, and how they are implemented, in detail?

I get the same things when I run your code, so you’re not missing anything. At least in this application; I’ve run a couple of other quick tests where a nested script block wasn’t picking up the variable. That was taking scheduled jobs out of the picture completely. Remote variables (e.g., $using) should be parsed by the SHELL, and bound to parameters; it shouldn’t matter what cmdlet you’re passing them to. Let me do a little more testing and see if I can come up with something a bit more definitive for you.

I’m wondering if you’ve tried using Trace-Command to see what PowerShell is doing? That’s what I plan to do.

So:

PowerShell will indeed parse inside a nested script block for $using; I was kinda wrong on that as a blanket statement.

PS C:\> invoke-command -ComputerName localhost -ScriptBlock { write $using:name ; & { write $using:proc } }

That works as expected. However, it works because I’m asking the remote computer to immediately execute the nested script block. What doesn’t work is $using inside a script block that is being passed as a parameter to something else, when that something else isn’t processing it immediately. So I was right on that count. It isn’t the scheduled job cmdlet per se.

In other words, PowerShell isn’t just replacing the $using reference with a static value. That isn’t how $using works. I guess… I’m not explaining this well, but I guess you could think of it as PowerShell not exactly creating a variable in the remote scope. It’s more hack-y, from what I’m getting from Trace-Command. The $using variable doesn’t “last” through passing the script block to the cmdlet. I suspect there are quite a few cmdlets, which accept script blocks as parameters, which would be affected by this. The scheduled job cmdlet doesn’t have the smarts to unwind the $using reference, if that makes sense, and its the cmdlet that would be responsible for doing so.

Passing the value via -ArgumentList works because that creates a variable in the remote scope. That’s certainly a cleaner way to do it, and is v2-compatible to boot ;). But I was able to replicate the $using problem with a couple of other commands that accept a script block as input but don’t execute them immediately, or whatever. So the problem isn’t with Register-ScheduleJob per se, although it’s certainly a cmdlet that would be affected by the behavior.

I guess, as a general practice, and if you only want to have to remember one safe way to do things, stay away from $using.

Thanks for the explanation! You came to the same explanation as I did then. The $Using scope will only be available for as long as the session is available. I did also replicate the same problem with other cmdlets which executes scriptblocks asynchronously. As soon as the session is no longer available, the $Using scope seems like it’s no longer available. Thus, anything that executes outside the session will not be able to use variables from the $Using scope. At least that is how it would seem to behave. Thanks for investigating and confirming!

Thank you both. This explanation makes a lot of sense.

Using the -ArgumentList worked. Again, thanks a lot.