Call PowerShell script with arguments from another powershell script - powershell

How do you call a PowerShell script which takes named arguments from within a PowerShell script?
foo.ps1:
param(
[Parameter(Mandatory=$true)][String]$a='',
[Parameter(Mandatory=$true)][ValidateSet(0,1)][int]$b,
[Parameter(Mandatory=$false)][String]$c=''
)
#stuff done with params here
bar.ps1
#some processing
$ScriptPath = Split-Path $MyInvocation.InvocationName
$args = "-a 'arg1' -b 2"
$cmd = "$ScriptPath\foo.ps1"
Invoke-Expression $cmd $args
Error:
Invoke-Expression : A positional parameter cannot be found that accepts
argument '-a MSFT_VirtualDisk (ObjectId =
"{1}\\YELLOWSERVER8\root/Microsoft/Windo...).FriendlyName -b 2'
This is my latest attempt - I've tried multiple methods from googling none seem to work.
If I run foo.ps1 from the shell terminal as ./foo.ps1 -a 'arg1' -b 2 it works as expected.

After posting the question I stumbled upon the answer. For completeness here it is:
bar.ps1:
#some processing
$ScriptPath = Split-Path $MyInvocation.InvocationName
$args = #()
$args += ("-a", "arg1")
$args += ("-b", 2)
$cmd = "$ScriptPath\foo.ps1"
Invoke-Expression "$cmd $args"

Here is something that might help future readers:
foo.ps1:
param ($Arg1, $Arg2)
Make sure to place the "param" code at the top before any executable code.
bar.ps1:
& "path to foo\foo.ps1" -Arg1 "ValueA" -Arg2 "ValueB"
That's it !

Related

How to call powershell script from other powershell script and script is assigned in the powershell object instead of the file

I am writing a PowerShell script and I have another PowerShell script.
I know, we can use below code if it is stored in the path
$scriptPath = "D:\Ashish\powershell\script.ps1"
$argumentList = "asdf fgh ghjk"
$output =Invoke-Expression "& `"$scriptPath`" $argumentList"
but my PowerShell is stored in the object instead of a file. I am using the below code
$argumentList = "asdf fgh ghjk"
$logPath = "C:\AshishG\powershell\script21.txt"
$x = 'Write-Host "Hello script2" return "script2"' #This is my powershell script
//Write code here to call this script($x) with params and store the return value in the other object and also store the logs in $logpath
The one way could be to store the PowerShell to the script.ps1 but I think, there should be some way to call it from the PowerShell object itself?
Please share your suggestions.
Seems like you're looking for a script block:
$argumentList = "asdf fgh ghjk"
$logPath = "C:\AshishG\powershell\script21.txt"
$x = {
param($arguments, $path)
"Arguments: $arguments"
"Path: $path"
}
A script block can be executed using the call operator &:
& $x -arguments $argumentList -path $logPath
Or the dot sourcing operator .:
. $x -arguments $argumentList -path $logPath
.Invoke(..) method works too however it's not commonly used and not recommended in this context. See this answer for more information:
$x.Invoke($argumentList, $logPath)
Yet another option is to call the [scriptblock]::Create(..) method, if the script is stored in strings this is the recommended alternative over Invoke-Expression which should be avoided.. This is also very useful for example when we need to pass a function to a different scope. Thanks #mklement0 for the reminder on this one :)
$argumentList = "asdf fgh ghjk"
$logPath = "C:\AshishG\powershell\script21.txt"
$x = #'
"Arguments: $argumentList"
"Path: $logPath"
'#
& ([scriptblock]::Create($x))
# Or:
$scriptblock = [scriptblock]::Create($x)
& $scriptblock

Powershell: Pass multiple values (with spaces) to an argument

I have a script that uses multiple arguments, some of which contain spaces. The script is called from another script, so I pass the arguments to it from variables with the calling script.
Calling script:
$script = "C:\Path\script.ps1"
$arg1 = "SomeValue"
$arg2 = "1234"
$arg3 = #("Value1","Some Value","Value 2")
$arg4 = $true
Invoke-Command $script -Arg1 $arg1 -Arg2 $arg2 -Arg3 $arg3 -Arg4 $arg4
The called script looks like this:
param (
[Parameter(Mandatory=$false,Position=0)]
[String]$arg1,
[Parameter(Mandatory=$false,Position=1)]
[String]$arg2,
[Parameter(Mandatory=$false,Position=2)]
[array]$arg3,
[Parameter(Mandatory=$false,Position=3)]
[bool]$arg4
)
# Do stuff with the arguments
When I call the script, I get the following error:
"A positional parameter cannot be found that accepts argument 'Some'."
I've also manually called the script (bypassing the calling script) in a powershell window as below:
powershell.exe -ExecutionPolicy bypass C:\Path\script.ps1 -Arg1 "SomeValue" -Arg2 "1234" -Arg3 #("Value1","Some Value","Value 2") -Arg4 $true
powershell.exe -ExecutionPolicy bypass C:\Path\script.ps1 -Arg1 "SomeValue" -Arg2 "1234" -Arg3 "Value1","Some Value","Value 2" -Arg4 $true
powershell.exe -ExecutionPolicy bypass C:\Path\script.ps1 -Arg1 "SomeValue" -Arg2 "1234" -Arg3 "Value1","SomeValue","Value2" -Arg4 $true
powershell.exe -ExecutionPolicy bypass C:\Path\script.ps1 -Arg1 "SomeValue" -Arg2 "1234" -Arg3 "Value1,SomeValue,Value2" -Arg4 $true
None of these variations work. I've also tried the ideas found here by changing the Arg3 value to (,$args) but that doesn't work. I also changed the parameter type as found here, but that didn't work either.
The goal is to be able to pass multiple variables (some with spaces) to the script through an argument/parameter.
EDIT 12/22/16: The goal includes passing this same information from a shortcut/typed command. For example, my calling script creates a RunOnce entry in the registry to reference the called script and places the arguments in the call just like the manual examples above. None of them work, either.
Set-ItemProperty $RegROPath "(Default)" -Value "powershell.exe -ExecutionPolicy Bypass $scriptPath $argumentList" -type String
Replace Invoke-Command With & or .
Use & if all you want is output, . if you want it to run in the current context (e.g. retain all variables set)
Get-Help about_Scripts for more detail (or read online version here)
Edit: Forgot to mention, it's not your script that is throwing that error, it's Invoke-Command. If you absolutely have to use Invoke-Command you need (running remotely for example) to pass the arguments as the parameter ArgumentList like this:
$script = "C:\Path\script.ps1"
$argumentList = #(
'-arg1 "SomeValue"',
'-arg2 1234',
'-arg3 #("Value1","Some Value","Value 2")',
'-arg4 $true'
)
Invoke-Command -FilePath $script -ArgumentList $argumentList
Edit 2:
I will try your suggestion as soon as I am able to. One question, what if I need to add a conditional argument? Currently, I add arguments to the list with $argumentlist += ("arg5", "value"). Some of them are conditional: if ($bool) {$argumentlist += ("arg5", "value")}. Is there a way to do that in your example?
Yes you can, the $argumentList variable in the example is an array like any other. It can be defined all at once, defined empty and added to later, or any mix.
Example
$argumentList = #(
'-arg1 "SomeValue"',
'-arg2 1234',
'-arg3 #("Value1","Some Value","Value 2")',
'-arg4 $true'
)
if ($bool) {
$argumentList += '-arg5 "value"'
}
Invoke-Command -FilePath $script -ArgumentList $argumentList
But again, unless you are running the command on a remote computer or PSSession, you should use & or dot sourcing (.). You can still add arguments conditionally using splatting (about_Splatting)
Example
$scriptParamsSplat = #{
arg1 = "SomeValue"
arg2 = 1234
arg3 = #("Value1","Some Value","Value 2")
arg4 = $true
}
if ($bool) {
$scriptParamsSplat.arg5 = "value"
}
& 'C:\Path\To\script.ps1' #scriptParamsSplat

Start-WebAppPool for Powershell 2.0

I'm experiencing a problem: I need a command to use on two different shells that gives me the chance to restart a web pool appliction, but only one of these shells is 4.0 (and has the command Start-WebAppPool) and the other one has Ps 4.0...
What I'm supposed to do?
Thanks in advance for any help.
I found a solution that works surely from Powershell 2.0 and above.
I called it inside of a script like that:
$args = #()
$args += "stop"
$args += "apppool"
$args += "/apppool.name:PoolNameHere"
$cmd = "C:\Windows\System32\inetsrv\appcmd.exe"
Invoke-Expression "$cmd $args" #PoolNameHere stopped.
Dually, for restarting:
$args = #()
$args += "start"
$args += "apppool"
$args += "/apppool.name:PoolNameHere"
$cmd = "C:\Windows\System32\inetsrv\appcmd.exe"
Invoke-Expression "$cmd $args" #PoolNameHere started.
Hope this helps somebody!

Run batch file on UNC path with parameters and capture output to variable without Community Extensions

I'd like to run a command such as:
pushd \\myServer\share\scripts
myBatchFile.bat param1 param2 "parameter 3"
popd
Only initiating through powershell.
NB: The name of the batch file is held in a variable, as are each of the parameters.
function Escape-StringForCmd($a)
{
if(($a -like '*"*') -or ($a -like '* *'))
{
('"{0}"' -f ($a -replace '"','""'))
}
else
{
$a
}
}
$batch = "myBatchFile.bat"
$p1 = "param1"
$p2 = "param2"
$p3 = "parameter 3"
$batch = Escape-StringForCmd($batch)
$p1 = Escape-StringForCmd($p1)
$p2 = Escape-StringForCmd($p2)
$p3 = Escape-StringForCmd($p3)
pushd \\myServer\share\scripts
cmd.exe /c $batch $p1 $p2 $p3
#above fails; no error returned; I think because cmd doesn't like the UNC path, so reverts to the system directory
Start-Process "cmd.exe" -ArgumentList "/c",$batch,$p1,$p2,$p3 -NoNewWindow -Wait -WorkingDirectory "\\myServer\share\scripts"
#above also fails; not sure why as looks healthy when running outside of ps1 file
popd
I've also interested in capturing the output - though as at present the batch file's not being run I'll focus on that initially.
I've not yet tried the ProcessStartInfo solution (see link below) as it seems start-process, or simply cmd.exe /c should work (certainly when I've run tests outside of a ps1 file this has worked), but I'll resort to trying that method shortly.
ProcessStartInfo solution: Powershell: Capturing standard out and error with Process object
Using #JNK's answer along with the below hack, I found a way to get this to work
$tempBatchName = ".\~myTempBatchFile.bat" #put this in a variable so we can easily amend if required
"
pushd \\myServer\share\scripts
$batch $p1 $p2 $p3
popd
" | out-file $tempBatchName -encoding ascii
$MyCmd = ("{0} *>&1" -f $tempBatchName)
$ReturnOutput = Invoke-Expression $MyCmd
$ReturnOutput | out-file ("{0}.log" -f $tempBatchName)
remove-item $tempBatchName
Is there a reason you can't use invoke-expression for this?
$MyCmd = "$batch $p1 $p2 $p3 *>&1"
$ReturnOutput = Invoke-Expression $MyCmd
The *>&1 puts all output from the StdErr and StdOut to the output stream.
More info on redirection operators here.

Run Executable from Powershell script with parameters

So I have a powershell script that is supposed to run an executable with an argument to pass to set which method I want to run, and I need to pass a parameter, which is a directory to a config file. So this is what I have
Start-Process -FilePath "C:\Program Files\MSBuild\test.exe" -ArgumentList /genmsi/f $MySourceDirectory\src\Deployment\Installations.xml
/f is the shortname and file is the long name for my attribute... I get an error in powershell telling me that a positional parameter cannot be found for /f or /file.
Any thoughts?
Try quoting the argument list:
Start-Process -FilePath "C:\Program Files\MSBuild\test.exe" -ArgumentList "/genmsi/f $MySourceDirectory\src\Deployment\Installations.xml"
You can also provide the argument list as an array (comma separated args) but using a string is usually easier.
Here is an alternative method for doing multiple args. I use it when the arguments are too long for a one liner.
$app = 'C:\Program Files\MSBuild\test.exe'
$arg1 = '/genmsi'
$arg2 = '/f'
$arg3 = '$MySourceDirectory\src\Deployment\Installations.xml'
& $app $arg1 $arg2 $arg3
I was able to get this to work by using the Invoke-Expression cmdlet.
Invoke-Expression "& `"$scriptPath`" test -r $number -b $testNumber -f $FileVersion -a $ApplicationID"
Just adding an example that worked fine for me:
$sqldb = [string]($sqldir) + '\bin\MySQLInstanceConfig.exe'
$myarg = '-i ConnectionUsage=DSS Port=3311 ServiceName=MySQL RootPassword= ' + $rootpw
Start-Process $sqldb -ArgumentList $myarg