Keywords: PowerShell | script path | compatibility | $PSCommandPath | $MyInvocation
Abstract: This article delves into various methods for obtaining the path of the currently executing PowerShell script, focusing on the behavioral differences of key variables such as $PSCommandPath, $MyInvocation.ScriptName, $MyInvocation.MyCommand.Name, and $MyInvocation.MyCommand.Definition. Through detailed code examples and scenario testing, it reveals compatibility issues across different PowerShell versions (particularly 1.0 to 5.0) and provides practical backward-compatible solutions. The article also discusses special cases for retrieving script paths within functions and techniques for extracting filenames rather than full paths, offering comprehensive references for developers handling script path issues in real-world projects.
Introduction and Problem Context
In PowerShell script development, obtaining the path of the currently executing script is a common yet nuanced requirement. For instance, when launching a script via powershell.exe .\myfile.ps1, developers might want to retrieve path information like \"myfile.ps1\" or \".\\myfile.ps1\". This issue is particularly complex in early versions like PowerShell 1.0 due to the lack of a unified solution.
Core Methods and Variable Analysis
PowerShell provides several automatic variables to access script information, but their behavior varies by context and version. The most commonly used variables include:
$PSCommandPath Variable
This is the recommended method in PowerShell 3.0 and later. $PSCommandPath directly returns the full path of the currently executing script, regardless of whether it's called inside a function. For example:
# Example: Direct use of $PSCommandPath
Write-Host \"Script path: \" $PSCommandPath
# Output: C:\\Test\\test.ps1
However, this variable does not exist in versions prior to PowerShell 3.0, requiring compatibility handling.
$MyInvocation-Related Variables
For older PowerShell versions, the $MyInvocation object offers multiple properties with complex behaviors:
$MyInvocation.ScriptName: May be empty when called outside a function but returns the script path inside a function.$MyInvocation.MyCommand.Name: Returns the name of the current command. At the script top-level, it's the script filename; inside a function, it's the function name.$MyInvocation.MyCommand.Definition: Returns the command's definition. For scripts, it's typically the path; for functions, it's the function body code.$MyInvocation.PSCommandPath: Similar to$PSCommandPath, but with similar context limitations.
Compatibility Solutions
To use $PSCommandPath in versions before PowerShell 3.0, a shim code can be inserted:
if ($PSCommandPath -eq $null) {
function GetPSCommandPath() {
return $MyInvocation.PSCommandPath;
}
$PSCommandPath = GetPSCommandPath
}
This code checks if $PSCommandPath exists and assigns it via a function if not. Note variable scoping rules: if the shim is placed inside a function, the variable is limited to that function.
Detailed Testing and Comparison
To fully understand these variables' behaviors, we designed a test script:
function PSCommandPath() { return $PSCommandPath }
function ScriptName() { return $MyInvocation.ScriptName }
function MyCommandName() { return $MyInvocation.MyCommand.Name }
function MyCommandDefinition() {
# Note: This function returns the definition content, not execution results
return $MyInvocation.MyCommand.Definition
}
function MyInvocationPSCommandPath() { return $MyInvocation.PSCommandPath }
Write-Host \"\"
Write-Host \"PSVersion: \" $($PSVersionTable.PSVersion)
Write-Host \"\"
Write-Host \"`$PSCommandPath:\"
Write-Host \" * Direct: \" $PSCommandPath
Write-Host \" * Function: \" $(PSCommandPath)
Write-Host \"\"
Write-Host \"`$MyInvocation.ScriptName:\"
Write-Host \" * Direct: \" $($MyInvocation.ScriptName)
Write-Host \" * Function: \" $(ScriptName)
Write-Host \"\"
Write-Host \"`$MyInvocation.MyCommand.Name:\"
Write-Host \" * Direct: \" $($MyInvocation.MyCommand.Name)
Write-Host \" * Function: \" $(MyCommandName)
Write-Host \"\"
Write-Host \"`$MyInvocation.MyCommand.Definition:\"
Write-Host \" * Direct: \" $($MyInvocation.MyCommand.Definition)
Write-Host \" * Function: \" $(MyCommandDefinition)
Write-Host \"\"
Write-Host \"`$MyInvocation.PSCommandPath:\"
Write-Host \" * Direct: \" $($MyInvocation.PSCommandPath)
Write-Host \" * Function: \" $(MyInvocationPSCommandPath)
Write-Host \"\"
Example output when executing C:\\Test\\test.ps1 in PowerShell 5.1:
PSVersion: 5.1.19035.1
$PSCommandPath:
* Direct: C:\\Test\\test.ps1
* Function: C:\\Test\\test.ps1
$MyInvocation.ScriptName:
* Direct:
* Function: C:\\Test\\test.ps1
$MyInvocation.MyCommand.Name:
* Direct: test.ps1
* Function: MyCommandName
$MyInvocation.MyCommand.Definition:
* Direct: C:\\Test\\test.ps1
* Function:
# Note: This function returns the definition content, not execution results
return $MyInvocation.MyCommand.Definition;
$MyInvocation.PSCommandPath:
* Direct:
* Function: C:\\Test\\test.ps1
Key observations:
- Script executed from
C:\\, but actual path isC:\\Test\\test.ps1. - No method directly retrieves the passed invocation path (e.g.,
.\\Test\\test.ps1). $PSCommandPathis the most reliable method but only works in PowerShell 3+.- For earlier versions, no single method works both inside and outside functions.
Advanced Scenarios and Supplementary Techniques
Based on supplementary answers, note the following cases:
Retrieving Script Name Inside Functions
Inside a function, $MyInvocation.MyCommand.Name returns the function name, not the script name:
function test {
$MyInvocation.MyCommand.Name # Returns \"test\", not the script filename
}
In this case, use $MyInvocation.ScriptName to get the script path.
Extracting Filename Without Full Path
If only the script filename (without path) is needed, use:
$ScriptName = $MyInvocation.MyCommand.Name # Returns e.g., \"test.ps1\"
Or combine with Split-Path:
Split-Path $MyInvocation.PSCommandPath -Leaf # Extracts filename from path
Path Validation and Fallbacks
$MyInvocation.MyCommand.Definition typically returns a valid path but may not be the original user-input path. In complex scenarios, validate by combining multiple variables.
Summary and Best Practices
When obtaining the current PowerShell script path, recommend the following strategies:
- If targeting PowerShell 3.0 or later, use
$PSCommandPathdirectly. - For backward compatibility, insert shim code to emulate
$PSCommandPath. - Inside functions, prefer
$MyInvocation.ScriptNamefor script paths. - For filenames only, use
$MyInvocation.MyCommand.NameorSplit-Path -Leaf. - Always test code behavior in target PowerShell versions to avoid assumptions.
By understanding these variables' nuances and version differences, developers can write more robust and portable PowerShell scripts.