Cách tốt nhất để xác định vị trí của tập lệnh PowerShell hiện tại là gì?


530

Bất cứ khi nào tôi cần tham chiếu một mô-đun hoặc tập lệnh phổ biến, tôi muốn sử dụng các đường dẫn liên quan đến tệp tập lệnh hiện tại. Bằng cách đó, tập lệnh của tôi luôn có thể tìm thấy các tập lệnh khác trong thư viện.

Vì vậy, cách tốt nhất, tiêu chuẩn để xác định thư mục của tập lệnh hiện tại là gì? Hiện tại, tôi đang làm:

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

Tôi biết trong các mô-đun (.psm1) bạn có thể sử dụng $PSScriptRootđể lấy thông tin này, nhưng điều đó không được đặt trong các tập lệnh thông thường (ví dụ: tệp .ps1).

Cách chính tắc để có được vị trí của tệp tập lệnh PowerShell hiện tại là gì?


Câu trả lời:


865

PowerShell 3+

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

PowerShell 2

Trước PowerShell 3, không có cách nào tốt hơn là truy vấn thuộc MyInvocation.MyCommand.Definitiontính cho các tập lệnh chung. Tôi đã có dòng sau ở đầu về cơ bản mọi tập lệnh PowerShell tôi có:

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

1
Cái gì Split-Pathđược sử dụng cho ở đây?
CMCDragonkai

7
Split-Pathđược sử dụng với -Parenttham số để trả về thư mục hiện tại mà không có tên tập lệnh hiện đang thực thi.
hjoelr

5
Lưu ý: với PowerShell trên Linux / macOS, tập lệnh của bạn phải có phần mở rộng .ps1 cho PSScriptRoot / MyInvocation, v.v. để được điền. Xem báo cáo lỗi tại đây: github.com/PowerShell/PowerShell/issues/4217
Dave Wood

3
Phân biệt với một khả năng thú vị sang một bên: Sự gần đúng của v2- gần hơn $PSScriptRootlà ( Split-Path -Parentáp dụng cho) $MyInvocation.MyCommand.Path, $MyInvocation.MyCommand.Definitionmặc dù trong phạm vi cấp cao nhất của một tập lệnh mà chúng hành xử giống nhau (là nơi hợp lý duy nhất để gọi cho mục đích này). Khi được gọi bên trong một khối chức năng hoặc tập lệnh , cái trước trả về chuỗi rỗng, trong khi cái sau trả về định nghĩa của khối cơ thể / tập lệnh dưới dạng một chuỗi (một đoạn mã nguồn PowerShell).
mkuity0

62

Nếu bạn đang tạo Mô-đun V2, bạn có thể sử dụng biến tự động được gọi là $PSScriptRoot .

Từ PS> Trợ giúp Automatic_variable

$ PSScriptRoot
       Chứa thư mục mà mô-đun tập lệnh đang được thực thi.
       Biến này cho phép các tập lệnh sử dụng đường dẫn mô-đun để truy cập khác
       tài nguyên.

16
Đây là những gì bạn cần trong 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
Chỉ cần thử nghiệm $ PSScriptRoot và hoạt động như mong đợi. Tuy nhiên, nó sẽ cung cấp cho bạn một chuỗi rỗng nếu bạn chạy nó ở dòng lệnh. Nó sẽ chỉ cung cấp cho bạn kết quả nếu được sử dụng trong một tập lệnh và tập lệnh được thực thi. Đó là ý nghĩa của nó .....
Farrukh Waheed

4
Tôi bối rối. Câu trả lời này cho biết sử dụng PSScriptRoot cho V2. Một câu trả lời khác cho biết PSScriptRoot dành cho V3 + và để sử dụng một cái gì đó khác cho v2.

6
@user $ PSScriptRoot trong v2 chỉ dành cho các mô-đun , nếu bạn đang viết các tập lệnh 'bình thường' không có trong một mô-đun, bạn cần $ MyInvocation.MyCommand.Def định, xem câu trả lời hàng đầu.
yzorg

1
@Lofful Tôi đã nói trong "v2", nó chỉ được xác định cho các mô-đun. Bạn đang nói nó được định nghĩa bên ngoài các mô-đun trong v3. Tôi nghĩ rằng chúng ta đang nói điều tương tự. :)
yzorg

35

Dành cho 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.

Chức năng là:

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

20
Thậm chí tốt hơn, sử dụng $ PSScriptRoot. Đây là thư mục / mô-đun của tập tin hiện tại.
Aaron Jensen

2
Lệnh này bao gồm tên tệp của tập lệnh, nó đã ném tôi đi cho đến khi tôi nhận ra điều đó. Khi bạn muốn đường dẫn, có lẽ bạn cũng không muốn tên tập lệnh trong đó. Ít nhất, tôi không thể nghĩ ra một lý do bạn sẽ muốn điều đó. $ PSScriptRoot không bao gồm tên tệp (lượm lặt từ các câu trả lời khác).
YetAnotherRandomUser

$ PSScriptRoot trống từ tập lệnh PS1 thông thường. $ PSCommandPath hoạt động, mặc dù. Hành vi của cả hai được mong đợi theo các mô tả được đưa ra trong các bài viết khác. Cũng có thể chỉ sử dụng [IO.Path] :: GetDirectoryName ($ PSCommandPath) để lấy thư mục tập lệnh mà không có tên tệp.
xì hơi

19

Dành cho PowerShell 3+

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

Tôi đã đặt chức năng này trong hồ sơ của tôi. Nó cũng hoạt động trong ISE bằng cách sử dụng F8/ Run Selection.


16

Có thể tôi đang thiếu một cái gì đó ở đây ... nhưng nếu bạn muốn thư mục làm việc hiện tại, bạn chỉ có thể sử dụng cái này: (Get-Location).Pathcho một chuỗi hoặc Get-Locationcho một đối tượng.

Trừ khi bạn đang đề cập đến một cái gì đó như thế này, mà tôi hiểu sau khi đọc lại câu hỏi.

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

16
Điều này có được vị trí hiện tại nơi người dùng đang chạy tập lệnh . Không phải vị trí của tập tin kịch bản .
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 Lưu nó và chạy nó từ một thư mục khác. Bạn sẽ chỉ đường dẫn đến kịch bản.
Sean C.

Đó là một chức năng tốt và làm những gì tôi cần, nhưng làm cách nào để chia sẻ và sử dụng nó trong tất cả các tập lệnh của tôi? Đó là vấn đề về gà và trứng: Tôi muốn sử dụng một chức năng để tìm ra vị trí hiện tại của mình, nhưng tôi cần vị trí của mình để tải chức năng.
Aaron Jensen

2
LƯU Ý: Việc gọi hàm này phải ở mức cao nhất của tập lệnh của bạn, nếu nó được lồng trong một hàm khác, thì bạn phải thay đổi tham số "-Scope" để chỉ định mức độ sâu của ngăn xếp cuộc gọi.
kenny

11

Rất giống với câu trả lời đã được đăng, nhưng đường ống có vẻ giống PowerShell hơn:

$PSCommandPath | Split-Path -Parent

11

Tôi sử dụng biến tự động $ExecutionContext . Nó sẽ hoạt động từ PowerShell 2 trở lên.

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

$ ExecutContext Chứa một đối tượng EngineIntrinsics đại diện cho bối cảnh thực thi của máy chủ Windows PowerShell. Bạn có thể sử dụng biến này để tìm các đối tượng thực thi có sẵn cho lệnh ghép ngắn.


1
Đây là người duy nhất nó làm việc cho tôi, cố gắng cung cấp năng lượng từ STDIN.
Sebastian

Giải pháp này cũng hoạt động đúng khi bạn ở trong bối cảnh đường dẫn UNC.
dùng2030503

1
Đây rõ ràng là chọn thư mục làm việc - chứ không phải là nơi tập lệnh được đặt?
monojohnny

@monojohnny Có, đây cơ bản là thư mục làm việc hiện tại và sẽ không hoạt động khi gọi một tập lệnh từ một vị trí khác.
soái ca

9

Tôi phải mất một thời gian để phát triển một cái gì đó đã đưa ra câu trả lời được chấp nhận và biến nó thành một chức năng mạnh mẽ.

Tôi không chắc chắn về người khác, nhưng tôi làm việc trong môi trường có máy trên cả PowerShell phiên bản 2 và 3, vì vậy tôi cần phải xử lý cả hai. Các chức năng sau đây cung cấp một dự phòng duyên dáng:

Function Get-PSScriptRoot
{
    $ScriptRoot = ""

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

    Write-Output $ScriptRoot
}

Điều đó cũng có nghĩa là hàm này đề cập đến phạm vi Script chứ không phải phạm vi của cha mẹ như được Michael Sorens nêu ra trong một trong các bài đăng trên blog của mình .


Cảm ơn! "Tập lệnh $:" là những gì tôi cần để làm cho nó hoạt động trong Windows PowerShell ISE.
Kirk Liêmohn

Cám ơn vì cái này. Đây là người duy nhất làm việc cho tôi. Tôi thường phải đưa CD vào thư mục mà tập lệnh nằm trong trước khi những thứ như Get-location hoạt động với tôi. Tôi muốn biết lý do tại sao PowerShell không tự động cập nhật thư mục.
Zain

7

Tôi cần phải biết tên tập lệnh và nó được thực thi từ đâu.

Tiền tố "$ global:" cho cấu trúc MyInvocation trả về đường dẫn và tên tập lệnh đầy đủ khi được gọi từ cả tập lệnh chính và dòng chính của tệp thư viện .PSM1 đã nhập. Nó cũng hoạt động từ bên trong một chức năng trong một thư viện nhập khẩu.

Sau nhiều lần loay hoay, tôi quyết định sử dụng $ global: MyInvocation.InvocationName. Nó hoạt động đáng tin cậy với việc khởi chạy CMD, Run With Powershell và ISE. Cả hai địa phương và UNC ra mắt trả lại đường dẫn chính xác.


4
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) đã hoạt động hoàn hảo nhờ. Các giải pháp khác trả về đường dẫn của ứng dụng gọi điện.
Dynamiclynk

1
Lưu ý nhỏ: trong ISE gọi chức năng này bằng F8 / Run Selection sẽ kích hoạt một ParameterArgumentValidationErrorNullNotAllowedngoại lệ.
đập vào

5

Tôi luôn sử dụng đoạn mã nhỏ này hoạt động cho PowerShell và ISE theo cùng một cách:

# 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

Tôi thấy rằng các giải pháp cũ hơn được đăng ở đây không hoạt động với tôi trên PowerShell V5. Tôi nghĩ ra cái này:

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

Bạn cũng có thể xem xét split-path -parent $psISE.CurrentFile.Fullpathnếu bất kỳ phương pháp nào khác không thành công. Cụ thể, nếu bạn chạy một tệp để tải một loạt các chức năng và sau đó thực thi các chức năng đó với trình bao ISE (hoặc nếu bạn đã chọn chạy), có vẻ như Get-Script-Directorychức năng như trên không hoạt động.


3
$PSCommandPathsẽ hoạt động trong ISE miễn là bạn lưu tập lệnh trước và thực thi toàn bộ tệp. Mặt khác, bạn không thực sự thực thi một tập lệnh; bạn chỉ "dán" các lệnh vào shell.
Zenexer

@Zenexer Tôi nghĩ đó là mục tiêu của tôi lúc đó. Mặc dù nếu mục tiêu của tôi không khớp với mục tiêu ban đầu, điều này có thể không quá hữu ích ngoại trừ các nhân viên Google thỉnh thoảng ...

2

Sử dụng các mẩu từ tất cả các câu trả lời và các ý kiến, tôi kết hợp chúng cho bất kỳ ai nhìn thấy câu hỏi này trong tương lai. Nó bao gồm tất cả các tình huống được liệt kê trong các câu trả lời khác

    # 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.