How do I retrieve a HashTable key from a value? - powershell

I have a PowerShell HashTable that contains a set of key-value pairs (naturally). All of the HashTable values are unique.
I would like to retrieve a HashTable key, based on a value that I specify, using PowerShell.

Another options:
to iterate over the HashTable keys and find a key that contains the
value:
$HashTable.Keys |? { $HashTable[$_] -eq $Val }
to iterate using GetEnumerator() function:
$HashTable.GetEnumerator() | ?{ $_.Value -eq $Val } | %{ $_.Key }

You can use PowerShell 4.0's Where method syntax to achieve this. The Where method accepts a PowerShell ScriptBlock to find objects matching the specified criteria. We can iterate over the HashTable keys and find a key that contains the desired value.
In case you do have a scenario where you have duplicate HashTable values, you can optionally specify a second parameter, of type WhereOperatorSelectionMode, that specifies which objects should be returned by the call to the Where method. By specifying First for the second method parameter, we can ensure that only a single HashTable key is ever returned.
All of the supported values for the second parameter are as follows:
Default
First
Last
SkipUntil
Until
Split
$HashTable = #{
1 = 10;
2 = 20;
3 = 30;
}
$Val = 30;
$HashTable.Keys.Where({ $HashTable[$PSItem] -eq $Val; }, [System.Management.Automation.WhereOperatorSelectionMode]::First);

Related

Powershell passing multiple parameters from one script to another [duplicate]

I've seen the # symbol used in PowerShell to initialise arrays.
What exactly does the # symbol denote and where can I read more about it?
In PowerShell V2, # is also the Splat operator.
PS> # First use it to create a hashtable of parameters:
PS> $params = #{path = "c:\temp"; Recurse= $true}
PS> # Then use it to SPLAT the parameters - which is to say to expand a hash table
PS> # into a set of command line parameters.
PS> dir #params
PS> # That was the equivalent of:
PS> dir -Path c:\temp -Recurse:$true
PowerShell will actually treat any comma-separated list as an array:
"server1","server2"
So the # is optional in those cases. However, for associative arrays, the # is required:
#{"Key"="Value";"Key2"="Value2"}
Officially, # is the "array operator." You can read more about it in the documentation that installed along with PowerShell, or in a book like "Windows PowerShell: TFM," which I co-authored.
While the above responses provide most of the answer it is useful--even this late to the question--to provide the full answer, to wit:
Array sub-expression (see about_arrays)
Forces the value to be an array, even if a singleton or a null, e.g. $a = #(ps | where name -like 'foo')
Hash initializer (see about_hash_tables)
Initializes a hash table with key-value pairs, e.g.
$HashArguments = #{ Path = "test.txt"; Destination = "test2.txt"; WhatIf = $true }
Splatting (see about_splatting)
Let's you invoke a cmdlet with parameters from an array or a hash-table rather than the more customary individually enumerated parameters, e.g. using the hash table just above, Copy-Item #HashArguments
Here strings (see about_quoting_rules)
Let's you create strings with easily embedded quotes, typically used for multi-line strings, e.g.:
$data = #"
line one
line two
something "quoted" here
"#
Because this type of question (what does 'x' notation mean in PowerShell?) is so common here on StackOverflow as well as in many reader comments, I put together a lexicon of PowerShell punctuation, just published on Simple-Talk.com. Read all about # as well as % and # and $_ and ? and more at The Complete Guide to PowerShell Punctuation. Attached to the article is this wallchart that gives you everything on a single sheet:
You can also wrap the output of a cmdlet (or pipeline) in #() to ensure that what you get back is an array rather than a single item.
For instance, dir usually returns a list, but depending on the options, it might return a single object. If you are planning on iterating through the results with a foreach-object, you need to make sure you get a list back. Here's a contrived example:
$results = #( dir c:\autoexec.bat)
One more thing... an empty array (like to initialize a variable) is denoted #().
The Splatting Operator
To create an array, we create a variable and assign the array. Arrays are noted by the "#" symbol. Let's take the discussion above and use an array to connect to multiple remote computers:
$strComputers = #("Server1", "Server2", "Server3")<enter>
They are used for arrays and hashes.
PowerShell Tutorial 7: Accumulate, Recall, and Modify Data
Array Literals In PowerShell
I hope this helps to understand it a bit better.
You can store "values" within a key and return that value to do something.
In this case I have just provided #{a="";b="";c="";} and if not in the options i.e "keys" (a, b or c) then don't return a value
$array = #{
a = "test1";
b = "test2";
c = "test3"
}
foreach($elem in $array.GetEnumerator()){
if ($elem.key -eq "a"){
$key = $elem.key
$value = $elem.value
}
elseif ($elem.key -eq "b"){
$key = $elem.key
$value = $elem.value
}
elseif ($elem.key -eq "c"){
$key = $elem.key
$value = $elem.value
}
else{
Write-Host "No other value"
}
Write-Host "Key: " $key "Value: " $value
}

Hashtable - Combining unknown number of hashtables

I'm trying to programmatically combine an unknown number of hashtables into a larger hashtable. Each individual table will have the same keys. I tried just appending it, but it throws an error about duplicate keys.
ForEach ($Thing in $Things){
$ht1 = New-Object psobject #{
Path = $Thing.path
Name = $Thing.name
}
$ht2 += $ht1
}
That throws the error
Item has already been added. Key in dictionary: 'Path' Key being added: 'Path'
The end result would be that later I can say
ForEach ($Item in $ht2){
write-host $Item.path
write-host $Item.name
}
Converting my comment to an answer.
What you probably want to create is an array of hashtables. Each item in the array can have its own value for each key. This structure can be used in the way you indicate in your query at the end of your post.
Try this:
$things = gci $home
$ht2 = #() # create empty array
ForEach ($Thing in $Things){
$ht1 = New-Object psobject #{
Path = $Thing.PSpath
Name = $Thing.name
}
$ht2 += $ht1
}
$ht2
Note that I changed .path to .PSpath in order to make the example work. Note that $ht2 gets initialized to an empty array before looping.
Adding an element to a hash table indeed fails if there already is an element with same key. To overcome that, first see if the element exists. Like so,
if(-not $ht2.ContainsKey($thing.name)){
$ht2.Add($thing.name, $thing.value)
}
It is also possible to use item property [] for the check, since it returns $null for non-existing element. Like so,
if($ht2[$thing.name] -eq $null){
$ht2.Add($thing.name, $thing.value)
}
If this really is about merging Hashtables as the title says, then basically you have two options to add the entries from the second hashtable into the first.
1. Use the static method Add() (first item 'wins')
This has already been explained in vonPryz's answer, but in short:
Adding an entry in a hashtable with a key that already exists in the hash, the Add() method will throw an exception, because all keys in a hash must be unique.
To overcome that, you need to check if an entry with that key exists and if so, do not add the entry from the second hash.
foreach ($key in $secondHash.Keys) {
if (!$firstHash.Contains($key)) {
# only add the entry if the key (name) did not already exist
$firstHash.Add($key, $secondHash[$key])
}
}
This way, all entries already in the first hashtable will NOT get overwritten and duplicate entries from the second hash are discarded.
2. Overwriting/adding regardless of the existance (last item 'wins')
You can also opt to 'merge' the entries without the need for checking like this:
foreach ($key in $secondHash.Keys) {
# always add the entry even if the key (name) already exist
$firstHash[$key] = $secondHash[$key]
}
This way, if an entry already existed in the first hash, its value will be overwritten with the value from the second hash.
If the entry did not already exist, it is simply added to the first hashtable.
But, what if you want to merge without skipping or overwriting existing values?
In that case, you need to come up with some method of creating a unique key for the entry to add.
Something like this:
foreach ($key in $secondHash.Keys) {
if ($firstHash.Contains($key)) {
# ouch, the key already exists..
# now, we only want to add this if the value differs (no point otherwise)
if ($firstHash[$key] -ne $secondHash[$key]) {
# add the entry, but create a new unique key to store it under first
# this example just adds a number between brackets to the key
$num = 1
do {
$newKey = '{0}({1})' -f $key, $num++
} until (!$firstHash.Contains($newKey))
# we now have a unique new key, so add it
$firstHash[$newKey] = $secondHash[$key]
}
}
else {
# no worries, the key is unique
$firstHash[$key] = $secondHash[$key]
}
}
Turns out what I needed for my results was an array of hashtables, not a hashtable of hashtables, as pointed out by #WalterMitty. My final code was:
#varibale name ht2 kept for clarity in how it relates to original question
$ht2 = #()
ForEach ($Thing in $Things){
$ht1 = New-Object psobject #{
Path = $Thing.path
Name = $Thing.name
}
$ht2 += $ht1
}

Casting Object to String Array Powershell

I want to create an array of strings instead of a variable object so that I can use the "contains" keyword on each index of the array.
$myArray = Get-ADDomain
The above creates an object, which is not what I want. I also tried
[string[]] $myArray = Get-ADDomain
But after that, $myArray only contains one string and it is the first non-empty property of Get-ADDomain, in my case "ComputersContainer". What should I do to receive an array of strings where each string is a different property, such as
$myArray[0] = "AllowedDNSSuffixes = {}"
PowerShell will always return objects by design of course, and specifying that [string[]], does not really change that.
For what you are trying to use, you have to force the array creation. The below is just one way, but I am sure others will have more elegant ways of doing this as well. Though I am curious why one would want to do this, this way. But, hey, that's just me.
# Create an empty array
$DomainData = #()
# Get all the data points for the utilized cmdlet, split on a common delimiter for the array
[string[]]$DomainData = (Get-ADDomain | Select *) -split ';'
# Display the array count
$DomainData.Count
34
# validate getting a value from the array by using an index number
$Item = $DomainData[17]
NetBIOSName=CONTOSO
[array]::IndexOf($DomainData, $Item)
17
# Use that element number to validate the use of the contains comparison operator
0..($DomainData.Count - 1) | %{ If($DomainData[$_] -contains $item){"Index key is $_ contains a value of $Item"} }
Index key is 17 contains a value of NetBIOSName=CONTOSO
# Use the previous with a partial string for a comparison, -contains cannot be used, like or match has to be used
# From the documentation:
# -Contains
# Description: Containment operator. Tells whether a collection of reference values includes a single test value.
$Item = '*domain*'
0..($DomainData.Count - 1) | %{ If($DomainData[$_] -like $item){"Index key is $_ like a value of $Item"} }
Index key is 1 like a value of *domain*
Index key is 6 like a value of *domain*
Index key is 7 like a value of *domain*
Index key is 8 like a value of *domain*
Index key is 18 like a value of *domain*
Index key is 20 like a value of *domain*
You cannot cast a PSObject directly to a string array like that.
However, this can be accomplished rather easily.
To get an array of string from the object
$myArray = Get-ADDomain
# You can use a standard array #() but these tends to be slower for bigger amount of data
$outArray = New-Object -TypeName System.Collections.Generic.List[String]
#To add just the value
$myArray.psobject.properties | Foreach { $outArray.Add($_.Value) }
# To add Name = {Value} instead
$myArray.psobject.properties | Foreach { $outArray.Add("$($_.Name) = {$($_.Value)}") }
Using an hasthable instead:
$myArray = Get-ADDomain
$hashtable = #{}
$myArray.psobject.properties | Foreach { $hashtable[$_.Name] = $_.Value }
# If you need to do something with the key
Foreach ($key in $hashtable.Keys) {
$Value = $hashtable[$key]
if ($value -like '*prod*') {
Write-Host $key
}
}

What's the proper way to iteratively grab a value from an array when the key is a string?

I'm trying to export some lists from Sharepoint into a CSV file. My goal is to make a single function that is flexible enough to take a List name, identifier for the CSV file, and a list of FieldValues to export, and then produce a CSV file. Here's what I have so far:
function getTableData($_ctx, [string]$_colName)
{
$list = $_ctx.Web.Lists.GetByTitle("$_colName")
$camlQuery = [Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery(100)
$colItems = $list.GetItems($camlQuery)
$_ctx.Load($colItems)
$_ctx.ExecuteQuery();
return $colItems
}
# More will go into this array, but for now a single entry is sufficient for testing purposes
$mstLists = #("GroupMst", "Groups", #("Title", "GroupCode"))
$cols = #()
foreach($col in $mstLists[0][2])
{
$cols += #{Name=$col;expression={$_[$col];}}
}
$cols
# Grab all items from a list
getListData $ctx $mstLists[0][0] |
%{ select-object -input $_ -prop $cols } |
Export-Csv -Path ($export_path + '\' + $current_date + '_' + $mstLists[0][1] + '.csv') -Encoding UTF8 -NoTypeInformation
The problem I'm having is in the loop that populates $cols. Basically, each item needs to look like #{Name="Title";expression={$_["Title"];}} in order for select-object in the ForEach to grab the proper fields from the List. Unfortunately $cols ends up being looking like this:
Name Value
---- -----
expression $_[$col];
Name Title
expression $_[$col];
Name GroupCode
Which (somehow) produces a CSV file that looks like this:
"Title","GroupCode"
"LA","LA"
"NY","NY"
"TK","TK"
When the output needs to look like this:
"Title","GroupCode"
"Los Angeles","LA"
"New York","NY"
"Tokyo","TK"
I know the field names are correct - if I hardcode them in like so...
# Grab all items from a list
getListData $ctx $mstLists[0][0] |
%{ select-object -input $_ -prop `
#{Name="Title";expression={$_["Title"];}}, `
#{Name='GroupCode';expression={$_["GroupCode"];}}; } |
Export-Csv -Path ($export_path + '\' + $current_date + '_' + $mstLists[0][1] + '.csv') -Encoding UTF8 -NoTypeInformation
...then I get the desired CSV output. I just can't figure out how to get $_[$col] to instead return $_["Title"]
Not sure what the $mstLists[0][2] meant to refer to, but the following code seems to give what you are after...
$mstLists = #("GroupMst", "Groups", #("Title", "GroupCode"))
$cols = #()
foreach($col in $mstLists[2])
{
$cols += #{Name=$col; Expression = [scriptblock]::Create('$_["{0}"]' -f $col)}
}
$cols
which gives...
Name Value
---- -----
Name Title
Expression $_["Title"]
Name GroupCode
Expression $_["GroupCode"]
In your response to andyb in the comments, you say that each item of the array will follow the format
#("ListName", "CSVFileID", #("Field1", "Field2", "Etc..."))
and that $mstLists[0][2] "refers to the list of fields in the first item in the array."
The problem is that it doesn't refer to the list of fields in the first item of the array, because the first item of the array isn't a list of anything, it's the string GroupMst. When you index into a string, you get the character indicated by the index. Since $mstLists[0] is a string, $mstLists[0][2] returns the third character of that string, which is o.
I suppose you were expecting that the # operator would make the array in the parentheses a single item, which becomes the first element of $mstLists? It doesn't. All the # does is ensure that the expression in the parentheses is evaluated as an array rather than a scalar. So, with $a = ('string'), $a is a string, whereas with $a = #('string'), $a is an array with a single string element.
However, since ("GroupMst", "Groups", #("Title", "GroupCode")) evaluates to an array anyway, putting an # in front of it is redundant. Either way you're still assigning a literal array to the variable. $mstLists is an array of three elements:
Element 0 is the string GroupMst
Element 1 is the string Groups
Element 2 is an array of the strings Title and GroupCode
What you want to do is use , as a unary operator:
$mstLists = , ("GroupMst", "Groups", #("Title", "GroupCode"))
Now $mstLists is an array of a single item whose value is the array described in the bulleted list above, and $mstLists[0][2] evaluates to an array of the strings Title and GroupCode, as you were expecting.
Note that , works as a unary or binary operator that returns an array of the operands. To return a single-element array, you use it as a unary operator in front of that element. If you have multiple literal arrays that you want to assign to mstLists, you only need commas between them, not the one in front:
$mstLists = ("ListName", "CSVFileID", #("Field1", "Field2", "Etc...")), ("ListName2", "CSVFileID2", #("Field1", "Field2", "Etc..."))
That addresses the main problem. That still won't quite give you what you want, because $col won't get interpolated in the Expression scriptblock, so Expression will always be literally $_[$col]. However, in order to figure out how to do what you actually want to do, it would be helpful to see a sample of the contents of $ctx.

PowerShell: How can I traverse through registry values named "SQLArg1", "SQLArg2" etc.?

I am working on a PowerShell function that is supposed to check if two values in the registry named "SQLArg4" and "SQLArg5" (or two other numbers) are set to a certain value content and set them if not.
The problem is I cannot enumerate the values to traverse through them to compare all of them and then add my two value contents if they are not present.
I tried creating a string out of "SQLArg" and a $i index but PowerShell would not allow me to use that string as a field of a variable.
Any ideas?
It sounds like you only need to work with one registry key (no recursion). Try this code. It will get all the values under $key that start with "SQLArgs" and store them in $values. It loops through a number sequence and tests for the existence of the key values named SQLArgs#. When it finds one it will set the key value data.
$key = "HKCU:\Andy"
$values = Get-ItemProperty -Path $key -Name SQLArg*
1..20 | % {
if ($values."SQLArg$_" -ne $null) {
Set-ItemProperty -Path $key -Name "SQLArg$_" -Value "Powershell Rocks"
}
}