Powershell array interpreted as single item - arrays

I'm just learning PowerShell, and running into an issue.
I'm trying to use an array to define a collection of folder/file paths, and then use that collection in a foreach loop. When the loop is executed, it seems to be concatenating the collection into a single path instead of separately processing each item in the array. This results in a path not being found error.
Code:
$SourcePathRoot = "C:\Temp\"
#$SourcePaths = #($SourcePathRoot + "File1.dat", $SourcePathRoot + "File2.txt")
foreach ($Path in $SourcePaths) {
Write-Host $Path
Test-Path $Path }
Output ($Path and Test-Path $Path):
C:\Temp\File1.dat C:\Temp\File2.txt
False
I know it's probably something simple - what am I doing wrong?!? Thank you.

This has to do with operator precedence and type-dependent operator behavior in PowerShell.
When evaluating an expression, , takes precedence over +.
In your example, the , operator in the middle of your array expression is therefore evaluated before the + operators are.
Let's step through:
# Okay, this is the raw expression
#($SourcePathRoot + "File1.dat", $SourcePathRoot + "File2.txt")
# , takes precedence and is evaluated first, leaving us with
#("C:\Temp\" + #("File1.data", "C:\Temp\") + "File2.txt")
The behavior of + depends on the type of the left-hand side operand - which is a string - so PowerShell will attempt string concatenation of the middle array to the first string, resulting in:
#("C:\Temp\File1.data C:\Temp\" + "File2.txt")
Which is why the result is an array consisting of 1 string with the value C:\Temp\File1.dat C:\Temp\File2.txt
Solution is simple, just split the array expression into two separate statements:
$SourcePaths = #($SourcePathRoot + "File1.dat"; $SourcePathRoot + "File2.txt")
# or
$SourcePaths = #(
$SourcePathRoot + "File1.dat"
$SourcePathRoot + "File2.txt"
)

Related

How do I remove characters like "?" from an array powershell

I am trying to validate strings of text taken from PC descriptions in Active Directory.
But I want to remove rogue characters like a single value of "??" from any text before validating any text.
I have this test code as an example. But whenever it hits the random character "??"
It throws this error:
Error:
parsing "??" - Quantifier {x,y} following nothing.
At C:\Users\#####\OneDrive\Workingscripts\testscripts\removeingfromarray.ps1:11 char:5
+ If ($charigmorematch -match $descstr)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException
When all I want to do is remove it from the array!
Any help greatly appreciated.
This is the example code I have.
##Type characters to remove in array.
$charigmorematch = #("?"; "#"; "$")
##array declare
$userdesc = #()
###Where this would be an AD description from AD.
$ADUser = "Offline - ?? - test"
###Split AD Descrip into individual strings
$userdesc = $ADUser.Split("-").Trim()
###Run through them to check for rogue characters to remove
ForEach($descstr in $userdesc)
{
###If match found try and take it out
If ($charigmorematch -match $descstr)
{
###Store match in variable.
$strmatch = ($charigmorematch -match $descstr)
###Get the index of the string
$indexstr = $userdesc.indexof($descstr)
Write=host "Match: $strmatch Index: $indexstr"
###Once found a match of a rogue character then remove from the array!
##But I haven't figured out that code yet.
###Then a command to remove the string from the array with the index number.
###In this case it's likely to be [1] to remove. But the code has to work that out.
}
}
# Sample input.
$ADUser = "Offline - ?? - test"
# Split into tokens by "-", potentially surrounded by spaces,
# and filter out tokens that contain '?', '#', or '$'.
($ADUser -split ' *- *') -notmatch '[?#$]'
The result is the following array of tokens: 'Offline', 'test'
Note that -notmatch, like all comparison operators that (also) operate on strings, acts as a filter with an array as the LHS, as is the case here (-split always returns an an array).
Based on the additional requirements you mentioned in later comments, you're probably looking for something like this (splitting by - or |, trimming of surrounding (...)):
# Sample input
$ADUser = "Notebook PC | (Win 10) | E1234567 - simple ^^ user | Location ?? not # set"
($ADUser -split ' *[-|] *') -notmatch '[?#$]' -replace '^\(|\)$'
This results in the following array of tokens: 'Notebook PC', 'Win 10', 'E1234567', 'simple ^^ user'
Note that unless your input strings have leading or trailing spaces, there is no need for calling .Trim()
As for what you tried:
$charigmorematch -match $descstr
The -match operator:
requires the input string(s) to be the LHS (left-hand side) operand.
requires a regex (regular expression) as the RHS (right-hand side) operand, to formulate a pattern that the input is matched against.
By contrast, your attempted operation:
mistakenly reversed the order of operands ($descstr, as the string in which to look for regex patterns must be the LHS).
mistakenly used an array as the comparison pattern ($charigmorematch), instead of a (single) regex (expressed as a string) that uses a character set ([...]) to specify the characters of interest.

Split a string into an Object array

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 #(...))

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.

using variable in PowerShell array element

How to I add variables data inside the string element of an array? If I do $s.Length, the output is 1 instead of 2.
$IPAddress = '192.168.1.1'
[string[]]$s = (
'https://google.com/' + $IPAddress + '/hostname',
'https://google.com/' + $IPAddress + '/DNS'
)
foreach ($element in $s) {
Write-Host $element
}
The simplest way to accomplish what you are trying (string expansion) is:
$s = "https://google.com/$IPAddress/hostname",
"https://google.com/$IPAddress/DNS"
By using double quotes it will automatically expand $IPAddress within the strings. This works best when the variable is a string, as more complex objects may not perform as expected. If you need to reference a property of an object in this manner you will need to wrap it in $(), for example "Hello $($User.Name)!" to expand the Name property of the $User object.
$s contains a single string because of the way you define the array. The concatenation operator (+) has a weaker precedence than the array construction operator (,). Because of that a statement
'foo' + $v + 'bar', 'foo' + $v + 'baz'
actually works like this:
'foo' + $v + #('bar', 'foo') + $v + 'baz'
Due to the string concatenation operation the array is mangled into a space-separated string (the separator is defined in the automatic variable $OFS), resulting in this:
'foo' + $v + 'bar foo' + $v + 'baz'
To avoid this behavior you need to either put the concatenation operations in grouping expressions:
$s = ('https://google.com/' + $IPAddress + '/hostname'),
('https://google.com/' + $IPAddress + '/DNS')
or inline the variables (requires double-quoted strings):
$s = "https://google.com/${IPAddress}/hostname",
"https://google.com/${IPAddress}/DNS"
You could also use the format operator, but that requires grouping expressions as well:
$s = ('https://google.com/{0}/hostname' -f $IPAddress),
('https://google.com/{0}/DNS' -f $IPAddress)
Side note: Casting the variable to [string[]] is optional. Using the comma operator will give you an array even without an explicit cast.
TheMadTechnician beat me to it by a few seconds, but if you prefer to construct the string expressions explicitly, wrap them in parens:
$IPAddress = '192.168.1.1'
[string[]]$s = (
('https://google.com/'+$IPAddress+'/hostname'),
('https://google.com/'+$IPAddress+'/DNS'))
foreach ($element in $s)
{
Write-Host $element
}
The parens force the expressions inside to be evaluated first.

How can I get the value from a multidimentional array in Powershell

I am trying to pull some file paths from an array and use them to test if a folder is located on serval machines.
I seem to get an error pulling the value from the 2-dimentional array and putting it in a variable.
Here is a striped down, very basic version of my script.
$PCS = "PC1","PC2","PC3"
$locations= #("System32","Windows\System32"),
("Public","Users\Public")
ForEach($PC in $PCS){
$i=0
Do
{
$fullpath = "\\" + $PC + "\C$\" + "$locations[$i][1]"
test-path $fullpath
$i++
}
While ($i -le ($locations.length-1) )
}
However when I use $fullpath to test if the folder is there, I get false, and upon further investigation, the actual value of $fullpath is:
\\PC1\C$\System.Object[] System.Object[] System.Object[] System.Object[]
How can I get the value from the array, so I can use it as a filepath location?
$fullpath = "\\" + $PC + "\C$\" + "$($locations[$i][1])"
or
$fullpath = "\\" + $PC + "\C$\" + $locations[$i][1]
As arco444 points out, your code as posted seems incomplete, but I think your issue will be fixed by above.
Explanation
When you use "$locations[$i][1]" it only interprets $locations as a variable within the string. Using . to access properties or [] to access elements is interpreted as literal characters.
So in this case, you can use the second option (don't surround it in quotes) if the result of the lookup is already a string or can be coerced into one.
In the general sense, the first option uses $(...) which is a sub-expression. The ... can be any code, including entire pipelines and function calls and such.

Resources