Powershell Key/Value Pair Matching Problem - powershell

I am looking to check a key value pair in powershell. I have tried various methods but none seem to be working as I would expect.
I keep getting a True response on a lookup of $checked_group, even though I can see the KeyValue pair in the write-out.
if (!($checked_groups[$varDomain] -eq "$varName"))
Even if the keyValue pair is in the $checked_groups dictionary the above statement is True. Have you ever come across this before?
I cant recreate the problem using the snippet below even though its pretty much the same logic as my live code. The sources of the values are different, as I am dynamically collecting these in the live code. And iterating over them in a loop, my $checked_groups = #{ } is outside this loop to maintain the dict key/pairs across all iterations and I can confirm they are preserved across ever iteration.
I can't work out why this statement would always resolve to True :(
$checked_groups = #{ }
$varDomain = "example.com"
$varName = "Administrator"
if (!($checked_groups[$varDomain] -eq "$varName"))
{
write-host 'Not In There'
}
foreach ($thing in $checked_groups)
{
Write-Host ($thing | Out-String)
}
$checked_groups += #{$varDomain = $varName}
if (($checked_groups[$varDomain] -eq "$varName"))
{
write-host 'In There'
foreach ($thing in $checked_groups)
{
Write-Host ($thing | Out-String)
}
}

Related

Check if a condition is met by a line within a TXT but "in an advanced way"

I have a TXT file with 1300 megabytes (huge thing). I want to build code that does two things:
Every line contains a unique ID at the beginning. I want to check for all lines with the same unique ID if the conditions is met for that "group" of IDs. (This answers me: For how many lines with the unique ID X have all conditions been met)
If the script is finished I want to remove all lines from the TXT where the condition was met (see 2). So I can rerun the script with another condition set to "narrow down" the whole document.
After few cycles I finally have a set of conditions that applies to all lines in the document.
It seems that my current approach is very slow.( one cycle needs hours). My final result is a set of conditions that apply to all lines of code.
If you find an easier way to do that, feel free to recommend.
Help is welcome :)
Code so far (does not fullfill everything from 1&2)
foreach ($item in $liste)
{
# Check Conditions
if ( ($item -like "*XXX*") -and ($item -like "*YYY*") -and ($item -notlike "*ZZZ*")) {
# Add a line to a document to see which lines match condition
Add-Content "C:\Desktop\it_seems_to_match.txt" "$item"
# Retrieve the unique ID from the line and feed array.
$array += $item.Split("/")[1]
# Remove the line from final document
$liste = $liste -replace $item, ""
}
}
# Pipe the "new cleaned" list somewhere
$liste | Set-Content -Path "C:\NewListToWorkWith.txt"
# Show me the counts
$array | group | % { $h = #{} } { $h[$_.Name] = $_.Count } { $h } | Out-File "C:\Desktop\count.txt"
Demo Lines:
images/STRINGA/2XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg images/STRINGA/3XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg images/STRINGB/4XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg images/STRINGB/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg images/STRINGC/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
performance considerations:
Add-Content "C:\Desktop\it_seems_to_match.txt" "$item"
try to avoid wrapping cmdlet pipelines
See also: Mastering the (steppable) pipeline
$array += $item.Split("/")[1]
Try to avoid using the increase assignment operator (+=) to create a collection
See also: Why should I avoid using the increase assignment operator (+=) to create a collection
$liste = $liste -replace $item, ""
This is a very expensive operation considering that you are reassigning (copying) a long list ($liste) with each iteration.
Besides it is a bad practice to change an array that you are currently iterating.
$array | group | ...
Group-Object is a rather slow cmdlet, you better collect (or count) the items on-the-fly (where you do $array += $item.Split("/")[1]) using a hashtable, something like:
$Name = $item.Split("/")[1]
if (!$HashTable.Contains($Name)) { $HashTable[$Name] = [Collections.Generic.List[String]]::new() }
$HashTable[$Name].Add($Item)
To minimize memory usage it may be better to read one line at a time and check if it already exists. Below code I used StringReader and you can replace with StreamReader for reading from a file. I'm checking if the entire string exists, but you may want to split the line. Notice I have duplicaes in the input but not in the dictionary. See code below :
$rows= #"
images/STRINGA/2XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGA/3XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGB/4XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGB/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGC/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGA/2XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGA/3XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGB/4XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGB/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
images/STRINGC/5XXXXXXXX_rTTTTw_GGGG1_Top_MMM1_YY02_ZZZ30_AAAA5.jpg
"#
$dict = [System.Collections.Generic.Dictionary[int, System.Collections.Generic.List[string]]]::new();
$reader = [System.IO.StringReader]::new($rows)
while(($row = $reader.ReadLine()) -ne $null)
{
$hash = $row.GetHashCode()
if($dict.ContainsKey($hash))
{
#check if list contains the string
if($dict[$hash].Contains($row))
{
#string is a duplicate
}
else
{
#add string to dictionary value if it is not in list
$list = $dict[$hash].Value
$list.Add($row)
}
}
else
{
#add new hash value to dictionary
$list = [System.Collections.Generic.List[string]]::new();
$list.Add($row)
$dict.Add($hash, $list)
}
}
$dict

Foreach with Where-Object not yielding correct results

I have the following code:
$ErrCodes = Get-AlarmIDs_XML -fileNamePath $Paper_Dialog_FullBasePath
$excelDevice = Get_ErrorCodes -errorCodeListFilePath $outFilePath -ws "DEVICE COMPONENT MAP"
foreach ($errCode in $ErrCodes | Where-Object{$excelDevice.Common_Alarm_Number -eq $errCode.Value })
{
#$dataList = [System.Collections.Generic.List[psobject]]::new()
#$resultHelp = [System.Collections.Generic.List[psobject]]::new()
Write-Host "another:"
$err = $errCode.Value #get each error code to lookup in mdb file
$key = $errCode.Key
Write-Host $err
...
}
But it's definitely getting in the foreach loop when it shouldn't.
My intention is to use the foreach, and if it has a value in the $ErrCodes, then it should continue with the code that follows.
Let me know if you need to see the Functions that do the file reads, but the data structures look like this:
$excelDevice:
[Object[57]]
[0]:#{Common_Alarm_Number=12-2000}
[1]:#{Common_Alarm_Number=12-5707}
[2]:#{Common_Alarm_Number=12-9}
[3]:#{Common_Alarm_Number=12-5703}
...
$ErrCodes:
[Object[7]]
[0]:#{Key=A;Value=12-5702}
[1]:#{Key=B;Value=12-5704}
[2]:#{Key=C;Value=12-5706}
[3]:#{Key=D;Value=12-5707}
...
So we only care about the ones in $ErrCodes that are also in $excelDevice.
When I step through the code, it's getting into the foreach code for 12-5702 for some reason, when it shouldn't be there (prints 12-5702 to screen). I know I wouldn't want 12-5702 to be used because it isn't in $excelDevice list.
How would I get that Where-Object to filter out $ErrCodes that aren't in $excelDevice list? I don't want to process error codes that don't have data for this device.
Right now you're testing whether any of the values in $excelDevice.Common_Alarm_Number (which presumably evaluates to an array) is exactly the same value as all the values in $errCodes.Value - which doesn't make much sense.
It looks like you'll want to test each error code for whether it is contained in the $excelDevice.Common_Alarm_Number list instead. Use $_ to refer to the individual input items received via the pipeline:
foreach ($errCode in $ErrCodes | Where-Object{ $excelDevice.Common_Alarm_Number -contains $_.Value }) { ... }

Powershell compare arrays and get unique values

I am currently trying to write a powershell script that can be run weekly on two CSV files, to check they both contain the same information. I want the script to output anything that appears in one file but not the other to a new file.
The script I have written so far compares the two but only adds <= and => to the values.
It also doesn't work all the time, because I manually checked the file and found results that existed in both.
Code below:
$NotPresents = compare-object -ReferenceObject $whatsup -DifferenceObject $vmservers -Property device
foreach ($NotPresent in $NotPresents)
{
Write-Host $NotPresent.device
}
$NotPresents | Out-File Filepath.txt
$NotPresents.count
Any ideas what I have done wrong?
In order to avoid having to iterate over one of the arrays more than once*, you may want to throw them each into a hashtable:
$whatsupTable = #{}
foreach($entry in $whatsup){
$whatsupTable[$entry.device] = $true
}
$vmserversTable = #{}
foreach($entry in $vmservers){
$vmserversTable[$entry.device] = $true
}
Now you can easily find the disjunction with a single loop and a lookup against the other table:
$NotInWhatsUp = $vmservers |Where { -not $whatsupTable[$_] }
$NotInVMServers = $whatsup |Where { -not $vmserversTable[$_] }
*) ok, technically we're looping through each twice, but still much better than nested looping

Unable to remove item from hash table

In Powershell, I have a hash table that contains data similar to this -
Name Value
---- -----
1-update.bat 1
2-update.bat 2
3-update.bat 3
3.1-update.bat 3.1
4-update.bat 4
I also have an variable that contians a number, for example 3
What I would like to do is loop through the array and remove any entry where the value is less than or equal to 3
I'm thinking that this will be easy, especially as the docs say that has tables contain a .remove method. However, the code I have below fails, yeilding this error -
Exception calling "Remove" with "1" argument(s): "Collection was of a
fixed size."
Here is the code that I used -
$versions = #{}
$updateFiles | ForEach-Object {
$versions.Add($_.name, [decimal]($_.name -split '-')[0])
}
[decimal]$lastUpdate = Get-Content $directory\$updatesFile
$versions | ForEach-Object {
if ( $_.Value -le $lastUpdate ) {
$versions.Remove($version.Name)
}
}
I first tried to loop $versions in a different manner, trying both the foreach and for approaches, but both failed in the same manner.
I also tried to create a temporary array to hold the name of the versions to remove, and then looping that array to remove them, but that also failed.
Next I hit Google, and while I can find several similar questions, none that answer my specific question. Mostly they suggest using a list (New-Object System.Collections.Generic.List[System.Object]), whcih from what I can tell is of no help to me here.
Is anyone able to suggest a fix?
Here you go, you can use .Remove(), you just need a clone of the hashtable so that it will let you remove items as you enumerate.
[hashtable]$ht = #{ '1-update.bat'=1;'2-update.bat'=2;'3-update.bat'=3;'3.1-update.bat'=3.1; '4-update.bat'=4 }
#Clone so that we can remove items as we're enumerating
$ht2 = $ht.Clone()
foreach($k in $ht.GetEnumerator()){
if([decimal]$k.Value -le 3){
#notice, deleting from clone, then return clone at the end
$ht2.Remove($k.Key)
}
}
$ht2
Notice I've cast the original variable too so that it's explicitly a hash table, may not be required, but I like to do it to at least keep things clear.
It looks like you just confused ForEach-Object with foreach but only halfway (maybe it was foreach before and you converted it).
You can't send a [hashtable] directly to ForEach-Object; the $_ in that case will just refer to the single [hashtable] you sent in. You can do:
foreach ($version in $versions.GetEnumerator()) {
$version.Name
$version.Value
}
or you can do something like this:
$versions.Keys | ForEach-Object {
$_ # the name/key
$versions[$_] # the value
$versions.$_ # also the value
}
$ht.Keys # list all keys
$ht[$_] # take an element of hastable
$ht.Remove($_) # remove an element of hastable by his key
what you want:
$ht.Keys | ? { $ht[$_] -le 3 } | %{$ht.Remove($_) }
You need to create a temporary array to hold the name/key of the versions to remove, and then looping that array to remove them from hash table:
$versionKeysToRemove = $versions.Keys | Where-Object { $versions[$_] -le $lastUpdate }
$versionKeysToRemove | ForEach-Object { $versions.Remove($_) }
Or shorter:
($versions.Keys | ? { $versions[$_] -le $lastUpdate }) | % { $versions.Remove($_) }
Please note the parentheses.

Why Is It Possible to Loop Through a Null Array

Given the following PowerShell code:
$FolderItems = Get-ChildItem -Path "C:\Test"
Write-Host "FolderItems Is Null: $($FolderItems -eq $null)"
foreach ($FolderItem in $FolderItems)
{
Write-Host "Inside the loop: $($FolderItem.Name)"
}
Write-Host "Done."
When I test it with one file in the C:\Test folder, it outputs this:
FolderItems Is Null: False
Inside the loop: MyFile.txt
Done.
However, when I test it with ZERO files in the folder, it outputs this:
FolderItems Is Null: True
Inside the loop:
Done."
If $FolderItems is null, then why does it enter the foreach loop?
This was an intentional design choice made in V1 and revisited in V3.
In most languages, the foreach statement can only loop over collections of things. PowerShell has always been a little different, and in V1, you could loop over a single value in addition to collections of values.
For example:
foreach ($i in 42) { $i } # prints 42
In V1, if a value was a collection, foreach would iterate over each element in the collection, otherwise it would enter the loop for just that value.
Note in the above sentence, $null isn't special. It's just another value. From a language design point of view, this is fairly clean and concisely explained.
Unfortunately many people did not expect this behavior and it caused many bugs. I think some confusion arises because people expect the foreach statement to behave almost like the foreach-object cmdlet. In other words, I think people expect the following to work the same:
$null | foreach { $_ }
foreach ($i in $null) { $i }
In V3, we decided that it was important enough to change behavior because we could help scripters avoid introducing bugs in their scripts.
Note that changing the behavior could in theory break existing scripts in unexpected ways. We ultimately decided that most scripts that potentially see $null in the foreach statement already guard the foreach statement with an if, e.g.:
if ($null -ne $c)
{
foreach ($i in $c) { ... }
}
So in reality, most real world scripts would not see a change in behavior.
This was something of an idiosyncracy/bug in ForEach in V1 and V2. It was corrected in the V3 release.
Seems to me like you need to wrap your foreach within a conditional that checks if $FolderItem != null. This way, it'll never get in the if statement whenever $FolderItems is NULL
If (-NOT $FolderItems -eq $null) {
foreach ($FolderItem in $FolderItems)
{
Write-Host "Inside the loop: $($FolderItem.Name)"
}
}
This may be of help as well http://bit.ly/1brKRRk