Powershell hashtable.containskey is always returning true

1.1k Views Asked by At

The following code is trying to do one thing. Its trying to find IDs that have been added to an Active Directory group since the last time the job was run.

It does this by reading the user IDs from an Active Directory group and compares them against the IDs it saved in a file the day before.

I first read the AD group into a hashtable ($ADUsersHashtable) I then read the file into a similar hashtable ($YesterdaysADUsersFile) Both hashtables use the UserID as the key. I then check to see if each ID in $ADUsersHashtable is in $YesterdaysADUsersFile. An ID that is in $ADUsersHashtable but not in $YesterdaysADUsersFile is an ID that was added to AD since the last time this job ran.

The problem is, if $YesterdaysADUsersFile has more than one entry in it, the containskey method always returns true (see output below)

If I delete all but one entry in the file, the code works as expected.

If i have more than one entry in the file the code doesn't work as expected.

The following is the code that reads AD and the file into the hashtables and then compares the keys.

$scriptName = $MyInvocation.MyCommand.Name
$LogFile = "D:\Polarion\data\logs\User Access Dates\$scriptName.log"
$Today = Get-Date
$outDate = get-date -format "yyyy-MM-dd"
Add-content $LogFile "$Today Running $scriptName"
$MyServer = $env:computername
Add-content $LogFile "`t$Today Running on $MyServer"

#Will be populated with IDs from AD that i didn't find yesterday
$NewUsersFile = "D:\Polarion\data\logs\User Access Dates\NewUsersInPEALM_ALL_USERSADGroup.txt" 
clear-content $NewUsersFile #I only want the new users from todays AD group.

$ADUsersHashtable = @{} #Contains IDs of the members in the AD Group.
Get-ADGroupMember -Identity PEALM_ALL_USERS -Recursive `
    | Get-ADObject -Properties SamAccountName, mail `
    | select SamAccountName, mail `
    | foreach {$ADUsersHashtable.Add($_.SamAccountName, $_.mail)}
#$ADUsersHashtable

$YesterdaysADUsersFile = "D:\Polarion\data\logs\User Access Dates\UserIdFromPEALM_ALL_USERS.txt" #Contains the IDs that I knew about the last time this ran.
$YesterdaysADUsersHashTable = @{}
$YesterdaysADUsersHashTable = Get-Content($YesterdaysADUsersFile) | 
    foreach {$_.ToString().Replace(":", "=")} | 
    ConvertFrom-StringData
$YesterdaysADUsersHashTable

$NoNewUsersFound = $true
foreach ($UserIDFromAD in $ADUsersHashtable.keys){ #For each user ID in Todays AD group
    if ($YesterdaysADUsersHashTable.containsKey($UserIDFromAD)){ #If the UserID is in Yesterdays list ignore it.
        write-host YesterdaysADUsersHashTable contains key $UserIDFromAD
    } else {
        $NoNewUsersFound = $false
        write-host YesterdaysADUsersHashTable Doesnt contains key $UserIDFromAD 
        write-host "`tadding $UserIDFromAD to the $NewUsersFile file."
        Add-content $LogFile "`t$Today Adding $UserIDFromAD to $NewUsersFile file"
        Add-Content $NewUsersFile "$UserIDFromAD : $outDate" #if its not in yesterdays list write it to the new users file.
    }
}
if ($NoNewUsersFound){
    Add-content $LogFile "`t$Today No new users IDs found in Active Directory."
}
#Clear-Content $YesterdaysADUsersFile #we want to overwrite the file, not append to it. 
#$ADUsersHashtable.keys `
#    | %{ Add-Content $YesterdaysADUsersFile "$_ : $($ADUsersHashtable.$_)" } #writes the content of the hashtable to a file.

The following is the output when the file (and hence $YesterdaysADUsersHashTable) has two entries in it. The first four lines are the dump of $YesterdaysADUsersHashTable. The next five lines are from the output from the write-host commands in the if-containskey block. They show that hashtable.containskey is returning true for every key in $ADUsersHashtable. But those keys are not in $YesterdaysADUsersHashTable, this is what i don't understand.

Name                           Value                                                                                                                                               
----                           -----                                                                                                                                               
QZMRW2                         [email protected]                                                                                                                 
dzrbcn                         [email protected]  

YesterdaysADUsersHashTable contains key QZMRW2
YesterdaysADUsersHashTable contains key dzrbcn
YesterdaysADUsersHashTable contains key MZDP2G
YesterdaysADUsersHashTable contains key BZ5LBQ
YesterdaysADUsersHashTable contains key FZ080Y

$YesterdaysADUsersHashTable clearly doesn't contain "MZDP2G", "BZ5LBQ", or "FZ080Y"

And, if I remove everything from the file except one user ID, the code seems to work. $YesterdaysADUsersHashTable now has only one entry "QZMRW2" and the code seems to work.

Name                           Value                                                                                                                                               
----                           -----                                                                                                                                               
QZMRW2                         [email protected]  

YesterdaysADUsersHashTable contains key QZMRW2
YesterdaysADUsersHashTable Doesnt contains key dzrbcn
    adding dzrbcn to the D:\Polarion\data\logs\User Access Dates\NewUsersInPEALM_ALL_USERSADGroup.txt file.
YesterdaysADUsersHashTable Doesnt contains key MZDP2G
    adding MZDP2G to the D:\Polarion\data\logs\User Access Dates\NewUsersInPEALM_ALL_USERSADGroup.txt file.
YesterdaysADUsersHashTable Doesnt contains key BZ5LBQ
    adding BZ5LBQ to the D:\Polarion\data\logs\User Access Dates\NewUsersInPEALM_ALL_USERSADGroup.txt file.
YesterdaysADUsersHashTable Doesnt contains key FZ080Y

I am clearly not understanding somehting.

Any suggestions would be greatly appreciated.

2

There are 2 best solutions below

1
Mathias R. Jessen On

Just because a key exists doesn't mean that the corresponding value is not $null.

See below for an example:

$a = @{ keyName = "Value" }
$b = @{ keyName = $null }

foreach($key in $a.Keys){
    if($b.ContainsKey($key)){
        $bValue = $b[$key] # or `$b.$key` if the key is a string

        if($null -eq $bValue){
            Write-Host "Key '$key' exists in '`$b', but its value is `$null :("
        }
    }
}

Without details about how you populate your hashtables it's hard to say why :)

3
Steve Gray On

I got the code to work by changing from hashtable.containskey to -contains. In the following code snippit, i commented our the line containing .containsKey($UserIDFromAD) and added the line containing $UserIDsFromFile.keys -contains $UserIDFromAD. The code now works as expected.

This has to be something to do with the difference between checking by reference and checkig by value. I know how this works in Java but not in Powershell.

#if ($UserIDsFromFile.containsKey($UserIDFromAD)){ #If the UserID is in Yesterdays list ignore it.
if ($UserIDsFromFile.keys -contains $UserIDFromAD){ #If the UserID is in Yesterdays list ignore it.