Создание и использований функций PowerShell

Обновлено 18.05.2022

PowerShell logo

Добрый день! Уважаемые читатели и гости одного из крупнейших IT блогов рунета Pyatilistnik.org. В прошлый раз мы с вами очень подробно разобрали, как производится подключение к удаленному рабочему столу в разных операционных системах. Сегодня я вам так же хочу упростить жизнь и научить классным возможностям PowerShell. Мы научимся бороться с рутиной, созданием и использованием функций PowerShell, которые войдут в вашу жизнь так, что вы уже больше не сможете без них представить свое рабочее окружение, ведь проще запомнить одну команду, чем вспоминать, где располагаются ваши скрипты, какое у них имя и так далее.

Что такое функции PowerShell?

Функция - это список операторов Windows PowerShell, которым присвоено имя. Когда вы запускаете функцию, вы вводите имя функции. Операторы в списке запускаются так, как если бы вы ввели их в командной строке. Я бы сказал: внутри функции вы можете размещать коды, операторы, параметры и так далее. У этой функции есть имя. И это имя используется для запуска вашей функции.

По своему опыту хочу отметить, что если вы очень часто используете какие-то сценарии, то старайтесь переводить и сохранять их в функции, это будет экономить много времени, не забывайте делиться этими функциями в галереи PowerShell или github, давайте помогать друг другу.

Подробнее об этом можно прочитать в справке MS:

  • https://docs.microsoft.com/ru-ru/powershell/scripting/learn/ps101/09-functions?view=powershell-7.1
  • https://docs.microsoft.com/ru-ru/powershell/module/microsoft.powershell.core/about/about_functions?view=powershell-7.1

Представим себе ситуацию, что у вас есть какие-то повседневные задачи, которые вы решаете через скрипты PowerShell, например:

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

Упрощение рутины

Как создавать функции в PowerShell

Иногда мне приходится удалять старые профили с серверов, там я использую утилиту delprof2.exe, о которой я подробно уже писал. Там где мне нужна автоматизация, я создал bat-файл и все живет своей жизнью, а для разовых вещей я использую скрипт. Периодически я его теряю или забываю, где он лежит. поэтому я решил превратить его в функцию, которую легко смогу вызывать по ключевому имени.

Итак, обо всем по порядку, нам нужно определить, как мы будем запускать delprof2. Для моей функции я решил, что у меня будет стандартное расположение .exe и сделать его необязательным параметром:

Param (
...
[string]$delprof2 = "c:\windows\system32\delprof2.exe"
)

Я выбрал system32, потому что это начальный путь по умолчанию для PowerShell с повышенными привилегиями, но выберите то, что вам подходит. Чтобы фактически запустить delprof2, мы теперь используем амперсанд &, который является оператором вызова, например:

& $delprof2 '/l'

Затем, чтобы получить результат, нам нужно присвоить его переменной:

$output = & $delprof2 '/l'

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

Ignoring profile '\\DC01\C$\Users\Default' (reason: special profile)

Нам нужно получить оттуда несколько фрагментов данных. Помимо того факта, что это игнорируемый профиль, я хотел бы собрать имя профиля и причину. Итак, немного магии регулярных выражений, например:

if ($line -match "^Ignoring profile \'(?<profilePath>\\\\([^\\]+\\)+(?<profile>[^\']+))\' \(reason\: (?<reason>[^\)]+)\)")

Далее, мы можем построить возвращаемый объект:

[pscustomobject]@{
Ignored = $true
Reason = $Matches.reason
ProfilePath = $Matches.profilePath
Profile = $Matches.profile
}

Включенные профили немного проще, дополнительной информации для анализа нет. Итак, регулярное выражение будет выглядеть так:

if ($line -match "^(?<profilePath>\\\\([^\\]+\\)+(?<profile>\S+))")

Это дает нам больше полезных данных, которые мы можем использовать в возвращаемом объекте:

[pscustomobject]@{
Ignored = $false
Reason = $null
ProfilePath = $Matches.ProfilePath
Profile = $Matches.Profile
}

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

param (
[string]$Name
,
[string[]]$Include
,
[string[]]$Exclude
,
[int]$OlderThan
)

Так что нам понадобится пара циклов foreach, чтобы получить все переключатели в массиве:

$switches = & {
"/l"
if ($PSBoundParameters.ContainsKey('Include')) {
foreach ($inc in $include) {
"/id:$inc"
}
}
if ($PSBoundParameters.ContainsKey('Exclude')) {
foreach ($exc in $exclude) {
"/ed:$exc"
}
}
if ($PSBoundParameters.ContainsKey('OlderThan')) {
"/d:$OlderThan"
}
}

Затем, поскольку мы запускаем его на удаленном компьютере, мы должны проверить возможность подключения, все же вы знакомы с командой ping:

if ($PSBoundParameters.ContainsKey('Name') -and ($Name -ne $env:COMPUTERNAME)) {
if (Test-Connection $Name -Count 1 -Quiet) {
$computer += "/c:$Name"
} else {
Throw "Cannot ping $name"
}
}

И теперь наша команда будет выглядеть так:

$return = & $delprof2 $computer $switches

Кстати, еще очень удобно использовать функции PowerShell для утилиты robocopy, очень много можно выполнить разных сценариев.

Финальная версия созданной функции PowerShell

Теперь все объединим в единую функцию PowerShell.

Function Get-InactiveUserProfiles {
[cmdletbinding()]
param (
[Parameter(
Position = 1,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true
)]
[Alias('Computer','ComputerName','HostName')]
[ValidateNotNullOrEmpty()]
[string]$Name = $env:COMPUTERNAME
,
[Parameter()]
[string[]]$include # Supports ? and *
,
[Parameter()]
[string[]]$exclude # Supports ? and *
,
[Parameter()]
[int]$olderThan
,
[string]$delprof2 = "c:\windows\system32\delprof2.exe"
)
Begin {
$switches = & {
"/l"
if ($include) {
foreach ($inc in $include) {
"/id:$inc"
}
}
if ($exclude) {
foreach ($exc in $exclude) {
"/ed:$exc"
}
}
if ($olderThan) {
"/d:$olderThan"
}
}
}
Process {
if ($PSBoundParameters.ContainsKey('Name') -and ($Name -ne $env:COMPUTERNAME)) {
if (Test-Connection $Name -Count 1 -Quiet) {
$computer = "/c:$Name"
} else {
Throw "Cannot ping $Name"
}
}
Write-Verbose "CMD: $delprof2 $computer $switches"
$return = & $delprof2 $computer $switches
foreach ($line in $return) {
if ($line -match "^Ignoring profile \'(?<profilePath>\\\\([^\\]+\\)+(?<profile>[^\']+))\' \(reason\: (?<reason>[^\)]+)\)") {
# Ignored profile
[pscustomobject]@{
Ignored = $true
Reason = $Matches.reason
ProfilePath = $Matches.profilePath
Profile = $Matches.profile
ComputerName = $Name
}
} elseif ($line -match "^(?<profilePath>\\\\([^\\]+\\)+(?<profile>\S+))") {
# Included profile
[pscustomobject]@{
Ignored = $false
Reason = $null
ProfilePath = $Matches.ProfilePath
Profile = $Matches.Profile
ComputerName = $Name
}
} elseif ($line -match "^Access denied to profile \'(?<profilePath>\\\\([^\\]+\\)+(?<profile>[^\']+))\'") {
# Access denied
[pscustomobject]@{
Ignored = $true
Reason = 'Access denied'
ProfilePath = $Matches.ProfilePath
Profile = $Matches.Profile
ComputerName = $Name
}
}
}
}
End{}
}

Как запускать функцию PowerShell?

Тут два варианта запуска:

  • Если вы не планируете частое использование вашей функции, то вы легко можете ее сохранить в виде скрипта PowerShell с расширением .ps1 и вызывать в нужное время, вот такой командой:

[info]Import-Module C:\Scripts\delprof.ps1 -Force[/info]

Импортирование функции PowerShell

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

  • Второй вариант, это создание и использование модуля

Что такое модуль PowerShell?

Модуль - представляет собой набор функциональных возможностей, связанных Windows PowerShell, сгруппированных вместе в качестве удобного инструмента (обычно сохраняется в одном каталоге). Определив набор связанных файлов сценариев, сборок и связанных ресурсов в качестве модуля, вы можете ссылаться, загружать, сохранять и делиться своим кодом намного проще.

Это означает, что мы можем загрузить набор команд в наш сеанс PowerShell. В более старых версиях PowerShell вам необходимо вручную загружать модули в сеанс, чтобы использовать команды этого модуля.

Например, давным-давно вам нужно было импортировать модуль Active Directory, чтобы использовать команды PowerShell Active Directory. Выглядит это вот так:

Import-Module ActiveDirectory -Verbose

Обратите внимание, сколько разных командлетов импортирует один модуль.

Что такое модуль PowerShell

Чтобы показать все модули, которые установлены и могут быть импортированы, запустите

Get-Module -ListAvailable

Как посмотреть список модулей PowerShell

Модули хранятся в папке

C:\Windows\system32\WindowsPowerShell\v1.0\Modules

  • Папка по умолчанию для модулей, поставляемых с Windows - cd $PSHome\Modules - Не рекомендуется сохранять здесь свой модуль.

Папка по умолчанию для модулей, поставляемых с Windows

  • Папка модулей для всех пользователей - cd $Env:ProgramFiles\WindowsPowerShell\Modules

Папка модулей для всех пользователей

  • Папка модулей для текущего пользователя - cd $Home\Documents\WindowsPowerShell\Modules

Как сохранить свою функцию как модуль сценария

Откройте PowerShell ISE, щелкните "Файл" и выберите "Сохранить". Чтобы сделать вашу функцию доступной для всех пользователей, сохраните ее в ProgramFiles\WindowsPowerShell\Modules\Get-InactiveUserProfiles.

Как сохранить свою функцию как модуль сценария

Это означает, что вы должны создать там папку с тем же именем, что и ваш файл. Выберите Тип psm1.

Создание папки для модуля PowerShell

Щелкните Сохранить. Закройте все сеансы PowerShell. Снова откройте PowerShell и запустите Get-Module, чтобы просмотреть новый модуль сценария.

Get-Module -ListAvailable

Просмотр своего созданного модуля PowerShell

А вот и ваш новый модуль и его функция:

Get-Command -Module Get-InactiveUserProfiles | Format-List

Вывод справки по созданному модулю PowerShell

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

Get-InactiveUserProfiles | Format-Table

Использование собственной функции PowerShell

Или если бы мы хотели получить только те профили, которые начинаются с "А" и не использовались более 20 дней:

Get-InactiveUserProfiles -Include 'А*' -OlderThan 20 | Format-Table

Помните, что мы можем принимать как входные, так и выходные объекты конвейера? Да, так что мы можем сделать несколько довольно интересных вещей, например, если вам нужно очистить профиль на нескольких компьютерах:

Get-ADComputer -Filter {Name -like 'RDCB*'} | Get-InactiveUserProfiles -Exclude 'Administrator' -OlderThan 10 | Where-Object {-not $_.Ignored}

Это будет список профилей со всех компьютеров, возвращенных в этом запросе к AD, которые не являются профилем Administrator и не были использованы в течение 10 дней. Обратите внимание, что я использую командлет Where-Object для фильтрации только тех профилей, которые соответствуют этим критериям. Так что вы даже можете использовать Export-Excelи при необходимости создать хороший отчет для руководителя.

Вывод списка пользователей на определенном количестве компьютеров в Active Directory

Да, и я должен упомянуть, что параметр -Nameпередается через конвейер Get-InactiveUserProfiles, нет необходимости явно указывать его. Вы также можете иметь список компьютеров в текстовом файле и использовать его:

Get-Content C:\path\to\comps.txt | Get-InactiveUserProfiles | Where-Object {-not $_.ignored}

Как видите создавать свою функцию и свой модуль PowerShell не так уж и сложно, если вы это освоите, то сможете очень многое для себя улучшить. На этом у меня все, с вами был Иван Семин, автор и создатель IT портала Pyatilistnik.org.

Автор - Сёмин Иван

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *