PowerShell Invoke-Command with FilePath on local computer - vague parameters error? - powershell

I want to run a script in file on the local machine using Invoke-Command so I can pass in parameters with -ArgumentList. I've been getting an error I don't understand, so I simplified my command. When I do this:
Invoke-Command -FilePath 'getprocess.ps1'
The content of getprocess.ps1 is:
Get-Process
The error message I get is:
Invoke-Command : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:1
+ Invoke-Command -FilePath 'getprocess.ps1'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-Command], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.InvokeCommandCommand
I'm baffled by this error message. What does it mean? How do I get this to work?

tl;dr:
Generally, do not use Invoke-Command for local invocations - while technically possible, there's only one specific use case where doing so is called for (see below).
Instead, invoke scripts directly:
.\getprocess.ps1
Note: Unlike cmd.exe, PowerShell by design requires .\ in order to execute an executable located in the current directory. That is, to avoid accidental execution of executables in the current directory rather than from a directory listed in $env:Path, PowerShell, as a security feature, requires you to signal the intent to execute something in the current directory (.) explicitly.
For script blocks ({ ... }), use &, the call operator (e.g., & { Get-Date }).
For syntactic reasons alone, you situationally also need & for script-file paths if they're specified either as a quoted path (e.g., & '.\getprocess.ps1') and/or if the path involves variable references (e.g.,
& $HOME\getprocess.ps1).
(Separately, . , the dot-sourcing operator is needed in both cases in order to execute a script [block] directly in the caller's scope rather in a child scope).
Note that you can technically combine passing a script block to Invoke-Command (parameter -ScriptBlock) with invoking a local script:
# The script block positionally binds to the -ScriptBlock parameter.
# This is essentially the more expensive equivalent of:
# & .\getprocess.ps1
Invoke-Command { .\getprocess.ps1 }
This is slower and offers no advantage over direct invocation.
However, there is one conceivable use case:
If the script isn't an advanced script and you wanted to take advantage of Invoke-Command's stream-output-collecting
common parameters, such as -ErrorVariable (if the script or function being invoked is advanced, it supports these common parameters itself).
# Invoke locally and collect errors in $errs
Invoke-Command { .\getprocess.ps1 } -ErrorVariable errs
Caveat: At least as of PowerShell 7.2, Invoke-Command does not apply the common -ErrorAction parameter to errors that occur in the script block, so it cannot be used to control error handling; e.g., -ErrorAction Stop has no effect on the commands in the script block.
As for what you tried:
Indeed, as you point out in your own answer, -FilePath must be combined with the
-ComputerName parameter (that the error message is so generic is unfortunate).
More generally, -FilePath must be combined with any of the parameters that request remote execution, which includes -Session, -ConnectionUri, -VmId / -VmName, and, on Unix-like platforms, -HostName, and -SSHConnection.
The purpose of parameter -FilePath is to copy the content of a local script (*.ps1 file) to a remote computer for execution there. That is, it is a convenient mechanism of executing the code of a script that is (only) available locally on a remote machine.
While you can technically target the local computer via -ComputerName localhost (or, more succinctly, via -ComputerName . / -cn .), this does not amount to a local call:
Whenever -ComputerName is specified - even with -ComputerName localhost - PowerShell's remoting infrastructure is used, which has major implications:
The target computer - even if it is the local one - must be set up for PowerShell remoting - see about_Remote_Requirements.
If you target the local machine specifically, you must be running in an elevated session (running as administrator).
Execution will be much slower than direct (local) invocation.
Type fidelity can be lost for both input and output data, given that cross-process marshaling via PowerShell's XML-based serialization infrastructure is involved - see this answer.
That said, if the intent is to locally test remote execution of your script, and your local machine is set up as a remoting target, then use of -ComputerName localhost (-ComputerName . / -cn .) makes perfect sense, given that PowerShell's remoting infrastructure is then involved in the same way it would be in a truly remote call.
Note, however, that such "loopback remoting" calls require elevation (running as admin).

Although the error message doesn't make it clear, the -FilePath parameter makes the -ComputerName parameter required. To explicitly target the local computer, use -ComputerName localhost.
Invoke-Command -FilePath 'getprocess.ps1' -ComputerName localhost

I can't really use invoke-command locally. You use that when you're trying to run a command on a remote PC.
For example, you would want to run something like:
invoke-Command -ComputerName REMOTE-PC -Credentials $credential -Scriptblock {Get-Process}
That error is basically telling you that you need to fill out more parameters that are tied to that command.
try running Get-Help Invoke-Command to see some info on the command and how to run it.

You must have a computername.
$parameters = #{
ComputerName = '255.255.255.255'
FilePath = 'getprocess.ps1'
Credential = 'Domain01\User01'
}
invoke-command #parameters
Use your IP :) To allow that you must also include -credential
If that doesn't do it... invoke-expression may be a semi-suitable replacement for testing until you are ready to invoke-command on the remote machine.

You could run it locally like this, but you'd have to be at the administrator (elevated) prompt. It's nice to be able to run it as a test.
invoke-command localhost getprocess.ps1
You can actually do a strange form of parallelism too:
invoke-command localhost,localhost,localhost getprocess.ps1

Related

Invoke-command and running ps1 with parameters

I'm trying to run a script using invoke-command to install defender for endpoint with some associated parameters.
If I run a standard ps1 using invoke-command it works with no issues. However, if I run the following:
Invoke-Command -ComputerName NAME -FilePath \\srv\share\install.ps1 -OnboardingScript \\srv\share\WindowsDefenderATPonboardingscript.cmd -Passive
I receive "A parameter cannot be found that matches parameter name 'OnboardingScript'". Can someone please help me understand how I invoke a command and run a script with parameters?
Parameters already defined in the install.Ps1 file
https://github.com/microsoft/mdefordownlevelserver/blob/main/Install.ps1
Many thanks in advance
Your Invoke-Command call has a syntax problem, as Santiago Squarzon points out:
Any pass-through arguments - those to be seen by the script whose path is passed to -FilePath - must be specified via the -ArgumentList (-Args) parameter, as an array.
# Simplified example with - of necessity - *positional* arguments only.
# See below.
Invoke-Command -ComputerName NAME -FilePath .\foo.ps1 -Args 'bar', 'another arg'
The same applies to the more common invocation form that uses a script block ({ ... }), via the (potentially positionally implied) -ScriptBlock parameter.
However, there's a catch: Only positional arguments can be passed that way, which:
(a) requires that the target script support positional argument binding for all arguments of interest...
(b) ... which notably precludes passing switch parameters (type [switch]), such as -Passive in your call.
(c) requires you to pass the invariably positional arguments in the correct order.
Workaround:
Use a -ScriptBlock-based invocation, which allows for regular argument-passing with the usual support for named arguments (including switches):
If, as in your case, the script file is accessible by a UNC path visible to the remote session as well, you can simply call it from inside the remote script block.
Note: It isn't needed in your case, but you generally may need $using: references in order to incorporate values from the local session into the arguments - see further below for an example.
Invoke-Command -ComputerName NAME {
& \\srv\share\install.ps1 -OnboardingScript \\srv\share\WindowsDefenderATPonboardingscript.cmd -Passive
}
Otherwise (typically, a script file local to the caller):
Use a $using: reference to pass the content (source code) of your script file to the remote session, parse it into a script block there, and execute that script block with the arguments of interest :
$scriptContent = Get-Content -Raw \\srv\share\install.ps1
Invoke-Command -ComputerName NAME {
& ([scriptblock]::Create($using:scriptContent)) -OnboardingScript \\srv\share\WindowsDefenderATPonboardingscript.cmd -Passive
}
Small caveat: Since the original script file's source code is executed in memory in the remote session, file-related reflection information won't be available, such as the automatic variables that report a script file's full path and directory path ($PSCommandPath and $PSScriptRoot).
That said, the same applies to use of the -FilePath parameter, which essentially uses the same technique of copying the source code rather than a file to the remote session, behind the scenes.
thanks for your reply. I have managed to get this working by adding -ScriptBlock {. "\srv\share etc}

Invoke remote .exe on server with argument list

I'm quite new to PowerShell and I'm hoping to ask a question about a command I'm attempting to run.
I've read and read everything I can find on this so apologies in advance if I'm asking the impossible or something dumb.
From the Windows CLI on the remote computer I can run the following command;
'c:\config-files\app.exe foo /o /last' the exe generates an output file by reading the foo files and saves it as foo.txt.
app.exe doesn't exist in within the c:\config-files, when running it
on the computer the app.exe is in the local env path within
c:\main-app.
- The above is one of the key points here which has been addressed in replies below.
I've tried adding a path to the exe but that seems to be ignored when performing the following;
path='c:\main-app\'
& Invoke-Command -ComputerName foo -ScriptBlock { & cmd.exe /c "c:\config-files\app" } -ArgumentList 'foo', '/last', '/o'
The above fails (probably obvious to some!)
If I run:
Invoke-Command foo -ScriptBlock {& cmd.exe /c "c:\main-app\app" }
The application runs in the PowerShell window, I just then seem to be unable to send Arguments to the application.
Invoke-Command -Computername foo -ScriptBlock {param ($myarg) "cmd.exe /c c:\main-app\app" $myarg } -ArgumentList 'foo', '/last', '/o'
This is the closest I think I've got, but it only reads one argument and that's being invoked from the documents and setting folder of the user attempting to execute the command and not that path of the binary.
I've tried many, many things to make this work but still don't seem to be able to get past this point, any help you can provide on this would be hugely appreciated.
Thanks in advance for your time.
You don't need cmd /c to invoke a console application (or any external program).
To access arguments passed to a script block from within the script, either use the automatic $Args array or declare parameters explicitly (as you've attempted with a single parameter).
You can use an array directly for passing its elements as individual arguments to an external program.
Invoke-Command -Computername foo -ScriptBlock {
c:\main-app\app $Args # invoke app.exe, passing arguments through
} -ArgumentList 'foo', '/last', '/o'
Additionally, you mention wanting to interpret arguments that are file paths as relative to the directory in which the application is located; the simplest solution is to use a Set-Location command first:
Invoke-Command -Computername foo -ScriptBlock {
Set-Location c:\main-app
.\app $Args
} -ArgumentList 'foo', '/last', '/o'

Difference between - command and ScriptBlock Powershell

ps1 is on remote machine . I am running below command from some other mahcine.
Is there any difference between using both below script ---
invoke-command -computer $MachineName -command { C:\hello.ps1 }
invoke-command -computer $MachineName -scriptblock{ C:\hello.ps1 }
Also, I am going to use for loop for multiple machine having same script name but having diff sequence of work that is located on each remote machine only . Want to understand the execution for second machine will go only if first get completed . Correct ?
Difference between -command and -scriptblock
There is no difference in execution. -commandis merely an alias for scriptblock. You can verify this by getting the parameter info for the invoke-command command
(Get-Command -Name Invoke-Command).Parameters.Values | select name, aliases
Sequential execution
Yes, the execution is sequential. The command you specify will execute on the second machine after the command has completed on the first machine.
According to the help
These commands run synchronously (one at a time). When the commands
complete, the output of the commands from all of the computers is
saved in the $version variable. The output includes the name of the
computer from which the data originated.
To answer your second question: Invoke-Command works in parallel. It runs what ever is mentioned in the script block to all the machines in parallel. By default, powershell will talk upto 32 computers at once. If you specify more than that, it will queue them up so that as one computer completes, the next one in line will begin. However, I believe, you can increase that number by specifying the -throttlelimit parameter of the Invoke-Command.

Run Powershell script that uses -List (parameter) with alternate credentials that

I was struggling to get this simple (?) function working today.
I have a PowerShell script that reads computer names from a txt-based file. It works fine when run from a PowerShell session by the following one-liner:
./"Server Health Check.ps1" -List One-off.txt
As you can see, it's got a long file name, so it's wrapped with quotes.
However, I'm building a PowerShell GUI form with radio boxes that will pass on a choice for a text file that will be used to a call the script. Trick is, the script needs to be run with alternate admin account, and i'm not clear how to make that work.
For another script I've got that doesn't use I know i can use something along the lines of the following, this uses the old DOS "runas", however, it doesn't work with the -list function.
invoke-command -scriptblock {runas.exe /user:domain\$Env:Username"admin" "powershell.exe -file \"\\Server\c$\LONG FOLDER\Server Health Check.PS1""}
So, in a nutshell, how do get a script to launch with alternate credentials that reads a parameter (-List) from the command line? I'm also keen to preserve my directory structure, which includes folders with spaces. The script is titled: "Server health check.ps1"
The last thing I tried was the following
$ScriptPath = "C:\SCRIPTS FOLDER\Server Health Check.ps1"
$ArgList = "-List C:\SCRIPTS FOLDER\One-off.txt"
Invoke-Command -filepath $ScriptPath -Credential DragonBallDomain\$Env:UserName"Admin" -ArgumentList $ArgList
The result was the following message:
Invoke-Command : Parameter set cannot be resolved using the specified named parameters.
I'm almost certain this is do-able by invoke-command or start-process, it's just a matter of getting the correct formatting? I'm probably missing a / or a ' or "" somewhere in my trials with start-process or invoke-command.
Any help appreciated!
Update for April 30:
I've tried some more to make this work, i'm close, but still not quite there.
$LongScriptPath = resolve-path Script.ps1
$LongFolderPath = \\UNC\PATH TO FOLDER\WITH LONG NAME\
start-process -filepath powershell.exe -argumentlist " -file``"$($FilePath.path)`"" -cred DOMAIN\USERID -WorkingDirectory "$LongFolderPath"
Adding the -credential is what causes an error that states that the -file parameter is invalid. I'm sure there's a way to do this.
Note: Completely rewritten after the requirements became clearer.
To run a command as a different user locally, use Start-Process -Credential ...
That is what you've attempted in your update in principle, but there are problems with how you're passing parameters; try this instead:
$LongScriptPath = resolve-path Script.ps1
$LongFolderPath = '\\UNC\PATH TO FOLDER\WITH LONG NAME\'
start-process `
powershell.exe `
-ArgumentList '-file', $LongScriptPath, '-List', 'One-off.txt' `
-Credential DOMAIN\USERID `
-WorkingDirectory $LongFolderPath
The key to making this work is to pass all parameters to pass to powershell.exe as an array via Start-Process's -ArgumentList parameter, which means that the parameters must be ,-separated.
Note how an array is always parsed in expression mode, which means that literal string elements such as -file and -List must be quoted.
It is important in general to understand the difference between PowerShell's two fundamental parsing modes, argument mode and expression mode, and which is applied when - see https://technet.microsoft.com/en-us/library/hh847892.aspx
Add -Wait to wait for the script to finish; Start-Process is asynchronous by default (all PS cmdlets named Start-* are).
Caveat: For commands invoked as a different user, you can only wait from an elevated prompt.
If it isn't, the command will still execute, but will do so asynchronously, and you'll get an Access denied error message in the current console; in effect, -Wait is ignored.
Only if not running as a different user: Add -NoNewWindow -Wait if you want to run the script in the current console window; Start-Process opens a new window by default for console applications such as powershell.exe and cmd.exe.
If you do run the command as a different user, -NoNewWindow is quietly ignored.
As for the original symptom and why using Invoke-Command to run a command locally as a different user is ill-advised:
Invoke-Command -Credential ... requires that the -ComputerName parameter be specified too.
Run Get-Help Invoke-Command to see all parameter sets that involve the -Credential parameter. The OP's original command had only -Credential, but not -ComputerName, which caused PS to complain that no parameter set could be unambiguously identified.
Once you use -ComputerName, PowerShell remoting is invariably used, even if you specify . - the local computer - as the only computer to target.
Using remoting has two implications:
Remoting is not available by default, and must be configured on the target computer (the local computer, in this case).
Using remoting requires invocation with admin privileges.
In short:
While you can perform purely local invocations with Invoke-Command, you cannot do so as another user, because that invariably involves remoting.
Start-Process, by contrast, solely exists to run commands locally, optionally as a different user.

Best practices for writing PowerShell scripts for local and remote usage

What are some of the best practices for writing scripts that will execute in a remote context?
For instance, I just discovered that built-in var $Profile doesn't exist during remote execution.
Profile
You've discovered one main difference, $profile not being configured.
Buried in MSDN here are some FAQs about remote powershell, or do get-help about_Remote_FAQ.
Under the "WHERE ARE MY PROFILES?" (heh) it explains:
For example, the following command runs the CurrentUserCurrentHost profile
from the local computer in the session in $s.
invoke-command -session $s -filepath $profile
The following command runs the CurrentUserCurrentHost profile from
the remote computer in the session in $s. Because the $profile variable
is not populated, the command uses the explicit path to the profile.
invoke-command -session $s {. "$home\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"}
Serialization
Another difference that may affect you is that instead of the .NET objects returned by commands being just directly returned, when you run them remotely and return them, they get serialized and deserialized over the wire. Many objects support this fine, but some do not. Powershell automatically removes methods on objects that are no longer "hooked up", and they're basically data structures then... but it does re-hook methods on some types like DirectoryInfo.
Usually you do not have to worry about this, but if you're returning complex objects over a pipe, you might...
Script blocks don't act as closures, like they do normally:
$var = 5
$sb={ $var }
&$sb # 5
Start-Job $sb | Wait-Job | Receive-Job # nothing