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.

Anteckning

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

Skapa ett PSCustomObject

Jag älskar att använda [PSCustomObject] i PowerShell. Det har aldrig varit enklare att skapa ett användbart objekt. På grund av detta 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 stället du märker skillnaden är när du vill använda Format-Table eller Export-CSV och du inser att en hash-tabell bara är en samling nyckel/värde-par.

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

$myObject.Name

Konvertera en hash-tabell

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 du måste arbeta med en hashtable först. Det här exemplet fungerar eftersom konstruktorn tar en hash-tabell för objektegenskaperna. En viktig anmärkning ä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 kan du läsa Ordnade hashtables.

Äldre metod

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

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

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

Det här sättet är ganska lite långsammare, men det kan vara det bästa alternativet i tidiga versioner av PowerShell.

Spara i en fil

Jag hittar det bästa sättet att spara en hash-tabell till 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ätt 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 för ett objekt.

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

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

$myobject.psobject.properties.name

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 som egenskapsnamn så fungerar den 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 hash-tabell

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

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

Testa 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 hash-tabell. (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 den 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åde $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

Klonen 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. (där egenskaperna innehåller andra objekt). Endast värdena på den översta nivån kopieras. 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 måste göra är att ge den 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. Jag gjorde lite grävning och fler inlägg om idén från Adam Bertram och Mike Shepard där de pratar om detta tillvägagångssätt som gör att du kan definiera det infogat.

$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.

Anteckning

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 allt grovjobb. 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 när jag tittade på PowerShell kopplade bort 2016 med Jeffrey Snover & Don Jones. Jeffrey använde 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 massor av 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 kvar.

$myObject | Format-List *

Update-TypeData med ScriptProperty

Något annat jag fick ut av videon var att skapa skriptegenskaper för dina objekt. Det 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 detta 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

Nu kan du använda de här anpassade typerna 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 angav. 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 utvecklingsmiljön 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 det sagt, om du använder Pester för att enhetstesta dina funktioner är det 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 det.

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 med information om PSCustomObject. Så sent som förra veckan snubblade jag på en annan och blev förvånad över att jag inte hade sett den förut. Jag ville samla alla dessa idéer så att du förhoppningsvis kan se den större bilden 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.