Qual è il modo migliore per determinare la posizione dell'attuale script PowerShell?


530

Ogni volta che ho bisogno di fare riferimento a un modulo o uno script comune, mi piace usare i percorsi relativi al file di script corrente. In questo modo, il mio script può sempre trovare altri script nella libreria.

Quindi, qual è il modo migliore e standard per determinare la directory dello script corrente? Attualmente sto facendo:

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

So che nei moduli (.psm1) è possibile utilizzare $PSScriptRootper ottenere queste informazioni, ma ciò non viene impostato negli script regolari (ad es. File .ps1).

Qual è il modo canonico per ottenere la posizione corrente del file di script di PowerShell?


Risposte:


865

PowerShell 3+

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

PowerShell 2

Prima di PowerShell 3, non c'era modo migliore di interrogare la MyInvocation.MyCommand.Definitionproprietà per gli script generali. Ho avuto la seguente riga in cima a ogni script PowerShell che avevo:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition

1
A cosa Split-Pathserve qui?
CMCDragonkai,

7
Split-Pathviene utilizzato con il -Parentparametro per restituire la directory corrente senza il nome dello script attualmente in esecuzione.
hjoelr,

5
Nota: con PowerShell su Linux / macOS, lo script deve avere un'estensione .ps1 per poter popolare PSScriptRoot / MyInvocation ecc. Vedi la segnalazione di bug qui: github.com/PowerShell/PowerShell/issues/4217
Dave Wood

3
Dubbio con un lato potenzialmente interessante: l'approssimazione v2 più vicina $PSScriptRootè ( Split-Path -Parentapplicata a) $MyInvocation.MyCommand.Path, non $MyInvocation.MyCommand.Definition, sebbene nell'ambito di livello superiore di uno script si comportino allo stesso modo (che è l'unico posto ragionevole da cui chiamare per questo scopo). Quando viene chiamato all'interno di una funzione o di un blocco di script , il primo restituisce la stringa vuota, mentre il secondo restituisce la definizione del corpo della funzione / blocco di script come stringa (un pezzo di codice sorgente di PowerShell).
mklement0

62

Se stai creando un modulo V2, puoi utilizzare una variabile automatica chiamata $PSScriptRoot.

Da PS> Aiuto automatic_variable

$ PSScriptRoot
       Contiene la directory da cui viene eseguito il modulo di script.
       Questa variabile consente agli script di utilizzare il percorso del modulo per accedere ad altri
       risorse.

16
Questo è ciò di cui hai bisogno in 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
Ho appena testato $ PSScriptRoot e funziona come previsto. Tuttavia, ti darebbe una stringa vuota se la esegui dalla riga di comando. Ti darebbe risultati solo se usato in uno script e lo script viene eseguito. Ecco cosa significa .....
Farrukh Waheed,

4
Non ho capito bene. Questa risposta dice di usare PSScriptRoot per V2. Un'altra risposta dice che PSScriptRoot è per V3 + e per usare qualcosa di diverso per v2.

6
@user $ PSScriptRoot in v2 è solo per i moduli , se stai scrivendo script "normali" non in un modulo hai bisogno di $ MyInvocation.MyCommand.Definition, vedi la risposta principale.
yzorg,

1
@Lofful Ho detto in "v2" che è stato definito solo per i moduli. Stai dicendo che è definito fuori dai moduli nella v3. Penso che stiamo dicendo la stessa cosa. :)
yzorg

35

Per 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 funzione è quindi:

function Get-ScriptDirectory {
    Split-Path -Parent $PSCommandPath
}

20
Ancora meglio, usa $ PSScriptRoot. È la directory del modulo / file corrente.
Aaron Jensen,

2
Questo comando include il nome del file dello script, che mi ha gettato via finché non me ne sono reso conto. Quando vuoi il percorso, probabilmente non vuoi inserire anche il nome dello script. Almeno, non riesco a pensare a un motivo per cui lo vorresti. $ PSScriptRoot non include il nome file (ricavato da altre risposte).
YetAnotherRandomUser

$ PSScriptRoot è vuoto da un normale script PS1. $ PSCommandPath funziona, comunque. Il comportamento di entrambi è previsto in base alle descrizioni fornite in altri post. Puoi anche usare [IO.Path] :: GetDirectoryName ($ PSCommandPath) per ottenere la directory degli script senza il nome del file.
Fizzled

19

Per PowerShell 3+

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

Ho inserito questa funzione nel mio profilo. Funziona in ISE anche usando F8/ Esegui selezione.


16

Forse mi manca qualcosa qui ... ma se vuoi l'attuale directory di lavoro puoi semplicemente usare questo: (Get-Location).Pathper una stringa o Get-Locationper un oggetto.

A meno che non ti riferisca a qualcosa del genere, che capisco dopo aver letto di nuovo la domanda.

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}

16
Questo ottiene la posizione corrente in cui l'utente sta eseguendo lo script . Non la posizione del file di script stesso .
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 Salvalo ed eseguilo da un'altra directory. Mostrerai il percorso dello script.
Sean C.

Questa è una buona funzione e fa quello di cui ho bisogno, ma come posso condividerla e usarla in tutti i miei script? È un problema con galline e uova: mi piacerebbe usare una funzione per scoprire la mia posizione attuale, ma ho bisogno della mia posizione per caricare la funzione.
Aaron Jensen

2
NOTA: l'invocazione di questa funzione deve essere al livello più alto dello script, se è nidificato in un'altra funzione, è necessario modificare il parametro "-Scope" per indicare la profondità dello stack di chiamate.
Kenny,

11

Molto simile alle risposte già pubblicate, ma il piping sembra più simile a PowerShell:

$PSCommandPath | Split-Path -Parent

11

Uso la variabile automatica $ExecutionContext . Funzionerà da PowerShell 2 e versioni successive.

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

$ ExecutionContext Contiene un oggetto EngineIntrinsics che rappresenta il contesto di esecuzione dell'host Windows PowerShell. È possibile utilizzare questa variabile per trovare gli oggetti di esecuzione disponibili per i cmdlet.


1
Questo è l'unico che ha funzionato per me, cercando di alimentare PowerShell da STDIN.
Sebastian,

Questa soluzione funziona anche correttamente quando ci si trova in un contesto di percorso UNC.
user2030503,

1
Questo a quanto pare sta raccogliendo la directory di lavoro - piuttosto che dove si trova lo script?
monojohnny,

@monojohnny Sì, questa è sostanzialmente la directory di lavoro corrente e non funzionerà quando si chiama uno script da un'altra posizione.
Marsze

9

Mi ci è voluto un po 'di tempo per sviluppare qualcosa che prendesse la risposta accettata e la trasformasse in una solida funzione.

Non sono sicuro degli altri, ma lavoro in un ambiente con macchine sia su PowerShell versione 2 che 3, quindi dovevo gestirle entrambe. La seguente funzione offre un grazioso 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
}

Significa anche che la funzione si riferisce all'ambito dello script piuttosto che all'ambito del genitore, come sottolineato da Michael Sorens in uno dei suoi post sul blog .


Grazie! Lo "$ script:" era ciò di cui avevo bisogno per farlo funzionare in Windows PowerShell ISE.
Kirk Liemohn,

Grazie per questo. Questo è stato l'unico che funziona per me. Di solito devo CD nella directory in cui si trova lo script prima che cose come Get-location funzionino per me. Sarei interessato a sapere perché PowerShell non aggiorna automaticamente la directory.
Zain

7

Avevo bisogno di sapere il nome dello script e da dove viene eseguito.

Il prefisso "$ global:" alla struttura MyInvocation restituisce il percorso completo e il nome dello script quando viene chiamato sia dallo script principale sia dalla riga principale di un file di libreria .PSM1 importato. Funziona anche all'interno di una funzione in una libreria importata.

Dopo aver giocato molto, ho deciso di usare $ global: MyInvocation.InvocationName. Funziona in modo affidabile con il lancio di CMD, Run With Powershell e ISE. Sia i lanci locali che UNC restituiscono il percorso corretto.


4
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) ha funzionato perfettamente grazie. Le altre soluzioni hanno restituito il percorso dell'applicazione chiamante.
dynamiclynk,

1
Nota fondamentale: quando ISE chiama questa funzione usando F8 / Esegui selezione si innescherà ParameterArgumentValidationErrorNullNotAllowedun'eccezione.
diga

5

Uso sempre questo piccolo frammento che funziona per PowerShell e ISE allo stesso modo:

# 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

Ho scoperto che le soluzioni precedenti pubblicate qui non funzionavano per me su PowerShell V5. Ho pensato a questo:

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

Potresti anche considerare split-path -parent $psISE.CurrentFile.Fullpathse uno degli altri metodi fallisce. In particolare, se si esegue un file per caricare un mucchio di funzioni e quindi si eseguono tali funzioni all'interno della shell ISE (o se si è selezionate in modo corretto), la Get-Script-Directoryfunzione come sopra non funziona.


3
$PSCommandPathfunzionerà nell'ISE fintanto che salvi prima lo script ed esegui l'intero file. Altrimenti, non stai eseguendo uno script; stai solo "incollando" i comandi nella shell.
Zenexer,

@Zenexer Penso che fosse il mio obiettivo in quel momento. Anche se il mio obiettivo non coincidesse con quello originale, questo potrebbe non essere troppo utile se non per i googler occasionali ...

2

Usando pezzi di tutte queste risposte e commenti, ho messo questo insieme per chiunque veda questa domanda in futuro. Copre tutte le situazioni elencate nelle altre risposte

    # 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.