Главная страница
Навигация по странице:

  • COERCE arg type [System.Management.Automation.PSObject] to [System.Int64] CONVERT arg type to param type using LanguagePrimitives.ConvertTo

  • Эффективное программирование в Windows PowerShell Разбираться в Windows PowerShell и получать от него больше


    Скачать 0.88 Mb.
    НазваниеЭффективное программирование в Windows PowerShell Разбираться в Windows PowerShell и получать от него больше
    АнкорEffective_Windows_Powershell_RU
    Дата22.05.2020
    Размер0.88 Mb.
    Формат файлаpdf
    Имя файлаEffective_Windows_Powershell_RU.pdf
    ТипРеферат
    #124663
    страница6 из 6
    1   2   3   4   5   6
    BIND arg [1] to param [Id] SKIPPED
    Parameter [Id] PIPELINE INPUT ValueFromPipeline WITH COERCION
    BIND arg [1] to parameter [Id]
    COERCE arg type [System.Management.Automation.PSObject] to [System.Int64[]]
    ENCODING arg into collection
    Binding collection parameter Id: argument type [PSObject], parameter type
    System.Int64[]], collection type Array, element type [System.Int64], coerceElementType
    Creating array with element type [System.Int64] and 1 elements
    Argument type PSObject is not IList, treating this as scalar
    COERCE arg type [System.Management.Automation.PSObject] to [System.Int64]
    CONVERT arg type to param type using LanguagePrimitives.ConvertTo
    CONVERT SUCCESSFUL using LanguagePrimitives.ConvertTo: [1]
    Adding scalar element of type Int64 to array position 0
    Executing VALIDATION metadata:
    [System.Management.Automation.ValidateRangeAttribute]
    BIND arg [System.Int64[]] to param [Id] SUCCESSFUL
    MANDATORY PARAMETER CHECK on cmdlet [Get-History]
    CALLING ProcessRecord
    CALLING EndProcessing
    Заметьте, что при первой попытке PowerShell пытается конвертировать строку в массив Int64, но ему это не удаётся. Затем он пытается обработать исходные данные как PSObject. Он передаёт этот PSObject
    функции вспомогательного класса LanguagePrimitives.ConvertTo(), которая успешно преобразовывает '1' в массив Int64, содержащий элемент 1.
    Когда параметр помечен и как ByValue и как ByPropertyName одновременно, PowerShell пытается связать его с аргументами в следующем порядке:
    1. Привязать по значению, без преобразования типов
    2. Привязать по имени свойства без преобразования типов
    3. Привязать по значению, с преобразованием типов
    4. Привязать по имени свойства без преобразования типов
    Кроме того, еще используется дополнительная логика при нахождении лучшего совпадения среди нескольких наборов параметров.
    Последнее замечание, касающееся параметров. Файлы справки PowerShell создаются не полностью автоматически, и, как результат - они не всегда корректны. Например, посмотрите справку к Get-Content, и попробуйте найти упоминание параметра –Wait. Вы не найдете его. Однако метаданные командлета всегда содержат полную и точную информацию, например:
    PS> Get-Command Get-Content -Syntax
    Get-Content [-Path] [-ReadCount ] [-TotalCount ] [-Filter ]
    [-Include ] [-Exclude ] [-Force] [-Credential
    ] [-Verbose]
    [-Debug] [-ErrorAction ] [-ErrorVariable ] [-OutVariable ]
    [-OutBuffer ] [-Delimiter ] [-Wait] [-Encoding ]
    Get-Content [-LiteralPath] [-ReadCount ] [-TotalCount ] [-Filter ]
    [-Include ] [-Exclude ] [-Force] [-Credential
    ] [-Verbose]
    46

    [-Debug] [-ErrorAction ] [-ErrorVariable ] [-OutVariable ]
    [-OutBuffer ] [-Delimiter ] [-Wait] [-Encoding ]
    Надеюсь, эта глава добавила вам немного понимания о том, как работают параметры привязывающиеся по значению (ByValue), и не только. В итоге, практически, вам не нужно много знать о привязке параметров, потому что в большинстве случаев она работает интуитивно. Просто старайтесь обращать внимание на те параметры, которые привязываются по имени (ByPropertyName). Их привязка в конвейере не всегда является очевидной.
    47

    Часть 10: Регулярные выражения – один из мощнейших инструментов
    PowerShell
    Windows PowerShell основан на .NET Framework. То есть, он построен с использованием .NET Framework и предоставляет возможности .NET Framework пользователю. Одна из наиболее удобных возможностей в .NET Framework - класс Regex в пространстве имён System.Text.RegularExpressions. Это очень мощный механизм регулярных выражений. PowerShell использует его в ряде сценариев с помощью:

    оператора -match

    оператора -notmatch

    параметра -Pattern командлета Select-String
    Конечно, чтобы использовать эти операторы и Select-String максимально эффективно, необходимо хорошо понимать применение регулярных выражений. Справочная система PowerShell содержит раздел с именем "about_Regular_Expression", который вы можете вызвать так:
    PS> help about_reg*
    Данный раздел представляет собой лишь краткое справочное руководство по различным метасимволам, с его помощью вы не научитесь создавать мощные регулярные выражения. Чтобы узнать, как получить максимальную отдачу от регулярных выражений, и, следовательно, PowerShell, я очень рекомендую прочитать книгу Jeffrey Friedl Mastering Regular Expressions (Джеффри Фридл, Регулярные выражения).
    В механизме поддержки регулярных выражений PowerShell существует один недостаток, который необходимо знать. Большинство других скриптовых языков поддерживают синтаксис, позволяющий обнаружить все вхождения шаблона в строке. Например, в Perl я могу сделать так:
    $_ = "paul xjohny xgeorgey xringoy stu pete brian"; # PERL script
    ($first, $second, $third) = /x(.+?)y/g;
    К сожалению, командлет Select-String не поддерживает эту возможность в версии 1.0. Тем не менее, вы можете обойти это ограничение, используя класс System.Text.RegularExpressions.Regex напрямую. Нет необходимости указывать имя класса полностью, PowerShell имеет для него псевдоним [regex].
    Например:
    PS> $str = "paul xjohny xgeorgey xringoy stu pete brian"
    PS> $first,$second,$third = ([regex]'x(.+?)y').Matches($str) |
    >>Foreach {$_.Groups[1].Value}
    PS> $first john
    PS> $second george
    PS> $third ringo
    Одна из вещей, за которой необходимо тщательно следить - написание регулярного выражения для поиска во всей строке. Например, вы решили использовать Get-Content для получения содержимого файла и применения к нему регулярного выражения. В этом случае необходимо помнить, что Get-
    Content считывает файл частями - строка за строкой. Для применения регулярного выражения ко всему содержимому файла, необходимо будет преобразовать это содержимое в одну строку. Я могу сделать это в PowerShell 1.0, например, так:
    48

    PS> $regex = [regex]'(?/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
    PS> Get-Content foo.c | Join-String -Newline | Foreach {$regex.Matches($_)} |
    >> Foreach {$_.Groups["CMultilineComment"].Value}
    >>
    Здесь я использовал командлет Join-String, разработанный PowerShell Community Extensions, который принимает отдельные строки, выводимые Get-Content, и создаёт из них одну строку. Также в примере использован именованный захват CMultilineComment. Этот пример демонстрирует, что при отсутствии возможностей в самом PowerShell, использование среды .NET может стать замечательным "запасным выходом".
    Дополнение для PowerShell 2.0
    В PowerShell 2.0 появились новые возможности, помогающие выполнять поиск в вышеописанном случае. Во-первых, добавлен оператор объединения, с помощью которого можно соединять многострочный текст в одну строку. Во-вторых, в Select-String появились новые параметры, такие как
    -Context, -NotMatch и -AllMatches. Параметр AllMatches как раз подходит для решения предыдущей задачи. Вот как можно выполнить поиск комментариев в PowerShell 2.0:
    $pattern = '(?/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
    PS> (get-content .\foo.c) -join "`n" | Select-String $pattern -all |
    >>Foreach {$_.Matches} | Foreach {$_.Value}
    Использование регулярных выражений - очень мощный инструмент PowerShell. Научитесь его использовать, и он откроет вам много возможностей поиска и обработки текстовых данных.
    49

    Часть 11: Сравнение массивов
    В PowerShell используется несколько полезных операторов вроде -contains, которые проверяют, содержит ли массив заданный элемент. Но насколько я могу сказать, в PowerShell отсутствует простой способ проверить, является ли содержимое двух массивов идентичным. Такая потребность возникает довольно часто, и меня слегка удивило отсутствие такой возможности.
    Я столкнулся с такой необходимостью при ответе на вопрос в группе новостей
    microsoft.public.windows.powershell. Его автор спрашивал о поиске файлов с кодировкой UTF-8 при помощи проверки метки порядка байтов (англ. Byte Order Mark, BOM - способ определения формата представления Юникода в текстовом файле; для обозначения формата UTF-8 в заголовке файла используется последовательность EF BB BF - прим. переводчика). Одним из простых способов решения такой задачи может быть таким:
    PS> $preamble = [System.Text.Encoding]::UTF8.GetPreamble()
    PS> $preamble | foreach {"0x{0:X2}" -f $_}
    0xEF
    0xBB
    0xBF
    PS> $fileHeader = Get-Content Utf8File.txt -Enc byte -Total 3
    PS> $fileheader | foreach {"0x{0:X2}" -f $_}
    0xEF
    0xBB
    0xBF
    Визуально, конечно, легко проверить, совпадают значения или нет. Но, к сожалению, визуальная проверка не работает в сценариях. Можно также проверять каждый отдельный элемент, что годится для трех элементов массива, но когда вы столкнётесь, скажем, с 10 элементами, этот подход начинает выглядеть утомительным. Вы думаете, что мы могли бы просто сравнить эти два массива непосредственно примерно так:
    PS> $preamble -eq $fileHeader | Get-TypeName
    WARNING: Get-TypeName did not receive any input. The input may be an empty collection. You can either prepend the collection expression with the comma operator e.g. ",$collection | gtn" or you can pass the variable or expression to Get-TypeName as an argument e.g. "gtn $collection".
    PS> $preamble -eq 0xbb
    187
    ЗАМЕЧАНИЕ: Get-TypeName - это функция, написанная сообществом PowerShell Community
    Extensions.
    Сравнение массивов с помощью оператора -eq на самом деле не сравнивает содержимое этих двух массивов. Как показывает код в первой строке, эта конструкция ничего не возвращает. Когда слева от оператора -eq расположен массив, PowerShell возвращает элементы массива, значение которых
    совпадает со значением, заданным справа от оператора. Последние две строки в примере выше это поясняют - при сравнении с '0xbb' возвращен элемент со значением 187.
    Похоже, нам придётся создать свой собственный механизм сравнения массивов. Вот один из способов:
    50
    function AreArraysEqual($a1, $a2) {
    if ($a1 -isnot [array] -or $a2 -isnot [array]) {
    throw "Both inputs must be an array"
    }
    if ($a1.Rank -ne $a2.Rank) {
    return $false
    }
    if ([System.Object]::ReferenceEquals($a1, $a2)) {
    return $true
    }
    for ($r = 0; $r -lt $a1.Rank; $r++) {
    if ($a1.GetLength($r) -ne $a2.GetLength($r)) {
    return $false
    }
    }
    $enum1 = $a1.GetEnumerator()
    $enum2 = $a2.GetEnumerator()
    while ($enum1.MoveNext() -and $enum2.MoveNext()) {
    if ($enum1.Current -ne $enum2.Current) {
    return $false
    }
    }
    return $true
    }
    Он работает так, как и ожидалось:
    PS> AreArraysEqual $preamble $fileHeader
    True
    Однако существует способ сделать сравнение и штатными средствами PowerShell, но он не совсем очевиден. По крайней мере, для меня.
    PS> @(Compare-Object $preamble $fileHeader -sync 0).Length -eq 0
    True
    Compare-Object сравнит массивы, и если различий не будет, на выход ничего передано не будет. Если мы "обернём" вывод Compare-Object в массив с помощью оператора @(), то получим массив, содержащий 0 или более элементов, в зависимости от результата. Таким образом, если длина этого массива будет равна
    0, это будет означать, что сравниваемые массивы равны.
    Compare-Object сравнивает два объекта с точки зрения одинакового набора элементов. Обычно при этом не учитывается последовательность расположения элементов. Посмотрите:
    PS> $a1 = 1,1,2
    PS> $a2 = 1,2,1
    PS> @(Compare-Object $a1 $a2).length -eq 0
    True
    51

    Скажем так, это не совсем то, когда мы говорим о сравнении на эквивалентность. К счастью, можно использовать параметр SyncWindow, установив ему значение 0. Это заставит Compare-Object сравнивать объекты, соблюдая последовательность расположения элементов в них.
    Давайте замерим производительность этих двух методов:
    PS> $a1 = 1..10000
    PS> $a2 = 1..10000
    PS> (Measure-Command { AreArraysEqual $a1 $a2 }).TotalSeconds
    1.236252
    PS> (Measure-Command { @(Compare-Object $a1 $a2 -sync 0).Length -eq 0 }).TotalSeconds
    0.3259954
    Как видим, Compare-Object "уверенно побеждает" мою функцию AreArraysEqual, что, впрочем, и не удивительно*.
    * - за исключением случая, когда происходит сравнение объектов, ссылающихся на один и
    тот же массив. В этом случае функция оказывается на два порядка быстрее. Видимо,
    Compare-Object не использует проверку System.Object.ReferenceEquals. Следует признать,
    что это частный случай сценария.
    Всё-таки, Compare-Object использует скомпилированный код, а функция - интерпретируется. Если вам необходим быстрый способ сравнить массивы, просто запомните, что массивы - это тоже объекты, а для сравнения объектов лучше всего использовать Compare-Object.
    52

    Часть 12: Старайтесь использовать Set-PSDebug -Strict в своих сценариях
    Windows PowerShell, как и многие другие динамические языки, позволяет использовать переменные без объявления типа и без присваивания им начальных значений. Это удобно для интерактивного использования, и вы можете делать что-то наподобие этого:
    PS> Get-ChildItem | Foreach -Process {$sum += $_.Name.Length} -End {$sum}
    Здесь переменная $sum не объявлена, но мы добавляем к ней значение и присваиваем его. PowerShell просто берёт значение $null и преобразует его в 0 в этом случае. Попробуйте сами ввести:
    PS> $xyzzy -eq $null
    True
    Это не означает, что ранее эта переменная была объявлена. Конечно, мы можем убедится в том, что она не определена, обратившись к диску переменных:
    PS> Test-Path Variable:\xyzzy
    False
    Так почему мы должны стараться использовать Set-PSDebug -Strict в сценариях? Дело в том, что однажды вы можете ошибиться, допустив, например, досадную опечатку в коде. На обнаружение такой ошибки и её устранение вам придётся тратить время. Возможно, вы захотите избегать повторения таких ошибок в будущем. Возьмём для примера такой скрипт:
    $suceeded = test-path C:\ProjectX\Src\BuiltComponents\Release\app.exe if ($succeeded) {
    ...
    }
    else {
    ...
    }
    Этот скрипт содержит ошибку, о которой вам не сообщит PowerShell. Он с радостью покажет build failed
    даже в случае существования файла app.exe. Потому что в названии переменной, которой присваивается результат проверки пути файла, допущена небольшая опечатка. Здесь, в маленьком фрагменте, вероятно, обнаружить опечатку не столь сложно, но если речь будет идти о сценарии в несколько сотен строк?
    Вы можете предотвратить проблемы такого рода, поместив команду Set-PSDebug -Strict в начале файла сценария, сразу после инструкции param() (если она имеется). Например, этот же сценарий в виде
    Foo.ps1:
    53

    Set-PSDebug -Strict
    $suceeded = test-path C:\ProjectX\Src\BuiltComponents\Release\app.exe if ($succeeded) {
    "yeah"
    }
    else {
    "doh"
    }
    PS C:\Temp> .\foo.ps1
    The variable $succeeded cannot be retrieved because it has not been set yet.
    At C:\Temp\foo.ps1:6 char:14
    + if ($Succeded) <<<< {
    Что бы произошло если бы мы не написали Set-PSDebug -Strict? Сценарий будет всегда выводить "doh".
    В некоторых случаях избежать подобные ошибки помогает инициализация переменных. Возможно, название этой части звучит и слишком "перестраховочно", и вы можете не использовать Set-PSDebug
    -Strict при написании сценариев. Как всегда - решать вам.
    Примечание для PowerShell 2.0
    В PowerShell 2.0, следует использовать новый командлет Set-StrictMode:
    param(...)
    Set-StrictMode –version Latest

    Set-StrictMode проверяет больше, чем использование только инициализированных переменных. Он также осуществляет проверку ссылок на несуществующие свойства, вызовы функций и методов .NET и неименованные переменные типа ${}.
    54

    Часть 13: Комментирование строк в файле сценария
    Windows PowerShell 1.0 не позволяет комментировать несколько строк. В версии 2.0 этот недостаток устранён, но я расскажу об этом в конце этого раздела. Если вы используете исключительно PowerShell
    2.0, я бы всё равно рекомендовал ознакомиться с этим разделом, потому что в нём описаны некоторые особенности использования многострочных текстовых переменных (here-string). Многострочные комментарии полезны, когда необходимо закомментировать сразу несколько строк в сценарии.
    Существует и другой допустимый приём - использование многострочных текстовых переменных. Они позволяет ввести несколько строк кода, предотвращая их немедленную интерпретацию. Однако то, как будет расценивать содержимое строки интерпретатор PowerShell, зависит от типа используемой строки.
    Например, в двойных кавычках переменные будут заменены их значениями, а выражения - выполняться. Пример строки с двойными кавычками, в которой выполнено выражение :
    PS> @"
    >> $(get-process)
    >> "@
    >>
    System.Diagnostics.Process (audiodg) System.Diagnostics.Process (csrss)

    А в строке с одинарными кавычками такого не произойдёт:
    PS> @'
    >> $(get-process)
    >> '@
    >>
    $(get-process)
    Используйте многострочный текст с одинарными кавычками для комментирования строк сценария, так как в них никакие выражения не выполняются. Необходимо лишь помнить, что строка - это выражение, и если больше ничего не делать, её содержимое будет выведено в консоль. Обычно бывает нежелательно отображать закомментированные строки на экране. Чтобы избежать вывода этой информации, можно привести строку к типу [void] или перенаправить её в $null:
    [void]@'
    "Получаем информацию о процессах"
    get-process | select Name, Id
    "Останавливаем процессы с именем vd*"
    stop-process -name vd*
    '@
    Это позволяет эффективно комментировать строки скрипта. У этого метода существует несколько особенностей. После начальных символов последовательности @' не должно быть пробелов. Если после этой последовательности будет стоять хотя бы один пробел, вы получите вот такую загадочную ошибку:
    Unrecognized token in source text.
    At C:\Temp\foo.ps1:1 char:1
    + @ <<<< '
    55

    Далее, закрывающие символы '@ должны начинаться с начала строки, иначе вы получите ещё одну загадку:
    Encountered end of line while processing a string token.
    At C:\Temp\foo.ps1:1 char:3
    + @' <<<<
    Ещё одна особенность - в PowerShell 1.0 нельзя вложить одну автономную строку в другую строку того же типа. Это означает, что вы не можете использовать строку с одинарными кавычками для окружения комментарием части сценария, если внутри этой части уже содержится строка с одинарными кавычками.
    Дополнение для PowerShell 2.0
    В PowerShell 2.0 введена корректная поддержка многострочных комментариев. Используются они так:
    <# Это многострочный комментарий в PowerShell 2.0
    #>
    И наконец-то, многострочный текст в PowerShell 2.0 может быть вложен друг в друга:
    @"
    $(Get-Process | Foreach {
    @"
    `r`n
    "@
    })
    "@
    56
    1   2   3   4   5   6


    написать администратору сайта