Qual é a melhor maneira de determinar o local do script atual do PowerShell?

530

Sempre que preciso fazer referência a um módulo ou script comum, gosto de usar caminhos relativos ao arquivo de script atual. Dessa forma, meu script sempre pode encontrar outros scripts na biblioteca.

Então, qual é a melhor maneira padrão de determinar o diretório do script atual? Atualmente, estou fazendo:

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

Eu sei nos módulos (.psm1) que você pode usar $PSScriptRootpara obter essas informações, mas isso não é definido em scripts regulares (arquivos .ps1).

Qual é a maneira canônica de obter a localização atual do arquivo de script do PowerShell?

Aaron Jensen
fonte

Respostas:

865

PowerShell 3+

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

PowerShell 2

Antes do PowerShell 3, não havia uma maneira melhor do que consultar a MyInvocation.MyCommand.Definitionpropriedade para obter scripts gerais. Eu tinha a seguinte linha no topo de todos os scripts do PowerShell que eu tinha:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
JaredPar
fonte
1
O que é Split-Pathusado aqui?
CMDragonkai
7
Split-Pathé usado com o -Parentparâmetro para retornar o diretório atual sem o nome do script atualmente em execução.
hjoelr
5
Nota: com o PowerShell no Linux / macOS, seu script deve ter uma extensão .ps1 para que o PSScriptRoot / MyInvocation etc. seja preenchido. Veja o relatório de erros aqui: github.com/PowerShell/PowerShell/issues/4217
Dave Wood
3
Quibble com um aparte potencialmente interessante: quanto mais próxima é a v2 $PSScriptRoot- Split-Path -Parentaplicada ( aplicada a) $MyInvocation.MyCommand.Path, não $MyInvocation.MyCommand.Definition, embora no escopo de nível superior de um script eles se comportem da mesma forma (que é o único local sensato para chamar para esse fim). Quando chamado dentro de uma função ou bloco de script , o primeiro retorna a string vazia, enquanto o último retorna a definição do corpo da função / bloco de script como uma string (uma parte do código-fonte do PowerShell).
precisa
62

Se você estiver criando um módulo V2, poderá usar uma variável automática chamada $PSScriptRoot.

No PS> Ajuda automatic_variable

$ PSScriptRoot
       Contém o diretório a partir do qual o módulo de script está sendo executado.
       Essa variável permite que os scripts usem o caminho do módulo para acessar outros
       Recursos.
Andy Schneider
fonte
16
Isto é o que você precisa no 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 14/03
2
Acabei de testar o $ PSScriptRoot e funcionando conforme o esperado. No entanto, ele forneceria uma string vazia se você a executasse na linha de comando. Isso só daria resultado se usado em um script e o script for executado. Isso é o que se entende por .....
Farrukh Waheed
4
Estou confuso. Esta resposta diz para usar o PSScriptRoot para V2. Outra resposta diz que o PSScriptRoot é para a V3 + e para usar algo diferente para a v2.
6
@user $ PSScriptRoot na v2 é apenas para módulos , se você estiver escrevendo scripts 'normais' que não estão em um módulo, precisará de $ MyInvocation.MyCommand.Definition, consulte a resposta superior.
yzorg 16/01
1
@Lofful eu disse em "v2" que foi definido apenas para módulos. Você está dizendo que ele é definido fora dos módulos na v3. Acho que estamos dizendo a mesma coisa. :)
yzorg
35

Para o 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.

A função é então:

function Get-ScriptDirectory {
    Split-Path -Parent $PSCommandPath
}
CodeMonkeyKing
fonte
20
Melhor ainda, use $ PSScriptRoot. É o diretório do arquivo / módulo atual.
Aaron Jensen
2
Este comando inclui o nome do arquivo do script, o que me fez desconfiar até eu perceber isso. Quando você está querendo o caminho, provavelmente não quer o nome do script lá também. Pelo menos, não consigo pensar em uma razão para você querer isso. $ PSScriptRoot não inclui o nome do arquivo (obtido de outras respostas).
YetAnotherRandomUser
$ PSScriptRoot está vazio em um script PS1 comum. O $ PSCommandPath funciona, no entanto. O comportamento de ambos é esperado de acordo com as descrições fornecidas em outras postagens. Também pode usar [IO.Path] :: GetDirectoryName ($ PSCommandPath) para obter o diretório de scripts sem o nome do arquivo.
fracassou em
19

Para o PowerShell 3+

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

Eu coloquei essa função no meu perfil. Também funciona no ISE usando F8/ Run Selection.

nickkzl
fonte
16

Talvez esteja faltando alguma coisa aqui ... mas se você quiser o diretório de trabalho atual, você pode simplesmente usar isso: (Get-Location).Pathpara uma string ouGet-Location para um objeto.

A menos que você esteja se referindo a algo assim, o que eu entendo depois de ler a pergunta novamente.

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}
Sean C.
fonte
16
Isso obtém o local atual em que o usuário está executando o script . Não é o local do arquivo de script em si .
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 Salve isso e execute-o em um diretório diferente. Você mostrará o caminho para o script.
29511 Sean C.
Essa é uma boa função e faz o que eu preciso, mas como eu a compartilho e a uso em todos os meus scripts? É um problema de galinha e ovo: eu gostaria de usar uma função para descobrir minha localização atual, mas preciso da minha localização para carregar a função.
Aaron Jensen
2
NOTA: A invocação desta função deve estar no nível superior do seu script, se estiver aninhada em outra função, você precisará alterar o parâmetro "-Scope" para designar a profundidade da pilha de chamadas.
Kenny
11

Muito semelhante às respostas já postadas, mas a tubulação parece mais com o PowerShell:

$PSCommandPath | Split-Path -Parent
CPAR
fonte
11

Eu uso a variável automática $ExecutionContext . Ele funcionará no PowerShell 2 e posterior.

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

$ ExecutionContext Contém um objeto EngineIntrinsics que representa o contexto de execução do host do Windows PowerShell. Você pode usar essa variável para encontrar os objetos de execução disponíveis para os cmdlets.

Viggos
fonte
1
Este é o único que funcionou para mim, tentando alimentar o PowerShell do STDIN.
21717 Sebastian
Essa solução também funciona corretamente quando você está em um contexto de caminho UNC.
user2030503
1
Aparentemente, isso está pegando o diretório de trabalho - e não onde o script está localizado?
monojohnny
@monojohnny Sim, este é basicamente o diretório de trabalho atual e não funcionará ao chamar um script de outro local.
Marsze
9

Demorei um pouco para desenvolver algo que pegou a resposta aceita e a transformou em uma função robusta.

Não tenho certeza sobre os outros, mas trabalho em um ambiente com máquinas no PowerShell versão 2 e 3, portanto, precisava lidar com ambos. A função a seguir oferece um fallback 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
}

Isso também significa que a função se refere ao escopo do Script em vez do escopo dos pais, conforme descrito por Michael Sorens em uma de suas postagens no blog .

Bruno
fonte
Obrigado! O "$ script:" era o que eu precisava para que isso funcionasse no Windows PowerShell ISE.
precisa saber é o seguinte
Obrigado por isso. Este foi o único que funciona para mim. Normalmente, tenho que colocar um CD no diretório em que o script está antes de coisas como Get-location funcionarem para mim. Eu estaria interessado em saber por que o PowerShell não atualiza automaticamente o diretório.
Zain
7

Eu precisava saber o nome do script e de onde ele está sendo executado.

Prefixar "$ global:" à estrutura MyInvocation retorna o caminho completo e o nome do script quando chamado do script principal e da linha principal de um arquivo de biblioteca .PSM1 importado. Também funciona de dentro de uma função em uma biblioteca importada.

Depois de muita discussão, decidi usar o $ global: MyInvocation.InvocationName. Funciona de forma confiável com o lançamento do CMD, Run With Powershell e o ISE. Lançamentos locais e UNC retornam o caminho correto.

Bruce Gavin
fonte
4
Caminho-Split-Path $ ($ global: MyInvocation.MyCommand.Path) funcionou perfeitamente graças. As outras soluções retornaram o caminho do aplicativo de chamada.
Dynamic_nk # 6/14
1
Nota trivial: no ISE que chama esta função usando a seleção F8 / Run provocará uma ParameterArgumentValidationErrorNullNotAllowedexceção.
Weir
5

Eu sempre uso esse pequeno trecho que funciona para o PowerShell e o ISE da mesma maneira:

# 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
Pedro
fonte
3

Descobri que as soluções mais antigas postadas aqui não funcionavam para mim no PowerShell V5. Eu vim com isso:

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
fonte
2

Você também pode considerar split-path -parent $psISE.CurrentFile.Fullpathse algum dos outros métodos falhar. Em particular, se você executar um arquivo para carregar um monte de funções e depois executá-las dentro do shell do ISE (ou se você executar selecionado), parece que a Get-Script-Directoryfunção como acima não funciona.

fastboxster
fonte
3
$PSCommandPathfuncionará no ISE contanto que você salve o script primeiro e execute o arquivo inteiro. Caso contrário, você não está realmente executando um script; você está apenas "colando" comandos no shell.
Zenexer
@ Zenexer Acho que esse era meu objetivo na época. Embora se meu objetivo não coincidir com o original, isso pode não ser muito útil, exceto para os
2

Usando trechos de todas essas respostas e comentários, montei isso para todos que virem essa pergunta no futuro. Abrange todas as situações listadas nas outras respostas

    # 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
fonte
-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
fonte