A PowerShell workflow az nem teljesen PowerShell szkript

A legelső Hello workflow-mat egy picit leegyszerűsítettem, hogy egyszerűbb legyen megérteni, hogy mi is történik:

workflow Hello1 {

    (Get-WmiObject -Class Win32_computersystem).Name

}

Ennek a Hello1-nek az eredménye az AzureWin7-1 gépről futtatva két másik gépre futtatva:

PS C:\> hello1 -PSComputerName azurewin7-2, azuredc-1

AZUREWIN7-1

AZUREWIN7-1

Hoppá! Azt vártam, hogy a két különböző gépnevet kapok vissza, ezzel szemben kétszer visszakaptam a futtató gép nevét. Alakítsuk kicsit át ezt a workflow-t:

workflow Hello2 {

    $c = Get-WmiObject -Class Win32_computersystem

    $c.Name

}

Az „igazi” PowerShell-ben a két megoldás ugyanazt eredményezi, de vajon mit adnak ezek a Workflow Engine-ben? A Hello2 ezt adja:

PS C:\> hello2 -PSComputerName azurewin7-2, azuredc-1

AZUREWIN7-2

AZUREDC-1

Ez már úgy működik, ahogy vártuk, de vajon miért van különbség a két végrehajtás között? Ehhez nézzünk bele egy kicst a XamlDefinition tulajdonságba:

PS C:\> (Get-Command Hello1).XamlDefinition

<Activity

    x:Class="Microsoft.PowerShell.DynamicActivities.Activity_33218380"

    xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"

    xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activi

ties"

    xmlns:local="clr-namespace:Microsoft.PowerShell.DynamicActivities"

    xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.

Activities"

    mva:VisualBasic.Settings="Assembly references and imported namespaces seri

alized as XML namespaces"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:ns0="clr-namespace:System;assembly=mscorlib"

    xmlns:ns1="clr-namespace:Microsoft.PowerShell.Utility.Activities;assembly=

Microsoft.PowerShell.Utility.Activities"

    xmlns:ns2="clr-namespace:Microsoft.PowerShell.Activities;assembly=Microsof

t.PowerShell.Activities"

    xmlns:ns3="clr-namespace:System.Activities;assembly=System.Activities"

    xmlns:ns4="clr-namespace:System.Management.Automation;assembly=System.Mana

gement.Automation"

    >

    <Sequence>

        <ns2:SetPSWorkflowData>

            <ns2:SetPSWorkflowData.OtherVariableName>Position</ns2:SetPSWorkfl

owData.OtherVariableName>

            <ns2:SetPSWorkflowData.Value>

                <ns3:InArgument x:TypeArguments="ns0:Object">

                    <ns2:PowerShellValue x:TypeArguments="ns0:Object" Expressi

on="'2:5:Hello1'" />

                </ns3:InArgument>

            </ns2:SetPSWorkflowData.Value>

        </ns2:SetPSWorkflowData>

        <ns1:WriteOutput>

            <ns1:WriteOutput.NoEnumerate>[System.Management.Automation.SwitchP

arameter.Present]</ns1:WriteOutput.NoEnumerate>

            <ns1:WriteOutput.InputObject>

                <InArgument x:TypeArguments="ns4:PSObject[]">

                    <ns2:PowerShellValue x:TypeArguments="ns4:PSObject[]" Expr

ession="(Get-WmiObject -Class Win32_computersystem).Name" />

                </InArgument>

            </ns1:WriteOutput.InputObject>

        </ns1:WriteOutput>

        <Sequence.Variables>

            <Variable Name="WorkflowCommandName" x:TypeArguments="ns0:String"

Default = "Hello1" />

        </Sequence.Variables>

    </Sequence>

</Activity>

Nézzük ugyanezt a Hello2-re is:

PS C:\> (Get-Command Hello2).XamlDefinition

<Activity

    x:Class="Microsoft.PowerShell.DynamicActivities.Activity_1184504621"

    xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"

    xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activi

ties"

    xmlns:local="clr-namespace:Microsoft.PowerShell.DynamicActivities"

    xmlns:mva="clr-namespace:Microsoft.VisualBasic.Activities;assembly=System.

Activities"

    mva:VisualBasic.Settings="Assembly references and imported namespaces seri

alized as XML namespaces"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:ns0="clr-namespace:System;assembly=mscorlib"

    xmlns:ns1="clr-namespace:System.Management.Automation;assembly=System.Mana

gement.Automation"

    xmlns:ns2="clr-namespace:Microsoft.PowerShell.Activities;assembly=Microsof

t.PowerShell.Activities"

    xmlns:ns3="clr-namespace:System.Activities;assembly=System.Activities"

    xmlns:ns4="clr-namespace:Microsoft.PowerShell.Utility.Activities;assembly=

Microsoft.PowerShell.Utility.Activities"

    >

    <Sequence>

        <ns2:SetPSWorkflowData>

            <ns2:SetPSWorkflowData.OtherVariableName>Position</ns2:SetPSWorkfl

owData.OtherVariableName>

            <ns2:SetPSWorkflowData.Value>

                <ns3:InArgument x:TypeArguments="ns0:Object">

                    <ns2:PowerShellValue x:TypeArguments="ns0:Object" Expressi

on="'2:10:Hello2'" />

                </ns3:InArgument>

            </ns2:SetPSWorkflowData.Value>

        </ns2:SetPSWorkflowData>

        <ns2:SetPSWorkflowData>

            <ns2:SetPSWorkflowData.OtherVariableName>Position</ns2:SetPSWorkfl

owData.OtherVariableName>

            <ns2:SetPSWorkflowData.Value>

                <ns3:InArgument x:TypeArguments="ns0:Object">

                    <ns2:PowerShellValue x:TypeArguments="ns0:Object" Expressi

on="'2:10:Hello2'" />

                </ns3:InArgument>

            </ns2:SetPSWorkflowData.Value>

        </ns2:SetPSWorkflowData>

        <ns2:GetWmiObject>

            <ns2:GetWmiObject.Class>Win32_computersystem</ns2:GetWmiObject.Cla

ss>

            <ns2:GetWmiObject.Result>[c]</ns2:GetWmiObject.Result>

        </ns2:GetWmiObject>

        <ns2:SetPSWorkflowData>

            <ns2:SetPSWorkflowData.OtherVariableName>Position</ns2:SetPSWorkfl

owData.OtherVariableName>

            <ns2:SetPSWorkflowData.Value>

                <ns3:InArgument x:TypeArguments="ns0:Object">

                    <ns2:PowerShellValue x:TypeArguments="ns0:Object" Expressi

on="'3:5:Hello2'" />

                </ns3:InArgument>

            </ns2:SetPSWorkflowData.Value>

        </ns2:SetPSWorkflowData>

        <ns4:WriteOutput>

            <ns4:WriteOutput.NoEnumerate>[System.Management.Automation.SwitchP

arameter.Present]</ns4:WriteOutput.NoEnumerate>

            <ns4:WriteOutput.InputObject>

                <InArgument x:TypeArguments="ns1:PSObject[]">

                    <ns2:PowerShellValue x:TypeArguments="ns1:PSObject[]" Expr

ession="$c.Name" />

                </InArgument>

            </ns4:WriteOutput.InputObject>

        </ns4:WriteOutput>

        <Sequence.Variables>

            <Variable Name="WorkflowCommandName" x:TypeArguments="ns0:String"

Default = "Hello2" />

            <Variable Name="c" x:TypeArguments="ns1:PSDataCollection(ns1:PSObj

ect)" />

        </Sequence.Variables>

    </Sequence>

</Activity>

Mit látunk ezekben a kifejezésekben? Azt, hogy a Hello1-ben tevékenység szinten egy értékképzés van csak (Expression="(Get-WmiObject -Class Win32_computersystem).Name"), míg a Hello2-ben külön van egy natív GetWmiObject tevékenyéség:

            <ns2:GetWmiObject.Class>Win32_computersystem</ns2:GetWmiObject.Cla

ss>

            <ns2:GetWmiObject.Result>[c]</ns2:GetWmiObject.Result>

Majd külön az értékképzés:

                    <ns2:PowerShellValue x:TypeArguments="ns1:PSObject[]" Expr

ession="$c.Name" />

Miért fontos ez? Mert ezek a paraméterek, mint például a PSComputerName, csak bizonyos tevékenységekre „öröklődnek”, csak ott érvényesek. Ilyen tevékenység a cmdletekből származtatott tevékenységek. Ezzel szemben az értékadási tevékenységek csak helyben értelmezettek, azaz nem érvényes rájuk a PSComputerName. A parancsértelmező ebben a verzióban még nem ás nagyon mélyre, a Hello1-nél azt látja, hogy valamilyen értéknek a Name tulajdonságát kell venni, ez számára a fő tevékenység, ami csak helyben végezhető, a benne levő WMI lekérdezést már nem vizsgálja tovább. A Hello2-nél külön van számára egy WMI lekérdezés, amit a workflow meghívásából öröklötten a távoli gépeken hajt végre, majd a visszakapott értéket már a helyi gépen alakítgatja.

Összefoglalva: nem mindegy, hogy hogyan fogalmazzuk meg a workflowban az utasításainkat. Legyünk olyan „szájbarágósak” amennyire lehet, azokat a parancsokat, amelyektől elvárjuk a távoli futtatást és egyéb workflow képességeket, ne ágyazzuk be kifejezések mélyére.

Milyen egyéb különbségeket találhatunk a workflow-k és a függvények között? Elsőként az egyik legfurcsábban működő cmdletet vegyünk szemügyre:

workflow Test-AddMember1 {

    $obj = Get-Date

    $obj | Add-Member -MemberType NoteProperty -Name Nap -Value 'Szerda'

    $obj.nap

}

 

Test-AddMember1

 

workflow Test-AddMember2 {

    $obj = Get-Date

    $obj = Add-Member -MemberType NoteProperty -Name Nap -Value 'Szerda' -InputObject $obj -PassThru

    $obj.nap

}

 

Test-AddMember2

Ha a Test-AddMember1 változatot futtatjuk, akkor nem kapunk semmit:

PS C:\> Test-AddMember1

PS C:\>

Ezzel szemben az Add-Member2 válozat hozza az elvárt eredményt:

PS C:\> Test-AddMember2

Szerda

Az Add-Member-nél láttuk, hogy alaphelyzetben „visszefelé” dolgozik, nem ad ki semmit a kimenetén, de a beletöltött objektumokat kiegészíti a meghatározott tagjellemzővel. Na ez nem működik a workflow-ban, csak az a módszer, hogy kérjük a kimenetet a –PassThru kapcsolóval és az eredeti objektumot felüldefiniáljuk a kibővítettel.

Workflow-ban nem használhatunk interakciót, azaz nem kérhetünk be adatot Read-Host-al, se nem felejthetünk el megadni kötelező paramétereket:

PS C:\> workflow GetData {

>>     $name = Read-Host

>>     $name

>> }

>> 

At line:2 char:13

+     $name = Read-Host

+             ~~~~~~~~~

Cannot call the 'Read-Host' command. Other commands from this module have been

 packaged as workflow activities, but this command was specifically excluded.

This is likely because the command requires an interactive Windows PowerShell

session, or has behavior not suited for workflows. To run this command anyway,

 place it within an inline-script (InlineScript { Read-Host }) where it will b

e invoked in isolation.

    + CategoryInfo          : ParserError: (:) [], ParseException

    + FullyQualifiedErrorId : CommandActivityExcluded

A Read-Host-ot már a workflow definiálásának idején kiszúrja és már akkor jelzi a hibát. Ha szót fogadunk a hibaüzenetnek és egy inlinescript blokkba helyezzük, akkor sem fog természetesen működni:

PS C:\> workflow GetData {

>>     $name = inlinescript {Read-Host}

>>     $name

>> }

>> 

PS C:\> GetData

A command that prompts the user failed because the host program or the command

 type does not support user interaction. Try a host program that supports user

 interaction, such as the Windows PowerShell Console or Windows PowerShell ISE

, and remove prompt-related commands from command types that do not support us

er interaction, such as Windows PowerShell workflows.

    + CategoryInfo          : NotImplemented: (:) [Read-Host], HostException

    + FullyQualifiedErrorId : HostFunctionNotImplemented,Microsoft.PowerShell

   .Commands.ReadHostCommand

    + PSComputerName        : [localhost]

A kötelező paraméter hiányát is csak a workflow futtatása közben veszi észre sajnos:

PS C:\> Workflow NewDir {

>>     New-Item -Path c:\temp -Name UjKonyvtar

>> }

>> 

PS C:\> NewDir

Microsoft.PowerShell.Management\New-Item : A command that prompts the user fai

led because the host program or the command type does not support user interac

tion. The host was attempting to request confirmation with the following messa

ge:

    + CategoryInfo          : NotImplemented: (:) [New-Item], HostException

    + FullyQualifiedErrorId : HostFunctionNotImplemented,Microsoft.PowerShell

   .Commands.NewItemCommand

    + PSComputerName        : [localhost]

Az interakció hiánya nem csak adatbekérésnél, hanem kiírásnál, azaz a Write-Host-nál is fennáll, azzal se nagyon próbálkozzunk. Ezzel szemben az egyéb kimeneti stream-eket (error, warning, progress) használhatjuk, bár ezek kiolvasása, főleg ha –AsJob jelleggel futtatjuk a workflow-t, kicsit nehézkesebb.

A következő sajátosság az adatok szerializáltsága:

PS C:\> workflow GetItem {

>>     Get-Item -Path C:\temp

>> }

>> 

PS C:\> $folder = GetItem

PS C:\> $folder | Get-Member

 

 

   TypeName: Deserialized.System.IO.DirectoryInfo

 

Name                  MemberType   Definition

----                  ----------   ----------

GetType               Method       type GetType()

ToString              Method       string ToString(), string ToString(strin...

BaseName              NoteProperty System.String BaseName=temp

Mode                  NoteProperty System.String Mode=d----

Látható, hogy a belül Get-Item-el lekért mappa nem teljes értékű mappa lett, hanem Deserialized, azaz a két általános metóduson kívül más nem érhető el. Ezt azonban ki lehet kapcsolni, ha az adott tevékenységnél használjuk a –PSDisableSerialization paramétert. Ez nem kapcsoló, hanem bool adattípust fogadó paraméter:

PS C:\> workflow GetItem {

>>     Get-Item -Path C:\temp -PSDisableSerialization $true

>> }

>> 

PS C:\> $folder = GetItem

PS C:\> $folder | Get-Member

 

 

   TypeName: System.IO.DirectoryInfo

 

Name                      MemberType     Definition

----                      ----------     ----------

Mode                      CodeProperty   System.String Mode{get=Mode;}

Create                    Method         void Create(), void Create(System....

CreateObjRef              Method         System.Runtime.Remoting.ObjRef Cre...

CreateSubdirectory        Method         System.IO.DirectoryInfo CreateSubd...

Delete                    Method         void Delete(), void Delete(bool re...

Itt már látható, hogy megőrződtek a metódusok is, viszont ennek a módszernek az a hátránya, hogy nem lehet elmenteni az ilyen tevékenységeknél a workflow állapotát, tehát csínján bánjunk ezzel a lehetőséggel.

PowerShellben sok dolgot többféle módon is elvégezhetünk. Például az alábbi workflow-ban kétféleképpen olvasom ki a Computername környezeti változó tartalmát:

PS C:\> workflow TestEnvVar {

>>     $env:COMPUTERNAME

>>     Get-content -path env:computername

>> }

Látható azonban, hogy másképpen értékelődik ki ez a két kifejezés, ha futtatom a workflow-t:

PS C:\> TestEnvVar -PSComputerName AzureWin7-2

AZUREWIN7-1

AZUREWIN7-2

Az első módszer mindig a helyi gépen értékelődik ki, azaz rá nem vonatkozik a –PSComputername paraméter, míg a Get-Content-nek létezik tevékenység megfelelője, így az már ténylegesen a távoli gépen hajtódott végre. Ezt mindig érdemes letesztelni, hogy egy kifejezés vajon képes-e távol futni. Ha nem, akkor ezt azt jelenti, hogy az nem igazi tevékenység, így lehetőség szerint csak akkor használjuk a workflowban, ha ez biztos megfelelő számunkra.

 Vannak olyan esetek is, amikor a „sima” PowerShell logika abszolút nem működik, azaz ki kell találni a megkerülő megoldást. Például létre szeretnék hozni egy egyedi objektumot, majd annak később módosítani akarom az egyik tulajdonságát:

workflow TestPropSet1 {

    $x = New-Object -TypeName PSObject -Property @{egy = 1}

    $x.egy = 10

    $x.egy

}

Ha az ISE-ben szerkesztjük ezt, akkor a szerkesző felületen rögtön a harmadik sorban megjelenik egy aláhúzás, ami jelzi, hogy itt valami hiba van. Ha az egérrel a hiba fölé megyünk, akkor a felugró üzenet azt mondj, hogy ez a fajta értékadás nem működik a workflow-ban, a kifejezés bal oldalán mindenképpen egy változó kell hogy legyen.

Alakítsuk tovább, egy inlinescript blokkban végezük el akkor a tulajdonság módosítását, majd adjuk vissza a módosított objektumot az eredeti változónknak:

workflow TestPropSet2 {

    $x = New-Object -TypeName PSObject -Property @{egy = 1}

    $x = inlinescript {   

        ($using:x).egy = 10

        $using:x

    }

    $x.egy

}

Itt már szerkesztés közben nem kapunk hibát, csak futtatáskor:

PS C:\> TestPropSet2

The workflow 'TestPropSet2' could not be started: The following errors were

encountered while processing the workflow tree:

'DynamicActivity': The private implementation of activity '1:

DynamicActivity' has the following validation error:   Compiler error(s)

encountered processing expression "x".

Value of type 'System.Management.Automation.PSDataCollection(Of

System.Management.Automation.PSObject)' cannot be converted to

'System.Management.Automation.PSObject'.

At line:383 char:21

+                     throw (New-Object

System.Management.Automation.ErrorRecord $ ...

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

~~~~

    + CategoryInfo          : InvalidArgument: (System.Manageme...etersDictio

   nary:PSBoundParametersDictionary) [], RuntimeException

    + FullyQualifiedErrorId : StartWorkflow.InvalidArgument

Azaz nem nem engedi ugyanazt a változót módosítani, amelyiket átadtunk az inlinescript-nek. Akkor csavarjunk mégegyet, használjunk egy átmeneti változót és a végén annak tartalmát töltsük be $x-be:

workflow TestPropSet3 {

    $x = New-Object -TypeName PSObject -Property @{egy = 1}

    $y = inlinescript {   

        ($using:x).egy = 10

        $using:x

    }

    $x = $y

    $x.egy

}

Így már működik:

PS C:\> TestPropSet3 -PSComputerName AzureWin7-2

10

A tanulság az, hogy ami PowerShellben egy egyszerű művelet, az workflow-ban nem biztos, hogy alkalmazható, de azért kis trükközéssel és az inlinescript blokk alkalmazásával azért általában találhatunk megkerülő megoldást.

A következő eltérés, hogy az univerzális, sokat tudó switch kifejezés is csak korlátozásokkal használható. Például:

workflow TestBulkOps {      

    switch -CaseSensitive ("a"){

        {$_ -eq "a"}{ "alfa"}

    }

}

Nem véletlenül tettem oda a –CaseSensitive kapcsolót, mert anélkül eleve figyelmeztet, hogy a workflow-ban csak az a módja használható. Ezen kívül csak statikus feltételek alkalmazhatók, azaz csak egyenlőségviszgálat lehet a feltétel az elágazásra, de ilyenkor csak az érték kerül az elágazás első szkriptblokkja helyett, így:

workflow TestBulkOps {      

    switch -CaseSensitive ("a"){

        "a" { "alfa"}

    }

}

 

 

 

 



Word To HTML Converter