Каков наилучший способ определить местоположение текущего скрипта PowerShell?

530

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

Итак, что является лучшим, стандартным способом определения каталога текущего скрипта? В настоящее время я занимаюсь:

$MyDir = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition)

Я знаю, что в модулях (.psm1) вы можете использовать $PSScriptRootэту информацию, но она не устанавливается в обычных скриптах (то есть файлах .ps1).

Какой канонический способ узнать местонахождение текущего файла скрипта PowerShell?

Аарон Дженсен
источник

Ответы:

865

PowerShell 3+

# This is an automatic variable set to the current file's/module's directory
$PSScriptRoot

PowerShell 2

До PowerShell 3 не было лучшего способа, чем запросить MyInvocation.MyCommand.Definitionсвойство для общих сценариев. У меня была следующая строка вверху практически каждого скрипта PowerShell, который у меня был:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
JaredPar
источник
1
Что здесь Split-Pathиспользуется?
CMCDragonkai
7
Split-Pathиспользуется с -Parentпараметром для возврата текущего каталога без имени текущего исполняемого скрипта.
hjoelr
5
Примечание: в PowerShell для Linux / macOS ваш сценарий должен иметь расширение .ps1 для заполнения PSScriptRoot / MyInvocation и т. Д. Смотрите отчет об ошибках здесь: github.com/PowerShell/PowerShell/issues/4217
Дейв Вуд
3
Возьмем потенциально интересную сторону: более близкая v2-аппроксимация $PSScriptRoot( Split-Path -Parentприменяется к) $MyInvocation.MyCommand.Path, нет $MyInvocation.MyCommand.Definition, хотя в области видимости сценария они ведут себя одинаково (что является единственным разумным местом для вызова с этой целью). При вызове внутри блока функции или скрипта первый возвращает пустую строку, тогда как последний возвращает определение блока функции / скрипта в виде строки (фрагмент исходного кода PowerShell).
mklement0
62

Если вы создаете модуль V2, вы можете использовать автоматическую переменную под названием $PSScriptRoot.

Из PS> Помогите automatic_variable

$ PSScriptRoot
       Содержит каталог, из которого выполняется модуль скрипта.
       Эта переменная позволяет сценариям использовать путь к модулю для доступа к другим
       Ресурсы.
Энди Шнайдер
источник
16
Это то, что вам нужно в PS 3.0:$PSCommandPath Contains the full path and file name of the script that is being run. This variable is valid in all scripts.
CodeMonkeyKing
2
Только что протестировал $ PSScriptRoot и работает как положено. Тем не менее, это даст вам пустую строку, если вы запустите ее в командной строке. Он даст вам результат только в том случае, если он используется в сценарии, а сценарий выполняется. Вот для чего он предназначен .....
Фаррух Вахид
4
Я смущен. Этот ответ говорит использовать PSScriptRoot для V2. Другой ответ говорит, что PSScriptRoot для V3 +, и использовать что-то другое для v2.
6
@user $ PSScriptRoot в v2 предназначен только для модулей , если вы пишете «нормальные» сценарии не в модуле, вам нужно $ MyInvocation.MyCommand.Definition, см. верхний ответ.
yzorg
1
@Lofful Я сказал в "v2", это было определено только для модулей. Вы говорите, что он определен вне модулей в v3. Я думаю, что мы говорим то же самое. :)
yzorg
35

Для PowerShell 3.0

$PSCommandPath
    Contains the full path and file name of the script that is being run. 
    This variable is valid in all scripts.

Функция тогда:

function Get-ScriptDirectory {
    Split-Path -Parent $PSCommandPath
}
CodeMonkeyKing
источник
20
Еще лучше использовать $ PSScriptRoot. Это каталог текущего файла / модуля.
Аарон Дженсен
2
Эта команда включает имя файла сценария, который сбрасывал меня, пока я не понял это. Когда вы хотите путь, вы, вероятно, не хотите, чтобы имя сценария там тоже. По крайней мере, я не могу придумать причину, по которой ты этого хочешь. $ PSScriptRoot не включает имя файла (взято из других ответов).
Ещё один случайный пользователь
$ PSScriptRoot пуст от обычного сценария PS1. Хотя $ PSCommandPath работает. Поведение обоих ожидается в соответствии с описаниями, приведенными в других постах. Также можно просто использовать [IO.Path] :: GetDirectoryName ($ PSCommandPath), чтобы получить каталог скриптов без имени файла.
провалился
19

Для PowerShell 3+

function Get-ScriptDirectory {
    if ($psise) {
        Split-Path $psise.CurrentFile.FullPath
    }
    else {
        $global:PSScriptRoot
    }
}

Я поместил эту функцию в своем профиле. Это работает в ISE, используя F8/ Run Selection тоже.

nickkzl
источник
16

Может быть, я что-то здесь упускаю ... но если вам нужен настоящий рабочий каталог, вы можете просто использовать это: (Get-Location).Pathдля строки или Get-Locationдля объекта.

Если вы не имеете в виду нечто подобное, что я понимаю после прочтения вопроса снова.

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}
Шон С.
источник
16
Это получает текущее местоположение, где пользователь выполняет сценарий . Не расположение файла сценария сам .
Аарон Дженсен
2
function Get-Script-Directory { $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value return Split-Path $scriptInvocation.MyCommand.Path } $hello = "hello" Write-Host (Get-Script-Directory) Write-Host $hello Сохраните его и запустите из другого каталога. Вы покажете путь к сценарию.
Шон С.
Это хорошая функция, и она делает то, что мне нужно, но как мне поделиться ею и использовать ее во всех моих сценариях? Это проблема курицы и яйца: я хотел бы использовать функцию, чтобы узнать мое текущее местоположение, но мне нужно мое местоположение, чтобы загрузить функцию.
Аарон Дженсен
2
ПРИМЕЧАНИЕ. Вызов этой функции должен выполняться на верхнем уровне вашего скрипта. Если он вложен в другую функцию, вам нужно изменить параметр «-Scope», чтобы указать, насколько глубоко вы находитесь в стеке вызовов.
Кенни
11

Очень похоже на уже опубликованные ответы, но трубопровод выглядит более похожим на PowerShell:

$PSCommandPath | Split-Path -Parent
CPAR
источник
11

Я использую автоматическую переменную $ExecutionContext . Он будет работать с PowerShell 2 и более поздних версий.

 $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath('.\')

$ ExecutionContext Содержит объект EngineIntrinsics, который представляет контекст выполнения хоста Windows PowerShell. Вы можете использовать эту переменную для поиска объектов выполнения, доступных для командлетов.

Viggos
источник
1
Это единственное, что сработало для меня, пытаясь передать PowerShell из STDIN.
Себастьян
Это решение также работает правильно, когда вы находитесь в контексте пути UNC.
user2030503
1
Это, очевидно, поднимает рабочий каталог - а не где находится сценарий?
monojohnny
@monojohnny Да, это в основном текущий рабочий каталог, и он не будет работать при вызове скрипта из другого места.
марш
9

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

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

Function Get-PSScriptRoot
{
    $ScriptRoot = ""

    Try
    {
        $ScriptRoot = Get-Variable -Name PSScriptRoot -ValueOnly -ErrorAction Stop
    }
    Catch
    {
        $ScriptRoot = Split-Path $script:MyInvocation.MyCommand.Path
    }

    Write-Output $ScriptRoot
}

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

Bruno
источник
Спасибо! «$ Script:» - это то, что мне было нужно, чтобы заставить это работать в Windows PowerShell ISE.
Кирк Лимон
Спасибо за это. Это был единственный, который работает для меня. Мне обычно приходится записывать CD в каталог, в котором находится скрипт, прежде чем такие вещи, как Get-location, будут работать на меня. Мне было бы интересно узнать, почему PowerShell не обновляет каталог автоматически.
Zain
7

Мне нужно было знать имя скрипта и откуда он выполняется.

Префикс $ global: к структуре MyInvocation возвращает полный путь и имя сценария при вызове как из основного сценария, так и из основной строки импортированного файла библиотеки .PSM1. Он также работает из функции в импортированной библиотеке.

После долгих раздумий я остановился на использовании $ global: MyInvocation.InvocationName. Он надежно работает с запуском CMD, Run With Powershell и ISE. И локальные, и UNC-запуски возвращают правильный путь.

Брюс Гэвин
источник
4
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) работал отлично, спасибо. Другие решения возвращали путь к вызывающему приложению.
dynamiclynk
1
Тривиальное примечание: в ISE вызов этой функции с помощью F8 / Run Selection вызовет ParameterArgumentValidationErrorNullNotAllowedисключение.
Плотина
5

Я всегда использую этот небольшой фрагмент, который работает для PowerShell и ISE одинаково:

# Set active path to script-location:
$path = $MyInvocation.MyCommand.Path
if (!$path) {
    $path = $psISE.CurrentFile.Fullpath
}
if ($path) {
    $path = Split-Path $path -Parent
}
Set-Location $path
Питер
источник
3

Я обнаружил, что старые решения, опубликованные здесь, не работают для меня на PowerShell V5. Я придумал это:

try {
    $scriptPath = $PSScriptRoot
    if (!$scriptPath)
    {
        if ($psISE)
        {
            $scriptPath = Split-Path -Parent -Path $psISE.CurrentFile.FullPath
        }
        else {
            Write-Host -ForegroundColor Red "Cannot resolve script file's path"
            exit 1
        }
    }
}
catch {
    Write-Host -ForegroundColor Red "Caught Exception: $($Error[0].Exception.Message)"
    exit 2
}

Write-Host "Path: $scriptPath"
Quantium
источник
2

Вы также можете рассмотреть, split-path -parent $psISE.CurrentFile.Fullpathесли какой-либо из других методов потерпит неудачу. В частности, если вы запускаете файл для загрузки набора функций, а затем выполняете эти функции с помощью - в оболочке ISE (или если вы запускаете-selected), кажется, что Get-Script-Directoryфункция, описанная выше, не работает.

fastboxster
источник
3
$PSCommandPathбудет работать в ISE, пока вы сначала сохраните сценарий и выполните весь файл. В противном случае вы на самом деле не выполняете скрипт; вы просто «вставляете» команды в оболочку.
Zenexer
@ Zenexer Я думаю, что это была моя цель в то время. Хотя, если моя цель не совпадает с исходной, это может быть не слишком полезно, кроме случайных Googlers ...
2

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

    # If using ISE
    if ($psISE) {
        $ScriptPath = Split-Path -Parent $psISE.CurrentFile.FullPath
    # If Using PowerShell 3 or greater
    } elseif($PSVersionTable.PSVersion.Major -gt 3) {
        $ScriptPath = $PSScriptRoot
    # If using PowerShell 2 or lower
    } else {
        $ScriptPath = split-path -parent $MyInvocation.MyCommand.Path
    }
похотливый
источник
-4
function func1() 
{
   $inv = (Get-Variable MyInvocation -Scope 1).Value
   #$inv.MyCommand | Format-List *   
   $Path1 = Split-Path $inv.scriptname
   Write-Host $Path1
}

function Main()
{
    func1
}

Main
Ravi
источник