Работайте быстрее и эффективнее с PowerShell: советы эксперта
Время драгоценно для ИТ-специалиста. Рабочая нагрузка может быть чрезмерной, поскольку организации расходуют свои бюджеты, чтобы порадовать акционеров или бороться с конкуренцией на рынке. Автоматизация, безусловно, является ключом к эффективной работе в ИТ-среде, но ее достижение может означать крутую кривую обучения для ИТ-специалистов, которым уже не хватает времени для изучения новых навыков. Windows PowerShell — это одно из мощных средств реализации автоматизации в ориентированных на Windows средах (и все чаще в других), но если вы хотите эффективно использовать PowerShell, вам нужно уметь работать с ним быстро и разумно. Вот где совет и опыт эксперта могут пригодиться, и я попросил одного такого эксперта по PowerShell поделиться со мной некоторыми мыслями по этому вопросу, чтобы я мог передать их нашим читателям TechGenix. Десмонд Ли является главным архитектором облачных решений в Swiss IT Fabrik GmbH и специализируется на комплексных корпоративных инфраструктурных решениях, основанных на проверенных бизнес-процессах и интеграции людей в различных отраслях. Десмонд обладает более чем десятилетним глобальным полевым опытом в области управления проектами, безопасности, виртуализации, управления системами и автоматизации, образования, нефтегазовой отрасли, путешествий, здравоохранения, телекоммуникаций и унифицированных коммуникационных пространств от KMU/SME до конгломератов из списка Fortune 500. Десмонд в прошлом был самым ценным профессионалом Microsoft, сертифицированным тренером SVEB и основателем Swiss IT Pro User Group, независимой некоммерческой организации для ИТ-специалистов, созданной ИТ-специалистами, отстаивающими технологии Microsoft. Десмонд также регулярно ведет блоги на различные темы, связанные с PowerShell, и его также можно найти в Twitter. Теперь давайте узнаем, как работать быстро и разумно с помощью PowerShell, чтобы не быть погребенным под бесконечным напряжением нашей профессиональной рабочей нагрузки.
Немного справочной информации
«Работайте быстрее и эффективнее с PowerShell» — это секционная сессия, которую я представил на недавней конференции PowerShell 2017 Asia в Сингапуре. Для опытных PowerShell DevOps некоторые темы могут показаться очевидными, а другие — новыми или не всегда очевидными. В этой статье я расскажу и поделюсь некоторыми основными моментами, обсуждавшимися на конференции. Хотя PowerShell v2.0 устарел в последнем выпуске Windows 10 Fall Creators Update, большинство представленных примеров кода должны продолжать работать, если не указано иное.
Разогревать
Во-первых, это классический подход к проверке недопустимых элементов или нулевых объектов. Возьмем пример типичного фрагмента кода следующим образом:
$с = ""
Get-процесс | Селект-Объект-Недвижимость Компания | Компания Where-Object -ne $s
Попытка здесь отфильтровать объекты с пустыми (нулевыми) записями о компании не удалась, так как результат явно ложный, т.е.
“” -eq $null
#ЛОЖЬ
Простое исправление состоит в том, чтобы отредактировать $s = $null и повторно запустить инструкцию. Тем не менее, вы можете улучшить это, отказавшись от логического сравнения:
GPS | выберите Компания | ? Компания
Этот трюк работает, потому что только объекты, которые не являются нулевыми, могут успешно пройти прямо через конец конвейера через фильтр Where-Object.
Встроенные командлеты PowerShell
Обратим наше внимание на встроенные командлеты. Get-ChildItem (псевдонимы: dir, ls) — это часто используемый командлет, аналогичный следующему:
каталог *.cpl -Recurse -ErrorAction:SilentlyContinue
Вы, наверное, заметили, что может пройти некоторое время, прежде чем какие-либо хиты будут возвращены. Это связано с тем, что *.cpl автоматически сопоставляется с параметром -Path и может не соответствовать вашему намерению фильтровать имена файлов с соответствующим расширением. Как это работает на самом деле, можно понять с помощью Trace-Command.
Для оптимизации процесса поиска оператор должен читаться так:
#требуется -версия 5.0
#-Глубина ограничивает поиск одним уровнем подпапки
dir -Path $env:windir -Filter *.cpl -Recurse -ea:SilentlyContinue -Depth 1
Двигаясь вперед, получение списка файлов, начинающихся с определенного символа, можно просто реализовать с небольшой модификацией:
dir -Path $env:windir -Filter m *.cpl -Recurse -ea:SilentlyContinue -Depth 1
Явное использование параметра -Filter гарантирует, что фильтрация выполняется на «бэкенде сервера» до того, как соответствующий набор результатов будет возвращен клиенту или вызывающей стороне. Основываясь на этом понимании, у вас может возникнуть соблазн использовать параметр -Include в качестве альтернативы:
dir -Path $env:windir -Filter *.cpl -Recurse -ea:SilentlyContinue `
-Глубина 1 -Включить м*
Мало того, что время выполнения кажется медленнее, параметр -Depth больше не соблюдается, т.е. поиск выходит за пределы одного уровня подпапки:
Является ли это дизайном или ошибкой, еще предстоит выяснить. Обратите внимание, что улучшения в Get-ChildItem были введены после PowerShell версии 3.0, хотя это по-прежнему вызывает споры. Мораль этой истории заключается в том, чтобы хорошо знать свои командлеты и тестировать их до тех пор, пока не убедитесь, что они работают должным образом.
Работа с массивами
Тип массива идеально подходит для хранения нескольких элементов или объектов одного или разных типов. Попытка проверить, существует ли элемент $myCity в массиве $cities:
$cities = «Лондон», «Париж», «Берлин», «Берн»
$myCity = «Цюрих»
обычно реализуется с помощью Примера 1. Это может легко стать неуправляемым и громоздким с большим массивом. Вместо этого рассмотрите возможность использования оператора -in или -contains (-cin и -ccontains являются эквивалентами с учетом регистра) для выполнения той же работы ( пример 2 ).
| Пример 1 $myCity -eq «Лондон» -или $myCity -eq «Париж» -или $myCity -eq «Берлин» -или $myCity -eq «Берн» | Пример 2 #требуется -версия 3.0 $myCity - в $городах $cities - содержит $myCity |
Вполне вероятно, что вы хотите знать точное местоположение (индекс) элемента в массиве, а пример кода в примере 3 — это один из распространенных стилей, принятых многими DevOps. Лучше использовать статический метод IndexOf класса [array].NET, как показано в примере 4.
| Пример 3 для ($i = 0; $i -lt $cities.count; $i++) { если ($cities[$i] -eq $myCity) { «найдено '$($cities[$i])' ломать; } } | Пример 4 #if ($cities - содержит $myCity) если ($myCity -в $городах) { $k = [массив]::IndexOf($cities,$myCity) «найдено '$($cities[$k])' } |
Обратите внимание, что IndexOf нечувствителен к регистру и предоставляет правильную позицию индекса только при точном совпадении. Любые изменения регистра, длины или количества символов приведут к сбою этого метода. На это указывает возвращаемое значение -1. В результате вы можете полностью пропустить логический тест -in или -contains и получить свой пирог и съесть его!
Продолжая ту же тему, массив фиксированного размера может быть создан с использованием различных механизмов, таких как $arr = @(). Чтобы «вырастить» массив, за кулисами всегда создается совершенно новый массив с тем же именем, чтобы вместить входящие элементы. Это происходит за счет дополнительного времени и циклов ЦП и может серьезно повлиять на производительность, связанную с массивами со значительным количеством элементов, которые часто меняют размеры. Более того, существующие элементы нельзя удалить из обычного массива.
Ограничения обычного массива PowerShell можно преодолеть, обратившись за помощью к System.Collections.ArrayList. Как это развернуть, показано в понятном примере кода:
$arr1 = Новый объект System.Collections.ArrayList
#добавляет 1000 элементов в список ArrayList
1..1000 | % {[void]$arr1.Add($_)}
#preferred — добавляет еще 1000 элементов за один раз
$arr1.AddRange(1001..2000)
#убираем предмет со значением 3
$arr1.Удалить(3)
#удалить элемент в массиве с индексом 3
$arr1.RemoveAt(3)
# начиная с индекса 2 удалить количество 5 элементов
$arr3.RemoveRange(2,5)
[void] требуется только в том случае, если вы хотите подавить эхо индекса массива для вновь добавленного одиночного элемента. Вы можете добиться того же эффекта, направив оператор в Out-Null.
Вы можете удивиться, когда обнаружите, что ни один из предыдущих методов не появляется при запуске $arr1 | гм. В зависимости от вставленных типов данных результирующий вывод будет отображать соответствующие методы и свойства объекта на своем месте. Чтобы получить связанные свойства для ArrayList, нужно передать $arr1 в качестве аргумента для параметра InputObject команды Get-Member.
Обратите внимание, что вызов $arr1.ToArray() преобразует список объектов в базовый тип объекта PowerShell System.Array. Более того, присвоение этого отдельной переменной создаст независимую копию массива, которая не имеет никакой ссылки на исходную копию (хранящуюся в ArrayList ). То есть изменение элемента массива в любой копии не повлияет на содержимое, хранящееся в другой. С помощью этого метода он помогает удовлетворить одну из распространенных потребностей в дублировании списка (массива) элементов при сохранении четкого разделения.
Обычные выражения
Многие DevOps сразу же избегают регулярных выражений (регулярных выражений), потому что эта тема, похоже, имеет печально известную репутацию сложной для изучения, не говоря уже о использовании. Хорошей новостью является то, что основные административные задачи могут быть легко выполнены при элементарном понимании регулярных выражений.
Поясним на примере, где цель состоит в том, чтобы извлечь имя пользователя из поля Distinguished Name в Active Directory. Здесь указан простой способ:
$dn = "CN=dlee,OU=IT,OU=Corp,DC=leedesmond,DC=com"
$idx = $($dn).IndexOf(“,”)
$dn.SubString(0,$idx).SubString(3)
#дли
Используя регулярное выражение, решение можно сократить, чтобы оно стало следующим:
$m = [regex]”CN=(.+)”
$dn - соответствует $m
#Истинный
$matches
#Имя Значение
#—- ——
# 1 dlee, OU = IT, OU = Corp, DC = Leedesmond, DC = com
#0 CN=dlee,OU=IT,OU=Corp,DC=leedesmond,DC=com
Совпадение ($true) будет выведено как второй элемент хеш-таблицы $matches. Это дается именем «1», обозначающим совпадающее попадание, сохраненное как значение (как определено в первой совпадающей группе скобок). Точка или точка соответствует любому символу, тогда как знак плюса в конце означает несколько вхождений предыдущего элемента.
Следовательно, неожиданным эффектом является включение запятой (",") и остального текста как части первой совпадающей группы. Простое исправление состоит в том, чтобы исключить этот знак препинания, как показано на рисунке:
$m = [regex]”CN=(. [^,] +)”
$dn - соответствует $m
$ соответствует [1]
#дли
Как видите, работа с регулярными выражениями не такая уж сложная и пугающая.
Расширенные функции
Расширенная функция, представленная в версии 2.0, обычно начинается с директивы или тега [CmdletBinding()] для имитации собственного поведения командлетов в обычной функции PowerShell. Одной из наименее используемых, но экономящих время функций является автоматическая проверка параметров функции без необходимости написания собственной логики тестирования. Простое сравнение между классическим методом и методом Advanced Function представлено в примерах 5 и 6.
| Пример 5 $мин = 1; $ макс = 5 функция fb($i) { #проверить, находится ли $i между # заданный диапазон если ($i -ge $min -и $i -le $max) { «$ i находится в пределах допустимого диапазона» } еще { «$ i вне диапазона» } } | Пример 6 функция фб { [Привязка командлета()] параметр( [Проверить диапазон (1,5)] $ я ) «$ i находится в пределах допустимого диапазона» } |
Вскоре вы обнаружите, что с этой мощью и удобством приходит и новая ответственность. Выполнение расширенной функции с недопустимым параметром или параметром вне допустимого диапазона приведет к возникновению ошибки. Чтобы решить эту проблему, вы должны обернуть фактический вызов функции в блоке обработки ошибок try … catch:
#обработка ошибок
try { fb -i 0 } catch { «Вне досягаемости!» }
Вместо проверки фиксированного набора параметров в расширенной функции PowerShell вы можете использовать оператор проверки в блоке скрипта. Логика обработки ошибок прозрачна для пользователя и упакована внутри самого функционального блока. Пример кода для этого показан ниже:
функция foobar($mfl, $sbb)
{
$мфл | % {
пытаться {
if (&$sbb $PSItem) { "'$_' - мой цвет.`n" }
}
ловить {
«Не мой цвет!`n($_.Exception.Message)`n”
}
}
}
#
$sb = { param([ValidateSet("красный", "зеленый", "синий")]$flist) $flist }
$myflist = «розовый», «синий», «зеленый»
foobar $myflist $sb
Преимущество этой тактики заключается в том, что функция остается практически неизменной и может использоваться для различных наборов данных на основе правила проверки, передаваемого в качестве параметра функции через блок скрипта. Для дальнейшего изучения попробуйте вызвать Get-Help about_Functions_Advanced*, чтобы узнать больше об этой мощной функции.
Насколько быстр мой код?
Прежде чем мы подведем итог, вопрос о том, как быстро или сколько времени требуется для выполнения блока кода, явно требует ответа. Один простой способ измерить это — поместить код между парой командлетов Get-Date и измерить разницу во времени при завершении кода.
Альтернативным решением является развертывание командлета Measure-Command, в котором операторы кода помещаются в блок скрипта, используемый в качестве аргумента для параметра Expression, как указано здесь:
Измерение-команда-выражение { gps | ? компания | выберите имя, объект }
Третий вариант — буквально использовать секундомер из класса диагностики.NET Framework. Применить это на практике так же просто, как вызвать статический метод StartNew(), который немедленно запускает отсчет часов. После завершения последнего оператора кода необходимо выполнить метод Stop(). После проверки свойства Elapsed можно измерить фактическую продолжительность. Вы можете проверить свойство IsRunning и запустить () или остановить () секундомер в любое время по желанию.
$stopwatch = [system.diagnostics.stopwatch]::StartNew()
foreach ($ число от 1 до 1000) { $ число }
$секундомер.Стоп()
$stopwatch.Elapsed
Независимо от того, являетесь ли вы новичком или опытным специалистом по DevOps в мире автоматизации с помощью PowerShell, я, безусловно, надеюсь, что эта статья послужила своевременным напоминанием о себе и предоставила информацию, которая поможет вам исследовать новые смелые направления в будущем. И последняя пища для размышлений — как поменять местами значения, хранящиеся в двух разных переменных, без использования временной переменной? Веселиться!