Язык 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-ModuleVariable вместо номеров областей мы задействуем их имена, что сделает наш код гораздо более аккуратным (листинг 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.
Set-Variable -Name PSNames -Value ‘powershell’, ‘pwsh’ function Get-PowerShellProcess { Get-Process -Name $PSNames -ErrorAction SilentlyContinue } Set-Alias -Name pspwsh -Value Get-PowerShellProcess
function RedefineVariable { $Variable = 'Function Scope' } $Variable = 'Global Scope' RedefineVariable $Variable Global Scope
$Variable = "Global" & {& {& {$Variable = 'Local'; $Variable}}} Local $Variable = "Global" & {$Variable = 'Intermediate'; & {& {$Variable}}} Intermediate
$Variable = "Global" & {$Variable = "ScriptBlock" Write-Output "ScriptBlock variable: $Variable"} Write-Output "Global variable: $Variable" ScriptBlock variable: ScriptBlock Global variable: Global
$Variable = "Global" & {$Local:Variable = "ScriptBlock Local" $Global:Variable = "ScriptBlock Global" $Variable} $Variable ScriptBlock Local ScriptBlock Global
$Variable = 'Global' & { $Variable = 'ScriptBlock' $Script:Variable } Global
& {function Global:TheFunctionInTheGlobalScope { "I'm still here." } } TheFunctionInTheGlobalScope I'm still here.
& {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
$Variable = "Global" & {Set-Variable -Name Variable -Value "ScriptBlock" -Scope 1} $Variable ScriptBlock
$Variable = "Global" & {& {& {Set-Variable -Name Variable -Value "ScriptBlock" -Scope 3}}} $Variable ScriptBlock
Set-Variable -Name Variable -Value "Global Private" -Option Private & {$Variable $Global:Variable} Write-Output "Global scope: $Variable" Global scope: Global Private
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:
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
. 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
$PowerShell = ‘powershell’ $ScriptBlock = {Param($ProcessName) Get-Process -Name $ProcessName} Invoke-Command -ComputerName computer_name -ScriptBlock $ScriptBlock -ArgumentList $PowerShell
$PowerShell = ‘powershell’ Invoke-Command -ComputerName computer_name -ScriptBlock {Get-Process -Name $Using:PowerShell}
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.
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.
$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}
$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
$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
$Services = @{Name = ‘BITS’},@{Name = ‘Spooler’} $Services | Get-CimInstance -ClassName Win32_Service -Filter {"Name='$($_.Name)'"}
$ScriptBlock = {"Name='$($_.Name)'"} $Services | Get-CimInstance -ClassName Win32_Service -Filter $ScriptBlock
$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
$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
$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
$PSNames = ‘Windows PowerShell’ & $ScriptBlock Get-Process : Cannot find a process with the name "Windows PowerShell". Verify the process name and call the cmdlet again.
$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
$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.
$PowerShell = ‘PowerShell’ $PowerShellReference = [ref]$PowerShell UsingReferences -String $PowerShellReference $PowerShell POWERSHELL
$ScriptBlock = {Param([ref]$String) $String.Value = $String.Value.Substring(0,$String.Value.LastIndexOf(‘.’))} $Process = ‘powershell.exe’ $ProcessReference = [ref]$Process & $ScriptBlock $ProcessReference $Process powershell
$Public = 'Public Value' $Internal = 'Internal Value' function Get-ModuleVariable { "Public is: $Public" "Internal is: $Internal" } Export-ModuleMember -Variable Public -Function 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
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.
$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
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:
$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
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
$Public = "Another New Value" Get-ModuleVariable Public Public Local: Public Script: Another New Value Public Global: Another New Value
Remove-Module CimCmdlets -Force -ErrorAction SilentlyContinue Import-Module .\OuterModule.psm1 Test-ModuleImport ModuleType Version Name ExportedCommands ---------- ------- ---- ---------------- Binary 1.0.0.0 CimCmdlets {Get-CimAssociatedInstance...}
Import-Module .\OuterModule.psm1 -Force Test-ModuleImport Get-Module CimCmdlets ModuleType Version Name ExportedCommands ---------- ------- ---- ---------------- Binary 1.0.0.0 CimCmdlets {Get-CimAssociatedInstance...}
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...}
Import-Module .\TheModule.psm1 -Force $TheModule = Get-Module TheModule & $TheModule {Get-Variable -Name Public,Internal} Name Value ---- ----- Public Public Value Internal Internal Value
$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.
New-Variable -Name PSNames -Value ‘powershell’, ‘pwsh’ function Get-PowerShellProcess { InternalHelperFunction } function InternalHelperFunction { Get-Process -Name $PSNames -ErrorAction SilentlyContinue } Export-ModuleMember -Function Get-PowerShellProcess
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.
$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
$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
& $TheModule {Get-Variable -Name Private -Scope Script} Name Value ---- ----- Private Private Value & $TheModule {$Script:Private}
. $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