Meglevő cmdletek kiegészítése, átalakítása

A get-random cmdletnél láttuk a 1.8.1 Véletlen szám generálás és annál sokkal több (Get-Random) fejezetben, hogy – bár fantasztikusan jó kis cmdlet – de mégsem túl egyszerű vele 0 és 1 közötti véletlen lebegőpontos számot generáltatni. Merthogy ha 1-et adok meg maximum értéknek, akkor ő csak egy 1-nél kisebb véletlen nem negatív egészet fog generálni, ami véletlenül mindig 0. Jó lenne, átalakítani ezt a cmdletet úgy, hogy ha nem adok neki –Maximum paramétert, akkor adjon 0 és 1 közti lebegőpontos számot, azaz használatával a Commodore és ZX Spectrum világába megyünk vissza, hiszen akkoriban működött így az ottani véletlenszám-generáló függvény. És ha már belenyúlunk, akkor szeretnék neki egy újabb paramétert is, mondjuk, legyen egy „float” nevű kapcsoló, ami pedig arra jó, hogy ha egészet adok neki maximumként, akkor maximum ekkora lebegőpontos véletlen számot adjon. Ráadásul az eredeti funkcionalitást nem akarom leprogramozni, a működésnek más esetekben pontosan ugyanúgy kell folynia, mint eddig. Nem tűnik első hallásra egyszerűnek ez a feladat, de szerencsére elég sok segítséget kapunk a PowerShelltől, illetve a .NET keretrendszertől.

A PowerShellben minden objektum, még a cmdletek is objektumok. Egy speciális osztály, a System.Management.Automation.CommandMetaData képes megjeleníteni egy meglevő cmdlet jellemzőit:

[6] PS C:\> $metadata = New-Object System.Management.Automation.CommandMetaData

 (Get-Command get-random)

[7] PS C:\> $metadata

 

 

Name                    : Get-Random

CommandType             : Microsoft.PowerShell.Commands.GetRandomCommand

DefaultParameterSetName : RandomNumberParameterSet

SupportsShouldProcess   : False

SupportsTransactions    : False

ConfirmImpact           : Medium

Parameters              : {[SetSeed, System.Management.Automation.ParameterMet

                          adata], [Maximum, System.Management.Automation.Param

                          eterMetadata], [Minimum, System.Management.Automatio

                          n.ParameterMetadata], [InputObject, System.Managemen

                          t.Automation.ParameterMetadata]...}

Egy másik osztály ezen információk alapján képes legenerálni azt a függvénydefiníciós vázat, amibe belehelyezhetjük a testre szabásainkat. Nézzük tehát, hogyan hívhatjuk elő ezt a vázat:

[10] PS C:\> [Management.Automation.ProxyCommand]::Create($metadata)

[CmdletBinding(DefaultParameterSetName='RandomNumberParameterSet')]

param(

    [ValidateNotNull()]

    [System.Nullable``1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neut

ral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neu

tral, PublicKeyToken=b77a5c561934e089]

    ${SetSeed},

 

    [Parameter(ParameterSetName='RandomNumberParameterSet', Position=0)]

    [System.Object]

    ${Maximum},

 

    [Parameter(ParameterSetName='RandomNumberParameterSet')]

    [System.Object]

    ${Minimum},

 

    [Parameter(ParameterSetName='RandomListItemParameterSet', Mandatory=$true,

 Position=0, ValueFromPipeline=$true)]

    [ValidateNotNullOrEmpty()]

    [System.Object[]]

    ${InputObject},

 

    [Parameter(ParameterSetName='RandomListItemParameterSet')]

    [ValidateRange(1, 2147483647)]

    [System.Int32]

    ${Count})

 

begin

{

    try {

        $outBuffer = $null

        if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))

        {

            $PSBoundParameters['OutBuffer'] = 1

        }

        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-Random',

 [System.Management.Automation.CommandTypes]::Cmdlet)

        $scriptCmd = {& $wrappedCmd @PSBoundParameters }

        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.Com

mandOrigin)

        $steppablePipeline.Begin($PSCmdlet)

    } catch {

        throw

    }

}

 

process

{

    try {

        $steppablePipeline.Process($_)

    } catch {

        throw

    }

}

 

end

{

    try {

        $steppablePipeline.End()

    } catch {

        throw

    }

}

<#

 

.ForwardHelpTargetName Get-Random

.ForwardHelpCategory Cmdlet

 

#>

Ebben a kimenetben egyetlen karaktert sem én gépeltem be! Maga a Management.Automation.ProxyCommand osztály Create statikus metódusa írta ezt meg helyettem az eredeti cmdlet meta adatai alapján. Természetesen ezt nem a képernyőn szeretném látni, hanem be szeretném tenni egy fájlba, amihez egy kis függvénydefiníciós körítést is teszek:

[15] PS C:\> "function get-randomnew {`r`n" + [Management.Automation.ProxyComma

nd]::Create($metadata) + "}" > C:\munka\get-randomnew.ps1

Nézzük akkor egyben ezt a fájlt! Én már beletettem két megjegyzést, hogy ebbe a vázba majd hol kell módosítani ahhoz, hogy a saját igényeink szerinti működést kapjunk.

function get-randomnew {

[CmdletBinding(DefaultParameterSetName='RandomNumberParameterSet')]

param(

 

<#IDE KELL BEILLESZTENI A SAJÁT EXTRA PARAMÉTEREKET#>

 

    [ValidateNotNull()]

    [System.Nullable``1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]

    ${SetSeed},

 

    [Parameter(ParameterSetName='RandomNumberParameterSet', Position=0)]

    [System.Object]

    ${Maximum},

 

    [Parameter(ParameterSetName='RandomNumberParameterSet')]

    [System.Object]

    ${Minimum},

 

    [Parameter(ParameterSetName='RandomListItemParameterSet', Mandatory=$true, Position=0, ValueFromPipeline=$true)]

    [ValidateNotNullOrEmpty()]

    [System.Object[]]

    ${InputObject},

 

    [Parameter(ParameterSetName='RandomListItemParameterSet')]

    [ValidateRange(1, 2147483647)]

    [System.Int32]

    ${Count})

 

begin

{

    try {

        $outBuffer = $null

        if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))

        {

            $PSBoundParameters['OutBuffer'] = 1

        }

 

<#IDE KELL BEILLESZTENI AZT A SAJÁT KÓDOT, MELLYEL A PARAMÉTEREKET ALAKÍTOM ÁT, ILLETVE HA A NEM CSŐFELDOLGOZÓ SZAKASZBA SZERETNÉK BEAVATKOZNI#>

 

        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-Random', [System.Management.Automation.CommandTypes]::Cmdlet)

        $scriptCmd = {& $wrappedCmd @PSBoundParameters }

        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)

        $steppablePipeline.Begin($PSCmdlet)

    } catch {

        throw

    }

}

 

process

{

    try {

<#IDE LEHET BEILLESZTENI A SAJÁT KÓDOT, HA A CSŐFELDOLGOZÁSBA SZERETNÉNK BEAVATKOZNI#>

        $steppablePipeline.Process($_)

    } catch {

        throw

    }

}

 

end

{

    try {

<#IDE LEHET BEILLESZTENI A SAJÁT KÓDOT, HA A CSŐFELDOLGOZÁS VÉGÉBE SZERETNÉNK BEAVATKOZNI#>

        $steppablePipeline.End()

    } catch {

        throw

    }

}

<#

 

.ForwardHelpTargetName Get-Random

.ForwardHelpCategory Cmdlet

 

#>

}

Ebbe a vázba, a paraméterdefiníciós rész megjelölt helyére illesztem be egyrészt a saját –Float nevű kapcsoló paraméteremet:

    [Parameter(ParameterSetName='RandomNumberParameterSet')]

    [switch]

    ${Float},

És a begin szekcióba a következő kódot:

          if($pscmdlet.ParameterSetName -eq 'RandomNumberParameterSet' -and

                ($PSBoundParameters.ContainsKey('Float') -or

                      -not $PSBoundParameters.ContainsKey('Maximum')))

          {

                if($PSBoundParameters.ContainsKey('Float')){[Void] $PSBoundParameters.Remove('Float')}

                if($PSBoundParameters.ContainsKey('Maximum')) {$PSBoundParameters.Maximum = [float] $PSBoundParameters.Maximum}

                else{$PSBoundParameters.Maximum = [float] 1}

          }

A kód annyit csinál, hogy megnézi elsőként, hogy a RandomNumberParameterSet paraméterezést használjuk-e, hiszen ezzel generáljuk a véletlen számokat. A másik paraméterezés működésébe, amellyel egy gyűjteményből választunk ki véletlenül elemeket, nem akarok belenyúlni. Emellett még annak is teljesülnie kell, hogy vagy a –Float kapcsoló jelen van, vagy nincsen megadva –Maximum paraméter.

Ezután, ha van –Float, akkor azt a paraméterek közül kiveszem, hiszen azt az „igazi” get‑random nem tudja értelmezni. Ha volt -Maximum és mellette –Float, akkor a maximum értékét átalakítom lebegőpontossá, hogy a generált véletlen szám is az legyen, ha meg nem volt maximum, akkor létrehozok egy lebegőpontos 1-et maximumként. Ez utóbbi eset idézi fel a Commodore és ZX Spektrumok véletlen szám generálását.

Pillantsunk bele a „vázba” is, azaz abba a kódba, amelyet a

Management.Automation.ProxyCommand

generált számunkra. A paraméterdefiníciós részben semmi különleges nincsen, egy „sima” fejlett függvénydefinícióknál megszokott részt láthatunk. Majd van egy kicsit trükkösebb rész, amely az ‑OutBuffer paramétert veszi át, és ha ez definiálva van a függvényem hívásánál, akkor ezt átírja 1-re annak érdekében, hogy a csőfeldolgozó szakasz egyesével kapja az elemeket. Ezzel azt érjük el, hogy a saját csőfeldolgozó kódrészünknek könnyebb dolga lesz.

Majd a $wrappedCmd változóba berakja az „eredeti” get-random cmdletet. Erre azért van szükség, mert én a függvényemnek adhattam volna get-random nevet is. Ilyenkor, ha ebben a kódban csak egyszerűen meghívnám a get-random-ot, akkor valójában rekurzívan hívnám meg saját magát. Ehhez a $scriptCmd változóba hozzábiggyesztjük a $PSBoundParameters-ben tárolt paramétereket a passzírozó operátorral. Majd nekikezd a csőfeldolgozásnak, méghozzá un. steppable módon, azaz saját kóddal megtűzdelt, elemenként felszakított lépésekkel.

Mindenütt try blokkokban történik a műveletvégzés, így ha bármilyen hiba lép fel, akkor a catch szekciókban lehetőségünk van saját hibakezelő kód beillesztésére is.

Láthattuk, hogy viszonylag egyszerűen lehet testre szabni a gyári cmdleteket. Ezzel nagyon nagy lökést kapott a PowerShell közösség olyan kis szkriptek, un. proxy függvények létrehozására, amelyekkel a tovább lehet gazdagítani a PowerShell cmdletek funkcióit.



Word To HTML Converter