В PowerShell как определить функцию в файле и вызвать ее из командной строки PowerShell?

243

У меня есть файл .ps1, в котором я хочу определить пользовательские функции.

Представьте, что файл называется MyFunctions.ps1, а его содержимое выглядит следующим образом:

Write-Host "Installing functions"
function A1
{
    Write-Host "A1 is running!"
}
Write-Host "Done"

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

.\MyFunctions.ps1

Это выводит:

Installing functions
Done

Тем не менее, когда я пытаюсь вызвать A1, я просто получаю сообщение об ошибке, в котором говорится, что нет команды / функции с таким именем:

The term 'A1' 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.
At line:1 char:3
+ A1 <<<<
    + CategoryInfo          : ObjectNotFound: (A1:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Я должен неправильно понять некоторые концепции PowerShell. Могу ли я не определять функции в файлах скриптов?

Обратите внимание, что я уже установил мою политику выполнения на «RemoteSigned». И я знаю, что запускать файлы .ps1, используя точку перед именем файла:. \ MyFile.ps1

Виллем
источник
Хорошая ссылка на загрузку функций при запуске PS: sandfeld.net/powershell-load-your-functions-at-startup
Эндрю

Ответы:

262

Попробуйте это в командной строке PowerShell:

. .\MyFunctions.ps1
A1

Оператор точки используется для включения скрипта.

RSC
источник
11
Ну, это означает «запустить это в текущем контексте вместо дочернего контекста».
JasonMArcher
15
Это означает источник содержимого этого файла. То же, что в bash . ss64.com/bash/period.html
inquam
2
Это, кажется, не работает очень хорошо хотя (по крайней мере от ISE), если вы сначала не запускаете. \ MyFunctions.ps1, чтобы сделать его доступным. Я не уверен, что должен работать исключительно с powershell.exe.
Майк Чил
1
Я подумал, что было нелогично, что точечный источник использовал путь относительно pwd, а не сценария, поэтому я бы посоветовал людям взглянуть на ответ JoeG и использовать модули.
Spork
5
@Spork . "$PSScriptRoot\MyFunctions.ps1". Доступно начиная с версии 3, до этого смотрите stackoverflow.com/questions/3667238/… . Это ОЧЕНЬ распространено.
yzorg
233

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

Итак, сначала переименуйте файл .ps1, в котором есть все ваши функции, в MyFunctions.psm1 (вы только что создали модуль!). Теперь для правильной загрузки модуля вы должны сделать с файлом определенные вещи. Сначала, чтобы Import-Module увидел модуль (вы используете этот командлет для загрузки модуля в оболочку), он должен находиться в определенном месте. Путь к папке модулей по умолчанию: $ home \ Documents \ WindowsPowerShell \ Modules.

В этой папке создайте папку с именем MyFunctions и поместите в нее файл MyFunctions.psm1 (файл модуля должен находиться в папке с тем же именем, что и файл PSM1).

Как только это будет сделано, откройте PowerShell и выполните следующую команду:

Get-Module -listavailable

Если вы видите один с именем MyFunctions, вы сделали это правильно, и ваш модуль готов к загрузке (это просто для того, чтобы убедиться, что все настроено правильно, вам нужно сделать это только один раз).

Чтобы использовать модуль, введите в оболочке следующее (или поместите эту строку в свой профиль $, или поместите в качестве первой строки в скрипте):

Import-Module MyFunctions

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

Get-Command -module MyFunctions

Это довольно мило, и крошечное усилие, которое требуется для установки на лицевой стороне, ОЧЕНЬ стоит.

JoeG
источник
6
Как быть, если ваши функции относятся только к данному приложению PowerShell? Я имею в виду, что если вы устанавливаете пакет PS1 для какой-то работы, вам может не понадобиться каждая функция в вашем профиле, верно?
Ян Патрик Хьюз
3
В этом случае я бы создал модуль для этого конкретного приложения и либо загрузил бы его перед запуском сценариев (если он работает в интерактивном режиме), либо загрузил бы его внутри сценария. Но, вообще говоря, если у вас есть код, специфичный только для конкретной задачи, вы захотите эти функции в скрипте. Лично я пишу только те функции, которые в общем и целом делают одно. Если фрагмент кода является гиперспециализированным, на самом деле не имеет смысла заключать его в функцию или модуль (если нет нескольких скриптов, использующих один и тот же код, тогда это может иметь смысл).
JoeG
17
НЕ обязательно, чтобы файл модуля находился в папке с тем же именем, что и файл PSM1. Это может быть сделано как Import-Module .\buildsystem\PSUtils.psm1
Майкл Фрейдгейм
2
@MichaelFreidgeim, если это так же просто, как просто изменить .with Import-Moduleи переименовать расширение, и не требует, чтобы модули были помещены в определенную папку, то есть я могу иметь его в любом каталоге, который я хочу, так же как с точечным источником, есть ли причина даже делать точечный поиск по модулям, учитывая преимущества, которые приходят для определения объема? (если, конечно, вам не нужны эти "проблемы")
Абдул
2
@Abdul, точечный поиск проще, модули намного мощнее. См. Stackoverflow.com/questions/14882332/…
Майкл Фрейдгейм
17

. "$PSScriptRoot\MyFunctions.ps1" MyA1Func

Начиная с версии 3, можно ознакомиться с разделом Как узнать местоположение файловой системы сценария PowerShell? , Это ОЧЕНЬ распространено.

PS Я не подписываюсь под правилом «все является модулем». Мои сценарии используются другими разработчиками вне GIT, поэтому я не люблю помещать вещи в определенное место или изменять системные переменные среды до запуска моего сценария. Это всего лишь сценарий (или два, или три).

yzorg
источник
FWIW, вам не нужно делать ни одну из этих вещей, чтобы запустить скрипт в модуле.
Ник Кокс
@NickCox Я хотел бы увидеть некоторые примеры этого. У тебя есть? +10, если пример из проекта OSS. В частности, пример загрузки модуля PS по относительному пути (не PSModulePath или без настройки PSModulePath) и нетривиальный пример (т. Е. Когда модуль имеет преимущества по сравнению с обычной областью видимости скрипта).
Ёзорг
Я часто импортирую модуль FluentMigrator.PowerShell из относительного пути. Это позволяет нам проверить его в системе контроля версий и убедиться, что все используют одну и ту же версию. Это работает хорошо.
Ник Кокс
Я не уверен в относительных плюсах и минусах упаковки этого как модуля против сценария: возможно, это один, чтобы обсудить с автором? Я думаю, что Get-Command -Module FluentMigrator.PowerShellэто довольно приятно?
Ник Кокс
@NickCox Вы не указали полный путь к модулю в этой команде, что означает, что он не будет найден, если вы не скопируете модуль в глобальную папку модуля или не добавите свою папку GIT в глобальную переменную среды. Я думаю, что вы только что продемонстрировали мою точку зрения.
Ёзорг
7

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

Сначала вам нужно проверить, что функция загружена, запустив:

ls function:\ | where { $_.Name -eq "A1"  }

И убедитесь, что он появляется в списке (должен быть список из 1!), А затем дайте нам знать, что вы получите!

Jonny
источник
1
В PowerShell функция рассматривается как каталог, так что это то же самое, что сказать c: \ или d: \. В равной степени вы будете работать без обратной косой черты, поэтому функция ls: | где {$ _. Name -eq "A1"}
Джонни
4

Вы можете добавить функцию к:

c:\Users\David\Documents\WindowsPowerShell\profile.ps1

Функция будет доступна.

Дэвид Морроу
источник
3

Если в вашем файле есть только одна основная функция, которую вы хотите вызвать / открыть, тогда вы также можете просто запустить файл с:

Param($Param1)

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

.\MyFunctions.ps1 -Param1 'value1'

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

Бергмайстер
источник
Я должен также отметить, что я обнаружил сегодня (после того, как мой коллега сказал мне), что PowerShell автоматически добавляет [CmdletBinding()]атрибут и обновляет его бесплатно до расширенной функции. :-)
бергмейстер
1

Предполагая, что у вас есть файл модуля с именем Dummy-Name.psm1, который имеет метод с именем Function-Dumb ()

Import-Module "Dummy-Name.psm1";
Get-Command -Module "Function-Dumb";
#
#
Function-Dumb;
Bytekoder
источник