Obtaining RBAC Role Group MultiValuedProperties – C# – Exchange 2010

So I've decided to build my own custom Exchange management console to take care of many of the day to day things that get handed to me. I've got sections like a quick Get-MailboxStatistics out to listbox piece, to creating a new distribution list that handles all of the permissions, moderation notifications, and ad properties that we want to set automatically based on the values entered in the form. Just simple stuff…

One piece of this utility that really had me stuck was a form that would let me view and edit all RBAC based information. The OWA gui is fine and all that…but the pieces are so disconnected that unless you have a really in depth grasp of the RBAC Model it can be somewhat confusing.

I’m a visual person, so I need all the information available to me I can so I can put the puzzle pieces together in my brain. With that in mind the form I came up with was this(it’s still a work in progress so some components are still not integrated – like role scoping, etc):

Access method decisions

I started using the Exchange access method of invoking runspaces and adding the “Microsoft.Exchange.Management.PowerShell.E2010” powershell snap-in to the session to do alot of the distribution group creation, and rather than keep jumping between access methods, (EWS, Managed API, Runspaces, etc) I wanted to try and keep the scope narrowed as much as possible…

A quick example of invoking a runspace this way is shown below:


	// Create our result object
	PSObject strResult = new PSObject();
    
	// Configure Runspace and add PS-Snapin
	RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
    PSSnapInException snapInException = null;
    PSSnapInInfo info = rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.E2010", out snapInException);
    Runspace runspace = RunspaceFactory.CreateRunspace(rsConfig);

	// Open our runspace
	runspace.Open();
	
	// Create a command to add to the pipeline
	Command getRoleGroupMembers = new Command("Get-RoleGroupMember");
	// Adding paramaters is done like so:
	getRoleGroupMembers.Parameters.Add(new CommandParameter("Identity", roleGroupName));
	
	// Create our pipeline and start constructing its command list
	Pipeline commandPipeLine = runspace.CreatePipeline();
	// Add the Get-RoleGroupMember command to the first pipeline 
	commandPipeLine.Commands.Add(getRoleGroupMembers);

	Collection<PSObject> getResults = commandPipeLine.Invoke();
	
	[ ... ]


ADMultiValuedProperty – uh…what?

So as you saw from my form I wanted to be able to enumerate the roles assigned to the RoleGroup as well as get the members of the groups. At first I thought this was going to be as simple as accessing the properties like I did with the distribution group methods I used. Usually, all I would have to do to access the results properties is something similar to the following snip:


    [ ... ]
	
	Collection<PSObject> getResults = commandPipeLine.Invoke();
    foreach (PSObject getResult in getResults)
    {                         
        Console.WriteLine(getResult.Properties["Name"].Value.ToString());
	}
	
	[ ... ]
	

BUT…as I soon discovered…this wasn’t going to be a walk in the park. Sometimes, when working with the results of the invoke method…an object’s property that I wanted returns as type: ADMultiValuedProperty`1 ADObjectID . Nothing I tried was working to access these properties.

Retrieving MultiValuedProperty Values

Now according to my research there are 3 main methods of accessing these properties. This post on the MSDN forums was one of the places that kept coming back on my googling for information regarding this. I had tried all of the methods mentioned on the thread, but I still kept getting null values returning when trying to access the property “Roles” on my “Get-RoleGroup” object. It was very very frustrating…

The other main source of information for me while digging around was Dan’s WebDav blog, and particularly, this post. It has a good run down of the three most popular methods of accessing these properties. But unfortunately…in my situation…none of these were producing results. (To be fair I only briefly attempted to use the reference dll method because it was crashing my app, and since it’s unsupported I did not want to take the chance of hosing myself).

My Hack – Pros and Cons

After beating my head against the wall for a while I had an epiphany. For the most part those three methods described above all deal with getting the actual PSObject of the result back in order to manipulate it however you want. In MY case, all I wanted was the text value of the name contained within that property, I will access it LATER using that information, but I don’t need it right up front.

Why does that matter? Well…in the three example methods there is really no other alternative…if you need the object you will have to try to access them through those methods…but I racked my brain and found a workaround that will suffice for my needs…

The first step I thought was to take a step back. First…let me see how I can work with the object through a regular powershell session and maybe I can extract an idea from that…

After some fiddling around an idea started to form. By soley using powershell…I can dig down into the group and get the roles right? Once I have the roles I can access them through normal powershell commands as usual. Again, all I need is the string value of the group name, since I will use that to actually get more detailed information later through subsequent runspace invoke() calls…hmm…

I started thinking about how I could get those strings OUT of powershell…and viola…it hit me. Since RBAC is something that’s not dynamically changing all the time (hopefully not too many hands are in that pot)…I figured I could implement a “refresh” button or method if I needed to update the information..but having a “lagged” set of data for the current worksession would be ok. I also needed to figure out how I could get the information and temporarily hold it, but also clean it up after I exited the form so any extra resources and storage I had used would be removed.

How It Works

My RoleGroupInformation Class

I created a very simple class to hold my RoleGroupInformation. I wanted to cache as much of the info I could up front upon initialization of the form so every subsequent lookup call wouldn’t be a delay. My experience while using EWS is cool, but sometimes it get’s old seeing a “please wait” screen while data is being fetched, which is part of the reason I was wanting to use “runspace w/SnapIn” access method instead.

Below is the class I use to define my Role Groups in the app:

    class ExchangeRoleGroupInformation
    {
        public string RoleGroupName { get; set; }
        public string ManagedBy { get; set; }
        public string Description { get; set; }
        public List<string> AssignedRoles { get; set; }
        public List<string> RoleMembers { get; set; }

    }

As you can see each one of these properties links up to something on my form that should be updated when the user selects a different role group from the drop down. The first step is create a collection of my new class on my RBAC form:


    public partial class frmRBACRolesManagement : Form
    {
        private Collection<ExchangeRoleGroupInformation> myRoleCollection = new Collection<ExchangeRoleGroupInformation>();
		
	[ ... ]
	

Loading Collection with Role Group Properties

The drop down gets loaded by first going out to an Exchange runspace and invoking “Get-RoleGroup” and then looping through the results. For each result, I first create a new instance of my RoleGroupInformation class, and then I add the properties “Name” value to its RoleGroupName, add the Description, as well as retrieve the ManagedBy value like so:

private void GetAllRolesFromExchange()
{
	
	// Construct Runspace
	PSObject strResult = new PSObject();
	RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
	PSSnapInException snapInException = null;
	PSSnapInInfo info = rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.E2010", out snapInException);
	Runspace runspace = RunspaceFactory.CreateRunspace(rsConfig);

	try
	{
		// Open runspace and invoke the Get-RoleGroup cmdlet
		runspace.Open();
		Command getRoleGroups = new Command("Get-RoleGroup");
		Pipeline commandPipeLine = runspace.CreatePipeline();
		commandPipeLine.Commands.Add(getRoleGroups);
		Collection<PSObject> getRoleGroupResults = commandPipeLine.Invoke();

		if (getRoleGroupResults.Count > 0)
		{
			foreach (PSObject getRoleGroupResult in getRoleGroupResults)
			{
				// Create a new instance of my special RoleGroup class
				ExchangeRoleGroupInformation thisRole = new ExchangeRoleGroupInformation();
				
				// Get the Role Group Description
				thisRole.Description = getDistListResult.Properties["Description"].Value.ToString();
				
				// Get the Role Group Name
				thisRole.RoleGroupName = getDistListResult.Properties["Name"].Value.ToString();
				
				// Get the Role Group ManagedByObject 
				//(It was an accident I found this to work so I dont know if theres anything overly wrong with it or not)
				thisRole.ManagedBy = getDistListResult.ImmediateBaseObject.ToString();

				try { 
				
					// Add the newest member of our Role Group set to the collection	
					this.myRoleCollection.Add(thisRole);           
					cmbSelectRoleToView.Items.Add(thisRole.RoleGroupName);

				} catch {
					runspace.Close();
					MessageBox.Show("Error testing props");
					break;
				}                          
				
			}
		}          


	}
	catch (ApplicationException e)
	{
		//Console.WriteLine(e.Message);
		MessageBox.Show(e.Message, "Application Exception: frmRBACRolesManagement.cs - Line 227");

	}
}

Umm..what about the other properties on your class??

Nice catch! As you can see I’m missing the part that assigns those properties in the code above. I still hadn’t been able to populate the values for AssignedRoles and RoleGroupMembers because they are of the type ADMultiValuedProperty, and well, that’s where I was stuck…

In order for me to get the data out of powershell…I decided to say screw it and just obtain the information I needed, and export it to a CSV file that was named by RoleGroup.Replace(” “,null). That way the filename wouldn’t have any spaces, but I still could easily again retrieve it by calling the same replace() against the groupname to match. I would send the files to a temp directory in the application folder I already use for the sharing invitation xml generation I talked about in my earlier posts, so I already had a place the files could go without needing to rearchitect anything.

Keepin It Clean…

In order for me to be satisfied with this I needed a way to clean up the temp directory no matter what action precludes the form closing. I was kinda stuck with not being able to get it to work by clicking the “x” button and general exceptions I hadn’t caught yet, etc…but found this post and found it worked beautifully in my situation. I combined my idea and that concept by creating a tempdir cleanup method on the form as shown below:



        private void frmRBACRolesManagement_FormClosing(object sender, FormClosingEventArgs e) {
            // Here and anywhere I dispose my stuff I just add this
            CleanTempDirectory(); 
        }
		
		public void CleanTempDirectory()
        {
            // Get my temp directory and then get all the files in it
            DirectoryInfo tempdir = new DirectoryInfo(Application.StartupPath + "\\temp\\");
            var tempFiles = tempdir.GetFiles();

            // If there are files in the folder
            if (tempFiles.Count() > 0)
            {
                // For each file
                foreach (FileInfo file in tempdir.GetFiles())
                {
                    try
                    {
                        // Delete it
                        file.Delete();
                    }
                    catch (Exception errFinal)
                    {
                        MessageBox.Show("Error deleting file : \r\n" + errFinal.Message);
                    }
                }
            }
        }

Final Steps

Simple, yet effective. Ok, so now I had all the prep work done. The two last crucial pieces are:

  • 1)Getting the data I want to CSV

  • 2)Accessing that data for a datasource

Getting the data to CSV

After playing around with the Exchange Management Shell I was able to come up with a simple script which, when fed a name of a Role Group, would return all the properties of the “Roles” object, and pipe the resulting properties out to a CSV file. I started with plain-text and quickly realized that a CSV would be better, since I could use a method I describe below to attach to it as a datasource of sorts to load the properties into my RoleGroup collection.

Here is the powershell script I was able to figure out:


# A function well use to spit out all role information to a CSV
function GetRoleList {
param(
[string]$RoleGroupName
)
# Our Command
Get-RoleGroup -Identity "$RoleGroupName" | Select-Object Roles | % { 
	
	# I had to call Roles twice to get usable information, and then pipe it out for export
	$_.Roles | export-csv $ApplicationStartupPath + "\\temp\\" + $RoleGroupName.Replace(" ", null) + ".csv" -NoTypeInformation;
	};
};
# Actually Execute the function
GetRoleList $RoleGroupName

So now that I am able to do this through powershell, I just need to figure out how to construct that and integrate it into my application through C#. After fiddling around with multiple scenarios, I discovered that the only for sure way for me to get this running was to build the script as a giant “one-liner” and add it with the AddScript() method of the pipeline versus actually building the command through .NET.

Easy Breezy CSV Solution

Here is where I make a major shout out to Sebastien Lorion and his awesome Fast CSV Reader assembly. Thanks to this I was able to easily reference his dlls and use the examples on the project page to come up with a way to easily access these files and use them as a datasource of sorts for populating my AssignedRoles attributes.

An observation here is how now I have access to all the properties I want, even though I had only been needing the name of the role to load into a listbox. This information can be used to take away from this that even if I don’t have access to the object dynamically in the runspace or in my application, I now have a method to get these values in order to populate lists, etc, of my application. As long as the information isn’t CONSTANTLY being updated (I would be hard-pressed to do this for things like user account objects, etc), I can harness this process to have access to many pieces of information I otherwise would not have had an easy time accessing…but I digest… 😉

The code I used to perform this magical process(which gets called immediately after my GetAllRolesFromExchange() method from above) is shown below:

private void LoadAllRoleGroupInformationFromRoles()
{
    // Create my runspace
	RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
	PSSnapInException snapInException = null;
	PSSnapInInfo info = rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.E2010", out snapInException);
	Runspace runspace = RunspaceFactory.CreateRunspace(rsConfig);
	// Open runspace
	runspace.Open();
	
	// For every item in my collection 
	//(which has since been populated with name,description,managedby values)
	foreach (var roleGroup in myRoleCollection.ToList())
	{
		// Get the name of the current Role Group we're working with
		string thisRoleGroup = roleGroup.RoleGroupName;
		
		try
		{
			// Create pipeline
			Pipeline commandPipeLine = runspace.CreatePipeline();

            // Here's the one-liner      
            commandPipeLine.Commands.AddScript("function GetRoleList {param([string]$RoleGroupName)Get-RoleGroup -Identity \"$RoleGroupName\" | Select-Object Roles | % { $_.Roles | export-csv '" + Application.StartupPath + "\\temp\\" + roleGroup.RoleGroupName.Replace(" ", null) + ".csv' -NoTypeInformation;};};GetRoleList '" + roleGroup.RoleGroupName + "';");                  
                    		
			// Get my results
			Collection<PSObject> myResults = commandPipeLine.Invoke();
			  
			// Check for no members
			if (myResults != null)
			{
				// Check for my temp file first
				if (File.Exists(Application.StartupPath + "\\temp\\" + roleGroup.RoleGroupName.Replace(" ",String.Empty) + ".csv"))
				{				   
					// Open Temp File
					// open the file "data.csv" which is a CSV file with headers
					// Reference: Great CSV Reading DLL http://www.codeproject.com/Articles/9258/A-Fast-CSV-Reader
					using (CachedCsvReader csv = new
						   CachedCsvReader(new StreamReader(Application.StartupPath + "\\temp\\" + roleGroup.RoleGroupName.Replace(" ", String.Empty) + ".csv"), true))
					{
						// As per the project page: I had errors until I added this
						csv.MissingFieldAction = MissingFieldAction.ReplaceByNull;

						// Number of rows
						int fieldCount = csv.FieldCount;
						
						// Define a temporary list to build of all the roles in this current Group
						List<string> roleList = new List<string>();
						
						// Loop through our "records" in the CSV
						while (csv.ReadNextRecord())
						{
							
							// Position 8 is the Role "Name" Property
							// NOTE:  I will investigate this further to see how I can 
							// clean it up but for now I will just stick with extracting
							// the 8th columns value of the current row
							
							string thisRoleName = (csv[8] == null ? "MISSING" : csv[8]);
							
							// Create a new instance of a selectedRole
							ExchangeRoleGroupInformation selectedRoleGroup = new ExchangeRoleGroupInformation();
							
							// current role is valid
							if (thisRoleName != null)
							{
								try
								{
									// Find the fole in the follection
									selectedRoleGroup = myRoleCollection.Single(o => o.RoleGroupName == thisRoleGroup);
									
									// Add this role to the temp list
									roleList.Add(thisRoleName.ToString());
								}
								catch (Exception eerr)
								{
									MessageBox.Show("Error Getting selected RoleGroup in order to populate AssignedRoles Property\r\n<>: " + eerr.Message);

								}
							 
							}
							
							// Assign this temp list of roles to our current Role Group for the collection
							try 
							{ 
								roleGroup.AssignedRoles = roleList; 
							}
							catch (Exception err3) 
							{ 
								MessageBox.Show("Error assigning to myCollection AssignedRoles list:\r\n" + err3.Message); 
							}

						}
					}
					// This is my simple method that loops through my collection, calling get-rolegroupmembers and 
					// adding them to the RoleGroupMembers property of my collection.  I won't explain it on this 
					// post unless requested
					LoadAllRoleGroupMemberInformation(roleGroup.RoleGroupName);		 
				}
				else
				{
					MessageBox.Show("Woops!! Can't Find Temp File!!!\r\n" + Application.StartupPath + "\\temp\\" + roleGroup.RoleGroupName.Replace(" ", null) + ".csv");
				}
			}
			
			// Clear out the pipeline
			commandPipeLine.Commands.Clear();
		}
		catch (Exception getroleErr)
		{	   
			commandPipeLine.Commands.Clear();
			runspace.Close();
			MessageBox.Show(getroleErr.Message, "Error getting role info during response invoking");
		}	   
	}
	
	runspace.Close();
}


Now the final piece is to link my UI with this collection so when a user changes the selection in the combo box all the other values will be filled in accordingly.

Here’s how I did it:


[ ... ]

public partial class frmRBACRolesManagement : Form
{
	// Define our collection for the whole form 
	private Collection<ExchangeRoleGroupInformation> myRoleCollection = new Collection<ExchangeRoleGroupInformation>();

[ ... ]	

		
private void cmbSelectRoleToView_SelectedIndexChanged(object sender, EventArgs e)
{
	// Clear out current values on GUI
	txtRoleGroupDescription.Text = "";
	txtManager.Text = "";
	if (listAssignedRoles.Items.Count > 0) { listAssignedRoles.Items.Clear(); }
	if (listRoleGroupMembers.Items.Count > 0) { listRoleGroupMembers.Items.Clear(); }

	// As long as there are items in the collection
	if (myRoleCollection != null && myRoleCollection.Count > 0)
	{
		try
		{
			// Query our collection for the user based on the combobox selection
			ExchangeRoleGroupInformation selectedRole = myRoleCollection.Single(o => o.RoleGroupName == cmbSelectRoleToView.SelectedItem.ToString());
			
			// Add the Easy Attributes to the GUI
			txtRoleGroupDescription.Text = selectedRole.Description;
			txtManager.Text = selectedRole.ManagedBy;
			
			// Loop through the assigned roles and add them to the listbox
			if (selectedRole.AssignedRoles != null && selectedRole.AssignedRoles.Count > 0)
			{
				foreach (string strRole in selectedRole.AssignedRoles)
				{
					listAssignedRoles.Items.Add(strRole);
				}
			}			
			// Loop through the role members and add them to the listbox
			if (selectedRole.RoleMembers != null && selectedRole.RoleMembers.Count > 0)
			{
				foreach (string strMember in selectedRole.RoleMembers)
				{
					listRoleGroupMembers.Items.Add(strMember);
				}
			}
		}
		catch (ApplicationException erm)
		{
			MessageBox.Show(erm.Message, "ApplicationError");
		}
	}
}

Conclusion

So now I have a snappy GUI (after the initial load, which I decorate with “please wait” dialogues until all information is loaded), and I have the information I need to be able to manipulate the individual components of the RBAC Model (The Groups, The Roles Assigned, and The Members).

As I mentioned before I still don’t have anything dealing with the Scoping or even the Assignment Policies, but those are next. I just wanted to get this info out there in case anyone else finds themselves in a similar situation. If obtaining the raw data in a “datatable format” is what your ultimate goal is, and your comfortable with making later calls against objects based on “identity”, then hopefully this will help you out the way it did me.

I’ll gladly take constructive critisism on this process, as long as you can explain the methodology you were able to use to obtain the Role information from EMS via a runspace in C# based on a specific role group. I know this is a pretty extravagent solution but now that I have the template of this process in place I plan on using it if I run into troubles again later on down the road, until it’s decided that giving us access to the MultiValuedProperty`1ADObjectID attributes is something worth doing…or if I completely missed something and a real developer wants to bestow upon me the magical one liner that accomplishes this stuff, by all means…tell me!!! 😉

Till Next Time…

Advertisements

One comment on “Obtaining RBAC Role Group MultiValuedProperties – C# – Exchange 2010

  1. I blog frequently and I seriously appreciate your information.
    This great article has really peaked my interest.
    I’m going to bookmark your website and keep checking for new information about once a week.
    I subscribed to your RSS feed as well.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s