PowerShell, How to provide a pipe variable? - powershell

This is a high level question as the details might not be precise, as I'm not in my office but home.
I have a function that accept variables through pipe:
get-csv | myfunc
The pipe source is the fields from a .csv file.
How to define a variables and pipe into myfunc()? Would HashTable be good?
$my_pipe_variables = #{ Color = ‘Red’; Doors = 4; Convertible = $false}
$my_pipe_variables | myfunc
would that be the correct syntax?
Update:
I finally get around to try it but it is not working for me, as my myfunc accesses pipe variables directly via $_. Here is the demo:
function showThem { echo Color: $_.Color }
> [pscustomobject]#{ Color = ‘Red’; Doors = 4; Convertible = $false} | showThem
Color:
How can I make it works for myfunc, which accesses pipe variables directly via $_?

Import-Csv (not Get-Csv), for reading CSV data from a file, and ConvertFrom-Csv, for reading CSV data from a string, output a collection of custom objects (type [pscustomobject]) whose properties reflect the CSV data's columns.
To construct such custom objects on demand in order to simulate Import-Csv / ConvertFrom-Csv input, use the [pscustomobject] #{ <propertyName>=<value>; ... } syntax (PSv3+).
E.g., to simulate 2 rows of CSV data with columns Color, Doors,
and Convertible:
[pscustomobject] #{ Color = 'Red'; Doors = 4; Convertible = $false },
[pscustomobject] #{ Color = 'Blue'; Doors = 5; Convertible = $false } |
...
Separately, in order to make a function process input from the pipeline object by object via automatic variable $_, it must have a process { ...} block - see help topic about_Functions.
# Define the function body with a process { ... } block, which
# PowerShell automatically calls for each input object from the pipeline,
# reflected in automatic variable $_
function showThem { process { "Color: " + $_.Color } }
[pscustomobject] #{ Color = 'Red'; Doors = 4; Convertible = $false },
[pscustomobject] #{ Color = 'Blue'; Doors = 5; Convertible = $false } |
showThem
Note: In PowerShell, echo is an alias of Write-Output, whose explicit use is rarely needed; instead, the function relies on PowerShell's implicit output: the result of the string concatenation (+) implicitly becomes the function's output.
The above yields:
Color: Red
Color: Blue

Related

Powershell script output to Discord

removing source for Proprietary reasons
ERROR
Out-String : A positional parameter cannot be found that accepts argument 'System.Object[]'.
At line:176 char:28
content = $summaries | Out-String `
Note: This isn't a complete answer, but it addresses your immediate problem - broken [pscustomobject] literal syntax - and provides some background information.
Format-Table, as all Format-* cmdlets, is only intended to produce output for display as opposed to programmatic processing.
To produce data (objects), use the Select-Object, which uses the same syntax with respect to the -Property parameter as Format-Table, notably including the calculated properties your Format-Table call uses.
Select-Object outputs [pscustomobject] instances that each have the specified properties.
Thus, you may be looking for this:
# Note The ` at the end of the line continues the Select-Object call
# on the next line, and the following lines are an array of
# property names / calculated properties that are passed
# to the positionally implied -Property parameter
$payload = $summaries | Select-Object `
start,
end,
#{ Label = 'issued_amount'; Expression = { '{0:C0}' -f $_.issued_amount }; Align = 'right' },
#{ Label = 'maturing_amount'; Expression = { '{0:C0}' -f $_.maturing_amount }; Align = 'right' },
#{ Label = 'inflation_compensation'; Expression = { '{0:C0}' -f $_.inflation_compensation }; Align = 'right' },
#{ Label = 'secondary_purchased'; Expression = { '{0:C0}' -f $_.secondary_purchased_amount }; Align = 'right' },
#{ Label = 'secondary_sold'; Expression = { '{0:C0}' -f $_.secondary_sold_amount }; Align = 'right' },
#{ Label = 'change'; Expression = { '{0:$#,##0}' -f $_.change }; Align = 'right' }
$payload is now an array of [pscustomobject] instances with the specified properties.
However, it looks like you cannot pass such an array directly to Send-DiscordMessage, except perhaps via the -Text parameter, which requires you to create a formatted string representation via Out-String:
Send-DiscordMessage -Uri $hookUrl -Text ($payload | Out-String)

How in general can I find functions that contains bugs due to array output?

Unassigned variables are outputted in PowerShell functions. So for instance in the function below you will get an object array returned instead of a string because [regex] is unassigned and needs to become assigned or nullified or whatever.
These bugs are very hard to detect when scanning codebases. Since some functions DO output an object array willingly (and then correctly handled) while for others it is indeed a bug. In this case the output was used to write somewhere and since an array gets transformed to a string it was undetectable.
function Get-PascalizedString {
param(
[string]$String
)
$rx = "(?:[^a-zA-Z0-9]*)(?<first>[a-zA-Z0-9])(?<reminder>[a-zA-Z0-9]*)(?:[^a-zA-Z0-9]*)"
$result = ""
[regex]::Matches($String, $rx) | ForEach-Object {$_.Groups} {
$TextInfo = (Get-Culture).TextInfo
$part = $TextInfo.ToTitleCase($_.Value.ToLower()).Trim()
$part = $part -replace "[^a-zA-Z0-9]"
$result = $result + $part
}
return $result
}
$a = Get-PascalizedString -String "aaa"
write-host $a
$a.GetType() // but...
So is there a smart way to detect these kind of bugs in larger codebases?

Tab-complete a parameter value based on another parameter's already specified value

This question addresses the following scenario:
Can custom tab-completion for a given command dynamically determine completions based on the value previously passed to another parameter on the same command line, using either a parameter-level [ArgumentCompleter()] attribute or the Register-ArgumentCompleter cmdlet?
If so, what are the limitations of this approach?
Example scenario:
A hypothetical Get-Property command has an -Object parameter that accepts an object of any type, and a -Property parameter that accepts the name of a property whose value to extract from the object.
Now, in the course of typing a Get-Property call, if a value is already specified for -Object, tab-completing -Property should cycle through the names of the specified object's (public) properties.
$obj = [pscustomobject] #{ foo = 1; bar = 2; baz = 3 }
Get-Property -Object $obj -Property # <- pressing <tab> here should cycle
# through 'foo', 'bar', 'baz'
#mklement0, regarding first limitation stated in your answer
The custom-completion script block ({ ... }) invoked by PowerShell fundamentally only sees values specified via parameters, not via the pipeline.
I struggled with this, and after some stubbornness I got a working solution.
At least good enough for my tooling, and I hope it can make life easier for many others out there.
This solution has been verified to work with PowerShell versions 5.1 and 7.1.2.
Here I made use of $cmdAst (called $commandAst in the docs), which contains information about the pipeline. With this we can get to know the previous pipeline element and even differentiate between it containing only a variable or a command. Yes, A COMMAND, which with help of Get-Command and the command's OutputType() member method, we can get (suggested) property names for such as well!
Example usage
PS> $obj = [pscustomobject] #{ foo = 1; bar = 2; baz = 3 }
PS> $obj | Get-Property -Property # <tab>: bar, baz, foo
PS> "la", "na", "le" | Select-String "a" | Get-Property -Property # <tab>: Chars, Context, Filename, ...
PS> 2,5,2,2,6,3 | group | Get-Property -Property # <tab>: Count, Values, Group, ...
Function code
Note that apart from now using $cmdAst, I also added [Parameter(ValueFromPipeline=$true)] so we actually pick the object, and PROCESS {$Object.$Property} so that one can test and see the code actually working.
param(
[Parameter(ValueFromPipeline=$true)]
[object] $Object,
[ArgumentCompleter({
param($cmdName, $paramName, $wordToComplete, $cmdAst, $preBoundParameters)
# Find out if we have pipeline input.
$pipelineElements = $cmdAst.Parent.PipelineElements
$thisPipelineElementAsString = $cmdAst.Extent.Text
$thisPipelinePosition = [array]::IndexOf($pipelineElements.Extent.Text, $thisPipelineElementAsString)
$hasPipelineInput = $thisPipelinePosition -ne 0
$possibleArguments = #()
if ($hasPipelineInput) {
# If we are in a pipeline, find out if the previous pipeline element is a variable or a command.
$previousPipelineElement = $pipelineElements[$thisPipelinePosition - 1]
$pipelineInputVariable = $previousPipelineElement.Expression.VariablePath.UserPath
if (-not [string]::IsNullOrEmpty($pipelineInputVariable)) {
# If previous pipeline element is a variable, get the object.
# Note that it can be a non-existent variable. In such case we simply get nothing.
$detectedInputObject = Get-Variable |
Where-Object {$_.Name -eq $pipelineInputVariable} |
ForEach-Object Value
} else {
$pipelineInputCommand = $previousPipelineElement.CommandElements[0].Value
if (-not [string]::IsNullOrEmpty($pipelineInputCommand)) {
# If previous pipeline element is a command, check if it exists as a command.
$possibleArguments += Get-Command -CommandType All |
Where-Object Name -Match "^$pipelineInputCommand$" |
# Collect properties for each documented output type.
ForEach-Object {$_.OutputType.Type} | ForEach-Object GetProperties |
# Group properties by Name to get unique ones, and sort them by
# the most frequent Name first. The sorting is a perk.
# A command can have multiple output types. If so, we might now
# have multiple properties with identical Name.
Group-Object Name -NoElement | Sort-Object Count -Descending |
ForEach-Object Name
}
}
} elseif ($preBoundParameters.ContainsKey("Object")) {
# If not in pipeline, but object has been given, get the object.
$detectedInputObject = $preBoundParameters["Object"]
}
if ($null -ne $detectedInputObject) {
# The input object might be an array of objects, if so, select the first one.
# We (at least I) are not interested in array properties, but the object element's properties.
if ($detectedInputObject -is [array]) {
$sampleInputObject = $detectedInputObject[0]
} else {
$sampleInputObject = $detectedInputObject
}
# Collect property names.
$possibleArguments += $sampleInputObject | Get-Member -MemberType Properties | ForEach-Object Name
}
# Refering to about_Functions_Argument_Completion documentation.
# The ArgumentCompleter script block must unroll the values using the pipeline,
# such as ForEach-Object, Where-Object, or another suitable method.
# Returning an array of values causes PowerShell to treat the entire array as one tab completion value.
$possibleArguments | Where-Object {$_ -like "$wordToComplete*"}
})]
[string] $Property
)
PROCESS {$Object.$Property}
Update: See betoz's helpful answer for a more complete solution that also supports pipeline input.
The part of the answer below that clarifies the limitations of pre-execution detection of the input objects' data type still applies.
The following solution uses a parameter-specific [ArgumentCompleter()] attribute as part of the definition of the Get-Property function itself, but the solution analogously applies to separately defining custom-completion logic via the Register-CommandCompleter cmdlet.
Limitations:
[See betoz's answer for how to overcome this limitation] The custom-completion script block ({ ... }) invoked by PowerShell fundamentally only sees values specified via parameters, not via the pipeline.
That is, if you type Get-Property -Object $obj -Property <tab>, the script block can determine that the value of $obj is to be bound to the -Object parameter, but that wouldn't work with
$obj | Get-Property -Property <tab> (even if -Object is declared as pipeline-binding).
Fundamentally, only values that can be evaluated without side effects are actually accessible in the script block; in concrete terms, this means:
Literal values (e.g., -Object ([pscustomobject] #{ foo = 1; bar = 2; baz = 3 })
Simple variable references (e.g., -Object $obj) or property-access or index-access expressions (e.g., -Object $obj.Foo or -Object $obj[0])
Notably, the following values are not accessible:
Method-call results (e.g., -Object $object.Foo())
Command output (via (...), $(...), or #(...), e.g.
-Object (Invoke-RestMethod http://example.org))
The reason for this limitation is that evaluating such values before actually submitting the command could have undesirable side effects and / or could take a long time to complete.
function Get-Property {
param(
[object] $Object,
[ArgumentCompleter({
# A fixed list of parameters is passed to an argument-completer script block.
# Here, only two are of interest:
# * $wordToComplete:
# The part of the value that the user has typed so far, if any.
# * $preBoundParameters (called $fakeBoundParameters
# in the docs):
# A hashtable of those (future) parameter values specified so
# far that are side effect-free (see above).
param($cmdName, $paramName, $wordToComplete, $cmdAst, $preBoundParameters)
# Was a side effect-free value specified for -Object?
if ($obj = $preBoundParameters['Object']) {
# Get all property names of the objects and filter them
# by the partial value already typed, if any,
# interpreted as a name prefix.
#($obj.psobject.Properties.Name) -like "$wordToComplete*"
}
})]
[string] $Property
)
# ...
}

How to display pscustomobject in a loop?

I have the following pscutomobject
$output = [PSCustomObject]#{
'#' = ''
'Report Path' = ''
'Schedule ID' = ''
Status = ''
}
i have a foreach loop and i add values for each iteration into the pscustomobject
foreach ($refreshSchedulePlanId in $reportRefreshScheduleSubscriptionIdDataWithoutHeader)
{
$output.'#' = $iteration
$output.'Report Path' = $($reportRefreshScheduleSubscriptionIdDataWithoutHeader[$loopCount])
$output.'Schedule ID' = $refreshSchedulePlanId
$output.Status = $status
$output | ft
}
but its outputting this way:
# Report Path Schedule ID Status
1 report1/rep1 0998989898 success
# Report Path Schedule ID Status
2 report2/re2 76767868767 fail
it should output:
# Report Path Schedule ID Status
1 report1/rep1 0998989898 success
2 report2/re2 76767868767 fail
etc..
By using ft (Format-Table) inside your loop, each custom object is formatted instantly, individually, printing as a complete table - complete with header - every time.
The immediate fix is to move the ft call out of the loop and apply it to the whole loop:
& {
foreach ($refreshSchedulePlanId in $reportRefreshScheduleSubscriptionIdDataWithoutHeader)
{
$output.'#' = $iteration
$output.'Report Path' = $($reportRefreshScheduleSubscriptionIdDataWithoutHeader[$loopCount])
$output.'Schedule ID' = $refreshSchedulePlanId
$output.Status = $status
$output
}
} | ft
Note: Since a foreach loop is a statement, it can't be used as-is in a pipeline, and is therefore wrapped in a script block ({ ... }) invoked with &, the call operator.
However, since your [pscustomobject] instances have only 4 properties, you do not need ft (Format-Table at all, because PowerShell by default uses table formatting with custom objects that have 4 or fewer properties.
Not using a Format-* cmdlet has the added advantage that the output remains suitable for further programmatic processing, given that Format-* cmdlets are only ever useful for display formatting, given that the objects they return are formatting instructions to PowerShell's output-formatting system - see this answer.

What is '#{}' meaning in PowerShell

I have line of scripts for review here, I noticed variable declaration with a value:
function readConfig {
Param([string]$fileName)
$config = #{}
Get-Content $fileName | Where-Object {
$_ -like '*=*'
} | ForEach-Object {
$key, $value = $_ -split '\s*=\s*', 2
$config[$key] = $value
}
return $config
}
I wonder what #{} means in $config = #{}?
#{} in PowerShell defines a hashtable, a data structure for mapping unique keys to values (in other languages this data structure is called "dictionary" or "associative array").
#{} on its own defines an empty hashtable, that can then be filled with values, e.g. like this:
$h = #{}
$h['a'] = 'foo'
$h['b'] = 'bar'
Hashtables can also be defined with their content already present:
$h = #{
'a' = 'foo'
'b' = 'bar'
}
Note, however, that when you see similar notation in PowerShell output, e.g. like this:
abc: 23
def: #{"a"="foo";"b"="bar"}
that is usually not a hashtable, but the string representation of a custom object.
The meaning of the #{}
can be seen in diffrent ways.
If the #{} is empty, an empty hash table is defined.
But if there is something between the curly brackets it can be used in a contex of an splatting operation.
Hash Table
Splatting
I think there is no need in explaining what an hash table is.
Splatting is a method of passing a collection of parameter values to a command as unit.
$prints = #{
Name = "John Doe"
Age = 18
Haircolor = "Red"
}
Write-Host #prints
Hope it helps! BR
Edit:
Regarding the updated code from the questioner the answer is
It defines an empty hash table.
Be aware that Get-Content has its own parameters!
THE MOST IMPORTANT 1:
[-Raw]