Apa cara terbaik untuk menentukan lokasi skrip PowerShell saat ini?

530

Setiap kali saya perlu referensi modul atau skrip umum, saya suka menggunakan jalur relatif terhadap file skrip saat ini. Dengan begitu, skrip saya selalu dapat menemukan skrip lain di perpustakaan.

Jadi, apa cara terbaik dan standar untuk menentukan direktori skrip saat ini? Saat ini, saya sedang melakukan:

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

Saya tahu dalam modul (.psm1) yang dapat Anda gunakan $PSScriptRootuntuk mendapatkan informasi ini, tetapi itu tidak diatur dalam skrip biasa (yaitu file .ps1).

Apa cara kanonik untuk mendapatkan lokasi file skrip PowerShell saat ini?

Aaron Jensen
sumber

Jawaban:

865

PowerShell 3+

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

PowerShell 2

Sebelum ke PowerShell 3, tidak ada cara yang lebih baik daripada menanyakan MyInvocation.MyCommand.Definitionproperti untuk skrip umum. Saya memiliki baris berikut di bagian atas pada dasarnya setiap skrip PowerShell yang saya miliki:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
JaredPar
sumber
1
Apa yang Split-Pathdigunakan di sini?
CMCDragonkai
7
Split-Pathdigunakan dengan -Parentparameter untuk mengembalikan direktori saat ini tanpa nama skrip yang saat ini mengeksekusi.
hjoelr
5
Catatan: dengan PowerShell di Linux / macOS, skrip Anda harus memiliki ekstensi .ps1 untuk PSScriptRoot / MyInvocation dll untuk diisi. Lihat laporan bug di sini: github.com/PowerShell/PowerShell/issues/4217
Dave Wood
3
Dipertanyakan dengan potensi samping yang menarik: Pendekatan yang lebih dekat dari $PSScriptRoot( Split-Path -Parentditerapkan ke) $MyInvocation.MyCommand.Path, tidak $MyInvocation.MyCommand.Definition, meskipun dalam lingkup tingkat atas dari skrip mereka berperilaku sama (yang merupakan satu-satunya tempat yang masuk akal untuk menelepon dari untuk tujuan ini). Ketika dipanggil di dalam blok fungsi atau skrip , yang pertama mengembalikan string kosong, sedangkan yang kedua mengembalikan definisi fungsi / skrip blok fungsi sebagai string (sepotong kode sumber PowerShell).
mklement0
62

Jika Anda membuat Modul V2, Anda dapat menggunakan variabel otomatis yang disebut $PSScriptRoot .

Dari PS> Bantuan automatic_variable

$ PSScriptRoot
       Berisi direktori tempat modul skrip dieksekusi.
       Variabel ini memungkinkan skrip untuk menggunakan jalur modul untuk mengakses yang lain
       sumber daya.
Andy Schneider
sumber
16
Inilah yang Anda butuhkan di 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
Baru saja menguji $ PSScriptRoot dan berfungsi seperti yang diharapkan. Namun, itu akan memberi Anda string kosong jika Anda menjalankannya di baris perintah. Itu hanya akan memberi Anda hasil jika digunakan dalam skrip dan skrip dieksekusi. Itulah yang dimaksudkan untuk .....
Farrukh Waheed
4
Saya bingung. Jawaban ini mengatakan untuk menggunakan PSScriptRoot untuk V2. Jawaban lain mengatakan PSScriptRoot adalah untuk V3 +, dan menggunakan sesuatu yang berbeda untuk v2.
6
@user $ PSScriptRoot di v2 hanya untuk modul , jika Anda menulis skrip 'normal' tidak dalam modul, Anda memerlukan $ MyInvocation.MyCommand.Definition, lihat jawaban teratas.
yzorg
1
@Lofful saya katakan di "v2" itu hanya ditentukan untuk modul. Anda mengatakan itu didefinisikan di luar modul di v3. Saya pikir kita mengatakan hal yang sama. :)
yzorg
35

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

Kemudian fungsinya adalah:

function Get-ScriptDirectory {
    Split-Path -Parent $PSCommandPath
}
CodeMonkeyKing
sumber
20
Lebih baik lagi, gunakan $ PSScriptRoot. Ini adalah direktori file / modul saat ini.
Aaron Jensen
2
Perintah ini termasuk nama file dari skrip, yang melemparkan saya sampai saya menyadari itu. Ketika Anda menginginkan path, Anda mungkin tidak ingin nama skrip di sana juga. Setidaknya, saya tidak bisa memikirkan alasan Anda menginginkan hal itu. $ PSScriptRoot tidak termasuk nama file (diperoleh dari jawaban lain).
YetAnotherRandomUser
$ PSScriptRoot kosong dari skrip PS1 biasa. $ PSCommandPath berfungsi, meskipun. Perilaku keduanya diharapkan sesuai dengan deskripsi yang diberikan di pos lain. Bisa juga menggunakan [IO.Path] :: GetDirectoryName ($ PSCommandPath) untuk mendapatkan direktori skrip tanpa nama file.
gagal
19

Untuk PowerShell 3+

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

Saya telah menempatkan fungsi ini di profil saya. Ia bekerja di ISE menggunakan F8/ Run Selection juga.

nickkzl
sumber
16

Mungkin saya kehilangan sesuatu di sini ... tetapi jika Anda ingin direktori kerja saat ini Anda dapat menggunakan ini: (Get-Location).Pathuntuk string, atau Get-Locationuntuk objek.

Kecuali jika Anda merujuk pada sesuatu seperti ini, yang saya mengerti setelah membaca pertanyaan itu lagi.

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}
Sean C.
sumber
16
Ini mendapatkan lokasi saat ini di mana pengguna menjalankan skrip . Bukan lokasi file skrip itu sendiri .
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 Simpan itu dan jalankan dari direktori yang berbeda. Anda akan menunjukkan jalur ke skrip.
Sean C.
Itu fungsi yang baik, dan melakukan apa yang saya butuhkan, tetapi bagaimana saya membagikannya dan menggunakannya di semua skrip saya? Ini masalah ayam dan telur: Saya ingin menggunakan fungsi untuk mengetahui lokasi saya saat ini, tetapi saya perlu lokasi saya untuk memuat fungsi.
Aaron Jensen
2
CATATAN: Invokasi fungsi ini harus berada di tingkat atas skrip Anda, jika bersarang di dalam fungsi lain, maka Anda harus mengubah parameter "-Scope" untuk menentukan seberapa dalam tumpukan panggilan Anda.
kenny
11

Sangat mirip dengan jawaban yang sudah diposting, tetapi pemipaan tampaknya lebih mirip PowerShell:

$PSCommandPath | Split-Path -Parent
CPAR
sumber
11

Saya menggunakan variabel otomatis $ExecutionContext . Ini akan bekerja dari PowerShell 2 dan yang lebih baru.

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

$ ExecutionContext Berisi objek EngineIntrinsics yang mewakili konteks eksekusi host Windows PowerShell. Anda bisa menggunakan variabel ini untuk menemukan objek eksekusi yang tersedia untuk cmdlet.

Viggos
sumber
1
Ini adalah satu-satunya yang bekerja untuk saya, mencoba memberi makan PowerShell dari STDIN.
Sebastian
Solusi ini juga berfungsi dengan baik ketika Anda berada dalam konteks jalur UNC.
user2030503
1
Ini tampaknya mengambil direktori kerja - daripada di mana skrip berada?
monojohnny
@monojohnny Ya ini pada dasarnya direktori kerja saat ini dan tidak akan berfungsi saat memanggil skrip dari lokasi lain.
rawa
9

Butuh beberapa saat untuk mengembangkan sesuatu yang mengambil jawaban yang diterima dan mengubahnya menjadi fungsi yang kuat.

Saya tidak yakin tentang yang lain, tetapi saya bekerja di lingkungan dengan mesin pada PowerShell versi 2 dan 3, jadi saya harus menangani keduanya. Fungsi berikut menawarkan fallback yang anggun:

Function Get-PSScriptRoot
{
    $ScriptRoot = ""

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

    Write-Output $ScriptRoot
}

Ini juga berarti bahwa fungsi tersebut merujuk pada ruang lingkup Script daripada ruang lingkup orang tua seperti yang diuraikan oleh Michael Sorens di salah satu posting blognya .

Bruno
sumber
Terima kasih! "$ Script:" adalah apa yang saya perlukan agar ini berfungsi di Windows PowerShell ISE.
Kirk Liemohn
Terima kasih untuk ini. Ini adalah satu-satunya yang bekerja untuk saya. Saya biasanya harus memasukkan CD ke direktori tempat skrip berada sebelum hal-hal seperti Get-location berfungsi untuk saya. Saya tertarik untuk mengetahui mengapa PowerShell tidak secara otomatis memperbarui direktori.
Zain
7

Saya perlu tahu nama skrip dan dari mana itu dijalankan.

Awalan "$ global:" ke struktur MyInvocation mengembalikan path lengkap dan nama skrip ketika dipanggil dari kedua skrip utama, dan baris utama dari file library .PSM1 yang diimpor. Ini juga berfungsi dari dalam fungsi di perpustakaan yang diimpor.

Setelah banyak mengutak-atik, saya memutuskan untuk menggunakan $ global: MyInvocation.InvocationName. Ia bekerja dengan andal dengan peluncuran CMD, Run With Powershell, dan ISE. Kedua peluncuran lokal dan UNC mengembalikan jalur yang benar.

Bruce Gavin
sumber
4
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) bekerja dengan sempurna, terima kasih. Solusi lain mengembalikan jalur aplikasi panggilan.
dynamiclynk
1
Catatan sepele: di ISE memanggil fungsi ini menggunakan F8 / Run Seleksi akan memicu ParameterArgumentValidationErrorNullNotAllowedpengecualian.
weir
5

Saya selalu menggunakan potongan kecil ini yang berfungsi untuk PowerShell dan ISE dengan cara yang sama:

# 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
sumber
3

Saya menemukan bahwa solusi lama yang diposting di sini tidak bekerja untuk saya di PowerShell V5. Saya datang dengan ini:

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"
Kuantium
sumber
2

Anda juga dapat mempertimbangkan split-path -parent $psISE.CurrentFile.Fullpathjika salah satu metode lain gagal. Secara khusus, jika Anda menjalankan file untuk memuat banyak fungsi dan kemudian menjalankan fungsi-fungsi dengan-di shell ISE (atau jika Anda menjalankan-dipilih), tampaknya Get-Script-Directoryfungsi seperti di atas tidak berfungsi.

fastboxster
sumber
3
$PSCommandPathakan bekerja di ISE selama Anda menyimpan skrip terlebih dahulu dan mengeksekusi seluruh file. Jika tidak, Anda sebenarnya tidak menjalankan skrip; Anda hanya "menempel" perintah ke shell.
Zenexer
@ Zenexer Saya pikir itu adalah tujuan saya saat itu. Meskipun jika tujuan saya tidak cocok dengan yang asli, ini mungkin tidak terlalu membantu kecuali untuk Googler sesekali ...
2

Menggunakan potongan-potongan dari semua jawaban dan komentar ini, saya menyatukan ini untuk siapa saja yang melihat pertanyaan ini di masa depan. Ini mencakup semua situasi yang tercantum dalam jawaban lain

    # 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
    }
Randy
sumber
-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
sumber