Який найкращий спосіб визначити місце поточного сценарію 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

1
Для чого Split-Pathтут використовується?
CMCDragonkai

7
Split-Pathвикористовується з -Parentпараметром для повернення поточного каталогу без поточного імені сценарію.
hjoelr

5
Примітка. У PowerShell для Linux / macOS ваш сценарій повинен мати розширення .ps1 для заповнення PSScriptRoot / MyInvocation тощо. Дивіться звіт про помилки тут: github.com/PowerShell/PowerShell/isissue/4217
Дейв Вуд

3
Суперечка з потенційно цікавою стороною: Чим ближче v2-апроксимація $PSScriptRoot( Split-Path -Parentзастосовується) $MyInvocation.MyCommand.Path, ні $MyInvocation.MyCommand.Definition, хоча в області сценарію верхнього рівня вони поводяться однаково (що є єдиним розумним місцем для виклику з цією метою). Коли викликається всередині функції або блоку сценарію , перший повертає порожню рядок, тоді як другий повертає визначення блоку / сценарію блоку функції як рядок (фрагмент вихідного коду PowerShell).
mklement0

62

Якщо ви створюєте модуль V2, ви можете використовувати автоматичну змінну, що називається $PSScriptRoot.

З PS> Довідка автоматичного_змінного

$ 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 і працює як слід. Однак він дасть вам порожню рядок, якщо ви запустите її в командному рядку. Це дасть результат лише в тому випадку, якщо використовується сценарій та виконується сценарій. Це те, що воно призначене для .....
Farrukh Waheed

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
}

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/ Запуск вибору.


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", щоб визначити, наскільки глибоко в стеку викликів ви знаходитесь.
kenny

11

Дуже схожий на вже опубліковані відповіді, але трубопровід здається більш схожим на PowerShell:

$PSCommandPath | Split-Path -Parent

11

Я використовую автоматичну змінну $ExecutionContext . Він працюватиме від PowerShell 2 та пізніших версій.

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

$ ExecutionContext Містить об'єкт EngineIntrinsics, який представляє контекст виконання хоста Windows PowerShell. Ви можете використовувати цю змінну для пошуку об’єктів виконання, доступних командлетам.


1
Це єдиний, який він працював на мене, намагаючись подати PowerShell від STDIN.
Себастьян

Це рішення також працює належним чином, коли ви знаходитесь в контексті шляху UNC.
користувач2030503

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
}

Це також означає, що функція стосується сфери Скрипту, а не сфери батьків, як це окреслював Майкл Соренс в одному зі своїх публікацій в блозі .


Дякую! Мені потрібно було "$ script": щоб це працювало в Windows PowerShell ISE.
Кірк Лімохн

Дякую за це Це єдине, що працює для мене. Зазвичай мені доводиться CD до каталогу, в якому знаходиться сценарій, перш ніж такі речі, як Get-location, працюють для мене. Мені буде цікаво дізнатись, чому PowerShell не оновлює автоматично каталог.
Заїн

7

Мені потрібно було знати ім'я сценарію і звідки воно виконується.

Префіксація "$ global:" до структури MyInvocation повертає повний шлях та ім'я скрипта, коли викликається як з основного сценарію, так і з головного рядка імпортованого файлу бібліотеки .PSM1. Він також працює в межах функції в імпортованій бібліотеці.

Після довгих роздумів я вирішив використовувати $ global: MyInvocation.InvocationName. Він надійно працює при запуску CMD, Run With Powershell та ISE. Як місцеві, так і UNC запуски повертають правильний шлях.


4
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) спрацював ідеально завдяки. Інші рішення повернули шлях виклику програми.
динамічнонк

1
Тривіальна примітка: в ISE виклик цієї функції за допомогою F8 / Run Selection викликає ParameterArgumentValidationErrorNullNotAllowedвиняток.
weir

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"

2

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


3
$PSCommandPathбуде працювати в ISE до тих пір, поки ви збережете сценарій першим і виконаєте весь файл. В іншому випадку ви насправді не виконуєте сценарій; ви просто "вставляєте" команди в оболонку.
Zenexer

@Zenexer Я думаю, що це було моєю метою в той час. Хоча якщо моя мета не збігається з початковою, це може бути не надто корисним, крім випадкових випадків Google ...

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.