Dinamikus paraméterek

Már többször említettem a dinamikus paraméterek fogalmát. Ezek olyan paraméterek, amelyek bizonyos feltételek teljesülésekor jelennek meg a cmdleteknél, függvényeknél. A leggyakoribb ilyen feltétel, hogy a parancs éppen melyik provider környezetében fut. Egy kis rejtvényként próbáljuk meg felderíteni az összes olyan cmdletet, amelynek van dinamikus paramétere, illetve azt, hogy mely providerek esetében milyen paraméterekre kell számítanunk. Első lépésként nézzük meg, hogy hogyan tudunk információkhoz jutni ezekről a dinamikus paraméterekről. A help ebben nem segít. Én tudom, hogy pl. a get-content cmdletnek egy -delimiter dinamikus paramétere, de az alábbi, súgóból származó információ ebben nem segít nekünk:

[17] PS C:\> (Get-Help Get-Content).parameters.parameter | ForEach-Object {$_.n

ame}

Credential

Exclude

Filter

Force

Include

LiteralPath

Path

ReadCount

TotalCount

UseTransaction

Egy másik cmdlettel, a get-command -dal is fel tudjuk deríteni a paramétereket, méghozzá sokkal precízebben, immár a dinamikus paraméterekkel együtt:

[18] PS C:\> (Get-Command Get-Content).parameters

 

Key                                     Value

---                                     -----

ReadCount                               System.Management.Automation.Parame...

TotalCount                              System.Management.Automation.Parame...

Path                                    System.Management.Automation.Parame...

LiteralPath                             System.Management.Automation.Parame...

Filter                                  System.Management.Automation.Parame...

Include                                 System.Management.Automation.Parame...

Exclude                                 System.Management.Automation.Parame...

Force                                   System.Management.Automation.Parame...

Credential                              System.Management.Automation.Parame...

Verbose                                 System.Management.Automation.Parame...

Debug                                   System.Management.Automation.Parame...

ErrorAction                             System.Management.Automation.Parame...

WarningAction                           System.Management.Automation.Parame...

ErrorVariable                           System.Management.Automation.Parame...

WarningVariable                         System.Management.Automation.Parame...

OutVariable                             System.Management.Automation.Parame...

OutBuffer                               System.Management.Automation.Parame...

UseTransaction                          System.Management.Automation.Parame...

Delimiter                               System.Management.Automation.Parame...

Wait                                    System.Management.Automation.Parame...

Encoding                                System.Management.Automation.Parame...

Az a baj, hogy ez hashtáblát ad kimenetként, így csőfeldolgozó parancsokkal ezt nem lehet szűrni, feldolgozni, így egy kis átalakítást kell végezni ezzel, hogy megvizsgálhassuk, hogy az egyes paraméterek dinamikusak-e:

[19] PS C:\> $h_params = (Get-Command Get-Content).parameters

[20] PS C:\> $h_params.Keys | where-object {$h_params[$_].IsDynamic} | ForEach-

Object {$h_params[$_].name}

Delimiter

Wait

Encoding

Annyival többet tud a get-command a get-help-hez képest, hogy átadhatunk neki egy ‑ArgumentList paraméterben mindenféle paramétert, amelyekkel a cmdlet vizsgálatának körülményeit tudjuk modellezni, így pont elő lehet csiholni a dinamikus paramétereit. Azaz nézzük meg ugyanennek a get-content-nek a dinamikus paramétereit akkor, ha épp registry környezetben futtatnánk:

[27] PS C:\> $h_params = (Get-Command Get-Content -ArgumentList HKLM:).paramete

rs

[28] PS C:\> $h_params.Keys | where-object {$h_params[$_].IsDynamic} | ForEach-

Object {$h_params[$_].name}

[29] PS C:\>

Nem adott semmilyen találatot! Azaz abban a környezetben a fájlrendszerben látott dinamikus paraméterek már nem léteznek.

Ebből kiindulva nézzünk egy olyan szkriptet, ami feltérképezi az összes cmdlet összes provider környezetében levő dinamikus paramétereit:

function converthashtocollection ([hashtable] $h)

{

    $h.keys | ForEach-Object {$h[$_] | Where-Object {$_}}

}

 

$providers = get-psdrive | sort-object -unique provider |

ForEach-Object {$_.name}

 

function Get-DynamicParameter {

    param(        

    [string]$command,

    $drives

    ) 

       

    $ph = @{}

   

    foreach($d in $drives)

    {

        $ph[$d] = try { converthashtocollection ((Get-Command $command

                -ArgumentList "$($d):").parameters) |

                Where-Object {$_.isdynamic} | ForEach-Object {$_.name}

        }

        catch{}

    }

   

    if(converthashtocollection $ph){$ph["name"] = $command

          New-Object -TypeName psobject -Property $ph}

}

 

Get-Command -commandtype cmdlet |  foreach-object {

     get-dynamicparameter ($_.name) $providers} |

    Format-Table (,"name"+$providers) -Wrap

És nézzük ennek (illetve nem pont ennek, hanem egy Out-Gridview-re módosított változatának) kimenetét:

106 . ábra Dinamikus paraméterek a beépített cmdletekben

Nem mondom, hogy egy perc alatt sikerült ezt a szkriptet összeraknom, de nem volt irdatlan nagy munka. A kódban látható, hogy segédfüggvényként készítettem egy függvényt, ami egy hashtáblából gyűjteményt készít. A Get-DynamicParameter függvényben végig megyek a lehetséges meghajtókon, és mindegyikre megnézem az adott cmdlet paraméterezését. Miután ez némelyik cmdletre hibát ad (nem fogadnak elérési út jellegű paramétert), ezért az egész kifejezést egy Try blokkba ágyaztam, amihez egy üres Catch részt tartozik, így hibamentesen fut le minden cmdletnél.

Az egész adatábrázolást egy olyan hashtáblával oldottam meg ($ph), amelyben az egyes meghajtók a kulcsok, és a dinamikus paraméterek tömbje az érték. Ha volt dinamikus paraméter legalább egy is, akkor ezt még megtoldom a name kulccsal, ahova a cmdlet nevét töltöm be, és az egész függvény visszatérési értéke egy olyan egyedi objektum, amit ebből a $ph hashtáblából generálok, és amelynek tulajdonságai a „name” és a meghajtók lesznek és a tulajdonságértékek a dinamikus paraméterek tömbje, amit végül szép táblázatosan lehet megjeleníteni.

Dinamikus paraméter létrehozása függvényben

Ilyen dinamikus paramétereket mi magunk is tudunk létrehozni függvényeinkben. Ez hasonlóan, egy „template” alapján történhet, mint ahogy a meglevő cmdleteket kiegészítettük, azzal a különbséggel, hogy ehhez nem kapunk segítséget. Ezért álljon mintaként az alábbi példa, amelyben egy olyan függvény van, ami kisűri a paraméterként átadott típusú elemeket az adott meghajtón. Az egyszerűség kedvéért ezt csak a fájlrendszerre és az Active Directory-ra készítettem el. Fájlrendszeren a lehetséges típus, amire szűrni lehet a file és folder vagy minden (*), ActiveDirectory meghajtó esetében user, group, computer vagy minden (*).

function get-itemsoftype{

[cmdletbinding()]

    Param(

        [parameter(

                Mandatory=$true,

                ValueFromPipeline=$true,

                ValueFromPipelineByPropertyName = $true,

                Position=0,

                ParameterSetName='pset')]

        [String]

        $path

    )

    DynamicParam{

        $attributes = new-object -TypeName System.Management.Automation.ParameterAttribute

        $attributeCollection = new-object -TypeName System.Collections.ObjectModel.Collection[Attribute]

        $paramDictionary = new-object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary

       

        $attributes.ParameterSetName = 'pset'

        $attributes.Position = 1

        $attributes.Mandatory = $false

 

        if ((get-psdrive (                    

                (split-path (resolve-path $path) -Qualifier) -replace ":","")).provider.name -eq "FileSystem"){

                    $vsa = New-Object -TypeName System.Management.Automation.ValidateSetAttribute -ArgumentList "file", "folder", "*"

                    $attributes.HelpMessage = "Lehetséges értékek: file, folder, *"

          }

        elseif ((get-psdrive (

                (split-path (resolve-path $path) -Qualifier) -replace ":","")).provider.name -eq "ActiveDirectory") {

                $vsa = New-Object -TypeName System.Management.Automation.ValidateSetAttribute -ArgumentList "user", "group", "computer", "*"

                $attributes.HelpMessage = "Lehetséges értékek: user, group, computer, *"

        }

       

        $attributeCollection.Add($attributes)

        $attributeCollection.Add($vsa)           

        $ItemType = new-object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList "ItemType", "string", $attributeCollection

       

        $paramDictionary.Add("ItemType", $ItemType)

        return $paramDictionary

    }

    Process{

          switch($ItemType.value){

                "User" {$filter = {$_.objectclass -eq "user"}}

                "Group" {$filter = {$_.objectclass -eq "group"}}

                "Computer" {$filter = {$_.objectclass -eq "computer"}}

                 "File" {$filter = {$_.psiscontainer -eq $false}}

                "Folder" {$filter = {$_.psiscontainer -eq $true}}

                "*" {$filter = {$true}}

                default {$filter = {$true}}

          }

        Get-ChildItem -Path $path | Where-Object $filter

     }

}

 

Az egésznek a lényegi része, hogy a [cmdletbinding()] paraméterattribútumnak szerepelnie kell és a DynamicParam  szekciót kell használni a dinamikus paraméterek létrehozására. Ez csak akkor alkalmazható, ha a függvényben van külön Process (és esetleg Begin és End) szekció is. Ezen szekción belül felépítek egy $attributes változót, amely a dinamikus paraméter jellemzőit tartalmazza. Ennek az objektumnak vannak olyan tulajdonságai, mint például ParameterSetName, Position, Mandatory.

Van ezen kívül egy $attributeCollection változóm, ami tartalmazza a dinamikus paraméter jellemzőit, úgy mint az előbb tárgyalt attribútumokat és ezen kívül a validálási szabályokat.

Ezen kívül van még az $ItemType változóm, ez maga a dinamikus paraméter, ahol futásidőben felépítem a paraméter nevét („ItemType”), típusát ([string]), és ehhez biggyesztem hozzá az előbb említett $attributeCollection-t. De ez még nem elég, ezt a paramétert hozzá kell fűzni a függvény dinamikus paramétereihez, ami a $paramDictionary változóban van, és amelyet ez a DynamicParam szekció ad vissza visszatérési értékként.

Huh! Nem túl egyszerű még csak elmagyarázni sem. Szerencsére ezt nem kell fejből tudni, itt a könyv példája, mindenki kimásolhatja, és ez alapján építheti fel saját dinamikus paraméterét.

De mit is csinál a fenti függvény? A meghívásakor a kötelező $path tartalmát megvizsgálja, hogy vajon a fájlrendszerre vagy az Active Directory-ra mutat-e. Ennek megfelelően vagy egy olyan ItemType dinamikus paramétert vár, amely a „File” és „Folder” szavakat fogadja csak el, vagy egy olyat, ami a „User”, „group”, „computer” és „*” értékek valamelyikét fogadja el. A Process szekcióban ennek megfelelően felépítek egy szűrőt és a Get-ChildItem cmdlet kimenetét ezzel szűröm.

107 . ábra Az Intellisense és a dinamikus paraméterek

Természetesen ezt még tovább lehetne finomítani, de itt csak az volt a célom, hogy a dinamikus paraméterekből egy kis ízelítőt adjak.

Egy pillanatra nézzük meg, hogyan hivatkoztam a dinamikus paraméter értékére! A Process szekció switch kifejezésében $ItemType.value-val hivatkoztam rá, de ez csak annak köszönhető, hogy a dinamikus paraméterdefiníció blokkjának a vége felé erre a változónévre hivatkoztam:

$ItemType = new-object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList "ItemType", "string", $attributeCollection

De ez nem feltétlenül szükséges, a Process szekcióban a $PSBoundParameters automatikus hashtáblán keresztül is lehet hivatkozni rá, de fontos megjegyezni, hogy semmiképpen sem a $paraméternév változón keresztül!

Megjegyzés

Sajnos, ha egy „igazi” (nem dinamikus) paraméternek alaphelyzet szerinti értéke van, azt akkor még nem veszi fel, amikor a DynamicParam szekció kiértékelődik, így annak tartalmát sajnos ott nem tudjuk vizsgálni. Valószínű ez egy hiba, amit később PowerShell verziókban remélhetőleg kijavítanak.

Dinamikus paraméter dinamikus értékhalmazzal

Az egyik leggyakoribb felhasználásai területe a dinamikus paramétereknek, hogy futási időben generálhatjuk a paraméter értékeinek lehetséges listáját. Ez a felhasználók számára jelentősen megkönnyíti a paraméter használatát, mert akár TAB-kiegészítéssel, akár IntelliSense-el elég a lehetséges értékek kiválasztása, nem kell gépelni semmit sem.

Az alábbi példafüggvényben a már betöltött modulok közül lehet újra importálni valamelyiket. Ez jól jön, ha éppen fejlesztünk egy modult és szükség van a változtatások után újra importálni, a függvény használatával elég a modul nevét kiválasztani az IntelliSense segítségével.

function reimport-module {

[CmdletBinding()]           

param (

)           

           

DynamicParam {           

        $name = "Name"

        $Attributes = New-Object -TypeName 'Management.Automation.ParameterAttribute'

        $Attributes.ParameterSetName = "__AllParameterSets"           

        $Attributes.Mandatory = $true           

           

        $AttributeCollection = New-Object -TypeName 'Collections.ObjectModel.Collection[Attribute]'

        $AttributeCollection.Add($Attributes)           

 

        $arrSet = Get-Module | Select-Object -ExpandProperty name

        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $arrSet

        $AttributeCollection.Add($ValidateSetAttribute)

                   

        $Dynamic = New-Object -TypeName 'Management.Automation.RuntimeDefinedParameter' -ArgumentList $Name, 'system.string', $AttributeCollection

                       

        $ParamDictionary = New-Object 'Management.Automation.RuntimeDefinedParameterDictionary'

        $ParamDictionary.Add($Name, $Dynamic)           

        $ParamDictionary           

}            

end {

    $name = $PSBoundParameters.Name                       

    if($name -match "\\"){

        $shortname = $name.Split("\")[-1] -replace "\.(?!.*?\.).*"

    }

    else{

        $shortname = $name

    }

       

    $module = Get-Module -Name $shortname

 

    $path = $module.path

 

    $psdfile = $path -replace "\.psm1",".psd1"

 

    if(Test-Path -Path $psdfile -PathType Leaf){

        Import-Module -Name $psdfile -Force

    }

    else{

        Import-Module -Name $path -Force

    }

}           

}

Ha futtatom mindig az éppen aktuális modulok nevei jelennek meg, mást nem is lehet megadni a Name paraméternek:

108 . ábra Intellisense az aktuálisan betöltött modulokat utatja

A lehetséges értékeket az alábbi sorban állítjuk be:

        $arrSet = Get-Module | Select-Object -ExpandProperty name



Word To HTML Converter