Split a string into an Object array - arrays

I have this string in PowerShell:
$filter = "Name;Server;Mounted;ObjectCategory;Guid;WhenChangedUTC;WhenCreatedUTC;ObjectState"
I want to split it to get an object array, I tried:
$stringArray = $filter.Split(';')
but it's a string array.
At the end I would like to call:
... | Select-Object -Property #{Name = 'Command'; Expression = { "Insert" } }, $filterObjectArray
The $filterObjectArray doesn't work when it's a string array.
But it works if $filterObjectArray = 'Name', 'Server' ...
The problem is my custom property called Command because if I only use :
... | Select-Object -Property $filterObjectArray
it works.
Thank you for your help.

Your -Property argument must be passed as a flat array, which requires use of an expression:
# Note: Target parameter -Property is positionally implied.
Select-Object (, #{Name = 'Command'; Expression={ "Insert" }} + $filterObjectArray)
Note:
Because an expression is required that is to serve as a command argument, it must be enclosed in (...), the grouping operator.
,, the array constructor operator, is used to wrap the script block ({ ... }) in a single-element array so that applying the + operator on it performs array concatenation, which means that the elements of the RHS array, $filterObjectArray, directly become elements of the resulting array, resulting in the necessary flat array of calculated properties and property names.
As for what you tried:
#{Name = 'Command'; Expression = { "Insert" } }, $filterObjectArray
This argument, parsed in argument mode, results in a nested array: the first element is the script block ({ ... }), and the second and last element is the $filterObjectArray array as a whole, which is why the names stored in it weren't recognized.
Note that in argument mode - one of PowerShell's two fundamental parsing modes - only a small subset of operators can be used, such as , and #, the splatting operator; to use others, such as +, a grouping expression ((...)) is needed, as shown (or, for using the output from entire statements as arguments, $(...) or #(...))

Related

Powershell: Piping output of pracl command to array

pracl is a sysinternal command that can be used to list the ACLs of a directory. I have a list of shares and I want to create a csv file such that for each ACL entry, I want the share path in one column and share permission in the next. I was trying to do that by using the following code
$inputfile = "share.txt"
$outputFile = "out.csv"
foreach( $path in Get-Content $inputfile)
{
$results=.\pracl.exe $path
{
foreach ($result in $results) {write-host $path,$line}
}
$objResult = [pscustomobject]#{
Path = $Path
Permission = $line
}
$outputArray += $objResult
$objresult
}
$outputArray | Export-Csv -Path $outputfile -NoTypeInformation
It failed with the following error :-
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.
At C:\Users\re07393\1\sample.ps1:14 char:1
+ $outputArray += $objResult
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (op_Addition:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Any suggestions ?
You're trying to create an array of [pscustomobject]s in your $outputArray variable iteratively, using +=, but you're not initializing $outputArray as an array - see the bottom section for an explanation of the resulting behavior.
Thus, the immediate solution to your problem is to do just that:
# Do this before your `foreach` loop, then `+=` will work for appending elements.
$outputArray = #()
However, using += to add to arrays is inefficient, because in reality a new array instance must be created every time, because arrays are immutable data structures. That is, every time += is used, PowerShell creates a new array instance behind the scenes to which the existing elements as well as the new element are copied.
A simpler and much more efficient approach is to let PowerShell create an array for you, by using the foreach loop as an expression and assigning it to a variable as a whole:
That is, whatever is output in every iteration of the loop is automatically collected by PowerShell:
A simplified example:
# Create an array of 10 custom objects
[array] $outputArray = foreach ($i in 1..10) {
# Create and implicitly output a custom object in each iteration.
[pscustomobject] #{
Number = $i
}
}
Note the use of type constraint [array] to the left of $outputArray, which ensures that the variable value is always an array, even if the loop happens to produce just one output object (in which case PowerShell would otherwise just store that object itself, and not wrap it in an array).
Note that you can similarly use for, if, do / while / switch statements as expressions.
In all cases, however, these statements can only serve as expressions by themselves; regrettably, using them as the first segment of a pipeline or embedding them in larger expressions does not work - see GitHub issue #6817.
As for what you tried:
$outputArray += $objResult
Since you didn't initialize $outputArray before the loop, the variable is implicitly created in the loop's first iteration:
If the LHS variable doesn't exist yet, += is effectively the same as =: that is, the RHS is stored as-is in the LHS variable, so that $outputArray now contains a [pscustomobject] instance.
In the second iteration, because $outputArray now has a value, += now tries to perform a type-appropriate + operation (such as numeric addition for numbers, and concatenation for strings), but no + (op_Addition()) operation is defined for type [pscustomobject], so the operation fails with the error message you saw.

I keep getting "Unable to index into an object of type System.String." when I change the value of my array in PowerShell

I am trying to create a PowerShell script that creates folders (nested and multiple). I managed to get the nested folders to work, and I am pretty new to this, but I cant seem to get the multiple folders to work. I basically want to make a string array with each item being a user inputted string. I tried everything but it just doesn't seem to work, any help would be greatly appreciated, heres my code
echo "Folder Creator for FS2019 by Skaytacium, enter the values for unlimited nested folders! Remember though, 260 characters is the max nesting limit!"
$count = Read-Host -Prompt 'How many folders? Value'
$count = [int16]$count
$sub = Read-Host -Prompt 'Do you wanna nest? 2/1'
$namestring = "./"
$storay
[string]$array = "0", "0", "0"
$arac = 0
$arac = [int16]$arac
if ($sub -eq "2") {
echo "The specified folders will be nested"
while ($count -gt 0) {
$namestring = $namestring + (Read-Host -Prompt 'Name') + "/"
$count--
echo $namestring
if ($count -eq 0) {
md $namestring
}
}
}
elseif ($sub -eq "1") {
echo "The specified folders will be consecutive (in the same dir)"
while ($count -gt 0){
$storay = Read-Host "Name"
$array[1] = #("please help")
echo $array
$arac++
$count--
}
}
Pause
Thanks,
Sid
Replace:
[string] $array = "0", "0", "0" # !! creates *single* string
with:
[string[]] $array = "0", "0", "0" # creates *array* of strings
In your original command, the [string] type constraint to the left of the variable caused the input array (which is implicitly of type [object[]]) to be converted to a single string ([string]) with contents 0 0 0
(PowerShell stringifies arrays by concatenating their elements with separator $OFS, which defaults to a space.)
By contrast, [string[]] results in an array, whose elements are string-typed.
A [...]-enclosed literal in PowerShell refers to a .NET type. It is called a type literal.
If you place such a type literal to the left of variable assignment, you type-constrain that variable, meaning that it can only store values that are instances of that type, and that it automatically tries to convert instances of other types to that type, if possible; see this answer for more information about type constraints.
You can also use a type literal to cast expressions to that type (as your code does in statement $arac = [int16] $arac), which also means converting the operand to that type, as a one-time operation.
In type literal [string[]], the [] after the type name, string, specifies an array of elements of that type; except for the enclosing [...], this notation is the same as the one used by the .NET Type.GetType() method; see this answer for more information.
Note that PowerShell's type conversions are much more flexible than C#'s for instance; they are augmented by built-in conversion rules and attempts to automatically use appropriate constructors and static .Parse() methods.
Note that you don't strictly need the type constraint [string[]], except if you want to make sure that assigning to elements of this array later automatically performs to-string conversion.
# OK, but creates [object[]] array that now happens to contain [string]
# instances, but the type of the elements isn't locked in.
$array = "0", "0", "0"
As for the specific error message you saw:
Because $array was just a single [string] in your code, indexing with, say, [0] indexes in the string's character array.
Technically, this works for getting characters from the array, but you cannot set them:
# GET the 1st character from the string stored in $var
PS> $var = 'foo'; $var[0]
f # 1st character in the string
# !! You CANNOT SET characters that way
PS> $var[0] = 'F'
InvalidOperation: Unable to index into an object of type "System.String".

PowerShell enumerate an array that contains only one inner array

Conside this:
This is one array and contains only one item. And the item of the array is another array(I call it list object).
I want to get the only list object inside an array.
I use foreach to get the list, but it treates the list object as an array and return the first item of the list object.
$OSName1 = #(
#("win2008r2")
)
$OSName2 = #(
#("win2008r2"),
#("win2012")
)
foreach ($osname in $OSName1) {
write-host $osname[0]
}
foreach ($osname in $OSName2) {
write-host $osname[0]
}
I expect the output of result is:
win2008r2
win2008r2
win2012
But the real result is
w
win2008r2
win2012
Why powershell auto expands the inside array to it parent?
You're misunderstanding how array construction in general and the #() operator in particular work in PowerShell. If you take a look at the value of your 2 array variables you'll notice that only the second one has nested arrays:
PS C:\> ConvertTo-Json $OSName1
[
"win2008r2"
]
PS C:\> ConvertTo-Json $OSName2
[
[
"win2008r2"
],
[
"win2012"
]
]
That is because the array subexpression operator #() evaluates the nested expression and then returns the result as an array. But when you're nesting an array subexpression into another array subexpression the result of the inner subexpression is automatically unrolled upon the evalution of the outer subexpression. Because of that your first variable becomes ['win2008r2'] instead of the intended [['win2008r2']].
Your second example works the way you expect because the outer array subexpression contains not just a nested array subexpression, but an array of nested subexpressions:
#(...), #(...)
^
`- this comma is what actually creates the array of arrays
The outer array subexpression unrolls only the outer array, so that the result is still an array of arrays in the end. Basically, you don't need the outer #() for the desired result. Remove it and you will get the exact same result:
$OSName2 = #("win2008r2"), #("win2012")
To get a similar result with just a single nested array you need to use the unary array construction operator:
$OSName1 = ,#("win2008r2")
Adding to Ansgar's explanation, you just need a nested array to achieve what you need.
$OSName1 = #(
#("win2008r2")
)
$OSName2 = #(
#("win2008r2"),
#("win2012")
)
foreach ($osname in $OSName1)
{
$OSName
foreach($OS2 in $OSName2 )
{
$OS2
}
}
I hope it helps.

IF contains more than one string do "this" else "this"

Trying to make a script that request more info (group Id) if there are SCOM groups with identical names:
function myFunction {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string[]]$ObjectName
)
foreach ($o in $ObjectName) {
$p = Get-SCOMGroup -DisplayName "$o" | select DisplayName
<#
if ($p contains more than one string) {
"Request group Id"
} else {
"do this"
}
#>
}
}
Need help with the functionality in the comment block.
Wrap the value in an array subexpression #() and count how many entries it has:
if(#($p).Count -gt 1){"Request group Id"}
Note: This answer complements Mathias R. Jessen's helpful answer.
Counting the number of objects returned by a command:
Mathias' answer shows a robust, PowerShell v2-compatible solution based on the array sub-expression operator, #().
# #() ensures that the output of command ... is treated as an array,
# even if the command emits only *one* object.
# You can safely call .Count (or .Length) on the result to get the count.
#(...).Count
In PowerShell v3 or higher, you can treat scalars like collections, so that using just (...).Count is typically enough. (A scalar is a single objects, as opposed to a collections of objects).
# Even if command ... returns only *one* object, it is safe
# to call .Count on the result in PSv3+
(...).Count
These methods are typically, but not always interchangeable, as discussed below.
Choose #(...).Count, if:
you must remain PSv2-compatible
you want to count output from multiple commands (separated with ; or newlines)
for commands that output entire collections as a single object (which is rare), you want to count such collections as 1 object.[1]
more generally, if you need to ensure that the command output is returned as a bona fide array, though note that it is invariably of type [object[]]; if you need a specific element type, use a cast (e.g., [int[]]), but note that you then don't strictly need the #(...); e.g.,
[int[]] (...) will do - unless you want to prevent enumeration of collections output as single objects.
Choose (...).Count, if:
only one command's output must be counted
for commands that output entire collections as a single object, you want to count the individual elements of such collections; that is, (...) forces enumeration of command output.[2]
for counting the elements of commands's output already stored in a variable - though, of course, you can then simply omit the (...) and use $var.Count
Caveat: Due to a longstanding bug (still present as of PowerShell Core 6.2.0), accessing .Count on a scalar fails while Set-StrictMode -Version 2 or higher is in effect - use #(...) in that case, but note that you may have to force enumeration.
To demonstrate the difference in behavior with respect to (rare) commands that output collections as single objects:
PS> #(Write-Output -NoEnumerate (1..10)).Count
1 # Array-as-single-object was counted as *1* object
PS> (Write-Output -NoEnumerate (1..10)).Count
10 # Elements were enumerated.
Performance considerations:
If a command's output is directly counted, (...) and #(...) perform about the same:
$arr = 1..1e6 # Create an array of 1 million integers.
{ (Write-Output $arr).Count }, { #(Write-Output $arr).Count } | ForEach-Object {
[pscustomobject] #{
Command = "$_".Trim()
Seconds = '{0:N3}' -f (Measure-Command $_).TotalSeconds
}
}
Sample output, from a single-core Windows 10 VM (the absolute timings aren't important, only that the numbers are virtually the same):
Command Seconds
------- -------
(Write-Output $arr).Count 0.352
#(Write-Output $arr).Count 0.365
By contrast, for large collections already stored in a variable, #(...) introduces substantial overhead, because the collection is recreated as a (new) array (as noted, you can just $arr.Count):
$arr = 1..1e6 # Create an array of 1 million integers.
{ ($arr).Count }, { #($arr).Count } | ForEach-Object {
[pscustomobject] #{
Command = "$_".Trim()
Seconds = '{0:N3}' -f (Measure-Command $_).TotalSeconds
}
}
Sample output; note how the #(...) solution is about 7 times slower:
Command Seconds
------- -------
($arr).Count 0.009
#($arr).Count 0.067
Coding-style considerations:
The following applies in situations where #(...) and (...) are functionally equivalent (and either perform the same or when performance is secondary), i.e., when you're free to choose which construct to use.
Mathias recommends #(...).Count, stating in a comment:
There's another reason to explicitly wrap it in this context - conveying intent, i.e., "We don't know if $p is a scalar or not, hence this construct".
My vote is for (...).Count:
Once you understand that PowerShell (v3 or higher) treats scalars as collections with count 1 on demand, you're free to take advantage of that knowledge without needing to reflect the distinction between a scalar and an array in the syntax:
When writing code, this means you needn't worry about whether a given command situationally may return a scalar rather than a collection (which is common in PowerShell, where capturing output from a command with a single output object captures that object as-is, whereas 2 or more output objects result in an array).
As a beneficial side effect, the code becomes more concise (and sometimes faster).
Example:
# Call Get-ChildItem twice, and, via Select-Object, limit the
# number of output objects to 1 and 2, respectively.
1..2 | ForEach-Object {
# * In the 1st iteration, $var becomes a *scalar* of type [System.IO.DirectoryInfo]
# * In the 2nd iteration, $var becomes an *array* with
# 2 elements of type [System.IO.DirectoryInfo]
$var = Get-ChildItem -Directory / | Select-Object -First $_
# Treat $var as a collection, which in PSv3+ works even
# if $var is a scalar:
[pscustomobject] #{
Count = $var.Count
FirstElement = $var[0]
DataType = $var.GetType().Name
}
}
The above yields:
Count FirstElement DataType
----- ------------ --------
1 /Applications DirectoryInfo
2 /Applications Object[]
That is, even the scalar object of type System.IO.DirectoryInfo reported its .Count sensibly as 1 and allowed access to "its first element" with [0].
For more about the unified handling of scalars and collections, see this answer.
[1] E.g., #(Write-Output -NoEnumerate 1, 2).Count is 1, because the Write-Output command outputs a single object - the array 1, 2 - _as a whole. Because only a single object is output, #(...) wraps that object in an array, resulting in , (1, 2), i.e. a single-element array whose first and only element is itself an array.
[2] E.g., (Write-Output -NoEnumerate 1, 2).Count is 2, because even though the Write-Output command outputs the array as a single object, that array is used as-is. That is, the whole expression is equivalent to (1, 2).Count. More generally, if a command inside (...) outputs just one object, that object is used as-is; if it outputs multiple objects, they are collected in a regular PowerShell array (of type [object[]]) - this is the same behavior you get when capturing command output via a variable assignment ($captured = ...).

How does Powershell array flattening work?

Please, observe:
C:\> $x = #(1)
C:\> $x = #($x,2)
C:\> $x = #($x,3)
C:\> $x = #($x,4)
C:\> $x = #($x,5)
C:\> $x.Length
2
C:\> #($x |% { $_ }).Length
3
C:\> $x
Length : 2
LongLength : 2
Rank : 1
SyncRoot : {System.Object[] 2, 3}
IsReadOnly : False
IsFixedSize : True
IsSynchronized : False
Count : 2
4
5
C:\>
I expected the pipeline to flatten the list. But it does not happen. What am I doing wrong?
I expected the pipeline to flatten the list.
PowerShell enumerates arrays (lists) and, generally speaking, (most) enumerable data types, i.e. it sends their elements one by one to the success output stream (for which the pipeline acts as a conduit).
If an element itself happens to be another enumerable, it is not also enumerated.
Conceptualizing output in PowerShell as object streams of indeterminate length is better than to think of it in terms of arrays (lists) and their flattening - see the bottom section.
Separately, during for-display formatting only, enumerables are enumerated two levels deep, so that visually you can't tell the difference between #(1, 2) (an array that is enumerated, resulting in two integers being output) and Write-Output -NoEnumerate #(1, 2) (an array that is output as a single object) - even though in terms of data output these commands differ.
This for-display-only nested enumeration stops at the second level, so that any element at that level is formatted as it itself, even if it happens to be yet another enumerable; e.g., in the case of an array, that array is formatted according to the usual PowerShell rules for a given object: because the .NET Array type has more than 4 properties, Format-List formatting is implicitly applied, resulting in a line-by-line display of Array instance's properties, as shown in the following example:
# The 2nd element - a nested array - is formatted as a whole.
PS> Write-Output -NoEnumerate #(1, #(2))
1
Length : 1
LongLength : 1
Rank : 1
SyncRoot : {2}
IsReadOnly : False
IsFixedSize : True
IsSynchronized : False
Count : 1
Applying the above to your specific example:
# Your nested array.
$x = #(1); $x = #($x,2); $x = #($x,3); $x = #($x,4); $x = #($x,5)
To visualize the resulting nested array, pass it to ConvertTo-Json:
PS> ConvertTo-Json -Depth 4 $x
[
[
[
[
[
1
],
2
],
3
],
4
],
5
]
This tells us that you've created a two-element array, whose first element happens to be a nested array, itself comprising two elements, the first of which contains another nested array.
Therefore, $x.Length outputs 2.
#($x | % { $_ }).Length outputs 3 for the following reason:
Sending $x to the pipeline operator | enumerates its elements, and outputting each element (via $_ in the % (ForEach-Object) script block) causes each element to also be enumerated, if it happens to be an array.
Thus, the elements of the two-element array nested inside the first element of $x were output individually, followed by 5, the second element of $x, resulting in a total of three output objects.
Capturing these output objects in an array (#(...)) creates a three-element array whose .Length reports 3.
The for-display representation resulting from outputting $x itself follows from the explanation above.
Background information:
The PowerShell pipeline is a conduit for the success output stream, which is a stream of objects of indeterminate length, and only when that stream is captured - in the context of assigning to a variable ($var = ...) or making command output participate in an expression (e.g. (...).Foo) - does the concept of arrays enter the picture:
If the output stream happens to contain just one object, that object is captured as-is.
Otherwise, the PowerShell engine - of necessity - captures the multiple objects in a collection, which is an [object[]]-typed array.
Thus, the output stream has no concept of arrays, and it ultimately leads to confusion to discuss it in term of arrays and their flattening.
Note that the output stream (a pipeline) isn't only used when using the pipeline operator (|) to explicitly pipe data between commands, ...
... it is also used implicitly by any single command to send its output to.
However, it is not used in an expression (alone), such as 1 + 2 or [int[]] (1, 2, 3) (a command is any cmdlet, function, script, script block, or external program) or when passing arguments to commands.
That said, if you send the result of an expression to a command via | (which only works if the expression is the first pipeline segment), a pipeline is again involved, and the usual enumeration behavior (discussed below) applies to the expression's result; e.g. [int[]] (1, 2, 3) | ForEach-Object { 1 + $_ }
Perhaps surprisingly, use of the #(...) and $(...) operators invariably involves a pipeline, so that even enclosing stand-alone expressions in them results in enumeration (and re-collection); e.g., #([int[]] (1, 2, 3)).GetType().Name reports Object[] ([object[]]), because the strongly typed [int[]] array was enumerated, and the results were collected in a regular PowerShell array.
The only exceptions are array literals such as #(1, 2, 3), where (in PowerShell version 5 and above) this behavior is optimized away.
By contrast, the (...) operator does not enumerate expression results.
By default, outputting an array to the success output stream (more generally, instances of most .NET types that implement the IEnumerable interface[1]), causes it to be enumerated, i.e. its elements are sent one by one.
As such, there is no guaranteed relationship between outputting an array and whether or not capturing the output also results in an (new) array.
Notably, outputting a single object is indistinguishable from outputting that single object wrapped in a (single-element) array.
Sending arrays (collections) as a whole to the pipeline requires additional effort:
Either use Write-Output -NoEnumerate
# -> 1, because only *one* object was output, the array *as a whole*
(Write-Output -NoEnumerate #(1, 2) | Measure-Object).Count
Or - more obscurely, but more efficiently, use the unary form of ,, the array constructor operator, in order to wrap an array in an aux., transitory array whose enumeration then outputs the original array as a single object:
(, #(1, 2) | Measure-Object).Count # -> 1
[1] For a summary of which types PowerShell considers enumerable - which both excludes select types that do implement IEnumerable and includes one that doesn't - see the bottom section of this answer.

Resources