Replace values in hashtable in PowerShell - powershell

I have a hashtable in PowerShell that looks like this:
$table = #{
1 = 3;
2 = 3;
5 = 6;
10 = 12;
30 = 3
}
I need to replace all "3" values with "4".
Is there a nice and clean way to do this without iterating over each pair and writing each one to a new hashtable?
Could the action with the same data be done easier if I'd use some other .NET collection class?
This throws exception that "Collection was modified":
$table.GetEnumerator() | ? {$_.Value -eq 3} | % { $table[$_.Key]=4 }
This adds another "Values" member to the object and breaks it:
$table.Values = $table.Values -replace 3,4

You can't modify the table while iterating over it, so do the iteration first and then do the updates. Just split your pipeline in two:
PS>$change = $table.GetEnumerator() | ? {$_.Value -eq 3}
PS>$change | % { $table[$_.Key]=4 }
PS>$table
Name Value
---- -----
30 4
10 12
5 6
2 4
1 4

The above answer didn't work for me and I couldn't fit this as a comment. The above single-lined answer didn't do anything. I am trying to change a single value to "Off" based on my hashtable.Key aka Name. Notice where I wrote $(backtick) is supposed to be a literal backtick, but it was messing up the code block. Here is my hashtable that is pulled from .\BeepVariables.txt.
Name Value
---- ----
$varAwareCaseSound "On"
$varEmailSound "On"
function SetHash2([string]$keyword, [string]$value){
$hash = #{}
$hash = (get-content .\BeepVariables.txt).replace(";"," $(backtick) n") | ConvertFrom-StringData
#($hash.GetEnumerator()) | ?{$_.key -like "*$keyword*"} | %{$hash[$_.value]=$value}
$hash
}
SetHash2 "aware" '"Off"'

Related

PowerShell script to sort an array ascending order and return last value

I am having below PowerShell script which does not result in the sorting order I want.
$string = #("Project-a1-1", "Project-a1-10", "Project-a1-2", "Project-a1-5", "Project-a1-6", "Project-a1-8")
$myobjecttosort=#()
$string | ForEach{
$myobjecttosort+=New-Object PSObject -Property #{
'String'=$_
'Numeric'=[int]([regex]::Match($_,'\d+')).Value
}
}
$myobjecttosort | Sort-Object Numeric | Select Numeric,String | Format-Table -AutoSize
The output of the above script:
Numeric String
1 Project-a1-5
1 Project-a1-6
1 Project-a1-8
1 Project-a1-1
1 Project-a1-10
1 Project-a1-2
Required Output
1 Project-a1-1
2 Project-a1-2
3 Project-a1-5
4 Project-a1-6
5 Project-a1-8
6 Project-a1-10
Also, I want always output to be returned as the last value so here output would be Project-a1-10
Sort-Object accepts a script block allowing for a more robust sort. With that said, just like any other object in the pipeline, the objects are accessible via $PSItem, or $_. So, a quick way to go about this is splitting the string at the - selecting just the ending numerical digits, then casting [int] to the result to sort by.
$string = "Project-a1-1", "Project-a1-10", "Project-a1-2", "Project-a1-5", "Project-a1-6", "Project-a1-8"
$string |
Sort-Object -Property { [int]($_ -replace '^.*?(?=\d+$)') } |
% { $i = 1 } {
'{0} {1}' -f $i++, $_
}
The above yields:
1 Project-a1-1
2 Project-a1-2
3 Project-a1-5
4 Project-a1-6
5 Project-a1-8
6 Project-a1-10
Passing the sorted items to % (alias to Foreach-Object), we can then format a new string giving it an index # to each string starting at 1.

Adding Header to the Variable

This is probably a dumb question but I cant seem to figure it out. How do I add a header to already existing variable? I have a variable with bunch of strings in it and I am trying to make it so it has a header which will simplify the script later on. Just as an example
$test = 1,2,3,4,5,6
Which comes out to be:
PS C:\windows\system32> $test
1
2
3
4
5
6
Where as what I want it to do is:
PS C:\windows\system32> $test
Numbers
--------
1
2
3
4
5
6
Additionally when implementing for each loop is it possible to add a blank header like to existing variable (from which foreach loop is running) and fill it automatically? for example going from original variable:
Letters Value
------- -----
a 10
b 15
c 23
d 25
To after for each loop:
Letters Value Numbers
------- ----- ------
a 10 1
b 15 2
c 23 3
d 25 4
This is a super generic example but basically i have one object with headers and when using a function someone made I am trying to populate the table with output of that function, the issue is that its returning stuff with no header and just returns the output only so I cant even make a hash table.
Thanks in advance.
In your example, your variable is a list of integers.
That's why there's no header.
If your variable were something else, like, a custom object, it would be displayed with headers.
To make your example a list of custom objects:
$test = 1..6
$test | Foreach-Object { [PSCustomObject]#{Number=$_} }
You can save this back to a variable:
$test = 1..6
$testObjects = $test | Foreach-Object { [PSCustomObject]#{Number=$_} }
If an object has four or fewer properties, it will be displayed as a table.
So you could also, say, make an object with two properties and still get headers.
$test = 1..6
$test | Foreach-Object { [PSCustomObject]#{Number=$_;NumberTimesTwo = $_ * 2} }
If you want to control how any object displays in PowerShell, you'll want to learn about writing formatters. There's a module I make called EZOut that makes these a lot easier to work with.
To offer an alternative to Start-Automating's helpful answer:
You can use Select-Object with calculated properties:
To turn a list of numbers into objects ([pscustomobject] instances) with a .Number property, whose display formatting defaults to the tabular display you're looking for:
$objects =
1,2,3,4,5,6 | Select-Object #{ Name='Number'; Expression={ $_ } }
Outputting $objects yields:
Number
------
1
2
3
4
5
6
You can use the same technique for adding additional properties to (copies of) existing objects, filling them at the same time (builds on the $objects variable filled above):
# Values for the new property.
$nums = 6..1 # same as: 6, 5, 4, 3, 2, 1
$i = #{ val = 0 } # Helper hashtable for indexing into $nums
$objects | Select-Object *, #{ Name='NewProperty'; Expression={ $nums[$i.val++] } }
Output:
Numbers NewProperty
------- -----------
1 6
2 5
3 4
4 3
5 2
6 1
If you want to add a property with $null values first and fill them later, use:
$objects | Select-Object *, NewProperty

Powershell Get Hashtable Values

I've got this Hashtable with this values:
Name Value
---- -----
Bayas_palm_stem_A0311.jpg 1
Bayas_palm_stem_A0312.jpg 2
Bukit_Bangkong_area.tiff 1
BY_and_siblings_A0259.jpg 5
Cassava_camp_A0275.jpg 1
Children_A0115.jpg 6
cip_barau_kubak.jpg 1
How can i get only the Name and Value where Value is greater than 1 ?
I'm tryng with this code, but i'm doing something wrong!!!
$RT | Group-Object Name , Value | Where-Object {$RT.Values -gt 1}
Thanks a lot for any help.
Use $hashtable.GetEnumerator() to enumerate the individual name-value pairs in the hashtable:
$RT.GetEnumerator() |Where-Object {$_.Value -gt 1}
Beware that if you assign the resulting pairs to a variable, it's not longer a hashtable - it's just an array of individual name-value pairs.
To create a new hashtable with only the name-value pairs that filter through, do:
$RTFiltered = #{}
$RT.GetEnumerator() |Where-Object {$_.Value -gt 1} |ForEach-Object {$RTFiltered.Add($_.Name, $_.Value)}
Like this:
$RT.getenumerator() | Where-Object {$_.Value -gt 1}
Assuming you have a Hashtable defined like this.
$rt = #{
"Bayas_palm_stem_A0311.jpg " = 1
"Bayas_palm_stem_A0312.jpg " = 2
"Bukit_Bangkong_area.tiff " = 1
"BY_and_siblings_A0259.jpg " = 5
"Cassava_camp_A0275.jpg " = 1
"Children_A0115.jpg " = 6
"cip_barau_kubak.jpg " = 1
}
You can call GetEnumerator() which allows you to iterate through the Hashtable.
Once you've got an enumeration of the members, then your normal PowerShell value comparisons will work.
You can get values greater than 1 like this:
$rt.GetEnumerator() | ? Value -gt 1
Name Value
---- -----
BY_and_siblings_A0259.jpg 5
Children_A0115.jpg 6
Bayas_palm_stem_A0312.jpg 2

How to get Count of Values from a Hashtable per key in powershell

Currently I am able to get the list of values from a hashtable with the below cmdlet, but I would like get a count of values per key. Please lend me some advice, if this can be achieved using GetEnumerator method
$keys.GetEnumerator() | % {
[PSCustomObject]#{
key = $_.Value
}
}
My Hashtable $keys:
Name Value
---- -----
9 {G637A146}
-3 {F637A146, G637A146}
3 {F637A146, E637A146}
-2 {F637A146}
Expected Output Using GetEnumerator:
Key Value
---- -----
9 1
-3 2
3 2
-2 1
New Edit:
2. How to get the unique count of values as well?
My Hashtable $keys:
Name Value
---- -----
9 {G637A146, F637A146, J637A146}
-3 {F637A146, F637A146, G637A146, F637A146}
Expected Output Using GetEnumerator:
Key Value
---- -----
9 3
-3 2
Supposing your hashtable looks like this:
$keys = [ordered]#{
9 = 'G637A146'
-3 = 'F637A146', 'G637A146'
3 = 'F637A146', 'E637A146'
-2 = 'F637A146'
}
Then this should get you what you want:
$keys.GetEnumerator() | ForEach-Object {
[PSCustomObject]#{
Key = $_.Key
Value = ($_.Value).Count
}
}
Output:
Key Value
--- -----
9 1
-3 2
3 2
-2 1
Update
You could create a second hashtable where you keep track of the count values and only output if that vaue has not been seen before:
$seenThisBefore = #{}
$keys.GetEnumerator() | ForEach-Object {
$count = ($_.Value).Count
if (!$seenThisBefore.ContainsKey($count)) {
[PSCustomObject]#{
Key = $_.Key
Value = $count
}
$seenThisBefore[$count] = $true # add the count as key. The value here doesn't matter
}
}
I think you can cast the value to an array and then get the Count property.
So:
$keys.GetEnumerator() | % {
[PSCustomObject]#{
key = #($_.Value).Count
}
}
if I'm understanding your code correctly.
This is called the array sub-expression operator according to about_Arrays.
Since the comment response of the prior suggestion indicates that the format of the output for value isn't of some object but rather a string, then the value would need to be parsed.
Here's an example of how that might be done:
#('{a,b}' -replace '{', '' -replace '}', '' -split ',').Count
which produces the output of 2.
That just uses operators as documented on about_Operators

re-arrange and combine powershell custom objects

I have a system that currently reads data from a CSV file produced by a separate system that is going to be replaced.
The imported CSV file looks like this
PS> Import-Csv .\SalesValues.csv
Sale Values AA BB
----------- -- --
10 6 5
5 3 4
3 1 9
To replace this process I hope to produce an object that looks identical to the CSV above, but I do not want to continue to use a CSV file.
I already have a script that reads data in from our database and extracts the data that I need to use. I'll not detail the fairly long script that preceeds this point but in effect it looks like this:
$SQLData = Custom-SQLFunction "SELECT * FROM SALES_DATA WHERE LIST_ID = $LISTID"
$SQLData will contain ~5000+ DataRow objects that I need to query.
One of those DataRow object looks something like this:
lead_id : 123456789
entry_date : 26/10/2018 16:51:16
modify_date : 01/11/2018 01:00:02
status : WRONG
user : mrexample
vendor_lead_code : TH1S15L0NGC0D3
source_id : A543212
list_id : 333004
list_name : AA Some Text
gmt_offset_now : 0.00
SaleValue : 10
list_name is going to be prefixed with AA or BB.
SaleValue can be any integer 3 and up, however realistically extremely unlikely to be higher than 100 (as this is a monthly donation) and will be one of 3,5,10 in the vast majority of occurrences.
I already have script that takes the content of list_name, creates and populates the data I need to use into two separate psobjects ($AASalesValues and $BBSalesValues) that collates the total numbers of 'SaleValue' across the data set.
Because I cannot reliably anticipate the value of any SaleValue I have to dynamically create the psobjects properties like this
foreach ($record in $SQLData) {
if ($record.list_name -match "BB") {
if ($record.SaleValue -gt 0) {
if ($BBSalesValues | Get-Member -Name $($record.SaleValue) -MemberType Properties) {
$BBSalesValues.$($record.SaleValue) = $BBSalesValues.$($record.SaleValue)+1
} else {
$BBSalesValues | Add-Member -Name $($record.SaleValue) -MemberType NoteProperty -Value 1
}
}
}
}
The two resultant objects look like this:
PS> $AASalesValues
10 5 3 50
-- - - --
17 14 3 1
PS> $BBSalesvalues
3 10 5 4
- -- - -
36 12 11 1
I now have the data that I need, however I need to format it in a way that replicates the format of the CSV so I can pass it directly to another existing powershell script that is configured to expect the data in the format that the CSV is in, but I do not want to write the data to a file.
I'd prefer to pass this directly to the next part of the script.
Ultimately what I want to do is to produce a new object/some output that looks like the output from Import-Csv command at the top of this post.
I'd like a new object, say $OverallSalesValues, to look like this:
PS>$overallSalesValues
Sale Values AA BB
50 1 0
10 17 12
5 14 11
4 0 1
3 3 36
In the above example the values from $AASalesValues is listed under the AA column, the values from $BBSalesValues is listed under the BB column, with the rows matching the headers of the two original objects.
I did try this with hashtables but I was unable to work out how to both create them from dynamic values and format them to how I needed them to look.
Finally got there.
$TotalList = #()
foreach($n in 3..200){
if($AASalesValues.$n -or $BBSalesValues.$n){
$AACount = $AASalesValues.$n
$BBcount = $BBSalesValues.$n
$values = [PSCustomObject]#{
'Sale Value'= $n
AA = $AACount
BB = $BBcount
}
$TotalList += $values
}
}
$TotalList
produces an output of
Sale Value AA BB
---------- -- --
3 3 36
4 2
5 14 11
10 18 12
50 1
Just need to add a bit to include '0' values instead of $null.
I'm going to assume that $record contains a list of the database results for either $AASalesValues or $BBSalesValues, not both, otherwise you'd need some kind of selector to avoid counting records of one group with the other group.
Group the records by their SaleValue property as LotPings suggested:
$BBSalesValues = $record | Group-Object SaleValue -NoElement
That will give you a list of the SaleValue values with their respective count.
PS> $BBSalesValues
Count Name
----- ----
36 3
12 10
11 5
1 4
You can then update your CSV data with these values like this:
$file = 'C:\path\to\data.csv'
# read CSV into a hashtable mapping the sale value to the complete record
# (so that we can lookup the record by sale value)
$csv = #{}
Import-Csv $file | ForEach-Object {
$csv[$_.'Sale Values'] = $_
}
# Add records for missing sale values
$($AASalesValues; $BBSalesValues) | Select-Object -Expand Name -Unique | ForEach-Object {
if (-not $csv.ContainsKey($_)) {
$csv[$_] = New-Object -Type PSObject -Property #{
'Sale Values' = $_
'AA' = 0
'BB' = 0
}
}
}
# update records with values from $AASalesValues
$AASalesValues | ForEach-Object {
[int]$csv[$_.Name].AA += $_.Count
}
# update records with values from $BBSalesValues
$BBSalesValues | ForEach-Object {
[int]$csv[$_.Name].BB += $_.Count
}
# write updated records back to file
$csv.Values | Export-Csv $file -NoType
Even with your updated question the approach would be pretty much the same, you'd just add another level of grouping for collecting the sales numbers:
$sales = #{}
$record | Group-Object {$_.list_name.Split()[0]} | ForEach-Object {
$sales[$_.Name] = $_.Group | Group-Object SaleValue -NoElement
}
and then adjust the merging to something like this:
$file = 'C:\path\to\data.csv'
# read CSV into a hashtable mapping the sale value to the complete record
# (so that we can lookup the record by sale value)
$csv = #{}
Import-Csv $file | ForEach-Object {
$csv[$_.'Sale Values'] = $_
}
# Add records for missing sale values
$sales.Values | Select-Object -Expand Name -Unique | ForEach-Object {
if (-not $csv.ContainsKey($_)) {
$prop = #{'Sale Values' = $_}
$sales.Keys | ForEach-Object {
$prop[$_] = 0
}
$csv[$_] = New-Object -Type PSObject -Property $prop
}
}
# update records with values from $sales
$sales.GetEnumerator() | ForEach-Object {
$name = $_.Key
$_.Value | ForEach-Object {
[int]$csv[$_.Name].$name += $_.Count
}
}
# write updated records back to file
$csv.Values | Export-Csv $file -NoType