Jaki jest najlepszy sposób ustalenia lokalizacji bieżącego skryptu PowerShell?

530

Ilekroć muszę odwoływać się do wspólnego modułu lub skryptu, lubię używać ścieżek względem bieżącego pliku skryptu. W ten sposób mój skrypt może zawsze znajdować inne skrypty w bibliotece.

Jaki jest najlepszy, standardowy sposób określania katalogu bieżącego skryptu? Obecnie robię:

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

Wiem, że w modułach (.psm1) możesz użyć $PSScriptRoottej informacji, ale nie ustawia się tego w zwykłych skryptach (np. Plikach .ps1).

Jaki jest kanoniczny sposób uzyskania bieżącej lokalizacji pliku skryptu PowerShell?

Aaron Jensen
źródło

Odpowiedzi:

865

PowerShell 3+

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

PowerShell 2

Przed wersją PowerShell 3 nie było lepszego sposobu niż zapytanie o MyInvocation.MyCommand.Definitionwłaściwość o ogólne skrypty. Na początku każdego skryptu PowerShell miałem następujący wiersz:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
JaredPar
źródło
1
Do czego Split-Pathsłuży tutaj?
CMCDragonkai
7
Split-Pathjest używany z -Parentparametrem, aby zwrócić bieżący katalog bez nazwy aktualnie wykonywanego skryptu.
hjoelr,
5
Uwaga: w programie PowerShell w systemie Linux / macOS skrypt musi mieć rozszerzenie .ps1, aby PSScriptRoot / MyInvocation itp. Mógł zostać wypełniony. Zobacz raport o błędzie tutaj: github.com/PowerShell/PowerShell/issues/4217
Dave Wood
3
Sprzeczność z potencjalnie interesującym bokiem: bliższe przybliżenie v2 $PSScriptRootjest ( Split-Path -Parentstosowane) $MyInvocation.MyCommand.Path, ale nie $MyInvocation.MyCommand.Definition, chociaż w najwyższym zakresie skryptu zachowują się tak samo (jest to jedyne rozsądne miejsce, z którego można zadzwonić w tym celu). Po wywołaniu w bloku funkcji lub skryptu , ten pierwszy zwraca pusty ciąg znaków, podczas gdy drugi zwraca definicję treści funkcji / bloku skryptu jako ciąg (fragment kodu źródłowego PowerShell).
mklement0
62

Jeśli tworzysz moduł V2, możesz użyć automatycznej zmiennej o nazwie $PSScriptRoot.

Z PS> Pomoc zmienna_automatyczna

$ PSScriptRoot
       Zawiera katalog, z którego wykonywany jest moduł skryptu.
       Ta zmienna umożliwia skryptom korzystanie ze ścieżki modułu w celu uzyskania dostępu do innych
       zasoby.
Andy Schneider
źródło
16
Oto, czego potrzebujesz w 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
Właśnie przetestowałem $ PSScriptRoot i działa zgodnie z oczekiwaniami. Jednak dałoby ci pusty ciąg, jeśli uruchomisz go w wierszu poleceń. To da wynik tylko, jeśli zostanie użyty w skrypcie i skrypt zostanie wykonany. Właśnie po to jest przeznaczony .....
Farrukh Waheed,
4
Jestem zmieszany. Ta odpowiedź mówi o użyciu PSScriptRoot dla V2. Inna odpowiedź mówi, że PSScriptRoot jest przeznaczony do wersji V3 + i do używania czegoś innego w wersji v2.
6
@ użytkownik $ PSScriptRoot w v2 dotyczy tylko modułów , jeśli piszesz „normalne” skrypty nie w module, potrzebujesz $ MyInvocation.MyCommand.Definition, zobacz najwyższą odpowiedź.
yzorg
1
@Lofful powiedziałem w „v2”, że zostało zdefiniowane tylko dla modułów. Mówisz, że jest zdefiniowane poza modułami w v3. Myślę, że mówimy to samo. :)
yzorg
35

Dla 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.

Funkcja jest wtedy:

function Get-ScriptDirectory {
    Split-Path -Parent $PSCommandPath
}
CodeMonkeyKing
źródło
20
Jeszcze lepiej, użyj $ PSScriptRoot. Jest to katalog bieżącego pliku / modułu.
Aaron Jensen
2
To polecenie zawiera nazwę pliku skryptu, który mnie wyrzucił, dopóki się nie zorientowałem. Kiedy potrzebujesz ścieżki, prawdopodobnie nie chcesz też, aby zawierała nazwę skryptu. Przynajmniej nie mogę wymyślić powodu, dla którego byś tego chciał. $ PSScriptRoot nie zawiera nazwy pliku (zaczerpniętej z innych odpowiedzi).
YetAnotherRandomUser
$ PSScriptRoot jest pusty od zwykłego skryptu PS1. $ PSCommandPath działa jednak. Zachowanie obu jest oczekiwane według opisów podanych w innych postach. Można również użyć [IO.Path] :: GetDirectoryName ($ PSCommandPath), aby uzyskać katalog skryptu bez nazwy pliku.
zgasło
19

Dla PowerShell 3+

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

Umieściłem tę funkcję w moim profilu. Działa również w ISE przy użyciu F8/ Run Selection.

nickkzl
źródło
16

Może coś mi tu brakuje ... ale jeśli chcesz mieć obecny katalog roboczy, możesz po prostu użyć tego: (Get-Location).Pathdla łańcucha lub Get-Locationobiektu.

Chyba że masz na myśli coś takiego, co rozumiem po ponownym przeczytaniu pytania.

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}
Sean C.
źródło
16
Pobiera bieżącą lokalizację, w której użytkownik uruchamia skrypt . Nie lokalizacja samego pliku skryptu .
Aaron Jensen
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 Zapisz to i uruchom z innego katalogu. Pokażesz ścieżkę do skryptu.
Sean C.
To dobra funkcja i robi to, czego potrzebuję, ale jak mogę ją udostępnić i używać we wszystkich moich skryptach? Jest to problem z kurczakiem i jajkiem: chciałbym użyć funkcji, aby znaleźć moją aktualną lokalizację, ale potrzebuję mojej lokalizacji, aby załadować funkcję.
Aaron Jensen
2
UWAGA: Wywołanie tej funkcji musi być na najwyższym poziomie skryptu, jeśli jest zagnieżdżone w innej funkcji, musisz zmienić parametr „-Scope”, aby określić, jak głęboko jesteś w stosie wywołań.
kenny
11

Bardzo podobny do już opublikowanych odpowiedzi, ale potokowanie wydaje się bardziej przypominać PowerShell:

$PSCommandPath | Split-Path -Parent
CPAR
źródło
11

Używam zmiennej automatycznej $ExecutionContext . Będzie działać z PowerShell 2 i nowszych.

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

$ ExecutionContext Zawiera obiekt EngineIntrinsics, który reprezentuje kontekst wykonania hosta Windows PowerShell. Tej zmiennej można użyć do znalezienia obiektów wykonawczych dostępnych dla poleceń cmdlet.

Viggos
źródło
1
To był jedyny, który mi zadziałał, próbując nakarmić PowerShell od STDIN.
Sebastian,
To rozwiązanie działa również poprawnie, gdy znajdujesz się w kontekście ścieżki UNC.
user2030503
1
To najwyraźniej podnosi katalog roboczy - a nie gdzie znajduje się skrypt?
monojohnny
@monojohnny Tak. Jest to zasadniczo bieżący katalog roboczy i nie będzie działał podczas wywoływania skryptu z innej lokalizacji.
marsze
9

Zajęło mi trochę czasu opracowanie czegoś, co przyjęło zaakceptowaną odpowiedź i przekształciło ją w solidną funkcję.

Nie jestem pewien co do innych, ale pracuję w środowisku z maszynami zarówno w wersji PowerShell 2, jak i 3, więc musiałem obsłużyć oba. Następująca funkcja oferuje płynną rezerwę:

Function Get-PSScriptRoot
{
    $ScriptRoot = ""

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

    Write-Output $ScriptRoot
}

Oznacza to również, że funkcja odnosi się do zakresu skryptu, a nie zakresu rodzica, jak to nakreślił Michael Sorens w jednym ze swoich postów na blogu .

Bruno
źródło
Dzięki! „$ Script:” było tym, czego potrzebowałem, aby działało w Windows PowerShell ISE.
Kirk Liemohn
Dzięki za to. To był jedyny, który działa dla mnie. Zwykle muszę włożyć CD do katalogu, w którym znajduje się skrypt, zanim coś takiego jak Get-location będzie dla mnie działać. Chciałbym wiedzieć, dlaczego PowerShell nie aktualizuje automatycznie katalogu.
Zain
7

Musiałem znać nazwę skryptu i miejsce jego wykonywania.

Przedrostek „$ global:” w strukturze MyInvocation zwraca pełną ścieżkę i nazwę skryptu, gdy jest wywoływany zarówno ze skryptu głównego, jak i głównego wiersza importowanego pliku biblioteki .PSM1. Działa również z funkcji w importowanej bibliotece.

Po wielu zabawach postanowiłem użyć $ global: MyInvocation.InvocationName. Działa niezawodnie z uruchomieniem CMD, Run With Powershell i ISE. Zarówno uruchamianie lokalne, jak i UNC zwracają prawidłową ścieżkę.

Bruce Gavin
źródło
4
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) działało idealnie dzięki. Inne rozwiązania zwróciły ścieżkę aplikacji wywołującej.
dynamiclynk
1
Uwaga: w wywołaniu ISE ta funkcja za pomocą F8 / Run Selection spowoduje ParameterArgumentValidationErrorNullNotAllowedwyjątek.
jaz
5

Zawsze używam tego małego fragmentu, który działa w PowerShell i ISE w ten sam sposób:

# 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
Piotr
źródło
3

Przekonałem się, że opublikowane tutaj starsze rozwiązania nie działały dla mnie w PowerShell V5. Wymyśliłem to:

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
źródło
2

Możesz również rozważyć, split-path -parent $psISE.CurrentFile.Fullpathczy którakolwiek z pozostałych metod zawiedzie. W szczególności, jeśli uruchomisz plik, aby załadować wiązkę funkcji, a następnie uruchomisz te funkcje w powłoce ISE (lub jeśli wybierzesz opcję uruchomienia), wygląda na to, że Get-Script-Directorypowyższa funkcja nie działa.

fastboxster
źródło
3
$PSCommandPathbędzie działał w ISE, dopóki najpierw zapiszesz skrypt i wykonasz cały plik. W przeciwnym razie nie wykonasz skryptu; po prostu „wklejasz” polecenia do powłoki.
Zenexer
@Zenexer Myślę, że to był mój cel w tym czasie. Chociaż jeśli mój cel nie pasuje do pierwotnego, może to nie być zbyt pomocne, z wyjątkiem okazjonalnych
2

Wykorzystując fragmenty wszystkich tych odpowiedzi i komentarzy, zestawiłem to dla każdego, kto zobaczy to pytanie w przyszłości. Obejmuje wszystkie sytuacje wymienione w innych odpowiedziach

    # 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
    }
Niespokojny
źródło
-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
źródło