Partilhar via


Tudo o que você queria saber sobre PSCustomObject

PSCustomObject é uma ótima ferramenta para adicionar ao seu cinto de ferramentas do PowerShell. Vamos começar com o básico e trabalhar nosso caminho para os recursos mais avançados. A ideia por trás do uso de um PSCustomObject é ter uma maneira simples de criar dados estruturados. Dê uma olhada no primeiro exemplo e você terá uma ideia melhor do que isso significa.

Nota

A versão original deste artigo apareceu no blog escrito por @KevinMarquette. A equipe do PowerShell agradece Kevin por compartilhar esse conteúdo conosco. Por favor, confira seu blog em PowerShellExplained.com.

Criando um PSCustomObject

Adoro usar [PSCustomObject] no PowerShell. Criar um objeto utilizável nunca foi tão fácil. Por isso, vou ignorar todas as outras maneiras de criar um objeto, mas preciso mencionar que a maioria desses exemplos são PowerShell v3.0 e mais recentes.

$myObject = [PSCustomObject]@{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}

Este método funciona bem para mim porque eu uso hashtables para praticamente tudo. Mas há momentos em que gostaria que o PowerShell tratasse as hashtables mais como um objeto. O primeiro lugar em que você percebe a diferença é quando você quer usar Format-Table ou Export-CSV e você percebe que uma hashtable é apenas uma coleção de pares chave/valor.

Em seguida, você pode acessar e usar os valores como faria com um objeto normal.

$myObject.Name

Convertendo uma hashtable

Enquanto estou no tópico, você sabia que poderia fazer isso:

$myHashtable = @{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}
$myObject = [pscustomobject]$myHashtable

Eu prefiro criar o objeto desde o início, mas há momentos em que você tem que trabalhar com uma hashtable primeiro. Este exemplo funciona porque o construtor usa um hashtable para as propriedades do objeto. Uma observação importante é que, embora esse método funcione, ele não é um equivalente exato. A maior diferença é que a ordem dos imóveis não é preservada.

Se quiser preservar a ordem, consulte Tabelas de hash ordenadas.

Abordagem herdada

Você já deve ter visto as pessoas usarem New-Object para criar objetos personalizados.

$myHashtable = @{
    Name     = 'Kevin'
    Language = 'PowerShell'
    State    = 'Texas'
}

$myObject = New-Object -TypeName PSObject -Property $myHashtable

Essa maneira é um pouco mais lenta, mas pode ser sua melhor opção nas versões anteriores do PowerShell.

Guardar num ficheiro

Eu acho que a melhor maneira de salvar uma hashtable em um arquivo é salvá-lo como JSON. Você pode importá-lo de volta para um [PSCustomObject]

$myObject | ConvertTo-Json -depth 1 | Set-Content -Path $Path
$myObject = Get-Content -Path $Path | ConvertFrom-Json

Eu abordo mais maneiras de salvar objetos em um arquivo no meu artigo sobre As muitas maneiras de ler e gravar em arquivos.

Trabalhar com propriedades

Adicionando propriedades

Você ainda pode adicionar novas propriedades ao seu PSCustomObject com Add-Membero .

$myObject | Add-Member -MemberType NoteProperty -Name 'ID' -Value 'KevinMarquette'

$myObject.ID

Remover propriedades

Você também pode remover propriedades de um objeto.

$myObject.psobject.properties.remove('ID')

O .psobject é um membro intrínseco que lhe dá acesso aos metadados do objeto de base. Para obter mais informações sobre membros intrínsecos, consulte about_Intrinsic_Members.

Enumerando nomes de propriedade

Às vezes, você precisa de uma lista de todos os nomes de propriedade em um objeto.

$myObject | Get-Member -MemberType NoteProperty | Select -ExpandProperty Name

Podemos obter esta mesma lista fora da psobject propriedade também.

$myobject.psobject.properties.name

Nota

Get-Member Retorna as propriedades em ordem alfabética. Usar o operador member-access para enumerar os nomes de propriedade retorna as propriedades na ordem em que foram definidas no objeto.

Acesso dinâmico às propriedades

Já mencionei que você pode acessar valores de propriedade diretamente.

$myObject.Name

Você pode usar uma cadeia de caracteres para o nome da propriedade e ela ainda funcionará.

$myObject.'Name'

Podemos dar mais um passo e usar uma variável para o nome do imóvel.

$property = 'Name'
$myObject.$property

Eu sei que parece estranho, mas funciona.

Converter PSCustomObject em uma hashtable

Para continuar a partir da última seção, você pode percorrer dinamicamente as propriedades e criar uma hashtable a partir delas.

$hashtable = @{}
foreach( $property in $myobject.psobject.properties.name )
{
    $hashtable[$property] = $myObject.$property
}

Teste de propriedades

Se você precisa saber se existe um imóvel, basta verificar se esse imóvel tem um valor.

if( $null -ne $myObject.ID )

Mas se o valor pode ser $null , você pode verificar se ele existe, verificando o psobject.properties para ele.

if( $myobject.psobject.properties.match('ID').Count )

Adicionando métodos de objeto

Se você precisar adicionar um método de script a um objeto, poderá fazê-lo com Add-Member e um ScriptBlockarquivo . Você tem que usar a this variável automática referenciar o objeto atual. Aqui está um scriptblock para transformar um objeto em uma hashtable. (mesmo código do último exemplo)

$ScriptBlock = {
    $hashtable = @{}
    foreach( $property in $this.psobject.properties.name )
    {
        $hashtable[$property] = $this.$property
    }
    return $hashtable
}

Em seguida, adicionamo-lo ao nosso objeto como uma propriedade de script.

$memberParam = @{
    MemberType = "ScriptMethod"
    InputObject = $myobject
    Name = "ToHashtable"
    Value = $scriptBlock
}
Add-Member @memberParam

Então podemos chamar a nossa função assim:

$myObject.ToHashtable()

Tipos de objetos vs valores

Objetos e tipos de valor não lidam com atribuições variáveis da mesma maneira. Se você atribuir tipos de valor uns aos outros, somente o valor será copiado para a nova variável.

$first = 1
$second = $first
$second = 2

Neste caso, $first é 1 e $second é 2.

As variáveis de objeto contêm uma referência ao objeto real. Quando você atribui um objeto a uma nova variável, eles ainda fazem referência ao mesmo objeto.

$third = [PSCustomObject]@{Key=3}
$fourth = $third
$fourth.Key = 4

Porque $third e $fourth referenciar a mesma instância de um objeto, ambos $third.key e $fourth.Key são 4.

psobject.copy()

Se precisar de uma cópia verdadeira de um objeto, você pode cloná-lo.

$third = [PSCustomObject]@{Key=3}
$fourth = $third.psobject.copy()
$fourth.Key = 4

O clone cria uma cópia superficial do objeto. Eles têm instâncias diferentes agora e $third.key é 3 e $fourth.Key é 4 neste exemplo.

Eu chamo isso de cópia superficial porque se você tiver objetos aninhados (objetos com propriedades contêm outros objetos), somente os valores de nível superior serão copiados. Os objetos filho farão referência uns aos outros.

PSTypeName para tipos de objeto personalizados

Agora que temos um objeto, há mais algumas coisas que podemos fazer com ele que podem não ser tão óbvias. A primeira coisa que temos de fazer é dar-lhe um PSTypeName. Esta é a forma mais comum de ver as pessoas a fazê-lo:

$myObject.PSObject.TypeNames.Insert(0,"My.Object")

Eu descobri recentemente outra maneira de fazer isso do Redditor u/markekraus. Ele fala sobre essa abordagem que permite defini-la em linha.

$myObject = [PSCustomObject]@{
    PSTypeName = 'My.Object'
    Name       = 'Kevin'
    Language   = 'PowerShell'
    State      = 'Texas'
}

Eu amo como isso se encaixa bem na linguagem. Agora que temos um objeto com um nome de tipo próprio, podemos fazer mais algumas coisas.

Nota

Você também pode criar tipos personalizados do PowerShell usando classes do PowerShell. Para obter mais informações, consulte Visão geral da classe do PowerShell.

Usando DefaultPropertySet (o caminho longo)

O PowerShell decide por padrão quais propriedades exibir. Muitos dos comandos nativos têm um .ps1xml arquivo de formatação que faz todo o trabalho pesado. A partir deste post da Boe Prox, há outra maneira de fazer isso em nosso objeto personalizado usando apenas o PowerShell. Podemos dar-lhe um MemberSet para ele usar.

$defaultDisplaySet = 'Name','Language'
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultDisplaySet)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
$MyObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers

Agora, quando meu objeto cai no shell, ele só mostrará essas propriedades por padrão.

Update-TypeData com DefaultPropertySet

Isso é bom, mas recentemente vi uma maneira melhor usando Update-TypeData para especificar as propriedades padrão.

$TypeData = @{
    TypeName = 'My.Object'
    DefaultDisplayPropertySet = 'Name','Language'
}
Update-TypeData @TypeData

Isso é simples o suficiente para que eu quase pudesse me lembrar se eu não tivesse este post como uma referência rápida. Agora eu posso facilmente criar objetos com muitas propriedades e ainda dar-lhe uma bela visão limpa ao olhar para ele a partir da concha. Se eu precisar acessar ou ver essas outras propriedades, elas ainda estarão lá.

$myObject | Format-List *

Update-TypeData com ScriptProperty

Outra coisa que eu consegui desse vídeo foi criar propriedades de script para seus objetos. Este seria um bom momento para salientar que isso também funciona para objetos existentes.

$TypeData = @{
    TypeName = 'My.Object'
    MemberType = 'ScriptProperty'
    MemberName = 'UpperCaseName'
    Value = {$this.Name.toUpper()}
}
Update-TypeData @TypeData

Você pode fazer isso antes que seu objeto seja criado ou depois e ele ainda funcionará. Isso é o que torna isso diferente de usar Add-Member com uma propriedade de script. Quando você usa Add-Member a maneira que mencionei anteriormente, ela só existe nessa instância específica do objeto. Este aplica-se a todos os objetos com este TypeName.

Parâmetros de função

Agora você pode usar esses tipos personalizados para parâmetros em suas funções e scripts. Você pode fazer com que uma função crie esses objetos personalizados e, em seguida, passá-los para outras funções.

param( [PSTypeName('My.Object')]$Data )

O PowerShell requer que o objeto seja do tipo especificado. Ele lança um erro de validação se o tipo não corresponder automaticamente para salvar a etapa de teste para ele em seu código. Um ótimo exemplo de como o PowerShell faz o que faz melhor.

Função OutputType

Você também pode definir um OutputType para suas funções avançadas.

function Get-MyObject
{
    [OutputType('My.Object')]
    [CmdletBinding()]
        param
        (
            ...

O valor do atributo OutputType é apenas uma nota de documentação. Ele não é derivado do código da função ou comparado com a saída da função real.

A principal razão pela qual você usaria um tipo de saída é para que as informações meta sobre sua função reflitam suas intenções. Coisas como Get-Command e Get-Help que seu ambiente de desenvolvimento pode aproveitar. Se você quiser mais informações, então dê uma olhada na ajuda para isso: about_Functions_OutputTypeAttribute.

Dito isso, se você estiver usando o Pester para testar suas funções por unidade, seria uma boa ideia validar os objetos de saída correspondentes ao seu OutputType. Isso poderia pegar variáveis que simplesmente caem no tubo quando não deveriam.

Considerações finais

O contexto disso era todo sobre [PSCustomObject], mas muitas dessas informações se aplicam a objetos em geral.

Eu já vi a maioria desses recursos de passagem antes, mas nunca os vi apresentados como uma coleção de informações sobre PSCustomObject. Só esta última semana me deparei com outro e fiquei surpreendido por não o ter visto antes. Eu queria reunir todas essas ideias para que você possa ver o quadro geral e estar ciente delas quando tiver a oportunidade de usá-las. Espero que você tenha aprendido algo e possa encontrar uma maneira de trabalhar isso em seus scripts.