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

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

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

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

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

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


person Aaron Jensen    schedule 28.03.2011    source источник
comment
возможный дубликат Как я могу получить расположение в файловой системе сценария PowerShell?   -  person JohnC    schedule 14.04.2014
comment
Ваше решение $ PSScriptRoot ЯВЛЯЕТСЯ правильным, не нужно читать дальше.   -  person Timo    schedule 19.11.2020


Ответы (14)


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
person JaredPar    schedule 28.03.2011
comment
Для чего здесь Split-Path? - person CMCDragonkai; 09.12.2016
comment
Split-Path используется с параметром -Parent для возврата текущего каталога без имени исполняемого в данный момент скрипта. - person hjoelr; 09.12.2016
comment
Примечание: с PowerShell в Linux / macOS ваш скрипт должен иметь расширение .ps1 для заполнения PSScriptRoot / MyInvocation и т. Д. См. Отчет об ошибке здесь: github.com/PowerShell/PowerShell/issues/4217 - person Dave Wood; 11.03.2018
comment
Придирка с потенциально интересным отступлением: более близкое v2- приближение к $PSScriptRoot (Split-Path -Parent применяется к) $MyInvocation.MyCommand.Path, а не $MyInvocation.MyCommand.Definition, хотя в области верхнего уровня сценария они ведут себя одинаково (это единственное разумное место для вызова с этой целью). При вызове внутри функции или блока скрипта первый возвращает пустую строку, тогда как второй возвращает определение тела функции / блока скрипта как строка (фрагмент исходного кода PowerShell). - person mklement0; 04.09.2019
comment
В документации Split-Path указано, что параметр Parent является параметром разделения по умолчанию. и IME -Parent действительно не нужен. - person Tim Sparkles; 23.03.2021

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

Из PS> Справка automatic_variable

$PSScriptRoot
       Contains the directory from which the script module is being executed.
       This variable allows scripts to use the module path to access other
       resources.
person Andy Schneider    schedule 29.03.2011
comment
Это то, что вам нужно в 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. - person CodeMonkeyKing; 14.03.2013
comment
Просто протестировал $ PSScriptRoot и работает должным образом. Однако, если вы запустите ее из командной строки, она выдаст пустую строку. Это даст вам результат только в том случае, если он используется в сценарии, и сценарий выполняется. Это то, для чего он предназначен ... - person Farrukh Waheed; 17.09.2013
comment
Я запутался. В этом ответе говорится об использовании PSScriptRoot для V2. В другом ответе говорится, что PSScriptRoot предназначен для V3 + и использовать что-то другое для v2. - person ; 09.05.2014
comment
@user $ PSScriptRoot в v2 предназначен только для модулей, если вы пишете «обычные» скрипты не в модуле, вам нужен $ MyInvocation.MyCommand.Definition, см. верхний ответ. - person yzorg; 16.01.2015
comment
Кроме того, $ MyInvocation.InvocationName работает для скриптов, чтобы вернуть их местоположение. - person Daz C; 15.02.2016
comment
@yzorg Это неправда. Он работает с любым типом сценария PowerShell, если интерпретатор соответствует версии PowerShell 3.0. Он работает со всеми моими файлами .ps1. Не обязательно определять как модуль. - person ZaxLofful; 28.07.2016
comment
@Lofful Я сказал, что в версии 2 он был определен только для модулей. Вы говорите, что он определен внешними модулями в v3. Я думаю, мы говорим то же самое. :) - person yzorg; 01.08.2016
comment
@yzorg: Понятия не имею, кто пользуется v2. Я пропустил эту часть, когда читал ее. :) - person ZaxLofful; 01.08.2016

Для 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
}
person CodeMonkeyKing    schedule 14.03.2013
comment
Еще лучше использовать $ PSScriptRoot. Это каталог текущего файла / модуля. - person Aaron Jensen; 29.04.2014
comment
Эта команда включает имя файла сценария, что меня сбивало с толку, пока я не понял этого. Когда вам нужен путь, вы, вероятно, не хотите, чтобы там было имя сценария. По крайней мере, я не могу придумать причину, по которой вы этого захотите. $ PSScriptRoot не включает имя файла (позаимствовано из других ответов). - person YetAnotherRandomUser; 21.06.2017
comment
$ PSScriptRoot пуст из обычного сценария PS1. Однако $ PSCommandPath работает. Ожидается поведение обоих в соответствии с описаниями, приведенными в других сообщениях. Также можно просто использовать [IO.Path] :: GetDirectoryName ($ PSCommandPath), чтобы получить каталог сценария без имени файла. - person fizzled; 15.04.2020

Для PowerShell 3+

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

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

person nickkzl    schedule 20.01.2017

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

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

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}
person Sean C.    schedule 28.03.2011
comment
Это получает текущее местоположение , где пользователь запускает скрипт. Не расположение самого файла сценария. - person Aaron Jensen; 29.03.2011
comment
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 Сохраните это и запустите из другого каталога. Вы укажете путь к сценарию. - person Sean C.; 29.03.2011
comment
Это хорошая функция, и она делает то, что мне нужно, но как мне поделиться ею и использовать во всех моих скриптах? Это проблема с курицей и яйцом: я хотел бы использовать функцию, чтобы узнать мое текущее местоположение, но мне нужно мое местоположение для загрузки функции. - person Aaron Jensen; 29.03.2011
comment
ПРИМЕЧАНИЕ. Вызов этой функции должен происходить на верхнем уровне вашего скрипта. Если она вложена в другую функцию, вам необходимо изменить параметр -Scope, чтобы указать, насколько глубоко вы находитесь в стеке вызовов. - person kenny; 02.06.2015

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

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

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

person Viggos    schedule 25.02.2016
comment
Это единственное, что у меня сработало, когда я пытался кормить PowerShell из STDIN. - person Sebastian; 15.04.2017
comment
Это решение также работает правильно, когда вы находитесь в контексте пути UNC. - person user2030503; 12.02.2018
comment
Это, по-видимому, подбирает рабочий каталог, а не то, где находится скрипт? - person monojohnny; 09.11.2018
comment
@monojohnny Да, это в основном текущий рабочий каталог, и он не будет работать при вызове скрипта из другого места. - person marsze; 24.01.2019

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

$PSCommandPath | Split-Path -Parent
person CPAR    schedule 13.10.2016

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

Я не уверен насчет других, но я работаю в среде с машинами как на 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
}

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

person Bruno    schedule 09.04.2016
comment
Спасибо! $ Script: это то, что мне нужно, чтобы заставить это работать в Windows PowerShell ISE. - person Kirk Liemohn; 11.01.2017
comment
Спасибо за это. Это единственное, что мне подходит. Обычно мне нужно записать компакт-диск в каталог, в котором находится сценарий, прежде чем у меня сработают такие вещи, как Get-location. Мне было бы интересно узнать, почему PowerShell не обновляет каталог автоматически. - person Zain; 25.02.2020

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

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

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

person Bruce Gavin    schedule 15.04.2014
comment
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) работал отлично, спасибо. Остальные решения вернули путь к вызывающему приложению. - person dynamiclynk; 07.05.2014
comment
Тривиальное примечание: в ISE вызов этой функции с использованием F8 / Run Selection вызовет исключение ParameterArgumentValidationErrorNullNotAllowed. - person weir; 19.10.2015

Я всегда использую этот небольшой фрагмент, который работает для 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
person Carsten    schedule 25.04.2019
comment
Часть Split-Path выглядит хорошо, но довольно медленно. Если вам это нужно быстро, используйте RegEx следующим образом: $ path = [regex] :: Match ($ path, '. + (? = \)'). Value - person Carsten; 06.07.2021

Я обнаружил, что старые решения, размещенные здесь, не работают для меня в 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"
person Quantium    schedule 03.04.2019

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

    # 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
    }

person Randy    schedule 08.11.2019

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

person fastboxster    schedule 29.01.2013
comment
$PSCommandPath будет работать в ISE до тех пор, пока вы сначала сохраните сценарий и выполните весь файл. В противном случае вы фактически не выполняете сценарий; вы просто вставляете команды в оболочку. - person Zenexer; 25.07.2013
comment
@Zenexer Я думаю, это было моей целью в то время. Хотя, если бы моя цель не совпадала с исходной, это могло бы быть не слишком полезно, за исключением случайных гуглеров ... - person ; 16.01.2015

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

$PSScriptRoot, который работает при вызове в качестве сценария, например, с помощью команды PowerShell $psISE.CurrentFile.FullPath, который работает, когда вы работаете внутри ISE.

Но если вы ни того, ни другого и просто набираете текст в оболочке PowerShell, вы можете использовать:

pwd.Path

Вы можете присвоить одно из трех значений переменной с именем $base в зависимости от среды, в которой вы работаете, например:

$base=$(if ($psISE) {Split-Path -Path $psISE.CurrentFile.FullPath} else {$(if ($global:PSScriptRoot.Length -gt 0) {$global:PSScriptRoot} else {$global:pwd.Path})})

Затем в своих сценариях вы можете использовать это так:

Import-Module $base\lib\someConstants.psm1
Import-Module $base\lib\myCoolPsModule1.psm1
#etc.
person zumalifeguard    schedule 09.07.2020