sobre escopos no Powershell

Aug 24 2020

Estou aprendendo sobre escopos no Powershell e tenho algumas dúvidas:

  1. Sobre o "escopo local": Pelo que li, o escopo local é sempre o escopo atual. Então, por padrão, quando criamos um item (sem modificador de escopo), por exemplo, uma variável, em algum escopo, seja script ou global, o escopo será script/global de acordo. Portanto, minha pergunta é: quando precisaremos especificar explicitamente o localmodificador?
  2. MSDN disse:

Você pode criar um novo escopo executando um script ou função, criando uma sessão ou iniciando uma nova instância do PowerShell. Quando você cria um novo escopo, o resultado é um escopo pai (o escopo original) e um escopo filho (o escopo que você criou). ...
A menos que você explicitamente torne os itens privados, os itens no escopo pai estarão disponíveis para o escopo filho. No entanto, os itens que você cria e altera no escopo filho não afetam o escopo pai, a menos que você especifique explicitamente o escopo ao criar os itens.

Mas quando tento o seguinte:

PS> $Name = "John"
PS> Powershell.exe
PS>echo $Name  // No Output

Parece da citação acima que "iniciar uma nova instância do powershell" é um escopo filho, portanto, todos os itens no escopo pai devem estar visíveis lá. Alguém pode explicar?

Respostas

2 mklement0 Aug 24 2020 at 23:16

Para complementar a resposta útil de iRon :

  1. [...] quando precisaremos especificar explicitamente o modificador local?

$local:raramente é necessário , porque o escopo local está implícito na ausência de um especificador de escopo.

No entanto, isso só se aplica se a variável referenciada realmente existir como uma variável local , uma vez que o escopo dinâmico do PowerShell torna variáveis ​​de escopos ancestrais (pais) visíveis para escopos descendentes (filhos) também (consulte esta resposta para obter mais informações):

  • Por exemplo, digamos que você tenha $foo = 'bar'declarado no escopo global , então a referência $fooem um script procuraria primeiro uma instância local ; $foose não houver nenhum, será usado um $foodefinido em um escopo ancestral (pai), se houver, que seria o global $foo neste exemplo e 'bar'seria retornado.

  • Por outro lado, se, em seu script, você usar $local:foo, sem que uma $foovariável local seja definida, você obtém $nullpor padrão ou, se Set-StrictMode -Version 2ou superior estiver em vigor, ocorrerá um erro de finalização de instrução .


  1. MSDN diz: [...] criando uma sessão, ou iniciando uma nova instância do PowerShell [...] o resultado é um escopo pai (o escopo original) e um escopo filho (o escopo que você criou).

A documentação está incorreta a esse respeito no momento em que este livro foi escrito (um problema do GitHub foi registrado):

  • Relacionamentos ancestrais (pai-filho) entre escopos existem apenas no contexto de uma determinada sessão (runspace) .

    • Ou seja, o escopo dinâmico - a visibilidade de variáveis ​​e outras definições de escopos ancestrais - só se aplica a escopos dentro de uma determinada sessão.

    • Uma exceção notável é que as funções de um módulo não são executadas em um escopo filho do escopo de chamada - exceto se o escopo de chamada for o escopo global ; os módulos têm seus próprios domínios de escopo (tecnicamente chamados de estados de sessão ) que estão vinculados apenas ao escopo global - consulte este problema de documentação do GitHub para uma discussão.

  • Portanto, nenhum escopo filho do escopo de chamada é criado nos seguintes cenários, onde o código recém-lançado não sabe nada sobre as variáveis ​​(e outras definições) no escopo de chamada :

    • Iniciar uma nova sessão via comunicação remota do PowerShell (por exemplo, com Enter-PSSession) ouInvoke-Command -Computer

    • Iniciando um trabalho [thread] em segundo plano com Start-Jobou Start-ThreadJobexecutando threads em paralelo com ForEach-Object -Parallela versão 7.0+

    • Iniciando uma nova instância do PowerShell (processo) , usando a CLI do PowerShell ( pwshpara PowerShell [Core], powershell.exepara Windows PowerShell).

    • Para comunicar valores do escopo de chamada para o código recém-lançado nesses cenários, é necessária uma ação explícita :

      • Ao chamar a CLI ou usar , onde um processoStart-Job filho na mesma máquina é criado, apenas as variáveis ​​de ambiente definidas no processo de chamada ficam automaticamente disponíveis para o processo filho.
      • Caso contrário, os valores do chamador devem ser passados ​​como argumentos ou - exceto ao usar a CLI - por meio do $using:escopo - veja esta resposta .
4 iRon Aug 24 2020 at 14:48

Começando com a última pergunta:

Os escopos entram em jogo com funções e scripts invocados (cmdlets), como:

Function Test {
    $Test++
    Write-Host 'Local:' $Test
}
$Test = 5
Test
Write-Host 'Global:' $Test

Retorna:

Local: 6
Global: 5

E:

Function Test {
    $Global:Test++
    Write-Host 'Local:' $Test
}
$Test = 5
Test
Write-Host 'Global:' $Test

Retorna:

Local: 6
Global: 6

Ou se você colocar a função em um script (por exemplo MyScript.ps1):

$Test = 5
.\MyScript.ps1
Write-Host $Test # $Test is unaffected unless you use the $Global scope in your script

Que retornará basicamente os mesmos resultados acima, a menos que você Dot-Source seu script onde ele será executado no escopo atual:

$Test = 5
. .\MyScript.ps1
Write-Host $Test # $Test might be affected by MyScript.ps1 if you just use $Test

Para o que você está fazendo:
Você está criando uma nova sessão completa do PowerShell (com Powershell.exe) que começará com uma nova lista de variáveis.
Observe aqui que você verá as variáveis ​​iniciais novamente se exitda nova sessão:

PS C:\> $Name = "John"
PS C:\> Powershell.exe
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS C:\> Write-Host 'New session' $Name
New session
PS C:\> Exit
PS C:\> Write-Host 'Initial session' $Name
Initial session John

Com relação à primeira pergunta, não acho que existam muitos aplicativos em que você precise se referir explicitamente ao $Localescopo, mas para dar um exemplo de onde você pode usá-lo:

$Test = 5
Function Test {
    Write-Host ($Local:Test++)
}
Test

No exemplo acima, o operador de incremento unário começará com 0se você usar explicitamente o $Localescopo (na verdade, você começa com uma variável local vazia que será convertida para 0) e com 5se você omitir o $Localescopo onde herdará uma cópia da $Testvariável de o escopo pai.