Глава 5. Изучаем .NET

В этой главе мы рассмотрим следующие рецепты:

  • Исследуем сборки .NET

  • Изучаем классы .NET

  • Применяем методы .NET

  • Создаём расширение C#

  • Создаём командлет PowerShell

Введение

Microsoft впервые запустила Microsoft .NET Framework в июне 2000 имея целью безумное маркетинговое рвение с кодовым названием Next Generation Windows Services. Microsoft, казалось, добавляла прозвище .NET к каждому продукту. Это были Windows .NET Server (переименованный в Windows Server 2003), Visual Studio .NET и даже MapPoint .NET. Как это часто происходит, со временем .NET предоставила функциональные возможности, которые были заменены позднее более новыми функциональными возможностями, основанными на современных технологиях. Например, SOAP (Simple Object Access Protocol) и основанные на XML веб- службы уступили дорогу REST (Representation State Transfer) и JSON (JavaScript Object Notation).

Microsoft в ответ на отзывы клиентов с каждым выпуском вносила значительные улучшения в .NET и добавляла новые функции. .NET начиналась с закрытым исходным кодом, как .NET Framework. Затем Microsoft перевела .NET на открытый исходный код, также известный как .NET Core. PowerShell 7.0 основан на .NET Core 3.1.

Проблема состояла в том, что со временем .NET превратилась во фрагментированную по различным ОС и в Интернете. Чтобы решить это, Microsoft создала .NET 5.0 и отбросила прозвище &qout;Core&qout;. В дальнейшем планируется переход к единой .NET во всех платформах и форм- факторах, тем самым упрощая разработку приложений. Для ознакомления с дополнительными подробностями об этом обратитесь к https://www.zdnet.com/article/microsoft-takes-a-major-step-toward-net-unification-with-net-5-0-release/.

Важно отметить, что ни .NET 5.0, ни PowerShell 7.1 не обладают LTS (long-term support, Долговременной поддержкой). Следующей версией .NET является .NET 6.0, а для PowerSell это PowerSell 7.2, причём оба не должны быть выпущены до конца 2021. PowerSell 7.1 строится поверх и получает преимущества от самой последней версии .NET Core, именуемой как .NET 5.0.

Для конкретного разработчика приложения .NET в первую очередь это некий API (Application Program Interface, Интерфейс прикладных программ) и некая модель программирования со связанными с нею инструментами и средой исполнения. .NET это среда, в которой разработчики способны создавать богатые приложения и вебсайты на множестве платформ.

Для профессионалов ИТ .NET предоставляет подведение фундамента PowerShell, однако в остальном прозрачна. Для осуществления действий вы применяете командлеты. Эти командлеты пользуются .NET для осуществления своей работы не уведомляя своего пользователя о том, что в неё вовлечена .NET. Вы можете пользоваться Get-Process не заботясь о том как в реальности работает этот командлет. Это так вплоть до этого момента: существуют некоторые важные функциональные возможности .NET, которые не обладают командлетами. К примеру, создание доверительных отношений между лесами Active Directory осуществляется с применением соответствующих классов и методов .NET (и также очень просто!). .NET важный и глубинный инструмент, способствующий вам выходить за рамки командлетов, предоставляемых в Windows и приложениях Windows.

Вот иллюстрация на верхнем уровне основных компонентов .NET:

 

Рисунок 5-01


Компоненты .NET

Ключевыми компонентами в этой схеме являются:

  • Операционная система: Собственно хранилище .NET начинается с самой операционной системы. Его операционная система предоставляет все фундаментальные операции вашего компьютера. .NET пользуется компонентами ОС. Команда .NET поддерживает .NET 5.0, основу PowerShell, в Windows, Linux и Apple Mac. .NET также работает на платформах ARM, что позволяет вам получать PowerShell на ARM. Для полного перечня операционных систем, которые поддерживают PowerShell 7 обращайтесь к https://github.com/dotnet/core/blob/master/release-notes/5.0/5.0-supported-os.md.

  • Common Language Runtime (CLR) (Среда времени исполнения обычных языков программирования): CLR является ядром .NET, средой управления кодом, в которой выполняются все приложения .NET (включая PowerShell). Эта CLR управляет памятью, потоками, объектами, выделением/ возвратом ресурсов, а также сборкой мусора. Для обзора CLR отсылаем вас к https://docs.microsoft.com/dotnet/standard/clr/. Для пользователей PowerShell CLR "просто работает".

  • Base Class Libraries (BCL) (библиотеки базового класса): .NET поставляется с большим числом библиотек базового класса, всех фундаментальных встроенных функциональных возможностей, которые разработчики применяют при сборке приложений .NET. Каждая BCL это DLL, который содержит классы и типы .NET. Когда вы устанавливаете PowerShell, ваш установщик добавляет все компоненты .NET Framework в папку установки PowerShell. Разработчики PowerShell применяют эти библиотеки для реализации командлетов PowerShell. Вы можете также напрямую вызывать классы из BCL непосредственно из PowerShell. Именно BCL делает возможным для вас всё богатство .NET.

  • WMI, COM, Win32: Это традиционные технологии разработки приложений которым вы можете получать доступ через PowerShell. Внутри PowerShell вы можете исполнять программы, которые применяют эти технологии, а также получать к ним доступ из своей консоли или через некий сценарий. В случае необходимости, ваши сценарии могут быть смесью командлетов, , .NET, WMI, COM, а также Win32.

  • Языки программирования: Это те языки программирования, которые некие командлет или разработчик приложения могут применять для разработки командлетов PowerShell. Вы можете пользоваться насыщенным разнообразием языков, основываясь на своих наборах предпочтений и навыков. Большинство разработчиков командлетов применяют C#, хотя будет работать и всякий поддерживающий .NET язык программирования, скажем, VB.Net. Команда разработчиков первоначального PowerShell создавала язык программирования PowerShell на основе C#. Вы можете описывать PowerShell как скольжение вниз к границам C#. Это полезно, поскольку существует очень много документации с примерами на C#.

  • PowerShell: PowerShell располагается поверх всех этих прочих компонентов. Из PowerShell вы можете применять командлеты, разрабатываемые с применением поддерживаемых языков программирования, а также вы можете пользоваться как BCL, так и WMI/COM/Win32.

По большей части перемещение к .NET 5 по отношению к PowerShell более или менее прозрачно, хотя и не полностью. Одним из изменившихся в .NET 5 моментов состоит в том, что он прекратил поддержку большого числа API WinRT. Команда .NET придерживалась этого для ускорения кроссплатформенной поддержки. Тем не менее, это означает, что все модули, которые полагаются на такой API, например модуль Appx (https://github.com/PowerShell/PowerShell/issues/13138), больше не совместимы с PowerShell 7.1 (когда они пребывают в PS7), хотя они могут применять решение совместимости Winows PowerShell, описанное в Главе 3, Изучаем совместимость с Windows PowerShell.

В данной главе вы изучите те сборки, которые составляют PowerShell 7.1. Затем вы взглянете как на классы, предоставляемые .NET, так и на применение тех методов, которые предоставляются .NET. Вы также взглянете на создание простого расширения C# и создание командлета PowerShell.

Изучаем сборки .NET

При помощи .NET сборка хранит скомпилированный код, который может исполнять .NET. Сборка может представлять собой DLL (Dynamic Link Library, Динамически подключаемую библиотеку) или некий исполняемый файл. Как вы обнаружите из данного рецепта, в DLL содержатся командлеты и классы .NET. Всякая сборка также содержит некий манифест, который описывает что пребывает в его сборке, причём совместно с откомпилированным кодом.

Большинство модулей и команд PowerShell применяют сборки скомпилированного кода. Когда PowerShell загружает любой модуль, соответствующий манифест (файл .PSD1) перечисляет все сборки, которые составляют его модуль. Например, модуль Microsoft.PowerShell.Management предоставляет большое число команд ядра PowerShell, такие как Get-ChildItem и Get-Process. В манифесте этого модуля зарегистрирован вложенный модуль (то есть Microsoft.PowerShell.Commands.Management.dll) в качестве сборки, содержащей соответствующие реальные команды.

Отличной функциональной возможностью PowerShell является возможность для вас активировать некий метод класса .NET непосредственно или получать некое статическое значение класса .NET. Соответствующий синтаксис вызов метода .NET или поля .NET, демонстрирующийся в многочисленных рецептах в этой книге, состоит в том, чтобы заключить соответствующее имя класса в квадратные скобки, а затем поставить за ним два символа двоеточия (::), за коими следует собственно название соответствующего метода или статического поля.

В этом рецепте вы исследуете соответствующие сборки, загружаемые в PowerShell и сравниваем это с поведением в Windows PowerShell. Данный рецепт демонстрирует некоторые основные отличия между PowerShell 7 и Windows PowerShell сосуществуют с .NET. Вы также взглянете на модуль и соответствующую сборку, которая реализует все команды в этом модуле.

Подготовка

Вы выполняете этот рецепт в SRV1, сервер рабочей группы в котором вы установили PowerShell 7 и VS Code.

Как это сделать...

  1. Подсчитываем загруженные сборки

    
    $Assemblies = [System.AppDomain]::CurrentDomain.GetAssemblies()
    "Assemblies loaded: {0:n0}" -f $Assemblies.Count
    		
  2. Просматриваем первые 10

    
    $Assemblies | Select-Object -First 10
    		
  3. Проверяем сборки в Windows PowerShell

    
    $SB = {
      [System.AppDomain]::CurrentDomain.GetAssemblies() 
    } 
    $PS51 = New-PSSession -UseWindowsPowerShell
    $AssIn51 = Invoke-Command -Session $PS51 -ScriptBlock $SB
    "Assemblies loaded in Windows PowerShell: {0:n0}" -f $Assin51.Count
    		
  4. Просматриваем сборки Microsoft.PowerShell

    
    $AssIn51 | 
      Where-Object FullName -Match "Microsoft\.Powershell" |
        Sort-Object -Property Location
    		
  5. Изучаем модуль Microsoft.PowerShell.Management

    
    $Mod = Get-Module -Name Microsoft.PowerShell.Management -ListAvailable
    $Mod | Format-List
    		
  6. Просматривам собственно манифест мдуля

    
    $Manifest = Get-Content -Path $Mod.Path
    $Manifest | Select-Object -First 20
    		
  7. Выявляем сборку модуля

    
    Import-Module -Name Microsoft.PowerShell.Management
    $Match = $Manifest | Select-String Modules
    $Lube = $Match.Line
    $DLL = ($Line -Split '"')[1]
    Get-Item -Path $PSHOME\$DLL
    		
  8. Просматриваем ассоциированные загруженные сборки

    
    $Assemblies2 = [System.AppDomain]::CurrentDomain.GetAssemblies()
    $Assemblies2 | Where-Object Location -match $DLL
    		
  9. Получаем подробности команд PowerShell внутри DLL модуля

    
    $Commands = $Assemblies2
      Where-Object Location -match Commands.Management\.dll
    $Commands.GetTypes() | 
      Where-Object Name -match "Addcontentcommand$"
    		

Как это работает...

На Шаге 1 для возврата всех загруженных PowerShell в настоящее время сборок вы пользуетесь методом GetAssemblies(). Затем вы выводите счётчик загруженных в настоящий момент сборок, что выглядит примерно так:

 

Рисунок 5-02


Пересчитываем все загруженные сборки

На Шаге 2 вы просматриваете самые первые 10 возвращённых сборок, что отображено ниже:

 

Рисунок 5-03


Просматриваем самые первые 10 сборок

На Шаге 3 вы изучаете загруженные в Windows PowerShell 5.1 сборки, что выглядит следующим образом:

 

Рисунок 5-04


Проверка сборок в Windows PowerShell 5.1

На Шаге 4 вы изучаете сборки Microsoft.PowerShell.* в Windows PowerShell 5.1, что показано далее:

 

Рисунок 5-05


Просмотр сборок Microsoft.PowerShell в Windows PowerShell

На Шаге 5 вы изучаете подробности модуля Microsoft.PowerShell.Management, который содержит большое число команд ядра PowerShell. Вывод этого шага приводится ниже:

 

Рисунок 5-06


Изучение модуля Microsoft.PowerShell.Management в PowerShell 7.1

На Шаге 6 вы просматриваете соответствующий манифест для модуля Microsoft.PowerShell.Management. На следующем ниже снимке экрана показаны первые 20 строк этого манифеста:

 

Рисунок 5-07


Просмотр манифеста модуля Microsoft.PowerShell.Management

На Шаге 7 вы выделяете значение названия той DLL, которая реализует модуль Microsoft.PowerShell.Management и изучаете её местоположение на диске, что выглядит следующим образом:

 

Рисунок 5-08


Выявление сборок модуля Microsoft.PowerShell.Management

На Шаге 8 вы находите ту сборку, которая содержит все командлеты в модуле Microsoft.PowerShell.Management, что показано ниже:

 

Рисунок 5-09


Просмотр всех ассоциированных загруженных сборок

На Шаге 9 вы выявляете название того класса, который реализует команду Add-Content, что отражено далее:

 

Рисунок 5-10


Получение подробностей команд

Есть кое- что ещё...

В этом рецепте вы рассмотрели применяемые PowerShell сборки .NET. Они составлены как из сборок .NET Framework (то есть, BCL), так и из сборок, которые реализуют команды PowerShell. Например, вы нашли в модуле Microsoft.PowerShell.Management командлетAdd-Content.

На Шаге 1 для возврата всех загруженных в настоящий момент времени PowerShell 7.1 сборок методом GetAssemblies(). Как вы можете видеть, его синтаксис отличается от вызова командлетов PowerShell.

На Шаге 3 и на Шаге 4 вы получаете и просматриваете загруженные Windows PowerShell 5.1 сборки. Как вы моете наблюдать, Windows PowerShell загружаются иные сборки.

На Шаге 6 вы просматриваете первые 20 строк манифеста модуля для модуля Microsoft.PowerShell.Management. Данный вывод отсекает полный перечень экспортируемых этим модулем командлетов и длинную цифровую подпись для манифеста этого модуля. На Рисунке 5.7 вы можете наблюдать, что сама команда Fig0510 реализуется внутри Microsoft.PowerShell.Management.dll.

На Шаге вы выявляете ту DLL, которая реализует помимо всего прочего командлет Add-Content и на Шаге 8 вы можете обнаружить что эта сборка загружена. На Шаге 9 вы выявляете, что команда Add-Content реализуется классом AddContentCommand внутри соответствующей DLL сборки. Для любознательных, проследуйте в https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Management/commands/management/AddContentCommand.cs, где вы можете ознакомиться с самим исходным кодом для этого командлета.

Исследуем классы .NET

В .NET какой- то класс определяет некий объект. Для PowerSell фундаментальное значение имеют объекты и вхождения объектов, когда командлет производит и потребляет объекты. К примеру, команда Get-Process возвращает объект класса System.Diagnostics.Process. Если для возврата файлов или папок вы пользуетесь Get-ChildItem, его вывод это некий набор объектов на основе классов System.IO.FileInfo и System.IO.DirectoryInfo.

В большинстве случаев ваши действия в консоли и сценарии пользуются автоматически создаваемыми командами PowerShell объектами. Но в случае необходимости вы также можете применять команду New-Object для создания вхождений любого класса. Эта книга показывает большое число примеров создания объекта при помощи New-Object.

В рамках .NET у вас имеются две разновидности определений объектов: классов .NET и типов .NET. Некий тип определяет простой объект, который присутствует в среде исполнения в стеке вашего ЦПУ. Классы, будучи более сложными, пребывают в имеющейся глобальной куче. Собственно глобальная куча это некая большая область памяти, которую .NET применяет для содержания вхождений объектов в процессе сеанса PowerShell. Практически в всех случаях, имеющаяся разница между типом и классом практически не важна для пользующихся PowerShell профессионалов ИТ.

После того как некий сценарий или даже часть сценария исполнились, .NET может приводить в порядок свою глобальную кучу в неком процессе, носящем название garbage collection (GC, сборка мусора). Такой процесс сборки мусора в большинстве случаев также не важен ИТ профессионалам.Те сценарии, которые вы наблюдаете в данной книге, например, обычно не подвергаются воздействию процесса сборки мусора, как и большинство промышленных сценариев. Для получения дополнительных сведений относительно процесса сборки мусора в .NET обращайтесь к https://docs.microsoft.com/dotnet/standard/garbage-collection/.

Имеются ситуации, при которых процесс сборки мусора способен воздействовать на производительность. Например, класс System.Array создаёт объекты фиксированной длины. Когда вы добавляете в какой- то массив некий элемент, .NET создаёт новую копию этого массива (включая само добавление) и перемещает старый. Когда вы добавляете в свой массив несколько элементов, воздействие на производительность незначительно. Однако если вы добавляете миллионы вхождений, удар по производительности может быть существенным. Во избежание этого вы можете просто применять класс ArrayList, который поддерживает добавление/ удаление элементов из некого массива без штрафов производительности. Для получения дополнительных сведений относительно сборки мусора и производительности отсылаем вас к https://docs.microsoft.com/dotnet/standard/garbage-collection/performance.

Функциональные возможности .NET 5.0 во много улучшились в отношении процесса сборки мусора. Дополнительные сведения об усовершенствовании сборки мусора в .NET 5.0 вы можете получить здесь: https://docs.microsoft.com/dotnet/standard/garbage-collection/performance.

Во вхождениях .NET всякого класса или типа могут содержаться участники, в том числе свойства, методы и события. Свойство (property) это некий атрибут какого- то вхождения в класс. Например, некое вхождение объекта System.IO.FileInfo обладает свойством FullName. Метод (method) это в реальности некая функция, которую вы способны вызывать и которая может что- то делать с вхождением объекта. Более подробно методы .NET вы рассмотрите в рецепте Пользуемся методами .NET. Некое событие (event) это что- то, что может произойти с со вхождением объекта, скажем, когда вырабатывается некое событие при осуществлении какого- то процесса Windows. В этой книге не рассматриваются события .NET, хотя применение событий WMI и описывается Глава 15. Управление с помощью Windows Management Instrumentation, а именно в рецепте Управление событиями WMI.

Вы быстро можете определить некий класс (или тип) объекта, направляя конвейером вывод любого командлета или некого объекта в командлет Get-Member. Этот командлет Get-Member пользуется функциональной возможностью .NET reflection (отражение) для заглядывания вовнутрь и снабжения вас неким определяющим оператором того, что содержит этот объект. Эта функция по истене бесценна - вместо того чтобы строить предположения где в неком фрагменте строкового вывода ваш сценарий способен найти полное название файла, вы способны обнаружить свойство FullName, строку, или же, свойство Length, однозначно целое. Отражение и командлет Get-Member помогают вам выявлять все свойства и прочих участников определённого объекта.

Классы .NET могут обладать статическими свойствами и статическими методами. Статический свойства/ методы это стороны соответствующего класса в целом, в противоположность некому конкретному экземпляру класса. Некое статическое свойство это значение фиксированной константы, такое как значения maximum и minimum для 32- битного целого со знаком или величины значения pi. Статическим методом является метод, который не зависит от всякого конкретного экземпляра. Например, метод Parse() класса INT32 способен выполнять синтаксический разбор строки чтобы предоставлять её в качестве значения 32- битного целого со знаком. В большинстве случаев вы пользуетесь статическим методом для создания экземпляров объекта или для выполнения чего- то, относящегося к самому классу.

В данном рецепте вы взглянете на некоторые повседневные объекты, автоматически создаваемые PowerShell. Также вы изучите статические поля класса .NET [Int]

Подготовка

Вы выполняете этот рецепт в SRV1, хост рабочей группы в редакции Windows Server Datacenter. Это хост обладает установленными PowerShell и VS Code.

Как это сделать...

  1. Создаём объект FileInfo

    
    $FILE = Get-ChildItem -Path $PSHOME\pwsh.exe
    $FILE
    		
  2. Выясняем лежащий в основе класс

    
    $TYPE = $FILE.GetType().FullName
    ".NET Class name: $TYPE"
    		
  3. Обзаводимся типами участников своего объекта FileInfo

    
    $File | 
      Get-Member | 
        Group-Object -Property MemberType |
          Sort-Object -Property Count -Descending
    		
  4. Выявляем свойства службы Windows

    
    Get-Service | 
      Get-Member -MemberType Property
    		
  5. Выясняем что лежащий в основе тип - целое

    
    $I = 42
    $IntType = $I.GetType()
    $TypeName = $IntType.FullName
    $BaseType = $IntType.BaseType.Name
    ".Net Class name : $TypeName"
    ".NET Class Base Type : $BaseType"
    		
  6. Рассматриваем объекты процесса

    
    $PWSH = Get-Process -Name pwsh |
      Select-Object -First 1
    $PWSH |
      Get-Member | 
        Group-Object -Property MemberType |
          Sort-Object -Property Count -Descending
    		
  7. Взглянем на статические свойства некого класса

    
    $Max = [Int32]::MaxValue
    $Min = [Int32]::MinValue
    "Minimum value [$Min]"
    "Maximum value [$Max]"
    		

Как это работает...

На Шаге 1 для возврата некоторого объекта, представляющего исполняемый файл PowerShell 7 вы пользуетесь командлетом Get-ChildItem с подобным следующему выводом:

 

Рисунок 5-11


Создание объекта FileInfo

При помощи метода GetType() вы можете определить значение названия класса объекта. Этот метод представлен во всех объектах и возвращает сведения относительно установленного типа объекта.

На Шаге 2 вы выявляете и отображаете полное название класса, что выглядит примерно так:Add-Content

 

Рисунок 5-12


Выявление лежащего в основе класса

На Шаге 3 для отображения различных типов участников, содержащихся в объекте FileInfo, вы пользуетесь Get-Member, вывод которого следующий:

 

Рисунок 5-13


Получение участников типов объекта FileInfo

На Шаге 4 вы изучаете объекты, возвращаемые из командлета Get-Service. Получаемый на этом шаге вывод выглядит так:

 

Рисунок 5-14


Обнаружение свойств службы Windows

На Шаге 5 вы изучаете подробности целого с типом System.Int32. Для возврата сведений о значении типа переменной, включая полное имя класса его класса, вы пользуетесь методом GetType(), который представлен во всех объектах .NET. Вывод выглядит примерно так:

 

Рисунок 5-15


Изучение сведений типа некого целого

На Шаге 6 вы изучаете участников типов объекта System.Diagnostics.Process. Командлет Get-Process возвращает объекты этого класса. Вывод с этого шага подобен следующему:

 

Рисунок 5-16


Рассматриваем типы участников объекта процесса

На Шаге 7 вы изучаете два статических значения класса System.Int32. Эти два поля содержат, соответственно, самое большое и самое маленькое значения, которые способно содержать 32- битное целое со знаком. Получаемый вывод выглядит как отражено ниже:

 

Рисунок 5-17


Изучаем статические значения класса System.Int32

Есть кое- что ещё...

На Шаге 1 вы создаёте некий объект, представляющий pwsh.exe. Полное название типа этого объекта System.IO.FileInfo. В обитающих в пространствах имён классах .NET, как правило, пространства имён эквивалентны DLL. В данном случае наш объект представлен в пространстве имён System.IO, а его класс содержится в System.IO.FileSystem.DLL. Вы можете узнать подробности относительно пространства имён и DLL изучив документацию соответствующего класса, в данном случае это https://docs.microsoft.com/dotnet/api/system.io.fileinfo?view=net-5.0.

По мере того как вы пользуетесь предпочитаемым вами механизмом поиска для получения дополнительных сведений относительно классов .NET, которые могут оказаться полезными, обратите внимание, на то, что многие площадки описывают соответствующий класс без значения имени пространства, просто как класс FileInfo, в то время как прочие обстоятельно именуют класс как System.IO.FileInfo. Если вы намерены применять названия класса .NET в своих сценариях, рекомендуется применять подробную транскрипцию полного названия класса.

Пользуемся методами .NET

В .NET некий метод это какое- то действие, которое происходит с вхождением объекта или соответствующим классом .NET. Эти методы формируют необходимую основу для множества командлетов PowerShell. Например, вы можете остановить некий процесс Windows воспользовавшись командлетом Stop-Process. Этот командлет затем применяет метод Kill() ассоциированного с соответствующим объектом процесса. В качестве рекомендуемой практики вам надлежит применять командлеты всегда, когда это возможно. Вам следует пользоваться классами и методами .NET только когда нет иной альтернативы.

Методы .NET могут быть выигрышными для производительности операций, которые не обладают командлетами PowerShell. К тому же они могут оказаться полезными в вашей командной строке; к примеру, когда вы хотите уничтожить некий процесс. Профессионалы ИТ знакомы с процессами, которые не откликаются, а их требуется уничтожить, порой вы можете выполнять это в своём GUI при помощи Диспетчера задач. Или же при помощи PowerShell вы можете воспользоваться командлетом Stop-Process, как это объяснялось ранее. В вашей командной строке, когда полезна краткость, вы можете воспользоваться Get-Process для нахождения процесса, который вы желаете остановить и направить конвейером его вывод в метод Kill() каждого процесса. Затем PowerShell вызывает метод Kill() соответствующего объекта. Естественно, в помощь профессионалам ИТ команда PowerShell создала соответствующий псевдоним Kill для своего командлета Stop-Process. На практике это четыре символа для набора против 12 (или восьми когда вы пользуетесь заполнением табуляцией для пущего эффекта). В вашей командной строке конвейер в метод kill (когда вам даже не приходится применять открывающую/ закрывающую скобки!) это просто быстрее и имеет меньше риска для опечаток. Это хорошая закладка в вашей командной строке - однако избегайте её в промышленном коде.

Другим отличным примером являются зашифрованные файлы. Windows поддерживает функциональную возможность NTFS EFS (Encrypting File System, Зашифрованной файловой системы). EFS позволяет вам шифровать или расшифровывать файлы в вашем компьютере с основанном на X.509 шифровании. Относительно подробностей EFS и как она работает отсылаем вас к https://docs.microsoft.com/windows/win32/fileio/file-encryption.

На момент написания этих строк не имелось командлетов для шифрования и дешифрации файлов. Тем не менее, класс System.IO.FileInfo обладает методами, которые вы можете применять: Encrypt() и Decrypt(). Эти методы шифруют и расшифровывают файл на основе сертификатов EFS. Вы можете пользоваться этими методами .NET для шифрации и дешифрации, не прибегая к помощи соответствующего GUI.

Как вы уже видели в рецепте Исследуем классы .NET, вы можете отправлять конвейером любой объект в командлет Get-Member для выявления имеющихся в этом объекте методов. Выяснение соответствующих названий свойств и значений типов свойств простое и лёгкое - никаких догадок или основанных на молитвах разборах текста, столь любимых администраторами Linux.

Подготовка

Вы выполняете этот рецепт в SRV1 после загрузки PowerShell 7.1 и VS Code.

Как это сделать...

  1. Запускаем Блокнот

    
    notepad.exe
    		
  2. Получаем методы в соответствующем процессе Notepad

    
    $Notepad = Get-Process -Name Notepad
    $Notepad | Get-Member -MemberType method
    		
  3. Пользуемся методом Kill()

    
    $Notepad | 
      ForEach-Object {$_.Kill()}
    		
  4. Убеждаемся что наш процесс Notepad уничтожен

    
    Get-Process -Name Notepad
    		
  5. Создаём новую папку и несколько файлов

    
    $Path = 'C:\Foo\Secure'
    New-Item -Path $Path -ItemType directory -ErrorAction SilentlyContinue  |
      Out-Null
    1..3 | ForEach-Object {
      "Secure File" | Out-File "$Path\SecureFile$_.txt"
    }
    		
  6. Просматриваем файлы в папке $Path

    
    $Files = Get-ChildItem -Path $Path
    $Files | Format-Table -Property Name, Attributes
    		
  7. Шифруем эти файлы

    
    $Files| ForEach-Object Encrypt
    		
  8. Просматриваем атрибуты файлов

    
    Get-ChildItem -Path $Path |
      Format-Table -Property Name, Attributes
    		
  9. Расшифровываем и просматриваем эти файлы

    
    $Files| ForEach-Object {
      $_.Decrypt()
    }
    Get-ChildItem -Path $Path |
      Format-Table -Property Name, Attributes
    		

Как это работает...

На не выдающим никакого вывода Шаге 1 вы запускаете Notepad.exe. Это создаёт некий процесс, который вы можете изучать и применять.

На Шаге 2 вы получаете объект процесса Notepad и изучаете доступные вам методы. Его вывод отображается так:

 

Рисунок 5-18


Изучаем методы в своём процессе Notepad

На Шаге 3 вы пользуетесь в объекте System.Diagnostics.Process методом Kill() для остановки своего процесса Notepad. Этот шаг не выдаёт вывод. На Шаге 4 вы убеждаетесь что вы остановили свой процесс Notepad с подобным следующему выводом:

 

Рисунок 5-19


Подтверждаем что процесс Notepad был уничтожен

Для иллюстрации прочих применений методов .NET вы создаёте некую папку и три файла внутри этой папки на Шаге 5. Создание таких файлов не приводит к выводу. На Шаге 6 вы пользуетесь командлетом Get-ChildItem для получения сведений относительно этих трёх файлов, включая все атрибуты файлов, что выглядит как- то так:

 

Рисунок 5-20


Просматриваем все файлы в своей папке $Path

На Шаге 7 вы применяете метод для шифрации этих файлов, причём без выработки какого бы то нибыло вывода в вашей командной строке. На Шаге 8 вы снова просматриваете установленные атрибуты этих файлов, которые теперь выглядят так:

 

Рисунок 5-21


Просматриваем значения атрибутов файлов

Наконец, на Шаге 9 вы расшифровываете свои файлы при помощи метода Decrypt(). Далее вы пользуетесь командлетом Get-ChildItem для просмотра значений атрибутов файлов, что позволяет вам определить, что эти файлы не зашифрованы. Вывод с этого шага выглядит приблизительно так:

 

Рисунок 5-22


Выполняем дешифрацию и просматриваем атрибуты файлов снова

Есть кое- что ещё...

На Шаге 2 вы просматриваете те методы, которые вы способны активировать в процессе объекта. Одним из таких методов выступает метод Kill(), что вы можете наблюдать на Рисунке 5.18. На Шаге 3 вы пользуетесь этим методом для останова своего процесса Notepad. Метод Kill() является методом экземпляра, что подразумевает, что вы активируете его для уничтожения (останова) конкретного процесса. Дополнительные сведения относительно этого метода .NET вы можете прочесть в https://docs.microsoft.com/dotnet/api/system.diagnostics.process.kill.

Получаемый на Шаге 4 иллюстрирует некую ошибку, возникающую внутри VS Code. Если вы пользуетесь консолью PowerShell 7, вы можете наблюдать слегка отличающийся вывод, хотя и с тем же самым реальным сообщением об ошибке.

На Шаге 7 и на Шаге 9 вы пользуетесь объектами FileInfo (создаваемыми через Get-ChildItem) и вызываете их методы Encrypt() и Decrypt(). Эти шаги демонстрируют использование методов .NET для достижения некой объективности при отсутствии конкретных командлетов. Наилучшим советом является всегда применять командлеты, когда это возможно. Вам также следует обратить внимание на отличие синтаксиса, имеющееся в этих двух шагах. На Шаге 7 вы пользуетесь более современным синтаксисом, который вызывает метод Encrypt для каждого файла. На Шаге 9 вы применяете более старый синтаксис, который делает то же самое, хотя и с большим числом символов. Работают оба метода синтаксиса.

На Шаге 7 вы не получаете никакого вывода на консоль, причём Windows может выработать некий всплывающий блок (также именуемый тостом) чтобы сообщить вам что вам надлежит сделать резервную копию своего ключа шифрования. Это неплохая мысль, поскольку если вы утратите этот ключ, вы можете потерять и доступ к своему файлу.

Создаём расширение C#

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

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

ЗщцукЫрудд делает простым добавление некого класса на основе языка исходного кода .NET в сеанс PowerShell. Вы сопровождаете свой код C#, а PowerShell создаёт некий класс.NET, который вы можете применять тем же самым способом, которым вы используете методы .NET (и с применением примерно того же самого синтаксиса). Для этого вы применяете командлет Add-Type и определяете необходимый код C# для своего класса/ типа (классов/ типов). PowerShell компилирует этот код и загружает получаемый в результате класс в ваш сеанс PowerShell.

Существенной стороной методов .NET является то, что метод способен обладать множеством определений или вызываемых последовательностей. Именуемое перегрузкой метода (method overload), такое множество определений делает для вас возможным активировать метод с применением различных наборов параметров. Это отличается от применения PowerShell наборов параметров. К примеру, класс System.String, который PowerShell использует для хранения строк, содержит метод Trim(). Вы пользуетесь этим методом для удаления дополнительных символов, обычно пробельных символов, причём как с начала или конца строки (или с обеих сторон). Метод Trim() обладает тремя различными определениями, которые вы просмотрите в этом рецепте. Всякое перегруженное определение отсекает символы из строки несколько иначе. Чтобы узнать больше об этом методе и сами три перегружающих определения, отсылаем вас к https://docs.microsoft.com/dotnet/api/system.string.trim?view=net-5.0/.

В данном рецепте вы создаёте и применяете два простых класса, причём каждый со статическими методами.

Подготовка

Вы выполняете этот рецепт в SRV1, системе рабочей группы, запущенной в редакции Windows Server Datacenter Вам надлежит имеет загруженными в этом хосте PowerShell 7.1 и VS Code.

Как это сделать...

  1. Изучаем указанное выше определение перегруженного метода

    
    ("a string").Trim
    		
  2. Создаём некое определение класса C# во встроенной строке (here- string)

    
    $NewClass = @"
    namespace Reskit {
       public class Hello {
         public static void World() {
             System.Console.WriteLine("Hello World!");
         }
       }
      }   
    "@
    		
  3. Добавляем этот тип в своём текущем сеансе PowerShell

    
    Add-Type -TypeDefinition $NewClass
    		
  4. Изучаем полученное определение метода

    
    [Reskit.Hello]::World
    		
  5. Применяем метод своего класса

    
    [Reskit.Hello]::World()
    		
  6. Расширяем свой код параметрами

    
    $NewClass2 = @"
    using System;
    using System.IO;
    namespace Reskit {
      public class Hello2  {
        public static void World() {
          Console.WriteLine("Hello World!");
        }
        public static void World(string name) {
          Console.WriteLine("Hello " + name + "!");
        }
      }
    }  
    "@
    		
  7. Добавляем полученный тип в своё текущий сеанс PowerShell

    
    Add-Type -TypeDefinition $NewClass2 -Verbose
    		
  8. Просматриваем определения метода

    
    [Reskit.Hello2]::World
    		
  9. Вызываем новый метод без предписанных параметров

    
    [Reskit.Hello2]::World
    		
  10. Вызываем новый метод с неким параметром

    
    [Reskit.Hello2]::World('Jerry')
    		

Как это работает...

На Шаге 1 вы изучаете различные определения для описанного выше метода Trim(). Его вывод, отображающий различные перегруженные определения выглядит подобно приводимому ниже:

 

Рисунок 5-23


Изучение различных методов определения для Trim()

На Шаге 2 вы создаёте некое определение класса и сохраняете это определение в некой переменной. На Шаге 3 вы добавляете определение этого класса в рабочее пространство своего текущего PowerShell. Эти два шага не продуцируют никакого вывода.

На Шаге 4 пользуетесь своим новым методом для просмотра доступных вам определений, что выглядит как- то так:

 

Рисунок 5-24


Изучаем определения методов

На Шаге 5 вы применяете свой метод World(), вывод которого приводится ниже:

 

Рисунок 5-25


Применяем метод World()

На Шаге 6 и на Шаге 7 вы создаёте иной новый класс, на этот раз с двумя перегружающими определениями для его метода World(). Эти шаги не производят вывод. На Шаге 8 вы просматриваете определение для своего метода World() внутри его класса Reskit.Hello2 с подобным следующему выводом:

 

Рисунок 5-26


Просматриваем определения метода World()

На Шаге 9 вы активируете свой метод World() без предоставления есу параметров, что продуцирует такой вывод:

 

Рисунок 5-27


Вызов метода World() без параметров

На Шаге 9 вы активируете тот же метод World(), определяя некий единичный строковый параметр, что воспроизводит приводимый ниже вывод:

 

Рисунок 5-28


Вызов метода World() при определении единственного строкового параметров

Есть кое- что ещё...

На Шаге 1 вы изучаете различные определения некого метода - в данном случае метода класса System.String Trim(). Существуют различные способы для описания вами перегруженности метода. На данном шаге вы создаёте некий псевдо- объект, содержащий какую- то строку и затем просматриваете определения этого метода. PowerShell создаёт именованный объект (управляемый в .NET кучей), который немедленно уничтожается после завершения данного оператора. В некоторый момент позднее, .NET запускает свой процесс сборки мусора для возврата той памяти, которая применялась этим временным объектом и преобразует свою управляемую кучу.

Как вы можете наблюдать, имеются три перегруженных определения - три различных способа, коими вы способны активировать этот метод Trim(). Вы пользуетесь самым первым перегруженным определением для удаления как ведущих, так и замыкающих пробельных символов из некой строки, вероятно, наиболее применяемого использования Trim(). Для второго определения вы предписываете некий определённый символ и .NET удаляет все ведущие или завершающие появления этого символа. Для третьего определения вы предписываете некий массив символов для их отсечения с начала или с конца представленной строки. В большинстве случаев, последние два определения менее полезны, хотя это зависит от применяемого вами варианта. Всякая дополнительная гибкость полезна.

На Шаге 3 вы пользуетесь командлетом Add-Type для добавления определения своего класса в вашем текущем рабочем пространстве. Этот командлет компилирует соответствующий код C# и затем загружает некий DLL, который в последствии позволяет применять вам его классы. Если вы намерены применять это на постоянной основе, тогда вам надлежит добавить Шаг 2 и Шаг 3 в файл профиля своего PowerShell или в некий промышленный сценарий. В качестве альтернативы, когда вы можете применять этот метод в рамках некого сценария, вы можете добавить эти шаги в такой сценарий.

Как вы можете наблюдать из своего кода C# на Шаге 6, вы способны добавлять в некий метод множество определений. При помощи своих классов внутри .NET и их BCL, или в вашем собственном коде, перегружающие методы способны предоставлять большой выигрыш стоимости. Та документация по методам .NET, которую вы можете обнаружить на https://docs.microsoft.com/, в целом ориентирована на разработчиков, нежели на ИТ профессионалов. Если вы столкнулись с проблемами некого метода .NET, чувствуйте себя вправе выпалить некий запрос на одном из многочисленных форумов поддержки PowerShell, таком как группа PowerShell на Spiceworks: https://community.spiceworks.com/programming/powershell.

Создаём cmdlet PowerShell

Как уже упоминалось ранее, для большинства операций те команды и командлеты, которые доступны для вас естественным образом, снабжают вас всей необходимой вам функциональностью (в большинстве случаев). В рецепте Создаём расширение C# вы видели как вы можете создавать некое определение класса и добавлять его в PowerShell. В некоторых случаях вы можете пожелать выполнить расширение определения этого класса и создать свой собственный командлет.

Создание некого скомпилированного командлета требует от вас воспользоваться неким инструментом, таким как Visual Studio или применять бесплатные инструменты, предоставляемые Microsoft в качестве части их SDK (Software Development Kit, Набора разработки программного обеспечения) .NET Core. Visual Studio, будь то бесплатная редакция сообщества или коммерческие выпуски, это насыщенный и сложный инструмент, чья внутренняя работа далеко вне рамок этой книги. В помощь создания вами некого командлета с применением C# вполне достаточно бесплатных инструментов из SDK.

Как и в рецепте Создаём расширение C#, некий важный вопрос это почему и когда вам надлежит создавать командлет? Помимо вечной отговорки "потому что вы можете", имеются причины почему некое расширение или командлет могут оказаться неплохой мыслью. Вы можете создавать командлет для улучшения производительности - для ряда операций, скомпилированный командлет просто быстрее чем выполнение операций при помощи сценария PowerShell. Для ряда операций применение командлета состоит в выполнении действий, которые сложны или просто невозможны непосредственно в PowerShell, в том числе асинхронные операции и применение LINQ (Language Independent Query). Некий разработчик может писать свой командлет на C#, что позволяет вам запросто применять его в PowerShell.

Чтобы создать командлет, вам необходимо установить Windows SDK. Этот SDK содержит все те инструменты, которые вам требуются для создания командлета. Этот SDK бесплатен для выгрузки, которую вы можете получить через Интернет.

В данном рецепте вы выгружаете Windows SDK и вы пользуетесь им для создания некого нового командлета PowerShell. Собственно установка такого SDK .NET потребует от вас перехода на веб страницу, выгрузку и затем запуска установки.

Подготовка

Вы выполняете этот рецепт в SRV1, системе рабочей группы, запущенной в редакции Windows Server Datacenter Вам надлежит имеет загруженными в этом хосте PowerShell 7.1 и VS Code.

Как это сделать...

  1. Устанавливаем SDK .NET 5.0

    Откройте свой браузер, перейдите к https://dotnet.microsoft.com/download/ и следуйте инструкциям GUI для выгрузки и запуска установщика SDK. Вы можете наблюдать этот процесс в разделе Как это работает... ниже.

  2. Создаём папку cmdlet

    
    New-Item -Path C:\Foo\Cmdlet -ItemType Directory -Force
    Set-Location C:\Foo\Cmdlet
    		
  3. Создаём проект библиотеки нового класса

    
    dotnet new classlib --name SendGreeting
    		
  4. Просматриваем содержимое этой новой папки

    
    Set-Location -Path .\SendGreeting
    Get-ChildItem
    		
  5. Создаём и отображаем файл global.json

    
    dotnet new globaljson
    Get-Content .\global.json
    		
  6. Добавляем этот пакет PowerShell

    
    dotnet add package PowerShellStandard.Library
    		
  7. Создаём исходный файл командлета

    
    $Cmdlet = @"
    using System.Management.Automation;  // Windows PowerShell assembly.
    namespace Reskit
    {
      // Declare the class as a cmdlet
      // Specify verb and noun = Send-Greeting
      [Cmdlet(VerbsCommunications.Send, "Greeting")]
      public class SendGreetingCommand : PSCmdlet
      {
        // Declare the parameters for the cmdlet.
        [Parameter(Mandatory=true)]
        public string Name
        {
          get { return name; }
          set { name = value; }
        }
        private string name;
        // Override the ProcessRecord method to process the
        // supplied name and write a greeting to the user by 
        // calling the WriteObject method.
        protected override void ProcessRecord()
        {
          WriteObject("Hello " + name + " - have a nice day!");
        }
      }
    }
    "@
    $Cmdlet | Out-File .\SendGreetingCommand.cs
    		
  8. Удаляем не используемый файл класса

    
    Remove-Item -Path .\Class1.cs
    		
  9. Собираем свой командлет

    
    dotnet build
    		
  10. Импортируем тот DLL, который содержит собираемый командлет

    
    $DLLPath = '.\bin\Debug\net5.0\SendGreeting.dll'
    Import-Module -Name $DLLPath
    		
  11. Исследуем подробности полученного модуля

    
    Get-Module SendGreeting
    		
  12. Применяем этот командлет

    
    Send-Greeting -Name "Jerry Garcia"
    		

Как это работает...

На Шаге 1 вы открываете своё браузер и переходите на страницу выгрузки .NET, которая выглядит подобно следующему:

 

Рисунок 5-29


Страница выгрузки .NET

Как показано, кликаете на ссылку Download the .NET SDK X64 для выгрузки программы установки .NET SDK. Убедитесь, что вы выгружаете Download the .NET 5.0 SDK, что выглядит как показано ниже:

 

Рисунок 5-30


Выгрузка установщика SDK .NET

Программа установки имеет размер порядка 150МБ, поэтому для выгрузки может потребоваться какое- то время, зависящее от скорости вашего подключения к Интернету. По завершению выгрузки, вы обнаружите указание о выгрузке, которое отображается как- то так:

 

Рисунок 5-31


Завершение выгрузки установщика

Когда вы кликните по ссылке Open file, вы запустите полученный установщик и обнаружите открывшуюся страницу установки:

 

Рисунок 5-32


Установщик SDK .NET

После того как вы кликните по кнопке Install, эта программа установки завершит настройку SDK .NET. По окончанию настройки вы увидите финальный экран:

 

Рисунок 5-33


Экран успешной установки

Для завершения настройки кликните по Close. Если в процессе выполнения установки у вас имеется открытая консоль PowerShell (или VS Code), закройте и повторно откройте их. Выполнявшийся установщик обновляет вашу системную переменную Path с тем, чтобы вы были способны обнаруживать необходимые инструменты, что требует для вступления изменений в действие повторного запуска.

На Шаге 2 вы создаёте новую папку для содержания в ней вашего нового командлета и применяете Set-Location для перемещения в эту новую папку. Вывод этого шага приводится далее:

 

Рисунок 5-34


Создание папки командлета и переход в неё

На Шаге 3 вы создаёте при помощи команды dotnet.exe вы создаёте проект новой библиотеки класса, что выглядит как отображено ниже:

 

Рисунок 5-35


Создание проекта библиотеки нового класса

На Шаге 4 вы просматриваете содержимое той папки , которую создали на предыдущем шаге. Вы получаете такой вывод:

 

Рисунок 5-36


Просмотр содержимого вашей новой папки

На Шаге 5 вы создаёте новый файл global.json. Этот файл JSON, помимо всего прочего, определяет какую версию .NET надлежит применять для сборки вашего нового командлета. Вывод на этом этапе выглядит так:

 

Рисунок 5-37


Создание и отображение нового файла global.json

На Шаге 6 вы добавляете свой новый пакет PowerShell в ваш проект. Этот шаг позволяет вам применять тот класс, который содержится в этом пакете для сборки вашего командлета. Получаемый вывод отображается следующим образом:

 

Рисунок 5-38


Создание и отображение нового файла global.json

На Шаге 7 вы пользуетесь PowerShell и встроенной строкой для создания необходимого файла исходного кода под свой новый командлет. этот этап не производит вывод.

На Шаге 3 вы создаёте свой новый проект. Этот шаг к тому же создаёт файл с названием Class1.cs, который не требуется в данном проекте. Шаге 8, который не производит вывод, вы удаляете этот ненужный файл.

На Шаге 9 вы собираете свой командлет, что продуцирует следующий вывод:

 

Рисунок 5-39


Сборка вашего командлета

На Шаге 10 вы пользуетесь Import-Module для импорта созданного DLL, что не выдаёт вывода. На Шаге 11 вы применяете Get-Module для выявления этого импортированного модуля (содержащего лишь одну команду), что выглядит примерно так:

 

Рисунок 5-40


Сборка вашего командлета

На окончательном Шаге 12 этого рецепта вы исполняете полученный командлет, который воспроизводит такой вывод:

 

Рисунок 5-40


Применение вашего командлета

Есть кое- что ещё...

На Шаге 1 вы выгружаете SDK .NET, который необходим для создания командлета. Кажется, нет очевидного способа автоматизации всей установки, а потому вам требуется запустить установщик и покликать в его GUI чтобы установить SDK .NET. {Прим. пер.: если бы дела обстояли настолько плохо, это оказалось бы жестоким разочарованием для пользователей Windows Core и Linux с командной строкой, к счастью, имеются сценарии установки!}.

SDK .NET должен быть относительно прост в установке, как показано в этом рецепте. Вы выгружаете и запускаете установщик .NET и долее получаете возможность применять эти инструменты для сборки своего командлета. Однако в некоторых, чрезвычайно редких, ситуациях, ваша установка может не сработать как положено и/ или установщик может завершиться неудачей. Вам может потребоваться выполнить деинсталляцию всех прочих SDK, который вы загрузили или, возможно, запросите поддержку на таком форуме как https://community.spiceworks.com/programming/powershell. При всём этом, у вас не должно возникать подобной проблемы во вновь созданной ВМ.

На Шаге 3 вы создаёте проект библиотеки нового класса. Этот этап, как вы можете наблюдать, также создаёт файл SendGreeting.csproj. Именно этот файл и выступает файлом проекта Visual Studio .NET C#, который содержит подробности относительно включаемых в ваш проект командлета Send-Greeting, сборок, на которые ссылается ваш код и прочего. Для дополнительных сведений относительно файла проекта отсылаем вас к https://docs.microsoft.com/aspnet/web-forms/overview/deployment/web-deployment-in-the-enterprise/understanding-the-project-file.

На Шаге 5 вы создаёте файл global.json, если вы не создадите данный файл, команда dotnet выполнит компиляцию вашего командлета с самой последней версией .NET Core, загруженной в вашей системе. Применение данного файла сообщает процессу сборки вашего проекта применение конкретной версии .NET (например, версии .NET 5.0). Для обзора этого файла обратитесь к https://docs.microsoft.com/dotnet/core/tools/global-json.

На Шаге 9 вы компилируете свой файл исходного кода и создаёте DLL, содержащую ваш командлет. Этот этап для создания необходимой DLL компилирует все содержащиеся в данной папке файлы исходного кода. Это подразумевает, что у вас может иметься несколько командлетов, причём каждый в отдельном файле исходного кода и вы собираете весь набор в одной операции.

На Шаге 11 вы можете наблюдать, что ваш модуль, SendGreeting это некий модуль двоичного кода. Модуль двоичного кода просто загружается напрямую из DLL. Это прекрасно для проверки, однако при производстве вам надлежит создавать некий модуль манифеста, добавляя файл манифеста. Вы можете получить дополнительные сведения относительно файлов манифеста из https://docs.microsoft.com/powershell/scripting/developer/module/how-to-write-a-powershell-module-manifest?view=powershell-7.1. Вам также следует переместить сам модуль и манифест, а также прочие модули содержимого, такие как файлы справки, в некое поддерживающее модуль местоположение. К тому же вы можете рассмотреть публикацию такого завершённого модуля либо в Галерее PowerShell, либо в неком внутреннем репозитории.