Powershell hashtable.containskey всегда возвращает true

Следующий код пытается сделать одну вещь. Он пытается найти идентификаторы, которые были добавлены в группу Active Directory с момента последнего запуска задания.

Для этого он считывает идентификаторы пользователей из группы Active Directory и сравнивает их с идентификаторами, сохраненными в файле накануне.

Сначала я прочитал группу AD в хэш-таблицу ($ADUsersHashtable). Затем я прочитал файл в аналогичную хеш-таблицу ($YesterdaysADUsersFile). Обе хеш-таблицы используют идентификатор пользователя в качестве ключа. Затем я проверяю, находится ли каждый идентификатор в $ADUsersHashtable в $YesterdaysADUsersFile. Идентификатор, который находится в $ADUsersHashtable, но отсутствует в $YesterdaysADUsersFile, — это идентификатор, который был добавлен в AD с момента последнего запуска этого задания.

Проблема в том, что если $YesterdaysADUsersFile содержит более одной записи, метод containskey всегда возвращает true (см. вывод ниже).

Если я удалю все, кроме одной записи в файле, код будет работать так, как ожидалось.

Если у меня есть более одной записи в файле, код не работает должным образом.

Ниже приведен код, который считывает AD и файл в хеш-таблицы, а затем сравнивает ключи.

$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.

Ниже приведен вывод, когда файл (и, следовательно, $YesterdaysADUsersHashTable) содержит две записи. Первые четыре строки — это дамп $YesterdaysADUsersHashTable. Следующие пять строк взяты из вывода команд write-host в блоке if-containskey. Они показывают, что hashtable.containskey возвращает true для каждого ключа в $ADUsersHashtable. Но этих ключей нет в $YesterdaysADUsersHashTable, вот чего я не понимаю.

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 явно не содержит «MZDP2G», «BZ5LBQ» или «FZ080Y».

И если я удалю из файла все, кроме одного идентификатора пользователя, код, похоже, сработает. $YesterdaysADUsersHashTable теперь имеет только одну запись «QZMRW2», и код работает.

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

Я явно чего-то не понимаю.

Любые предложения будут ценны.


person Steve Gray    schedule 05.03.2020    source источник
comment
Тот факт, что ключ существует, не означает, что значение, на которое он ссылается, не равно $null :)   -  person Mathias R. Jessen    schedule 05.03.2020
comment
@MathiasR.Jessen, возможно, решил это - воспроизвел ошибку: $hash = @{MyKey = $null}; $hash.MyKey; $hash.MyKey.GetType(). Потому что хэш-таблица ищет ключ, но возвращает значение, а $UserIDsFromPEALM_ALL_USERSFileHashTable.$key.GetType() пытается получить тип значения. Я не уверен, что вообще возможно получить такой тип ключа.   -  person Dávid Laczkó    schedule 05.03.2020
comment
Если вам нужен тип ключа внутри цикла, просто выполните $key.GetType() :)   -  person Mathias R. Jessen    schedule 05.03.2020
comment
@MathiasR.Jessen Хорошо, но я имею в виду мой пример, когда нет цикла.   -  person Dávid Laczkó    schedule 05.03.2020
comment
Ну, может быть несколько ключей - если вам нужен только первый: $hash.Keys[0].GetType()   -  person Mathias R. Jessen    schedule 05.03.2020
comment
@SteveGray Покажите нам, как вы создали/заполнили хеш-таблицы, если хотите получить более квалифицированные ответы :)   -  person Mathias R. Jessen    schedule 05.03.2020
comment
@MathiasR.Jessen Да, по положению вы можете это сделать, но это должно было быть сделано по имени - однако я думаю, что мой вопрос может быть недействительным, поскольку, если я знаю ключ, я должен знать его тип.   -  person Dávid Laczkó    schedule 05.03.2020
comment
@DávidLaczkó Проблема здесь в том, что хеш-таблица будет принимать объект любого типа в качестве своего ключа, а не только строки, поэтому имя не обязательно имеет смысл :)   -  person Mathias R. Jessen    schedule 05.03.2020
comment
Переменная $PEALM_ALL_USERSGroupMembersHashTable, которую вы используете в цикле foreach, никогда не определялась. Откуда эта хеш-таблица? P.S. Если я могу быть таким смелым ... Почему вы используете такие невероятно трудные для чтения имена переменных? Код может быть намного легче читать и находить ошибки, если вы называете свои переменные коротко и по делу.   -  person Theo    schedule 05.03.2020
comment
@Theo, я изменил некоторые имена переменных, чтобы сделать их более читабельными, и включил весь код.   -  person Steve Gray    schedule 05.03.2020
comment
@MathiasR.Jessen Только потому, что объект не является String, у него есть имя: $innerhash1 = @{Key1 = "innerhash1"}; $innerhash2 = @{Key2 = "innerhash2"}; $outerhash = @{$innerhash1 = "outerhash1"}; $outerhash += @{$innerhash2 = "outerhash2"}; $outerhash. Но я не знаю, как получить доступ к таким именам, как $outerhash.Key1; $outerhash.{Key1}; $outerhash.@{Key1}; $outerhash.@{Key1 = "innerhash1"}; $outerhash.{Key1 = "innerhash1"} - ни одно из них не работает, но работает: $outerhash.$innerhash1. Таким образом, имя переменной каким-то образом обернуто, потому что это не позиционный поиск.   -  person Dávid Laczkó    schedule 06.03.2020
comment
@MathiasR.Jessen Ну, может быть несколько ключей - если вам нужен только первый: $hash.Keys[0].GetType() - это неправильно: $hash = @{MyKey = $null}; $hash += @{MyOtherKey = "NotNull"}; $hash; $hash.Keys[0].   -  person Dávid Laczkó    schedule 06.03.2020
comment
@DávidLaczkó Не уверен, что ты хочешь сказать с последним комментарием? Хэш-таблицы не поддерживают порядок ключей. Что касается ссылки по имени, это все еще не проблема, если вы используете нестроки в качестве ключей: $obj1 = [object]::new();$obj2 = [object]::new();$hash = @{ $obj1 = 1; $obj2 = 2} - в этом случае нет имени, соответствующего идентификатору ключей. Но $hash[$obj1] или $hash[$hash.Keys[0]] все еще работают, как и ожидалось :)   -  person Mathias R. Jessen    schedule 07.03.2020
comment
@MathiasR.Jessen Суть моего вопроса заключается в том, что хеш-таблицы работают с уникальным хэш-кодом в качестве ключа, и это просматривается. Когда я пытаюсь использовать $outerhash[@{Key1 = "innerhash1"}], он синтаксически корректен и запускается, но ничего не возвращает. Теперь то, что находится внутри скобок, можно было бы назвать поиском по имени, потому что это своего рода шаблон, который существует внутри $outerhash, но это не строка. Однако он не совпадает, потому что это отдельный объект, создаваемый каждый раз, когда он встречается в коде, и каждый раз имеет разный хэш-код: @{Key1 = "innerhash1"}.GetHashCode().   -  person Dávid Laczkó    schedule 20.03.2020


Ответы (2)


Тот факт, что ключ существует, не означает, что соответствующее значение не равно $null.

См. пример ниже:

$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 :("
        }
    }
}

Без подробностей о том, как вы заполняете свои хеш-таблицы, трудно сказать почему :)

person Mathias R. Jessen    schedule 05.03.2020
comment
@SteveGray Пожалуйста, отредактируйте существующий пост, комментарии не подходят для кода :) - person Mathias R. Jessen; 05.03.2020

Я заставил код работать, изменив hashtable.containskey на -contains. В следующем фрагменте кода я прокомментировал строку, содержащую .containsKey($UserIDFromAD), и добавил строку, содержащую $UserIDsFromFile.keys -contains $UserIDFromAD. Теперь код работает так, как ожидалось.

Это должно быть как-то связано с разницей между проверкой по ссылке и проверкой по значению. Я знаю, как это работает в Java, но не в 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.
person Steve Gray    schedule 06.03.2020
comment
Не могли бы вы сказать, какую версию PowerShell и .NET вы используете? Я не мог воспроизвести это с помощью этого: $innerhash1 = @{Key1 = "innerhash1"}; $innerhash2 = @{Key2 = "innerhash2"}; $outerhash = @{$innerhash1 = "outerhash1"}; $outerhash += @{$innerhash2 = "outerhash2"}; $outerhash - после этого, если я запрошу как $outerhash.$innerhash1; $outerhash.Contains($innerhash1); $outerhash.ContainsKey($innerhash1); $outerhash.Keys -contains $innerhash1, все они дают правильные результаты. Не могли бы вы проверить это? Ваши результаты такие же (outerhash1 True True True)? - person Dávid Laczkó; 06.03.2020
comment
Я нашел следующую статью, которая объясняет, что происходит. ссылка - person Steve Gray; 06.03.2020
comment
Спасибо, но с .NET 4.7.2 и PowerShell 5.1 это не так: я получил True за $hash.ContainsKey($key). - person Dávid Laczkó; 06.03.2020