Hallo zusammen

Dieser Blog Eintrag soll der Beginn sein für eine Reihe von Beiträgen zur deutschen Service Manager Community. Da ich von mehreren Personen gefragt wurde, wie in Service Manager 2012 – oder SvcMgr Workflows mit PowerShell implementiert werden können, sehe ich das als Anlass den ersten Blog Eintrag dem Thema Workflows und PowerShell zu widmen.

Um ein Error Handling in PowerShell Scripts zu implementieren, die für Workflow Activities in SvcMgr verwendet werden, ist ein Error Handling und Benachrichtigung im Fehlerfall zu implementieren. Service Manager kann innerhalb von Workflows nur anzeigen, ob ein Script mittels PowerShell Engine gestartet und ausgeführt wurde, jedoch ist man über die Statusanzeige der Workflows nicht in der Lage, Exit Codes oder Error Messages zu übergeben. Das sorgt schnell für Unmut, da man die Scripts dadurch im Blindflug bedient und oft nicht sagen kann, wieso nun ein Wert nicht gesetzt oder eine beliebige Aktion nicht ausgeführt wurde.

Ich will nun beschreiben wie ein einfaches Error Handling implementiert werden kann, welches auch ohne tiefe .NET Error Objekt Erfahrung in fast allen Fällen angewendet werden kann. Ziel soll sein, ein einfaches Werkzeug mit hohem Wiederverwendungswert zu bieten. Natürlich ist das bestimmt nicht der Weisheit letzter Schluss und ich bin gerne für Feedback offen um auch diesen Beitrag weiter zu entwickeln.

Erkennen von Fehlern:

Bei einem Fehler in PowerShell wird nach jedem Command abgefragt, ob das letzte Kommando erfolgreich war oder nicht:

If($? -ne $true)

Auf dieser Basis lässt sich dann die Exception, die von PowerShell geworfen wird, an eine Funktion übergeben. Da die Scripts innerhalb eines Try – Catch Blocks laufen, wird zunächst die Exception geworfen:

 {

   Throw $error[0].exception

  }

Verarbeitung des Fehlers:

Im Catch Block findet sich dann der Funktionsaufruf mit der Übergabe des Error Objekts und des Activity Objects:

Catch

 {

  LogError $error[0] $Activity

  Exit 1

 }

Die Aktivität ist für die Funktion wichtig, da die Funktion die Fehlerbenachrichtigung baut und diese an die Property outResult an die Aktivität zurückgibt.

Die Funktion wird initialisiert mit den Übergabewerten $objError und $objActivity

Function LogError ($objError, $objActivity)

Als erstes wird die Variable outResult erzeugt. Diese beinhaltet die folgende Struktur:

Source:                                Activity Title

ID:                          Error ID

Text:                     Exception Message; Script Stack Trace

{

    $outResult = ("Source: "+$objActivity.Title+"ID:"+$objActivity.Id,

                $objError.FullyQualifiedErrorId,

                $objError.exception.message,

                $objError.ScriptStackTrace)

   

 # Danach wird der Inhalt von outResult in die Property outResult der Aktivität geschrieben:

$objActivity | SetSCSMObject –Propery outResult –Value $outResult

}

Das CmdLet Set-SCSMObject ist Teil der SMLets, die unter smlets.codeplex.com heruntergeladen werden können.

 

Unterstützung für Error Handling in der Aktivität:

Um die Fehlerausgabe später in der Console lesen zu können muss eine vererbte Klasse für die Aktivität angelegt werden und die Property „outResult“ hinzugefügt werden.

  <ClassTypes>

        <ClassType ID="System.WorkItem.WorkflowActivity.MyAutomatedActivity"

                    Accessibility="Public"

                    Abstract="false"

                    Base="ActivityLibrary!System.WorkItem.Activity"

                    Hosted="false"

                    Singleton="false"

                    Extension="false">

          <Property ID="outResult"

                           Type="string"

                           AutoIncrement="false"

                           Key="false"

                           CaseSensitive="false"

                           MaxLength="2048"

                           MinLength="0"

                           Required="false"

                           Scale="0" />

        </ClassType>

 </ClassTypes>

Zu bemerken ist, dass die MaxLength für das neue Property 2048 Byte groß ist. Je nach Volumen des Fehlers ist es notwendig, die vorgegebene Größe von 256 Byte zu erweitern.

Im Anschluss daran kann ein beliebiges PowerShell Script mit Hilfe des Authoring Tools in einen Workflow geladen und in SvcMgr importiert werden.

Implementieren eines PowerShell Scripts als Workflow in SvcMgr:

Öffnen des Authoring Tools und anlagen eines neuen Management Packs:

Demo.Example.Base

Danach Erstellen einer neuen Klasse:

Die default Property löschen:

Eine neue Property anlegen: outResult

Den Wert Maximum Length ändern:

Einen neuen Workflow erstellen:

Die Bedingungen festlegen wann der Workflow gestartet werden soll:

In dem Fall soll der Workflow zu einem Ereignis in SvcMgr gestartet werden.

Dieses Ereignis wird genauestens festgelegt:

Wenn die Klasse Demo.Workflow.Activity sich ändert

UND zusätzlich

Activity STATUS = „in Progress“

Dabei ist zu beachten, dass Changed To ausgewählt ist.

Den Wizard beenden und man erhält einen neuen Workflow ohne Aktivitäten:

Hier kann man mit Drag und Drop eine PowerShell Aktivität hinzufügen:

Im Details Window kann neben dem Namen auch der Script Body hinzugefügt werden:

Dabei kann das Script eingefügt / importiert werden:

# -----------------------------------------------------------------------------------------------------------------

Function WriteEvent {

    param($EntryType,

          $ID,

          $Message,

          $Source = "DemoWorkflow",

          $EventGroup = "Service Manager Workflows"

            )

   

    # Check if Event Log Exists

    if (![system.diagnostics.eventlog]::Exists($EventGroup)) {

        new-eventlog -logname $EventGroup -Source $Source

        if($? -ne $true){

            [system.diagnostics.EventLog]::CreateEventSource($Source, "Operations Manager")

            write-eventlog -logname "Operations Manager" -Source $Source  -Message "Create EventLog failed." -id "3" -EntryType "Error"

            }

        }

    # Check if Source Exists

    if (![system.diagnostics.eventlog]::SourceExists($Source))  {

        # Create Event Source

        [system.diagnostics.EventLog]::CreateEventSource($Source, $EventGroup)

        if($? -ne $true){

            [system.diagnostics.EventLog]::CreateEventSource($Source, "Operations Manager")

            write-eventlog -logname "Operations Manager" -Source $Source  -Message "Create Event Source failed." -id "4" -EntryType "Error"

            }

        }

   

    write-eventlog -logname $EventGroup -Source $Source  -Message $Message -id $ID -EntryType $EntryType

   

    }

Function LogError ($objError, $objActivity){

   

    $erroractionpreference = "silentlycontinue"

    $oResult = ("Error in Activity: "+($objActivity.Title).Trim()+"`r`n",

                "Activity ID:"+ ($objActivity.Id).Trim()+"`r`n",

                "Error ID: "+ ($objError.FullyQualifiedErrorId).trim()+"`r`n",

                "Exception: "+($objError.exception.message).Trim()+"`r`n",

                "Position: "+($objError.InvocationInfo.PositionMessage).Trim())

   

        if($Debug -eq "true"){

           

            WriteEvent "Error" "2" $oResult

            }

 

    $objActivity | Set-SCSMObject -Property outResult -Value $oResult

    $Failed = Get-SCSMEnumeration -Name ActivityStatusEnum.Failed

    $objActivity | Set-SCSMObject -Property Status -Value $Failed

    }

 

Try {

   

    Import-Module SMLets -force

   

    # Get Activity by internal ID

    $Activity = Get-SCSMObject -ID $internalActivityID.substring(1,36).ToString()

   

        if($? -ne $true){Throw $error[0].exception}

   

    # Create an error

    $a = new-object "Objektdasnichtexistiert"

 

        if($? -ne $true){Throw $error[0].exception}

   

    # set status to completed

    $Completed = Get-SCSMEnumeration -Name ActivityStatusEnum.Completed

   

        if($? -ne $true){Throw $error[0].exception}

    

    if($? -eq $true){

        $Activity | Set-SCSMObject -Property Status -Value $Completed

        }

       

    remove-module -name SMLets -force

   

    }

 

Catch {

   

    LogError $error[0] $Activity

    Exit 1

    }   

Finally {

    }

 

Auf die Funktion WriteEvent werde ich in einem späteren Blog eingehen.


Zusätzlich muss die Activity ID an das Script übergeben werden um das Ergebnis zurückschreiben zu können. Dazu ist ein Script Property zu erstellen:

Mit Ok wird das Script in den Management Pack übernommen.

Der Management Pack kann nun versiegelt und importiert werden:

Beim speichern des Management Packs wurde vom Authoring Tool eine DLL erzeugt:

Diese DLL muss in den Ordner „C:\Program Files\Microsoft System Center 2012\Service Manager“ kopiert werden.

In der SvcMgr Console kann nun ein neues Template erzeugt werden unter „Library – Templates“:

Für das Template ist ein neuer Management Pack zu erzeugen: Demo.Example.Configuration

Der Name der Aktivität ist in diesem Fall: Demo Activity

In der Generic Form ist auch schon unser outResult Property zu sehen:

In einem Work Item welches Aktivitäten enthalten kann – z.B. ein Service Request – kann nun die neue Aktivität hinzugefügt werden:

Wenn nun ein Ticket erzeugt wird und die Aktivität „an der Reihe“ ist, wird unter Administration – Workflows – Status angezeigt, ob die Aktivität erfolgreich war oder nicht:

Laut Statusanzeige ist das Script Erfolgreich gelaufen, jedoch sieht die Aktivität im Ticket nicht so aus:

Das Property outResult hat nun folgenden Wert:

Error in Activity: Demo Activity

 Activity ID:AC509

 Error ID: TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand

 Exception: Cannot find type [Objektdasnichtexistiert]: make sure the assembly containing this type is loaded.

 Position: At line:67 char:20

+     $a = new-object <<<<  "Objektdasnichtexistiert"

 Damit ist ein Fehler im Script nun auch in der Console sichtbar.

 Viel Spass beim testen.