11. Módulos

11.1 Introdução

Como indicado no §3.14, um módulo é uma unidade reutilizável autossuficiente que permite que o código PowerShell seja dividido, organizado e abstrato. Um módulo pode conter um ou mais membros do módulo, que são comandos (como cmdlets e funções) e itens (tais como variáveis e pseudónimos). Os nomes destes membros podem ser mantidos em privado no módulo ou podem ser exportados para a sessão em que o módulo é importado.

Existem três tipos diferentes de módulos: manifesto, script e binário. Um módulo manifesto é um ficheiro que contém informações sobre um módulo, e controla certos aspetos da utilização desse módulo. Um módulo de script é um ficheiro de script PowerShell com uma extensão de ficheiro de .psm1 em vez de .ps1. Um módulo binário contém tipos de classe que definem cmdlets e fornecedores. Ao contrário dos módulos de script, os módulos binários são escritos em línguas compiladas. Os módulos binários não estão abrangidos por esta especificação.

Um módulo binário é um conjunto .NET (isto é, um DLL) que foi compilado contra as bibliotecas PowerShell.

Os módulos podem nidificar; ou seja, um módulo pode importar outro módulo. Um módulo que tem módulos aninhados associados é um módulo raiz.

Quando uma sessão PowerShell é criada, por padrão, não são importados módulos.

Quando os módulos são importados, o caminho de pesquisa usado para localizá-los é definido pela variável ambiental PSModulePath.

Os seguintes cmdlets tratam de módulos:

11.2 Escrever um módulo de guião

Um módulo de script é um ficheiro de script. Considere o seguinte módulo de script:

function Convert-CentigradeToFahrenheit ([double]$tempC) {
    return ($tempC * (9.0 / 5.0)) + 32.0
}
New-Alias c2f Convert-CentigradeToFahrenheit

function Convert-FahrenheitToCentigrade ([double]$tempF) {
    return ($tempF - 32.0) * (5.0 / 9.0)
}
New-Alias f2c Convert-FahrenheitToCentigrade

Export-ModuleMember -Function Convert-CentigradeToFahrenheit
Export-ModuleMember -Function Convert-FahrenheitToCentigrade
Export-ModuleMember -Alias c2f, f2c

Este módulo contém duas funções, cada uma das quais tem um pseudónimo. Por padrão, todos os nomes de funções e apenas nomes de funções são exportados. No entanto, uma vez utilizado o cmdlet Export-ModuleMember para exportar qualquer coisa, apenas as coisas exportadas explicitamente serão exportadas. Uma série de comandos e itens podem ser exportados numa chamada ou numa série de chamadas para este cmdlet; tais chamadas são cumulativas para a sessão atual.

11.3 Instalação de um módulo de script

Um módulo de script é definido num ficheiro de script, e os módulos podem ser armazenados em qualquer diretório. A variável ambiental PSModulePath aponta para um conjunto de diretórios a serem pesquisados quando os cmdlets relacionados com módulos procuram módulos cujos nomes não incluem um caminho totalmente qualificado. Podem ser fornecidos caminhos de procura adicionais; Por exemplo

$Env:PSModulepath = $Env:PSModulepath + ";<additional-path>"

Quaisquer caminhos adicionais adicionados afetam apenas a sessão atual.

Alternativamente, um caminho totalmente qualificado pode ser especificado quando um módulo é importado.

11.4 Importar um módulo de guião

Antes de os recursos de um módulo poderem ser utilizados, esse módulo deve ser importado para a sessão atual, utilizando o cmdlet Import-Module. Import-Module pode restringir os recursos que efetivamente importa.

Quando um módulo é importado, o seu ficheiro de script é executado. Este processo pode ser configurado definindo um ou mais parâmetros no ficheiro de scripts e transmitindo argumentos correspondentes através do parâmetro ArgumentList de Import-Module.

Considere o seguinte script que utiliza estas funções e pseudónimos definidos em §11.2:

Import-Module "E:\Scripts\Módulos\PSTestTemperature_" -Verbose

"0 degrees C is &quot; + (Convert-CentigradeToFahrenheit 0) + &quot; degrees F"
"100 degrees C is " + (c2f 100) + " degrees F"
"32 degrees F is " + (Convert-FahrenheitToCentigrade 32) + " degrees C"
"212 degrees F is " + (f2c 212) + " degrees C"

Importar um módulo causa um conflito de nomes quando os comandos ou itens no módulo têm os mesmos nomes que comandos ou itens na sessão. Um conflito de nomes resulta em um nome ser escondido ou substituído. O parâmetro prefixo pode Import-Module ser usado para evitar o nome de conflitos. Além disso, os parâmetros Alias, Cmdlet, Function e Variable podem limitar a seleção de comandos a importar, reduzindo assim as chances de conflito de nomes.

Mesmo que um comando seja escondido, pode ser executado qualificando o seu nome com o nome do módulo em que se originou. Por exemplo, & M\F 100 invoca a função F no módulo M, e passa-lhe o argumento 100.

Quando a sessão inclui comandos do mesmo tipo com o mesmo nome, como dois cmdlets com o mesmo nome, por padrão executa o comando mais recentemente adicionado.

Consulte §3.5.6 para uma discussão de âmbito no que diz respeito a módulos.

11.5 Remoção de um módulo de script

Um ou mais módulos podem ser removidos de uma sessão através do cmdlet Remove-Module.

A remoção de um módulo não desinstala o módulo.

Num módulo de script, é possível especificar código que deve ser executado antes da remoção do módulo, da seguinte forma:

$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { *on-removal-code* }

11.6 Manifestos de módulo

Como indicado no §11.1, um módulo manifesto é um ficheiro que contém informações sobre um módulo, e controla certos aspetos da utilização desse módulo.

Um módulo não precisa de ter um manifesto correspondente, mas se o fizer, esse manifesto tem o mesmo nome que o módulo que descreve, mas com uma .psd1 extensão de ficheiro.

Um manifesto contém um subconjunto limitado de script PowerShell, que devolve um Hashtable contendo um conjunto de teclas. Estas teclas e os seus valores especificam os elementos manifestos para esse módulo. Ou seja, descrevem os conteúdos e atributos do módulo, definem quaisquer requisitos prévios e determinam como os componentes são processados.

Essencialmente, um manifesto é um ficheiro de dados; no entanto, pode conter referências a tipos de dados, à declaração se afirma, e aos operadores aritméticos e de comparação. (Não são permitidas atribuições, definições de funções e loops.) Um manifesto também leu o acesso a variáveis ambientais e pode conter chamadas para o cmdlet Join-Path, para que os caminhos possam ser construídos.

Nota

Nota do editor: O documento original contém uma lista de chaves permitidas num ficheiro manifesto de módulos. A lista está desatualizada e incompleta. Para obter uma lista completa de chaves num manifesto de módulos, consulte o New-ModuleManifest.

A única chave que é necessária é a Versão módulo.

Aqui está um exemplo de um simples manifesto:

@{
ModuleVersion = '1.0'
Author = 'John Doe'
RequiredModules = @()
FunctionsToExport = 'Set*','Get*','Process*'
}

A chave GUID tem um string valor. Isto especifica um IDentifier Globally Unique (GUID) para o módulo. O GUID pode ser usado para distinguir entre os módulos com o mesmo nome. Para criar um novo GUID, ligue para o método [guid]::NewGuid().

11.7 Módulos dinâmicos

Um módulo dinâmico é um módulo que é criado na memória em tempo de execução pelo cmdlet New-Module; não é carregado a partir de disco. Considere o exemplo seguinte:

$sb = {
    function Convert-CentigradeToFahrenheit ([double]$tempC) {
        return ($tempC * (9.0 / 5.0)) + 32.0
    }

    New-Alias c2f Convert-CentigradeToFahrenheit

    function Convert-FahrenheitToCentigrade ([double]$tempF) {
        return ($tempF - 32.0) * (5.0 / 9.0)
    }

    New-Alias f2c Convert-FahrenheitToCentigrade

    Export-ModuleMember -Function Convert-CentigradeToFahrenheit
    Export-ModuleMember -Function Convert-FahrenheitToCentigrade
    Export-ModuleMember -Alias c2f, f2c
}

New-Module -Name MyDynMod -ScriptBlock $sb
Convert-CentigradeToFahrenheit 100
c2f 100

O bloco $sb de scripts define o conteúdo do módulo, neste caso, duas funções e dois pseudónimos para essas funções. Tal como acontece com um módulo on-disk, apenas as funções são exportadas por padrão, pelo que Export-ModuleMember existem chamadas de cmdlets para exportar tanto as funções como os pseudónimos.

Uma vez New-Module executados, os quatro nomes exportados estão disponíveis para uso na sessão, como mostra as chamadas para o Convert-CentigradeToFahrenheit e c2f.

Como todos os módulos, os membros de módulos dinâmicos funcionam num âmbito de módulos privados que é uma criança do âmbito global. Get-Module não pode obter um módulo dinâmico, mas Get-Command pode obter os membros exportados.

Para disponibilizar um módulo dinâmico para Get-Module, canalizar um New-Module comando para Import-Module, ou canalizar o objeto do módulo que New-Module retorna, para Import-Module. Esta ação adiciona o módulo dinâmico à Get-Module lista, mas não guarda o módulo para o disco nem o torna persistente.

11.8 Encerramentos

Um módulo dinâmico pode ser usado para criar um fecho, uma função com dados anexados. Considere o exemplo seguinte:

function Get-NextID ([int]$startValue = 1) {
    $nextID = $startValue
    {
        ($script:nextID++)
    }.GetNewClosure()
}

$v1 = Get-NextID      # get a scriptblock with $startValue of 0
& $v1                 # invoke Get-NextID getting back 1
& $v1                 # invoke Get-NextID getting back 1

$v2 = Get-NextID 100  # get a scriptblock with $startValue of 100
& $v2                 # invoke Get-NextID getting back 100
& $v2                 # invoke Get-NextID getting back 101

A intenção aqui é que Get-NextID devolva o próximo ID numa sequência cujo valor inicial pode ser especificado. No entanto, devem ser suportadas múltiplas sequências, cada uma com o seu próprio $startValue contexto.$nextID Isto é conseguido pela chamada ao método [scriptblock]::GetNewClosure (§4.3.7).

Cada vez que um novo fecho é criado por GetNewClosure, um novo módulo dinâmico é criado, e as variáveis no âmbito do chamador (neste caso, o bloco de scripts contendo o incremento) são copiadas para este novo módulo. Para garantir que o nextId definido dentro da função dos pais (mas fora do bloco de scripts) é incrementado, o script explícito: prefixo de âmbito é necessário.

É claro que o bloco de scripts não precisa de ser uma função nomeada; Por exemplo:

$v3 = & {      # get a scriptblock with $startValue of 200
    param ([int]$startValue = 1)
    $nextID = $startValue
    {
        ($script:nextID++)
    }.GetNewClosure()
} 200

& $v3          # invoke script getting back 200
& $v3          # invoke script getting back 201