Expand a function in a here-string in powershell - powershell

I need to use the result of a function in some output, and i would like to streamline the script by expanding the function into the here-strings of powershell
# instead of
$result = myfunction
"The result is $result"
# syntax that would let me call the function inside the string
"the result is ?myfunction()"
I don't see any documentation regarding this behavior but i'd really appreciate having missed it rather than it not existing.
If it is not doable what alternatives do i have?
"the result is also " myfunction
...?

Use a subexpression for that:
"the result is $(myfunction)"
Other options are concatenating string and function output:
"the result is " + (myfunction)
or using the format operator:
"the result is {0}" -f (myfunction)

Related

Passing $args[x] in an if statement in .ps1 (PowerShell) does not seem to work when it does not exist

Suppose I have the following testArgsX.ps1 file:
$ARG_X_PARAMETER=1
If ($args[0] -ne "") {
echo ARGS[X]="`"$args[0]`""
$ARG_X_PARAMETER=$args[0]
}
echo ARG_X_PARAMETER=$ARG_X_PARAMETER
When I execute it either in a command prompt (C:\Test> powershell .\testArgsX.ps1) or in PowerShell prompt (PS C:\Test> .\testArgsX.ps1), it outputs the following:
ARGS[X]="[0]"
ARG_X_PARAMETER=
It seems that $args[0], in the if-condition, is not interpreted as a scalar value. Although I understand there are 2 ways to circumvent this problem (shown below). I want to know why it does not interpret it as a scalar value and if there is a way to fix it so that it does.
Take out the entire if statement (If ( ... ) { ... }) and replace it with Param ( [Int]$RUN_JUST_ONCE = 1 )
Change the if condition from If ($args[0] -ne "") { to If ($args.Length -gt 0) {
$args[0] is a scalar, but accessing a non-existent array element in PowerShell returns $null[1], and $null is indeed not equal to the empty string ("" or '').
Therefore, you should test for $null.
Note that testing for $null is best performed by placing $null on the LHS ($null -ne $args[0]), because on the RHS it would act as a filter instead of returning a Boolean in case the other operand happens to be an array.
Also, note that in order to use an expression such as $args[0] inside "...", an expandable (interpolating) string, you must use $(), the subexpression operator; without it, $args as a whole is expanded, and [0] is used verbatim - see this answer
echo is a built-in alias for the Write-Output cmdlet, whose explicit use is rarely needed; simply using a command or expression whose output is neither captured nor redirected is implicitly output.
To put it all together:
$ARG_X_PARAMETER=1
If ($null -ne $args[0]) {
"ARGS[X]=`"$($args[0])`""
$ARG_X_PARAMETER=$args[0]
}
"ARG_X_PARAMETER=$ARG_X_PARAMETER"
Alternatively, declare parameters for your script, which also makes it easy to assign default values:
param(
# This implicitly becomes the 1st positional parameter.
$ARG_X_PARAMETER = 1
)
"ARG_X_PARAMETER=$ARG_X_PARAMETER"
By default, parameters are positional, which means they can be used unnamed (e.g. testArgsX.ps1 42), though using the name explicitly is always an option (e.g.
testArgsX.ps1 -ARG_X_PARAMETER 42) - though a better name is the probably called for.
PowerShell optionally allows you to assign a data type to parameters and use a variety of attributes, notably ones for argument validation.
See about_Functions and about_Functions_Advanced_Parameters.
[1] With Set-StrictMode -Version 3 or higher in effect, accessing a non-existent element causes a statement-terminating error instead.

Explain Bizarre Function Call Processing/Results

Can someone please explain how/why this code:
function DoIt($one, $two)
{
"`$one is $one"
"`$two is $two"
return $one * $two
}
$sum = DoIt 5 4
$sum
works exactly as expected/intended - i.e., it produces the following output:
$one is 5
$two is 4
20
However, this (seemingly almost identical) code:
function DoIt($one, $two)
{
"`$one is $one"
"`$two is $two"
return $one * $two
}
$sum = DoIt 5 4
"`$sum is $sum"
bends my brain and breaks my understanding of reality by producing the following output:
$sum is $one is 5 $two is 4 20
The reason for the odd behavior is that you are polluting the output stream. PowerShell doesn't explicitly have a pure "return" value like a compiled program, and relies on streams to pass data between functions. See about_Return for more information, as well as an example that illustrates this very behavior.
Basically the $sum variable gets all the output of the function, and as #TheMadTechnician says, it outputs differently depending on how it is being used.
The "correct" way in PowerShell to return values is in general not to use return but to use Write-Output to explicitly define which stream you want to output to. The second thing is that you have to use Write-Host when writing messages to the Host console, otherwise it gets returned on the Output stream as well.
function DoIt($one, $two)
{
Write-Host "`$one is $one"
Write-Host "`$two is $two"
Write-Output ($one * $two)
}
$sum = DoIt 5 4
"`$sum is $sum"
$one is 5
$two is 4
$sum is 20
There's good information in the existing answers, but let me try to bring it all together:
function DoIt($one, $two)
{
"`$one is $one"
"`$two is $two"
return $one * $two
}
produces three outputs ("return values") to PowerShell's success [output] stream (see about_Redirection), which are the evaluation results of:
expandable string "`$one is $one"
expandable string "`$two is $two"
expression $one * $two
The expandable strings are implicitly output - due to producing a result that is neither captured, sent to another command, nor redirected .
Similarly, the return in return $one * $two is just syntactic sugar for:
$one * $two # implicit output
return # control flow: exit the scope
Note:
return is unnecessary here and never required.
While Write-Output could be used in lieu of implicit output:
it is only ever helpful if you want output a collection as a single object, via its -NoEnumerate switch.
it is otherwise not only needlessly verbose, it slows things down.
If you want to print status information from a function without polluting the output stream, you have several choices:
Write-Host prints to the host's display (the console, if you're running in a console window); in PSv4-, such output could neither be captured nor suppressed; in PSv5+, Write-Host now writes to the information stream (number 6), which means that 6> can be used to redirect/suppress the output.
Write-Verbose is for opt-in verbose output, which you can activate by setting preference variable $VerbosePreference to 'Continue' or by using the -Verbose switch.
Write-Debug is for opt-in debugging output via $DebugPreference or -Debug, though note that in the latter case a prompt is displayed whenever a Write-Debug statement is encountered.
Because these outputs are captured in a variable - $sum = DoIt 5 4 - and there is more than 1 output, they are implicitly collected in an array (of type [object[]]).
Using implicit output to print this variable's value to the display - $sum - enumerates the array elements, which prints them each on their own line:
$one is 5
$two is 4
20
By contrast, using implicit output with expandable string "`$sum is $sum" results in a single output string in which the reference to variable $sum, which contains an array, is expanded as follows:
each element of the array is stringified
loosely speaking, this is like calling .ToString() on each element, but it's important to note that PowerShell opts for a culture-invariant string representation, if available - see this answer.
the results are joined with the value of (the rarely used automatic variable) $OFS as the separator to form a single string; $OFS defaults to a single space.
Thus, if you join array elements $one is 5, $two is 4, and 20 with a single space between them, you get:
$one is 5 $two is 4 20
To put it differently: the above is the equivalent of executing '$one is 5', '$two is 4', '20' -join ' '
This is due to how you are outputting an array, and has nothing to do with the function. You can replicate the same behavior with the following:
$sum='$one is 5','$two is 4',20
"`$sum is $sum"
Putting the variable in double quotes performs string expansion, which performs the .ToString() method on each item in the array, and then joins them with a space to convert the array into a string.
After playing around more (using the info I learned from #HAL9256), I discovered the problem isn't actually with using return vs Write-Output but rather with just how command-less strings are handled in functions. For example, this too works perfectly:
function DoIt($one, $two)
{
Write-Host "`$one is $one"
Write-Host "`$two is $two"
return ($one * $two)
}
$sum = DoIt 5 4
Write-Host "`$sum is $sum"
that is, it produces the following expected output:
$one is 5
$two is 4
$sum is 20
(And, apparently, the mathematical computation without explicit parentheses works just fine within a return command - unlike within a Write-Output command.)
In other words, I guess a static string in a function is treated as if you're building an array to be returned (as #TheMadTechnician was referring) as opposed to shorthand for a Write-Host command. Live and learn - "explicit is better than implicit". :-)
Granted, I still don't understand why the final output wasn't exactly the same (right or wrong) between the two code blocks in my original question ... but, hey, my brain is tired enough for one day. :-P
thanks again guys!!!

Powershell String Length Validation

I created a really simple HelloWorld.ps1 Power-shell script which accepts a Name parameter, validates its length and then prints a hello message, for example if you pass John as Name, it's supposed to print Hello John!.
Here is the Power-shell script:
param (
[parameter(Mandatory=$true)]
[string]
$Name
)
# Length Validation
if ($Name.Length > 10) {
Write-Host "Parameter should have at most 10 characters."
Break
}
Write-Host "Hello $Name!"
And here is the command to execute it:
.\HelloWorld.ps1 -Name "John"
The strange behavior is every time that I execute it:
It doesn't perform validation, so it accepts Name parameters longer than 10 characters.
Every time I execute it, it creates and updates a file named 10 without any extension.
What's the problem with my script and How can I validate string length in PowerShell?
The problem - Using wrong operator
Using wrong operators is a common mistake in PowerShell. In fact > is output redirection operator and it sends output of the left operand to the specified file in the right operand.
For example $Name.Length > 10 will output length of Name in a file named 10.
How can I validate string length?
You can use -gt which is greater than operator this way:
if($Name.Length -gt 10)
Using ValidateLength attribute for String Length Validation
You can use [ValidateLength(int minLength, int maxlength)] attribute this way:
param (
[ValidateLength(1,10)]
[parameter(Mandatory=$true)]
[string]
$Name
)
Write-Host "Hello $Name!"

Using string replace to insert the string '$_' in powershell?

I am having trouble with string replacement in powershell. Please consider the following expression:
"replaceTarget: %rep" -replace '%rep','$_'
I expect the result of this expression to be:
replaceTarget: $_
but instead, it is
replaceTarget: replaceTarget: $rep
I assume it is because the replacement string '$_' has some other meaning in the replace function.
How can I escape the input string so that $_ can be passed in without being evaluated?
try with this:
'replaceTarget: %rep' -replace '%rep','$$_'
you coud use the .net replace function like this :
'replaceTarget: $rep'.replace("`$rep",'$_')

Populating an array using inline variables

I generally use the following format when I populate a script with variables:
$string = ("Hi my name is {0} and I live in {1}, {2}." -f $username,$usercity,$userstate)
However this doesn't seem to work with an array, or I might be getting the syntax completely wrong:
$Arguments = #("/Settings:{0}", "/Tests:{1}", "/output:{2}" -f $TestSetting,$TestList,$Output)
When I output the results of that association, it all comes out as one string (with the substitution correct). If I look at the Count, it is 1. What am I missing?
Each value in the array needs to be it's own expression with it's own operator:
$Arguments = #(("/Settings:{0}" -f $TestSetting), ("/Tests:{0}" -f $TestList), ("/output:{0}" -f $Output))