Любой хорошо написанный скрипт должен содержать справочную информацию о себе. Справочная информация должна содержать как минимум:

  • Целевое назначение скрипта/команды.
  • Описание параметров и правила их использования.
  • Один или несколько примеров использования скрипта/команды.

Я видел множество вариантов оформления справки. Самые популярные это:

  • Комментарий перед параметром.
  • Выделенный блок комментариев, где приводится какая-то справка по использованию в произвольном исполнении.
  • Отсутствие какой-либо справки вообще.

Я не буду останавливаться на том, как это всё выглядит, а лишь расскажу, как должна быть оформлена справка к команде. Windows PowerShell предлагает нам 2 пути.

Comment-based help

Справка, оформленная специальным образом в виде встроенного блока комментариев. Специальным образом — чтобы можно было её прочитать не только из тела самого скрипта, но и через стандартный командлет Get-Help. Если мы посмотрим на вывод командлета Get-Help, мы увидим, что вся справка разбита на категории/секции — описание, детальное описание, параметры, примеры и т.д. То же самое разделение используется и при оформлении справки в коде. Вот как выглядит общий шаблон справки:

function Remove-File {
<#
.Synopsis
    Removes the specified command.
.Description
    The Remove-File cmdlet removes one or more files.
.Parameter Path
    Specifies the path to a file.
.Parameter Force
    Supresses all confirmation prompts.
.Example
    Remove-File c:\pagefile.sys
    
    Removes pagefile.sys file from C:\ drive
.Inputs
    System.String
.Link
    http://www.contoso.com/
#>
[CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Path,
        [switch]$Force
    )
    # располагаем основной код
}

Примечание: блок встроенной справки должен располагаться либо в самом начале функции (на следующей строке после первой открывающейся фигурной скобки) или в самом конце (перед последней закрывающейся фигурной скобкой). Я предпочитаю её размещать в начале.

Уже на данном этапе мы можем вставить код в консоль и выполнить 'Get-Help Remove-File' и получите знакомую справку. При этом, все дополнительные параметры (-Full, –Detailed, –Examples, etc.) так же доступны. Точка и слово на ней означает категорию или тэг. На следующей строке размещается текст, связанный с конкретным тэгом. Некоторые тэги могут быть использованы несколько раз — .Example и .Parameter. .Example применяется столько раз, сколько у вас используется примеров. Если 5 примеров, тэг .Example должен быть использован 5 раз. То же самое касается и .Parameter — на каждый параметр определённый в скрипте должен быть использован тэг Parameter. После ключевого слова .Parameter должно следовать имя параметра на этой же строке. Полный список возможных тэгов приводится в следующей таблице:

Название тэга Описание тэга
.Synopsis Краткое описание функции.
.Description Детальное описание функции.
.Parameter <ParameterName> Описание к каждому параметру.
.Example Пример использования функции и описание примера (т.е. что произойдёт, если выполнить конкретный пример).
.Inputs Тип данных, которые принимаются функцией. Например, [System.String]. Если их несколько, можете указать несколько типов на одной или нескольких строках.
.Outputs Тип возвращаемых данных. Например, [IO.FileInfo] Если функция может возвращать несколько разных типов, их можно указать на одной или нескольких строках.
.Notes Здесь можно указать какие-то заметки. Например, я использую .Notes для указания автора кода и его адреса.
.Link Можно указать какие-то связанные ссылки. Например, на связанные команды.
.Component Можно указать продукт или компонент, для которого предназначена конкретная функция. Скажем, это может быть PKI, Exchange, SQL, SharePoint и т.д.
.Role Указывает роль пользователя, который выполняет код. В принципе, можно указать необходимые права и/или привилегии.
.Functionality Тоже можно что-то написать. Но я не вижу особой разницы с Description.
.ForwardHelpTargetName Указывает команду, на которую надо форвардить пользователя при вызове справки. Т.е. если вы считаете, что в другом командлете/функции справка описана лучше, чем у вас (и по этой же теме), можете сослать пользователя туда. На практике это применяется, когда вы делаете proxy-функции. Т.е. реализуете функционал родного командлета и немного изменяете его под конкретные нужды.
.ForwardHelpCategory Если указали предыдущий тег, надо указать конкретный раздел справки командлета, на который ссылаетесь.
.RemoteHelpRunspace Можно указать имя переменной, которая хранит данные об удалённой сессии (pssession) для поиска хелпа. Маловероятно, что он вам понадобится
.ExternalHelp Данный тэг используется для ссылки на внешний файл справки в формате XML. Об этом будет написано ниже.

По собственному опыту могу сказать, что все их заполнять не обязательно (хотя и желательно указать максимальное количество необходимой информации). Как минимум, справка должна содержать следующие секции справки: Synopsis, Description, Parameter, Example. Остальное по желанию.

External Help

Внешняя справка — это альтернативный вариант оформления справки. Если первый вариант использует специально оформленный блок комментариев в коде, второй использует внешний файл XML. Я не буду расписывать схему и структуру этого XML, просто расскажу как его можно создать и когда его лучше всего использовать.

Справку к командам в отдельном файле можно создать при помощи CmdLet Help Editor.

Замтека: этот редактор достаточно бажный и нередко вылетает с разными ошибками. Поэтому периодически сохраняйте свои изменённые данные.

Если для одиночных функций удобней всего пользоваться справкой, встроенной в код, то для больших проектов (например, у вас модуль с кучей функций) есть смысл держать всю справку в отдельном файле. Например, вы можете распотрошить мой PowerShell PKI Module и посмотреть, как оно выглядит. В каждой функции я делаю ссылку на PKI.Help.xml, который уже хранит справку для всех функций, доступных в модуле. Т.е. если вы справку держите в отдельном XML, то в коде функции вам надо сделать ссылку на этот XML:

function Remove-File {
<#
.ExternalHelp File.xml
#>
[CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Path,
        [switch]$Force
    )
    # располагаем основной код
}

Заметка: если у кого-то из вас осталась самая первая версия (0.8) PowerShell PKI модуля, можете увидеть, что там используется ещё comment-based help, а в более новых уже XML.

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

Tuesday, December 13, 2011 9:31:59 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

Продолжаю начатую в прошлый раз цикл постов про гæдлайны оформления скриптов в PowerShell или «учимся писать скрипты». Сегодня мы поговорим о таких вещах, как оформление скриптов, правила именования скриптов/функций и поговорим о типах скриптов и функций — простых и расширенных (advanced).

Оформление скриптов — шляпа

Правильно написанный скрипт, который даже в теории может быть использован кем-то другим (или прочитан), должен содержать шапку. В шапке указывается название скрипта, краткое описание его работы (функций), версия скрипта, дата последнего изменения, автор скрипта и какие-то контактные данные, если потребуется связаться с автором кода. Вот примерный шаблон этой самой шапки:

<#
Project Name
Version 0.95c

Description: the script sends you too far.

Vadims Podans (c) 2011
http://www.sysadmins.lv/
#>

Формат даты зависит от того, как часто изменяется файл. Дата должна отражать наибольшую величину, в пределах которой может быть только одна ревизия скрипта. Т.е. если он меняется чаще, чем раз в год, следует добавлять месяц. Если чаще, чем раз в месяц, лучше указать конкретную дату ревизии и т.д. Контактные данные должны быть: веб-сайт и/или почтовый адрес автора (если скрипт предназначен для незнакомых лиц, например, для публикации в блоге). Если скрипт пишется команды людей занятых в одном общем проекте, можно указать и номера телефонов. Вобщем, всё зависит от ситуации. Главное — чтобы автора кода можно было бы как-то найти, если возникнут вопросы.

Именование функций или скриптов

Одна из особенностей PowerShell — унифицированный синтаксис. Как минимум все встроенные командлеты обзываются по простому принципу: Действие-Объект. Действие определяет что будет происходить с объектом и объект — это объект, над которым выполняется действие. Очевидных примеров примерно овер9000 — Copy-Item, Get-WMIObject и т.д. Можно даже не смотреть в справку, чтобы понять хотя бы примерно, что делает тот или иной командлет. Правильно написанный скрипт должен обзываться по такому же принципу. PowerShell определяет целый ряд разрешённых действий, которые следует применять. Например, ваш скрипт что-то удаляет — действие должно называться Remove, а не Delete или что-то ещё. Если вы не уверены в том, подпадает ли ваше название действия под разрешённое, вы всегда можете себя проверить выполнив команду Get-Verb. Если вы в списке не нашли такого, какой вы хотите — старайтесь использовать стандартное действие, которое максимально приближено по смыслу к вашему. Возвращаясь к нашему примеру, максимально приближенное действие к Delete является Remove.

Вот пример плохого названия скрипта/функции, написанного пкитимом (это который Windows PKI team): http://technet.microsoft.com/en-us/library/ff961506(WS.10).aspx. Я буду неоднократно ссылаться на него, как годный экземпляр несоблюдения гайдлайнов оформления скрипта (хотя, свою задачу он выполняет как положено :))

Т.е. вместо PKISync.ps1, скрипт следовало бы назвать Sync-PKI.ps1. Стандартов по описанию объектов почти нету, кроме одного — название объекта, над которым совершается действие должно быть в единственном числе. Не Remove-Users, а Remove-User, даже если ваш скрипт предполагает удаление множества пользователей. Всегда и во всех случаях, название объекта должно быть в единственном числе. Стандартизация именования всего и вся — это уже очень много и даёт как минимум +3 поинта вашим скриптинг скиллзам.

Функции — простые и расширенные функции

В предыдущий раз я говорил исключительно про расширенные или advanced функции, хотя и упоминал, что функционал возможно реализовать силами простых функций. Почему так? Простые функции изжили себя? Они не подпадают под бест-практисы? Вовсе нет. Дело в том, что простые функции (единственный тип функций, который был доступен в PowerShell 1.0) обладают достаточно небольшим функционалом, не позволяют создавать cmdlet-стайл справку и возлагают на пользователя всю работу по обработке параметров — взаимодействия с пользователем. С появлением расширенных функций, солидную часть этой работы мы можем обратно свалить на эти самые функции. При этом работа с такими функциями внешне неотличима от работы с родными командлетами.

Проблема появилась, когда пользователи стали писать свои скрипты и засорять имивыкладывать их в интернеты, чтобы другие могли воспользоваться. Функционально многие были очень полезными, а какие-то не очень. Но оформление скриптов, определение и парсинг параметров в большинстве случаев был просто ужасен. Каждый писал так, как он мог или умел. Думаете, что только рядовые пользователи грешили этим? Отнюдь! Практически каждая продакт-группа Microsoft писала свои скрипты так, что лучше бы не писали. В качестве примера можете посмотреть пример уже упомянутого скрипта PKISync.ps1. Посмотрите первые 70 строк и вы всё поймёте. Поэтому не обязательно считать себя самым главным неудачником в этой жизни. Вы не одни :)

Кстати говоря: ряд продуктовых команд Microsoft (в особенности Windows PKI team) люто-бешено не переваривают PowerShell и применяют его только потому что их заставляют. Будь их воля, они бы так и жили в мире cmd и VBS. Я очень надеюсь, что они не читают мой русскоязычный бложек, иначе доступ к ТЗ мне будет резко закрыт :)

Итак, простые и расширенные функции — что и когда применяем. Ту часть скрипта, которая отвечает за взаимодействие с пользователем (это определение параметров, их возможности и встроенная справка) необходимо оформлять только и только в виде расширенных функций. Рассмотрим простой пример. У нас есть функция, которая принимает 2 аргумента, один из них должен быть обязательно указан. Вот как примерно выглядит код простой функции:

function Test-Me {
    param(
        [string]$arg1 = $(throw "The parameter must not be empty"),
        [string]$arg2
    )
    # some stuff
}

Заметка: это ещё хороший вариант кода. Вышеупомянутый скрипт PKISync.ps1 вообще явно не определяет аргументы а парсит переменную $args, куда все аргументы падают.

При этом, весьма неочевидно, какой из них не может быть пустым (это было в PS 1.0). Или его имя явно указывать в скриптоблоке throw.

[↓] [vPodans] Test-Me The parameter must not be empty At line:3 char:32 + [string]$arg1 = $(throw <<<< "The parameter must not be empty"), + CategoryInfo : OperationStopped: (The parameter must not be empty:String) [], RuntimeException + FullyQualifiedErrorId : The parameter must not be empty [↓] [vPodans]

Мы бы хотели более дружелюбное поведение, как у родных командлетов — любезно попросить пользователя указать обязательный параметр. Как пример, выполните Remove-Variable без указания какого-либо аргумента. При этом, все аргументы определённые в секции param() не могут принимать данные из конвейера. Это всё потому, что данные из конвейера приходили в динамическую (которую нельзя чётко определить) переменную — $_. Плюс, полное отсутствие возможности встроить справку, чтобы она была доступна при вызове команды "Get-Help Test-Me". Список недостатков простых функций бесконечный.

Следовательно, основную функцию надо оформлять только в виде расширенной функции. Что делает функцию расширенной?

  • [CmdletBinding()] в самом начале скрипта;
  • Применение параметра аргумента.

изменим наш код до вида расширенной функции:

function Test-Me {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$arg1,
        [string]$arg2
    )
    Write-Host My arg1 is "'$arg1'"
}

[↓] [vPodans] Test-Me cmdlet Test-Me at command pipeline position 1 Supply values for the following parameters: arg1: мимими My arg1 is 'мимими' [↓] [vPodans]

Эпик! Вы не отличите внешнее поведение нашей функции от того же Remove-Variable. А что мы сделали? Мы преобразовали простую функцию в расширенную (путём добавления [CmdletBinding()] или конструкции в квадартных скобках в секции param(), которая определяет дополнительные требования к переменной.

Причём, мы можем сказать, что переменная $arg1 должна принимать значения из конвейера:

function Test-Me {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$arg1,
        [string]$arg2
    )
    Write-Host My arg1 is "'$arg1'"
}

[↓] [vPodans] "няяяяя!!!" | Test-Me My arg1 is 'няяяяя!!!' [↓] [vPodans]

Как видите, в коде изменилось только то, что мы явно разрешили параметру arg1 принимать аргументы из конвейера. Так же, я выкинул [CmdletBinding()], чтобы продемонстрировать, что простая функция превращается в advanced при применении конструкций в квадратных скобках. О конструкциях в квадратных скобках, известных как параметры аргументов, смотрите встроенную справку:

Get-Help about_Functions_Advanced
Get-Help about_Functions_Advanced_CmdletBindingAttribute
Get-Help about_Functions_Advanced_Methods
Get-Help about_Functions_Advanced_Parameters

Примечание: мой совет, всегда указывайте [CmdletBinding()] в начале скрипта (должен идти до секции param()), чтобы можно было сразу сказать, что это расширенная функция. Так же, учтите, что если эта конструкция указана, но ваша функция никаких аргументов не принимает, вы должны прописать секцию param(). Хотя бы пустую.

Надо ли понимать, что простые функции изжили себя? Отнюдь! Простые функции до сих пор используются как минимум в качестве вспомогательных функций. Если посмотрите мои скрипты, вспомогательные функции используются достаточно активно. Суть вспомогательной функции — повсеместное использование определённого кода несколько раз в теле основной функции. Например, реальный пример из моей жизни. Я использую какие-то API, которые возвращают Unicode строку в виде байтового массива. Причём эти API используются несколько раз. Чтобы каждый раз не писать конвертер, который преобразует этот байтовый массив в нормальную строку (плюс, сначала, преобразует little-endian последовательность в big-endian):

function Backup-CertificationAuthority {
[CmdletBinding()]
    param (
        # тут разные аргументы
    )
    function Split-DataPath ([Byte[]]$Bytes) {
        $SB = New-Object System.Text.StringBuilder
        $bytes1 = $bytes | %{"{0:X2}" -f $_}
        for ($n = 0; $n -lt $bytes1.count; $n = $n + 2) {
            [void]$SB.Append([char](Invoke-Expression 0x$(($bytes1[$n+1]) + ($bytes1[$n]))))
        }
        $SB.ToString().Split("`0",[StringSplitOptions]::RemoveEmptyEntries)
    }
    # тут всяко-разный код
    $Bytes = # тут получаем наши байтики из API
    $FileName = Split-BackupPath $Bytes
}

в данном случае я точно знаю, что при вызове вспомогательной функции, в переменной $Bytes всегда будут байты. Это будет реализовано на уровне кода (здесь не показано). Поэтому я использую расширенный тип функции для основной (которая взаимодействует с пользователем), а вспомогательные функции делаю простыми.

На сегодня всё. Я буду стараться писать по немножку, чтобы вы могли спокойно обдумать и переварить материал. В следующий раз продолжим.

Thursday, December 01, 2011 11:02:05 PM (FLE Standard Time, UTC+02:00)   Comments [6]    

 

Мы с Васей Гусевым задумали утопичную идею составить какой-то набор гæдлайнов как правильно писать скрипты для PowerShell. Так исторически сложилось, что каждый пишет скрипты так, как он хочет/удобней/умеет. PowerShell один из самых новых языков скриптования (в отличии от vbs/cmd) и он внёс совершенно новый синтаксис и идею оформления кода, который как правило несовместим с жизнью с другими языками.

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

Итак, сегодняшняя тема (посты в категории guildelines не будут последовательными) — как правильно реализовывать специальные параметры –Force, –WhatIf, –Confirm –Verbose и –Debug в скриптах и функциях.

Параметры Force, WhatIf и Confirm

  • Force

Данный параметр предназначен в общем случае для подавления запросов на проведение каких-то операций. Например, запрос на перезапись существующего файла.

  • WhatIf

Предназначен для эмуляции выполнения команды и вывода информации, о том, что бы случилось, если выполнить данную команду без этого параметра. Для примера можете посмотреть вывод команды:

Remove-Item C:\Windows\notepad.exe -WhatIf

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

  • Confirm

Если ваш скрипт выполняет какие-то серьёзные и/или деструктивные действия (например, как командлет Set-ExecutionPolicy), следует применять параметр Confirm.

Определение этих параметров в коде

Применение этих параметров в простых функциях (не advanced) по сути сводится к написанию собственных обработчиков этих параметров. В Advanced functions появилась возможность универсально обрабатывать эти параметры. Вот пример кода:

function Remove-File {
[CmdletBinding(
    ConfirmImpact = 'High',
    SupportsShouldProcess = $true
)]
    param(
        [string]$Path,
        [switch]$Force
    )
    if ($Force -or $PSCmdlet.ShouldProcess($Path,"Delete file")) {
        Remove-Item $Path
    }
}

И вот вывод какое у нас получится поведение:

[↓] [vPodans] Remove-File .\Desktop\qsca.txt Confirm Are you sure you want to perform this action? Performing operation "Delete file" on Target ".\Desktop\qsca.txt". [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): n [↓] [vPodans] Remove-File .\Desktop\qsca.txt -WhatIf What if: Performing operation "Delete file" on Target ".\Desktop\qsca.txt". [↓] [vPodans] Remove-File .\Desktop\qsca.txt -Force [↓] [vPodans]

В данном случае предполагается, что функция выполняет деструктивные действия, поэтому по умолчанию мы выводим запрос о подтверждении операции по умолчанию. Если укажем WhatIf, мы увидим, что бы случилось при обычном запуске (с подтвеждением операции). Но если мы хотим выполнить операцию без всяких подтверждений, мы используем параметр Force.

Как мы добились такого поведения? Во-первых, конструкция в начале скрипта [CmdletBinding()] определяет общее поведение всего скрипта. У него есть разные атрибуты. В данном случае были использованы:

  • SupportsShouldProcess

Этот атрибут указывает, должен ли код ожидать ответ от пользователя на выполнение операции. Т.е. он включает обработчик для параметров –Confirm и –WhatIf.

  • ConfirmImpact

Этот атрибут указывает уровень воздействия кода на систему и включает обработчик параметра –Confirm. Возможные значения:

  1. None (или атрибут не указан вообще) — никаких сообщений подтверждения операции выводиться не будет. Даже если вы явно укажете параметр Confirm.
  2. Low — код оказывает минимальное воздействие на систему с минимальным риском потери данных.
  3. Medium — код оказывает среднее воздействие с некоторым риском потерять данные или произвести деструктивные действия.
  4. High — выполняемый код обладает высоким риском потери данных. Например, уровень High выставлен у командлета Set-ExecutionPolicy.

По умолчанию в сессии PowerShell уровень воздействия выставлен в High (можно посмотреть в переменной $ConfirmPreference). И все командлеты, у которых уровень воздействия на систему выше или такой же, как в $ConfirmPreference, запрос на подтверждение будет выводиться всегда.

Например, в сессии у нас $ConfirmPreference = 'High', а у командлета/функции тоже 'High' будет выведен запрос подтверждения. Как в случае с Set-ExecutionPolicy. Если у командлета/функции ConfirmImpact ниже (Low или Medium) запрос подтверждения по умолчанию выводиться не будет. Но возможно указать –Confirm для принудительного вывода запроса подтверждения. Если посмотреть на наш код, мы увидим, что уровень воздействия на систему высокий, поэтому мы каждый раз получаем запрос подтверждения. Если мы изменим уровень воздействия в уровень 'Medium', наша функция не будет по умолчанию выводить никаких запросов. Но будет выводить его при указании параметра –Confirm:

function Remove-File {
[CmdletBinding(
    ConfirmImpact = 'Medium',
    SupportsShouldProcess = $true
)]
    param(
        [string]$Path,
        [switch]$Force
    )
    if ($Force -or $PSCmdlet.ShouldProcess($Path,"Delete file")) {
        Remove-Item $Path
    }
}

И вот какое у нас будет поведение:

[↓] [vPodans] Remove-File .\Desktop\qsca.txt -Confirm Confirm Are you sure you want to perform this action? Performing operation "Delete file" on Target ".\Desktop\qsca.txt". [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): n [↓] [vPodans] Remove-File .\Desktop\qsca.txt [↓] [vPodans]

Теперь запрос выводится только когда мы явно указываем параметр –Confirm. Параметры мы подключили, как реализовать код? Для этого есть специальная переменная $PsCmdlet, которая используется для обработки параметров командлета/функции. У этой переменной есть метод ShouldProcess("Target","Action description"). В аргумент Target указываете название объекта (в нашем случае — имя файла), над которым будет совершено действие, а в Action description пишите, какое действие будет произведено.

Указание параметра –WhatIf и/или –Confirm вызывает метод ShouldProcess и в зависимости от параметров выполняет нужное действие — выводит запрос или эмулирует выполнение команды. Поэтому мы просто засовываем $PSCmdlet.ShouldProcess() в условный оператор IF и в конструкции Then пишем код, который будет выполнять действие.

Чтобы реализовать функционал параметра –Force, я его поместил в то же условие. Причём, он должен проверяться первым. Это связано с тем, что при выполнении оператора –or, сначала проверяется выражение слева от оператора. Если оно возвращает True, выражение справа от оператора не проверяется. Следовательно, если параметр –Force указан, метод ShouldProcess() просто не вызовется.

Параметры Verbose и Debug

При распитиинаписании скриптов, весьма целесообразно включать отладочную информацию в скрипт, которую можно посмотреть, если что-то идёт не так. Запомните, НИКАКИХ этих ваших Write-Host или чего-то ещё. Для этого предусмотрены параметры Verbose и Debug. Verbose применяется для вывода на экран общего хода выполнения скрипта. Debug применяется для включения уже детальной отладочной информации и возможно переключение в пошаговое исполнение скрипта. Эти параметры не надо определять в параметрах функции, они автоматически добавляются к коду при использовании конструкции [CmdletBinding()].

Вот как правильно определять поведение этих параметров:

function Get-Something {
[CmdletBinding()]
    Param()
    if ($PSBoundParameters.Verbose) {$VerbosePreference = "Continue"}
    if ($PSBoundParameters.Debug) {$DebugPreference = "Continue"}
    Write-Verbose "Type some verbose information"
    Write-Debug "Type some debug information"
}

Как мы уже знаем, если указана конструкция [CmdletBinding()], эти параметры автоматически подключаются к функции и мы их не определяем в секции param(). Чтобы отловить эти параметры, мы используем специальную переменную $PSBoundParameters. Более подробней об этой переменной я писал в статье: Cmdlet wrapping и PsBoundParameters. По умолчанию, $VerbosePreference и $DebugPreference = 'SilentlyContinue', т.е. даже при указании этих параметров вы ничего не увидите. Поэтому, если параметр указан при вызове функции, мы переводим их в состояние 'Continue', что включает вывод для Verbose и Debug.

Давайте посмотрим более реальный случай в несферическом вакууме — сброс пароля локального администратора:

function Reset-LocalAdministratorPassword {
[CmdletBinding(
    ConfirmImpact = 'High',
    SupportsShouldProcess = $true
)]
    param(
        [string]$Password,
        [switch]$Force
    )
    if ($Verbose) {$VerbosePreference = "Continue"}
    if ($Debug) {$DebugPreference = "Continue"}
    $Computer = $Env:COMPUTERNAME
    Write-Debug "Connecting to ADSI provider on '$Computer'"
    $winnt = [ADSI]("WinNT://$Computer,computer")
    Write-Debug "Attempting to retrieve local administrator object"
    $User = $winnt.psbase.children.Find("administrator")
    Write-Debug "Write new password for local administrator. New password '$Password'"
    $User.psbase.invoke("SetPassword",$Password)
    if ($Force -or $PsCmdlet.ShouldProcess("Administrator","Set new password for")) {
        Write-Verbose "Setting new password for local administrator on '$env:computername'"
        Write-Debug "Writing new password for local administrator on '$env:computername'"
        $User.psbase.CommitChanges()
    }
}

И вот вывод в консоли для –Verbose:

[↑] [system32] Reset-LocalAdministratorPassword -Password "P@ssw0rd" -Verbose Confirm Are you sure you want to perform this action? Performing operation "Set new password for" on Target "Administrator". [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): VERBOSE: Setting new password for local administrator on 'THOR' [↑] [system32]

для –Debug:

[↑] [system32] Reset-LocalAdministratorPassword -Password "P@ssw0rd" -Debug DEBUG: Connecting to ADSI provider on 'THOR' Confirm Continue with this operation? [Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): DEBUG: Attempting to retrieve local administrator object Confirm Continue with this operation? [Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): DEBUG: Write new password for local administrator. New password 'P@ssw0rd' Confirm Continue with this operation? [Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): Confirm Are you sure you want to perform this action? Performing operation "Set new password for" on Target "Administrator". [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): DEBUG: Writing new password for local administrator on 'THOR' Confirm Continue with this operation? [Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): [↑] [system32]

Так же, мы видим, что Debug включает пошаговое исполнение скрипта, вне зависимоти от состояния параметра Force. Это только в случае, если у нас включен SupportsShouldProcess.

На сегодня вроде всё.

Sunday, November 27, 2011 6:38:24 PM (FLE Standard Time, UTC+02:00)   Comments [2]    

 

Собственно, добавлять тут особо нечего :). Детали нового релиза можно прочитать здесь и здесь. А остальное здесь:

>> PS PKI Module v0.9.2 <<

Однако, хочу добавить одну полезную (для меня) вещицу. На главной я вывешиваю список всех командлетов, которые есть в модуле с алиасами, если есть и ссылками на страницы wiki со справкой для командлетов. Некоторые могут подумать, что я это делаю вручную (первый раз я действительно делал в ручную и сильно устал). Конечно же, можно делать вручную, а можно и автоматически генерировать текст (если быть точнее, полный код HTML) при помощи PowerShell. Здесь есть много логики — ссылки на справку командлетов содержат их названия: http://pspki.codeplex.com/wikipage?title=CmdletName. Т.е. достаточно иметь основную часть ссылки и просто в конец добавлять название командлета. Однако, для ряда командлетов я применяю алиасы, чтобы можно было вводить команды покороче. Чтобы определить, есть ли у командлета алиасы, можно сделать вот так:

PS C:\> Get-Alias -Definition Get-Alias CommandType Name Definition ----------- ---- ---------- Alias gal Get-Alias PS C:\>

Вот так вы можете узнать, есть ли у командлета алиас или нету (тогда пошик насыпет вам много красного). Т.е. можно написать несколько строчек кода:

# через Get-Command получаем список команд, типа функции (по дефолту Get-Command
# выдаёт всё, что относится к командам, включая алиасы), которые относятся к
# конкретному модулю
gcm -Module pki -CommandType function | %{
# определяем, есть ли алиас у командлета
$alias = gal -Definition $_.name -ea 0
# если есть, тогда делаем код HTML с алиасом
if ($alias) {
@"
<li><a href="http://pspki.codeplex.com/wikipage?title=$($_.name)">$($_.name)</a> (Alias: <strong>$($alias.name)</strong>) </li>
"@    
} else {
# если нету, тогда просто код HTML без алиаса
@"
<li><a href="http://pspki.codeplex.com/wikipage?title=$($_.name)">$($_.name)</a> </li>
"@}
}

и всё. Просто в конце добавляете '| clip', чтобы он весь код HTML не выкидывал в консоль, а буфер обмена и получаете профит. Просто вставляете в редактор кодеплекса и всё.

Естественно, это была реклама пошика :)

Wednesday, November 16, 2011 5:23:13 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

КДПВВ разных интернетах ходят разные толки по поводу Policy CA. В своё время я давал краткую справку о нём: Обсуждение схем иерархии Certification Authority и есть необходимость немного дополнить ту статью и показать применением выделенного Policy CA в реальном мире. Назначение Policy CA вполне понятно — разделять PKI на одну или несколько веток, которые будут различаться правилами выдачи и обслуживания сертификатов. Интернеты (и разные там специалисты) всячески рекомендуют деплоить 3-х уровневые иерархии вида:

Offline Root CA
           Offline Policy CA
                      Online Issuing CA

Это считается бест-практисом. Отдельные люди указывали мне, что совмещение ролей (Policy и Issuing) не есть хорошо по причине безопасности и всё такое. Но если говорить о бест-практисах, то их здесь нету совсем. Есть только некоторые рекомендации, по которым можно подобрать себе оптимальную иерархию. Всё остальное является следствием буйной фантазии. Давайте ещё раз пробежимся по этим моментам.

Когда применяем 1-уровневую иерархию?

Если у вас небольшая компания (скажем, человек 10-100) без особых требований к PKI, вы спокойно разворачиваете одноуровневую иерархию с Online Root CA и используете сертификаты для логона (VPN, IIS и/или применяете смарт-карты) или шифруете данные. Как пример. Хоть я в предыдущей статье весьма критически относился к такому решению, оно в реальном мире вполне себе жизнеспособно (опыт иногда меняет отношение к каким-то вопросам в ту или иную сторону). Не потому что она ВНЕЗАПНО стала безопасной, а потому что это элементарно выгодней, чем покупать отдельное железо и лицензию Windows Server, даже если посчитать все риски. Плюс, экономия на административных издержках. Т.е. это случаи, когда компрометация PKI не ведёт к краху всего бизнеса, хотя это не очень приятно.

Для сравнения можно представить, сколько администраторов не делает бакупов. Бакупы — это вообще отдельная тема и ситуация с ними катастрофическая. Вы просто не представляете сколько компаний не делают бакупы. А когда один единственный (или парочка) выходят из строя, теряя всю финансовую отчётность и вообще всё, чем занималась компания — этот фейл более реальный и более эпический.

Когда применяем 2-уровневую иерархию?

Если вы интегрируете PKI в свою сеть более тесно (вы внедряете цифровые подписи, устанавливаете VPN между сайтами, появляются удалённые SCCM/OpsMgr клиенты и т.д.), вы скорее всего будете более благоразумны и примените 2-х или 3-х уровневую иерархию. На этом этапе у вас PKI может начать делиться, потому что сферы применения PKI становятся шире. И вот здесь может появиться желание вбахать выделенный Policy CA, который опишет политику применения (CPS) сертификатов в вашей организации. С одной стороны это выглядит идеологически правильно. Но с другой стороны это не очень практично. Дело в том, что CPS можно прикрутить и к издающим CA, совместив роль Policy и Issuing CA. Об этом я уже писал в предыдущей статье.

Даже если у вас на уровне шаблонов сертификатов действуют разные политики, их вполне возможно описать в пределах одного CPS. Политики на уровне шаблонов могут быть очень разнообразные: например, чтобы получить сертификат для шифрования данных достаточно залогониться в систему. А чтобы получить логонный сертификат на смарт-карте, нужно оформить соответствующую заявку и нотариально заверить у руководителя отделом. Для получения сертификата цифровой подписи (для подписи документов), нужно в должности быть руководителем какого-то отдела. Это наиболее распространённые примеры из реальной жизни.

Учитывая, что в этих случаях у вас будет один CPS, вы можете на уровне издающего CA применить OID = All Issuance Policies (2.5.29.32.0) в CAPolicy.inf:

[PolicyStatementExtension]
Policies = SomeCPS

[SomeCPS]
URL = Some URL 
OID = 2.5.29.32.0

Т.е. CA может выдавать сертификаты с использованием любых политик выдачи и использования сертификатов, которые более конкретно определены в CPS. Так же, данный сценарий позволяет провести последующую кросс-сертификацию с другой PKI (см. Что в OID'е тебе моём?). Если CA может издавать сертификаты только по конкретным политикам, вы можете ограничить CA только ими, применив следующий шаблон для CAPolicy.inf:

[PolicyStatementExtension]
Policies = CPS1, CPS2, CPS3

[CPS1]
URL = URL1
OID = 1.3.6.1.4.1.12345.1

[CPS2]
URL = URL2
OID = 1.3.6.1.4.1.12345.2

[CPS3]
URL = URL3
OID = 1.3.6.1.4.1.12345.3

В результате, CA сможет выдавать сертификаты на основе тех шаблонов (здесь я имею ввиду применение в Windows PKI), где эти политики определены.

Заметка: всякие верисайны, тафты и прочие коммерческие CA как правило применяют 1-2 уровневые (хотя есть и 3-х уровневые) иерархии. Но у них специфика другая.

Если всё так просто умещается в 2-х уровневой иерархии, когда же нам может понадобиться выделенный Policy CA?

Когда применяем 3-х уровневую иерархию?

Наконец-то добрались до цели нашей экскурсии. В реальной жизни выделенный Policy CA потребуется только когда у вас будут две и более совершенно разных политик выдачи и использования сертификатов. Наиболее классический случай — когда вы ведёте бизнес в разных странах.

Например, в России у вас головной офис, а в Европе у вас находится филиал. В этом случае у вас скорее всего политики выдачи для родной страны и Европы будут очень сильно отличаться, потому что законодательства в этих странах тоже разные. Но и здесь возможны варианты. Если все CA находятся в головном офисе (и европейский офис подключается к головному через VPN), вы можете рассмотреть предыдущий раздел. Это означает, что вы сделаете 2 издающих CA: один для локального офиса, второй для европейского. Естественно, на каждом CA будут описаны различные политики сертификатов.

Если же в европейском филиале находится свой CA и им управляет местный персонал, тогда понадобится выделенный Policy CA. Т.е. вы в головном офисе установите Policy CA, ограничите его на уровне CPS и тогда европейский офис может спокойно управлять своими издающими CA без риска нарушить политики сертификатов. При этом, для головного офиса вы можете использовать 2-х уровневую иерархию. Разумеется, в этом случае для Policy CA вы не сможете использовать All Issuance Policies, а нужно применить свой кастомный OID (или несколько, см предыдущий раздел).

Схожий сценарий: у вас есть большое количество филиалов, в каждом установлен свой издающий CA (но все они подчинаются одному корневому CA), т.к. интернеты между ними не очень шустрые и стабильные. И каждым CA в филиале управляет своя команда администраторов. В этом случае есть смысл (но совершенно не обязательно) под корнем установить Policy CA, на нём определить CPS и уже под ним размещать издающие CA филиалов. В этом случае от администраторов филиалов не требуется лишних телодвижений по прописыванию CPS в CAPolicy.inf, потому что CPS определённые на Policy CA будут наследоваться на сертификаты издающих CA.

Следующий сценарий, более специфический: вы хотите сделать root signing. Root signing — это когда коммерческий CA (VeriSign, Thawte, etc) выдаёт сертификат для вашего CA. В этом случае вы исключаете проблему организации доверия вашим сертификатам, потому что все сертификаты в этом случае будут заканчиваться корневым CA верисайна или какого-нибудь другого well-known коммерческого CA. В этом случае коммерческий CA выдаст сертификат для вашего выделенного Policy CA, который не может быть совмещён с издающим (это одно из требований к root signing).

Ещё один специфический сценарий: в вашей компании несколько лесов Active Directory. Если вы не применяете cross-forest enrollment, вам вероятнее всего придётся сделать выделенный Policy CA, который определит общую политику сертификатов для всех лесов Active Directory. И в каждом лесу уже устанавливаете издающие CA. Если же каждый лес будет иметь свой независимый набор политик сертификатов, можно ограничиться двумя уровнями.

Последний сценарий в этом разделе: вы планируете кросс-сертификацию с другой PKI. При этом под кросс-сертификацию должны подпадать все ваши издающие CA. В этом случае у вас есть выбор — оставаться на 2-х уровнях и получить на каждый CA по кросс-сертификату или сделать выделенный Policy CA и тогда получите только 1 кросс-сертификат. Вы можете сказать, что можно же получить один кросс-сертификат для корневого CA и тогда все издающие CA будут подпадать под кросс-сертификацию. С одной стороны это действительно возможно. Но кросс-сертификация подразумевает наличие CPS (а так же возможны внешние аудиты на предмет следования собственному CPS), который никогда на корневых CA не определяется.

Итоги

Про 4-х уровневую иерархию я ничего рассказывать не буду, потому что не посчастливилось лицезреть такое. Если что, можете прочитать об этом в книге Комара —http://www.sysadmins.lv/pkibookshelf.aspx. А мы, в свою очередь, немного освежили память об иерархиях в PKI и более детально рассмотрели некоторые аспекты, касающиеся Policy CA и разрушили миф о бест-практичности 3-х уровневой иерархии. Т.е. каждая PKI строится с учётом требований бизнеса и управления, а не бизнес и управление строятся под конкретную выбранную иерархию и этот пост лишь раскрывает некоторые аспекты, которыми можно руководствоваться для проектировании PKI.

В любом случае, как сделаете — так и будет хорошо. Лишь бы вы (как проектировщик PKI) подошли к делу с головой и ваше решение заработало, как запланировало.

Sunday, November 06, 2011 3:28:40 PM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

«Older Posts  ·  Newer Posts»

All content © 2008 - 2012, Vadims Podāns
"Spaces" Theme provided by: Vadims Podāns
About


E-mail - Send mail to the author(s)
Live Messenger -
For english language visitors
Библиотека
Календарик
<February 2012>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
26272829123
45678910

Карта расположения посетителей
Favorites





Disclaimer
Вся информация на сайте предоставляется на условиях «как есть», без предоставления каких-либо гарантий и прав.

При использовании материалов c данного сайта ссылка на оригинальный источник обязательна.
Protected by Copyscape Online Plagiarism Scanner