Is it possible to call a powershell script within another script as a variable? - powershell

I have a Powershell script where the user passes in a script as a parameter. After that is passed in, I cannot call the script by using $scriptvariable. Is there any way to call a Powershell script from within another Powershell script, when the one script needs to be called from a variable.
param(
[string]hostval,
[string]$scriptpath
)
Invoke-Command -Computer $hostval -Scriptblock { $scriptpath } -credential $cred
This does not work, and I'm not sure if what I want is possible. Is there a parameter type (ex: [script]$scriptpath) that I can use so the script can be called from $scriptpath?

It sounds like you need to use the -FilePath parameter, instead of -Scriptblock:
-FilePath <String>
Runs the specified local script on one or more remote computers. Enter the path and file name of the script, or pipe a script path to Invoke-Command. The script must reside on the local computer or in a directory that the local computer can access. Use the ArgumentList parameter to specify the values of parameters in the script.

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 $PSScriptRoot empty when using Invoke-Command

When running a .ps1 on a remote computer using Invoke-Command, $PSScriptRoot is empty on the remote computer.
Is this normal? How can one find the .ps1 path when using Invoke-Command?
Further, other methods, such as through $MyInvocation are also empty.
Is this normal? How can one find the .ps1 path when using Invoke-Command?
There is no script file involved on the remote computer when you call
Invoke-Command -ComputerName $someComputer -FilePath c:\path\to\some\script.ps1
What PowerShell does in this case is to send the contents (parsed source code)[1] of the caller-local script file to the remote machine and executes it there.
Therefore, $PSScriptRoot (the directory in which a script file resides) doesn't apply and returns the empty string.
The same applies to other script-file-related automatic variables, such as $PSCommandPath and $MyInvocation.MyCommand.Name (see about_Automatic_Variables).
To put it differently: When the script file's code executes remotely, it doesn't know anything about the file it came from on the caller's side.
Thus, the only information available you there is $PWD, i.e., the current (working) location (directory).
[1] From the docs: "When you use this [the -FilePath] parameter, PowerShell converts the contents of the specified script file to a script block, transmits the script block to the remote computer, and runs it on the remote computer."

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'

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.

Get script directory in PowerShell when script is invoked with Invoke-Command

I have a set of PowerShell scripts that include a "common" script, located in the same folder, like this:
# some-script.ps1
$scriptDir = Split-Path -Parent $myinvocation.mycommand.path
. "$scriptDir\script-utils.ps1"
This is fine if the script is called directly, e.g.
.\some-script.ps1
However, if the script is called with Invoke-Command, this does not work:
Invoke-Command -ComputerName server01 -FilePath "X:\some-script.ps1"
In this case, infact, $myinvocation.mycommand contains the contents of the script, and $myinvocation.mycommand.path is null.
How can I determine the script's directory in a way that works also when the script is invoked with Invoke-Command?
NOTE
In the end, this is the solution I actually used:
Invoke-Command -ComputerName server01 `
{param($scriptArg); & X:\some-script.ps1 $scriptArg } `
-ArgumentList $something
This also allows passing parameters to the script.
I don't believe you can, from within the invoked script. From get-help invoke-command:
-FilePath
Runs the specified local script on one or more remote computers. Enter the path and file name of the script, or
pipe a script path to Invoke-Command. The script must reside on the local computer or in a directory that the
local computer can access. Use the ArgumentList parameter to specify the values of parameters in the script.
**When you use this parameter, Windows PowerShell converts the contents of the specified script file to a script
block, transmits the script block to the remote computer, and runs it on the remote computer.**
When you use invoke-command using the -filepath parameter, the script is read from the file on the local computer, converted to a script block, and that's what gets passed to the remote computer. The remote computer doesn't have any way of knowing if that script block was read from a file.
For the remote computer to know what that original file path was, you'll have to tell it. I think the easiest way to do that would be to write a function to do the invocation, and have it pass the filename to the invoked script as a parameter.
alternatively rather than using filepath.. you could pass in a scriptblock, that dotsources the script from a UNC path that all machines have access to. However each machine will need to have the appropriate executionpolicy set so that they can run that script from the UNC path
let me be clear though, that i'm not saying to run the script from the UNC path as if that does the remoting, but still using invoke-command or start-job to run a scriptblock on a remote computer. It just happens that that scriptblock will run the script from a UNC path for convenience.