Остальные материалы цикла:


КДПВДанная серия постов заказана и оплачена благотворительным фондом винниклОлега Крылова и всех-всех-всех.

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

Password-based authentication background

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

  • Пароли очень короткие (5-8 символов);

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

  • Пароли забываются;

Действительно пароль вида $gf)a90sfLq*wrF4 запомнить нелегко. С очень высокой долей вероятности, что пользователь после удачных выходных вряд ли в понедельник утром вспомнит его — звонок в тех.поддержку. Пока решается его вопрос с паролем, пользователь простаивает и ничего не делает, просто любуется экраном логона. Но можно выйти из ситуации и обойтись без звонка в тех.поддержку:

  • Пароли записываются на бумажки и приклеиваются на монитор;

Я думаю, что многие администраторы с таким встречались. Поскольку наши пользователи очень современны, они часто обитают на различных интернет-ресурсах, форумах, чатах и прочих социальных сетях. Чтобы не сильно забивать себе голову паролями:

  • Один и тот же пароль используется для множества сетевых ресурсов одновременно;

Нередко, когда пользователь зарегистрирован в десятке (а то и в десятках) мест, где используется один и тот же пароль. Потерял 1 пароль — потерял доступ всюду. Лично я не в состоянии удержать в голове все пароли от всех (да хотя бы топ-10) сайтов, где я бываю. Признаюсь, что у меня есть секретный текстовый файл, где я записываю все свои пароли и на сегодняшний день он имеет размер в 5кб. Я знаю, что это несекурно, но пока ничего лучше не придумал (если есть идеи, можете их озвучить в комментариях).

Об этом можно говорить долго и упорно, но смысла это не добавит, поэтому переходим дальше.

Introduction to certificate-based authentication

Цифровые сертификаты — это альтернативная форма идентификации пользователя. Здесь и далее я буду говорить про цифровые сертификаты в контексте Active Directory.

Цифровой сертификат — это документ, защищённый цифровой подписью (т.е. защищён от подделки), который содержит необходимую информацию о его владельце, которая позволяет уникально идентифицировать пользователя. Эта информация включает как минимум логонную информацию (адрес учётной записи в каталоге Active Directory или его User Principal Name — UPN). Поскольку цифровые сертификаты это часть инфраструктуры открытого ключа, они обязательно содержат открытый ключ.

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

  • Зная открытый ключ, невозможно вычислить закрытый ключ и наоборот.
  • Данные зашифрованные одним ключом (например, открытым) могут быть расшифрованны только вторым ассоциированным (например. закрытым) ключом.
  • Если мы шифруем данные открытым ключом — это шифрование и данные прочитать может только владелец закрытого ключа.
  • Если мы шифруем данные закрытым ключом — это цифровая подпись и данные прочитать может любой пользователь, но создать конкретную цифровую подпись может только владелец закрытого ключа.

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

Смарт-карта это устройство со встроенным микрочипом, который хранит цифровой сертификат и закрытый ключ от него. Микрочип устроен так, что извлечь закрытый ключ из него не представляется возможным, а получить доступ можно только путём ввода отдельного пароля, называемым PIN (Personal Identification Number). Ряд смарт-карт оборудуются дополнительным элементом физической защиты, при разрушении которой уничтожаются и хранимые на них ключи и сертификаты. Это защитная мера, которая предотвращает доступ к ключам и защищённой этими ключами информации при попытке физического доступа к микрочипу, что и есть главное условие безопасности.

Где можно применять аутентификацию пользователей по сертификатам?

В Microsoft Windows мы можем применять пользовательские сертификаты для:

  • Интерактивного логона в домен (требуется смарт-карта);
  • Логона на сервер терминалов при помощи Remote Desktop (требуется смарт-карта);
  • Аутентификации на веб-странице;
  • Аутентификации в VPN;
  • Аутентификации в 802.11х сетях (они же wireless).
  • Аутентификации в ActiveSync;
  • и ещё по мелочам.

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

Sunday, January 22, 2012 9:43:16 PM (FLE Standard Time, UTC+02:00)   Comments [6]    

 

Сегодня хочу поговорить про использование алиасов в скриптах и дать несколько практических советов при сборке текстов из фрагментов.

Алиасы

Семантика языка PowerShell подразумевает использование унифицированного синтаксиса, где название каждой команды явно говорит о том, что она делает. Например, Get-Process. Совершенно очевидно, что эта команда должна делать. Но, порой, эти команды бывают очень длинными и набирать их постоянно в консоли бывает не очень удобно. Например, самый топовый — Get-ChildItem. Это даже не самая длинная команда, просто наиболее часто используемая. Или Foreach-Object. Даже автозавершение команд не всегда спасает ситуацию. Для этого были придуманы алиасы (короткие ссылки на команды), которые очень выгодно использовать в консоли. Так же у команд есть и очень длинные параметры. Например, всякие –InputObject, –Include, –ErrorAction и т.д. PowerShell позволяет сокращать параметры первыми буквами до тех пор, пока эти буквы не будут явно указывать на конкретное название. Например, у команды Get-ChildItem параметр –Include может быть сокращён до –I, а –Exclude до –Ex. Но многие скриптописатели пишут скрипты (и выкладывают их даже где-то) с использованием этих самых алиасов и коротких обозначений параметров.

Хорошо это или плохо? Ответ очевидный — за такое надо бить больно сапогами и по лицу. Использование алиасов приводит нас обратно к одной из проблем оболочки cmd — сразу не скажешь, что делает та или иная команда. Пользователь без соответствующей подготовки вряд ли сходу скажет, что делает команда regsvr32 или что делает ключ /i этой команды. Или вот 2 примера:

gps iex* | spps -f
ls .\ -r -fo | %{cp $_.fullname -des e:\ -ea 0}

Вы можете такое использовать в консоли, но не в скриптах. В скриптах эти две строчки должны выглядить только вот так:

Get-Process iex* | Stop-Process -Force
Get-ChildItem .\ -Recurse -Force | ForEach-Object {Copy-Item $_.fullname -Destination e:\ -ErrorAction SilentlyContinue}

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

Пользователи PowerGUI Script Editor могут воспользовться адд-оном, написанным одним пошикмвп Шейем Леви (Shay Levy) — Expand Alias, который автоматически разворачивает алиасы в их полное значение.

Кавычки и here strings

PowerShell очень часто используют не только для созидательных целей, но и для аналитических тоже — сбор каких-то данных. Или какой-то скрипт выполняющий сложные операции и попутно пишущий что-то в лог-файл.

На этой неделе мне прислали скрипт, который на выходе делает красивую HTML'ку с красивыми стилями и всё такое. И вот какое чудо (не единственное) я увидел:

$a = "<title>Телефонный справочник ООО `"Имя компании`"</title>"

# стили и заголовок окна
$a = $a + "<style>"
$a = $a + "BODY{background-color:#FFF;color:#000;font-family: tahoma; font-size: 8pt; }"
$a = $a + "TABLE{border-width: 1px;border-style: solid;border-color: #BFBFBF;border-collapse: collapse; width: 100%}"
$a = $a + "TH{border-width: 1px;padding: 0px;border-style: solid;border-color: #BFBFBF;background-color:#4E7DD1; color: #FFF}"
$a = $a + "TD{border-width: 1px;padding: 0px;border-style: solid;border-color: #BFBFBF;background-color:#FFF}"
$a = $a + "col#c1 { width: 10%;} col#c2{ width: 10%; }col#c3 {width: 10%;}col#c4 { width: 3%;}"
$a = $a + "col#c5 { width: 10%;} col#c6{ width: 11%; }col#c7 {width: 10%;}col#c8 { width: 25%;}"
$a = $a + "</style>"

Ах, какая красота. Уже в первой строке человек использует бэктик (`) для эскейпа двойных кавычек внутри текстовой строки. Зачем? Я не знаю. Ведь достаточно было заменить наружные кавычки на одинарные и всё стало бы прекрасно:

$a = '<title>Телефонный справочник ООО "Имя компании"</title>'

А какую функцию несёт переменная $a? Мы уже говорили, что название переменной должно отражать её сущность. Это сейчас понятно, что там, а чуть ниже в коде, где она будет использоваться — мы уже забудем, что там было.

Ок, идём дальше. А дальше он добавляет к этой строке другой код HTML (естественно, в виде текста). Я давно замечал, что люди или не знают или не хотят использовать here strings. Ведь можно сделать вот так:

$html = @'
<title>Телефонный справочник ООО "Имя компании"</title>
<style>
BODY{background-color:#FFF;color:#000;font-family: tahoma; font-size: 8pt; }
TABLE{border-width: 1px;border-style: solid;border-color: #BFBFBF;border-collapse: collapse; width: 100%}
TH{border-width: 1px;padding: 0px;border-style: solid;border-color: #BFBFBF;background-color:#4E7DD1; color: #FFF}
TD{border-width: 1px;padding: 0px;border-style: solid;border-color: #BFBFBF;background-color:#FFF}
col#c1 { width: 10%;} col#c2{ width: 10%; }col#c3 {width: 10%;}col#c4 { width: 3%;}
col#c5 { width: 10%;} col#c6{ width: 11%; }col#c7 {width: 10%;}col#c8 { width: 25%;}
</style>
'@

Если вам надо в коде разместить набор строк, заключайте их в here strings (с двойными кавычками, если внутри надо экспандить переменные или с одинарными, если это не надо).

Так же, в этом скрипте увидел ещё вот такую прелесть:

$menu = $menu + "<a href=`"#"+$head+"`">"+$head+"</a><br>"

А если мы снова упакуем это в here strings?

$menu += @"
<a href="#$head">$head</a><br>
"@

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

Вот ещё прелесть:

$PatternTo = "<col id=""c1""/><col id=""c2""/><col id=""c3""/><col id=""c4""/><col id=""c5""/><col id=""c6""/><col id=""c7""/><col id=""c8""/>"

И здесь мы видим груду бесполезных кавычек. А давайте это подмножество строк положим в одну строку, заключённую в одинарные кавычки:

$PatternTo = '<col id="c1"/><col id="c2"/><col id="c3"/><col id="c4"/><col id="c5"/><col id="c6"/><col id="c7"/><col id="c8"/>'

эффект тот же самый и минус 16 кавычек.

Отсюда следует вот такое правило: учитесь правильно использовать кавычки. Избегайте эскейпинг кавычек — в 99% случаев это не нужно совсем. Вместо этого надо предусмотреть использование одинарных кавычек или применять here strings, если выхода нет.

$menu= "</style>" + "<TABLE Style=`"border-color:#FFF; border-collapse: separate`"><tr><td Style=`"border-color:#FFF`">" +$menu + "</td></tr></TABLE>"

Например, здесь мы не можем обойтись просто одинарными кавычками, потому что внутри строки надо вставить значение переменной. Значит, нам поможет here strings:

$menu= @"
</style><TABLE Style="border-color:#FFF; border-collapse: separate"><tr><td Style="border-color:#FFF">$menu</td></tr></TABLE>
"@

Работа с большим объёмом текста

В PowerShell при работе с большим объёмом текста (уже начиная от мегабайта) наблюдается ощутимая деградация производительности. Например, ваш код на выходе генерирует большой XML или HTML код, следует избегать операторы конкатенации ($a = $a + "some text" или $a += "some text"). Вместо этого следует использовать StringBuilder:

# создаём объект StringBuilder
$SB = New-Object Text.StringBuilder
# добавляем фрагменты текста в коде
<...>
[void]$SB.Append("some string")
<...>
# выгружаем итоговый текст в файл:
Set-Content -Path $path -Value $SB.ToString()

В качестве примера можете посмотреть мой код в PSFCIV, который заворачивает объекты с конвейера в XML:

function _toxml_ {
# в Begin создаём заголовок XML и открывающийся тег <FCIV>
    Begin {
        $xmlstring = New-Object System.Text.StringBuilder
        [void]$xmlstring.Append('<?xml version="1.0" ?><FCIV>')
    }
# в Process будут по одному поступать объекты, которые описывают файл.
    Process {
# для каждого файла у нас будет один тег <FILE_ENTRY>
        [void]$xmlstring.Append(" <FILE_ENTRY>`n")
# чтобы вручную не создавать и не заполнять вложенные теги мы простым foreach
# перечисляем теги, какие у нас будут в XML и за счёт переменных автоматом
# создаём их в XML и заполняем их данными. Для этого свойства объектов должны
# называться так же, как и теги.
        foreach ($child in ("name", "Size", "TimeStamp", "SHA1", "MD5")) {
            [void]$xmlstring.Append(" <$child>$($_.$child)</$child>`n")
        }
# когда текущий объект обработан, закрываем тег и ждём следующий объект файла
        [void]$xmlstring.Append(" </FILE_ENTRY>`n")
    }
# когда объекты закончились, закрываем первый тег <FCIV> и подаём полученный XML дальше
    End {
        [void]$xmlstring.Append("</FCIV>`n")
        [string]$xmlstring.ToString()
    }
}

В этом случае деградация производительности будет минимальной.

Sunday, January 15, 2012 2:46:04 PM (FLE Standard Time, UTC+02:00)   Comments [5]    

 

Наконец-то я смог выкроить время на то, чтобы написать что-нибудь ещё полезного в бложек. В последнее время я занимался весьма интересными вопросами, результатами которых я, может, как-нибудь поделюсь здесь. А может быть и не поделюсь :)

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

Форматирование

Неотформатированный код можно смело кидать в ресуклер, даже не вникая в него. Можете соглашаться, а можете и нет, но суть от этого не меняется. В принципе, PowerShell не имеет своих стандартов форматирования кода, но их можно унаследовать из IDE, как Visual Stidio. В основном это относится к разбивке сложных выражений на несколько строк и отступе от левого края. Разбивка сложных выражений проводится по таким символам, как: круглая скобка — (, квадратная скобка — [ (хотя, чаще она используется как метасимвол регулярных выражений) или фигурная скобка — {. Любые открывющиеся скобки в PowerShell являются признаком начала какой-то вложенной конструкции. И эти конструкции, если они достаточно длинные, следует располагать на новой строке. Открывающаяся скобка должна быть последним символом в текущей строке (или быть единственным символом в строке), а закрывающая скобка должна быть на отдельной строке. При этом весь код внутри конструкции должен быть отбит дополнительным табом от основного кода (т.е. не быть на одном уровне).

Заметка: олдфаги наверняка помнять 80 column rule, которое означает, что любая строка кода не должна быть длиннее 80 символов (классический размер консоли — 80 столбцов на 25 строк), а если она пытается быть длиннее — её надо разбивать на несколько строк. Плюс, это добавляет читабельности коду, потому что читать по вертикали проще, чем по горизонтали. Но это не значит, что надо строго придерживаться этого правила. Лично я считаю, что это правило можно расширить, но без фанатизма. Скажем, мне 100-110 символов кажется вполне разумным пределом.

Чувствую, что написал чушь, но ничего лучше не придумал, поэтому предлагаю пример, как делать не следует:

param ([Parameter(Mandatory = $true, Position = 0)][string]$Path,[Parameter(Mandatory = $true, Position = 1)][string]$XML)

Вот так — тоже плохо:

param ([Parameter(Mandatory = $true, Position = 0)][string]$Path,
[Parameter(Mandatory = $true, Position = 1)][string]$XML)

А хорошо будет вот так:

param (
    [Parameter(Mandatory = $true, Position = 0)]
    [string]$Path,
    [Parameter(Mandatory = $true, Position = 1)]
    [string]$XML
)

или вот так:

param 
(
    [Parameter(Mandatory = $true, Position = 0)]
    [string]$Path,
    [Parameter(Mandatory = $true, Position = 1)]
    [string]$XML
)

Как вы видите, скобки находятся на одном вертикальном уровне с основной командой, а весь вложенный код размещается на отдельных строках. В конкретном примере (param()), параметры аргументов должны располагаться на отдельной строке:

param 
(
    [ValidateSet("Bad", "Locked", "Missed", "New", "Ok", "Total", "Unknown")]
    [ValidateCount(1,7)]
    [String[]]$Show
)

Это значительно улучшает читабельность кода. Это несложное правило форматирования относится и к остальным подобным конструкциям. Например, вот кусочек кода, который что-то проверяет по условию IF и что-то делает:

if (Test-Path -LiteralPath $path) {Set-Location -LiteralPath $path
if ($pwd.Provider.Name -ne "FileSystem") {Set-Location $oldpath; throw "Specified path is not filesystem path. Try again!"}}
else {throw "Specified path not found. Try again!"}

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

if (Test-Path -LiteralPath $path) {
    Set-Location -LiteralPath $path
    if ($pwd.Provider.Name -ne "FileSystem") {
        Set-Location $oldpath
        throw "Specified path is not filesystem path. Try again!"
    }
} else {
    throw "Specified path not found. Try again!"
}

или Visual Studio-стайл:

if (Test-Path -LiteralPath $path) 
{
    Set-Location -LiteralPath $path
    if ($pwd.Provider.Name -ne "FileSystem") 
    {
        Set-Location $oldpath
        throw "Specified path is not filesystem path. Try again!"
    }
} 
else
{
    throw "Specified path not found. Try again!"
}

В обоих последних примерах можно уже точно сказать что к чему относится. Или вызов методов .NET:

[Security.Cryptography.X509Certificates.X509Certificate2UI]::SelectFromCollection(
    $certs,
    "Select a certificate",
    "Select a certificate or certificates from the list",
    "MultiSelection"
)

красиво и понятно. В отдельных случаях можно и не разбивать простые конструкции на несколько строк:

if ($a % 2) {$true} else {$false}

Для пользователей PowerShell Plus, в редакторе доступна встроенный автоформат кода. Т.е. набираете код так, как вам нравится, а потом автоформат форматирует как нужно.

Если у вас есть ещё какие-то соображения относительно форматирования, я не прочь их прочитать в комментариях. Так же, можете помочь с какими-то формулировками, потому что мои мне кажутся не самыми лучшими.

Именование переменных

Через мои руки прошло не мало сторонних скриптов и заметил несколько особенностей в именовании переменных. Если быть точнее, то всего 2.

  • Префиксы

Очень много скриптов было портировано с VBS и переменные тоже унаследовали вбсную аттрибутику — префикс, означающий тип данных. Например, $strPath или $colServers или что-то типа $objWMI. Я не говорю, что так делать плохо, но с другой стороны я не рекомендую так делать, ибо незачем. В PowerShell переменные могут хранить любой тип данных. Причём, типы в переменной могут меняться. Если вы хотите ограничить переменную каким-то определённым типом — ограничивайте так:

[string]$Path, [String[]]$Servers, [int]$Numbers

никаких префиксов не надо и вы видите, какой тип данных хранится в переменных.

  • Однобуквенные переменные

Начинающие и/или просто малоопытные администраторы начинают с самых простых скриптов и зачастую экономят на названиях перменных. Например:

$a = C:\
$b = dir $a\*.mp3
$c = d:\
copy $b -D $c

Названия переменных должны отражать свою суть, т.е. что в переменной хранится. Не бойтесь использовать даже длинные переменные (в пределах разумного, естественно). Например, переменная, содержащая файлы, которые надо куда-то добавить, может называться $FilesToBeAdded. Всё очень просто :)

Thursday, January 12, 2012 10:07:47 PM (FLE Standard Time, UTC+02:00)   Comments [4]    

 

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

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

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

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

Я не буду останавливаться на том, как это всё выглядит, а лишь расскажу, как должна быть оформлена справка к команде. 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]    

 

 · 

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
Библиотека
Календарик
<January 2012>
SunMonTueWedThuFriSat
25262728293031
1234567
891011121314
15161718192021
22232425262728
2930311234

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





Fan list



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

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