How do I avoid getting data printed to stdout in my return value? - powershell

In doing some Powershell automation, I'm having trouble with the way that data written to stdout by a .cmd file is automatically captured. I have two functions that do something like the following:
function a {
& external.cmd # prints "foo"
return "bar"
}
function b {
$val = a
echo $val # prints "foobar", rather than just "bar"
}
Basically, data that external.cmd sends to stdout is added to the return value of a, even though all I really want to return from a is the string that I specified. How can I prevent this?

Here are a few different approaches for handling this:
capture the output of the .cmd script:
$output = & external.cmd # saves foo to $output so it isn't returned from the function
redirect the output to null (throw it away)
& external.cmd | Out-Null # throws stdout away
redirect it to a file
& external.cmd | Out-File filename.txt
ignore it in the caller by skipping it in the array of objects that's returned from the function
$val = a
echo $val[1] #prints second object returned from function a (foo is object 1... $val[0])
In PowerShell, any output value your code does not capture is returned the caller (including stdout, stderr, etc). So you have to capture or pipe it to something that doesn't return a value, or you'll end up with an object[] as your return value from the function.
The return keyword is really just for clarity and immediate exit of a script block in PowerShell. Something like this would even work (not verbatim but just to give you the idea):
function foo()
{
"a"
"b"
"c"
}
PS> $bar = foo
PS> $bar.gettype()
System.Object[]
PS> $bar
a
b
c
function foobar()
{
"a"
return "b"
"c"
}
PS> $myVar = foobar
PS> $myVar
a
b

I generally prefer to use one of the following two techniques which, in my opinion, make the code more readable:
Cast an expression to void in order to suppress the return value:
[void] (expression)
Assign the output value to the $null variable:
$null = expression
For example:
function foo
{
# do some work
return "success"
}
function bar
{
[void] (foo) # no output
$null = foo # no output
return "bar"
}
bar # outputs bar

If you want the output from the command to still print to the powershell command line, you can do a variant of the accepted answer:
& external.cmd | Out-Host

Related

how to use hashtable to pass parameters to a powershell cmd that uses double dashes before the parameters

i m writing a script that makes use of a cmdlet from a console app, let's call the cmdlet as cmdA. I have no control over implementation of cmdA. cmdA takes parameters with a double dash (cmdA --param1 value1 --param2 value2 --param3 value3 --param4 value4 --param5 value5)
Now param2, param3, param4 and param5 are optional. The user of my script may or may not provide values for these optional parameters. In the script, i formed a hashtable using the parameters for which the user provided values. Let's call it paramHashtable.
I am executing cmdA #paramHashtable but this is not working. I think it has to do something with double dashes because this approach works fine with a cmd that takes parameters with single dash
What would be an efficient way to pass parameters to the cmdA (an inefficient way would be to use many if blocks to check which values were provided and make calls accordingly)
edited*
This is how i m creating the hashtable :-
$optionalParameters = #{}
$optionalParameters = ParameterSelection ${OutputFolder} ${PerfQueryIntervalInSec} ${StaticQueryIntervalInSec} ${NumberOfIterations}
$requiredParameters = #{"sqlConnectionStrings " = "$SqlConnectionStrings"}
$parameters = $requiredParameters + $optionalParameters
function ParameterSelection ([string] $outputFolder, [string] $perfQueryIntervalInSec, [string] $staticQueryIntervalInSec, [string] $numberOfIterations)
{
$parametersList = #{}
if($outputFolder -ne "")
{
$parametersList["outputFolder "] = $outputFolder
}
if($perfQueryIntervalInSec -ne "")
{
$parametersList["perfQueryIntervalInSec "] = $perfQueryIntervalInSec
}
if($staticQueryIntervalInSec -ne "")
{
$parametersList["staticQueryIntervalInSec "] = $staticQueryIntervalInSec
}
if($numberOfIterations -ne "")
{
$parametersList["numberOfIterations "] = $numberOfIterations
}
return $parametersList
}
This is how i m calling it :-
& $ExePath actionName #parameters
The $ExePath has the path of the program to be executed
The actionName takes parameters like this:-
actionName --sqlConnectionStrings "Data Source=Server1" --outputFolder C:\Output
Can splatting with hash table work on cmdlets / functions where it's parameter have dashes?
It may work, but it is definitely not a good idea to have parameter names with dashes as this will result in a function / cmdlet where named parameters cannot be used, PowerShell binds the arguments positionally! (thanks mklement0 for pointing this out):
function Test-Splatting {
param(${-param1}, ${-param2})
"${-param1} ${-param2}"
}
$param = #{ '--param1' = 'hello'; '--param2' = 'world' }
Test-Splatting #param # => hello world
Example of what was mentioned before using the same function above:
# --param1 is bound positionally and not interpreted as a parameter:
Test-Splatting --param1 hello # => --param1 hello
As for an external programs, the linked answer in comments explains very well the approach you could take using a wrapper function and the use of the automatic variable $args:
function myfunc {
$binaryPath = 'path/to/file.exe'
& $binaryPath actionName $args
# or #args we can't be sure until testing
}
myfunc --sqlConnectionStrings "Data Source=Server1" --outputFolder C:\Output
As noted in the comments, because you are calling an external program, you should use array-based splatting or simply arrays directly to pass programmatically constructed arguments.
By contrast, hashtable-based splatting is usually only helpful when calling PowerShell commands (while it technically works with external programs too, the resulting parameter format (e.g. -foo:bar or -foo:"bar none") is unusual and understood by few external programs)
Note that the parameter names and values must be passed as separate array elements and the elements representing parameter names must include the - or -- prefix; e.g., consecutive array elements
'--sqlConnectionStrings' (the name) and
'Data Source=Server1' (the value).
PowerShell then constructs a command line for invocation of the external program behind the scenes, space-separating the array elements and double-quoting elements with embedded spaces on demand; the above example turns into the following:
--sqlConnectionStrings "Data Source=Server1"
Note that it is up to the target program to parse the single string that is its command line in a way that recognizes parameter name-value pairs.
# Get the array of *optional* parameter names and values from your helper function.
$optionalParameters = ParameterSelection ${OutputFolder} ${PerfQueryIntervalInSec} ${StaticQueryIntervalInSec} ${NumberOfIterations}
# Declare the *required* parameter(s) as an array.
$requiredParameters = '--sqlConnectionStrings', $SqlConnectionStrings
# Pass the arrays as-is to the external program.
# (Empty arrays are effectively ignored.)
& $ExePath actionName $requiredParameters $optionalParmeters
Your ParameterSelection function can be simplified as follows:
function ParameterSelection ([string] $outputFolder, [string] $perfQueryIntervalInSec, [string] $staticQueryIntervalInSec, [string] $numberOfIterations)
{
# Loop over all bound parameters.
$PSBoundParameters.GetEnumerator() | ForEach-Object {
# Emit the parameter name prefixed with '--' and the value.
# PowerShell automatically collects the output strings in an array
# when you assign a call to this function to a variable.
'--' + $_.Key
$_.Value
}
}

Assign empty string to a variable and change the value while executing the powershell script

I have a sample powershell script say test.ps1 with the below contents
$Arg=" "
Function EnableArg()
{
$Arg= "arg"
}
Function DisableArg()
{
$Arg= " "
}
Function Print()
{
Write-Host "Value - $Arg"
}
If I run this script using powershell ISE and try the following:
Print - > Value -
EnableArg
Print -> Value -
When I executed step 2 and then step 3, I was expecting
Value - arg
But unfortunately it shows
Value -
Not sure if the script is being reset everytime. I also tried explicitly declaring the type to [string] but no luck. Thanks in advance!
Instead of crossing scope boundaries, I recommend you use functions in a more appropriate fashion. You pass in parameters/arguments and you return output. Meaning you will capture the output of a function in a variable and pass arguments into a function.
Function EnableArg()
{
"arg"
}
Function DisableArg()
{
" "
}
Function Print($InternalArg)
{
Write-Host "Value - $InternalArg"
}
$arg = EnableArg
print $arg
$arg = DisableArg
Print $arg

How to use a powershell function to return the expected value?

As we know, PowerShell has wacky return semantics.
Function return value in PowerShell shows there are two main ideas to wrap my head around:
All output is captured, and returned
The return keyword just indicates a logical exit point
Even things like reserving variables in outer scopes cause output, like [boolean]$isEnabled. Another good one is $someCollection.Add("toto") which spits the new collection count. Even Append() function causes output.
For example :
Function MyFunc {
$res1 = new-object System.Text.StringBuilder
$res1.Append("titi");
$res2 = "toto"
return $res2
}
$s = MyFunc
Write-Host $s
The output is : titi toto.
The expected output should be toto.
How to use a powershell function to return the expected value? (at least when viewed from a more traditional programming perspective)
Change
$res1.Append("titi");
to
$res1.Append("titi") | Out-Null
because the function returns every output which otherwise would be visible in the console.
if by using 'toto' you are trying to understand if your function succeeded, you could do
Function MyFunc {
$res1 = new-object System.Text.StringBuilder
$res1.Append("titi") | Out-Null
return $?
}
"$?" returns a boolean if the previous command succeeded (true) or failed (false). so externally it would look like
$s = MyFunc
if ($s) {
Write-Host "successful" -Foregroundcolor Green
}
else {
Write-Error "unsuccessful"
}
When PowerShell was being developed, the team wanted to make it simple to use. But, it was confusing to people who know return from other languages. The implementation in classes is an attempt to rectify that mistake.
The return keyword works very differently in methods in PowerShell classes. It works like the return statements in other languages.
In a class method, the return keyword:
Exits the current scope.
Returns the associated object (return ).
Returns only the associated object.
The object that Return returns must match the return type of the method.
It is consistent with the return keyword and analogous keywords in other languages.
class ClassMyFunc
{
[string] MyFunc
{
$res1 = new-object System.Text.StringBuilder
$res1.Append("titi")
$res2 = "toto"
return $res2
}
}
$cmf = New-Object -TypeName ClassMyFunc
$cmf.MyFunc()
The output is : toto, as expected.
Using classes solved my problem, without having to search all functions returning a value in the console and piping it to Out-Null (as suggested by #TobyU).

$prompt = ($defaultValue,$prompt)[[bool]$prompt] - emulating a ternary conditional in PowerShell

I'm learning to write scripts with PowerShell, and I found this code that will help me with a project The example comes from Is there a one-liner for using default values with Read-Host?.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
$prompt = ($defaultValue,$prompt)[[bool]$prompt]
I think I understand that $prompt = ($defaultValue,$prompt) is creating a two-element array and that the [bool] part is forcing the $prompt data type to Boolean, but I don’t understand what this third line of code does as a whole.
This is a common programming pattern:
if (user entered a price)
{
price = user entered value
}
else
{
price = default value
}
and because that is quite common, and also long winded, some languages have a special ternary operator to write all that code much more concisely and assign a variable to "this value or that value" in one move. e.g. in C# you can write:
price = (user entered a price) ? (user entered value) : (default value)
# var = IF [boolean test] ? THEN (x) ELSE (y)
and the ? assigns (x) if the test is true, and (y) if the test is false.
In Python, it's written:
price = (user entered value) if (user entered a price) else (default value)
And in PowerShell, it's written:
# you can't have a ternary operator in PowerShell, because reasons.
Yeah. No nice short code pattern allowed.
But what you can do, is abuse array-indexing (#('x', 'y')[0] is 'x' and #('x', 'y')[1] is 'y' and ) and write that ugly and confusing code-golf line:
$price = ($defaultValue,$userValue)[[bool]$UserEnteredPrice]
# var (x,y) is an array $array[ ] is array indexing
(0,1) are the array indexes of the two values
[bool]$UserEnteredPrice casts the 'test' part to a True/False value
[True/False] used as indexes into an array indexing makes no sense
so they implicitly cast to integers, and become 0/1
# so if the test is true, the $UserValue is assigned to $price, and if the test fails, the $DefaultValue is assigned to price.
And it behaves like a ternary operator, except it's confusing and ugly and in some situations it will trip you up if you're not careful by evaluating both array expressions regardless of which one is chosen (unlike real ? operators).
Edit: What I should really add is a PowerShell form I prefer - you can assign the result of an if test directly in PowerShell and do:
$price = if ($userValue) { $userValue } else { $DefaultValue }
# ->
$prompt = if ($prompt) { $prompt } else { $DefaultValue }
Casting $prompt to [bool] produces a value of $true or $false depending on whether the variable is empty ($null or empty string both become $false) or not (non-emtpy strings become $true).
[bool]'' → $false
[bool]'something' → $true
Using that boolean value in the index operator then implicitly casts the value to an integer where $false becomes 0 and $true becomes 1, hence selecting the first or second element of the array.
[int]$false → 0
[int]$true → 1
($defaultValue,$prompt)[0] → $defaultValue
($defaultValue,$prompt)[1] → $prompt
To complement the great answers given by Ansgar Wiechers and by TessellatingHeckler:
It would be great if PowerShell had operators for ternary conditionals and null-coalescing, such as follows (applied to the example in the question):
Update: PowerShell (Core) 7+ now does have these operators.
# Ternary conditional
# Note: does NOT work in *Windows PowerShell*,
# only in PowerShell (Core) v7+
$prompt = $prompt ? $prompt : $defaultValue
# Or, more succinctly, with null coalescence:
# Note: does NOT work in *Windows PowerShell*,
# only in PowerShell (Core) v7+
# (Note: This example assumes that $prompt will be $null in the default
# case, whereas the code in the question actually assigns the
# empty string to $prompt if the user just presses Enter.)
$prompt = $prompt ?? $defaultValue
Unfortunately, these expressive constructs are not part of Windows PowerShell, the legacy edition of PowerShell, which will see no new functionality.
Below are adapted versions of the functions from a 2006 PowerShell Team blog post with associated alias definitions, whose use then allows the following solution:
# Ternary conditional - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?: $prompt $prompt $defaultValue
# Or, more succinctly, with null coalescence - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?? $prompt $defaultValue
Source code:
Note that the actual functions are quite short; it is the comment-based help that makes this listing lengthy.
Set-Alias ?: Invoke-Ternary -Option AllScope
<#
.SYNOPSIS
Emulation of a ternary conditional operator.
.DESCRIPTION
An emulation of the still-missing-from-the-PS-language ternary conditional,
such as the C-style <predicate> ? <if-true> : <if-false>
Because a function is used for emulation, however, the function name must
come first in the invocation.
If you define a succinct alias, e.g., set-alias ?: Invoke-Ternary,
concise in-line conditionals become possible.
To specify something other than a literal or a variable reference, pass a
script block for any of the tree operands.
A predicate script block is of necessity always evaluated, but a script block
passed to the true or false branch is only evaluated on demand.
.EXAMPLE
> Invoke-Ternary { 2 -lt 3 } 'yes' 'no'
Evaluates the predicate script block, which outputs $true, and therefore
selects and outputs the true-case expression, string 'yes'.
.EXAMPLE
> Invoke-Ternary $false { $global:foo = 'bar' } { Get-Date }
Outputs the result of executing Get-Date.
Note that the true-case script block is NOT evaluated in this case.
.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-Ternary
{
[CmdletBinding()]
param($Predicate, $Then, $Otherwise = $null)
if ($(if ($Predicate -is [scriptblock]) { & $Predicate } else { $Predicate })) {
if ($Then -is [ScriptBlock]) { & $Then } else { $Then }
} else {
if ($Otherwise -is [ScriptBlock]) { & $Otherwise } else { $Otherwise }
}
}
Set-Alias ?? Invoke-NullCoalescence -Option AllScope
<#
.SYNOPSIS
Emulation of a null-coalescence operator.
.DESCRIPTION
An emulation of a null-coalescence operator such as the following:
<expr> ?? <alternative-expr-if-expr-is-null>
Because a function is used for emulation, however, the function name must
come first in the invocation.
If you define a succinct alias, e.g., set-alias ?? Invoke-NullCoalescence,
concise in-line null-coalescing becomes possible.
To specify something other than a literal or a variable reference, pass a
script block for any of the two operands.
A first-operand script block is of necessity always evaluated, but a
second-operand script block is only evaluated on demand.
Note that only a true $null value in the first operand causes the second
operand to be returned.
.EXAMPLE
> Invoke-NullCoalescence $null '(empty)'
Since the first operand is $null, the second operand, string '(empty)', is
output.
.EXAMPLE
> Invoke-NullCoalescence '' { $global:foo = 'bar' }
Outputs the first operand, the empty string, because it is not $null.
Note that the second-operand script block is NOT evaluated in this case.
.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-NullCoalescence
{
[CmdletBinding()]
param($Value, $Alternative)
if ($Value -is [scriptblock]) { $Value = & $Value }
if ($null -ne $Value) {
$Value
} else {
if ($Alternative -is [ScriptBlock]) { & $Alternative } else { $Alternative }
}
}

Looking for a way to have all functions send output to another function

I realize the title is a little confusing but I couldn't figure out a better way to phrase it.
I have a Powershell script with a couple dozen functions. Currently, I have the exact same code in every function to format the output. Here's a snippet:
function function1 () {
do something...
output code here
}
function function2 () {
do something...
output code here
}
The output code is exactly the same. Being a fan of code deduplication, this is driving me crazy because every time I add a new function I have this template code that I have to apply. I've tried putting the entire script in a try/catch block and throwing the object that is output but I couldn't get it to work and this still requires coding in the same throw statement in every function.
Does anyone know of something I can do to have all of these functions in this script to automatically send their output to another function or am I just going to have to live with this?
If the functions have no parameters, you can use this simple solution:
function addOutputCode {
param($name)
$oldBody = (get-item function:$name).ScriptBlock
$newBody = {
param($computer)
$funcOutput = . $oldBody $computer
# some formatting
$funcOutput | % { 'FORMATTED: ' + $_ }
}.GetNewClosure()
Set-Item function:$name -value $newBody
}
As you can see the functions gets the body of the function and assignes new body with formatting code. You can try it, just copy & paste the code below.
# this is your file with defined functions
function f1 { param($c) 'this is test of ' + $c }
function f2 { $c.Length; 'this was length of $c' }
# now f1 and f2 would return unformatted data
# f1
# f2
# add formatting code
addOutputCode f1
addOutputCode f2
# now if you call f1 or f2, they return formatted data
# f1 comp1
# f2 comp2