Quelle est la meilleure façon de déterminer l'emplacement du script PowerShell actuel?

530

Chaque fois que j'ai besoin de référencer un module ou un script commun, j'aime utiliser des chemins relatifs au fichier de script actuel. De cette façon, mon script peut toujours trouver d'autres scripts dans la bibliothèque.

Alors, quelle est la meilleure façon standard de déterminer le répertoire du script actuel? Actuellement, je fais:

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

Je sais que dans les modules (.psm1), vous pouvez utiliser $PSScriptRootpour obtenir ces informations, mais cela n'est pas défini dans les scripts réguliers (c'est-à-dire les fichiers .ps1).

Quelle est la manière canonique d'obtenir l'emplacement du fichier de script PowerShell actuel?

Aaron Jensen
la source

Réponses:

865

PowerShell 3+

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

PowerShell 2

Avant PowerShell 3, il n'y avait pas de meilleur moyen que d'interroger la MyInvocation.MyCommand.Definitionpropriété pour des scripts généraux. J'avais la ligne suivante en haut de pratiquement tous les scripts PowerShell que j'avais:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
JaredPar
la source
1
À quoi Split-Pathsert ici?
CMCDragonkai
7
Split-Pathest utilisé avec le -Parentparamètre pour renvoyer le répertoire courant sans le nom du script en cours d'exécution.
hjoelr
5
Remarque: avec PowerShell sur Linux / macOS, votre script doit avoir une extension .ps1 pour PSScriptRoot / MyInvocation etc. pour être rempli. Voir le rapport de bogue ici: github.com/PowerShell/PowerShell/issues/4217
Dave Wood
3
Quibble avec un côté potentiellement intéressant: l'approximation v2 plus proche de $PSScriptRootest ( Split-Path -Parentappliquée à) $MyInvocation.MyCommand.Path, non $MyInvocation.MyCommand.Definition, bien que dans la portée de niveau supérieur d'un script, ils se comportent de la même manière (qui est le seul endroit raisonnable à appeler à cet effet). Lorsqu'il est appelé à l'intérieur d'une fonction ou d' un bloc de script , le premier renvoie la chaîne vide, tandis que le second renvoie la définition du corps de fonction / bloc de script sous forme de chaîne (un morceau de code source PowerShell).
mklement0
62

Si vous créez un module V2, vous pouvez utiliser une variable automatique appelée $PSScriptRoot .

Depuis PS> Aide automatic_variable

$ PSScriptRoot
       Contient le répertoire à partir duquel le module de script est exécuté.
       Cette variable permet aux scripts d'utiliser le chemin du module pour accéder à d'autres
       Ressources.
Andy Schneider
la source
16
Voici ce dont vous avez besoin dans 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
Je viens de tester $ PSScriptRoot et de travailler comme prévu. Cependant, cela vous donnerait une chaîne vide si vous l'exécutiez en ligne de commande. Il ne vous donnerait un résultat que s'il est utilisé dans un script et que le script est exécuté. C'est à ça que ça sert .....
Farrukh Waheed
4
Je suis confus. Cette réponse dit d'utiliser PSScriptRoot pour V2. Une autre réponse indique que PSScriptRoot est pour V3 + et pour utiliser quelque chose de différent pour v2.
6
@user $ PSScriptRoot dans v2 est uniquement pour les modules , si vous écrivez des scripts `` normaux '' pas dans un module, vous avez besoin de $ MyInvocation.MyCommand.Definition, voir la réponse du haut.
yzorg
1
@Lofful J'ai dit dans "v2" qu'il n'était défini que pour les modules. Vous dites qu'il est défini en dehors des modules dans la v3. Je pense que nous disons la même chose. :)
yzorg
35

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

La fonction est alors:

function Get-ScriptDirectory {
    Split-Path -Parent $PSCommandPath
}
CodeMonkeyKing
la source
20
Encore mieux, utilisez $ PSScriptRoot. Il s'agit du répertoire du fichier / module actuel.
Aaron Jensen
2
Cette commande inclut le nom de fichier du script, ce qui m'a bouleversé jusqu'à ce que je m'en rende compte. Lorsque vous voulez le chemin, vous ne voulez probablement pas que le nom du script y figure également. Au moins, je ne vois aucune raison pour laquelle vous voudriez cela. $ PSScriptRoot n'inclut pas le nom de fichier (glané d'autres réponses).
YetAnotherRandomUser
$ PSScriptRoot est vide d'un script PS1 standard. Cependant, $ PSCommandPath fonctionne. Le comportement des deux est attendu par les descriptions données dans d'autres articles. Peut également simplement utiliser [IO.Path] :: GetDirectoryName ($ PSCommandPath) pour obtenir le répertoire de script sans le nom de fichier.
pétillé le
19

Pour PowerShell 3+

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

J'ai placé cette fonction dans mon profil. Il fonctionne également dans ISE en utilisant F8/ Run Selection.

nickkzl
la source
16

Peut-être que je manque quelque chose ici ... mais si vous voulez le répertoire de travail actuel, vous pouvez simplement l'utiliser: (Get-Location).Pathpour une chaîne ou Get-Locationpour un objet.

À moins que vous ne parliez de quelque chose comme ça, ce que je comprends après avoir relu la question.

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}
Sean C.
la source
16
Cela obtient l'emplacement actuel où l'utilisateur exécute le script . Pas l'emplacement du fichier de script lui-même .
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 Enregistrez cela et exécutez-le à partir d'un répertoire différent. Vous allez montrer le chemin vers le script.
Sean C.
C'est une bonne fonction et fait ce dont j'ai besoin, mais comment la partager et l'utiliser dans tous mes scripts? C'est un problème de poule et d'oeuf: j'aimerais utiliser une fonction pour trouver ma position actuelle, mais j'ai besoin de ma position pour charger la fonction.
Aaron Jensen
2
REMARQUE: l'invocation de cette fonction doit être au niveau supérieur de votre script, si elle est imbriquée dans une autre fonction, vous devez modifier le paramètre "-Scope" pour désigner la profondeur de la pile d'appels.
kenny
11

Très similaire aux réponses déjà publiées, mais la tuyauterie semble plus proche de PowerShell:

$PSCommandPath | Split-Path -Parent
CPAR
la source
11

J'utilise la variable automatique $ExecutionContext . Cela fonctionnera à partir de PowerShell 2 et versions ultérieures.

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

$ ExecutionContext Contient un objet EngineIntrinsics qui représente le contexte d'exécution de l'hôte Windows PowerShell. Vous pouvez utiliser cette variable pour rechercher les objets d'exécution disponibles pour les applets de commande.

Viggos
la source
1
C'est le seul qui a fonctionné pour moi, en essayant d'alimenter PowerShell de STDIN.
Sebastian
Cette solution fonctionne également correctement lorsque vous êtes dans un contexte de chemin UNC.
user2030503
1
C'est apparemment ramasser le répertoire de travail - plutôt que où se trouve le script?
monojohnny
@monojohnny Oui, il s'agit essentiellement du répertoire de travail actuel et ne fonctionnera pas lors de l'appel d'un script à partir d'un autre emplacement.
marsze
9

Il m'a fallu un certain temps pour développer quelque chose qui prenait la réponse acceptée et en faisait une fonction robuste.

Je ne suis pas sûr des autres, mais je travaille dans un environnement avec des machines sur PowerShell version 2 et 3, donc j'avais besoin de gérer les deux. La fonction suivante offre une solution de rechange élégante:

Function Get-PSScriptRoot
{
    $ScriptRoot = ""

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

    Write-Output $ScriptRoot
}

Cela signifie également que la fonction fait référence à la portée du script plutôt qu'à la portée du parent comme indiqué par Michael Sorens dans l'un de ses articles de blog .

Bruno
la source
Merci! Le "$ script:" était ce dont j'avais besoin pour que cela fonctionne dans Windows PowerShell ISE.
Kirk Liemohn
Merci pour cela. Ce fut le seul qui fonctionne pour moi. Je dois habituellement CD dans le répertoire où se trouve le script avant que des choses comme Get-location ne fonctionnent pour moi. Je serais intéressé de savoir pourquoi PowerShell ne met pas automatiquement à jour le répertoire.
Zain
7

J'avais besoin de connaître le nom du script et d'où il s'exécute.

Le préfixe «$ global:» à la structure MyInvocation renvoie le chemin d'accès complet et le nom du script lorsqu'il est appelé à la fois à partir du script principal et de la ligne principale d'un fichier de bibliothèque .PSM1 importé. Il fonctionne également à partir d'une fonction dans une bibliothèque importée.

Après avoir beaucoup tripoté, j'ai décidé d'utiliser $ global: MyInvocation.InvocationName. Il fonctionne de manière fiable avec le lancement de CMD, Run With Powershell et ISE. Les lancements locaux et UNC renvoient le chemin correct.

Bruce Gavin
la source
4
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) a fonctionné parfaitement merci. Les autres solutions ont renvoyé le chemin de l'application appelante.
dynamiclynk
1
Remarque triviale: dans ISE, appeler cette fonction en utilisant F8 / Run Selection déclenchera une ParameterArgumentValidationErrorNullNotAllowedexception.
déversoir
5

J'utilise toujours ce petit extrait qui fonctionne pour PowerShell et ISE de la même manière:

# 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
la source
3

J'ai trouvé que les anciennes solutions publiées ici ne fonctionnaient pas pour moi sur PowerShell V5. Je suis venu avec ceci:

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
la source
2

Vous pouvez également considérer split-path -parent $psISE.CurrentFile.Fullpathsi l'une des autres méthodes échoue. En particulier, si vous exécutez un fichier pour charger un tas de fonctions, puis exécutez ces fonctions dans le shell ISE (ou si vous exécutez-sélectionné), il semble que la Get-Script-Directoryfonction comme ci-dessus ne fonctionne pas.

fastboxster
la source
3
$PSCommandPathfonctionnera dans l'ISE tant que vous enregistrez d'abord le script et exécutez le fichier entier. Sinon, vous n'exécutez pas réellement un script; vous "collez" simplement des commandes dans le shell.
Zenexer
@Zenexer Je pense que c'était mon objectif à l'époque. Bien que si mon objectif ne correspondait pas à celui d'origine, cela pourrait ne pas être trop utile, sauf pour les Googleurs occasionnels ...
2

En utilisant des morceaux de toutes ces réponses et des commentaires, j'ai mis cela ensemble pour quiconque verra cette question à l'avenir. Il couvre toutes les situations énumérées dans les autres réponses

    # 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
    }
Excité
la source
-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
la source