วิธีที่ดีที่สุดในการระบุตำแหน่งของสคริปต์ PowerShell ปัจจุบันคืออะไร


530

เมื่อใดก็ตามที่ฉันต้องการอ้างอิงโมดูลทั่วไปหรือสคริปต์ฉันต้องการใช้พา ธ ที่สัมพันธ์กับไฟล์สคริปต์ปัจจุบัน ด้วยวิธีนี้สคริปต์ของฉันสามารถค้นหาสคริปต์อื่น ๆ ในห้องสมุดได้เสมอ

ดังนั้นวิธีที่ดีที่สุดมาตรฐานในการกำหนดไดเรกทอรีของสคริปต์ปัจจุบันคืออะไร? ขณะนี้ฉันกำลังทำ:

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

ฉันรู้ในโมดูล (.psm1) คุณสามารถใช้$PSScriptRootเพื่อรับข้อมูลนี้ แต่นั่นไม่ได้รับการตั้งค่าในสคริปต์ปกติ (เช่นไฟล์. ps1)

วิธีที่ได้รับมาตรฐานของตำแหน่งไฟล์สคริปต์ PowerShell ปัจจุบันคืออะไร


คำตอบ:


865

PowerShell 3+

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

PowerShell 2

ก่อน PowerShell 3 ไม่มีวิธีที่ดีไปกว่าการค้นหา MyInvocation.MyCommand.Definitionคุณสมบัติสำหรับสคริปต์ทั่วไป ฉันมีบรรทัดต่อไปนี้ที่ด้านบนสุดของสคริปต์ PowerShell ทุกตัวที่ฉันมี:

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

1
อะไรSplit-Pathใช้สำหรับที่นี่
CMCDragonkai

7
Split-Pathใช้กับ-Parentพารามิเตอร์เพื่อส่งคืนไดเร็กทอรีปัจจุบันโดยไม่มีชื่อสคริปต์ที่กำลังเรียกใช้งานในปัจจุบัน
hjoelr

5
หมายเหตุ: ด้วย PowerShell บน Linux / macOS สคริปต์ของคุณจะต้องมีนามสกุล. ps1 สำหรับ PSScriptRoot / MyInvocation เป็นต้นเพื่อให้มีประชากร ดูรายงานข้อผิดพลาดได้ที่นี่: github.com/PowerShell/PowerShell/issues/4217
Dave Wood

3
เล่นลิ้นด้วยที่น่าสนใจนอกเหนือ: v2- ใกล้เคียงของ$PSScriptRootคือ ( Split-Path -Parentนำไปใช้กับ) $MyInvocation.MyCommand.Path, ไม่$MyInvocation.MyCommand.Definition, แม้ว่าในขอบเขตระดับบนสุดของสคริปต์ที่พวกเขาทำงานเหมือนกัน (ซึ่งเป็นสถานที่ที่เหมาะสมเท่านั้นที่โทรจากเพื่อวัตถุประสงค์นี้) เมื่อถูกเรียกภายในฟังก์ชั่นหรือบล็อกสคริปต์อดีตจะส่งคืนสตริงว่างขณะที่หลังจะส่งคืนคำจำกัดความของฟังก์ชั่นของ / สคริปต์บล็อกเป็นสตริง (ชิ้นส่วนของรหัสที่มา PowerShell)
mklement0

62

ถ้าคุณกำลังสร้างโมดูล V2 $PSScriptRootคุณสามารถใช้ตัวแปรอัตโนมัติที่เรียกว่า

จาก PS> ช่วยเหลือ Automatic_variable

$ PSScriptRoot
       มีไดเร็กทอรีที่จะเรียกใช้งานโมดูลสคริปต์
       ตัวแปรนี้อนุญาตให้สคริปต์ใช้เส้นทางโมดูลเพื่อเข้าถึงอื่น ๆ
       ทรัพยากร

16
นี่คือสิ่งที่คุณต้องการใน 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
เพิ่งทดสอบ $ PSScriptRoot และทำงานตามที่คาดไว้ อย่างไรก็ตามมันจะให้สตริงว่างถ้าคุณเรียกใช้ที่บรรทัดคำสั่ง มันจะให้ผลลัพธ์ถ้าคุณใช้ในสคริปต์และสคริปต์จะถูกดำเนินการ นั่นคือสิ่งที่มีไว้สำหรับ .....
Farrukh Waheed

4
ฉันสับสน คำตอบนี้บอกว่าจะใช้ PSScriptRoot สำหรับ V2 คำตอบอีกข้อหนึ่งกล่าวว่า PSScriptRoot สำหรับ V3 + และใช้บางอย่างที่แตกต่างสำหรับ v2

6
@user $ PSScriptRoot ใน v2 ใช้สำหรับโมดูลเท่านั้นหากคุณกำลังเขียนสคริปต์ 'ปกติ' ไม่ได้อยู่ในโมดูลคุณต้อง $ MyInvocation.MyCommand.Definition ดูคำตอบยอดนิยม
yzorg

1
@ Lofful ฉันพูดใน "v2" มันถูกกำหนดไว้สำหรับโมดูลเท่านั้น คุณกำลังบอกว่ามันถูกกำหนดนอกโมดูลใน v3 ฉันคิดว่าเรากำลังพูดในสิ่งเดียวกัน :)
yzorg

35

สำหรับ 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.

ฟังก์ชั่นคือ:

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

20
ยิ่งไปกว่านั้นใช้ $ PSScriptRoot มันเป็นไดเรกทอรีของไฟล์ / โมดูลปัจจุบัน
Aaron Jensen

2
คำสั่งนี้รวมถึงชื่อไฟล์ของสคริปต์ซึ่งทำให้ฉันผิดหวังจนฉันรู้ว่า เมื่อคุณต้องการเส้นทางคุณอาจไม่ต้องการชื่อสคริปต์ในนั้น อย่างน้อยฉันก็ไม่สามารถคิดถึงเหตุผลที่คุณต้องการได้ $ PSScriptRoot ไม่รวมชื่อไฟล์ (รวบรวมจากคำตอบอื่น ๆ )
YetAnotherRandomUser

$ PSScriptRoot ว่างเปล่าจากสคริปต์ PS1 ปกติ $ PSCommandPath ใช้งานได้ พฤติกรรมของทั้งคู่คาดว่าจะเกิดขึ้นตามคำอธิบายที่ระบุในโพสต์อื่น สามารถเพียงแค่ใช้ [IO.Path] :: GetDirectoryName ($ PSCommandPath) เพื่อรับไดเรกทอรีสคริปต์โดยไม่มีชื่อไฟล์
มลาย

19

สำหรับ PowerShell 3+

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

ฉันได้วางฟังก์ชันนี้ไว้ในโปรไฟล์ของฉัน มันใช้งานได้ใน ISE โดยใช้F8/ Run Selection เช่นกัน


16

บางทีฉันอาจขาดบางสิ่งบางอย่างที่นี่ ... แต่ถ้าคุณต้องการไดเรกทอรีการทำงานปัจจุบันคุณสามารถใช้สิ่งนี้: (Get-Location).PathสำหรับสตริงหรือGet-Locationวัตถุ

นอกจากว่าคุณจะพูดถึงสิ่งนี้ซึ่งฉันเข้าใจหลังจากอ่านคำถามอีกครั้ง

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

16
นี้ได้รับตำแหน่งปัจจุบันที่ผู้ใช้จะใช้สคริปต์ ไม่ระบุตำแหน่งของไฟล์สคริปต์เอง
แอรอนเซ่น

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 บันทึกและเรียกใช้จากไดเรกทอรีอื่น คุณจะแสดงเส้นทางไปยังสคริปต์
Sean C.

นั่นเป็นฟังก์ชั่นที่ดีและทำในสิ่งที่ฉันต้องการ แต่ฉันจะแบ่งปันและใช้ในสคริปต์ทั้งหมดของฉันได้อย่างไร มันเป็นปัญหาไก่และไข่: ฉันต้องการใช้ฟังก์ชั่นเพื่อค้นหาตำแหน่งปัจจุบันของฉัน แต่ฉันต้องการตำแหน่งของฉันเพื่อโหลดฟังก์ชัน
แอรอนเซ่น

2
หมายเหตุ: การเรียกใช้ฟังก์ชันนี้จะต้องอยู่ที่ระดับสูงสุดของสคริปต์หากมีการซ้อนกันภายในฟังก์ชั่นอื่นคุณต้องเปลี่ยนพารามิเตอร์ "-Scope" เพื่อกำหนดความลึกของสแต็กการโทรของคุณ
kenny

11

คล้ายกับคำตอบที่โพสต์แล้ว แต่การวางท่อดูเหมือนกับ PowerShell มากขึ้น:

$PSCommandPath | Split-Path -Parent

11

ผมใช้ตัวแปรอัตโนมัติ $ExecutionContextมันจะทำงานจาก PowerShell 2 และใหม่กว่า

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

$ ExecutionContext มีวัตถุ EngineIntrinsics ที่แสดงถึงบริบทการดำเนินการของโฮสต์ Windows PowerShell คุณสามารถใช้ตัวแปรนี้เพื่อค้นหาวัตถุการดำเนินการที่มีอยู่ใน cmdlets


1
นี่เป็นสิ่งเดียวที่ใช้ได้ผลกับฉันพยายามดึงพลังจาก STDIN
เซบาสเตียน

โซลูชันนี้ยังทำงานอย่างถูกต้องเมื่อคุณอยู่ในบริบทเส้นทาง UNC
user2030503

1
เห็นได้ชัดว่านี่คือการรวบรวมไดเรกทอรีทำงาน - แทนที่จะเป็นที่ตั้งของสคริปต์?
monojohnny

@monojohnny ใช่นี่เป็นไดเรกทอรีการทำงานปัจจุบันและจะไม่ทำงานเมื่อเรียกใช้สคริปต์จากที่อื่น
marsze

9

ฉันใช้เวลาสักครู่ในการพัฒนาบางสิ่งบางอย่างที่ใช้คำตอบที่ได้รับการยอมรับและเปลี่ยนเป็นฟังก์ชั่นที่แข็งแกร่ง

ฉันไม่แน่ใจเกี่ยวกับผู้อื่น แต่ฉันทำงานในสภาพแวดล้อมที่มีเครื่องจักรทั้ง PowerShell เวอร์ชัน 2 และ 3 ดังนั้นฉันจึงต้องจัดการทั้งสองอย่าง ฟังก์ชั่นต่อไปนี้นำเสนอทางเลือกที่สวยงาม

Function Get-PSScriptRoot
{
    $ScriptRoot = ""

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

    Write-Output $ScriptRoot
}

ก็หมายความว่าฟังก์ชั่นหมายถึงขอบเขตสคริปต์มากกว่าขอบเขตของผู้ปกครองตามที่ระบุไว้โดยไมเคิล Sorens หนึ่งในบล็อกโพสต์ของเขา


ขอบคุณ! "$ script:" เป็นสิ่งที่ฉันต้องการเพื่อให้ทำงานใน Windows PowerShell ISE
Kirk Liemohn

ขอบคุณสำหรับสิ่งนี้. นี่เป็นสิ่งเดียวที่เหมาะกับฉัน ฉันมักจะต้องซีดีลงในไดเรกทอรีสคริปต์ก่อนสิ่งต่าง ๆ เช่น Get-location ทำงานให้ฉัน ฉันสนใจที่จะทราบว่าเหตุใด PowerShell จึงไม่อัปเดตไดเรกทอรีโดยอัตโนมัติ
Zain

7

ฉันจำเป็นต้องรู้ชื่อสคริปต์และตำแหน่งที่เรียกใช้งาน

คำนำหน้า "$ global:" ไปยังโครงสร้าง MyInvocation ส่งคืนพา ธ เต็มและชื่อสคริปต์เมื่อถูกเรียกจากทั้งสคริปต์หลักและบรรทัดหลักของไฟล์ไลบรารี. PSM1 ที่นำเข้า มันยังทำงานได้จากภายในฟังก์ชั่นในห้องสมุดที่นำเข้า

หลังจากเล่นซอไปเรื่อย ๆ ฉันตัดสินใจใช้ $ global: MyInvocation.InvocationName มันทำงานได้อย่างน่าเชื่อถือด้วยการเปิดตัว CMD, Run With Powershell และ ISE ทั้งการเปิดตัวในท้องถิ่นและ UNC กลับเส้นทางที่ถูกต้อง


4
แยกเส้นทาง -Path $ ($ global: MyInvocation.MyCommand.Path)ทำงานได้อย่างสมบูรณ์แบบขอบคุณ โซลูชันอื่น ๆ ส่งคืนพา ธ ของแอปพลิเคชันการโทร
dynamiclynk

1
หมายเหตุเล็กน้อย: ใน ISE การเรียกใช้ฟังก์ชันนี้โดยใช้การเลือก F8 / Run จะทำให้เกิดParameterArgumentValidationErrorNullNotAllowedข้อยกเว้น
ฝาย

5

ฉันมักจะใช้ตัวอย่างเล็ก ๆ น้อย ๆ นี้ซึ่งใช้งานได้กับ PowerShell และ ISEในลักษณะเดียวกัน:

# 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

ฉันพบว่าโซลูชันเก่าที่โพสต์ที่นี่ไม่ทำงานสำหรับฉันใน PowerShell V5 ฉันมากับสิ่งนี้:

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

คุณอาจพิจารณา split-path -parent $psISE.CurrentFile.Fullpathว่าวิธีอื่นใดล้มเหลว โดยเฉพาะอย่างยิ่งหากคุณเรียกใช้ไฟล์เพื่อโหลดฟังก์ชั่นมากมายแล้วดำเนินการฟังก์ชั่นเหล่านั้นด้วย - ในเชลล์ ISE (หรือถ้าคุณเลือกรัน) ดูเหมือนว่าGet-Script-Directoryฟังก์ชั่นดังกล่าวจะไม่ทำงาน


3
$PSCommandPathจะทำงานใน ISE ตราบใดที่คุณบันทึกสคริปต์ก่อนและเรียกใช้ไฟล์ทั้งหมด มิฉะนั้นคุณจะไม่ได้เรียกใช้งานสคริปต์จริงๆ คุณเพียงแค่ "วาง" คำสั่งลงในเชลล์
Zenexer

@ Zenexer ฉันคิดว่านั่นเป็นเป้าหมายของฉันในเวลานั้น แม้ว่าหากเป้าหมายของฉันไม่ตรงกับเป้าหมายดั้งเดิมสิ่งนี้อาจไม่เป็นประโยชน์มากนักยกเว้นผู้ใช้ Google เป็นครั้งคราว ...

2

ฉันใช้ส่วนนี้จากคำตอบและความคิดเห็นทั้งหมดนี้เพื่อทุกคนที่เห็นคำถามนี้ในอนาคต ครอบคลุมทุกสถานการณ์ที่ระบุไว้ในคำตอบอื่น ๆ

    # 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.