Passing in $args variable to function - powershell

I have a script tester.ps1; the first thing it does is call a function (defined within the script itself) called main.
I need to pass in the automatic variable $args that was passed into it from the commandline.
How do I do this?
The following doesn't seem to work:
#Requires -Version 5.0
#scriptname: tester.ps1
function main($args) {
Write-Host $args
}
# Entry point
main $args
When I save this tester.ps1 and call it, the function doesn't see the passed-in parameter?
PS> . .\tester.ps1 hello world
From entry point: hello world
From Function:

In your example, simply removing $args from the main function declaration, would be enough to have the output you want.
Though, be aware that if you want to pass parameters by name, you need to call main with the splatting operator #, for example:
#Requires -Version 5.0
#scriptname: tester.ps1
function main($myString, $otherVar) {
Write-Host $myString
}
# Entry point
Write-Host "Not using splatting: " -NoNewline
main $args
Write-Host "Using splatting: " -NoNewline
main #args
Output:
PS> . .\test.ps1 -myString "Hi World" -otherVar foobar
Not using splatting: -myString Hi World -otherVar foobar
Using splatting: Hi World
Find more about the splatting operator # here

Based on Jeroen Mostert's comment*; the solution is below.
Basically I was incorrectly trying to 'overload' or 'shadow' the built-in $arg variable.
I just need to have a param with a different name like this:
#Requires -Version 5.0
function main($my_args) {
write-host "From Function:" $my_args
}
# Entry point
write-host "From entry point:" $args
main $args
> . .\tester.ps1 hello world
From entry point: hello world
From Function: hello world

Related

Send result from called powershell script to a calling powershell script

I'm trying to send just the result I want from a called PowerShell script back to the calling script.
So the script testcaller.ps1
$Result = Invoke-Expression "$PSScriptRoot\testcalled"
$LogMessage = "TestCalled ended with Result $Result"
Write-Output $LogMessage
Calls the script testcalled.ps1
$TestMessage = "this is a test"
Write-Output $TestMessage
$Level = 1
exit $Level
When run it produces this...
TestCalled ended with Result this is a test
0
I have two problems. I get the testmessage passed back to my calling script and the level past back is 0 when it should be 1. What I want to see is...
TestCalled ended with Result 1
Given testcalled.ps1
'hello world'
function Say-Hello { 'hello!' }
exit 1
If you want to run this external script from another script you can either dot source it:
The functions, variables, aliases, and drives
that the script creates are created in the scope in which you are
working. After the script runs, you can use the created items and
access their values in your session.
$Result = . "$PSScriptRoot\testcalled.ps1"
$Result # => hello world
Say-Hello # => 'hello!'
$LASTEXITCODE # => 1
Or use the call operator &:
The call operator executes in a child scope.
Meaning that, functions, variables, aliases, and drives will not be available on the current scope.
$Result = & "$PSScriptRoot\testcalled.ps1"
$Result # => hello world
Say-Hello # => SayHello: The term 'SayHello' is not recognized as a name of a cmdlet, function....
$LASTEXITCODE # => 1
As you can see, on both cases, you can use the automatic variable $LASTEXITCODE.
Last, but not least, it is not recommended to use Invoke-Expression. See https://devblogs.microsoft.com/powershell/invoke-expression-considered-harmful/

Correct parameters values not passed on to the function

I have a file called TestFunc.ps1. Its contents are as follows
Function TestFunc([string]$param1, [string]$param2)
{
Write-Host "------------------"
Write-Host $param1
Write-Host $param2
Write-Host "------------------"
}
TestFunc $param1 $param2
I called it as follows
C:\Test\TestFunc.ps1 "Hello" "World"
The output is as follows
------------------
------------------
I expect the output as
------------------
Hello
World
------------------
What am I doing wrong here?
The parameters are defined for the scope of the function, and not the script.
What you want is a Param section:
param ([string]$param1, [string]$param2)
function TestFunc([string]$param1, [string]$param2) {
Write-Host "------------------"
Write-Host $param1
Write-Host $param2
Write-Host "------------------"
}
TestFunc $param1 $param2
Of course, having duplicate variable names is misleading, but I it's just a test function. In your case, you wouldn't even need a function at all:
param ([string]$param1, [string]$param2)
Write-Host "------------------"
Write-Host $param1
Write-Host $param2
Write-Host "------------------"
Or alternatively:
param ([string]$param1, [string]$param2)
function TestFunc {
Write-Host "------------------"
Write-Host $param1
Write-Host $param2
Write-Host "------------------"
}
TestFunc
Or use the $args automatic variable, without defining any parameters at all:
function TestFunc {
Write-Host "------------------"
Write-Host $args[0]
Write-Host $args[1]
Write-Host "------------------"
}
TestFunc foo bar
To complement marsze's helpful and effective answer:
PowerShell has two largely equivalent syntax forms for defining parameters - leaving PSv5+ class definitions aside[1]:
Note: For brevity, the parameter lists are placed on a single line below; however, both syntax forms allow placing individual parameters on their own line.
For functions only:
C/C#-like: a ,-separated list of parameter-variable declarations inside (...) after the function name and before the opening {; e.g.:
function foo ($bar, $baz) {
# ...
}
For scripts and functions too, as well as script blocks ({ ... }, which are like anonymous functions):
PowerShell-specific: A ,-separated list of parameter-variable declarations inside param(...), which must be the first statement inside the body (apart from comments and using directives):
# --- Script foo.ps1
param($bar, $baz)
# ...
# --- Function
# This example is fully equivalent to `foo ($bar, $baz) { ...` above.
# Note that no () is needed after the function name.
function foo {
param($bar, $baz)
# ...
}
# --- Script block
& {
param($bar, $baz)
# ...
} # arguments...
For brevity, the following optional elements were omitted above:
On individual parameter declarations:
Typing; e.g., to declare parameter $foo as type [int] (System.Int32):
[int] $foo
Parameter attributes, typically, but not exclusively via the [Parameter()] attribute; among other things, the latter determines whether the associated parameter is mandatory; e.g.:
[Parameter(Mandatory=$true)] [int] $foo
Above the param(...) statement only:
The [CmdletBinding()] attribute, which makes a function or script an advanced one, with behaviors on par with (compiled) PowerShell cmdlets - see about_Functions_Advanced
In simple (non-advanced) scripts and functions it is also an option not to declare any parameters at all, in which any arguments passed are contained in the automatic $args variable, which is a regular PowerShell array ([object[]]).
You can even combine $args with declared parameters: $args then contains only those arguments that didn't bind to declared ones.
By contrast, in advanced scripts and functions, you are fundamentally only permitted to pass arguments that bind to declared parameters.
When to choose which syntax form:
Script files and script blocks must use a param(...) statement - the C#-like syntax isn't available.
Functions can technically use the C#-like syntax and the param(...) interchangeably, except if the [CmdletBinding()] attribute is needed, in which case only the param(...) syntax works.
That said, for consistency and easier extensibility (making a function an advanced one later), the param(...) syntax is generally preferable.
Also, using the C#-like syntax frequently can more easily lead to syntax confusion when invoking a function, given that PowerShell cmdlets and functions are invoked like shell commands (no parentheses, whitespace-separated arguments), not like C# methods; e.g., foo 1 2 (or foo -bar 1 -baz 2) rather than foo(1, 2)
[1] The method declarations in class definitions must use C#-like syntax, and no parameter attributes are supported (they're only supported on properties). Just like methods on native .NET types, class methods must also be called with method syntax - see this answer and help topic about_Classes.

How to resolve variables in a Powershell script block

Given I have:
$a = "world"
$b = { write-host "hello $a" }
How do I get the resolved text of the script block, which should be the entre string including write-host:
write-host "hello world"
UPDATE: additional clarifications
If you just print $b you get the variable and not the resolved value
write-host "hello $a"
If you execute the script block with & $b you get the printed value, not the contents of the script block:
hello world
This question is seeking a string containing the contents of the script block with the evaluated variables, which is:
write-host "hello world"
As in the original question, if your entire scriptblock contents is not a string (but you want it to be) and you need variable substitution within the scriptblock, you can use the following:
$ExecutionContext.InvokeCommand.ExpandString($b)
Calling .InvokeCommand.ExpandString($b) on the current execution context will use the variables in the current scope for substitution.
The following is one way to create a scripblock and retrieve its contents:
$a = "world"
$b = [ScriptBlock]::create("write-host hello $a")
$b
write-host hello world
You can use your scriptblock notation {} as well to accomplish the same thing, but you need to use the & call operator:
$a = "world"
$b = {"write-host hello $a"}
& $b
write-host hello world
A feature to using the method above is that if you change the value of $a at any time and then call the scriptblock again, the output will be updated like so:
$a = "world"
$b = {"write-host hello $a"}
& $b
write-host hello world
$a = "hi"
& $b
write-host hello hi
The GetNewClosure() method can be used to create a clone of the scriptblock above to take a theoretical snapshot of the scriptblock's current evaluation. It will be immune to the changing of the $a value later the code:
$b = {"write-host hello $a"}.GetNewClosure()
& $b
write-host hello world
$a = "new world"
& $b
write-host hello world
The {} notation denotes a scriptblock object as you probably already know. That can be passed into Invoke-Command, which opens up other options. You can also create parameters inside of the scriptblock that can be passed in later. See about_Script_Blocks for more information.

Strange order of variables written from Write-Host within function

With my following code seems Write-Host puts out variables in a strange way (for me coming from C# at least).
Code is here
function Run(
[string] $command,
[string] $args
)
{
Write-Host 'from function - command is:' $command '.args is: ' $args
}
$cmd = "ping"
$args = "208.67.222.222"
Write-Host 'from main - command is:' $cmd '.args is: ' $args
Run("ping","208.67.222.222")
Output is here
from main - command is: ping .args is: 208.67.222.222
from function - command is: ping 208.67.222.222 .args is:
How come Write-Host from main works as I expect, but within the function it outputs all variables at the same time? How can I correct this behaviour?
$args in the function is an automatic variable. It is an array that contains all the arguments passed to the function.
Use something besides $args for your IP address variable.

Passing around command line $args in powershell , from function to function

This is a nasty issue I am facing. Wont be surprised if it has a simple solution, just that its eluding me.
I have 2 batch files which I have to convert to powershell scripts.
file1.bat
---------
echo %1
echo %2
echo %3
file2.bat %*
file2.bat
--------
echo %1
echo %2
echo %3
On command line, I invoke this as
C:> file1.bat one two three
The output I see is as expected
one
two
three
one
two
three
(This is a crude code sample)
When I convert to Powershell, I have
file1.ps1
---------
Write-Host "args[0] " $args[0]
Write-Host "args[1] " $args[1]
Write-Host "args[2] " $args[2]
. ./file2.ps1 $args
file2.ps1
---------
Write-Host "args[0] " $args[0]
Write-Host "args[1] " $args[1]
Write-Host "args[2] " $args[2]
When I invoke this on powershell command line, I get
$> & file1.ps1 one two three
args[0] one
args[1] two
args[2] three
args[0] one two three
args[1]
args[2]
I understand this is because $args used in file1.ps is a System.Object[] instead of 3 strings.
I need a way to pass the $args received by file1.ps1 to file2.ps1, much the same way that is achieved by %* in .bat files.
I am afraid, the existing manner will break even if its a cross-function call, just the way its a cross-file call in my example.
Have tried a few combinations, but nothing works.
Kindly help. Would much appreciate it.
In PowerShell V2, it's trivial with splatting. bar just becomes:
function bar { foo #args }
Splatting will treat the array members as individual arguments instead of passing it as a single array argument.
In PowerShell V1 it is complicated, there's a way to do it for positional arguments. Given a function foo:
function foo { write-host args0 $args[0] args1 $args[1] args2 $args[2] }
Now call it from bar using the Invoke() method on the scriptblock of the foo function
function bar { $OFS=','; "bar args: $args"; $function:foo.Invoke($args) }
Which looks like
PS (STA) (16) > bar 1 2 3
bar args: 1,2,3
args0 1 args1 2 args2 3
when you use it.
# use the pipe, Luke!
file1.ps1
---------
$args | write-host
$args | .\file2.ps1
file2.ps1
---------
process { write-host $_ }