How to stop Powershell insisting on one dash in parameters? - powershell

It seems that somebody has subtly changed the way that parameter switches are parsed on powershell. On some machines "split-path c:\x\y --parent" works. On some it fails. Can anyone tell me a) what causes the difference and b) how can I stop it?

Switch parameters should work in the same way in both V1 and V2 (that means -parent is the right syntax).
In your case --parent should be bound to an parameter as a string. It should not be interpreted as a switch. You can test the binding via Trace-Command
Trace-Command parameterbinding -Expression { split-path c:\x\y --parent} -PSHost
Further info:
Considering --: every string behind -- is interpreted as argument, no matter if it looks like a switch.
[14]: function test {
param([switch]$sw, [string]$str)
write-host switch is $sw
write-host str is $str
}
[15]: test 1
switch is False
str is 1
[16]: test -sw
switch is True
str is
[17]: test -- -sw
switch is False
str is -sw

Related

Powershell - Write-Host printing in wrong place

My code has the following function:
function status{
if ($args[0] -eq "Stopped"){
Write-Host -NoNewline "Stopped" -fore red
.....
}
}
and the function is used as:
...
Write-Host "$($count)) $($i.DisplayName) STATUS: $(status $i.State)"
...
The result after script execution is:
Stopped 1) XXXXXX STATUS:
Why "Stopped" is in the beginning of the line? can someone help me?
The function is only to change the text color. As the example -fore red is to Stopped value.
You could use ANSI Escape sequences for this but it wouldn't work in old terminals. I'm not convinced if this is possible combining outputs from Write-Host and, if it is, I'll assume is an extremely tedious task.
function status {
$ansi = switch($args[0]) {
Stopped { "$([char] 27)[91m" }
Running { "$([char] 27)[32m" }
}
$ansi + $args[0]
}
foreach($state in 'Running', 'Stopped') {
$count++
$i = [pscustomobject]#{ state = $state; displayname = 'Hello!' }
Write-Host "$($count)) $($i.DisplayName) STATUS: $(status $i.State)"
}
Which would produce the following output:
Santiago Squarzon's helpful answer provides an effective solution; let me complement it with an explanation of what you tried:
Why "Stopped" is in the beginning of the line?
The reason is that Write-Host doesn't write to PowerShell's success output stream and therefore doesn't "return" a value from your status function.
Instead, Write-Host instantly prints to the host (display; in PSv5+ via the information stream), before exiting the function, and your function produces no success-stream output.
Since subexpressions (via $(...)) are evaluated first in an expandable string (or command in general):
the Write-Host call inside your status function executes right away and prints at the start of the line.
only then does the outer Write-Host call execute, which - due to the inner call's -NoNewLine switch - prints on the same line, after what the function printed, and $(status $i.State), due to the status call producing no success output, evaluates to the empty string.
A simple example can illustrate the problem:
Write-Host "foo: >>$(Write-Host bar)<<"
Output:
bar # The inner Write-Host printed right away.
foo >><< # $(Write-Host bar) produced no success output
Santiago's solution avoids this problem by replacing the Write-Host call inside the function with sending a string (implicitly) to the success output stream, so that it becomes the function's "return value" that subexpression $(status $i.State) expands to, and to make this string colored, ANSI / VT escape sequences are needed.
Note that while "$([char] 27)" is required in Windows PowerShell to embed an ESC character in an expanable string, PowerShell (Core) 7+ now offers escape sequence "`e"
Also, PowerShell (Core) 7.2+ offers the automatic $PSStyle variable, which, while more verbose, offers a more descriptive way to embed ANSI / VT escape sequences; e.g.:
# Prints only the word "green" in green.
"It ain't easy being $($PSStyle.Foreground.Green)green$($PSStyle.Reset)."
Note:
If you send strings with ANSI / VT sequences to the success output stream, as in the example above, colors are not automatically reset; $PSStyle.Reset is needed for a manual reset.
By contrast, if Write-Host prints a string, it resets colors automatically.

PowerShell 5.1 Why are these 2 functions returning different types

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.

Powershell: unexpected return value from function, use of $args to access parameters

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

PowerShell Script Arguments Passed as Array

EDIT: I've changed the code here to a simple test case, rather than the full implementation where this problem is arising.
I am trying to call one Powershell script from another, but things aren't working out as I'm expecting. As I understand things, the "&" operator is supposed to expand arrays into distinct parameters. That's not happening for me.
caller.ps1
$scriptfile = ".\callee.ps1"
$scriptargs = #(
"a",
"b",
"c"
)
& $scriptfile $scriptargs
callee.ps1
Param (
[string]$one,
[string]$two,
[string]$three
)
"Parameter one: $one"
"Parameter two: $two"
"Parameter three: $three"
Running .\caller.ps1 results in the following output:
Parameter one: a b c
Parameter two:
Parameter three:
I think that the problem I'm experiencing is $scriptargs array is not expanded, and is rather passed as a parameter. I'm using PowerShell 2.
How can I get caller.ps1 to run callee.ps1 with an array of arguments?
When invoking a native command, a call like & $program $programargs will correctly escape the array of arguments so that it is parsed correctly by the executable. However, for a PowerShell cmdlet, script, or function, there is no external programming requiring a serialize/parse round-trip, so the array is passed as-is as a single value.
Instead, you can use splatting to pass the elements of an array (or hashtable) to a script:
& $scriptfile #scriptargs
The # in & $scriptfile #scriptargs causes the values in $scriptargs to be applied to the parameters of the script.
You're passing the variables as a single object, you need ot pass them independently.
This here works:
$scriptfile = ".\callee.ps1"
& $scriptfile a b c
So does this:
$scriptfile = ".\callee.ps1"
$scriptargs = #(
"a",
"b",
"c"
)
& $scriptfile $scriptargs[0] $scriptargs[1] $scriptargs[2]
If you need to pass it as a single object, like an array, then you can have the callee script split it; the specific code for that would depend on the type of data you're passing.
Use Invoke-Expression cmdlet:
Invoke-Expression ".\callee.ps1 $scriptargs"
As the result you'll get :
PS > Invoke-Expression ".\callee.ps1 $scriptargs"
Parameter one: a
Parameter two: b
Parameter three: c
PS >

How to keep parameter list in a variable

I have a script internal.ps1 which accepts certain params:
param ($paramA, $paramB)
Write-Host $PSBoundParameters
And a script caller.ps1 that calls it:
.\internal -paramA A -paramB B
It works great:
PS C:\temp> .\caller
[paramA, A] [paramB, B] <<<< bounded to both params
However, in caller I want to keep the parameters to internal in a var, and use it later. However, that doesn't work:
$parms = "-paramA A -paramB B"
# Later...
.\internal $parms
Result: [paramA, A -paramB B] <<<<< All got bounded to ParamA
Neither does using an array:
$parms = #("A", "B")
# Later...
.\internal $parms
Result: [paramA, System.Object[]] <<<< Again, all bound to ParamA
How can I accomplish this? Note that the actual commandline is more complex, and may have unknown length.
The splatting operator (#) should do what you need.
Consider first this simple function:
function foo($a, $b) { "===> $a + $b" }
Calling with explicit arguments yields what you would expect:
foo "hello" "world"
===> hello + world
Now put those two values in an array; passing the normal array yields incorrect results, as you have observed:
$myParams = "hello", "world"
foo $myParams
===> hello world +
But splat the array instead and you get the desired result:
foo #myParams
===> hello + world
This works for scripts as well as for functions. Going back to your script, here is the result:
.\internal #myParams
[paramA, hello] [paramB, world]
Finally, this will work for an arbitrary number of parameters, so know a priori knowledge of them is needed.
powershell -file c:\temp\test.ps1 #("A","B")
or
powershell -command "c:\temp\test.ps1" A,B
Your script expects 2 arguments, but your previous attempts pass just a single one (a string and an array respectively). Do it like this:
$parms = "A", "B"
#...
.\internal.ps1 $parm[0] $parm[1]