Wie kann der Speicherort des aktuellen PowerShell-Skripts am besten ermittelt werden?

530

Wann immer ich auf ein allgemeines Modul oder Skript verweisen muss, verwende ich gerne Pfade relativ zur aktuellen Skriptdatei. Auf diese Weise kann mein Skript immer andere Skripte in der Bibliothek finden.

Was ist die beste Standardmethode zum Bestimmen des Verzeichnisses des aktuellen Skripts? Derzeit mache ich:

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

Ich weiß, dass Sie in Modulen (.psm1) $PSScriptRootdiese Informationen abrufen können , aber das wird nicht in regulären Skripten (dh .ps1-Dateien) festgelegt.

Wie kann kanonisch der Speicherort der aktuellen PowerShell-Skriptdatei ermittelt werden?

Aaron Jensen
quelle

Antworten:

865

PowerShell 3+

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

PowerShell 2

Vor PowerShell 3 gab es keinen besseren Weg, als die MyInvocation.MyCommand.DefinitionEigenschaft nach allgemeinen Skripten abzufragen. Ich hatte die folgende Zeile oben in praktisch jedem PowerShell-Skript, das ich hatte:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
JaredPar
quelle
1
Wofür Split-Pathwird hier verwendet?
CMCDragonkai
7
Split-Pathwird mit dem -ParentParameter verwendet, um das aktuelle Verzeichnis ohne den Namen des aktuell ausgeführten Skripts zurückzugeben.
Hjoelr
5
Hinweis: Bei PowerShell unter Linux / macOS muss Ihr Skript die Erweiterung .ps1 haben, damit PSScriptRoot / MyInvocation usw. ausgefüllt werden kann. Siehe Fehlerbericht hier: github.com/PowerShell/PowerShell/issues/4217
Dave Wood
3
Streit mit einem potenziell interessanten Nebeneffekt: Die engere v2-Annäherung von $PSScriptRootist ( Split-Path -Parentangewendet auf) $MyInvocation.MyCommand.Path, nicht $MyInvocation.MyCommand.Definition, obwohl sie sich im obersten Bereich eines Skripts gleich verhalten (was der einzig sinnvolle Ort ist, von dem aus zu diesem Zweck aufgerufen werden kann). Beim Aufruf innerhalb einer Funktion oder eines Skriptblocks gibt der erstere die leere Zeichenfolge zurück, während der letztere die Definition des Funktionskörpers / Skriptblocks als Zeichenfolge (ein Teil des PowerShell-Quellcodes) zurückgibt .
mklement0
62

Wenn Sie ein V2-Modul erstellen, können Sie eine automatische Variable namens verwenden $PSScriptRoot.

Über PS> Hilfe auto_variable

$ PSScriptRoot
       Enthält das Verzeichnis, aus dem das Skriptmodul ausgeführt wird.
       Mit dieser Variablen können Skripte den Modulpfad verwenden, um auf andere zuzugreifen
       Ressourcen.
Andy Schneider
quelle
16
Dies ist, was Sie in PS 3.0 benötigen:$PSCommandPath Contains the full path and file name of the script that is being run. This variable is valid in all scripts.
CodeMonkeyKing
2
Habe gerade $ PSScriptRoot getestet und arbeite wie erwartet. Es würde jedoch eine leere Zeichenfolge geben, wenn Sie sie in der Befehlszeile ausführen. Es würde nur dann ein Ergebnis liefern, wenn es in einem Skript verwendet wird und das Skript ausgeführt wird. Dafür ist es gedacht .....
Farrukh Waheed
4
Ich bin verwirrt. Diese Antwort besagt, dass PSScriptRoot für V2 verwendet werden soll. Eine andere Antwort besagt, dass PSScriptRoot für V3 + ist und für v2 etwas anderes verwendet werden soll.
6
@user $ PSScriptRoot in Version 2 gilt nur für Module . Wenn Sie "normale" Skripte schreiben, die nicht in einem Modul enthalten sind, benötigen Sie $ MyInvocation.MyCommand.Definition (siehe Antwort oben).
Yzorg
1
@Lofful Ich sagte in "v2", es wurde nur für Module definiert. Sie sagen, es ist außerhalb von Modulen in Version 3 definiert. Ich denke, wir sagen dasselbe. :)
yzorg
35

Für 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.

Die Funktion ist dann:

function Get-ScriptDirectory {
    Split-Path -Parent $PSCommandPath
}
CodeMonkeyKing
quelle
20
Verwenden Sie noch besser $ PSScriptRoot. Dies ist das Verzeichnis der aktuellen Datei / des aktuellen Moduls.
Aaron Jensen
2
Dieser Befehl enthält den Dateinamen des Skripts, der mich abschreckte, bis mir das klar wurde. Wenn Sie den Pfad möchten, möchten Sie wahrscheinlich auch nicht den Skriptnamen. Zumindest fällt mir kein Grund ein, warum Sie das wollen würden. $ PSScriptRoot enthält nicht den Dateinamen (aus anderen Antworten entnommen).
YetAnotherRandomUser
$ PSScriptRoot ist in einem normalen PS1-Skript leer. $ PSCommandPath funktioniert jedoch. Das Verhalten von beiden wird gemäß den Beschreibungen in anderen Beiträgen erwartet. Kann auch einfach [IO.Path] :: GetDirectoryName ($ PSCommandPath) verwenden, um das Skriptverzeichnis ohne den Dateinamen abzurufen.
Sande verlaufen
19

Für PowerShell 3+

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

Ich habe diese Funktion in mein Profil aufgenommen. Es funktioniert in ISE auch mit F8/ Run Selection.

nickkzl
quelle
16

Vielleicht fehlt mir hier etwas ... aber wenn Sie das aktuelle Arbeitsverzeichnis möchten, können Sie dies einfach verwenden: (Get-Location).Pathfür eine Zeichenfolge oder Get-Locationfür ein Objekt.

Es sei denn, Sie beziehen sich auf so etwas, was ich verstehe, nachdem ich die Frage erneut gelesen habe.

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}
Sean C.
quelle
16
Dadurch wird der aktuelle Speicherort abgerufen, an dem der Benutzer das Skript ausführt . Nicht der Speicherort der Skriptdatei selbst .
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 Speichern Sie das und führen Sie es aus einem anderen Verzeichnis aus. Sie zeigen den Pfad zum Skript.
Sean C.
Das ist eine gute Funktion und macht das, was ich brauche, aber wie teile ich sie und verwende sie in all meinen Skripten? Es ist ein Henne-Ei-Problem: Ich möchte eine Funktion verwenden, um meinen aktuellen Standort herauszufinden, aber ich benötige meinen Standort, um die Funktion zu laden.
Aaron Jensen
2
HINWEIS: Der Aufruf dieser Funktion muss sich auf der obersten Ebene Ihres Skripts befinden. Wenn sie in einer anderen Funktion verschachtelt ist, müssen Sie den Parameter "-Scope" ändern, um festzulegen, wie tief Sie im Aufrufstapel sind.
Kenny
11

Sehr ähnlich zu bereits veröffentlichten Antworten, aber Piping scheint eher PowerShell-ähnlich zu sein:

$PSCommandPath | Split-Path -Parent
CPAR
quelle
11

Ich benutze die automatische Variable $ExecutionContext . Es funktioniert ab PowerShell 2 und höher.

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

$ ExecutionContext Enthält ein EngineIntrinsics-Objekt, das den Ausführungskontext des Windows PowerShell-Hosts darstellt. Mit dieser Variablen können Sie die Ausführungsobjekte suchen, die Cmdlets zur Verfügung stehen.

Viggos
quelle
1
Dies ist die einzige, die für mich funktioniert hat und versucht hat, Powershell von STDIN zu füttern.
Sebastian
Diese Lösung funktioniert auch ordnungsgemäß, wenn Sie sich in einem UNC-Pfadkontext befinden.
user2030503
1
Dies nimmt anscheinend das Arbeitsverzeichnis auf - und nicht, wo sich das Skript befindet?
Monojohnny
@monojohnny Ja, dies ist im Grunde das aktuelle Arbeitsverzeichnis und funktioniert nicht, wenn ein Skript von einem anderen Speicherort aus aufgerufen wird.
Marsze
9

Es dauerte eine Weile, bis ich etwas entwickelt hatte, das die akzeptierte Antwort in eine robuste Funktion verwandelte.

Bei anderen bin ich mir nicht sicher, aber ich arbeite in einer Umgebung mit Computern auf PowerShell Version 2 und 3, daher musste ich beide verarbeiten. Die folgende Funktion bietet einen eleganten Fallback:

Function Get-PSScriptRoot
{
    $ScriptRoot = ""

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

    Write-Output $ScriptRoot
}

Dies bedeutet auch, dass sich die Funktion eher auf den Skriptbereich als auf den Bereich der Eltern bezieht, wie von Michael Sorens in einem seiner Blog-Beiträge beschrieben .

Bruno
quelle
Vielen Dank! Das "$ script:" war das, was ich brauchte, damit dies in Windows PowerShell ISE funktioniert.
Kirk Liemohn
Danke dafür. Dies war der einzige, der für mich funktioniert. Normalerweise muss ich in das Verzeichnis, in dem sich das Skript befindet, eine CD erstellen, bevor Dinge wie Get-Location für mich funktionieren. Mich würde interessieren, warum PowerShell das Verzeichnis nicht automatisch aktualisiert.
Zain
7

Ich musste den Namen des Skripts kennen und wissen, woher es ausgeführt wird.

Das Präfix "$ global:" in der MyInvocation-Struktur gibt den vollständigen Pfad und den Skriptnamen zurück, wenn es sowohl vom Hauptskript als auch von der Hauptzeile einer importierten .PSM1-Bibliotheksdatei aufgerufen wird. Es funktioniert auch innerhalb einer Funktion in einer importierten Bibliothek.

Nachdem ich viel herumgespielt hatte, entschied ich mich für $ global: MyInvocation.InvocationName. Es funktioniert zuverlässig mit CMD-Start, Run With Powershell und ISE. Sowohl lokale als auch UNC-Starts geben den richtigen Pfad zurück.

Bruce Gavin
quelle
4
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) hat perfekt funktioniert, danke. Die anderen Lösungen gaben den Pfad der aufrufenden Anwendung zurück.
Dynamiclynk
1
Trivialer Hinweis: Wenn Sie in ISE diese Funktion mit F8 / Run Selection aufrufen, wird eine ParameterArgumentValidationErrorNullNotAllowedAusnahme ausgelöst .
Wehr
5

Ich verwende immer dieses kleine Snippet, das für PowerShell und ISE auf die gleiche Weise funktioniert :

# 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
Peter
quelle
3

Ich habe festgestellt, dass die hier veröffentlichten älteren Lösungen unter PowerShell V5 bei mir nicht funktionieren. Ich habe mir das ausgedacht:

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
quelle
2

Sie können auch überlegen, split-path -parent $psISE.CurrentFile.Fullpathob eine der anderen Methoden fehlschlägt. Insbesondere wenn Sie eine Datei ausführen, um eine Reihe von Funktionen zu laden, und diese Funktionen dann in der ISE-Shell ausführen (oder wenn Sie ausgewählt ausführen), scheint die oben beschriebene Get-Script-DirectoryFunktion nicht zu funktionieren.

Fastboxster
quelle
3
$PSCommandPathfunktioniert in der ISE, solange Sie das Skript zuerst speichern und die gesamte Datei ausführen. Andernfalls führen Sie kein Skript aus. Sie "fügen" nur Befehle in die Shell ein.
Zenexer
@Zenexer Ich denke, das war damals mein Ziel. Obwohl, wenn mein Ziel nicht mit dem ursprünglichen übereinstimmte, dies möglicherweise nicht allzu hilfreich ist, außer für gelegentliche Googler ...
2

Unter Verwendung von Stücken aus all diesen Antworten und den Kommentaren habe ich dies für jeden zusammengestellt, der diese Frage in Zukunft sieht. Es deckt alle in den anderen Antworten aufgeführten Situationen ab

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