Язык PowerShell, как и множество других языков, поддерживает концепцию областей — Scopes. Что такое область? Давайте рассмотрим это на примере. Предположим, мы создали сценарий, показанный в листинге 1, и сохранили его на диске C: под именем Script.ps1. Если мы вызовем его из командной строки PowerShell

C:\Script.ps1

он, естественно, запустится и отработает. Однако если после его выполнения мы обратимся к переменной $PSNames, функции Get-PowerShellProcess или псевдониму pspwsh, то мы их не обнаружим.

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

Для чего это нужно? Во-первых, это очень удобно, когда каждый сценарий или функция не забывают «прибрать за собой» без каких-либо напоминаний с нашей стороны. Представьте, что было бы, если бы любой вызванный код оставлял за собой все переменные, псевдонимы, функции и прочие элементы. Во-вторых, выполнение сценариев и функций в отдельных областях предотвращает случайное изменение элементов, определенных в родительской области. Например, если мы выполним код листинга 2, то увидим, что, несмотря на то что функция RedefineVariable изменяет значение переменной $Variable, вне этой функции, то есть в родительской области, переменная сохраняет свое первоначальное значение.

Области

Действие областей в PowerShell распространяется на следующие элементы: переменные (Variables), псевдонимы (Aliases), функции (Functions) и диски PowerShell (PSDrives).

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

  • Переменные, псевдонимы, функции и диски PowerShell доступны только в той области, где были созданы, а также в ее дочерних областях, в том случае, если они не определены как частные (Private).
  • Элемент может быть изменен только в той области, где он был создан. Исключением является явное указание области, к которой он принадлежит.

В общем случае, в своей собственной — дочерней области выполняется код функций (Functions), сценариев (Scripts) и блоков сценариев (ScriptBlocks), однако под влиянием различных обстоятельств, которые мы рассмотрим позже, их поведение может меняться.

Обращение к элементам в других областях

Как уже упоминалось выше, элемент, созданный в определенной области, может быть доступен из ее дочерних областей. Например, если мы создадим переменную $Variable, присвоим ей значение Global, а затем обратимся к ней из блока сценария, содержимое которого, как мы уже знаем, выполняется в дочерней области, то в качестве результата получим изначально присвоенное этой переменной значение:

$Variable = "Global"
& {$Variable}

Global

Что здесь произошло? Когда мы из блока сценария обратились к переменной $Variable, командная среда сначала попыталась найти ее в собственной, локальной области. Так как поиск не принес каких-либо результатов, PowerShell предпринял попытку поиска переменной с этим именем в родительской области, где она и была успешно найдена. Это справедливо и для большего количества областей:

$Variable = "Global"
& {& {& {$Variable}}}

Global

Если бы переменная с таким же именем была определена в локальной области, или же в одной из областей, расположенных выше по иерархии, то в поиске переменной в самой верхней — глобальной области не было бы необходимости (листинг 3).

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

В первой строке листинга 4 мы определили переменную $Variable в глобальной области и присвоили ей значение Global. Затем в блоке сценария мы назначили переменной $Variable значение ScriptBlock. Так как мы сделали это внутри блока сценария и, кроме того, не указали явным образом область переменной, то получается, что, в сущности, мы создали новую переменную в области блока сценария с указанным значением. Таким образом, переменная $Variable в области блока сценария имеет значение ScriptBlock, а одноименная переменная в глобальной области — значение Global.

Изменение элементов в других областях

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

Следует отметить, что имена областей являются абсолютными, а номера — относительными. Рассмотрим эти варианты подробнее.

Имена областей

Имена областей могут быть следующими: Global, Local и Script. Как можно догадаться, имя области Global используется при обращении к элементам, расположенным в глобальной области. Глобальная область — это вершина иерархии областей, и создается она при запуске среды PowerShell. В ней расположены функции, псевдонимы, переменные и диски PowerShell, создаваемые при запуске среды. Элементы, расположенные в файлах профилей (определяемых переменной $Profiles), также создаются в глобальной области.

Имя Local указывает на локальную область, вне зависимости от того, на каком уровне иерархии областей она расположена. Таким образом, при обращении к ней в глобальной области она будет указывать на глобальную область, в файле сценария — на область этого сценария, а в функции — соответственно на область функции.

Имя Script, как опять-таки можно предположить, указывает на область файла сценария. Кроме того, это имя может нам пригодиться при создании модулей, однако об этом позже.

Использовать имена областей мы можем несколькими способами. Один из них — указать имя области в качестве модификатора непосредственно перед именем элемента. Например, так, как показано в листинге 5.

Имя области Script можно использовать при обращении к некоторым элементам, определенным в файле сценария и общим для всех расположенных в нем функций и блоков сценария. К примеру, код файла сценария Script.ps1 может быть следующим:

$Counter = 0

function IncreaseCounter
{
    $Script:Counter++
}
function DecreaseCounter
{
    $Script:Counter--
}

function ReturnCounter
{
    $Script:Counter
}

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

Здесь напрашивается вопрос: на какую область будет указывать имя Script в случае, если из одного файла сценария мы вызовем другой? А из него еще один?

Работает это следующим образом. Использование имени Script всегда указывает на область текущего файла сценария. Обращение к любой переменной из кода файла сценария с использованием имени области Script всегда возвращает переменную, определенную в этом же файле сценария (естественно, если она была в нем определена), вне зависимости от того, каким образом этот сценарий был вызван, напрямую или из другого сценария.

Еще один любопытный факт состоит в том, что имя области Script, использованное вне файла сценария, ссылается на глобальную область (листинг 6). Исключением является использование этого имени в файлах модуля, о чем, опять же, чуть позже.

Кроме переменных, данный способ указания имен областей (и надо сказать, только он) может быть использован и для функций, как показано в листинге 7.

Вторым способом использования имен областей является их указание в качестве значения параметра -Scope команд PowerShell для работы с переменными, псевдонимами и дисками PowerShell, такими как Get-Variable, Set-Alias и New-PSDrive. Пример приведен в листинге 8.

Напомню, что для функций этот способ недоступен.

Номера областей

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

Значение 0 всегда указывает на локальную область, и можно сказать, что номер области 0 является синонимом имени Local. Значение 1 указывает на непосредственную родительскую область, 2 — на область выше и т. д. Указываются номера областей в качестве значений все того же параметра -Scope уже упомянутых выше команд PowerShell, предназначенных для работы с переменными, псевдонимами и дисками PowerShell. Например, так, как показано в листингах 9 и 10.

Если в качестве аргумента параметра -Scope мы укажем число, превосходящее количество родительских областей, то получим сообщение об ошибке. Например, если из глобальной области мы попытаемся обратиться к области с номером 1, то, поскольку глобальная область является верхней в иерархии и не имеет собственной родительской области, мы получим следующий результат:

Get-Variable -Scope 1
Get-Variable : The scope number
   '1' exceeds the number of active scopes.

Параметр Option

Помимо параметра -Scope, команды PowerShell для работы с переменными и псевдонимами обладают параметром -Option. Среди всех его возможных значений особенный интерес для нас представляют два. Это Private и AllScope.

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

Например, если мы создадим переменную $PrivateVariable в глобальной области и затем попробуем обратиться к ней из блока сценария, как с использованием имени области, так и без него, ее содержимое нам будет недоступно, тогда как в глобальной области мы вполне можем ею воспользоваться (листинг 11).

Еще одним способом создания частных переменных является указание Private в качестве модификатора перед именем переменной, так же как и в случаях с использованием имен областей — Global, Local или Script.

$Private: Variable = "Global Private"

Таким же образом мы можем создать и частную функцию.

function Private:TheFunction
{
    Write-Output "Private function in the
    global scope."
}

AllScope. Что же касается второго значения параметра -Option, а именно AllScope, то используется оно для создания элементов — переменных и псевдонимов, принадлежащих всем областям в иерархии, начиная с той, в которой они были созданы.

Это означает, что созданная в области сценария переменная не станет частью глобальной области, однако будет входить как в область сценария, так и в каждую дочернюю по отношению к ней область (листинг 12).

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

Что же касается изменения значений переменных, определенных с помощью значения AllScope, то, поскольку такие переменные принадлежат каждой области, начиная с той, в которой были созданы, то и изменение их значений отрази­тся на каждой из этих областей (листинг 13). Все сказанное справедливо и для псевдонимов.

Команда Invoke-Command и операторы вызова

Как мы знаем, для того чтобы выполнить сценарий или функцию, нам достаточно указать их имена с уточнением, что для файла сценария указываемое имя должно быть полностью определенным, то есть включать в себя либо полный путь с указанием диска, либо путь относительно текущего каталога, представленного символом точки. Таким образом, файл сценария с именем Script.ps1, расположенный в корне диска C:, мы можем вызвать следующими способами:

C:\> C:\Script.ps1
C:\> .\Script.ps1

Что же касается блоков сценариев, то их определение само по себе не приводит к их исполнению.

{Get-Process powershell}

Для того чтобы выполнить интересующий нас блок сценария, нам потребуется задействовать команду PowerShell по имени Invoke-Command или оператор вызова (call operator) — ‘&’.

Invoke-Command -ScriptBlock
   {Get-Process powershell}
& {Get-Process powershell}

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

$ScriptBlock = {Get-Process powershell}
Invoke-Command $ScriptBlock
& $ScriptBlock

Кроме того, мы можем задействовать оператор ‘&’ и для вызова функций и файлов сценариев.

& SomeFunction
& C:\script.ps1

Что же касается использования команды PowerShell Invoke-Command для вызова файлов сценариев, то здесь нам потребуется указать имя компьютера, на котором мы собираемся его выполнить, даже в том случае, если это локальный компьютер.

Invoke-Command -FilePath
   C:\script.ps1 -ComputerName localhost

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

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

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

Чтобы выполнить сценарий, функцию или блок сценария в текущей области, нам потребуется воспользоваться оператором ‘.’, также известным как dot sourcing operator. Например:

. C:\script.ps1
. SomeFunction
. {$Variable = ‘Value’}

В случае файлов сценариев также допустимо использование относительного пути:

. .\script.ps1

В качестве иллюстрации предлагаю вернуться к самому первому приведенному в статье примеру и на этот раз сделать так, чтобы все созданные им элементы остались в нашем распоряжении. Итак, в корне диска C: находится файл сценария с именем Script.ps1, содержащий код, указанный в листинге 1. Выполним его с использованием оператора ‘.’. После завершения работы сценария мы увидим, что все определенные в его коде элементы — переменная $PSNames, функция Get-PowerShellProcess и псевдоним pspwsh — были созданы в текущей области и по-прежнему доступны для использования (листинг 14).

Удаленные подключения. Оператор ‘.’ также может нам понадобиться при выполнении подключений к удаленным компьютерам. Когда вы устанавливаете удаленное подключение, а сделать это можно при помощи команд PowerShell с именами New-PSSession или Enter-PSSession, по умолчанию файлы профилей не применяются. Также не будет доступна и автоматическая переменная $Profiles, та, что при локальной работе содержит пути к файлам профилей.

Таким образом, если профиль на некотором компьютере содержит нужную вам функцию, то, подключившись к нему удаленно, вы ее не обнаружите. Для того чтобы определенные в файле профиля элементы стали нам доступны, потребуется выполнить файл профиля с использованием оператора ‘.’.

Поскольку переменная $Profiles в удаленной сессии не создается, путь к файлу профиля нам придется указывать самостоятельно, хотя и в этом случае мы можем несколько сократить строку пути, воспользовавшись автоматической переменной $Home или переменной среды $Env:UserProfile.

Сначала, используя команду PowerShell Enter-PSSession, подключимся к компьютеру с именем computer_name:

Enter-PSSession -ComputerName
   computer_name

Затем, уже на удаленном компьютере, выполним следующую команду:

. $home\Documents\WindowsPowerShell\
   Microsoft.PowerShell_profile.ps1

Если же вы хотите, чтобы в удаленной сессии были доступны элементы, определенные в вашем локальном профиле, можете поступить следующим образом:

$Session = New-PSSession
   -ComputerName some_computer
Invoke-Command -Session
   $Session -FilePath $profile

Модификатор Using. К сфере удаленных подключений относится и модификатор Using, по способу использования напоминающий имена областей, такие как Global, Local и Scope. Тем не менее, используется он для несколько иных целей.

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

Однако, что если в качестве аргумента для некоторой выполняемой в удаленной сессии команды мы хотим задать значение определенной локальной переменной?

До выхода Windows PowerShell версии 3 для этого был предназначен способ, приведенный в листинге 15. Здесь мы внутри блока сценария определяем параметр с именем ProcessName, которому в момент вызова команды PowerShell Invoke-Command при помощи параметра ArgumentList передаем значение локальной переменной $PowerShell.

В третьей версии Windows PowerShell появилась поддержка модификатора Using, и теперь мы можем преобразовать команду так, как показано в листинге 16.

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

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

С другой стороны, мы всегда можем указать:

$Process = Invoke-Command
   -ComputerName computer_name
   -ScriptBlock {Get-Process
   -Name $Using:PowerShell}

Кроме того, этот способ не позволит нам задействовать в удаленной сессии функцию, определенную локально (листинг 18). Однако мы можем сделать так, как показано в листинге 19.

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

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

Блоки сценариев

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

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

Получить список команд PowerShell, принимающих блоки сценариев в качестве значений определенных параметров, можно следующим образом:

Get-Command -ParameterType ScriptBlock

Некоторые из них, такие как New-Module, Set-PSBreakpoint или Start-Job, используют указанный в качестве аргумента блок сценария для создания на его основе некоторых новых сущностей. В случае приведенных выше команд это будут динамический модуль, точка останова при отладке и фоновое задание PowerShell соответственно. В таких случаях блоки сценария, будучи их частью, выполняются в собственной области. Кроме того, как мы уже знаем, в собственной области выполняет блоки сценария и команда Invoke-Command. Те же из команд, что используют полученный блок сценария непосредственно в процессе работы, к примеру, для анализа поступающих по конвейеру объектов, таких как Where-Object, выполняют его в текущей области.

К таким командам относятся уже упомянутая Where-Object, а также ForEach-Object, Measure-Command и Trace-Command. Указав блок сценария в качестве аргумента для любой из них, мы можем быть уверены, что выполняться он будет в той области, откуда эта команда была вызвана.

Это же касается и конструкции foreach, которую при всей схожести не стоит путать с командой PowerShell с именем ForEach-Object: содержащийся в ней код выполняется в той же области, где находится сама конструкция:

$ThreadCount = 0
foreach ($Process in Get-Process)
{
    $ThreadCount += $Process.Threads.Count
}
$ThreadCount
2578

Кроме того, в текущей области выполняются и конструкции if, for, while и do.

Особняком стоит конструкция switch. Если условия для сравнения представлены в виде блоков сценариев, каждый из них будет выполняться в своей области. Однако же код, вызываемый в случае выполнения соответствующего ему условия, будет выполнен в текущей области. Рассмотрим пример, приведенный в листинге 20.

Предположим, мы решили подсчитать количество выполняющихся процессов с именем powershell, представляющих собой Windows PowerShell, pwsh, являющихся процессами PowerShell Core, а также общее количество запущенных процессов. Для этого мы заранее определили переменные $PowerShell, $Pwsh и $Processes как равные нулю. Затем мы используем конструкцию switch, где в блоках условий сравниваем имя обрабатываемого в данный момент процесса со строками ‘powershell’ и ‘pwsh’ и в случае совпадения увеличиваем значение соответствующей переменной на 1.

Что же касается общего количества процессов, то их подсчет мы ведем в блоке сценария, который представляет собой первое условие конструкции switch. Хотя, так как мы не используем ключевые слова Continue и Break, мы точно так же могли бы это делать и в блоке сценария второго условия, поскольку в данном случае каждый из блоков будет выполнен для каждого обрабатываемого процесса. Однако, когда мы получаем значения переменных $PowerShell, $Pwsh и $Processes, мы видим, что последнее из них равно нулю. А происходит это потому, что, как уже было сказано, каждый из блоков сценариев, представляющих собой условия конструкции switch, выполняется в своей области.

Для того чтобы после завершения выполнения кода определенная в глобальной области переменная $Processes содержала интересующее нас значение, нам потребуется задействовать модификатор Global. Естественно, это не касается переменных $PowerShell и $Pwsh, поскольку блоки сценариев, в которых они расположены, выполняются в той области, где находится сама конструкция switch, и в данном случае это глобальная область (листинг 21).

Блоки сценариев как значения параметров в конвейере. Отдельно стоит рассмотреть применение блоков сценариев в качестве значений параметров при использовании конвейера.

К примеру, если мы вызовем команду листинга 22, то формирующий значение параметра -Filter блок сценария каждый раз будет выполняться в своей области.

Для того чтобы код в последующих примерах выглядел более аккуратно, предлагаю блок сценария, указываемый в качестве значения параметра -Filter, задать в виде значения переменной $ScriptBlock и в команде PowerShell Get-CimInstance использовать уже ее (листинг 23).

Теперь если мы решим добавить в этот блок сценария некоторую функциональность, например возможность сохранения в переменной $Status сообщений о том, в какой момент времени происходит обработка каждого из поступающих по конвейеру объектов, то без использования в коде нужных модификаторов после завершения выполнения команды содержимое этой переменной будет нам недоступно (листинг 24).

Однако, добавив перед именем переменной модификатор Global, мы укажем, что сохранение значений должно происходить в переменной $Status, определенной в глобальной области (листинг 25).

Метод GetNewClosure. Стоит упомянуть и о такой любопытной особенности объектов блоков сценариев System.Management.Automation.ScriptBlock, как метод GetNewClosure. Для чего он нужен?

К примеру, вы определили некоторую переменную как содержащую определенное значение. Пусть это будет уже использовавшаяся нами переменная $PSNames, на этот раз с единственным значением ‘powershell’. Если мы создадим блок сценария, как в листинге 26, используя эту переменную, и затем запустим его на выполнение, то получим список процессов с именем ‘powershell’.

Однако, если затем мы изменим значение переменной $PSNames на, скажем, ‘Windows PowerShell’, то, еще раз выполнив блок сценария, получим сообщение о том, что процесса с именем ‘Windows PowerShell’ не существует (листинг 27).

Но что если мы хотим, чтобы созданный нами блок сценария не зависел от текущего значения переменной, а использовал то ее значение, которое было на момент его создания? Для этого мы можем воспользоваться методом GetNewClosure. Например, как показано в листинге 28.

Как мы видим, изменение значения переменной $PSNames больше не влияет на работу блока сценария.

Обращение к переменным с использованием свойств и методов

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

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

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

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

$Variable = @{ProcessName = ''}
& {$Variable.ProcessName = 'powershell'}
$Variable.ProcessName

powershell

То же касается и использования методов.

& {$Variable.Add('Description',
   'Windows PowerShell')}
$Variable.Description

Windows PowerShell

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

Использование объектов класса PSReference

В некоторых случаях альтернативой именам и номерам областей может стать использование ссылок на объекты, являющиеся значениями определенных в какой-либо области переменных. Они представляют собой объекты типа System.Management.Automation.

PSReference. Сокращенное наименование, которое мы и будем использовать в последующих примерах, — [ref].

Когда мы преобразовываем значение некоторой переменной к типу [ref], мы фактически создаем ссылку на находящийся в ней объект. И затем, посредством свойства Value объекта ссылки, можем с ним взаимодействовать таким же образом, как если бы мы обращались к переменной, значением которой он является, напрямую.

$Variable = 'Value'
$Reference = [ref]$Variable
$Reference.Value = 'New Value'
$Variable
New Value

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

Сначала мы создаем нужную нам функцию, где в блоке Param определяем некий параметр как соответствующий типу [ref]. Строго говоря, указание типов данных параметров функций, а также сценариев и блоков сценариев обязательным в PowerShell не является, однако, для того чтобы избежать передачи им аргументов иных, отличных от ожидаемых, типов, стоит задать их явным образом.

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

function UsingReferences
{
    Param([ref]$String)
    $String.Value = $String.Value.ToUpper()
}

Затем в текущей области, например в глобальной, мы определяем переменную, обладающую неким начальным значением, причем это вполне может быть и $null. Здесь важно, чтобы ее тип был совместим с типом данных, которые будут ей назначены в процессе выполнения функции. То есть это должен быть либо тот же самый тип, либо такой, к которому назначаемый функцией объект может быть преобразован.

После этого при вызове созданной нами ранее функции в качестве аргумента мы передаем ссылку на данную переменную, указав перед ее именем [ref].

$PowerShell = 'PowerShell'

UsingReferences([ref]$PowerShell)

Теперь, если мы обратимся к определенной в глобальной области переменной $PowerShell, то увидим, что ее значение было изменено.

$PowerShell

POWERSHELL

И естественно, мы можем создать ссылку на нужный нам объект и назначить ее некоторой переменной заранее, а затем, при вызове функции, указать эту переменную в качестве аргумента (листинг 30).

Помимо функций, мы можем использовать объекты PSReference при работе со сценариями и блоками сценариев (листинг 31).

Модули PowerShell

Теперь давайте перейдем к модулям. В сущности, модуль является контейнером, в котором содержатся команды PowerShell, функции, псевдонимы, переменные и другие элементы. В обычном случае он состоит из файла манифеста —. psd1, корневого файла модуля —. psm1 и, при необходимости, файлов вложенных модулей. Это могут быть как файлы модулей. psm1, так и файлы сценариев. ps1.

При импорте модуля, как при помощи команды PowerShell с именем Import-Module, так и автоматически, при вызове одной из входящих в него команд, определенные в модуле элементы экспортируются в текущую сессию. Использование терминов «импорт» и «экспорт» в одном предложении может сбить с толку, однако в соответствии с официальной документацией именно так это и происходит: мы импортируем модуль, при этом составляющие его элементы экспортируются в рабочую среду.

При создании собственного модуля мы можем указывать, какие именно элементы должны быть экспортированы при его импорте. Сделать это можно при помощи команды PowerShell с именем Export-ModuleMember, указав ее в конце корневого файла модуля. Выглядеть она может следующим образом:

Export-ModuleMember -Function
   Get-Something -Alias gsmth
   -Variable Something

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

Давайте остановимся на этом поподробнее. Допустим, в корневом файле модуля мы определили две переменные: $Public и $Internal. Переменная $Public будет экспортирована при импорте модуля, а $Internal — нет. Для простоты предлагаю в данном случае не создавать файл манифеста, а ограничиться корневым файлом модуля. Содержимое этого файла — назовем его TheModule.psm1 — будет следующим:

$Public = ‘Public Value’
$Internal = 'Internal Value'

Export-ModuleMember -Variable Public

Теперь если, предварительно импортировав модуль, мы обратимся к обеим определенным в нем переменным, то увидим, что переменная $Public нам доступна, в то время как $Internal отсутствует.

Import-Module .\TheModule.psm1
$Public
Public Value

$Internal

Давайте добавим в наш модуль функцию, получающую значения этих переменных (листинг 32).

Заново импортировав модуль, на этот раз с использованием параметра -Force, который указывает на необходимость повторного импорта модуля, даже если он уже загружен, еще раз обратимся к переменным, теперь посредством определенной в этом же модуле функции.

Import-Module .\TheModule.psm1 -Force
Get-ModuleVariable

Public is: Public Value
Internal is: Internal Value

Как видите, мы получили оба значения.

Если же мы изменим наш модуль таким образом, как в листинге 33, и снова вызовем функцию Get-ModuleVariable, то получим результат, показанный в листинге 34.

Мы знаем, что область 0 — это локальная область функции и, поскольку в ней самой переменные с такими именами определены не были, запрос их значений не приносит каких-либо результатов.

Область 1 — это область модуля, та, где были определены переменные $Public и $Internal. А область 2, содержащая только переменную $Public, — это глобальная область, куда эта переменная была экспортирована при импорте модуля.

Подтверждением тому, что область 2 — это вершина иерархии областей, служит сообщение об ошибке, которое представляет собой результат запроса области с номером 3. Из него следует, что области 3 не существует, а это означает, что область с номером 2 — глобальная область.

Если же в функции Get-Module­Variable вместо номеров областей мы задействуем их имена, что сделает наш код гораздо более аккуратным (листинг 35), мы увидим, что модификатор Script, использованный в файлах модуля, ссылается на область этого самого модуля (листинг 36).

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

Однако как же так получается, что переменная $Public доступна и в глобальной области, и в области модуля? Является ли она частью обеих областей или же это разные переменные, принадлежащие каждая своей области?

Давайте проверим. Для этого в файл модуля TheModule.psm1 добавим еще одну функцию, на этот раз для изменения значений переменных. Назовем ее Set-ModuleVariable. А также немного изменим и функцию Get-ModuleVariable (листинг 37).

Теперь при помощи функции Set-ModuleVariable изменим значение переменной Public из области модуля, как показано в листинге 38.

Как мы видим, новое значение возвращается при запросе переменной как из области модуля, так и из глобальной. Теперь давайте попробуем изменить ее значение из глобальной области (листинг 39).

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

Вложенные модули. Область модуля содержит все определенные в файлах модуля элементы, вне зависимости от того, расположены ли они в корневом файле модуля, указываемом в манифесте как RootModule, или, до выхода PowerShell версии 3, как ModuleToProcess, либо же в файлах вложенных модулей, определенных в манифесте как NestedModules.

Таким образом, даже если наш модуль состоит из корневого файла модуля — TheModule.psm1 и, к примеру, двух файлов вложенных модулей — Functions.ps1 и HelperFunctions.ps1, область модуля будет общей для всех и соответственно будет содержать элементы из всех трех файлов.

Параметр Visibility. Неким аналогом переменных, доступных только изнутри модуля, то есть тех, что при импорте модуля не экспортируются в рабочую среду, являются переменные, созданные при помощи команд PowerShell с именами New-Variable и Set-Variable со значением параметра Visibility, установленным в Private (не путать с одноименным значением параметра Option).

К примеру, если в одном из файлов модуля мы укажем команду

New-Variable -Name PrivateVisibilityVariable
   -Visibility Private -Value 'Private Value'

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

$PrivateVisibilityVariable

Cannot access the variable
   '$PrivateVisibilityVariable' because it is
   a private variable.

Команда Import-Module. Если мы присмотримся к команде PowerShell с именем Import-Module, то увидим, что она, как и команды для работы с переменными и псевдонимами, обладает параметром -Scope, хотя в этом случае возможных значений только два — Local и Global. Для чего он используется?

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

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

function Test-ModuleImport
{
    Import-Module CimCmdlets
    Get-Module CimCmdlets
}

а затем, предварительно удостоверившись, что модуль CimCmdlets не был импортирован, выполним входящую в OuterModule функцию Test-ModuleImport (листинг 40), то увидим, что в области модуля OuterModule импорт произошел успешно. Однако если затем мы выполним команду PowerShell с именем Get-Module уже в глобальной области

Get-Module CimCmdlets

то каких-либо результатов не получим, что означает — в глобальную область модуль импортирован не был.

Происходит это потому, что по умолчанию в качестве значения параметра -Scope команды PowerShell Import-Module используется Local.

Теперь если мы изменим код файла модуля OuterModule.psm1 таким образом, чтобы команда PowerShell Import-Module содержала параметр -Scope со значением Global, уберем команду Get-Module

function Test-ModuleImport
{
    Import-Module CimCmdlets -Scope Global
}

а затем снова вызовем функцию Test-ModuleImport, как показано в листинге 41, то увидим, что на этот раз модуль CimCmdlets был импортирован уже в глобальную область.

Вместо параметра -Scope со значением Global мы также можем использовать и отдельный параметр -Global. Таким образом, следующие две команды с точки зрения функциональности являются полностью равнозначными:

Import-Module module_name -Scope Global
Import-Module module_name -Global

Использование же в модуле команд напрямую, то есть без предварительного импорта содержащего их модуля при помощи команды Import-Module, приводит к тому, что соответствующий модуль автоматически импортируется в глобальную область. Например, если в коде функции Test-ModuleImport мы заменим Import-Module на входящую в модуль CimCmdlets команду Get-CimClass

function Test-ModuleImport
{
    Get-CimClass -ClassName
    Win32_ComputerSystem
}

а затем, выгрузив модуль CimCmdlets из рабочей среды и заново импортировав OuterModule.psm1, вызовем эту функцию и еще раз воспользуемся командой Get-Module, как в листинге 42, то в качестве результата опять же получим объект PSModuleInfo, соответствующий модулю CimCmdlets. Это говорит о том, что модуль был импортирован в глобальную область.

Стоит подчеркнуть, что результатом вызова команды Import-Module из сценария, функции или блока сценария, не являющихся частью модуля, будет импорт указанного в качестве аргумента модуля в глобальную область, вне зависимости от использования параметра -Scope и его значения.

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

Для этого нам понадобится оператор вызова — ‘&’. Мы его уже использовали для вызова блоков сценариев. Выглядело это следующим образом:

& {Get-Variable}

Мы помним, что расположенный в блоке сценария код выполняется в своей области, которая, в свою очередь, является дочерней по отношению к той области, откуда этот блок сценария был вызван. Однако если между оператором ‘&’ и кодом, который нам нужно выполнить, мы укажем объект типа PSModuleInfo, получить который мы можем при помощи команды PowerShell Get-Module, то следующий за ним блок сценария будет выполнен в области, являющейся дочерней уже по отношению к области указанного модуля.

Для иллюстрации этого подхода давайте еще раз обратимся к созданному нами ранее модулю TheModule.psm1 (листинг 37).

Как показано в листинге 43, вследствие того, что команда была выполнена в области, дочерней по отношению к области модуля, ее результат содержит значения обеих переменных. Стоит подчеркнуть, что при выполнении команд в области модуля переменная, которую мы указываем после оператора (в нашем случае это $TheModule), должна быть создана после того, как модуль был импортирован. Таким образом, если мы сначала создадим переменную, содержащую объект модуля, а затем, импортировав этот модуль, сошлемся на нее при вызове команды, то получим сообщение об ошибке, как в листинге 44.

С другой стороны, мы вполне можем обойтись и без использования переменной для хранения объекта модуля путем его импорта непосредственно при вызове команды, что, однако, несколько усложнит наш код. Например, так:

& (Import-Module .\TheModule.psm1
   -Force -PassThru) {Get-Variable
   -Name Public,Internal}

Помимо получения значений переменных, используя оператор ‘&’, мы таким же образом можем обратиться и к определенным, но не экспортированным в рабочую среду функциям. Предположим, что, кроме основной, наш модуль TheModule.psm1 содержит еще вспомогательную функцию с именем InternalHelperFunction, предназначенную для использования только элементами данного модуля (листинг 45).

Если, импортировав модуль, мы вызовем функцию Get-PowerShellProcess, то в рамках ее выполнения функция InternalHelperFunction также будет выполнена. Тем не менее обратиться к ней напрямую у нас не получится, как показано в листинге 46.

Однако, воспользовавшись оператором ‘&’ и объектом модуля, в котором эта функция была определена, мы все-таки сможем ее выполнить (листинг 47).

Вследствие того что при использовании оператора ‘&’ блок сценария выполняется в собственной области, мы не сможем обратиться к переменным (за некоторым исключением) и функциям, определенным в файлах модуля в качестве частных (Private). Например, если мы изменим корневой файл модуля, TheModule.psm1, так, как показано в листинге 48, а затем попробуем получить значение переменной Private

Import-Module .\TheModule.psm1 -Force
$TheModule = Get-Module TheModule
& $TheModule {$Private}

то желаемого результата мы не получим. Хотя мы все же сможем к ней обратиться, если воспользуемся командой PowerShell с именем Get-Variable, где в качестве значения параметра -Scope укажем область этой переменной явным образом. Однако использовать формат $Script: Private нам опять же не удастся (листинг 49).

Если же мы попытаемся вызвать функцию PrivateFunction, то получим сообщение об ошибке:

& $TheModule {PrivateFunction}
PrivateFunction: The term 'PrivateFunction'
   is not recognized as the name of a
   cmdlet, function, script file, or operable
   program. Check the spelling of the name,
   or if a path was included, verify that the
   path is correct and try again.

Как мы помним, элементы, созданные с использованием параметра Private, доступны только из той области, где они были определены. Кроме того, мы знаем, что, в отличие от оператора вызова ‘&’, использование оператора ‘.’ приводит к тому, что блок сценария будет выполнен не в собственной области, а в той, откуда он был вызван. Или, в случае указания объекта модуля, в области этого самого модуля.

Таким образом, если мы попробуем обратиться к элементам нашего модуля TheModule при помощи оператора ‘.’, то, поскольку выполнение блока сценария будет происходить в области самого модуля, а не в дочерней, они все будут нам доступны (листинг 50).

Надо сказать, что описанный выше подход будет особенно полезен в диагностике и устранении ошибок при создании собственных модулей.

Модуль sthModuleScope. Вам также может пригодиться модуль sthModuleScope и входящие в него функции: Enter-sthModuleScope, Get-sthModuleScopeFunction, Get-sthModuleScopeVariable и Get-sthScopeDepth. Функция Enter-sthModuleScope позволяет войти в область указанного в качестве аргумента модуля и работать с его элементами без необходимости использования операторов ‘&’ или ‘.’ при вызове каждой команды.

Get-sthModuleScopeFunction и Get-sthModuleScopeVariable дают возможность получить все определенные в модуле функции и переменные соответственно. Вы можете задействовать эти функции как находясь в области модуля, так и из глобальной области. При их использовании без предварительного входа в область нужного модуля вам потребуется указать его в качестве значения параметра -Module.

Функция Get-sthScopeDepth позволяет узнать, на каком уровне иерархии областей вы в данный момент находитесь, что может быть полезно при отладке функций и сценариев.

Вы можете загрузить модуль sthModuleScope из PowerShell Gallery при помощи следующей команды:

Install-Module -Name sthModuleScope

Репозиторий модуля на GitHub: https://github.com/sethvs/sthModuleScope.

Листинг 1. Демонстрация областей в PowerShell
Set-Variable -Name PSNames -Value ‘powershell’, ‘pwsh’
 
function Get-PowerShellProcess
{
    Get-Process -Name $PSNames -ErrorAction SilentlyContinue
}
 
Set-Alias -Name pspwsh -Value Get-PowerShellProcess
Листинг 2. Сосуществование областей
function RedefineVariable
{
    $Variable = 'Function Scope'
}

$Variable = 'Global Scope'
RedefineVariable
$Variable

Global Scope
Листинг 3. Обращение к переменной
$Variable = "Global"
& {& {& {$Variable = 'Local'; $Variable}}}

Local

$Variable = "Global"
& {$Variable = 'Intermediate'; & {& {$Variable}}}

Intermediate
Листинг 4. Пример изменения переменной
$Variable = "Global"
& {$Variable = "ScriptBlock"

Write-Output "ScriptBlock variable: $Variable"}
Write-Output "Global variable: $Variable"

ScriptBlock variable: ScriptBlock
Global variable: Global
Листинг 5. Использование имен областей
$Variable = "Global"
& {$Local:Variable = "ScriptBlock Local"
$Global:Variable = "ScriptBlock Global"
$Variable}
$Variable

ScriptBlock Local
ScriptBlock Global
Листинг 6. Имя области Script, использованное вне файла сценария
$Variable = 'Global'
& {
    $Variable = 'ScriptBlock'
    $Script:Variable
}
Global
Листинг 7. Указание имен областей для функций
& {function Global:TheFunctionInTheGlobalScope
    {
        "I'm still here."
    }
}
TheFunctionInTheGlobalScope

I'm still here.
Листинг 8. Указание имен областей в качестве значения параметра -Scope
& {Set-Variable -Name "Variable" -Value "GlobalVariable" -Scope "Global"
Set-Alias -Name "gtps" -Value "Get-Process" -Scope "Global"
New-PSDrive -Name "Windows" -PSProvider "FileSystem" -Root "C:\Windows" -Scope "Global"}

$Variable
Get-Alias gtps | Select-Object -ExpandProperty Definition
Test-Path Windows:\

GlobalVariable
Get-Process
True
Листинг 9. Использование номеров областей
$Variable = "Global"
& {Set-Variable -Name Variable -Value "ScriptBlock" -Scope 1}
$Variable

ScriptBlock
Листинг 10. Использование номеров областей
$Variable = "Global"
& {& {& {Set-Variable -Name Variable -Value "ScriptBlock" -Scope 3}}}
$Variable

ScriptBlock
Листинг 11. Использование опции Private
Set-Variable -Name Variable -Value "Global Private" -Option Private
& {$Variable
$Global:Variable}
Write-Output "Global scope: $Variable"

Global scope: Global Private
Листинг 12. Использование опции AllScope
Set-Variable -Name Variable -Value ‘’ -Option None
& {New-Variable -Name Variable -Value ‘AllScopeVariable’ -Option AllScope
    Write-Output "ScriptBlock 1 Scope: $Variable"
    & {Write-Output "ScriptBlock 2 Scope: $Variable"}}
Write-Output "Global Scope: $Variable"

ScriptBlock 1 Scope: AllScopeVariable
ScriptBlock 2 Scope: AllScopeVariable
Global Scope:
Листинг 13. Изменение значения переменной, определенной с AllScope
Set-Variable -Name Variable -Value "AllScopeVariable" -Option AllScope
& {& { $Variable = "New Value"
Write-Output "ScriptBlock 2 Scope: $Variable"}
Write-Output "ScriptBlock 1 Scope: $Variable"}
Write-Output "Global Scope: $Variable"

ScriptBlock 2 Scope: New Value
ScriptBlock 1 Scope: New Value
Global Scope: New Value
Листинг 14. Выполнение файла сценария в текущей области
. C:\Script.ps1

$PSNames

powershell
pwsh

Get-Command Get-PowerShellProcess

Command  Type  Name  Version  Source
----------- ---- ------- ------
Function Get-PowerShellProcess

Get-Alias pspwsh

Command  Type  Name  Version  Source
----------- ---- ------- ------
Alias  pspwsh -> Get-PowerShellProcess
Листинг 15. Использование параметра -ArgumentList
$PowerShell = ‘powershell’
$ScriptBlock = {Param($ProcessName) Get-Process -Name $ProcessName}
Invoke-Command -ComputerName computer_name -ScriptBlock $ScriptBlock -ArgumentList $PowerShell
Листинг 16. Использование модификатора Using
$PowerShell = ‘powershell’
Invoke-Command -ComputerName computer_name -ScriptBlock {Get-Process -Name $Using:PowerShell}
Листинг 17. Назначить значение локальной переменной в удаленной команде не получится 
Invoke-Command -ComputerName computer_name -ScriptBlock {$Using:Process = Get-Process -Name $Using:PowerShell}

The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property.
Листинг 18. Модификатор Using не работает c функциями
function GetPSProcess
{
    Get-Process -Name 'powershell','pwsh' -ErrorAction SilentlyContinue
}
Invoke-Command -ComputerName computer_name -ScriptBlock {Using:GetPSProcess}

The term 'Using:GetPSProcess' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Листинг 19. Определение локальной функции в удаленной сессии
$FunctionGetPSProcess = @"
function GetPSProcess
{
    Get-Process -Name 'powershell','pwsh' -ErrorAction SilentlyContinue
}
"@

$Session = New-PSSession -ComputerName computer_name
Invoke-Command -Session $Session -ScriptBlock {Invoke-Expression $Using:FunctionGetPSProcess}
Invoke-Command -Session $Session -ScriptBlock {GetPSProcess}
Листинг 20. Области в конструкции switch
$PowerShell = $Pwsh = $Processes = 0
 
switch (Get-Process)
{
    {$_.Name -eq ‘powershell’; $Processes++}
    {
        $PowerShell++
    }
 
    {$_.Name -eq ‘pwsh’}
    {
        $Pwsh++
    }
}
 
Write-Output "PowerShell: $PowerShell`nPwsh: $Pwsh`nProcesses: $Processes"

PowerShell: 3
Pwsh: 1
Processes: 0
Листинг 21. Использование модификатора Global
$PowerShell = $Pwsh = $Processes = 0
switch (Get-Process)
{
    {$_.Name -eq ‘powershell’; $Global:Processes++}
    {
        $PowerShell++
    }
 
    {$_.Name -eq ‘pwsh’}
    {
        $Pwsh++
    }
}
 
Write-Output "PowerShell: $PowerShell`nPwsh: $Pwsh`nProcesses: $Processes"

PowerShell: 3
Pwsh: 1
Processes: 198
Листинг 22. Использование блоков сценариев в качестве значений параметров в конвейере
$Services = @{Name = ‘BITS’},@{Name = ‘Spooler’}
$Services | Get-CimInstance -ClassName Win32_Service -Filter {"Name='$($_.Name)'"}
Листинг 23. Блок сценария как значение переменной $ScriptBlock
$ScriptBlock = {"Name='$($_.Name)'"}
$Services | Get-CimInstance -ClassName Win32_Service -Filter $ScriptBlock
Листинг 24. Переменная $Status создается в области блока сценария
$Status = ""

$ScriptBlock = {"Name='$($_.Name)'`n";
    $Status += "$(Get-Date -Format H:mm:ss.fff): Processing $($_.Name)`n"}

$Services | Get-CimInstance -ClassName Win32_Service -Filter $ScriptBlock
$Status
Листинг 25. Переменная $Status создается в глобальной области
$Status = ""
$ScriptBlock = {"Name='$($_.Name)'`n";
    $Global:Status += "$(Get-Date -Format H:mm:ss.fff): Processing $($_.Name)`n"}

$Services | Get-CimInstance -ClassName Win32_Service -Filter $ScriptBlock
$Status

11:50:12.275: Processing BITS
11:50:12.310: Processing Spooler
Листинг 26. Создание блока сценария с использованием переменной $PSNames
$PSNames = 'powershell'
$ScriptBlock = {Get-Process -Name $PSNames}
& $ScriptBlock

NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
35 91.89 5.10 4.81 2084 1 powershell
33 62.94 3.90 3.67 3140 1 powershell
Листинг 27. Изменение значения переменной $PSNames
$PSNames = ‘Windows PowerShell’
& $ScriptBlock

Get-Process : Cannot find a process with the name "Windows PowerShell". Verify the process name and call the cmdlet again.
Листинг 28. Использование метода GetNewClosure
$PSNames = 'powershell'
$ScriptBlock = {Get-Process -Name $PSNames}.GetNewClosure()
& $ScriptBlock

NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
35 91.89 5.10 4.81 2084 1 powershell
33 62.94 3.90 3.67 3140 1 powershell

$PSNames = ‘Windows PowerShell’
& $ScriptBlock

NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
35 91.89 5.10 4.81 2084 1 powershell
33 62.94 3.90 3.67 3140 1 powershell
Листинг 29. Обращение к переменной в локальной области 
$Variable = @{ProcessName = ‘’}
& {$Variable = ‘Some Value’
$Variable.ProcessName = 'powershell'}

The property 'ProcessName' cannot be found on this object. Verify that the property exists and can be set.
Листинг 30. Объекты PSReference
$PowerShell = ‘PowerShell’
$PowerShellReference = [ref]$PowerShell
UsingReferences -String $PowerShellReference
$PowerShell

POWERSHELL
Листинг 31. Объекты PSReference в блоках сценариев
$ScriptBlock = {Param([ref]$String)
    $String.Value = $String.Value.Substring(0,$String.Value.LastIndexOf(‘.’))}

$Process = ‘powershell.exe’
$ProcessReference = [ref]$Process
& $ScriptBlock $ProcessReference
$Process

powershell
Листинг 32. Добавление в модуль функции Get-ModuleVariable
$Public = 'Public Value'
$Internal = 'Internal Value'

function Get-ModuleVariable
{
    "Public is: $Public"
    "Internal is: $Internal"
}

Export-ModuleMember -Variable Public -Function Get-ModuleVariable
Листинг 33. Изменение функции Get-ModuleVariable
$Public = ‘Public Value’
$Internal = 'Internal Value'

function Get-ModuleVariable
{
    "Public Scope 0: $(Get-Variable Public -Scope 0 -ErrorAction SilentlyContinue -ValueOnly)"
    "Public Scope 1: $(Get-Variable Public -Scope 1 -ErrorAction SilentlyContinue -ValueOnly)"
    "Public Scope 2: $(Get-Variable Public -Scope 2 -ErrorAction SilentlyContinue -ValueOnly)"

    "Internal Scope 0: $(Get-Variable Internal -Scope 0 -ErrorAction SilentlyContinue -ValueOnly)"
    "Internal Scope 1: $(Get-Variable Internal -Scope 1 -ErrorAction SilentlyContinue -ValueOnly)"
    "Internal Scope 2: $(Get-Variable Internal -Scope 2 -ErrorAction SilentlyContinue -ValueOnly)"

    Get-Variable -Scope 3
}

Export-ModuleMember -Variable Public -Function Get-ModuleVariable
Листинг 34. Результат выполнения функции Get-ModuleVariable
Import-Module .\TheModule.psm1 -Force
Get-ModuleVariable

Public Scope 0:
Public Scope 1: Public Value
Public Scope 2: Public Value
Internal Scope 0:
Internal Scope 1: Internal Value
Internal Scope 2:

Get-Variable : The scope number ‘3’ exceeds the number of active scopes.
Листинг 35. Использование имен областей в функции Get-ModuleVariable
$Public = 'Public Value'
$Internal = 'Internal Value'

function Get-ModuleVariable
{
    "Public Local: $Local:Public"
    "Public Script: $Script:Public"
    "Public Global: $Global:Public"

    "Internal Local: $Local:Internal"
    "Internal Script: $Script:Internal"
    "Internal Global: $Global:Internal"
}
Export-ModuleMember -Variable Public -Function Get-ModuleVariable
Листинг 36. Модификатор Script ссылается на область модуля
Import-Module .\TheModule.psm1 -Force
Get-ModuleVariable

Public Local:
Public Script: Public Value
Public Global: Public Value
Internal Local:
Internal Script: Internal Value
Internal Global:
Листинг 37. Измененный TheModule.psm1
$Public = 'Public Value'
$Internal = 'Internal Value'

function Get-ModuleVariable
{
    Param($Name)

    "$Name Local: $(Invoke-Expression "`$Local:$Name")"
    "$Name Script: $(Invoke-Expression "`$Script:$Name")"
    "$Name Global: $(Invoke-Expression "`$Global:$Name")"
}

function Set-ModuleVariable
{
    Param($Name,$Value,$Scope)

    Set-Variable -Name $Name -Value $Value -Scope $Scope
}

Export-ModuleMember -Variable Public -Function Get-ModuleVariable, Set-ModuleVariable
Листинг 38. Изменение переменной Public из области модуля
Import-Module .\TheModule.psm1 -Force
Set-ModuleVariable -Name Public -Value 'New Value' -Scope Script
Get-ModuleVariable Public

Public Local:
Public Script: New Value
Public Global: New Value
Листинг 39. Изменение переменной Public из глобальной области
$Public = "Another New Value"
Get-ModuleVariable Public

Public Local:
Public Script: Another New Value
Public Global: Another New Value
Листинг 40. Импорт модуля CimCmdlets в область модуля OuterModule
Remove-Module CimCmdlets -Force -ErrorAction SilentlyContinue
Import-Module .\OuterModule.psm1
Test-ModuleImport

ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Binary 1.0.0.0 CimCmdlets {Get-CimAssociatedInstance...}
Листинг 41. Импорт модуля CimCmdlets в глобальную область
Import-Module .\OuterModule.psm1 -Force
Test-ModuleImport
Get-Module CimCmdlets

ModuleType Version  Name ExportedCommands
---------- ------- ----  ----------------
Binary 1.0.0.0 CimCmdlets {Get-CimAssociatedInstance...}
Листинг 42. Автоматический импорт модуля CimCmdlets
Remove-Module CimCmdlets -Force -ErrorAction SilentlyContinue
Import-Module .\OuterModule.psm1 -Force
Test-ModuleImport
Get-Module CimCmdlets

ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Binary 1.0.0.0 CimCmdlets {Get-CimAssociatedInstance...}
Листинг 43. Обращение к переменным из области модуля
Import-Module .\TheModule.psm1 -Force
$TheModule = Get-Module TheModule
& $TheModule {Get-Variable -Name Public,Internal}
Name Value
---- -----
Public Public Value
Internal Internal Value
Листинг 44. Ошибка при обращении к модулю
$TheModule = Get-Module .\TheModule.psm1 -ListAvailable
Import-Module $TheModule -Force
& $TheModule {Get-Variable -Name Public,Internal}

Cannot use ‘&’ to invoke in the context of module ‘TheModule’ because it is not imported. Import the module ‘TheModule’ and try the operation again.
Листинг 45. Основная и вспомогательная функции
New-Variable -Name PSNames -Value ‘powershell’, ‘pwsh’
 
function Get-PowerShellProcess
{
    InternalHelperFunction
}
 
function InternalHelperFunction
{
    Get-Process -Name $PSNames -ErrorAction SilentlyContinue
}

Export-ModuleMember -Function Get-PowerShellProcess
Листинг 46. Вызов функций модуля
Import-Module .\TheModule.psm1 -Force
Get-PowerShellProcess

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
432 29 71912 3404 0.97 1956 1 powershell

InternalHelperFunction

InternalHelperFunction : The term 'InternalHelperFunction' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Листинг 47. Вызов функции из области модуля
$TheModule = Get-Module TheModule
& $TheModule {InternalHelperFunction}

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
432 29 71912 3404 0.97 1956 1 powershell
Листинг 48. Измененный TheModule.psm1
$Public = ‘Public Value’
$Internal = ‘Internal Value’
$Private:Private = ‘Private Value’

function PublicFunction
{
    Write-Output "I am a PublicFunction"
}
function InternalFunction
{
    Write-Output "I am an InternalFunction"
}
function Private:PrivateFunction
{
    Write-Output "I am a PrivateFunction"
}
Export-ModuleMember -Variable Public -Function PublicFunction
Листинг 49. Обращение к частной переменной модуля
& $TheModule {Get-Variable -Name Private -Scope Script}

Name Value
---- -----
Private Private Value
& $TheModule {$Script:Private}
Листинг 50. Обращение к элементам модуля при помощи оператора ‘.’
. $TheModule {$Public,$Internal,$Private}

Public Value
Internal Value
Private Value

. $TheModule {PublicFunction; InternalFunction; PrivateFunction}

I am a PublicFunction
I am an InternalFunction
I am a PrivateFunction