Updating a Change Request when Activities are Updated Using PowerShell in a Workflow

Updating a Change Request when Activities are Updated Using PowerShell in a Workflow

  • Comments 18
  • Likes

Andreas Rynes, a Microsoft Consulting Services consultant, has come up with a clever solution to a problem.  The problem is how to surface up the contained activity details to the change request level.  This makes it easier to format/send notifications and such things.  He wrote a guest blog post about his solution which uses the SMLets PowerShell module (a CodePlex solution) to get the activity and update the parent change request description.

Thanks for figuring this out and sharing it Andreas!

====================================================

During my work with Service Manager I’ve got a requirement that data from custom activities should be update to the main form of the change request. So in the case of a change request template that has multiple activities and all are having some custom fields, every time a single activity from a change request is updated with some data, the complete list of activity data (not just the one that got updated, also all the other activities from the same change request) should be written to the description field of the parent change request or should be updated if it already exists. Manually entered descriptions from users should be kept and not overwritten by that solution.

The overall idea is to implement that in a generic way, so that even custom activity classes that are implemented in the future are working with that solution as well.

This is how it might look, after you’ve implemented my solution:

clip_image002

Seems not possible with the out of the box features of SCSM, but it is possible with a custom workflow and Windows Powershell. Thanks to Jim Truher, there is a Service Manager Cmdlet for Powershell and thanks to the Product itself there is an easy way to create a custom workflow with a Powershell script.

1. Custom Activity

So I don’t tell you in this blog post, how to create a custom activity, but there is an excellent post about that from Travis Wright and Jim Pitts: http://blogs.technet.com/b/servicemanager/archive/2010/11/16/how-to-automate-vm-provisioning-in-20-minutes-using-service-manager-and-opalis.aspx

So after you’ve created a similar sample of a custom activity that has some custom attributes and a custom form to enter the data and you’ve imported that to SCSM, you can create an activity template and a change request template that has this new custom activity defined. (You’ll find both templates in the MP attached to this blog post)

2. Custom Workflow in the SCSM Authoring Tool

Now it’s time to create the custom workflow, go to Service Manager Authoring Tool, create a new MP and a new workflow, give it a name and enter some description in the wizard.

On the next page you’ve been asked to provide the trigger (time based or database based). This time we need to specify “Run only when a database object meets specified conditions” as we like to trigger that workflow when an activity is updated/changed.

On the next page click the Browse button and choose Activity, that means that this workflow triggers whenever an class that inherited from Activity is updated (so in our case we’ve a custom activity that inherits from ManualActivity, which inherits from the base class Activity).

Choose “When an object of the selected class is updated” in the Change Event box. You could possible define additional criteria, but this time we don’t do that (as this should be a generic solution). Then you’re done, click on Next, Create and Close button.

clip_image003

After that you’ll find the new workflow completely empty in the main pane in the middle and you’re able to add a new shape to the area saying “Drop activities to create a Sequential Workflow”.

Drag and drop a “Windows Powershell Script” shape to the workflow and change the name to something that makes sense for your sample.

clip_image005

This is the result at that stage:

clip_image006

3. Powershell Script and Parameter

As long as you see the red exclamation mark in the corner of a shape, there is some information missing. In this case the script body and its parameter are not defined yet, so that makes the Powershell worthless for now, so we are going to define those values:

clip_image008

The script body:

set-executionpolicy -executionPolicy ByPass

$a = (get-module|%{$_.name}) -join " "

if(!$a.Contains("SMLets")){Import-Module SMLets -ErrorVariable err -Force}

This part defines the execution policy for the Powershell session. For that sample I’ve used ByPass, which allows to run everything, which might not be perfect and you should consider digital signing the cmdlets you are using. Then I’m going to get all loaded modules (get-module), check if the SMLets module is already loaded (Contains) and load it if not done before. The –ErrorVariable is used for troubleshooting only, so if you’re not going to use the variable you can skip it. I’ll describe troubleshooting techniques for that in an separate post later.

$indexend = (Get-SCSMRelationshipObject -Target (get-SCSMClass System.WorkItem.Activity$)|?{$_.TargetObject -match $activity_id}).SourceObject.ToString().IndexOf(':');

$cr_id = (Get-SCSMRelationshipObject -Target (get-SCSMClass System.WorkItem.Activity$)|?{$_.TargetObject -match $activity_id}).SourceObject.ToString().Substring(0,$indexend);

Then I’m using the SMCmdlet for the first time to get the parent CR from the activity. The activity id is passed into the script (we’ll define that later!) and with Get-SCSMRelationshipObject I’ll get the parent change request of the activity parameter.

$activities = Get-SCSMRelatedObject -SMObject (get-scsmobject (get-scsmclass System.workitem.ChangeRequest$)|?{$_.Id -eq $cr_id}) -Relationship (Get-SCSMRelationshipClass System.WorkItemContainsActivity)

$ActivityDict = @{}

$countKeys = 0

foreach($activity in $activities)

{

foreach($val in $activity.Values)

{

if($val.Type -match "^z.*")

{

$countKeys++

$ActivityDict[$activity.id+"@"+$val.Type] = $val.Value

}

}

}

This parts grabs all the related activities from the parent change request using the SCSMRelatedObject and the SCSMRelationshipClass “WorkItemContainsActivity”. For each activity I’m then look up for values from fields that are name with a starting z and adding those into a dictionary and count them (for later use).

$description_start = "-------------------------------------------------" + "`n" + "-- Activity Data Begin (Do not change!) --" + "`n" + "-------------------------------------------------" + "`n"

$description_ende = "------------------------------------------------" + "`n" + "-- Activity Data End (Do not change!) --" + "`n" + "------------------------------------------------" + "`n"

$desc_beginindex= (Get-SCSMObject (get-SCSMClass System.WorkItem.ChangeRequest$) -Filter "Id -eq $cr_id").Description.ToString().IndexOf("Activity Data Begin");

$desc_endeindex= (Get-SCSMObject (get-SCSMClass System.WorkItem.ChangeRequest$) -Filter "Id -eq $cr_id").Description.ToString().IndexOf("Activity Data End");

if($desc_beginindex -eq -1) {$desc_beginindex = 0 } else {$desc_beginindex -= 53}

if($desc_endeindex -eq -1) {$desc_endeindex = 0} else {$desc_endeindex += 87}

So then it’s the hard part of the solution J Building the data string in a nice and readable way, which is also easy to recognize to make it updateable. So with the method IndexOf we look for an existing activity data block. If there is none we set the begin and end index to 0 otherwise we set it to the beginning and end of the data block.

$description = (Get-SCSMObject (get-SCSMClass System.WorkItem.ChangeRequest$) -Filter "Id -eq $cr_id").Description.ToString()

$descriptionnew = $description_start

foreach($a in $ActivityDict.Keys)

{

$descriptionnew += $a.Substring(0,$a.IndexOf('@')) + " / " + $a.Substring($a.IndexOf('@')+1,$a.Length-$a.IndexOf('@')-1) + ": " + $ActivityDict[$a].ToString() + "`n"

}

$descriptionnew += $description_ende

$descriptionnew += $description.Substring(0,$desc_beginindex) + $description.Substring($desc_endeindex,$description.Length-$desc_endeindex)

Now we grab description that is already in the change request with Get-SCSMObject and filter the output by the change request ID. The goal is to keep that data in the description field and not to overwrite it with the new values. Then we iterate the collection and build up the new description, the first part is the activity data then we add the existing data (except the activity data that was already there)

if($countKeys -gt 0){

Set-SCSMObject -SMObject (Get-SCSMObject (get-SCSMClass System.WorkItem.ChangeRequest$) -Filter "Id -eq $cr_id") -Property 'Description' -Value $descriptionnew

}

remove-module -name SMLets –force

The count of the custom fields, where the name of the field starts with z defines if the new description is written to the change request or not. If there are no custom fields, nothing is changed in the change request. So we are using the $count variable and then use Set-SCSMObject to set the description field. At the end we remove the SMLets module, so that we can Import it again the next time (If you don’t remove it, it might be a problem importing the module again)

Now it’s time to define the only input parameter for the script. Click on the “Script Properties” tab and enter activity_id in the name field (don’t use the $ here, just in the script you need to reference to $activity_id)

clip_image010

In the value choose a class property and choose the ID of the activity class. This is the only input parameter that we need for that solution.

clip_image012

4. Deployment

Now you’re done with that and save the solution in the Authoring Tool. After that a dll is generated in the Authoring Tool location. This dll has the same name as your MP and must be copied to the Service Manager installation location and the Management Pack needs to be imported in the SCSM console as well.

The last thing that needs to be done is the download and installation of the Service Manager Cmdlet from that location: http://smlets.codeplex.com/

Please be aware of unblock the SMLets archive download before installing it (see documentation at http://smlets.codeplex.com/documentation)

Copy the content as described to the powershell default location c:\windows\system32\WindowsPowerShell\v1.0\Modules\SMLets

In the attachment you’ll find the Management Pack that contains a custom activity, a custom form for that activity and the templates for the activity and change request and the custom workflow of course.

Attachment: VirtualMaschineManualActivity.xml
Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi Travis,

    great post!

    I'm using that idea to develop a different purpose: show the "in progress" activity on the custom view "Change Requests: In progress" without opening the change request form.

    To do this i've made this steps:

    1) create a new mp extends the class change request, manual activity and review activity

    2) create a custom data (string) on the extended change request class

    3) create a new workflow that starts on every update on the class activity (from pending to in progress)

    4) for every update i run this powershell script:

    set-executionpolicy -executionPolicy ByPass

    $a = (get-module|%{$_.name}) -join " "

    if(!$a.Contains("SMLets")){Import-Module SMLets -ErrorVariable err -Force}

    $indexend = (Get-SCSMRelationshipObject -Target (get-SCSMClass System.WorkItem.Activity$)|?{$_.TargetObject -match $activity_id}).SourceObject.ToString().IndexOf(':');

    $cr_id = (Get-SCSMRelationshipObject -Target (get-SCSMClass System.WorkItem.Activity$)|?{$_.TargetObject -match $activity_id}).SourceObject.ToString().subString(0,$indexend);

    $activity_in_progress = Get-SCSMRelatedObject -SMObject (get-scsmobject (get-scsmclass System.workitem.ChangeRequest$)|?{$_.Id -eq $cr_id}) -Relationship (Get-SCSMRelationshipClass System.WorkItemContainsActivity) | ?{ (($_.status.displayname -eq "In Corso") -or ($_.status.displayname -eq "In Progress"))  }

    $titoloActivity = $activity_in_progress.displayname;

    //activity_in_progress is the custom data variable on the change request class

    Set-SCSMObject -SMObject (Get-SCSMObject (get-SCSMClass System.WorkItem.ChangeRequest$) -Filter "Id -eq '$cr_id'") -Property 'activity_in_progress' -Value $titoloActivity

    remove-module -name SMLets –force

    5) save and seal the management pack (import MP and copy the dll file on the SM installation directory)

    The workflow starts correctly and I can see the activity_in_progress data with the activity name actually in progress, BUT the workflow remains in SCHEDULED status... after 30 minutes it go on FAILED status with a timeout error.

    Why?

    thanks!

  • @Donato -

    Your script is timing out because you are trying to get too much data from the data access service and then filtering through it on the client.  You need to make SQL Server do the work.  see this blog post:

    blogs.technet.com/.../properly-querying-scsm-using-smlets-get-scsmobject-cmdlet.aspx

  • I am having a hard time getting this running. When I run the powershell script with some constants and run it on the Managment server it works fine. When I put the script into a workflow the workflow succeeds but the fields do not update. When I look up jobstatus in the database I see that I am getting the following error 'Get-SCSMRelationshipObject' is not recognized as the name of a cmdlet. I get the same error for Set-SCSMObject.

    I can run get-scsmcommand and see all the cmdlets and I have another workflow that uses smlets successfully. Has anyone else run into a similar issue?

  • @Lee - sounds like SMLets is not being imported in your script.  I would replace all of this

    ==============

    $a = (get-module|%{$_.name}) -join " "

    if(!$a.Contains("SMLets")){Import-Module SMLets -ErrorVariable err -Force}

    ==============

    with "Import-Module SMlets" at the top of your script body in the authoring tool and see if that helps.

  • @Travis Thanks for the reply.

    I figured out what the issue was. There was another powershell workflow that had been imported previously that did not have remove-module -name SMLets -force line added. Once I added this to the other workflow mine started working.

    Thanks,

    -Lee

  • Travis,

    I am using this blog post as a guideline and applying it to another concept but I noticed in 2012 it is asking me what modules are used in the workflow after providing the script properties required. Does this mean that I do not have to import the SMLets module within my script?

    Thanks,

    Ryan Durbin

  • @Ryan -

    I always import the module as part of the script instead of specifying it as part of the workflow activity configuration.  It's more of a personal choice though.  It would probably work either way.

  • Could I start from step 2 if I want to have it run any and all activities?

  • Hi Travis,
    In SCSM Custom Workflow, how do get the output from a Powershell script activity passed over to next activities??
    Eg: Consider that we have one powershell script, which gets the VMID as input from related VM Object and creates a snapshot. After creating the snapshot, it should produce two output as "SnapshotName" and "CreatedTime".... This two output parameters has to be passed over to next Powershell script.....

    I couldn't find any place in the script configuration dialog box to specify the output parameters... Please HELP!!!!