I have a string that I know will contain one entry from an array of strings. I'm looking for which array entry the string contains.
If I have the following variables:
$String = "This is my string"
$Array = "oh","my","goodness"
I can verify if any of the array entries exist in the string with:
$String -match $($Array -join "|")
But, this will only return true or false. I want to know which entry was matched.
-Contains seems to be going in the right direction, but that only verifies if an array contains a specific object, and I want to verify which of the array entries is contained within the string. I'm thinking something like a foreach loop would do the trick, but that seems like a fairly resource intensive workaround.
Any help is appreciated!
That's what the automatic variable $Matches is for.
See about_Regular_Expressions
$String = "This is my string"
$Array = "oh","my","goodness"
if($String -match $($Array -join "|"))
{
$Matches
}
Returns:
PS />
Name Value
---- -----
0 my
This is another way you can see the matches:
# Adding "This" for the example
$String = "This is my string"
[regex]$Array = "oh","my","goodness","This" -join '|'
$Array.Matches($String)
Returns:
PS />
Groups Success Name Captures Index Length Value
------ ------- ---- -------- ----- ------ -----
{0} True 0 {0} 0 4 This
{0} True 0 {0} 8 2 my
Related
I'm currently learning PowerShell, starting with the basics, and I've gotten to arrays. More specifically, looping arrays. I noticed that when declaring an array by itself, it's just simply written as
$myarray = 1, 2, 3, 4, 5
However, when an array is being declared with the intention of it being looped, it is written as
$myarray = #(1, 2, 3, 4, 5)
Out of curiosity, I tried running the code for looping through the array both with and without the # sign just to see if it would work and it was displayed within the string I created in the exact same way for both.
My question is what is the purpose of the # sign? I tried looking it up, but couldn't find any results.
It's an alternative syntax for declaring static arrays, but there are some key details to understanding the differences in syntax between them.
#() is the array sub-expression operator. This works similarly to the group-expression operator () or sub-expression operator $() but forces whatever is returned to be an array, even if only 0 or 1 element is returned. This can be used inline wherever an array or collection type is expected. For more information on these operators, read up on the Special Operators in the documentation for PowerShell.
The 1, 2, 3, 4 is the list-expression syntax, and can be used anywhere an array is expected. It is functionally equivalent to #(1, 2, 3, 4) but with some differences in behavior than the array subexpression operator.
#( Invoke-SomeCmdletOrExpression ) will force the returned value to be an array, even if the expression returns only 0 or 1 element.
# Array sub-expression
$myArray = #( Get-Process msedge )
Note this doesn't have to be a single cmdlet call, it can be any expression, utilizing the pipeline how you see fit. For example:
# We have an array of fruit
$fruit = 'apple', 'banana', 'apricot', 'cherry', 'a tomato ;)'
# Fruit starting with A
$fruitStartingWithA = #( $fruit | Where-Object { $_ -match '^a' } )
$fruitStartingWithA should return the following:
apple
apricot
a tomato ;)
There's another way to force an array type and I see it often mentioned on Stack Overflow as a cool trick (which it is), but with little context around its behavior.
You can use a quirk of the list-expression syntax to force an array type, but there are two key differences between this and using the array sub-expression operator. Prefixing an expression or variable with a comma , will force an array to be returned, but the behavior changes between the two. Consider the following example:
# Essentially the same as #( $someVar )
$myArray1 = , $someVar
# This behaves differently, read below
$myArray2 = , ( Invoke-SomeCmdletOrExpression )
#() or prefixing a variable with , will flatten (another word often used here is unroll) the resulting elements into a single array. But with an expression you have to use the group-expression operator if you use the comma-prefix trick. Due to how the grouped expression is interpreted you will end up with an array consisting of one element.
It will not flatten any resulting elements in this case.
Consider the Get-Process example above. If you have three msedge processes running, $myArray.Count will show a count of 3, and you can access the individual processes using the array-index accessor $myArray[$i]. But if you do the same with $myArray2 in the second list-expression example above, $myArray2.Count will return a count of 1. This is essentially now a multi-dimensional array with a single element. To get the individual processes, you would now need to do $myArray2[0].Count to get the process count, and use the array-index accessor twice to get an individual process:
$myArray2 = , ( Get-Process msedge )
$myArray2.Count # ======> 1
# I have 32 Edge processes right now
$myArray2[0].Count # ===> 32
# Get only the first msedge process
$myArray[0][0] # =======> Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
# =======> ------- ------ ----- ----- ------ -- -- -----------
# =======> 430 19 101216 138968 74.52 3500 1 msedge
This can be unclear at first because printing $myArray2 to the output stream will show the same output result as $myArray from the first example, and $myArray1 in the second example.
In short, you want to avoid using the comma-prefix trick when you want to use an expression, and instead use the array sub-expression #() operator as this is what it is intended for.
Note: There will be times when you want to define a static array of arrays but you will be using list-expression syntax anyways, so the comma-prefix becomes redundant. The only counterpoint here is if you want to create an array with an array in the first element to add more arrays to it later, but you should be using a generic List[T] or an ArrayList instead of relying on one of the concatenation operators to expand an existing array (+ or += are almost always bad ideas on non-numeric types).
Here is some more information about arrays in PowerShell, as well as the Arrays specification for PowerShell itself.
The operator you're looking for documentation on consists not only of the # but the ( and ) too - together they make up #(), also known as the array subexpression operator.
It ensures that the output from whatever pipeline or expression you wrap in it will be an array.
To understand why this is useful, we need to understand that PowerShell tends to flatten arrays! Let's explore this concept with a simple test function:
function Test-PowerShellArray {
param(
$Count = 2
)
while($count--){
Get-Random
}
}
This function is going to output a number of random numbers - $Count numbers, to be exact:
PS ~> Test-PowerShellArray -Count 5
652133605
1739917433
1209198865
367214514
1018847444
Let's see what type of output we get when we ask for 5 numbers:
PS ~> $numbers = Test-PowerShellArray -Count 5
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Alright, so the resulting output that we've stored in $numbers is of type [Object[]] - this means we have an array which fits objects of type Object (any type in .NET's type system ultimately inherit from Object, so it really just means we have an array "of stuff", it could contain anything).
We can try again with a different count and get the same result:
PS ~> $numbers = Test-PowerShellArray -Count 100
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
So far so good - we collected multiple output values from a function and ended up with an array, all is as expected.
But what happens when we only output 1 number from the function:
PS ~> $numbers = Test-PowerShellArray -Count 1
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
Say what? Now we're getting System.Int32 - which is the type of the individual integer values - PowerShell noticed that we only received 1 output value and went "Only 1? I'm not gonna wrap this in an array, you can have it as-is"
For this reason exactly, you might want to wrap output that you intend to loop over (or in other ways use that requires it to be an array):
PS ~> $numbers = Test-PowerShellArray -Count 1
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
PS ~> $numbers = #(Test-PowerShellArray -Count 1) # #(...) saves the day
PS ~> $numbers.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
I have one array of hashtables like the one below:
$hashtable1 = #{}
$hashtable1.name = "aaa"
$hashtable1.surname =#()
$hashtable1.surname += "bbb"
$hashtable2 = #{}
$hashtable2.name = "aaa"
$hashtable2.surname =#()
$hashtable2.surname += "ccc"
$hashtable3 = #{}
$hashtable3.name = "bbb"
$hashtable3.surname = #()
$hashtable3.surname += "xxx"
$A = #($hashtable1; $hashtable2; $hashtable3)
I need to iterate though the array and I need to find out duplicates based on hashtable[].name
Then I need to group those hashtable.surname to hashtable[].surname so that the result will be an array of hashtables that will group all for name all the surnames:
$hashtable1.name = "aaa"
$hashtable1.surname = ("bbb","ccc")
$hashtable3.name = "bbb"
$hashtable3.surname = ("xxx")
I was looking into iterating to empty array
+
I have found this link:
powershell compare 2 arrays output if match
but I am not sure on how to reach into the elements of the hashtable.
My options:
I was wondering if -contain can do it.
I have read about compare-object but I am not sure it can be done like that.
(It looks a bit scary in the moment)
I am on PS5.
Thanks for your help,
Aster
You can group your array items by the names using a scriptblock like so.
Once grouped, you can easily build your output to do what you seek.
#In PS 7.0+ you can use Name directly but earlier version requires the use of the scriptblock when dealing with arrays of hashtables.
$Output = $A | Group-Object -Property {$_.Name} | % {
[PSCustomObject]#{
Name = $_.Name
Surname = $_.Group.Surname | Sort-Object -Unique
}
}
Here is the output variable content.
Name Surname
---- -------
aaa {bbb, ccc}
bbb xxx
Note
Improvements have been made in PS 7.0 that allows you to use simply the property name (eg: Name) in Group-Object for arrays of hashtables, just like you would do for any other arrays type. For earlier version though, these particular arrays must be accessed by passing the property in a scriptblock, like so: {$_.Name}
References
MSDN - Group_Object
SS64 - Group Object
Dr Scripto - Use a Script block to create custom groupings in PowerShell
Updated: I am trying to take a user input values(string) into a 2d array. For testing purpose, I assigned a string to variable.
Goal: Later I want my program to look at each row for column 1 of my array and do something like install printer software if it says Printer and don't install software if noPrinter. I can do this for 1d array but I want anywhere from 1 to 10 rows. That is why 2d array thought would be complexity.
I want to learn to do this and not ask you to write all my code.
Thank you.
Here is my code.
#set user input to a string variable
$UserInputVar = "computer1,noPrinter,Computer2,Printer,Computer3,Printer"
#this is my 2d array; Declare. i want to be able to use anywhere from 1 to 10 rows and always 2 columns
$my2d=[object[]]::(,2)
$my2d.clear #for testing purpose
#assign values of sting to 2 dimensional array
#split the string at each comma(,)
$my2d = $UserInputVar.Split(",")
#show me the values in this array
"`n #my2d[0][0] "
$my2d[0][0] #expect value 'computer1'
$my2d[0][1] #expect value 'noPrinter'
$my2d[0][2]
$my2d[0][3]
"`n #my2d[1][0] "
$my2d[1][0] #expect value 'computer2'
$my2d[1][1] #expect value 'Printer'
$my2d[1][2]
$my2d[1][3]
"`n #my2d[2][0] "
$my2d[2][0] #expect value 'computer3'
$my2d[2][1] #expect value 'Printer'
$my2d[2][2]
$my2d[2][3]
"`n #array with no 2nd index"
$my2d[0]
$my2d[1]
$my2d[2]
I'm not sure if you want an object as output, but if so, this is a simple way to do it:
$z = 0
$UserInputVar = "This,is,my,strg,to,test".Split(',')
for($z=0; $z -lt $UserInputVar.Count; $z=$z+2)
{
[pscustomobject]#{
1 = $UserInputVar[$z]
2 = $UserInputVar[$z+1]
}
}
Output
1 2
- -
This is
my strg
to test
Edit
Looking again at the question, this may be what you're looking for:
$z = 0
$UserInputVar = "This,is,my,strg,to,test".Split(',')
$result = [System.Collections.Generic.List[object]]::new()
for($z=0; $z -lt $UserInputVar.Count; $z=$z+2)
{
$result.Add(#($UserInputVar[$z],$UserInputVar[$z+1]))
}
Some Testing
PS /> $result[0]
This
is
PS /> $result[0][0]
This
PS /> $result[0][1]
is
PS /> $result[0][2]
PS /> $result[1][0]
my
You may also use regex matching to return your groups of 2 elements. Then you can split each of those returned pairs into a single array (because of the unary operator ,). All the returned single arrays are then wrapped automatically into Object[] array.
$UserInputVar = "computer1,noPrinter,Computer2,Printer,Computer3,Printer"
$my2d = [regex]::Matches($UserInputVar,'[^,]+(,[^,]+|$)') |
Foreach-Object {,($_.Value -split ',')}
Given an array of key-value pairs (for example read in through ConvertFrom-StringData), is there a streamlined way of turning this into a Hashtable or similar to allow quick lookup? I.e. a way not requiring me to loop through the array and manually build up the hashtable myself.
Example data
10.0.0.1=alice.example.com
10.0.0.2=bob.example.com
Example usage
$names = gc .\data.txt | ConvertFrom-StringData
// $names is now Object[]
$map = ?
// $map should now be Hashtable or equivalent
echo $map['10.0.0.2']
// Output should be bob.example.com
Basically what I'm looking for is a, preferably, built-in file-to-hashtable function. Or an array-to-hashtable function.
Note: As #mjolnior explained, I actually got hash tables, but an array of single value ones. So this was fixed by reading the file -raw and hence didn't require any array to hashtable conversion. Updated the question title to match that.
Convertfrom-Stringdata does create a hash table.
You need to give it the key-value pairs as a single multi-line string (not a string array)
$map = Get-Content -raw .\data.txt | ConvertFrom-StringData
$map['10.0.0.2']
bob.example.com
When you use Get-Content without the -Raw switch, you're giving ConvertFrom-StringData an array of single-line strings, and it's giving you back an array of single-element hash tables:
$map = Get-Content .\data.txt | ConvertFrom-StringData
$map.gettype()
$Map[0].GetType()
$map[0]
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
True True Hashtable System.Object
Key : 10.0.0.1
Value : alice.example.com
Name : 10.0.0.1
I usually do the following to create a hashtable from a list of key/value pairs:
$hash = #{}
Get-Content 'C:\input.txt' | Where-Object {
$_ -like '*=*'
} | ForEach-Object {
$key, $value = $_ -split '\s*=\s*', 2
$hash[$key] = $value
}
This might not be what you're looking for, but it avoids converting the whole thing into a hash.
$content = #("10.0.0.1=alice.example.com","10.0.0.2=bob.example.com");
$content | ForEach-Object {
$keyval = $_.split("=");
if ($keyval[0] -eq "10.0.0.2") {
$keyval[1]
}
}
The output will be every value on the right side of the = where the left side matches that IP.
I can intuitively figure out most languages, but apparently not Powershell.
I want to create an array of arrays (this will contain disk directories and a count so later I can verify we have at least that many files).
From that array of arrays, I want to pull out a single array of just the directory names so I can pass it to Get-ChildItem.
$DirInfo = #('d:\Work',2),
('d:\Temp',3)
$DirNameArray=#() #declare empty array
foreach ($item in $DirInfo)
{
$DirNameArray += , $item[0] #tried with and without the comma here
Write-Host 'Loop1 ' $item[0]
}
write-host $DirNameArray.count
#Let's Verify what we got so we know how many items we have in our array
Write-Host "Verify with a loop"
foreach ($dir in $DirNameArray)
{
Write-Host 'Loop2:' $dir
}
Write-Host "Verify the other way"
Write-Host $DirNameArray
Actual Results:
Loop1 d:\Work
Loop1 d:\Temp
1
Verify with a loop
Loop2: d:\Workd:\Temp
Verify the other way
d:\Workd:\Temp
What I don't understand is why Loop2 didn't execute twice.
It looks like the =+ is just stringing together the values instead of adding a new item to my array called $DirNameArray.
I'm still utterly baffled, one file I created does this and gives me the expected results:
$a = "one","two"
Write-Host $a.count
$a += "three"
Write-Host $a.count
Results:
2
3
So if the above worked, why didn't my code work?
A second file I created does this - and I don't understand the results. I even made the variable name different so I wouldn't be dealing with any prior definition or values of that variable:
$DirNameArray5="abc","def"
write-host $DirNameArray5.count
$DirNameArray5 += "xyz"
write-host $DirNameArray5.count
$DirNameArray5 += #("opq")
write-host $DirNameArray5.count
Results:
1
1
1
$DirNameArray7="abc","def"
write-host $DirNameArray7.count
$DirNameArray7 += "xyz"
write-host $DirNameArray7.count
$DirNameArray7 += #("opq")
write-host $DirNameArray7.count
Results:
2
3
4
So apparently, if you once define a variable as a string, it's hard to get Powershell to redefine it as an array.
But I still have my original question. How to define an empty array so I can add to it in a loop using the += operator.
$DirNameArray=#()
I finally used the .GetType() method to see what my variables actually were:
PS C:\Users\nwalters> $DirNameArray5.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
PS C:\Users\nwalters> $DirNameArray7.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Bottom line - this is what I want to do - in as few lines of code as possible with no loop:
[string]$x=#() # declare empty array
Write-Host $x.GetType()
$x += "one"
$x += "two"
Write-Host $x.count
Write-Host $x
Actual Results
System.String
1
onetwo
Desired Results:
object[] or string[]???
2
one two
Powershell will not let you create an empty array, or let you empty an array down to nothing (with one exception). There are two ways I have discovered to work around this issue:
Method 1: Use -OutVariable to a new variable name to create an array with your input
Example: gci C:\TestDir1 -OutVariable test
Using the .GetType() method returns:
$test.GetType()
Directory: C:\TestDir1
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/20/2020 11:36 AM 8015 Test1.xls
-a---- 6/26/2020 12:59 PM 0 test2.txt
Module : CommonLanguageRuntimeLibrary
Assembly : mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
TypeHandle : System.RuntimeTypeHandle
DeclaringMethod :
BaseType : System.Object
UnderlyingSystemType : System.Collections.ArrayList
FullName : System.Collections.ArrayList
This will create a new variable named $test that is an array (since there are multiple items in it). $test.gettype() shows the object as an array.
Method 2: Explicitly declare an array with two dummy objects and then remove both dummy objects.
[System.Collections.ArrayList]$array = "value1", "value2"
$array.remove("value1")
$array.remove("value2")
Using the gettype method will still show $test is an array even though it is empty:
> $test.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True ArrayList System.Object
The array will now be completely empty and can be used to store any new input. This doesn't work unless you explicitly name the variable type like I did in my example (not sure why). Example shown below:
$test = "value1", "value2"
$test.Remove("value1")
Exception calling "Remove" with "1" argument(s): "Collection was of a fixed size."
At line:3 char:1
+ $test.Remove("value1")
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NotSupportedException
P.S. I know this is an old thread, but it is unanswered and I just came upon this issue myself so I am answering it for anyone else that searches this issue.
The following code is tested on two different computers, both with PowerShell 2.0. Can you try this and post results.
# declare an empty array
$var = #()
write-host "var.count = '$($var.count)' var.type ='$($var.GetType())' var.type.BaseType = '$($var.GetType().BaseType)'"
# add a single item
$var += "single item"
write-host "var.count = '$($var.count)' var.type ='$($var.GetType())' var.type.BaseType = '$($var.GetType().BaseType)'"
# add an array
$var += , #("array 1 - item 1","array 1 - item 2")
write-host "var.count = '$($var.count)' var.type ='$($var.GetType())' var.type.BaseType = '$($var.GetType().BaseType)'"
# display the 'single item'
write-host "single item = '$($var[0])'"
# display first element of array item
write-host "first element of array item = '$($var[1][0])'"
gives me
var.count = '0' var.type ='System.Object[]' var.type.BaseType = 'array'
var.count = '1' var.type ='System.Object[]' var.type.BaseType = 'array'
var.count = '2' var.type ='System.Object[]' var.type.BaseType = 'array'
single item = 'single item'
first element of array item = 'array 1 - item 1'