В данной планируемой статье из нескольких частей я попытаюсь рассказать о PowerShell и о том, как написать расширение для этой оболочки. Мы применим несколько технологий:
- вспомним, что огромная часть Windows API реализована COM-объектами;
- вспомним про C++/CLI, который поможет нам связать unmanaged- и manager-миры;
- и наконец напишем расширение (.NET Framework 4.5, C#).
И начну я с небольшого введения, прочитав которое, вы сможете понять, о чём всё-таки идёт речь, если вы вдруг что-то запамятовали.
Введение
Когда-то Microsoft приступила к разработке новой командной оболочки, которая должна была заменить cmd.exe
— оболочку, возможности которой, мягко говоря, никогда особо не впечатляли. Так появлился PowerShell. Сейчас на дворе 2013-й год, и у нас есть уже 4-я версия не очередного интерпретатора командной строки, но очень мощного, расширяемого средства автоматизации и администрирования.
Shell Libraries?
Windows 7 среди прочего привнёс в систему новшество — библиотеки. По замыслу разработчиков они должны были стать основным хранилищем пользовательских данных — документов, музыки, картинок, видео. Сами по себе библиотеки не хранят никаких файлов. Вместо этого они в одном месте показывают файлы, которые физически находятся в разных местах. Например, если часть музыки хранится в папке My Music, а часть — на внешнем жёстком диске, через библиотеку «Музыка» можно просмотреть всю коллекцию целиком.
На вкус и цвет все фломастеры разные, поэтому оставим в стороне вопросы удобства и популярности этих технологий (думаю, немалое количество людей по тем или иным причинам игнорируют их наличие). Для нас главное, что они есть.
Постановка задачи
Мы заглянем внутрь PowerShell, посмотрим, как он устроен, и на примере библиотек напишем небольшое расширение.
Почему библиотеки?
Во-первых, один из лучших способов научиться чему-то — как известно, научить этому другого. У меня давно на слуху всякие Shell Extensions, COM-составляющая Windows и пр., теперь пришла пора построить пару велосипедов и систематизировать знания.
Во-вторых, то, каким образом мы видим библиотеки и взаимодействуем с ними (а основное средство взаимодействия — это Проводник), гармоничным и очевидным образом переносится на концепции PowerShell. Чуть позже мы увидим это.
В-третьих, удобных средств работы с библиотеками из командной строки нет. Или, по крайней мере, я плохо искал и не нашёл. То есть, конечно, есть утилиты, например, в тех же Shell SDK Samples: можно создавать библиотеки, переименовывать их, удалять, но самого главного они не умеют. Я хочу просматривать библиотеки и работать с файлами в них так же прозрачно как и в Проводнике. Я хочу делать вот так:
Windows PowerShell
Copyright (C) 2012 Microsoft Corporation. All rights reserved.
C:WindowsSystem32WindowsPowerShellv1.0$ cd Libraries:Documents
Libraries:Documents$ ls
Directory: Libraries:Documents
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 07.10.2013 17:51 Bootstrap
d---- 14.08.2013 19:42 Custom Office Templates
d---- 30.08.2013 12:36 OpenTTD
d---- 24.09.2013 11:58 Outlook Files
d---- 08.10.2013 18:28 vfw4
d---- 29.04.2013 13:59 Visual Studio 2008
d---- 05.08.2013 18:05 Visual Studio 2010
d---- 09.10.2013 16:48 Visual Studio 2012
d---- 11.10.2013 13:27 Visual Studio 2013
d---- 17.10.2013 18:40 WindowsPowerShell
-a--- 18.10.2013 16:29 0 1.txt
-a--- 18.10.2013 16:29 0 10.txt
-a--- 18.10.2013 16:29 0 2.txt
-a--- 28.06.2013 16:54 18480 wix.png
Libraries:Documents$ new-item 3.txt -type file
Directory: Libraries:Documents
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 18.10.2013 19:32 0 3.txt
Видите? Мы как будто бы находимся в обычной папке. cmd.exe
так не умеет.
В-четвёртых, просто так.
PS> Get-Item D:Temparchive.zipFolderfile.xmlroot*
PS> Copy-Item D:Temparchive.zipfile.txt D:NewFolderfile.7zFolder
PS> Delete-Item D:Temparchive.7zFolder*.txt
PS> Move-Item D:Tempfile.txt D:TempexistingArchive.7zFolder
PS> Get-ChildItem D:Temparchive.7z
Взято отсюда.
Или можно «зайти» в sqlite базу данных, набрать dir
(или ls
, или Get-ChildItem
, или… в общем, как больше нравится) и посмотреть список таблиц. Давайте посмотрим, сколько стоят наушники?
PS C:$ cd Market:Электроника и ФотоПортативная аудиотехникаНаушникиAKG
PS Market:Электроника и ФотоПортативная аудиотехникаНаушникиAKG$ ls
В общем… думаю, понятно.
Приступим!
Сердце объектной модели PowerShell — сборка System.Management.Automation.dll. Она не отображается в окне Reference Manager студии, там что искать её нам предстоит самостоятельно. Что, на самом деле, не совсем тривиально. У меня эта сборка лежит в %windir%Microsoft.NetassemblyGAC_MSILSystem.Management.Automationv4.0_3.0.0.0__31bf3856ad364e35
. Обратите внимание, что с .NET Framework 4 GAC переехал в подпапку Microsoft.Net
.
Прежде всего
Создаём проект типа Class Library и добавляем ссылку на вышеуказанную сборку. Также для удобства отладки сразу поменяем некоторые параметры. В окне Project Properties › Debug:
- Start external program:
C:WindowsSystem32WindowsPowerShellv1.0Powershell.exe
- Command line arguments:
-noexit -command "[Reflection.Assembly]::LoadFrom('LibrariesProvider.dll') | Import-Module"
- Start external program:
C:Program FilesConEmuConEmu64.exe
- Command line arguments:
/Single /cmd C:WindowsSystem32WindowsPowerShellv1.0PowerShell.exe -noexit -command "[Reflection.Assembly]::LoadFrom('PowerShell.Libraries.dll') | Import-Module"
В любом случае, в появившейся консоли наберите
[Appdomain]::CurrentDomain.GetAssemblies() | where { $_.FullName -match "LibrariesProvider" }
— если вы увидели вашу сборку, значит, всё нормально и можно продолжать.
NavigationCmdletProvider
Поставщики в экосистеме PowerShell — это интерфейс между пользователем и данными. Данные могут быть различными: файловая система, реестр, переменные окружения, хранилища сертификатов и пр. Всё вышеперечисленное входит в PowerShell «из коробки».
Поставщики различаются функционалом, и, как видно на картинке выше, можно выделить пять различных типов:
CmdletProvider
— в конечном счёте этот класс является базовым для любых поставщиков. Как самый простой он даже не предоставляет возможности взаимодействовать с данными. Всё, что вы можете сделать, это узнать, когда ваш поставщик инициализировали и когда он завершает работу.DriveCmdletProvider
добавляет поддержку устройств, которые логически или физически делят данные на разделы. Например, ветки реестраHKCU
иHKLM
представлены PowerShell'ом двумя разными устройствами (или дисками, по аналогии сC:
,D:
и т.д.).ItemCmdletProvider
добавляет поддержку путей и и объектов. Данные-объекты можно получать, очищать, вызывать.ContainerCmdletProvider
добавляет поддержку объектов-контейнеров, то есть таких данных, которые сами содержат данные. Данные можно копировать, удалять и переименовывать. Важно одно обстоятельство: контейнеры не могут содержать другие контейнеры, только простые данные. Один уровень вложенности.NavigationCmdletProvider
— самый «навороченный» тип поставщиков, который предоставляет полную поддержку иерархии. Очевидный пример — файловая система, тут вам и папки (контейнеры), и подпапки («подконтейнеры»), и всё что угодно.
Поскольку библиотеки — это всё та же файловая система, думаю, очевидно, что нам нужен тип NavigationCmdletProvider
.
Libraries:Documentsthatlongpathexiststoo$
Давайте для затравки напишем поставщик, который позволит свободно побродить по его виртуальному пространству:
[CmdletProvider("Libraries", ProviderCapabilities.None)] ①
public class LibrariesProvider : NavigationCmdletProvider
{
protected override bool IsValidPath(string path) ②
{
return false;
}
protected override bool ItemExists(string path) ③
{
return true;
}
protected override bool IsItemContainer(string path) ④
{
return true;
}
protected override void GetChildItems(string path, bool recurse) ⑤
{
}
protected override Collection<PSDriveInfo> InitializeDefaultDrives() ⑥
{
var driveInfos = new Collection<PSDriveInfo>();
driveInfos.Add(new PSDriveInfo("Libraries", this.ProviderInfo, string.Empty, "Description", null));
return driveInfos;
}
}
① CmdletProviderAttribute
— это атрибут, без которого PowerShell не найдёт нашего поставщика. Мы указываем название поставщика и его возможности. Так, самой частой возможностью является ShouldProcess
: многие команды (командлеты) поддерживают параметр -WhatIf
, который отменяет непосредственный эффект (удаление, перемещение объекта и т.п.) и вместо этого делает вид, что эффект произошёл, то есть просто выводит строку вида
What if: Performing operation "Create directory" on Target "Destination: XXX".
Пока что нашей задачей является свободное перемещение, стало быть, ② любой путь правилен, ③ существует и ④ является контейнером.
Метод ⑤ позволяет вернуть содержимое контейнера. Пока что все наши подпапки будут пустыми.
Метод ⑥ позволяет на месте создать один или несколько устройств-дисков. Например, при подключении поставщика реальной файловой системы, можно сразу же создать устройства по количеству логических разделов на жёстких дисках. В нашем случае будет одно устройство (месторасположение «Библиотеки» единственно). А можно было бы насоздавать дисков по количеству библиотек (Music:
, Video:
и т.п.) — дело вкуса.
Убедимся, что приведённого кода достаточно; запускаем отладку и набираем в консоли:
PS C:LibrariesProviderbinDebug$ cd Libraries:
PS Libraries:$ cd Documents
PS Libraries:Documents$ dir ①
PS Libraries:Documents$ cd thatlongpathexiststoo
PS Libraries:Documentsthatlongpathexiststoo$ ②
① Действительно пусто.
② Работает.
Что дальше?
Дальше всё только интереснее.
На самом деле, я не хотел бы писать огромную статью; думаю, лучше будет её разбить на части. Ну и ко всему прочему я хочу «прощупать» аудиторию: если тема будет интересна, я конечно же продолжу, работы непочатый край.
Исходники предоставлю по первой просьбе.
Спасибо за внимание. Надеюсь увидеться в комментариях.
Автор: withkittens