Compare-Object в Powershell для 2 объектов на основе поля внутри. Объекты, заполненные JSON и XML

Извиняюсь за недостаток знаний PowerShell, искал решение повсюду, потому что я не очень программист.

Фон:

В настоящее время я пытаюсь стандартизировать некоторые настройки сайта в Incapsula. Чтобы сделать это, я хочу поддерживать локальный XML с правилами и использовать некоторые PowerShell, чтобы раскрыть существующие правила и сравнить их с тем, что есть, чтобы убедиться, что я не удваиваюсь. Я использую такой подход, пытаясь применить только дельты как:

  1. Для большинства настроек инкапсула недостаточно умна, чтобы знать, что она уже существует
  2. То, что может быть опубликовано в API, отличается от того, что возвращается API

Примеры:

Ниже приведен пример того, что API будет возвращать по запросу, это в формате JSON.

JSON FROM WEBSITE

{
"security": {
        "waf": {
            "rules": [{
                "id": "api.threats.sql_injection",
                "exceptions": [{
                    "values": [{
                        "urls": [{
                            "value": "google.com/thisurl",
                            "pattern": "EQUALS"
                        }],
                        "id": "api.rule_exception_type.url",
                        "name": "URL"
                    }],
                    "id": 256354634
                }]
            }, {
                "id": "api.threats.cross_site_scripting",
                "action": "api.threats.action.block_request",
                "exceptions": [{
                    "values": [{
                        "urls": [{
                            "value": "google.com/anotherurl",
                            "pattern": "EQUALS"
                        }],
                        "id": "api.rule_exception_type.url",
                        "name": "URL"
                    }],
                    "id": 78908790780
                }]
            }]
        }
    }
}

И это формат XML с настройками нашего сайта

OUR XML RULES
    <waf>
    <ruleset>
        <rule>
            <id>api.threats.sql_injection</id>
                <exceptions>
                    <exception>
                        <type>api.rule_exception_type.url</type>
                        <url>google.com/thisurl</url>   
                    </exception>
                    <exception>
                        <type>api.rule_exception_type.url</type>
                        <url>google.com/thisanotherurl</url>
                    </exception>
                </exceptions>
        </rule>
        <rule> 
            <id>api.threats.cross_site_scripting</id>
                <exceptions>
                    <exception>
                        <type>api.rule_exception_type.url</type>
                        <url>google.com/anotherurl</url>
                    </exception>
                    <exception>
                        <type>api.rule_exception_type.url</type>
                        <url>google.com/anotherurl2</url>
                    </exception> 
                </exceptions>
        </rule> 
    </ruleset>
</waf>

Я успешно смог сравнить другие настройки сайта с XML с помощью команды compare-object, однако они были немного проще и не доставляли мне особых хлопот. Я застрял в том, является ли это логической проблемой или ограничением по сравнению с объектом сравнения. Пример кода приведен ниже, он потребует, чтобы поставленные json и xml были сохранены как stack.json/xml в том же каталоге и должны привести к упомянутому результату:

    $existingWaf = Get-Content -Path stack.json | ConvertFrom-Json
    [xml]$xmlFile = Get-Content -Path stack.xml 

    foreach ($rule in $xmlFile)
    {
        $ruleSet = $rule.waf.ruleset
    }

    foreach ($siteRule in $ExistingWaf.security.waf.rules)
        {
            foreach ($xmlRule in $ruleSet)
            {
                if ($xmlRule.rule.id -eq $siteRule.id)
                    {
                        write-output "yes"
                        $delta = Compare-Object -ReferenceObject @($siteRule.exceptions.values.urls.value | Select-Object) -DifferenceObject @($xmlRule.rule.exceptions.exception.url | Select-Object) -IncludeEqual | where {$xmlRule.rule.id -eq $siteRule.id}
                        $delta

                    }
            }
        }

Это отчасти работает, но не совсем то, что я хотел. Я получаю сравнение между объектами, но не для конкретных идентификаторов, это показывает мне результаты ниже:

    InputObject                                 SideIndicator
    -----------                                 -------------
    google.com/thisurl                               ==
    google.com/thisanotherurl                        =>
    google.com/anotherurl                            =>
    google.com/anotherurl2                           =>

    google.com/anotherurl                            ==
    google.com/thisurl                               =>
    google.com/thisanotherurl                        =>
    google.com/anotherurl2                           =>

Где, как я больше после

    InputObject                                 SideIndicator
    -----------                                 -------------
    google.com/thisurl                               ==
    google.com/thisanotherurl                        =>

    google.com/anotherurl                            ==
    google.com/anotherurl2                           =>

Надеюсь, это имеет смысл.

Можно ли сравнивать только те значения, где совпадают идентификаторы?

Пожалуйста, дайте мне знать, если у вас есть дополнительные вопросы.

Благодарю.

1 ответ

Решение

Проблема была в вашей логике итерации, которая по ошибке обрабатывала несколько правил из документа XML за одну итерацию:

  • foreach ($xmlRule in $ruleSet) ничего не перечислил - вместо этого он обработал единственный <ruleset> элемент; перечислить ребенка <rule> элементы, вы должны использовать $ruleSet.rule,

  • $xmlRule.rule.exceptions.exception.url затем неявно повторяется по всем <rule> дети и, следовательно, сообщили URL-адреса по всем из них, что объясняет дополнительные строки в вашем Compare-Object выход.

Вот упрощенная аннотированная версия вашего кода:

$existingWaf = Get-Content -LiteralPath stack.json | ConvertFrom-Json
$xmlFile = [xml] (Get-Content -raw -LiteralPath stack.xml )

# No need for a loop; $xmlFile is a single [System.Xml.XmlDocument] instance.
$ruleSet = $xmlFile.waf.ruleset

foreach ($siteRule in $ExistingWaf.security.waf.rules)
{
    # !! Note the addition of `.rule`, which ensures that the rules
    # !! are enumerated *one by one*. 
    foreach ($xmlRule in $ruleSet.rule)
    {
        if ($xmlRule.id -eq $siteRule.id)
        {
          # !! Note: `$xmlRule` is now a single, rule, therefore:
          # `$xmlRule.rule.[...]-> `$xmlRule.[...]`
          # Also note that neither @(...) nor Select-Object are needed, and
          # the `| where ...` (Where-Object) is not needed.
          Compare-Object -ReferenceObject $siteRule.exceptions.values.urls.value `
                         -DifferenceObject $xmlRule.exceptions.exception.url -IncludeEqual
        }
    }
}

Дополнительные замечания относительно вашего кода:

  • Нет необходимости гарантировать, что операнды переданы Compare-Object являются массивами, поэтому нет необходимости заключать их в оператор подвыражения массива @(...), Compare-Object отлично справляется со скалярными операндами.

  • ... | Select-Object является виртуальным запретом - входной объект пропущен через [1]

  • ... | Where-Object {$xmlRule.rule.id -eq $siteRule.id} бессмысленно, потому что он дублирует вложение foreach состояние петли.

    • Вообще говоря, потому что вы не обращаетесь к входному объекту конвейера под рукой через автоматическую переменную $_, ваш Where-Object Фильтр статичен и будет соответствовать либо всем входным объектам (как в вашем случае), либо ни одному.

[1] Существует тонкий, невидимый побочный эффект, который обычно не имеет значения: Select-Object добавляет невидимое [psobject] Обертка вокруг входного объекта, которая в редких случаях вызывает другое поведение - см. эту проблему GitHub.

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