Закрытие Powershell для .Where() в строковой переменной

У меня есть общий список хеш-таблиц, полученный из ключей удаления в реестре, который мне нужно найти. Каждый член списка представляет собой упорядоченный словарь, содержащий эти ключи. Фактическое значение каждого ключа определяется отдельно, поскольку некоторые из них являются значениями непосредственно из реестра, а некоторые вычисляются.

[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))})
Foreach($Item in $Collection){ If( ) { } } на самом деле быстрее, чем .Where() в большинстве ситуаций, возможно, это может помочь?
sirtao 25.06.2024 09:43

@sirtao, это не мой опыт. Я обнаружил, что .Where() быстрее, чем цикл, который сам по себе быстрее (обычно намного быстрее), чем все, что использует конвейер. Мой план состоит в том, чтобы профилировать производительность здесь, прежде чем я реализую что-либо в рабочем коде, но заставить закрытие работать было непросто.

Gordon 25.06.2024 09:58

Можете ли вы показать код, выполняющий поиск по списку верхнего уровня? У меня возникли проблемы с визуализацией этого по описанию… (если оно большое из-за большого количества вариаций в шаблонах, возможно, сократите его до репрезентативного примера). Кроме того, уверены ли вы, что их проблема с производительностью связана с поиском по списку, а не с той частью, которая очищает реестр для создания списка хеш-таблиц?

mclayton 25.06.2024 10:24

@mclayton Упрощенная версия: это работает $rawUninstallKeys.Where({$_.publisher -like "Autodesk*"}). Но если я помещу замыкание в переменную, либо с помощью {} в замыкании, либо в вызове, как здесь, $closureString = '$_.publisher -like "Autodesk*"'; $rawUninstallKeys.Where({$closureString}) ничего не фильтрует.

Gordon 25.06.2024 10:30

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

Gordon 25.06.2024 10:33

Кстати, если {} находится в переменной, не похоже, что она действует как замыкание и выдает ошибку, тогда как когда она находится в вызове, переменная просто оценивается как true, потому что она не равна нулю, поэтому нет фильтрации, но нет ошибок. Так что, возможно, это просто место, где нельзя использовать замыкание. :(

Gordon 25.06.2024 10:39

В последнем примере вы пишете неправильно: $Closure = { $_.Publisher -like 'Autodesk*' }; $rawUninstallKeys.Where($Closure)

sirtao 25.06.2024 11:12
.Where на самом деле является одним из самых медленных способов фильтрации коллекции, но мне также непонятно, почему бы не перечислить коллекцию один раз, используя условие if с $_.Publisher -like 'Autodesk*' -and -not $_.Contains('DisplayName')
Santiago Squarzon 25.06.2024 19:07

Итак, вот мои результаты первого теста производительности. Я запускаю каждый вид поиска 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

Gordon 26.06.2024 07:02

Я понимаю, что никогда не занимался удалением найденных предметов. Действительно долгое выполнение происходит при поиске всех установленных программ на основе шаблона данных (для создания подходящего удаления). Интересно, поможет ли удаление всего, что было учтено, чтобы список стал меньше, или время, потраченное на удаление, сделает его пустым. Еще предстоит протестировать!

Gordon 26.06.2024 07:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
10
87
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Согласно комментарию @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 в серию замыканий для применения в цикле. А затем убедитесь, что это действительно быстрее. :)

Gordon 25.06.2024 13:33

@Гордон - если вы не возражаете против некоторой уязвимости внедрения кода, вы можете просто сохранить код 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

mclayton 25.06.2024 14:33

Другие вопросы по теме