C# code snippets for Service Manager #2

What seems like a long time ago now, way back in May, I posted C# code snippets for Service Manager #1, showing you how to do various things using the SDK and Service Manager in C#.

I’ve been meaning to revisit this and add a 2nd post but I really don’t know where the time goes and I’ve only just got around to doing it.

I hope you find these useful…

Custom Console Tasks

Console tasks must inherit ConsoleCommand from Microsoft.EnterpriseManagement.UI.SdkDataAccess.dll and the code that is executed when you click the task is in ExecuteCommand():

public class TestTask : ConsoleCommand
{
    public TestTask()
    {
    } 

    public override void ExecuteCommand(IList<NavigationModelNodeBase> nodes, NavigationModelNodeTask task, ICollection<string> parameters)
    {
    }
}

Each node in nodes is a selected object. You can get the EnterpriseManagementObject to work on for a node like this:

//Get selected object
EnterpriseManagementObject emo = null;       

if (nodes[0] is EnterpriseManagementObjectNode)
{
    emo = (nodes[0] as EnterpriseManagementObjectNode).SDKObject;              
}
else if (nodes[0] is EnterpriseManagementObjectProjectionNode)
{
    EnterpriseManagementObjectProjection emop = (EnterpriseManagementObjectProjection)(nodes[0] as EnterpriseManagementObjectProjectionNode).SDKObject;
    emo = emop.Object;      
}

If your task is running from a view, you can tell the console to refresh the view after updating one or more instances like this:

public override void ExecuteCommand(IList<NavigationModelNodeBase> nodes, NavigationModelNodeTask task, ICollection<string> parameters)
{
     //Do some actions then refresh the view
     this.RequestViewRefresh();
}

Console tasks can be run from a view or a form, and how you handle data is different depending on this. For example, if you run a console task from a view, you might work with an EnterpriseManagementObject and Commit() your changes. If you run the task from a form, you must work with the current instance data and this data is only saved when the user clicks OK or Apply (or you programmatically save it).

Instances (IDataItem)

I should point out that Microsoft keep telling us that the IDataItem interface is not a supported or documented part of the SDK. However, it is impossible not to use it, at some point you are just going to have to use IDataItem and hope that someone at Microsoft doesn’t do the dirty and change the interface in a future release.

The way I use to determine if my current instance is running from a form or a view on a console task is this:

if (nodes[0].Location.AbsoluteUri.IndexOf("FormDisplay", 0) != -1) bIsInstance = true;

You can get the current object instance (IDataItem) to work on like this:

//Get the current instance (2012)
IDataItem i = Microsoft.EnterpriseManagement.GenericForm.FormUtilities.Instance.GetFormDataContext(nodes[0]);

Properties and type projections can then be accessed using an indexer of the property name:

//Id
string sId = i["Id"] as string;    
//Internal Id
Guid gId = (Guid)i["$Id$"];
//Change Title
i["Title"] = "This is a new Title for the object";

If you are using a form instance, changing properties such as the Title example above will be reflected on the form automatically.

When working on a form, you can set simple relationships such as the Resolved By User relationship on an instance like this:

//Get the Microsoft.AD.User class
ManagementPackClass mpcADUser = emg.EntityTypes.GetClass("Microsoft.AD.User", mpWindows);
//Set resolved by user
EnterpriseManagementObjectDataType dataType = new EnterpriseManagementObjectDataType(mpcADUser); //Microsoft.EnterpriseManagement.UI.SdkDataAccess.DataAdapters
IDataItem iUser = dataType.CreateProxyInstance(emoUser); //Microsoft.EnterpriseManagement.UI.DataModel
i["ResolvedByUser"] = iUser;

This will update the instance data on the form but will not be saved until you click OK or Apply.

It is possible to programmatically save instance data like this:

//Save the instance i (2012)
Microsoft.EnterpriseManagement.UI.Extensions.Shared.ConsoleContextHelper.Instance.UpdateInstance(i); 

To determine the properties that are available to use for the instance, compile your assembly using the “debug” configuration and copy the .dll and .pdb to “C:\Program Files\Microsoft System Center 2012\Service Manager” (or wherever your SCSM console is installed). Set a suitable breakpoint in your code and run the console and attach to it’s process when the taskbar icon has appeared.

You can then inspect the property cache, see all the available properties, their values and data types (if applicable):

See how item 10 is a DataItemCollection? This is a relationship collection. I showed you above how to set a simple one-to-one relationship such as Resolved By User. However, for a one-to-many relationship, such as reviewers on a Review Activity, you can read this data like this:

//Via a form, using the instance, check if this user is a reviewer on this RA
foreach (IDataItem reviewer in (DataItemCollection)(i["Reviewer"]))
{
    if (reviewer["User"] != null)
    {
        if ((Guid)(reviewer["User"] as IDataItem)["$Id$"] == emoCurrentUser.Id)
        {
            //Save instance to check status later and return true
            this.iReviewItem = reviewer;
            return true;
        }
    }
} 

This method can also be used to return the review item for the current user if needed. Of course, the IDataItem must be using a type projection with “Reviewer” and “User” as aliases, for example:

<TypeProjection ID="TypeProjection_Reviewers" Accessibility="Public" Type="Activity!System.WorkItem.Activity.ReviewActivity">
  <Component Path="$Context/Path[Relationship='Activity!System.ReviewActivityHasReviewer']$" Alias="Reviewer">
    <Component Path="$Context/Path[Relationship='Activity!System.ReviewerIsUser']$" Alias="User" />
    <Component Path="$Context/Path[Relationship='Activity!System.ReviewerVotedByUser']$" Alias="VotedBy" />
  </Component>
</TypeProjection>

Notice how I get the internal Id of the reviewer user object? You can get any property using this method:

Guid gInternalId = ((Guid)(reviewer["User"] as IDataItem)["$Id$"];
String sDisplayName = ((string)(reviewer["User"] as IDataItem)["DisplayName"];

You can use IDataItem from views as well, you don’t have to use EnterpriseManagementObject, it’s up to you.

Remember to remove the debug files from the console installation folder when you are done, these will over-ride any version in the current Management Pack.

Working with Workflows

An example of how to reset the schedule time for a workflow in an unsealed Management Pack:

//Sets the Schedule time for a workflow
public void SetScheduleTime(
EnterpriseManagementGroup emg,       
string rule,
string sTime,
string sMPName,
string sMPVer
)
{
    try
    {
        //Get the MP that contains our workflow
        ManagementPack mp = emg.ManagementPacks.GetManagementPack(sMPName, null, new Version(sMPVer));
        //Get the rule with the passed name
        ManagementPackRule mpr = mp.GetRule(rule);
        //Build our new schedule
        string sScheduleXML = String.Format(@"
                    <Scheduler>
                        <WeeklySchedule>
                        <Windows>
                            <Daily>
                            <Start>" + sTime + @"</Start>
                            <End>" + sTime + @"</End>
                            <DaysOfWeekMask>127</DaysOfWeekMask>
                            </Daily>
                        </Windows>
                        </WeeklySchedule>
                        <ExcludeDates />
                    </Scheduler>");    
        //Set new schedule and update the Management Pack            
        mpr.DataSourceCollection[0].Configuration = sScheduleXML;
        mpr.Status = ManagementPackElementStatus.PendingUpdate;
        mp.AcceptChanges();
    }
    catch
    {
    }
}

Return the enabled state for a given workflow:

//Gets the status of the passed workflow
private bool GetWorkflowStatus(
    EnterpriseManagementGroup emg,
    ManagementPack mp,
    string rule
)
{
    try
    {
        ManagementPackRule mpr = mp.GetRule(rule);
        if (mpr.Enabled != ManagementPackMonitoringLevel.@false) return true;
        return false;
    }
    catch
    {
        return false;
    }
}

Enable or disable a given workflow in an unsealed Management Pack:

//Sets the status of the passed workflow
public void SetWorkflowStatus(
    EnterpriseManagementGroup emg,         
    string rule,
    bool bStatus,
    string sMPName,
    string sMPVer
)
{
    try
    {
        //Get MP
        ManagementPack mp = emg.ManagementPacks.GetManagementPack(sMPName, null, new Version(sMPVer));
        //Get rule
        ManagementPackRule mpr = mp.GetRule(rule);
        //Set status
        if (bStatus) mpr.Enabled = ManagementPackMonitoringLevel.@true;
        if (!bStatus) mpr.Enabled = ManagementPackMonitoringLevel.@false;

        //Save changes
        mpr.Status = ManagementPackElementStatus.PendingUpdate;
        mp.AcceptChanges();
    }
    catch
    {
    }
}

More on Reviewers

Up there somewhere I showed you how to get the review item using IDataItem for the current reviewer.

Here’s how it is done using EnterpriseManagementObject:

//System.ReviewerIsUser relationship
ManagementPackRelationship relIsUser = emg.EntityTypes.GetRelationshipClass(new Guid("90da7d7c-948b-e16e-f39a-f6e3d1ffc921"));

//Search for review items for the current RA in emoRA
foreach (EnterpriseManagementRelationshipObject<EnterpriseManagementObject> obj in
    emg.EntityObjects.GetRelationshipObjectsWhereSource<EnterpriseManagementObject>(emoRA.Id, TraversalDepth.OneLevel, ObjectQueryOptions.Default))
{
    if (obj.RelationshipId == relHasReviewer.Id)
    {
        //We have a reviewer relationship, now get the user for it and see if it is the current user in emoCurrentUser
        foreach (EnterpriseManagementRelationshipObject<EnterpriseManagementObject> objUser in
            emg.EntityObjects.GetRelationshipObjectsWhereSource<EnterpriseManagementObject>(obj.TargetObject.Id, TraversalDepth.OneLevel, ObjectQueryOptions.Default))
        {
            if (objUser.RelationshipId == relIsUser.Id)
            {
                //Do we have a match? 
                if (objUser.TargetObject.Id == emoCurrentUser.Id)
                {
                    //Get the review item on this RA for the current console user
                    emoReviewItem = obj.TargetObject;
                    break;
                }
            }
        }
    }
}

Of course, you could also use a type projection, ObjectProjectionCriteria and IObjectProjectionReader and then use something like this for the best performance:

foreach (EnterpriseManagementObjectProjection emop in oprRAs)
{
    foreach (IComposableProjection icpReviewItem in emop[DataSources.relHasReviewer.Target])
    {
        foreach (IComposableProjection icpUser in emop[DataSources.relUserIsReviewer.Target])
        {
        }                        
    }
}

Here’s how to approve a Review Activity for both an IDataItem and an EnterpriseManagementObject:

//(DecisionEnum.Approved) (0e856c6c-04e5-0a8e-6041-bc7715b4747e)
ManagementPackEnumeration mpeApproved = emg.EntityTypes.GetEnumeration(new Guid("0e856c6c-04e5-0a8e-6041-bc7715b4747e"));    
                
//User class
ManagementPackClass mpcUser = emg.EntityTypes.GetClass(new Guid("943d298f-d79a-7a29-a335-8833e582d252"));
                       
//IDataItem
                      
//Approve
this.iReviewItem["Decision"] = mpeApproved;
this.iReviewItem["DecisionDate"] = DateTime.UtcNow;

//Create new voted by user relationship and add to the reviewer instance                           
EnterpriseManagementObjectDataType dataType = new EnterpriseManagementObjectDataType(mpcUser);
IDataItem iUser = dataType.CreateProxyInstance(emoCurrentUser);
this.iReviewItem["VotedBy"] = iUser;

//EneterpriseManagementObject

//Approve the review object for this user                                                
emoReviewItem[mpcReviewer, "Decision"].Value = mpeApproved;
emoReviewItem[mpcReviewer, "DecisionDate"].Value = DateTime.UtcNow;
emoReviewItem.Commit();

//Create new voted by user relationship                            
ManagementPackRelationship mprVotedByuser = emg.EntityTypes.GetRelationshipClass(new Guid("9441a6d1-1317-9520-de37-6c54512feeba"));
CreatableEnterpriseManagementRelationshipObject cemroVotedByUser = new CreatableEnterpriseManagementRelationshipObject(emg, mprVotedByuser);
cemroVotedByUser.SetSource(emoReviewItem);
cemroVotedByUser.SetTarget(emoCurrentUser);
cemroVotedByUser.Commit();

Miscellaneous

How to get a user EnterpriseManagementObject via the SMTP email address:

//Returns the emoUser for the passed SMTP address or null if not found
private EnterpriseManagementObject GetUserFromSMTPAddress(
    EnterpriseManagementGroup emg,
    string sAddress
    )
{
    try
    {
        //Get the System.Notifications.Library MP
        ManagementPack mpNotifications =
            emg.ManagementPacks.GetManagementPack(new Guid("8e02a0aa-04b1-812b-6ab5-1c12e3288e99"));

        //Build criteria to find the endpoint with the passed email address
        string sEmailCriteria = string.Format(@"
        <Criteria xmlns=""http://Microsoft.EnterpriseManagement.Core.Criteria/"">
            <Expression>
                <SimpleExpression>
                    <ValueExpressionLeft>
                        <Property>$Context/Property[Type='System.Notification.Endpoint']/TargetAddress$</Property>
                    </ValueExpressionLeft>
                    <Operator>Equal</Operator>
                    <ValueExpressionRight>
                        <Value>" + sAddress + @"</Value>
                    </ValueExpressionRight>
                </SimpleExpression>
            </Expression>
        </Criteria>
        ");

        //Setup criteria
        ManagementPackClass mpcNotifications = emg.EntityTypes.GetClass("System.Notification.Endpoint", mpNotifications);
        EnterpriseManagementObjectCriteria criteria = new EnterpriseManagementObjectCriteria(sEmailCriteria, mpcNotifications, mpNotifications, emg);

        //System.UserHasPreference relationship Guid
        Guid gPrefRel = new Guid("649e37ab-bf89-8617-94f6-d4d041a05171");

        //Process results (there should only be one)
        foreach (EnterpriseManagementObject emoUserSMTP in emg.EntityObjects.GetObjectReader<EnterpriseManagementObject>(criteria, ObjectQueryOptions.Default))
        {
            foreach (EnterpriseManagementRelationshipObject<EnterpriseManagementObject> emoObj in
                emg.EntityObjects.GetRelationshipObjectsWhereTarget<EnterpriseManagementObject>(emoUserSMTP.Id, ObjectQueryOptions.Default))
            {
                //Return source user if this is the correct relationship
                if (emoObj.RelationshipId == gPrefRel) return emoObj.SourceObject;
            }
        }
        return null;
    }
    catch
    {
        return null;
    }
}

This entry was posted in Code and tagged , , . Bookmark the permalink.