Allt du ville veta om PSCustomObject

PSCustomObject är ett bra verktyg att lägga till i ditt PowerShell-verktygsbälte. Vi börjar med grunderna och arbetar oss in i de mer avancerade funktionerna. Tanken med att använda en PSCustomObject är att ha ett enkelt sätt att skapa strukturerade data. Ta en titt på det första exemplet så får du en bättre uppfattning om vad det innebär.

Kommentar

Den ursprungliga versionen av den här artikeln visades på bloggen skriven av @KevinMarquette. PowerShell-teamet tackar Kevin för att ha delat det här innehållet med oss. Kolla in hans blogg på PowerShellExplained.com.

Skapa en PSCustomObject

Jag älskar att använda [PSCustomObject] i PowerShell. Det har aldrig varit enklare att skapa ett användbart objekt. På grund av det kommer jag att hoppa över alla andra sätt du kan skapa ett objekt, men jag måste nämna att de flesta av dessa exempel är PowerShell v3.0 och senare.

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

Den här metoden fungerar bra för mig eftersom jag använder hashtables för nästan allt. Men det finns tillfällen då jag skulle vilja att PowerShell behandlar hashtables mer som ett objekt. Det första du märker skillnaden är när du vill använda Format-Table eller Export-CSV och du inser att en hashtable bara är en samling nyckel/värde-par.

Du kan sedan komma åt och använda värdena som du skulle göra med ett vanligt objekt.

$myObject.Name

Konvertera en hashtable

Medan jag är på ämnet, visste du att du kunde göra detta:

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

Jag föredrar att skapa objektet från början men det finns tillfällen då du måste arbeta med en hashtable först. Det här exemplet fungerar eftersom konstruktorn tar en hashtable för objektegenskaperna. En viktig sak är att även om den här metoden fungerar är den inte en exakt motsvarighet. Den största skillnaden är att ordningen på egenskaperna inte bevaras.

Om du vill bevara ordningen läser du Ordnade hashtables.

Äldre metod

Du kanske har sett personer som använder New-Object för att skapa anpassade objekt.

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

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

Detta sätt är ganska lite långsammare men det kan vara ditt bästa alternativ på tidiga versioner av PowerShell.

Spara i en fil

Jag hittar det bästa sättet att spara en hashtable i en fil är att spara den som JSON. Du kan importera tillbaka den till en [PSCustomObject]

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

Jag beskriver fler sätt att spara objekt i en fil i min artikel om De många sätten att läsa och skriva till filer.

Arbeta med egenskaper

Lägga till egenskaper

Du kan fortfarande lägga till nya egenskaper i med PSCustomObjectAdd-Member.

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

$myObject.ID

Ta bort egenskaper

Du kan också ta bort egenskaper från ett objekt.

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

.psobject är en inbyggd medlem som ger dig åtkomst till basobjektmetadata. Mer information om inbyggda medlemmar finns i about_Intrinsic_Members.

Räkna upp egenskapsnamn

Ibland behöver du en lista över alla egenskapsnamn på ett objekt.

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

Vi kan också ta bort samma lista från egenskapen psobject .

$myobject.psobject.properties.name

Kommentar

Get-Member returnerar egenskaperna i alfabetisk ordning. Om du använder operatorn för medlemsåtkomst för att räkna upp egenskapsnamnen returneras egenskaperna i den ordning de definierades för objektet.

Dynamisk åtkomst till egenskaper

Jag har redan nämnt att du kan komma åt egenskapsvärden direkt.

$myObject.Name

Du kan använda en sträng för egenskapsnamnet och det fungerar fortfarande.

$myObject.'Name'

Vi kan ta det här steget till och använda en variabel för egenskapsnamnet.

$property = 'Name'
$myObject.$property

Jag vet att det ser konstigt ut, men det fungerar.

Konvertera PSCustomObject till en hashtable

Om du vill fortsätta från det sista avsnittet kan du dynamiskt gå över egenskaperna och skapa en hashtable från dem.

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

Testning för egenskaper

Om du behöver veta om det finns en egenskap kan du bara kontrollera att egenskapen har ett värde.

if( $null -ne $myObject.ID )

Men om värdet kan vara $null kan du kontrollera om det finns genom att söka efter psobject.properties det.

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

Lägga till objektmetoder

Om du behöver lägga till en skriptmetod i ett objekt kan du göra det med Add-Member och en ScriptBlock. Du måste använda den this automatiska variabeln som refererar till det aktuella objektet. Här är en scriptblock för att omvandla ett objekt till en hashtable. (samma kod utgör det sista exemplet)

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

Sedan lägger vi till det i objektet som en skriptegenskap.

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

Sedan kan vi anropa vår funktion så här:

$myObject.ToHashtable()

Objekt jämfört med värdetyper

Objekt och värdetyper hanterar inte variabeltilldelningar på samma sätt. Om du tilldelar värdetyper till varandra kopieras bara värdet till den nya variabeln.

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

I det här fallet $first är 1 och $second är 2.

Objektvariabler innehåller en referens till det faktiska objektet. När du tilldelar ett objekt till en ny variabel refererar de fortfarande till samma objekt.

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

Eftersom $third och $fourth refererar till samma instans av ett objekt är båda $third.key och $fourth.Key 4.

psobject.copy()

Om du behöver en sann kopia av ett objekt kan du klona det.

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

Klon skapar en ytlig kopia av objektet. De har olika instanser nu och $third.key är 3 och $fourth.Key är 4 i det här exemplet.

Jag kallar detta en ytlig kopia eftersom om du har kapslade objekt (objekt med egenskaper innehåller andra objekt) kopieras endast de översta värdena. De underordnade objekten refererar till varandra.

PSTypeName för anpassade objekttyper

Nu när vi har ett objekt finns det några fler saker vi kan göra med det som kanske inte är lika uppenbart. Det första vi behöver göra är att ge det en PSTypeName. Detta är det vanligaste sättet jag ser människor göra det:

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

Jag upptäckte nyligen ett annat sätt att göra detta från det här inlägget av /u/markekraus. Han talar om den här metoden som gör att du kan definiera den infogad.

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

Jag älskar hur fint detta bara passar in i språket. Nu när vi har ett objekt med rätt typnamn kan vi göra några fler saker.

Kommentar

Du kan också skapa anpassade PowerShell-typer med hjälp av PowerShell-klasser. Mer information finns i Översikt över PowerShell-klass.

Använda DefaultPropertySet (långt)

PowerShell bestämmer vilka egenskaper som ska visas som standard. Många av de interna kommandona har en .ps1xmlformateringsfil som utför alla tunga lyft. Från det här inlägget av Boe Prox finns det ett annat sätt för oss att göra detta på vårt anpassade objekt med bara PowerShell. Vi kan ge den en MemberSet för den att använda.

$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

Nu när mitt objekt bara faller till gränssnittet, kommer det bara att visa dessa egenskaper som standard.

Update-TypeData med DefaultPropertySet

Detta är trevligt men jag såg nyligen ett bättre sätt att använda Update-TypeData för att ange standardegenskaperna.

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

Det är enkelt nog att jag nästan kunde komma ihåg det om jag inte hade det här inlägget som en snabb referens. Nu kan jag enkelt skapa objekt med många egenskaper och ändå ge det en fin ren vy när man tittar på det från gränssnittet. Om jag behöver komma åt eller se de andra egenskaperna finns de fortfarande där.

$myObject | Format-List *

Update-TypeData med ScriptProperty

Något annat jag fick ut av videon var att skapa skriptegenskaper för dina objekt. Detta skulle vara ett bra tillfälle att påpeka att detta fungerar även för befintliga objekt.

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

Du kan göra detta innan objektet skapas eller efter och det fungerar fortfarande. Det är det som gör detta annorlunda än att använda Add-Member med en skriptegenskap. När du använder Add-Member det sätt som jag refererade till tidigare finns det bara på den specifika instansen av objektet. Den här gäller för alla objekt med den här TypeName.

Funktionsparametrar

Du kan nu använda dessa anpassade typer för parametrar i dina funktioner och skript. Du kan låta en funktion skapa dessa anpassade objekt och sedan skicka dem till andra funktioner.

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

PowerShell kräver att objektet är den typ som du har angett. Det utlöser ett valideringsfel om typen inte matchar automatiskt för att spara steget för att testa den i koden. Ett bra exempel på hur du låter PowerShell göra det som är bäst.

Funktionsutdatatyp

Du kan också definiera en OutputType för dina avancerade funktioner.

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

Attributet OutputType är bara en dokumentationsanteckning. Den härleds inte från funktionskoden eller jämförs med de faktiska funktionsutdata.

Den främsta anledningen till att du använder en utdatatyp är att metainformation om din funktion återspeglar dina avsikter. Saker som Get-Command och Get-Help som din utvecklingsmiljö kan dra nytta av. Om du vill ha mer information kan du ta en titt på hjälpen för den: about_Functions_OutputTypeAttribute.

Med detta sagt, om du använder Pester för att enhetstesta dina funktioner skulle det vara en bra idé att verifiera att utdataobjekten matchar din OutputType. Detta kan fånga variabler som bara faller till röret när de inte borde.

Avslutande tankar

Kontexten för detta handlade om [PSCustomObject], men mycket av den här informationen gäller för objekt i allmänhet.

Jag har sett de flesta av dessa funktioner i förbigående tidigare men aldrig sett dem presenteras som en samling av information om PSCustomObject. Så sent som i förra veckan snubblade jag på en annan och blev förvånad över att jag inte hade sett den tidigare. Jag ville samla alla dessa idéer så att du förhoppningsvis kan se helheten och vara medveten om dem när du har möjlighet att använda dem. Jag hoppas att du har lärt dig något och kan hitta ett sätt att arbeta detta i dina skript.