Вы можете задействовать сценарии для автоматизации всех процессов управления системой SQL Server, поскольку это позволяет убедиться, что задачи выполняются стабильно и с приложением минимальных усилий. И в самом деле, ведь не хотите же вы использовать графический интерфейс среды SQL Server Management Studio, SSMS, для резервного копирования содержимого всех баз данных своей сети — или я ошибаюсь?

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

В большинстве сценариев, с которыми мне приходится иметь дело, я передаю имя сервера (точнее сказать, экземпляра SQL Server, но поскольку при работе с интерфейсом SQL Server Management Objects (SMO) полагается оперировать объектом Server, я использую термин «server» для обозначения этого экземпляра). Блок param я размещаю на месте первого исполняемого модуля, который PowerShell видит в моем сценарии, и поэтому оболочка воспринимает переменные, определяемые мною, как аргументы командной строки, так что в моих сценариях я определяю сервер, к которому необходимо подключиться, следующим образом:

param {
[string] $inst = $null
}

Итак, если мой сценарий называется Get-DatabaseOptions.ps1, я запущу его для работы с сервером WS12SQL01, используя при этом следующий вызов из командной строки:

PS C:\Demos>./Get-DatabaseOptions.ps1 WS12SQL01

Таким образом, в начале выполнения сценария переменной $inst будет присвоено значение 'WS12SQL01', и это значение будет использоваться с целью подключения к данному серверу для его обработки. Этот механизм работает безупречно, но у нас нет гарантии, что вызывающая программа передаст требуемый параметр, а если установить первоначальное значение равным NULL, оно будет использоваться в случаях, когда параметр не указан, что приведет к ошибке.

А что произойдет, если вы передадите программе второй аргумент? Ответ — по умолчанию PowerShell возвратит дополнительные аргументы в коллекции $args, и вы при желании сможете использовать их в своем сценарии.

PS C:\Demos>. \Get-DatabaseOptions.ps1 WS12SQL01 AdventureWorks
$inst = WS12SQL01
$args = AdventureWorks

Поскольку согласно моему замыслу сценарий должен был принимать только один аргумент, я решил использовать директиву CmdletBinding(), которая сообщает оболочке PowerShell, что допускается применение только заданных аргументов (наряду с прочими объектами, описание которых выходит за рамки данной статьи).

CmdletBinding()]
param (
[string] $inst = $null
)

А если я попытаюсь включить параметр AdventureWorks, PowerShell выдаст сообщение об ошибке, как на рисунке 1.

 

Сообщение об ошибке
Рисунок 1. Сообщение об ошибке

Непосредственно перед определением переменной $inst я могу добавить следующую строку, в которой будет содержаться запрос на ввод аргумента:

[Parameter(Mandatory=$true)]

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

cmdlet Get-DatabaseOptions.ps1 at command pipeline position 1

Supply values for the following parameters:

inst:

Сценарий дает пользователю возможность ввести параметр и затем продолжает выполняться с использованием предложенного значения.

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

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

Get-Service | Stop-Service

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

 

Состояние служб
Рисунок 2. Состояние служб

Еще одна возможность, реализованная в директиве CmdletBinding(), позволяет пользователю включить ту же возможность в свой сценарий. Чтобы активировать эту функцию, введите в скобки директивы CmdletBinding() параметр SupportsShouldProcess=$True. Встроенная переменная $PSCmdlet содержит сведения о стеке вызовов и наделена свойством ShouldProcess, значение которого можно проверить. Если данному свойству назначено значение $False, в дело вступает аргумент -WhatIf, и вы можете не допустить фактического выполнения определенных в сценарии действий. Поскольку код, в котором условия имеют значение $True, представляется более ясным, наш код будет выглядеть следующим образом:

[CmdletBinding(SupportsShouldProcess=$True)]
param (
[Parameter(Mandatory=$true)]
[string] $inst = $null
)
if ($PSCmdlet.ShouldProcess(«$inst»,«Return Database Options»))
{
write-output «Do stuff on server `$inst = $inst»
}

Если мы включим в команду аргумент -Whatif, PowerShell возвратит следующие данные.

What if: Performing operation «Return Database Options» on Target «WS12SQL01».

Если аргумент -Whatif не используется, мы получаем обычные результаты обработки.

PS C:\Demos>. \Get-DatabaseOptions.ps1 WS12SQL01

Do stuff on server $inst = WS12SQL01

Давайте рассмотрим ситуацию чуть более подробно. Предположим, что вы, как правило, выполняете свои сценарии по очереди на ряде серверов. Я упоминал о возможностях конвейера. Отмечу, что вы можете воспользоваться им для дальнейшей автоматизации процессов. PowerShell обеспечивает возможность выполнения процессов инициализации, итеративных процессов и процессов упаковки с помощью блоков Begin {}, Process {} и End {}. Блок Begin {} выполняется однократно, блок Process {} выполняется по одному разу для каждого переданного по конвейеру значения, а блок End {} — однократно в ситуации, когда конвейер пуст. Пользователю нет нужды производить какие-либо операции в начале или в конце конвейера, но вы можете с помощью блока Process {} активировать упомянутую функцию и, получив набор серверов из конвейера, выполнить свой сценарий применительно к каждому из них.

Прежде всего, вам следует дополнить блок param еще одним компонентом — аргументом ValueFromPipeline. Дополнение вносится непосредственно в раздел определения Parameter.

param (
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[string] $inst = $null
)

Теперь введите логику в блок сценария Process {} — и можете приступать к работе.

Process {
if ($PSCmdlet.ShouldProcess(«$inst»,«Return Database Options»)) {
foreach ($svr in $inst){
write-output «Do stuff on server `$inst = $inst»
}
}
}

Вы можете запускать сценарий так же, как и раньше.

PS C:\Demos>. \Get-DatabaseOptions.ps1 WS12SQL01
Do stuff on server $inst = WS12SQL01

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

 

Обработка нескольких серверов
Рисунок 3. Обработка нескольких серверов

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