Кроме того, вы не сможете воспользоваться таким простым механизмом запуска файлов, как их буксировка на значок сценария PowerShell. Пользователь может создать ярлык Windows или пакетный файл Cmd.exe, который включает процесс явного вызова PowerShell для выполнения сценария. Этот обходной прием сам по себе не составляет проблемы с точки зрения безопасности, поскольку не создает уязвимых мест, которые возникают при использовании ассоциации файлов определенных типов. Но механизм анализа и передачи аргументов системой Windows делает невозможной корректную передачу в сценарии PowerShell заключаемых в кавычки аргументов (например, аргументов, содержащих пробелы). Существует несколько методов обхода этой проблемы; наиболее распространенные из них предполагают формирование пакетного файла-оболочки, который я для простоты именую сценарием-оболочкой. Ниже я опишу суть проблемы и основные пути ее решения, а также остановлюсь на некоторых моментах, которые могут оказаться важными, если нужно будет решать вопросы, которые мы не охватим в данной статье.
Проблема: удаление двойных кавычек
Проще всего проиллюстрировать ситуацию на примере. Начнем с простого сценария PowerShell с именем echodemo.ps1, который выглядит следующим образом:
#echodemo.ps1
$i = 0;
foreach ($arg in $args)
{$i++; «$i $arg»}
Этот сценарий выполняет одну задачу — перебирает переданные ему аргументы в цикле и для каждого из них выдает номер аргумента и его значение. Для простоты я поместил сценарий в ту же папку, где хранится пара файлов, имена которых содержат пробелы. Как показано на экране 1, когда я выполняю сценарий, передавая ему в качестве аргументов имена этих файлов, он воспринимает каждую заключенную в кавычки строку как отдельный аргумент.
Среда PowerShell позволяет указывать параметры командной строки при запуске PowerShell, а с помощью параметра Command мы можем явным образом дать оболочке PowerShell предписание выполнить сценарий или внутреннюю команду. Чтобы получить подробную справку по параметрам командной строки PowerShell, нужно в строке приглашения Cmd.exe или PowerShell.exe ввести следующую команду:
powershell -?
Мы хотим выполнять команды, которым необходимы параметры командной строки для передачи среде PowerShell. Как явствует из справочной документации PowerShell 1.0, для этого необходимо ввести строку
powershell -Command
За ней должен следовать блок сценария. Все будет работать превосходно, если только имена файлов или других параметров, передаваемых среде PowerShell, не нужно заключать в кавычки. Если эти разъяснения повергают вас в замешательство, не расстраивайтесь. В реальной жизни выполнять команды проще, чем это представляется в файлах справочно-информационной системы.
В процессе поиска решения мы будем исходить из того, что в сценариях оболочки команды Cmd.exe имеется специальная видовая переменная-заполнитель %*, которая замещает все неиспользованные аргументы, заключая их в кавычки по мере необходимости. Однако этот процесс не обеспечивает корректное выполнение всех команд в PowerShell. В листинге 1 показано несколько сценариев‑оболочек Cmd.exe, написанных мною, чтобы показать, как функционирует расширение переменной %*. Эти примеры помогут нам разобраться с тем, что именно происходит при создании сценария-оболочки Cmd.exe для выполнения сценария PowerShell, который принимает несколько заключенных в кавычки аргументов.
На экране 2 мы видим выходные данные первых четырех сценариев листинга 1. Выходные данные сценария wrapper0 явно показывают, что расширение %* заменяет имена файлов, заключенные при необходимости в кавычки. Сценарий wrapper1 вообще не запускает файл echodemo.ps1; вместо этого среда PowerShell, как представляется, воспринимает все символы, заключенные в скобки блока сценария, как единую строку, и PowerShell просто воспроизводит то, что получил. В сценарий wrapper2 я включаю &, оператор вызова PowerShell, и привожу всю строку. Теперь сценарий echodemo.ps1 выполняется, но кавычки, оформляющие закавыченные аргументы, удаляются. В результате каждое слово во всех именах файлов интерпретируется как отдельный аргумент. В сценарии wrapper3 я предпринял последнюю отчаянную попытку (хотя и знал, что она закончится неудачей) закавычить выражение расширения аргумента %*. В результате все аргументы командной строки сцепляются в один длинный аргумент.
Так как же передавать среде PowerShell аргументы, содержащие пробелы? В нашем распоряжении имеется несколько решений, и им будет посвящена оставшаяся часть статьи.
Решение 1. Параметр File версии PowerShell 2.0
В версии PowerShell 2.0 реализован альтернативный метод анализа строк при выполнении сценариев. Вместо -Command нужно ввести -File и далее путь к сценарию, а также аргументы, которые вы хотите использовать в сценарии, и эти аргументы будут проанализированы корректно. Таким образом, в среде PowerShell 2.0 мы можем задействовать пакетный файл-оболочку наподобие следующей команды (показанной также в сценарии wrapper4.cmd листинга 1):
PowerShell -File echodemo.ps1%*
По маршруту, ведущему к месту хранения сценария, PowerShell передает аргументы непосредственно в сценарий в виде предварительно проанализированного массива $args. В результате каждый обрамленный кавычками аргумент представляется отдельным аргументом. Разумеется, этот метод будет работать лишь в том случае, если на системе установлена версия PowerShell 2.0.
Впрочем, мало найдется других ответов на просьбу помочь с решением технической проблемы, которые раздражали бы так же, как предложение установить новую версию программного продукта. Поэтому давайте посмотрим, как можно решить эту проблему средствами PowerShell 1.0.
Решение 2. Превращение аргументов во входные данные PowerShell 1.0
Для повышения гибкости среды PowerShell мы можем ввести дефис (-) после параметра Command, тем самым предписывая оболочке PowerShell рассматривать входные данные в качестве текста команды, которую необходимо выполнить. В случае использования пакетного файла этот прием означает, что вы можете по конвейеру передать оболочке PowerShell все, что угодно, включая выходные данные, причем данные, переданные вами, будут восприняты без дальнейшего анализа или удаления кавычек средствами Windows или Cmd.exe. Пакетный файл wrapper5, приведенный в листинге 1, с помощью этого приема обеспечивает корректную интерпретацию средствами PowerShell заключенных в кавычки аргументов.
Между прочим, при использовании сценария wrapper5.cmd по завершении его выполнения оболочка PowerShell автоматически закрывается. Такая реакция желательна в случаях, когда сценарий применяется для выполнения той или иной задачи напрямую. Но если вы намереваетесь проверить выходные данные сценария PowerShell, целесообразно использовать параметр PowerShell NoExit, как это сделано в сценарии wrapper6.cmd, представленном в листинге 1.
Данный прием можно выполнять и в CTP-версиях PowerShell 2.0, так что он, по-видимому, будет полностью совместимым снизу вверх. Однако здесь имеет место один базовый недостаток. Многие специальные символы PowerShell используются в различных, но, к сожалению, очень распространенных именах файлов. Обрабатывая имена, содержащие скобки, PowerShell часто пытается оценить их содержимое как вложенную команду. Для решения подобных проблем требуется более эффективный метод передачи команд среде PowerShell. Нужно также обрабатывать элементы таким образом, чтобы среда PowerShell однозначно трактовала их как имена файлов и папок. Следовательно, нам требуется более сильное решение, нежели пакетный файл-оболочка. В последнем предлагаемом нами решении для корректной передачи аргументов среде PowerShell используется язык VBScript.
Решение 3. Использование сценария-оболочки WSH
В листинге 2 приведен пример сценария-оболочки Windows Script Host (WSH), который можно использовать с целью выполнения операций подготовки для любого сценария PowerShell. Чтобы создать сценарий-оболочку, достаточно выполнить следующие действия.
- Сохраните копию шаблона VBScript в той же папке, где хранится сценарий PowerShell, который вы хотите выполнить, и удостоверьтесь в том, что базовое имя файла VBScript (имя без расширения и указания пути) идентично базовому имени сценария PowerShell. К примеру, сценарий PowerShell C:appsScan-File.ps1 имеет базовое имя Scan-File. Поэтому шаблон VBScript следует сохранять как C:appsScan-File.vbs. Тогда оболочка VBScript сможет определить имя сценария PowerShell.
- Оболочка VBScript выполняет сценарий PowerShell таким образом, что по завершении его выполнения сценарий автоматически закрывается. Если вы хотите, чтобы сценарий PowerShell продолжал выполняться, перейдите в область сценарного файла, где определяется базовая команда, — во фрагмент A, и отключите определение знаком комментария, т. е. введите в начале строки символ одинарной кавычки (что является в языке VBScript односимвольным маркером комментария). Затем во фрагменте B удалите из начала строки маркер комментария — одинарную кавычку. Если же вы хотите, чтобы по завершении выполнения сценарий прекращал свою работу, оставьте все как есть.
- Оболочка VBScript выполняет сценарий PowerShell в уменьшенном окне, чтобы он не привлекал много внимания. Если вы хотите, чтобы окно PowerShell, в котором выполняется сценарий, имело другой формат, перейдите во фрагменте C к строке с записью WshShell. Run Command, 2. Последняя цифра определяет стиль окна. Чтобы при выполнении окно имело заданные по умолчанию позицию и размер (это будет полезно, если вы собираетесь просматривать выходные данные сценария или ожидаете, что система предложит ввести дополнительные сведения), замените 2 на 1. Если же сценарий не предлагает вам вводить какую-либо информацию и не отображает данных, которые нужно увидеть, можно заменить эту цифру на 0, и тогда окно будет скрыто. Выбирайте данный параметр только в том случае, если вы не изменяете код во фрагменте B, с тем чтобы по завершении выполнения сценарий продолжал свою работу. Если же сеанс работы будет продолжен и при этом окно PowerShell будет оставаться скрытым, это приведет к тому, что всякий раз при запуске сценария будет инициироваться новый сеанс PowerShell, который будет продолжаться до перезагрузки системы или до того момента, когда вы завершите этот процесс из диспетчера задач.
- Если вы хотите организовать более удобный доступ к сценарию WSH, можете создать ярлык на своем рабочем столе.
Чтобы задействовать сценарий PowerShell, достаточно перетащить файлы и папки на сценарий WSH или на его экранный ярлык. Этот сценарий обнаружит сценарий PowerShell (при условии, что сценарий PowerShell имеет то же имя, что и сценарий WSH, и размещается в той же папке) и начнет сборку командной строки для выполнения сценария PowerShell.
Первым делом код VBScript удостоверяется в том, что все пробелы в пути к сценарию экранированы, а это значит, что PowerShell не будет воспринимать имя как несколько аргументов. Далее сценарий организует цикл, в ходе которого выполняется перебор всех элементов, отбуксированных на его значок. Если VBScript определяет, что тот или иной элемент не является ни реальным файлом, ни папкой, данный элемент выбраковывается. Если же проверка подтверждает, что элемент является элементом файловой системы, VBScript осуществляет поиск одинарных кавычек, используемых как часть имени файла, и экранирует их для среды PowerShell. Далее имя заключается в одинарные кавычки и сохраняется в коллекции приготовленных аргументов. По завершении обработки всех аргументов сценарий собирает их в PowerShell-совместимую командную инструкцию — и запускает ее.
Может показаться, что подобный процесс — слишком мощное (и потому недостаточно эффективное) средство для решения такого рода проблемы, но это не так. В других языках сценариев порой используются аналогичные инструменты; так, в языке Perl сходные пакетные файлы применяются в качестве оболочек для сценариев Perl, предназначенных для выполнения из командной строки, и эти пакетные сценарии порой достигают объема в несколько сот строк. Когда вы знаете, как обычно выполняются ваши PowerShell-сценарии буксировки, вам не приходится даже осуществлять их настройку на конкретное применение. Достаточно скопировать шаблон и переименовать его так, чтобы он соответствовал следующему сценарию PowerShell, в котором вы хотите применить данный прием.
Выбор решения
Выбор метода, обеспечивающего корректную передачу закавыченных строк в среду PowerShell с помощью пакетных файлов‑оболочек, в большинстве случаев осуществляется просто. Если у вас будет возможность использовать версию PowerShell 2.0, имейте в виду, что запуск программы Powershell.exe с параметром File — это удачное решение проблемы, поскольку оно дает возможность выполнять корректный синтаксический анализ заключенных в кавычки имен файлов и других закавыченных входных аргументов. Если же вы по какой-либо причине не можете использовать версию PowerShell 2.0, и вам предстоит работа главным образом методом перетаскивания, эту задачу поможет выполнить пакетный файл-оболочка, подобный файлу wrapper5.cmd или файлу wrapper6.cmd. Если вам требуется совместимость Cmd.exe и PowerShell, можете использовать сценарий wrapper2.cmd с явно выраженными одинарными кавычками, где это необходимо, или (предпочтительное решение) задействовать PowerShell в качестве принимаемой по умолчанию командной оболочки.
Если вы собираетесь работать методом буксировки, достаточно создать подходящий сценарий-оболочку в той же папке, где размещается сценарий PowerShell, который вы хотите использовать в качестве «мишени» для перетаскивания; результирующий сценарий должен выглядеть как мои оболочки, но вместо имени echodemo.ps1 следует указать имя вашего сценария.
Описанные решения должны работать в большинстве случаев, но возможны ситуации, когда будут возникать проблемы. В среде PowerShell широко используются специальные символы, которые порой применяются и в именах файлов. К этим специальным символам, способным в определенных ситуациях инициировать нестандартные реакции программ, относятся все виды скобок, а также символы $, ` и ‘. Конечно, данные символы можно экранировать так, чтобы среда PowerShell интерпретировала их корректно, но это сложная работа. Такие задачи лучше всего решать средствами сценария-оболочки WSH, который явным образом анализирует всю последовательность команд и экранирует ее перед передачей в среду PowerShell.
В большинстве случаев в качестве шаблонов для ориентированных на «буксировку» сценариев‑оболочек PowerShell, обеспечивающих корректную обработку имен файлов с включенными пробелами, можно использовать сценарии-оболочки 2, 5 и 6.
Алекс Ангелопулос (aka@mvps.org) — старший ИТ-консультант, специализируется на технологиях автоматизации административных задач