У меня есть общий список хеш-таблиц, полученный из ключей удаления в реестре, который мне нужно найти. Каждый член списка представляет собой упорядоченный словарь, содержащий эти ключи. Фактическое значение каждого ключа определяется отдельно, поскольку некоторые из них являются значениями непосредственно из реестра, а некоторые вычисляются.
[Ordered]@{
displayName = 'DisplayName'
displayVersion = 'DisplayVersion'
installSource = 'InstallSource'
publisher = 'Publisher'
quietUninstallString = 'QuietUninstallString'
uninstallParam = 'UninstallParam'
uninstallPath = 'UninstallPath'
uninstallString = 'UninstallString'
installDate = 'InstallDate'
keyID = '$keyID'
keyName = '$keyName'
keyGUID = '$keyGUID'
keyPath = '$keyPath'
architecture = '$architecture'
}
Как только эта коллекция появится в переменной $rawUninstallKeys
, мне нужно ее поискать, и поиск усложняется, поскольку Autodesk имеет тенденцию иметь несколько ключей удаления с одинаковыми или похожими данными, но работать будет только один. Например, будет ключ GUID с DisplayName
из Autodesk Revit 2025
, который предоставляет строку удаления, которая действительно работает, и другой ключ GUID с DisplayName
из Revit 2025
, который ничего не делает. Итак, чтобы извлечь правильную строку uninstallString, мне нужно найти ключ, соответствующий первому шаблону, И иметь второй ключ, соответствующий второму шаблону. И на самом деле существует масса вариаций, потому что Autodesk просто отстой в любой последовательности.
Итак, теперь у меня работает это: я просто выполняю foreach для необработанных ключей в поисках первого шаблона, и если я его нахожу, я получаю второй шаблон (в этом случае я заменяю «Autodesk» на $null
), а затем ищу другой для каждого. И это работает, но боже мой, это медленно.
Итак, я хочу попробовать использовать метод .Where()
из списка, чтобы оптимизировать .NET. Но это означает создание набора замыканий для применения.
Итак, это сработает
$rawUninstallKeys.Where({$_.publisher -like "Autodesk*"})
но я изо всех сил пытаюсь получить замыкание в переменную, которая все еще позволяет $_
расширяться соответствующим образом. Это еще больше усложняется тем фактом, что мое замыкание может быть просто {$_.DisplayName}
, когда мне просто нужно посмотреть, существует ли это значение вообще, или {-not $_.DisplayName}
, когда мне нужен только ключ, который не содержит этого свойства, или первый пример, где мне нужно для сравнения значения свойства с другим значением, которое может быть подстановочным знаком.
По сути, мне нужно расширить переменную в закрывающей строке внутри .Where()
, но это не работает.
.Where({$($ExecutionContext.InvokeCommand.ExpandString($closure))})
@sirtao, это не мой опыт. Я обнаружил, что .Where()
быстрее, чем цикл, который сам по себе быстрее (обычно намного быстрее), чем все, что использует конвейер. Мой план состоит в том, чтобы профилировать производительность здесь, прежде чем я реализую что-либо в рабочем коде, но заставить закрытие работать было непросто.
Можете ли вы показать код, выполняющий поиск по списку верхнего уровня? У меня возникли проблемы с визуализацией этого по описанию… (если оно большое из-за большого количества вариаций в шаблонах, возможно, сократите его до репрезентативного примера). Кроме того, уверены ли вы, что их проблема с производительностью связана с поиском по списку, а не с той частью, которая очищает реестр для создания списка хеш-таблиц?
@mclayton Упрощенная версия: это работает $rawUninstallKeys.Where({$_.publisher -like "Autodesk*"})
. Но если я помещу замыкание в переменную, либо с помощью {}
в замыкании, либо в вызове, как здесь, $closureString = '$_.publisher -like "Autodesk*"'; $rawUninstallKeys.Where({$closureString})
ничего не фильтрует.
Со временем мне захочется создать несколько замыканий с разными свойствами и значениями, но только эта упрощенная версия заставляет меня ломать голову.
Кстати, если {}
находится в переменной, не похоже, что она действует как замыкание и выдает ошибку, тогда как когда она находится в вызове, переменная просто оценивается как true, потому что она не равна нулю, поэтому нет фильтрации, но нет ошибок. Так что, возможно, это просто место, где нельзя использовать замыкание. :(
В последнем примере вы пишете неправильно: $Closure = { $_.Publisher -like 'Autodesk*' }; $rawUninstallKeys.Where($Closure)
.Where
на самом деле является одним из самых медленных способов фильтрации коллекции, но мне также непонятно, почему бы не перечислить коллекцию один раз, используя условие if
с $_.Publisher -like 'Autodesk*' -and -not $_.Contains('DisplayName')
Итак, вот мои результаты первого теста производительности. Я запускаю каждый вид поиска 3 раза, чередуя. Целое число в конце каждой строки — это .Count
. Согласно этому, эти два подхода на самом деле очень похожи: .Where()
имеет самое быстрое время, а foreach
самое медленное, но ненамного. [0:0:0.464] : Получить все ключи удаления 159 [0:0:0.271] : .Where() MsiExec /X 92 [0:0:0.279] : foreach MsiExec /X 92 [0:0:0.270] : .Where() MsiExec /X 92 [0:0:0.285] : foreach MsiExec /X 92 [0:0:0.273] : .Where() MsiExec /X 92 [0:0:0.272] : foreach MsiExec /X 92
Я понимаю, что никогда не занимался удалением найденных предметов. Действительно долгое выполнение происходит при поиске всех установленных программ на основе шаблона данных (для создания подходящего удаления). Интересно, поможет ли удаление всего, что было учтено, чтобы список стал меньше, или время, потраченное на удаление, сделает его пустым. Еще предстоит протестировать!
Согласно комментарию @sirtao, вам не нужно преобразовать в строку, а затем де-стринговать блок сценария — вы можете просто передать его непосредственно .Where()
:
Давайте настроим некоторые тестовые данные:
$rawUninstallKeys = [System.Collections.Generic.List[System.Collections.IDictionary]]::new()
$rawUninstallKeys.Add(
[ordered] @{
publisher = "Autodesk Revit 2025"
}
)
$rawUninstallKeys.Add(
[ordered] @{
displayname = "displayname"
publisher = "Revit 2025"
}
)
а затем, если у вас есть ряд фильтров, которые вы хотите применить:
$filters = @(
{ $_.Publisher -like 'Autodesk*' },
{ $_.DisplayName }
)
вы можете просто сделать это:
foreach( $filter in $filters )
{
write-host "testing for $filter"
$rawUninstallKeys.Where($filter) | ft | out-string
}
который дает результат:
testing for $_.Publisher -like 'Autodesk*'
Name Value
---- -----
publisher Autodesk Revit 2025
testing for $_.DisplayName
Name Value
---- -----
displayname displayname
publisher Revit 2025
Вы можете расширить это, чтобы просто запускать фильтры по порядку, пока не найдете первый, который возвращает результат, если это то, что вам нужно...
Ух ты! Клейтону и Сиртао. Следующий шаг — выяснить, как получить свойства, операторы и значения из XML в серию замыканий для применения в цикле. А затем убедитесь, что это действительно быстрее. :)
@Гордон - если вы не возражаете против некоторой уязвимости внедрения кода, вы можете просто сохранить код PowerShell - например. <filter>$_.Publisher -like 'Autodesk*'</filter>
, а затем преобразуйте его в блок сценария внутри foreach
: foreach( $filter in $filters ) { $sb = [scriptblock]::Create($filter); $rawUninstallKeys.Where($sb) | ft | out-string
, но имейте в виду, что вы будете выполнять произвольный код, хранящийся в xml (например, <filter>Format-Volume -DriveLetter C</filter>
:-S
Foreach($Item in $Collection){ If( ) { } }
на самом деле быстрее, чем.Where()
в большинстве ситуаций, возможно, это может помочь?