Поделиться через


Создание двоичного модуля стандартной библиотеки

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

Заметка

Оригинальная версия этой статьи появилась в блоге, написанном @KevinMarquette. Команда PowerShell благодарит Кевина за предоставление этого содержимого нам. Пожалуйста, ознакомьтесь с его блогом на PowerShellExplained.com.

Что такое стандартная библиотека PowerShell?

Стандартная библиотека PowerShell позволяет создавать кроссплатформенные модули, работающие как в PowerShell, так и в Windows PowerShell 5.1.

Почему двоичные модули?

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

Например, у нас есть критически важный процесс, который работает с JSON и хэш-файлами. Мы оптимизировали PowerShell столько, сколько мы могли бы, но процесс по-прежнему занимает 12 минут. Модуль уже содержал много PowerShell-кода в стиле C#. Это делает преобразование в двоичный модуль чистым и простым. Преобразовав в двоичный модуль, мы сократили время процесса свыше 12 минут до четырех минут.

Гибридные модули

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

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

Сборка скриптов

Я держал скрипт сборки простым здесь. Обычно я использую большой скрипт Invoke-Build в рамках конвейера CI/CD. Это делает больше магии, как запуск тестов Pester, запуск PSScriptAnalyzer, управление версиями и публикация в PSGallery. Когда я начал использовать скрипт сборки для своих модулей, я смог найти много вещей, которые нужно добавить в него.

Планирование модуля

План для этого модуля — создать папку src для кода C# и структурировать остальные, как для модуля скрипта. Это включает использование скрипта сборки для компиляции всего в папку Output. Структура папок выглядит следующим образом:

MyModule
├───src
├───Output
│   └───MyModule
├───MyModule
│   ├───Data
│   ├───Private
│   └───Public
└───Tests

Начало работы

Сначала нужно создать папку и создать репозиторий Git. Я использую $module в качестве заполнителя для имени модуля. Это позволит вам повторно использовать эти примеры при необходимости.

$module = 'MyModule'
New-Item -Path $module -Type Directory
Set-Location $module
git init

Затем создайте папки корневого уровня.

New-Item -Path 'src' -Type Directory
New-Item -Path 'Output' -Type Directory
New-Item -Path 'Tests' -Type Directory
New-Item -Path $module -Type Directory

Настройка двоичного модуля

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

Сначала нужно проверить версию SDK dotnet core, который у нас установлен. Я использую 2.1.4, но вы должны иметь 2.0.0 или более поздней версии, прежде чем продолжить.

PS> dotnet --version
2.1.4

Я веду работу из папки src для этого раздела.

Set-Location 'src'

С помощью команды dotnet создайте библиотеку классов.

dotnet new classlib --name $module

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

Move-Item -Path .\$module\* -Destination .\
Remove-Item $module -Recurse

Задайте версию пакета SDK для .NET Core для проекта. У меня есть пакет SDK 2.1, поэтому я собираюсь указать 2.1.0. Используйте 2.0.0, если вы используете пакет SDK 2.0.

dotnet new globaljson --sdk-version 2.1.0

Добавьте в проект пакет NuGet стандартной библиотеки PowerShell . Убедитесь, что вы используете последнюю версию, доступную для уровня совместимости, который требуется. Я бы выбрал последнюю версию по умолчанию, но я не думаю, что этот модуль использует какие-либо функции, появившиеся после PowerShell 3.0.

dotnet add package PowerShellStandard.Library --version 7.0.0-preview.1

У нас должна быть папка src, которая выглядит следующим образом:

PS> Get-ChildItem
    Directory: \MyModule\src

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        7/14/2018   9:51 PM                obj
-a----        7/14/2018   9:51 PM             86 Class1.cs
-a----        7/14/2018  10:03 PM            259 MyModule.csproj
-a----        7/14/2018  10:05 PM             45 global.json

Теперь мы готовы добавить собственный код в проект.

Создание двоичного командлета

Необходимо обновить src\Class1.cs, чтобы он содержал этот начальный командлет:

using System;
using System.Management.Automation;

namespace MyModule
{
    [Cmdlet( VerbsDiagnostic.Resolve , "MyCmdlet")]
    public class ResolveMyCmdletCommand : PSCmdlet
    {
        [Parameter(Position=0)]
        public Object InputObject { get; set; }

        protected override void EndProcessing()
        {
            this.WriteObject(this.InputObject);
            base.EndProcessing();
        }
    }
}

Переименуйте файл в соответствии с именем класса.

Rename-Item .\Class1.cs .\ResolveMyCmdletCommand.cs

Затем мы можем создать наш модуль.

PS> dotnet build

Microsoft (R) Build Engine version 15.5.180.51428 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

Restore completed in 18.19 ms for C:\workspace\MyModule\src\MyModule.csproj.
MyModule -> C:\workspace\MyModule\src\bin\Debug\netstandard2.0\MyModule.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:02.19

Чтобы загрузить новый командлет, можно вызвать Import-Module в новой библиотеке DLL.

PS> Import-Module .\bin\Debug\netstandard2.0\$module.dll
PS> Get-Command -Module $module

CommandType Name                    Version Source
----------- ----                    ------- ------
Cmdlet      Resolve-MyCmdlet        1.0.0.0 MyModule

Если импорт завершается сбоем в системе, попробуйте обновить .NET до версии 4.7.1 или более поздней версии. Руководство по созданию кроссплатформенного двоичного модуля содержит дополнительные сведения о поддержке и совместимости .NET для более старых версий .NET.

Манифест модуля

Это здорово, что мы можем импортировать библиотеку dll и иметь рабочий модуль. Мне нравится продолжать работать с ним и создавать манифест модуля. Нам нужен манифест, если мы хотим опубликовать в PSGallery позже.

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

$manifestSplat = @{
    Path              = ".\$module\$module.psd1"
    Author            = 'Kevin Marquette'
    NestedModules     = @('bin\MyModule.dll')
    RootModule        = "$module.psm1"
    FunctionsToExport = @('Resolve-MyCmdlet')
}
New-ModuleManifest @manifestSplat

Я также создаю пустой корневой модуль для будущих функций PowerShell.

Set-Content -Value '' -Path ".\$module\$module.psm1"

Это позволяет смешивать обычные функции PowerShell и двоичные командлеты в одном проекте.

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

Я компилирую все вместе в выходную папку. Для этого необходимо создать скрипт сборки. Как правило, я добавлю это к скрипту Invoke-Build, но мы можем сохранить его простым для этого примера. Добавьте это в build.ps1, находящееся в корневой директории проекта.

$module = 'MyModule'
Push-Location $PSScriptRoot

dotnet build $PSScriptRoot\src -o $PSScriptRoot\output\$module\bin
Copy-Item "$PSScriptRoot\$module\*" "$PSScriptRoot\output\$module" -Recurse -Force

Import-Module "$PSScriptRoot\Output\$module\$module.psd1"
Invoke-Pester "$PSScriptRoot\Tests"

Эти команды создают библиотеку DLL и помещает ее в папку output\$module\bin. Затем он копирует другие файлы модулей в нужные места.

Output
└───MyModule
    ├───MyModule.psd1
    ├───MyModule.psm1
    └───bin
        ├───MyModule.deps.json
        ├───MyModule.dll
        └───MyModule.pdb

На этом этапе мы можем импортировать модуль с psd1-файлом.

Import-Module ".\Output\$module\$module.psd1"

Отсюда мы можем переместить папку .\Output\$module в директорию $Env:PSModulePath, и она автоматически загрузит нашу команду каждый раз, когда это необходимо.

Обновление: dotnet new PSModule

Я узнал, что средство dotnet имеет шаблон PSModule.

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

Вот как устанавливать и использовать шаблон PSModule.

dotnet new -i Microsoft.PowerShell.Standard.Module.Template
dotnet new psmodule
dotnet build
Import-Module "bin\Debug\netstandard2.0\$module.dll"
Get-Module $module

Этот минимальный жизнеспособный шаблон заботится о добавлении пакета SDK для .NET, стандартной библиотеки PowerShell и создает пример класса в проекте. Его можно создать и запустить сразу.

Важные сведения

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

Выгрузка библиотек DLL

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

Перезагрузка окна в VS Code

Большую часть работы по разработке на PowerShell я выполняю в VS Code. Когда я работаю над двоичным модулем (или модулем с классами), я пошел в привычку перезагрузить VS Code каждый раз при сборке. Ctrl+Shift+P отображает командное окно и Reload Window всегда находится в верхней части списка.

Вложенные сеансы PowerShell

Один из других вариантов заключается в том, чтобы иметь хорошее покрытие тестов Пестера. Затем можно настроить скрипт build.ps1, чтобы запустить новый сеанс PowerShell, выполнить сборку, выполнить тесты и закрыть сеанс.

Обновление установленных модулей

Эта блокировка может раздражать при попытке обновить локально установленный модуль. Если какой-либо сеанс его загрузил, вам нужно его найти и закрыть. Это меньше проблемы при установке из PSGallery, так как управление версиями модулей помещает новую в другую папку.

Вы можете настроить локальную PSGallery и публиковать в ней в рамках сборки. Затем выполните локальную установку из этой PSGallery. Это звучит как много работы, но это может быть так же просто, как запуск контейнера Docker. Я объясняю, как это сделать, в моей публикации на тему Использование сервера NuGet для PSRepository.

Окончательные мысли

Я не касался синтаксиса C# для создания командлета, но в пакете SDK для Windows PowerShell имеется обширная документация. Это определенно что-то, что стоит исследовать как первую ступень к более углубленному изучению C#.