How to run EXE in powershell with parameters, in one line - powershell

I need to run a .exe with parameters, currently this takes two lines. I would like to be able to pass a string as a variable to a function, and only need to do this once.
This is how I am currently doing it, which takes two variables:
function Foo{
$ExeToStart = "C:\Program Files\x\program.exe"
Start-Process $ExeToStart -ArgumentList "--arg1","--arg2"
}
Is there a way of combining this so that I can just define a variable, "y" and pass it into the function as one line similar to below?
$x = "C:\Program Files\x\program.exe -ArgumentList "--arg1","--arg2""
function Foo{
Start-Process $x
}

Here is a way you can achieve what you want.
# Create an array of the program and arguments
$x = 'C:\Program Files\x\program.exe','--arg1','--arg2'
# Example with no parameters
Function Foo {
Start-Process -FilePath $args[0][0] -ArgumentList $args[0][1..$args[0].Count]
}
# Example using parmeters
Function Foo {
param([string[]]$program)
$params = #{
FilePath = $program[0]
}
if ($program.Count -gt 1) {
$params['ArgumentList'] = $program[1..$program.Count]
}
Start-Process #Params
}
# Call the function
Foo $x

Related

Chaining powershell scripts with varying named parameters

I have several Powershell scripts which I want to chain together. Each script has some named parameters, and some of these parameters can be shared across scripts.
a.ps1
param(
[String]$foo="",
[String]$bar=""
)
& "./b.ps1"
b.ps1
param(
[String]$baz=""
)
& "./c.ps1"
c.ps1
param(
[String]$thing="",
[String]$stuff="",
[String]$baz="",
[String]$foo=""
)
I want to call the first script in this stack with a single list of arguments
powershell ./a.ps1 -foo myfoo -bar mybar -baz mybaz -thing mything -stuff mystuff
and have each script take what it needs from the parameter list, and then pass the entire list on to the next script, all the way down the chain. The number and order of the parameters in each script can change over time, and this shouldn't have to require changing code in the other scripts. Is this possible? I've tried the usual splatting stuff like #args, and that doesn't seem to work (received parameters are all empty). I can combined bound and unbound args with
$allArgs = $PsBoundParameters.Values + $args
but this arranges all parameters in a fixed sequence that requires each script in the chain to follow the parameter list of its caller.
Edit : The purpose of this exercise to completely obscure the logic within the chain from the point where we call it. I simply provide a list of all parameters required by the entire stack, and each unit script can throw an error if a parameter is invalid.
Create a property bag and pass that.
function a ([PSCustomObject] $obj)
{
"foo: $($obj.foo)"
"bar: $($obj.bar)"
b #PSBoundParameters
}
function b ([PSCustomObject] $obj)
{
"baz: $($obj.baz)"
c #PSBoundParameters
}
function c ([PSCustomObject] $obj)
{
"thing: $($obj.thing)"
"stuff: $($obj.stuff)"
"baz: $($obj.baz)"
"foo: $($obj.foo)"
}
$obj = [PSCustomObject]#{
foo = $null
bar = $null
baz = $null
thing = $null
stuff = $null
}
$obj.foo = "1"
$obj.bar = "2"
$obj.baz = "3"
$obj.thing = "4"
$obj.stuff = "5"
a $obj
# output
foo: 1
bar: 2
baz: 3
thing: 4
stuff: 5
baz: 3
foo: 1
However, I don't recommend doing this because it is shortsighted
$obj = [PSCustomObject]#{
foo = $null
bar = $null
baz = $null
thing = $null
stuff = $null
foov2 = $null
barv2 = $null
bazv2 = $null
thingv2 = $null
stuffv2 = $null
foov3 = $null
barv3 = $null
bazv3 = $null
thingv3 = $null
stuffv3 = $null
stuffv3 = $null
}
and dangerous.
function executeCommand ([PSCustomObject] $obj) { "$($obj.myCommand)" }
$obj = [PSCustomObject]#{
myCommand = "harmless"
}
# lab
$obj.myCommand = "format drive"
executeCommand $obj
# production
executeCommand $obj
Instead, explicitly define and call functions. You might choose to save and reuse variables in scripts. You can dot source functions or create a script-based module.
# UtilityFunctions.ps1
function executeCommand ([string] $command, [string] $hostname) { "$command $hostname" }
# lab.ps1
. C:\scripts\UtilityFunctions.ps1
$hostname = "lab"
$command = "format drive"
executeCommand $command $hostname
# production.ps1
. C:\scripts\UtilityFunctions.ps1
$hostname = "production"
$command = "list free space"
executeCommand $command $hostname
Script scope and dot sourcing
How to Write a PowerShell Script Module - PowerShell | Microsoft Docs
Also, your parameters have a default value of an empty string which probably is not your intent.
param(
[String]$foo="",
[String]$bar=""
)
param(
[String]$foo,
[String]$bar
)
default values

Powershell get return value from visual basic script

I have some old code in visual basic script. Instead of re-writing this old code into PowerShell, I'd like to call the VB scripts from PowerShell and capture their return value.
How can I get the return value of a visual basic script in powershell?
Something like this:
$returnValue = Invoke-Command -ScriptBlock{.\vbs\legacyVbsFunction.vbs}
The visual basic function may look like this
Function MyFunction() As Double
Return 3.87 * 2
End Function
It sounds like you want to capture a VBScript's (stdout) output:
$output = cscript.exe //nologo .\vbs\legacyVbsFunction.vbs
Note that $output will either be a single string - if the script outputs just 1 line - or an array of strings in case of multi-line output.
For example, assuming that .\vbs\legacyVbsFunction.vbs contains the following code:
Function MyFunction
MyFunction = 3.87 * 2
End Function
' Call the function and print it to stdout.
Wscript.Echo(MyFunction)
You could capture the output and convert it to a [double] as follows:
[double] $output = cscript.exe //nologo .\vbs\legacyVbsFunction.vbs
$output then contains 7.74.
You can actually embed a vbscript function right in powershell using a com object called ScriptControl, but it only works in 32-bit powershell, C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe: Embed VBS into PowerShell
function Call-VBScript
{
$sc = New-Object -ComObject ScriptControl
$sc.Language = 'VBScript'
$sc.AddCode('
Function MyFunction
MyFunction = 3.87 * 2
End Function
')
$sc.CodeObject
}
$vb = Call-VBScript
$returnvalue = $vb.MyFunction()
"returnvalue is " + $returnvalue
I found out you can run a job as 32-bit:
$returnvalue =
start-job {
function Call-VBScript {
$sc = New-Object -ComObject MSScriptControl.ScriptControl.1
$sc.Language = 'VBScript'
$sc.AddCode('
Function MyFunction
MyFunction = 3.87 * 2
End Function
')
$sc.CodeObject
}
$vb = call-vbscript
$vb.MyFunction()
} -runas32 | wait-job | receive-job
"returnvalue is " + $returnvalue
You don't really have an exit code in the VBS, you have a return for a function.
To actually have a return code you must close the script with something like:
wscript.quit(0)
or
wscript.quit(returncode)
In the powershell script you must execute the vbs something like this:
(Start-Process -FilePath "wscript.exe" -ArgumentList "Script.vbs" -Wait -Passthru).ExitCode

Passing parameters to a PowerShell job [duplicate]

This question already has an answer here:
Parenthesis Powershell functions
(1 answer)
Closed 7 years ago.
I've been toying around with this dang parameter passing to powershell jobs.
I need to get two variables in the script calling the job, into the job. First I tried using -ArgumentList, and then using $args[0] and $args[1] in the -ScriptBlock that I provided.
function Job-Test([string]$foo, [string]$bar){
Start-Job -ScriptBlock {#need to use the two args in here
} -Name "Test" -ArgumentList $foo, $bar
}
However I realized that -ArgumentList gives these as parameters to -FilePath, so I moved the code in the scriptblock into its own script that required two parameters, and then pointed -FilePath at this script.
function Job-Test([string]$foo, [string]$bar){
$myArray = #($foo,$bar)
Start-Job -FilePath .\Prog\august\jobScript.ps1 -Name 'Test' -ArgumentList $myArray
}
#\Prog\august\jobScript.ps1 :
Param(
[array]$foo
)
#use $foo[0] and $foo[1] here
Still not working. I tried putting the info into an array and then passing only one parameter but still to know avail.
When I say no avail, I am getting the data that I need however it all seems to be compressed into the first element.
For example say I passed in the name of a file as $foo and it's path as $bar, for each method I tried, I would get args[0] as "filename path" and args[1] would be empty.
ie:
function Job-Test([string]$foo, [string]$bar){
$myArray = #($foo,$bar)
Start-Job -FilePath .\Prog\august\jobScript.ps1 -Name 'Test' -ArgumentList $myArray
}
Then I called:
$foo = "hello.txt"
$bar = "c:\users\world"
Job-Test($foo,$bar)
I had jobScript.ps1 simply Out-File the two variables to a log on separate lines and it looked like this:
log.txt:
hello.txt c:\users\world
#(empty line)
where it should have been:
hello.txt
c:\users\world
you don't need to call the function like you would in java. just append the two variables to the end of the function call Job-Test $foo $bar

Synax Highlighting w/ Dynamic Scriptblock

I'm creating a dynamic ScriptBlock the way below so I can use local functions and variables and easily pass them to remote computers via Invoke-Command. The issue is that since all the text inside Create is enclosed with double quotes, I loose all my syntax highlighting since all editors see the code as one big string.
While this is only a cosmetic issue, I'd like to find a work around that allow my code to be passed without having double quotes. I've tried passing a variable inside Create instead of the actually text, but it does not get interpreted.
function local_admin($a, $b) {
([adsi]"WinNT://localhost/Administrators,group").Add("WinNT://$a/$b,user")
}
$SB = [ScriptBlock]::Create(#"
#Define Function
function local_admin {$Function:local_admin}
local_admin domain username
"#)
Invoke-Command -ComputerName server2 -ScriptBlock $SB
You can pass the function into the remote session using the following example. This allows you to define the ScriptBlock using curly braces instead of as a string.
# Define the function
function foo {
"bar";
}
$sb = {
# Import the function definition into the remote session
[void](New-Item -Path $args[0].PSPath -Value $args[0].Definition);
# Call the function
foo;
};
#(gi function:foo) | select *
Invoke-Command -ComputerName . -ScriptBlock $sb -ArgumentList (Get-Item -Path function:foo);
Here is a modified version of your function. Please take note that the domain and username can be dynamically passed into the remote ScriptBlock using the -ArgumentList parameter. I am using the $args automatic variable to pass objects into the ScriptBlock.
function local_admin($a, $b) {
([adsi]"WinNT://localhost/Administrators,group").Add("WinNT://$a/$b,user")
}
$SB = {
#Define Function
[void](New-Item -Path $args[0].PSPath -Value $args[0].Definition);
# Call the function
local_admin $args[1] $args[2];
}
Invoke-Command -ComputerName server2 -ScriptBlock $SB -ArgumentList (Get-Item -Path function:local_admin), 'domain', 'username';

Powershell Start-Process to start Powershell session and pass local variables

Is there a way to use the Powershell Start-Process cmdlet to start a new Powershell session and pass a scriptblock with local variables (once of which will be an array)?
Example:
$Array = #(1,2,3,4)
$String = "This is string number"
$Scriptblock = {$Array | ForEach-Object {Write-Host $String $_}}
Start-Process Powershell -ArgumentList "$Scriptblock"
Thanks.
I'm pretty sure there's no direct way to pass variables from one PowerShell session to another. The best you can do is some workaround, like declaring the variables in the code you pass in -ArgumentList, interpolating the values in the calling session. How you interpolate the variables into the declarations in -ArgumentList depends on what types of variables. For an array and a string you could do something like this:
$command = '<contents of your scriptblock without the curly braces>'
Start-Process powershell -ArgumentList ("`$Array = echo $Array; `$String = '$String';" + $command)
I was able to get this to work by joining the array with "/" to create a string and entering the scriptblock into another .ps1 script with appropriate parameters and splitting the joined string back to an array within the second script and using
Start-Process Powershell -ArgumentList "&C:\script.ps1 $JoinedArray $String"
Ugly, but it's the only way I could get it to work. Thanks for all the replies.
You could wrap the contents of your script block in a function, and then call the function from the ArgumentList and pass in the variables as parameters to the function, as I do on this post.
$ScriptBlock = {
function Test([string]$someParameter)
{
# Use $someParameter to do something...
}
}
# Run the script block and pass in parameters.
$myString = "Hello"
Start-Process -FilePath PowerShell -ArgumentList "-Command & {$ScriptBlock Test('$myString')}"
The command line options for PowerShell.exe say that you should be able to pass arguments when using a script block by adding -args:
PowerShell.exe -Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] }
However when I try to do that I get the following error:
-args : The term '-args' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and
try again.
I added $MyInvocation | fl to the script block to see what was happening, and it looks like the -args is just appended to the deserialized commands in the script block (hence the error since -args is not a valid command). I also tried using GetNewClosure() and $Using:VariableName but those only appear to work when the script block is invoked (as opposed to this where we are using it to serialize/deserialize the commands).
The I was able to get it to work by wrapping it in a function like deadlydog's answer.
$var = "this is a test"
$scriptblock = {
$MyInvocation | fl #Show deserialized commands
function AdminTasks($message){
write-host "hello world: $message"
}
}
Start-Process powershell -ArgumentList '-noexit','-nologo','-noprofile','-NonInteractive','-Command',$scriptblock,"AdminTasks('$var')" -Verb runAs #-WindowStyle Hidden
#Output:
MyCommand :
$MyInvocation | fl #Show deserialized commands
function AdminTasks($message){
write-host hello world: $message
}
AdminTasks('this is a test')
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 0
OffsetInLine : 0
HistoryId : 1
ScriptName :
Line :
PositionMessage :
PSScriptRoot :
PSCommandPath :
InvocationName :
PipelineLength : 2
PipelinePosition : 1
ExpectingInput : False
CommandOrigin : Runspace
DisplayScriptPosition :
hello world: this is a test
Wrapping it in a script block and using $args[0] or $args[1] also works, just be aware that you many need to wrap the $var0 or $var1 in quotes if there are issues when it is deserialized and use `$ to prevent the $sb from being replaced with "" since that variable doesn't exist in the caller's scope:
$var0 = "hello"
$var1 = "world"
$scriptblock = {
$MyInvocation | fl #Show deserialized commands
$sb = {
write-host $args[0] $args[1]
}
}
Start-Process powershell -ArgumentList '-noexit','-nologo','-noprofile','-NonInteractive','-Command',$scriptblock,"& `$sb $var0 $var1"
If you want to pass objects that are serializable, but are not strings, I wrote up a solution: Is there a way to pass serializable objects to a PowerShell script with start-process?