Если ваша организация использует имена файлов в формате Unicode, формирование ярлыков с помощью объекта WshShortcut сервера сценариев Windows Script Host (WSH) может доставить немало хлопот. Особенно досадным является то обстоятельство, что этот объект позволяет беспрепятственно вводить текст в формате Unicode в любое из своих свойств, но, когда пользователь пытается сохранить созданный ярлык, WshShortcut либо генерирует сообщение об ошибке (в случае, когда символы Unicode содержатся в имени файла или в пути к нему), либо без каких-либо уведомлений превращает Unicode в невразумительную тарабарщину (это в случае, когда символы Unicode содержатся в описании какого-либо другого свойства).

В довершение всего WshShortcut возвращает одно и то же типичное сообщение об ошибке — unable to save file — в ответ на каждый отказ, исходящий от файловой системы Windows. Символы Unicode, содержащиеся в пути к файлу, обычно замещаются вопросительными знаками (?), в результате чего сообщение об ошибке становится еще более непонятным. К счастью, существуют способы решения подобных проблем с помощью обходных маневров, о которых я расскажу подробнее. Но сначала мне хотелось бы объяснить, почему объект WshShortcut ведет себя подобным образом.

Почему Unicode «не понимает» объект WshShortcut?

Ответить на вопрос, почему объект WshShortcut принимает символы Unicode, совсем несложно: у него просто нет выбора. Приведу выдержку из составленных специалистами Microsoft правил проектирования COM-интерфейса: «Все строковые параметры методов интерфейса должны быть в формате Unicode» (msdn.microsoft.com/en-us/library/ms692709(VS.85).aspx). В WSH все тексты, включенные в файл (или в объект другого типа), внутренне представлены с помощью символов Unicode. Внутренние механизмы сервера WSH и его языков сценариев всегда предусматривают использование стандарта Unicode. Таким образом, проблема конверсии строк переводится на уровень компонентов, которые генерируют выходные данные в том или ином представлении.

Так почему же объект WshShortcut выдает сообщения об ошибках и искажает содержимое — при том, что пользователю разрешается вводить в ярлыки символы Unicode? Однозначного ответа на этот вопрос не существует, однако картина проясняется, если взглянуть на WSH в историческом контексте.

Вспомогательные объекты WSH, включая WshShell и входящие в его состав рядовые объекты (например, WshShortcut), проектировались в то время, когда на рынке операционных систем для настольных компьютеров господствовали Windows 98 и Windows 95 (ни одна из этих систем не имела средств поддержки файловой системы Unicode). В этот период были реализованы три упрощения.

  • Разработчики стремились к тому, чтобы объект WshShortcut мог функционировать на всех поддерживаемых платформах. Простейший способ обеспечения идентичного функционирования базы кода на всех системах состоит в использовании API, не применяющих стандарт Unicode; именно такие API использует объект WshShortcut.
  • WshShortcut не инспектирует полученный текст, а просто исходит из того, что речь идет о тексте ANSI, представленном в формате Unicode. Если направить объекту текст в формате ANSI, никаких проблем не возникает. Когда текст ANSI представлен в формате Unicode, первый байт в каждой паре байтов является пустым, и используемый объектом WshShortcut метод отсечения первого байта в каждой паре байтов не влияет на полученный результат. Когда же вы передаете символы Unicode, программа принимает представление только второй половины каждого символа. Сходная ситуация возникала бы, если бы при указании адреса мы отсекали первую половину номера дома. Адрес 8922 North Main превращался бы в 22 North Main, а это не только неверно, но может не соответствовать реальному адресу. Вот почему, когда текст, который нужно воспринимать в представлении Unicode, обрабатывается подобным образом, на выходе получается «мусор».
  • Объект WshShortcut просто генерирует бинарный файл ярлыка в формате необработанных байтов (raw bytes); эти байты и будут записаны на диск. Проверки, которые могли бы определить, не являются ли записанные данные бессмыслицей и не может ли их обработка привести к возникновению проблем, не производятся. Если говорить о маршруте доступа к ярлыку, важно отметить, что интерфейсы API файловой системы невосприимчивы к большинству нетекстовых символов, и это при том, что деформированное имя файла в формате Unicode наверняка будет включать в себя несколько недопустимых символов.

На данный момент нам приходится иметь дело с недостатками WshShortcut. Перспективы обновления этого объекта были утрачены еще в 2001 году с прекращением всех работ по WSH. Однако, как я уже отмечал, существует несколько обходных маневров, которые позволяют создавать ярлыки, обеспечивающие возможность работы с форматом Unicode.

Создаем один раз, копируем несколько

Если мы создадим ярлык щелчком правой кнопкой мыши на каком-либо объекте с последующей его буксировкой (или щелчком правой кнопкой мыши с последующим выбором пункта New Shortcut в раскрывшемся меню), то сможем задействовать символы Unicode в маршруте доступа к ярлыку или в описании других его свойств. При создании ярлыков для пользователей стандартизированной сети (то есть сети, где используются единообразные компьютеры, операционные системы, программные средства и пользовательские настройки) вы можете выполнить эти действия один раз, разместить ярлык в сети (желательно с настройкой «только для чтения», чтобы свести к минимуму вероятность непреднамеренных изменений), а затем скопировать ярлык на настольные системы с помощью сценария. Возможность использования формата Unicode при указании имен маршрутов предусматривается при работе с пакетными файлами, сценариями PowerShell, а также с объектами Scripting.FileSystemObject сервера WSH, так что применять этот метод можно без особых ограничений.

Объекты Shell link

COM-объект Shell.Application воспринимает как формат Unicode, так и ярлыки Windows, которые в данном контексте именуются Shell link. Но несмотря на это, создатели сценариев редко используют объекты Shell link из-за некоторой сложности синтаксиса, а также в связи с тем обстоятельством, что возможность создания ярлыка с помощью допускающего работу со сценариями API Shell.Application не предусмотрена. Однако вы можете создать ярлык с помощью такого инструмента, как WshShortcut, и открыть его с применением Shell.Application. После этого появляется возможность использования символов Unicode в описании любого свойства данного ярлыка.

Я продемонстрирую два метода применения WshShortcut и Shell.Application с целью автоматизации процесса создания ярлыков, совместимых со стандартом Unicode. Первый метод показывает, как пользоваться ими для создания ярлыка с именем файла в формате Unicode. Второй метод демонстрирует, как применять упомянутые объекты для создания ярлыка, в котором символы Unicode используются и в нескольких других свойствах (например, в описании и в маршруте к цели).

Shell link, метод 1

Если требуется создать ярлык с именем файла в формате Unicode, можно создать его с помощью объекта WshShortcut и с именем файла в формате ANSI. Затем откройте ярлык с использованием объекта Shell.Application и переименуйте его. Продемонстрирую сказанное на простом примере.

Допустим, нам нужно создать ярлык с именем файла, состоящим из набора символов CJK (базовые символы, использующиеся в китайском, японском и корейском языках), и разместить его на настольных системах пользователей. Для этого можно задействовать сценарий, подобный сценарию UnicodeShortcut1.vbs, приведенному в листинге 1. Обратите внимание: этот сценарий открыт в редакторе, совместимом со стандартом Unicode. Содержимое в формате Unicode может быть искажено, если сценарий будет открыт в редакторе, не поддерживающем Unicode. Я использую бесплатно распространяемый редактор PrimalPad от компании SAPIEN Technologies. Кстати, редактор Notepad тоже поддерживает формат Unicode.

 

Листинг 1. UnicodeShortcut1.vbs

 

Первым делом сценарий Unicode Shortcut1.vbs создает объекты, которые будут применяться в дальнейшем. Далее в строке 3 он определяет путь к нужной папке рабочего стола. В строке 4 сценарий хранит текст в формате Unicode, который вы хотите позднее использовать как имя файла и описание.

Применение имени в формате Unicode, такого как имя в строке 7 (которое снабжено комментарием) приведет к сбою, поэтому вам придется воспользоваться альтернативным именем файла, состоящим из стандартных символов ANSI, например приведенным в строке 8 именем bpmfName (b, p, m и f — это первые четыре буквы системы CJK, которую часто именуют Bopomofo).

После сохранения ярлыка в строке 17 он превращается в файл. Все интерфейсы прикладного программирования объекта FileSystemObject могут использовать Unicode, так что вы можете с легкостью переименовать упомянутый файл, как показано в строках 19–21. Результат отображен на рисунке. Единственное, чего не позволяет делать рассматриваемый метод, так это включать содержимое в формате Unicode в другие свойства ярлыка. Для решения этой задачи необходимо использовать метод 2.

 

Рисунок. Ярлык, созданный с помощью сценария UnicodeShortcut1.vbs

Shell link, метод 2

Сценарий UnicodeShortcut2.vbs (листинг 2) реализует метод создания ярлыков, в которых содержимое в формате Unicode помещается в несколько свойств ярлыка. Я опишу механизм функционирования сценария шаг за шагом, и вы поймете, как работает данный метод.

 

Листинг 2. UnicodeShortcut2.vbs

Сценарий начинается с задания четырех переменных:

  • переменная TargetPath содержит имя пути доступа к приложению или к файлу, для которого создается новый ярлык;
  • описание содержит текст, всплывающий на экране при наведении указателя мыши на ярлык;
  • объект WorkingDirectory содержит рабочий каталог приложения или файла при его запуске;
  • объект FinalLinkPath содержит каталог, в котором предстоит разместить новый ярлык.

Далее сценарий инициализирует объекты, которые он будет использовать в дальнейшем (строки с 6 по 8) и создает в формате ANSI путь к каталогу, где он сможет сохранить шаблон ярлыка (строки 12 и 13). Речь идет именно о шаблоне, поскольку данный объект имеет форму ярлыка, но при этом является пустым. Необходимость создания шаблона диктуется единственным обстоятельством. Дело в том, что объект Shell.Application, который мы будем использовать на протяжении всей дальнейшей работы, сам по себе не может создать файл ярлыка. Shell.Application может лишь модифицировать уже существующий ярлык.

Для создания шаблона ярлыка сценарий вызывает метод Create-Shortcut объекта WshShell. В качестве аргумента методу передается безопасный путь. Сразу же после этого сценарий вызывает возвращенный метод Save объекта WshShortcut для его сохранения. Сценарий не должен записывать в файл ярлыка какие-либо данные, поскольку WshShortcut не подтверждает правильность содержимого. Мало того, он даже не присваивает имени объекту WshShortcut, поскольку последнему оно не требуется.

В строке 19 сценарий использует ссылку на Shell.Application, содержащуюся в переменной ShApp, для получения объекта Shell link. Эта строка кода начинается с подключения к пространству имен папки оболочки. В данном случае папкой оболочки является папка рабочего стола пользователя (имеющая идентификатор 0), но речь может идти о любой папке оболочки. Далее код вызывает метод папки оболочки Parse-Name, получающий в качестве аргумента путь доступа к временному ярлыку; этот аргумент обеспечивает возможность соединения с упомянутым файлом. Затем код считывает данный файл как ярлык Shell link с помощью свойства GetLink.

В строках 22–25 сценарий заполняет данные, описывающие ярлык, и сохраняет объект Shell link с использованием имени пути, содержащего символы в формате Unicode. Наконец, он удаляет шаблон ярлыка. В данном процессе используется несколько ссылок на объект, однако его можно полностью автоматизировать.

Самый лучший метод

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

Если ваша сеть не является стандартизированной и вам нужно использовать символы Unicode только в одном месте — в имени пути доступа к ярлыку, — вам, видимо, лучше всего применить предложенный мною первый метод работы с Shell link. При его использовании вы затратите меньше усилий на создание сценария и настройку его параметров, нежели в случае применения второго метода Shell link.

Второй метод работы с Shell link лучше всего задействовать в ситуациях, когда нужно использовать символы Unicode в других свойствах ярлыка, таких как описание или рабочий каталог. Этот метод предъявляет к создателю сценария самые высокие требования.

Как я отмечал выше, для открытия сценариев нужно применять UnicodeShortcut1.vbs и редактор Unicode Shortcut2.vbs, обеспечивающий возможность использования символов Unicode (например, PrimalPad или Notepad).

Если вы работаете с редактором, не поддерживающим стандарт Unicode, воспользуйтесь предложенными мною версиями рассмотренных сценариев, в которых символы Unicode вводятся в экранированную форму. Сценарии AlternateUnicodeShortcut1.vbs и AlternateUnicodeSho rtcut2.vbs (листинги 3 и 4) функционируют в точности так же, как их аналоги UnicodeShortcut1.vbs и UnicodeShortcut2.vbs.

Алекс Ангелопулос (aka@mvps.org) — старший ИТ-консультант, специализируется на технологиях автоматизации административных задач

Листинг 3. AlternateUnicodeShortcut1.vbs
Set WshShell = CreateObject("WScript.Shell")
Set ShApp = CreateObject("Shell.Application")
DesktopPath = ShApp.Namespace(0).Self.Path
‘ Unicode paths like next line will fail.
‘ Failure happens when saving the link.
bpmf = Unescape("%u3105%u3106%u3107%u3108")
‘bpmfName = UnEscape(escaped) & ".lnk"
bpmfName = "bpmf.lnk"
shortcutPath = DesktopPath & "\" & bpmfName
Set lnk = WshShell.CreateShortcut(shortcutPath)
lnk.TargetPath = "C:\Windows\System32\Notepad.exe"
‘ Unicode internally just writes garbage
‘lnk.Description = bpmf
lnk.Save()
Set FSO = CreateObject("Scripting.FileSystemObject")
Set file = FSO.GetFile(shortcutPath)
file.name = bpmf & ".lnk"
Листинг 4. AlternateUnicodeShortcut2.vbs
TargetPath = "C:\Windows\System32\Notepad.exe"
UnicodeData = Unescape("%u3105%u3106%u3107%u3108")
Description = UnicodeData
WorkingDirectory = "C:\temp\vm"
FinalLinkPath = "C:\tmp\" & UnicodeData & ".lnk"
Set WshShell = CreateObject("WScript.Shell")
Set ShApp = CreateObject("Shell.Application")
Set FSO = CreateObject("Scripting.FileSystemObject")
‘ Create a unique random name in the temp folder
‘ Use it to save a shortcut template.
tmplnk = WshShell.ExpandEnvironmentStrings("%temp%\") _
 & FSO.GetTempName() & ".lnk"
‘ Create and save an empty shortcut
WshShell.CreateShortcut(tmplnk).Save()
‘ Open shortcut as Shell Link; the namespace doesn’t matter.
Set lnk = ShApp.NameSpace(0).ParseName(tmplnk).GetLink
‘ Set properties of shortcut, including new path, and save.
lnk.Path = TargetPath
lnk.Description = Description
lnk.WorkingDirectory = WorkingDirectory
lnk.Save(FinalLinkPath)
‘ Force delete the shortcut template
FSO.DeleteFile tmplnk, True