Today (2017-05-29) I am using PowerShell 5.0.10586.117 on Windows 7 Enterprise and run the following (shortened):
$dateOfLicense = "2017-04-20"
$dateOfToday = '{0:yyyy-MM-dd}' -f (Get-Date)
$TimeDifference = [DateTime]$dateOfToday - [DateTime]$dateOfLicense
if (($TimeDifference) = 14)
{
Write-Host "test"
}
Even the difference between both days is 39, my code jumps in the if-clause and sends "test" to screen.
What am I doing wrong here?
You are assigning 14 to $TimeDifference. Instead you wan't to compare the Days property using -le:
if ($TimeDifference.Days -le 14)
{
Write-Host "test"
}
To complement Martin Brandl's helpful answer:
Like many other languages - but unlike VBScript, for instance - PowerShell uses distinct symbols for:
the assignment operator (=)
vs. the equality test operator (-eq).
This distinction enables using assignments as expressions, which is what you inadvertently did:
if ($TimeDifference = 14) ... # same as: if (($TimeDifference) = 14) ...
assigns 14 to variable $TimeDifference, as Martin explains, and, because the assignment is (of necessity, to serve as a conditional for if) enclosed in (...), returns the assigned value (the inner (...) around $TimeDifference make no difference here, however) and that value is used as the Boolean conditional for if.
That is, the (...) expression evaluated by if has value 14 - a nonzero number - and is therefore interpreted as $true in this Boolean context, irrespective of the original value of $TimeDifference.
Note:
To learn more about PowerShell's operators, run Get-Help about_Operators
To learn about how PowerShell interprets arbitrary values as Booleans in conditionals (to-Boolean coercion), see the bottom section of this answer.
To test variables or expressions that already are Booleans, just use them as-is or, if necessary, negate them with -not (!); e.g.:
if ($someBoolean) { # Better than: if ($someBoolean -eq $true)
if (-not $someBoolean) { # Better than: if ($someBoolean -eq $false)
Finally, here's a streamlined version of your code that doesn't require intermediate variables, uses a cast to convert the string to a [datetime] instance and uses [datetime]::now, the more efficient equivalent of Get-Date (though that will rarely matter).
if (([datetime]::now - [datetime] '2017-04-20').Days -eq 14) {
"test"
}
Note how "test" as a statement by itself implicitly sends output to PowerShell's (success) output stream, which prints to the console by default.
Write-Host bypasses this stream and should generally be avoided.
Not better solution of Martin, just an shorty code
$dateOfLicense = [DateTime]"2017-04-20"
$TimeDifferenceDays = ((Get-Date) - $dateOfLicense).Days
if ($TimeDifferenceDays -lt 14)
{
Write-Host "test"
}
Related
function Main {
$result1 = DoWork1
$result1.GetType()
$result2 = DoWork2
$result2.GetType()
}
function DoWork1 {
$result1 = Invoke-Sqlcmd -Query "select top 1 * from customer" -ServerInstance "(localdb)\MSSQLLocalDB" -Database "Database1" -OutputAs DataTables
#assign to variable then return
return $result1
}
function DoWork2 {
#return results without assigning to variable
return Invoke-Sqlcmd -Query "select top 1 * from customer" -ServerInstance "(localdb)\MSSQLLocalDB" -Database "Database1" -OutputAs DataTables
}
Main
Here is the unexpected output:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False DataRow System.Object
True True DataTable System.ComponentModel.MarshalByValueComponent
Using a similar example from the previous Q&A, to reproduce the same behavior:
function Invoke-SqlExample {
$dtt = [System.Data.DataTable]::new()
[void] $dtt.Columns.Add('Name')
$row = $dtt.NewRow()
$row.Name = "Hello"
$dtt.Rows.Add($row)
, $dtt
}
function Main {
$result1 = DoWork1
$result1.GetType()
$result2 = DoWork2
$result2.GetType()
}
function DoWork1 {
$result1 = Invoke-SqlExample
[pscustomobject]#{
Function = $MyInvocation.MyCommand.Name
Type = $result1.GetType().Name
} | Out-Host
return $result1
# Immediate fixes:
# return , $result1
# Write-Output $result1 -NoEnumerate
# $PSCmdlet.WriteObject($result1, $false) !! Only if Advanced Function
}
function DoWork2 {
return Invoke-SqlExample
}
Main
The output you would get from this is:
Function Type
-------- ----
DoWork1 DataTable
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False DataRow System.Object
True True DataTable System.ComponentModel.MarshalByValueComponent
We can see that the unrolling of the DataTable is only done when previously assigned to a variable, even though the variable ($result1 in DoWork1 is still of the type DataTable).
This could be explained as, DoWork2 happens in a single pipeline as opposed to DoWork1 which happens in two pipelines, first the output from Invoke-SqlExample is collected in a variable, and then emitted as output (this is where the unrolling is triggered). This is based on assumptions, and may not be entirely correct.
As iRon suggested in his helpful comment from the prior answer, an immediate fix to have DoWork1 return the DataTable instance untouched (unrolled), we can use the comma operator , which will wrap the DataTable instance in an array which is then lost during enumeration (output from the function) since it is an array of one element. The other alternative would be using Write-Output -NeEnumerate. As last alternative we can also use $PSCmdlet.WriteObject(..., $false) only if the function is an advanced one.
Adding a similar example that demonstrates the same behavior, this one was provided by mclayton in his helpful comment:
function test {
, [Collections.Generic.List[string]]#(
'hello'
'world'
)
}
(& { test }).GetType() # => List`1
(& { $a = test; $a }).GetType() # => Object[]
To complement Santiago Squarzon's helpful answer and mclayton's helpful comments:
Note:
The use of return is incidental to the behavior, given that return ... is merely syntactic sugar for ...; return, i.e. outputting ..., followed by exiting the scope; at the end of a script or function, return is never required, as the function / script is exited anyway. Any statement in a script or function can produce output, due to PowerShell's implicit output behavior - see this answer.
The behavioral difference comes down this:
Invoke-Sqlcmd -Query ... is a command, so whatever it outputs is output as-is to the success output stream of the pipeline in which the enclosing function runs.
A command in PowerShell is a unit of functionality you invoke by name, which comprises cmdlets, scripts, functions, external programs, and script blocks.
Commands invariably involve a pipeline whose success output stream they directly write to - whether or not multiple commands are connected with |.
By contrast, $result1 is an expression, and if an expression evaluates to a value of a .NET type that PowerShell considers an enumerable,[1] it is enumerated when it is sent to the success output stream (of a pipeline), which in the case of a a collection means sending its elements one by one; note that the original collection type is therefore lost in the process.
As for the rationale for performing such enumeration: It is in line with the fundamental nature of PowerShell pipelines as streams of individual objects of indeterminate length - see this answer.
An expression in PowerShell is a construct that involves any combination of variable references, literals, most operators, and .NET method calls. In an of itself, an expression does not involve a pipeline, and collections and enumerables are treated as themselves. (An expression may have nested pipelines, however, if commands participate in it.)
A pipeline - and therefore enumeration - does get involved when an expression is used in the following contexts:
When you enclose the expression in $(...), the subexpression operator or #(...), the array-subexpression operator; e.g. $(1+2) or #(1+2)
Note: (...), the grouping operator does not do that when applied to an expression: in the context of a larger expression, it merely clarifies precedence. However, when applied to a command instead (only one supported, e.g., (Get-ChildItem).Count), it runs the command to completion in a nested pipeline, collects its output object(s) and either returns them as-is, if there's only one, otherwise as an [object[]] array.
When you pipe the expression to a command, using |, the pipeline operator; e.g. 1..3 | ForEach-Object { 1 + $_ }. Note that expressions are only allowed as the first segment of pipeline.
When you output the expression - whether implicitly or explicitly (with the rarely necessary Write-Output), whether in combination with return or not - from inside a command, such as in the case of outputting $result1 from your DoWork1 function.
The upshot:
If you store a command's output in a variable ($result1 = Invoke-Sqlcmd -Query ...), and later output that variable's value to the success output stream (return $result1 or just $result1 or Write-Output $result1), you potentially introduce an additional layer of enumeration.
To prevent enumeration, you can use the techniques mentioned in Santiago's answer (transitory single-element array wrapper trick constructed with unary , - , $return1 - or Write-Output's -NoEnumerate switch - Write-Output -NoEnumerate $return1)
However, this only matters for commands that output collections as a whole, as a single object, instead of streaming collections, i.e enumerating them and outputting their elements, one by one, which is the typical case.
Commands that output collections as single objects are rare, not least because the general expectation of PowerShell commands is that they indeed stream their output.
Invoke-SqlCmd -OutputAs DataTables is such a command, only because PowerShell - perhaps surprisingly - considers System.Data.DataTable instances enumerable - it conceives of it as a collection of rows, which it enumerates in the pipeline by default.[1]
[1] See the bottom section of this answer for an overview of what types PowerShell considers enumerable. In short: PowerShell has several exceptions where it considers types that declare themselves to be enumerable not enumerable, and one opposite exception: System.Data.DataTable does not declare itself enumerable, yet PowerShell enumerates it, namely as the collection of its rows stored in the .Rows property; Santiago provided the relevant source-code link.
I'm using a format operator inside a script with a following example:
$current = 1
$total = 1250
$userCN = "contoso.com/CONTOSO/Users/Adam Smith"
"{0, -35}: {1}" -f "SUCCESS: Updated user $current/$total", $userCN
This excpectedly shows the following output:
SUCCESS: Updated user 1/1250 : contoso.com/CONTOSO/Users/Adam Smith
The format operator is there to keep the targeted output text in place with current / total running numbers varying between 1-99999. Without the format operator I could highlight the success line like this:
Write-Host -BackgroundColor Black -ForegroundColor Green "SUCCESS: Updated user $current/$total: $userCN"
But the question is how could I use the highlight-colors combined with the format operator? There's only the -f parameter and it doesn't allow the color parameters because, well, it's not the same thing as Write-Host in the first place.
Unlike other shells, PowerShell allows you to pass commands and expressions as command arguments simply by enclosing them in parentheses, i.e by using (...), the grouping operator.
When calling PowerShell commands (cmdlets, scripts, functions), the output is passed as-is as an argument, as its original output type.
Therefore, Theo's solution from a comment is correct:
Write-Host -BackgroundColor Black -ForegroundColor Green `
("{0, -35}: {1}" -f "SUCCESS: Updated user $current/$total", $userCN)
That is, the -f expression inside (...) is executed and its output - a single string in this case - is passed as a positional argument to Write-Host (implicitly binds to the -Object parameter).
Note that you do not need, $(...), the subexpression operator, in this case:
(...) is sufficient to enclose an expression or command.
In fact, in certain cases $(...) can inadvertently modify your argument, because it acts like the pipeline; that is, it enumerates and rebuilds array expressions as regular PowerShell arrays (potentially losing strong typing) and unwraps single-element arrays:
# Pass a single-element array to a script block (which acts like a function).
# (...) preserves the array as-is.
PS> & { param($array) $array.GetType().Name } -array ([array] 1)
Object[] # OK - single-element array was passed as-is
# $(...) unwraps it.
PS> & { param($array) $array.GetType().Name } -array $([array] 1)
Int32 # !! Single-element array was unwrapped.
# Strongly-typed array example:
PS> & { param($array) $array.GetType().Name } ([int[]] (1..10))
Int32[] # OK - strongly typed array was passed as-is.
# Strongly-typed array example:
PS> & { param($array) $array.GetType().Name } $([int[]] (1..10))
Object[] # !! Array was *enumerated and *rebuilt* as regular PowerShell array.
The primary use of $(...) is:
expanding the output from expressions or commands inside expandable strings (string interpolation)
To send the output from compound statements such as foreach (...) { ... } and if (...) { ... } or multiple statements directly through the pipeline, after collecting the output up front (which (...) does as well); however, you can alternatively wrap such statements & { ... } (or . { ... } in order to execute directly in the caller's scope rather than a child scope) in order to get the usual streaming behavior (one-by-one passing of output) in the pipeline.
Taking a step back: Given that you already can use compound statements as expressions in variable assignments - e.g., $evenNums = foreach ($num in 1..3) { $num * 2 } - and expressions generally are accepted as the first segment of a pipeline - e.g., 'hi' | Write-Host -Fore Yellow - it is surprising that that currently doesn't work with compound statements; this GitHub issue asks if this limitation can be lifted.
In the context of passing arguments to commands:
Use $(...), the subexpression operator only if you want to pass the output from multiple commands or (one or more) compound statements as an argument and/or, if the output happens to be a single object, you want that object to be used as-is or, if it happens to be a single-element array (enumerable), you want it to be unwrapped (pass the element itself, not the array.
Of course, if you're constructing a string argument, $(...) can be useful inside that string, for string interpolation - e.g., Write-Host "1 + 1 = $(1 + 1)"
Use #(...), the array subexpression operator only if you want to pass the output from multiple commands as an argument and/or you want to ensure that the output becomes a array; that is, the output is returned as a (regular PowerShell) array, of type [object[]], even if it happens to comprise just one object. In a manner of speaking it is the inverse of $(...)'s behavior in the single-object output case: it ensures that single-object output too becomes an array.
Today (2017-05-29) I am using PowerShell 5.0.10586.117 on Windows 7 Enterprise and run the following (shortened):
$dateOfLicense = "2017-04-20"
$dateOfToday = '{0:yyyy-MM-dd}' -f (Get-Date)
$TimeDifference = [DateTime]$dateOfToday - [DateTime]$dateOfLicense
if (($TimeDifference) = 14)
{
Write-Host "test"
}
Even the difference between both days is 39, my code jumps in the if-clause and sends "test" to screen.
What am I doing wrong here?
You are assigning 14 to $TimeDifference. Instead you wan't to compare the Days property using -le:
if ($TimeDifference.Days -le 14)
{
Write-Host "test"
}
To complement Martin Brandl's helpful answer:
Like many other languages - but unlike VBScript, for instance - PowerShell uses distinct symbols for:
the assignment operator (=)
vs. the equality test operator (-eq).
This distinction enables using assignments as expressions, which is what you inadvertently did:
if ($TimeDifference = 14) ... # same as: if (($TimeDifference) = 14) ...
assigns 14 to variable $TimeDifference, as Martin explains, and, because the assignment is (of necessity, to serve as a conditional for if) enclosed in (...), returns the assigned value (the inner (...) around $TimeDifference make no difference here, however) and that value is used as the Boolean conditional for if.
That is, the (...) expression evaluated by if has value 14 - a nonzero number - and is therefore interpreted as $true in this Boolean context, irrespective of the original value of $TimeDifference.
Note:
To learn more about PowerShell's operators, run Get-Help about_Operators
To learn about how PowerShell interprets arbitrary values as Booleans in conditionals (to-Boolean coercion), see the bottom section of this answer.
To test variables or expressions that already are Booleans, just use them as-is or, if necessary, negate them with -not (!); e.g.:
if ($someBoolean) { # Better than: if ($someBoolean -eq $true)
if (-not $someBoolean) { # Better than: if ($someBoolean -eq $false)
Finally, here's a streamlined version of your code that doesn't require intermediate variables, uses a cast to convert the string to a [datetime] instance and uses [datetime]::now, the more efficient equivalent of Get-Date (though that will rarely matter).
if (([datetime]::now - [datetime] '2017-04-20').Days -eq 14) {
"test"
}
Note how "test" as a statement by itself implicitly sends output to PowerShell's (success) output stream, which prints to the console by default.
Write-Host bypasses this stream and should generally be avoided.
Not better solution of Martin, just an shorty code
$dateOfLicense = [DateTime]"2017-04-20"
$TimeDifferenceDays = ((Get-Date) - $dateOfLicense).Days
if ($TimeDifferenceDays -lt 14)
{
Write-Host "test"
}
If you google for PowerShell Eqv you currently will find the VBScript-to-Windows PowerShell Conversion Guide on top of the list. Yet the answer is much of technical help:
Eqv Operator
Definition: Performs a logical equivalence on two expressions.
No doubt Eqv has its uses; we’re just not sure if it has any practical uses. Although there might be a Windows PowerShell equivalent we have to be honest: we didn’t look real hard for one.
In fact, I used the Eqv operator (also written as: A↔B) a number of times in VBScript and likely would have used it in PowerShell if it existed.
Example
I have a list of groups ("HR", "SAP", "!IT", "..."). If a user is a member of all the listed groups and explicitly not a member of groups that are preceded with an escalation mark (like "!IT", which is unraveled to: $Negate = $True and $Group = "IT") a specific task should be done. The script needs to iterate through the groups and immediately break the iteration when a group condition is not met (to save time).
A command for this would have been something like:
If ($Negate -eqv (IsMember($Group))) {Break}
How can I build a logical equivalence operator with a minimum of code?
If you take definition quiet literally, you will probably already see a possible way to achieve a logical equivalence operation:
If ([Bool]$Expression1 -eq [Bool]$Expression2) {...}
But if you take a close look to the truth table you might notice that the results for Eqv are exactly the opposite of an Xor operation.
Meaning that you can also achieve logical equivalence operation with an inverted Xor:
If (!(Expression1 -xor $Expression2)) {...}
And as it doesn’t matter what you invert for an Xor (either the whole operation or one of the expressions), you can even simplify it to:
If (!Expression1 -xor $Expression2) {...}
Check
0..3 | ForEach {
$Expression1, $Expression2 = [Int]($_ / 2), [Int]($_ % 2)
New-Object PSObject -Property #{
Expression1 = [Bool]$Expression1
Expression2 = [Bool]$Expression2
Equivalence = !$Expression1 -xor $Expression2
}
} | Format-Table -AutoSize
Truth Table
Expression1 Expression2 Equivalence
----------- ----------- -----------
False False True
False True False
True False False
True True True
Note: In this solution $Null expressions are considered $False. This differs from the VBScript Eqv implementation but is consistent with other PowerShell operators that contain $Null expressions. e.g. The VBScript statement: If 1 And vbNull Then msgbox "True" Else msgbox "False", returns True where the PowerShell statement If (1 -and $Null) {"True"} Else {"False"}, returns False.
Bitwise
If you looking for a bitwise Eqv operator (which should probably be called -bEqv, if it existed), then it would be:
$Equivalence = -bNot Expression1 -bXOr Expression2 # e.g.: -bNot 3 -bXOr 5 = -7 (-bAnd 0xF = 9)
Ok, I have coded for quite a while in different, but I am not getting Powershells concept of a function return?....
I am very new to Powershell, so I am sure I am missing something very basic.
I have the function below:
function plGetKeyValue ([string] $FileName, [string] $SectionName, [string] $Key)
{
if ($PSBoundParameters.Count -lt 2 -or $PSBoundParameters.Count -gt 3 )
{
"Invalid call to {0} in {1}" -f $MyInvocation.MyCommand.Name,
$MyInvocation.MyCommand.ModuleName
return
}
# Declaration
$lFileContents = ""
$lSections = ""
$lDataStart = ""
$lStart = -1
$lEnd = -1
$lFoundSection = ""
$lNextSection = ""
$lResults = ""
$lRetValue = ""
# Handle the optional parameter.
if ( $PSBoundParameters.Count -eq 2 ) {
$PSBoundParameters.Add('Key', $SectionName)
$PSBoundParameters.Remove('SectionName')
$Key = $SectionName
$SectionName = $null
}
# Read the file in
$lFileContents = Get-Content $FileName | Select-String -Pattern .*
# Get the sections.
$lSections = $lFileContents -match '\['
$lSections = $lSections -notmatch '#'
# Start of the data.
$lDataStart = $lFileContents | Select-String -Pattern "^#", "^$" -NotMatch `
| select-object -First 1
# We have a section.
if ( $PSBoundParameters.ContainsKey( 'SectionName' ) ) {
# Find the section.
$lFoundSection = $lSections | Select-String -Pattern "$lSectionName\b"
# If none found we are out.
if ( -Not $lFoundSection) { return $lRetValue }
# Starting point for the key search is the line following
# the found section.
$lStart = $lFoundSection[0].LineNumber
# Loop through the sections and find the one after the found one.
$lNextSection = $lSections | ForEach-Object {
# If we hit it, break.
if ($_.LineNumber -gt $lStart) {
break;
}
}
# Set the ending line for the search to the end of the section
# or end of file. Which ever we have.
if ($lNextSection) {
$lEnd = $lNextSection[0].LineNumber
} else {
$lEnd = $lFileContents[-1]
}
} else {
# No section.
$lStart = $lDataStart.LineNumber
# Set the ending line for the search to the end of the section
# or end of file. Which ever we have.
if ($lSections) {
$lEnd = $lSections[0].LineNumber
} else {
$lEnd = $lFileContents[-1]
}
}
# Extract the lines starting with the key.
$lResults = $lFileContents[$lStart..$lEnd] -match "$Key\b"
# We got results.
# Split the value off.
return $lRetValue = $lResults[0] | Select -ExpandProperty "Line"
}
The process of creating this function has sparked several questions that I have researched and become confused with
1) The documentation indicates that $args should be used to determine arguments. It never seems to populate for me? I am using version 4? As a alternative I used $PSBoundParameters. Is this advisable?
2) Based on a lot of reading and head scratching, I have found that return values from functions rturn all uncaptured output to the pipeline. Can someone, please clarify uncaptured?
As an example, I would like the function below to return a string in the variable $lRetValue. Currently, it is returning True. Based on that I believe I have something uncaptured? But everything I am executing is captured in a variable. What am I missing?
The calling routine is calling the code in the following form:
$FileName = "S:\PS\Home\GlobalConfig\jobs.cfg"
$key = "Help"
$section = "Section"
$r = plGetKeyValue $FileName $Key
write-host "r is: $r"
The output shows as follows:
PS C:> S:\PS\Home\Job\Test.ps1
r is: True
Any assistance would be very much appreciated.
Terminology note: somewhat arbitrarily, I'll distinguish between parameters and arguments below:
- parameters as the placeholders that are defined as part of a function declaration,
- as distinct from arguments as the values that are bound to the placeholders in a given invocation.
Conceptual information:
1) The documentation indicates that $args should be used to determine arguments.
$args is a fallback mechanism for examining unbound arguments in non-advanced (non-cmdlet) functions.
$args is populated:
ONLY if your function is not an advanced function (a function is marked as an advanced function by the presence of a param(...) parameter-declaration statement - as opposed to declaring the parameters inside function someFunc(...)) - if decorated with a [CmdletBinding()] attribute).
even then it only contains the unbound arguments (those not mapped to declared parameters).
In other words: only if you declare your function without any parameters at all does $args contain all arguments passed.
Conversely, in an advanced function there mustn't be unbound arguments, and invoking an advanced function with arguments that cannot be bound to parameters simply fails (generates an error).
Since defining advanced functions is advisable in general, because they are best integrated with the PowerShell infrastructure as a whole, it's best to make do without $args altogether.
Instead, use a combination of multiple parameter sets and/or array parameters to cover all possible valid input argument scenarios.
$PSBoundArguments contains the arguments bound to declared parameters, and is normally not needed, because the variable names corresponding to the parameters names (e.g., $SectionName) can be used directly. (It has specialized uses, such as passing all bound parameters on to another cmdlet/function via splat #PSBoundArguments).
2) Based on a lot of reading and head scratching, I have found that return values from functions return all uncaptured output to the pipeline. Can someone, please clarify "uncaptured"?
Generally, any PowerShell statement or expression that generates output is sent to the success stream (loosely comparable to stdout in Unix shells) by default, UNLESS output is captured (e.g., by assigning to a variable) or redirected (e.g., by sending output to a file).
Thus, in a reversal of how most programming languages behave, you must take action if you do NOT want a statement to produce output.
If you're not interested in a statement's output (as opposed to capturing / redirecting it for later use), you can redirect to $null (the equivalent of /dev/null), pipe to cmdlet Out-Null, or assign to dummy variable $null ($null = ...).
Therefore, in a manner of speaking, you can call output that is sent to the success stream uncaptured.
That, however is unrelated to the return statement:
The return statement does not work the same way as in other languages; its primary purpose in PowerShell is as a control-flow mechanism - to exit a function or script block - rather than a mechanism to output data (even though it can also be used for that: with an argument, is another way to send output to the success stream).
Diagnosing your specific problem:
There are many ways in which your function could be made a better PowerShell citizen[1]
, but your immediate problem is this:
$PSBoundParameters.Remove('SectionName')
returns a Boolean value that is sent to the output stream, because you neither suppress, capture nor redirect it. In your case, since the $SectionName parameter is bound, it does have an entry in $PSBoundParameters, so $PSBoundParameters.Remove('SectionName') returns $true.
To suppress this unwanted output, use something like this:
$null = $PSBoundParameters.Remove('SectionName')
Generally speaking, unless you know that a statement does not generate output, it's better to be safe and prepend $null = (or use an equivalent mechanism to suppress output).
Especially with direct method calls on objects, it's often not clear whether a value - which turns into output (is sent to the success stream) - will be returned.
[1] The following help topics provide further information:
- USE of parameters, including how to inspect them with help -Full / -Detailed ...:
help about_Parameters
- DEFINING simple functions and their parameters:
help about_functions,
from which you can progress to advanced functions:
help about_functions_advanced
and their parameter definitions:
help about_Functions_Advanced_Parameters