tl;dr
Invoke-VMScript -ScriptText
accepts only a string containing PowerShell commands, not a script block ({...}
).
Since there is no separate parameter for passing arguments to the -ScriptText
code, the only way to include - invariably stringified - values from the caller's scope is to use string interpolation, as shown in the next section.
- Notably, the usual script-block-based mechanism of employing the
$using:
scope in order to embed values from the caller does not work.
Passing a script-block literal { ... }
to -ScriptText
technically works, because on conversion to string the verbatim content between {
and }
is used, but is best avoided, to avoid conceptual confusion.
Note:
Below is an explanation of your problem and a fix for your original approach.
You can bypass your problem if you add the calculated columns via select
(Select-Object
) on the caller's side (as hinted at by iRon), using the VM
property of the object output by Invoke-VMScript
, which contains the remote computer name (which Lee_Dailey helped discover):
$vmName = "VM NAME"
# Only run `Get-Disk` remotely, and run the `select` (`Select-Object`)
# command *locally*:
# Note the need to extract the actual script output via the .ScriptOutput
# property and the use of the .VM property to get the computer name.
$output = Invoke-VMScript -ScriptText { Get-Disk } -VM $vmName -GuestUser $Username -GuestPassword $Password |
select -ExpandProperty ScriptOutput -Property VM |
select @{l="ComputerName";e='VM'}, Number, @{name='Size (GB)';expr={[int]($_.Size/1GB)}}, PartitionStyle
The command you're passing to Invoke-VMScript
via -ScriptText
is executed on a different machine, which knows nothing about your local $vmName
variable.
In PowerShell remoting commands such as Invoke-Command
, the $using:
scope would be the solution: ... | select @{l="ComputerName";e={$using:vmName}}, ...
However, $using:
can only be used in script blocks ({ ... }
).
While you are trying to pass a script block to Invoke-VMScript -ScriptText
, it is actually converted to a string before it is submitted to the VM, because Invoke-VMScript
's -ScriptText
parameter is [string]
-typed.
Since Invoke-VMScript
has no separate parameter for passing arguments from the caller's scope, the only way to include values from the caller's scope is to use string interpolation:
$vmName = "VM NAME"
$output = Invoke-VMScript -ScriptText @"
Get-Disk |
select @{ l="ComputerName"; e={ '$vmName' } },
Number,
@{ name='Size (GB)'; expr={[int](`$_.Size/1GB)} },
PartitionStyle
"@ -VM $vmName -GuestUser $Username -GuestPassword $Password
Note the use of an expandable here-string (@"<newline>...<newline>"@
); important: the closing delimiter, "@
, must be at the very start of a line (not even whitespace is permitted before it); see this answer for more information about string literals in PowerShell.
$vmName
is now expanded up front in the caller's scope; to make the resulting e={ ... }
script block syntactically valid, the expanded $vmName
value must be enclosed in '...'
.
By contrast, the $
in $_
must be _escaped - as `$
- since it must be passed through to the remote machine.
That said, since you want to determine the actual computer name on that remote computer, you don't even need string interpolation; use $env:COMPUTERNAME
:
$vmName = "VM NAME"
$output = Invoke-VMScript -ScriptText @'
Get-Disk |
select @{ l="ComputerName"; e={ $env:COMPUTERNAME } },
Number,
@{ name='Size (GB)'; expr={[int]($_.Size/1GB)} },
PartitionStyle
'@ -VM $vmName -GuestUser $Username -GuestPassword $Password
Note that in this case - since no string interpolation is needed - a verbatim (literal) here-string (@'<newline>...<newline>'@
) is used, which also means that no escaping of pass-through $
chars. is required.
When string interpolation isn't needed, you could actually pass the script text via a script block ({ ... }
), which simplifies the syntax somewhat (a script block stringifies to its verbatim content between {
and }
), but since a string is ultimately passed, it is better to use one to begin with, for conceptual clarity.