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

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.

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}

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

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

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'

Launch PowerShell script into new window while passing variables

I've been using the site for a while, searching through the questions and answers, trying to map them to my scenario, but I'm either missing something, or what I'm looking to do isn't possible (at least the way I'm trying to do it), hence I'm hoping for a push in the right direction. Thanks in advance for reading.
I've been working on a fairly sizeable automation project. My main script performs a number of tasks, and generally works well, and reliably. At one stage of the script, I execute another PowerShell script, which was written by another team. I call the script as follows:
.\DeployMySQLProvider.ps1 -AzCredential $asdkCreds `
-VMLocalCredential $vmLocalAdminCreds `
-CloudAdminCredential $cloudAdminCreds
-PrivilegedEndpoint $ERCSip `
-DefaultSSLCertificatePassword $secureVMpwd -AcceptLicense
When I call it this way, from my main script, it works fine, however, this script uses and registers a DLL file during it's deployment, and locks it until the PowerShell window and session is closed. At the end of my main script, I have a cleanup phase, which can't complete it's job because of this locked DLL.
My thoughts therefore, were to launch the 2nd script into a new PowerShell window and session, either using Start-Process or Invoke-Expression, but I just can't seem to get either right. Most of the variables I'm passing through to the 2nd script aren't just strings, which is probably where I'm falling over. They are a mix of usernames and passwords (secure strings) along with $ERCSip which is a string.
Should I be looking at Start-Process / Invoke-Expression, or something else entirely? When I was testing with Start-Process, I had the following defined, but couldn't get the ArgumentList side working correcly for me (blank below):
Start-Process "$pshome\powershell.exe" -PassThru -Wait `
-Verb RunAs -ErrorAction Stop -ArgumentList ""
Any pointers in the right direction would be much appreciated.
Thanks!
I've used something similar to this in my scripting:
$scriptpath="c:\pathto\deploysqlProvider"
$a = "$scriptpath\DeployMySQLProvider.ps1 -AzCredential $asdkCreds `
-VMLocalCredential $vmLocalAdminCreds `
-CloudAdminCredential $cloudAdminCreds
-PrivilegedEndpoint $ERCSip ` "
-DefaultSSLCertificatePassword $secureVMpwd -AcceptLicense
Start-Process -Verb runas -FilePath powershell.exe -ArgumentList $a -wait -PassThru ;
Not sure if you need it to runas admin or not (-verb runas).
I'd suggest you then look for the Powershell process and path. So that if you have to kill this separate process you can.

Powershell Putty Connection and Automating Tasks

I want to use PowerShell to connect to a PuTTY "saved session" and then specify a file that contains some batch commands. Using CMD this would look like
d:\putty\psftp 'Saved Session Name' -b d:\location.txt.
I think that the PS equivalent should look like
Start-Process d:\putty\psftp.exe 'Saved Session Name'
(and then a call to pass a 'get' script) i.e. cd Outgoing get <date>.txt
However, I get the following error:
a positional parameter cannot be found that accepts the argument
How can I accomplish this using PowerShell?
All you need is plink:
plink 'Saved Session Name'
You don't necessarily need Start-Process.
What happens when you try to run d:\putty\psftp.exe 'Saved Session Name' -b d:\location.txt from Powershell? The first thing I do is try it exactly like I'd run it from the command line.
The biggest catch is if you have spaces in path names. You might need to use quotes and the call operator (ampersand): &"d:\putty\psftp.exe" 'Saved Session Name' -b "d:\location.txt".
If you do need to use Start-Process, you can do this:
Start-Process -FilePath "d:\putty\psftp.exe" `
-ArgumentList "'Saved Session Name' -b d:\location.txt" -Wait
Or like this:
Start-Process -FilePath "d:\putty\psftp.exe" `
-ArgumentList 'Saved Session Name', '-b', "d:\location.txt" -Wait
Note that the argument list in the first is a single string with every argument in it, and in the second it is an array of strings with one string for every argument. Everything needs to be in the same order that they'd be on the command line, and it's not uncommon for it to be a bit flaky. Usually one method or the other works better, but it depends on the application you're calling. Usually with quotes and spaces in path names because you're going through multiple levels of escaping depending on the program you're calling (noticing a theme?).
I added the -Wait parameter to my code above because, by default, Start-Process continues to the next line without waiting since it actually spawns a separate process. -Wait forces Powershell to, well, wait, which is what people usually want in a non-interactive script.
See Get-Help about_Operators or Get-Help "call operator" for more topics for help with the call operator. See Get-Help Start-Process for help with that.
Adding the below Technet Wiki link which contains various ways to run executables in PowerShell.
PowerShell: Running Executables
Try this:
$Path = "d:\putty\psftp.exe"
$Prm1 = 'Saved Session Name'
$Prm2 = "-b"
$Prm3 = "d:\location.txt"
&$Path $Prm1 $Prm2 $Prm3