¿Cuál es la mejor manera de determinar la ubicación del script actual de PowerShell?

530

Siempre que necesito hacer referencia a un módulo o script común, me gusta usar rutas relativas al archivo de script actual. De esa manera, mi script siempre puede encontrar otros scripts en la biblioteca.

Entonces, ¿cuál es la mejor forma estándar de determinar el directorio del script actual? Actualmente estoy haciendo:

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

Sé que en los módulos (.psm1) puede usar $PSScriptRootpara obtener esta información, pero eso no se configura en scripts normales (es decir, archivos .ps1).

¿Cuál es la forma canónica de obtener la ubicación actual del archivo de script de PowerShell?

Aaron Jensen
fuente

Respuestas:

865

PowerShell 3+

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

PowerShell 2

Antes de PowerShell 3, no había una mejor manera que consultar la MyInvocation.MyCommand.Definitionpropiedad en busca de scripts generales. Tenía la siguiente línea en la parte superior de esencialmente cada script de PowerShell que tenía:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
JaredPar
fuente
1
¿Para qué Split-Pathsirve aquí?
CMCDragonkai
77
Split-Pathse usa con el -Parentparámetro para devolver el directorio actual sin el nombre del script que se está ejecutando actualmente.
hjoelr
55
Nota: con PowerShell en Linux / macOS, su script debe tener una extensión .ps1 para que se complete PSScriptRoot / MyInvocation, etc. Ver informe de error aquí: github.com/PowerShell/PowerShell/issues/4217
Dave Wood
3
Quibble con un lado potencialmente interesante: la aproximación más cercana v2- $PSScriptRootes ( Split-Path -Parentaplicada a) $MyInvocation.MyCommand.Path, no $MyInvocation.MyCommand.Definition, aunque en el ámbito de nivel superior de un script se comportan igual (que es el único lugar sensible desde el que llamar para este propósito). Cuando se llama dentro de una función o bloque de script , el primero devuelve la cadena vacía, mientras que el segundo devuelve la definición del bloque de función / script del cuerpo de la función como una cadena (una parte del código fuente de PowerShell).
mklement0
62

Si está creando un módulo V2, puede usar una variable automática llamada $PSScriptRoot.

Desde PS> Ayuda automatic_variable

$ PSScriptRoot
       Contiene el directorio desde el que se ejecuta el módulo de script.
       Esta variable permite que los scripts utilicen la ruta del módulo para acceder a otros
       recursos
Andy Schneider
fuente
16
Esto es lo que necesita en 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
Acabo de probar $ PSScriptRoot y funciona como se esperaba. Sin embargo, le daría una cadena vacía si la ejecuta en la línea de comando. Solo le daría resultado si se usa en un script y se ejecuta el script. Para eso está destinado .....
Farrukh Waheed
44
Estoy confundido. Esta respuesta dice que use PSScriptRoot para V2. Otra respuesta dice que PSScriptRoot es para V3 +, y para usar algo diferente para v2.
66
@user $ PSScriptRoot en v2 es solo para módulos , si está escribiendo scripts 'normales' que no están en un módulo, necesita $ MyInvocation.MyCommand.Definition, consulte la respuesta superior.
yzorg
1
@Lofful Dije en "v2" que solo estaba definido para módulos. Estás diciendo que está definido fuera de los módulos en v3. Creo que estamos diciendo lo mismo. :)
yzorg
35

Para 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 función es entonces:

function Get-ScriptDirectory {
    Split-Path -Parent $PSCommandPath
}
CodeMonkeyKing
fuente
20
Aún mejor, use $ PSScriptRoot. Es el directorio del archivo / módulo actual.
Aaron Jensen
2
Este comando incluye el nombre de archivo de la secuencia de comandos, lo que me arrojó hasta que me di cuenta de eso. Cuando desee la ruta, probablemente tampoco desee el nombre del script allí. Al menos, no puedo pensar en una razón por la que quieras eso. $ PSScriptRoot no incluye el nombre de archivo (extraído de otras respuestas).
YetAnotherRandomUser
$ PSScriptRoot está vacío de un script PS1 normal. Sin embargo, $ PSCommandPath funciona. El comportamiento de ambos se espera según las descripciones dadas en otras publicaciones. También puede usar [IO.Path] :: GetDirectoryName ($ PSCommandPath) para obtener el directorio del script sin el nombre del archivo.
fracasó el
19

Para PowerShell 3+

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

He puesto esta función en mi perfil. Funciona en ISE usando F8/ Ejecutar selección también.

nickkzl
fuente
16

Tal vez me falta algo aquí ... pero si quieres el directorio de trabajo actual, puedes usar esto: (Get-Location).Pathpara una cadena o Get-Locationpara un objeto.

A menos que se refiera a algo como esto, lo entiendo después de leer la pregunta nuevamente.

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}
Sean C.
fuente
16
Esto obtiene la ubicación actual donde el usuario ejecuta el script . No es la ubicación del archivo de script en .
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 Guarde eso y ejecútelo desde un directorio diferente. Mostrará la ruta al guión.
Sean C.
Esa es una buena función, y hace lo que necesito, pero ¿cómo la comparto y la uso en todos mis scripts? Es un problema de huevo y gallina: me gustaría usar una función para averiguar mi ubicación actual, pero necesito mi ubicación para cargar la función.
Aaron Jensen el
2
NOTA: La invocación de esta función debe estar en el nivel superior de su secuencia de comandos, si está anidada dentro de otra función, debe cambiar el parámetro "-Scope" para designar qué tan profundo está en la pila de llamadas.
kenny
11

Muy similar a las respuestas ya publicadas, pero las tuberías parecen más como PowerShell:

$PSCommandPath | Split-Path -Parent
CPAR
fuente
11

Yo uso la variable automática $ExecutionContext . Funcionará desde PowerShell 2 y versiones posteriores.

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

$ ExecutionContext Contiene un objeto EngineIntrinsics que representa el contexto de ejecución del host de Windows PowerShell. Puede usar esta variable para buscar los objetos de ejecución que están disponibles para los cmdlets.

Viggos
fuente
1
Este es el único que funcionó para mí, tratando de alimentar PowerShell de STDIN.
Sebastián
Esta solución también funciona correctamente cuando se encuentra en un contexto de ruta UNC.
user2030503
1
Aparentemente, esto está recogiendo el directorio de trabajo, en lugar de dónde se encuentra el script.
monojohnny
@monojohnny Sí, este es básicamente el directorio de trabajo actual y no funcionará cuando se llame a un script desde otra ubicación.
marsze
9

Me llevó un tiempo desarrollar algo que tomara la respuesta aceptada y la convirtiera en una función robusta.

No estoy seguro acerca de los demás, pero trabajo en un entorno con máquinas en PowerShell versión 2 y 3, por lo que necesitaba manejar ambos. La siguiente función ofrece un retroceso elegante:

Function Get-PSScriptRoot
{
    $ScriptRoot = ""

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

    Write-Output $ScriptRoot
}

También significa que la función se refiere al alcance del Script en lugar del alcance de los padres como lo describe Michael Sorens en una de sus publicaciones de blog .

Bruno
fuente
¡Gracias! El "$ script:" era lo que necesitaba para que esto funcionara en Windows PowerShell ISE.
Kirk Liemohn
Gracias por esto. Este fue el único que funciona para mí. Por lo general, tengo que CD en el directorio en el que se encuentra el script antes de que cosas como Get-location funcionen para mí. Me interesaría saber por qué PowerShell no actualiza automáticamente el directorio.
Zain
7

Necesitaba saber el nombre del script y desde dónde se está ejecutando.

El prefijo "$ global:" a la estructura MyInvocation devuelve la ruta completa y el nombre del script cuando se llama desde el script principal y la línea principal de un archivo de biblioteca .PSM1 importado. También funciona desde una función en una biblioteca importada.

Después de mucho jugar, decidí usar $ global: MyInvocation.InvocationName. Funciona de manera confiable con el lanzamiento de CMD, Run With Powershell e ISE. Tanto los lanzamientos locales como UNC devuelven la ruta correcta.

Bruce Gavin
fuente
44
Split-Path -Path $ ($ global: MyInvocation.MyCommand.Path) funcionó perfectamente, gracias. Las otras soluciones devolvieron la ruta de la aplicación de llamada.
dynamiclynk
1
Nota trivial: en ISE llamando a esta función usando F8 / Run Selection se activará una ParameterArgumentValidationErrorNullNotAllowedexcepción.
vertedero
5

Siempre uso este pequeño fragmento que funciona para PowerShell e ISE de la misma manera:

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

Descubrí que las soluciones más antiguas publicadas aquí no me funcionaron en PowerShell V5. Se me ocurrió esto:

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"
Quantium
fuente
2

También puede considerar split-path -parent $psISE.CurrentFile.Fullpathsi alguno de los otros métodos falla. En particular, si ejecuta un archivo para cargar un montón de funciones y luego ejecuta esas funciones dentro del shell ISE (o si seleccionó la ejecución), parece que la Get-Script-Directoryfunción como la anterior no funciona.

fastboxster
fuente
3
$PSCommandPathfuncionará en el ISE siempre que guarde primero el script y ejecute todo el archivo. De lo contrario, en realidad no estás ejecutando un script; solo estás "pegando" comandos en el shell.
Zenexer
@ Zenexer Creo que ese era mi objetivo en ese momento. Aunque si mi objetivo no coincidía con el original, esto podría no ser demasiado servicial, excepto a los empleados de Google de vez en cuando ...
2

Utilizando las piezas de todas estas respuestas y los comentarios, preparé esto para cualquiera que vea esta pregunta en el futuro. Cubre todas las situaciones enumeradas en las otras respuestas.

    # 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
    }
Cachondo
fuente
-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
fuente