Sunday, May 24, 2015

C# - Programmatically Add/Delete Membership Rule To/From SCCM Query Based Collection

I had been working on how to create membership rule into query based collection using SCCM SDK with C#. The article from MSDN has limited information especially in this area using C#. What I have found mostly are done using PowerShell script. Luckily, PowerShell and C# are somehow similar and I am able to base on the sample to convert it to the C# code to perform the automation work that I want. I am going to share the detail in my today's post.

Pre-requisite

Before we can begin, we need to include the following DLLs into your project assembly reference.

adminui.wqlqueryengine.dll
Microsoft.ConfigurationManagement.ManagementProvider.dll

Both DLLs can be obtained from SCCM installed directory which default at C:\Program Files\ConfigMgr2012\bin

If you download the SCCM 2012 SDK from this Microsoft Download Center link, it does not include the Microsoft.ConfigurationManagement.ManagementProvider.dll. The sample code provided from the SDK does not work as it miss out this dll. So, you have to get it from the SCCM 2012 server.

Query

Basically what we want to achieve here is to add multiple users into a membership rule then add the rule into the SCCM query based collection. Instead of manually doing it, we are going to automate it. First, we need to identify how the query should look like which to be added into SCCM collection.

We can check it out by opening the Configuration Manager Console. Then, go to the SCCM collection page. Open the Collection Properties.



Click the Add Rule button, then select Query Rule.



Enter a name for the query, then click Edit Query Statement.


Choose Criteria tab then click the little sun button.


I am going to add multiple users into a rule, so select Criterion Type as List of values. Then, click the Select button.


In my environment, I use Unique User Name to identify the users. Therefore, select Unique User Name as the attribute.

After clicking the OK button, it will lead me back to Criterion Properties page. Then, type the unique user name into the Value to add textbox or click the Values button to select the user that you want to add them into the membership rule. Once you have done adding all the users into the list, click the OK button.


The query is displayed in the Query Statement page. That query is going to be needed to be added to the SCCM query based collection programmatically later.

Coding

Add Membership To Collection

The following are the source code to add the query to membership rule to SCCM collection.

The semantic flow is as follow:

1. Connect to SCCM server.
2. Form the WQL query as you have seen from the Configuration Manager Console.
3. Validate the WQL query.
4. Create a new collection query instance.
5. Invoke AddMembershipRule method.
6. Request collection membership refresh.


public void AddUsersToCollectionMembership(string sccmCollectionID, params string[] userNames)
{
    SmsNamedValuesDictionary namedValues = new SmsNamedValuesDictionary();
    WqlConnectionManager connectionManager = new WqlConnectionManager(namedValues);

    if (string.IsNullOrWhiteSpace(sccmCollectionID) || userNames == null)
        throw new ArgumentException("The parameter collectionID value cannot be null or empty or whitespace.");

    try
    {
        connectionManager.Connect(this.SccmServerName);
        WqlResultObject collection = (WqlResultObject)connectionManager.GetInstance("SMS_Collection.CollectionID='" + sccmCollectionID + "'");

        string[] users = userNames.Select(x => x.Replace("\\", "\\\\")).ToArray();
        string query = string.Format("SELECT * " +
            "FROM SMS_R_User " +
            "WHERE SMS_R_User.UniqueUserName IN ({0})", string.Join(",", users));

        //Validate the query before adding the query to SCCM collection
        var validateQueryParam = new Dictionary<string, object>();
        validateQueryParam.Add("WQLQuery", query);
        IResultObject validationResult = connectionManager.ExecuteMethod("SMS_CollectionRuleQuery", "ValidateQuery", validateQueryParam);

        if (validationResult["ReturnValue"].BooleanValue == true)
        {
            //Create collection rule query instance
            IResultObject rule = connectionManager.CreateInstance("SMS_CollectionRuleQuery");
            rule["QueryExpression"].StringValue = query;
            rule["RuleName"].StringValue = "Members of collection " + sccmCollectionID; //The rule name will reflect in the CM Console

            //Add the rule into a parameter object
            var membershipRuleParam = new Dictionary<string, object>();
            membershipRuleParam.Add("collectionRule", rule);

            //Add new rule to SCCM collection
            IResultObject addResult = collection.ExecuteMethod("AddMembershipRule", membershipRuleParam);
           
            //NOTE: The added rule will have an ID return. You need to store it somewhere, e.g: database
            //You need this query ID to delete this rule later
            int sccmQueryID = addResult["QueryID"].IntegerValue;


            if (addResult["ReturnValue"].IntegerValue != 0)
            {
                Debug.WriteLine("Failed to add membership rule to SCCM Collection.");
                throw new ApplicationException("Failed to add membership rule to SCCM Collection.");
            }

            //Refresh the SCCM collection membership
            Dictionary<string, object> requestRefreshParameters = new Dictionary<string, object>();
            requestRefreshParameters.Add("IncludeSubCollections", false);
            collection.ExecuteMethod("RequestRefresh", requestRefreshParameters);
        }
        else
        {
            Debug.WriteLine(string.Format("Invalid WQL query: ", query));
            throw new ApplicationException(string.Format("Invalid WQL query: ", query));
        }
    }
    catch (SmsException smsEx)
    {
        Debug.WriteLine("Failed to run queries. Error: " + smsEx.Details);
        throw;
    }
    catch (UnauthorizedAccessException accessEx)
    {
        Debug.WriteLine("Failed to authenticate. Error:" + accessEx.Message);
        throw;
    }
    finally
    {
        connectionManager.Close();
        connectionManager.Dispose();
    }

}

Delete Membership From Collection

Similar concept in adding membership to collection. One thing to take note is the SCCM Query ID to be passed to the DeleteMembershipRule method.

The SCCM Query ID was obtained during AddMembershipRule method from above code (highlighted yellow).

public void DeleteSccmCollectionRule(string sccmCollectionID, int sccmQueryID)
{
    SmsNamedValuesDictionary namedValues = new SmsNamedValuesDictionary();
    WqlConnectionManager connectionManager = new WqlConnectionManager(namedValues);

    if (string.IsNullOrWhiteSpace(sccmCollectionID))
        throw new ArgumentException("The parameter collectionID value cannot be null or empty or whitespace.");

    try
    {
        connectionManager.Connect(this.SccmServerName);
        WqlResultObject collection = (WqlResultObject)connectionManager.GetInstance("SMS_Collection.CollectionID='" + sccmCollectionID + "'");

        //Create collection rule query instance
        IResultObject rule = connectionManager.CreateInstance("SMS_CollectionRuleQuery");
        rule["QueryID"].IntegerValue = sccmQueryID;

        //Add the rule into a parameter object
        var membershipRuleParam = new Dictionary<string, object>();
        membershipRuleParam.Add("collectionRule", rule);

        //Delete existing rule from SCCM collection
        IResultObject deleteResult = collection.ExecuteMethod("DeleteMembershipRule", membershipRuleParam);
        if (deleteResult["ReturnValue"].IntegerValue != 0)
        {
            Debug.WriteLine("Failed to delete membership rule from SCCM Collection.");
            throw new ApplicationException("Failed to delete membership rule from SCCM Collection.");
        }

        //Refresh the SCCM collection membership
        Dictionary<string, object> requestRefreshParameters = new Dictionary<string, object>();
        requestRefreshParameters.Add("IncludeSubCollections", false);
        collection.ExecuteMethod("RequestRefresh", requestRefreshParameters);
    }
    catch (SmsException smsEx)
    {
        Debug.WriteLine("Failed to run queries. Error: " + smsEx.Details);
        throw;
    }
    catch (UnauthorizedAccessException accessEx)
    {
        Debug.WriteLine("Failed to authenticate. Error:" + accessEx.Message);
        throw;
    }
    finally
    {
        connectionManager.Close();
        connectionManager.Dispose();
    }

}


That's all. Happy coding!




Sunday, March 8, 2015

C# & PowerShell

This post is to keep a record of what I had done with PowerShell. I have been using PowerShell for quite some time dealing with automation in Active Directory (AD) and System Center Configuration Management (SCCM). I discover that PowerShell can do a lot more thing than I was expected. 

In this post, I will use the example of how to automate in checking a user whether he/she is a member of a particular Active Directory (AD) group. It is written in PowerShell script, but I wanted to explore one more option with C#, knowing the truth that both options will work just fine. The intention is to find out the scripting difference between C# and PowerShell.

Concept

AD has a Directory Service which allow clients to query and manipulate directory objects. There are several methods to access directory service, and the commons are: 
  1. LDAP (Lightweight Directory Access Protocol)
  2. ADSI (Active Directory Service Interface)
In order to check a user whether he/she is a member of a particular AD group, we need to do a query search in AD. The concept is similar to query something from a database. The way how the data is being queried from the database is different from AD. We use SQL to query data from database, but we use LDAP to access directory service to query data from AD.

LDAP is a directory service protocol that run over TCP. Therefore, there are sets of TCP communication between client and directory service to establish session and data exchange. However, we do not need to worry in such low level implementation as we have Directory Service library in .NET Framework.

Implementation

C# version:


Add the Directory Service library reference in to your project.



Create a function for group member search:

public static bool IsGroupMember(string userName, string groupDN)
{
    var searcher = new DirectorySearcher("LDAP://DC=<Your Domain Name>,DC=com");
    searcher.Filter = "(&(objectCategory=person)(CN=" + userName + "))";
    searcher.SearchScope = SearchScope.Subtree;
    searcher.PropertiesToLoad.Add("memberOf");

    var result = searcher.FindAll();

    if (result != null)
    {
        return result[0].Properties["memberOf"].Contains(groupDN);
    }

    return false;
}


The function usage is to pass in the user name that you want to search in the group name.

PowerShell version:

function IsGroupMember($userName, $groupDN)
{
    $searcher = new-object System.DirectoryServices.DirectorySearcher("LDAP://DC=<Your Domain Name>,DC=com")
    $searcher.filter = "(&(objectCategory=person)(CN=$userName))"
    $searcher.SearchScope = "subtree"
    $searcher.PropertiesToLoad.Add("memberOf")

    $result = $searcher.findall()
   
    if ($result -ne $null)
    {
        return $result.Properties.memberof.Contains($groupDN)
    }
}

Hybrid version:


What I like about PowerShell is I can simply invoke any method in C# dll. The benefit of doing so is you can write complex code in C# which you think it is difficult to be written in PowerShell. System engineer will be saved from the horror of coding by just writing simple PowerShell script to call the method in C# which can perform sophisticated execution.

I have created a framework which contains all the functionalities that used to query and manipulate Active Directory related objects and groups, named ADFX. Since this framework was developed in C#, therefore, it can be shared with PowerShell, ASP.NET, windows service, console application, anything which are developed with .NET.

So, instead of having System Engineer to study Directory Service module from .NET Framework, they just need to write a simple PowerShell script below:

Import-Module -Name "C:\Users\Seng-Liang.S.Lee\Desktop\ADFX.dll"

$membership = New-Object ADFX.Membership
$membership.IsGroupMember("<Your User Name>", "<Your Group Distinguished Name>")


Summary

I find that C# and PowerShell are quite the same. Compare the C# code and the PowerScript, both look quite similar. How you write in C#, you can also write the same way in PowerShell, of course except the syntax are different. However, semantically they are the same.

PowerShell is able to import and use with any .NET built component which I like the most. It gives you the flexibility in dividing or separating the complex coding to be done in C# which are Software Engineer expertise, while we leave the easy one to be done in PowerShell which System Engineer can coped with.  



Thursday, January 1, 2015

ASP.NET - MS Chart

Today I want to share about creating charts in ASP.NET web form. There are a lot of awesome chart controls available out there such as Infragistic and Telerik. As you might know, you need to pay for the license to use those 3rd party chart controls. If you have budget constraint, you may opt for free chart control such as Microsoft (MS) Chart.

By default, MS Chart is built-in with Visual Studio 2010 or later. It is under Data category in the design toolbox. MS Chart is available for Winform and Webform.


Chart is useful when you are creating a dashboard or report page. Before you begin creating a chart, you need to have a database with data ready first. In this article, I cover the steps to create a basic nice looking chart which connecting to a SQL server database without writing a single line of code:

I am using a simple address book database containing a contact list for this article. I am creating a pie chart displaying the count of gender from my contact list.


Create Basic Chart

First, drag the chart control from the toolbox to your web page design pane. Then, create a new data source.


Configure Data Source Control

Enter a data source name.




Enter database connection info.



Give a name to the new connect string.



Create a custom SQL statement to display the count of gender.



The query should return only 2 columns, one for X axis and another for Y axis.

I am setting up gender as X axis, and the count as Y axis.




Configure Chart

Back to the chart control, change the chart type to "Pie". Here you can change to any chart type if you do not like pie.



Configure the X Value Member as Gender.

Configure the Y Value Member as Count.


That's it. You can test run your web application, the chart should display now.

I know it look plain and "fugly", we can make it look nicer and customize it to be more meaningful or user friendly.

Change to 3D

We can change the chart to become 3D by customizing the Chart Areas in the properties window.


Locate the Area3DStyle property, then set the Enable 3D value to true.



Display Value in the Pie Area Tooltip

As you can see the pie chart does not show the value in the pie. It is not user friendly and user can never know the actual Y value. Here is how you want to do if you want to let user mouse over to the pie to see the Y value in the tooltip. 

Go to the Series in properties window.




You can ether type the reserved keyword #VAL in the ToolTip property, or you can click the small button there to insert the correct keyword for you.


Click the Insert New Keyword button, then choose the Y value since you want to display that Y value in the pie area.


Click the OK button and you are done.

Your pie chart should look like this now, and when you mouse over to the pie, you can see the actual Y value in the tooltip.


Show Legends

Let's further decorating the chart by adding legend. Go to the Legends in the chart properties window.


Set the Legend title.



Now you can see the legend in your chart, however, it looks like duplicate. Therefore, you can remove the label or replace it with the Y value in the pie.


Replace the Label with Y Value in the Pie

Go back to the same Series properties windows, locate the IsValueShownAsLabel property and then set it to true.



Customize Label

Alternatively, you can display the Y value next to the X value by setting the Label in such format #VALX (#VAL).





Enable Hyperlink in the Pie Area

Imagine that the chart is clickable, and it will bring the user to another page which displaying the detail information. You can enable hyperlink in the pie area.

Go back to the same Series properties window, locate Url property under MapArea category.



Enter the detail page url with the dynamic value. My detail page expect query string named filter. #VALX is the X value which is Male or Female. When the detail page receive the male value, it will display all the contacts with male gender only.

If you want to know all the supported dynamic value or keywords, you can always go to the Keyword Editor to get them.



Once you are done with the configuration, test it out, you will notice the chart is now clickable and the url is formed dynamically.



Clicking the pie will redirect you to the detail page.



Summary

You can do a lot more and customization with MS Chart. This article only cover the very basic chart configuration and screenshots without any coding involve. Feel free to checkout this link: http://msdn.microsoft.com/en-us/library/dd456753(v=vs.140).aspx

By the way, this is my first post of the year 2015. Happy new year everyone!

Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...