다음을 통해 공유


Using the FIM CM 2010 Notification API

This article describes how to use the FIM CM Notification API to send an e-mail message to a distribution list when a certificate enrolled through the FIM CM portal enters its renewal period.

Background

Back in 2005, Microsoft acquired Canadian firm Alacris, primarily for its idNexus smart card management software. After rebranding the product as Microsoft Certificate Lifecycle Manager during its beta period, it was merged with Microsoft’s meta-directory product, MIIS 2003 and released under the banner of ILM 2007; the certificate management component was known as Certificate Lifecycle Manager 2007.

The focus of Certificate Lifecycle Manager 2007 (CLM 2007) remained the management of smart cards, but a session at TechEd North America in 2008 by Brian Komar with the tongue-in-cheek title of Using Microsoft Identity Lifecycle Manager 2007 Certificate Management for Something Other than Smart Cards highlighted other possibilities for the product. One of the scenarios discussed in the session, which can still be viewed here http://channel9.msdn.com/Events/TechEd/NorthAmerica/2008/IDA355, was using CLM 2007 to enrol web server certificates. The main benefit of using CLM 2007 to enrol the certificates was that it allowed an e-mail notification to be sent when the certificate entered its renewal period.

Brian’s session didn’t go in to too much detail about how the e-mail was generated or how the body of the e-mail could be customised, but I thought there would be enough information out on the Internet and maybe even some example code, that would allow me to get the scenario working. Rather surprisingly I found very little information other than the Notification API documentation on MSDN (http://msdn.microsoft.com/en-us/library/windows/desktop/bb468066(v=vs.85).aspx), so I set myself a challenge. The objective was to send an e-mail to a distribution group whenever a web server certificate enrolled through CLM 2007’s successor, FIM Certificate Management 2010 (FIM CM), entered its renewal period. The body of the e-mail would provide the subject name of the certificate, the name of the host where the certificate was installed and any useful information about the certificate due to expire. In addition, only the published FIM CM APIs would be used. This latter requirement introduced some inefficiency into the solution and I’ll describe how these may be overcome later in this article.

FIM CM 2010 Basics

In order to keep the length of this article to a minimum, I don’t want to go into too much detail about how to set up FIM CM or how to configure the delivery of e-mail notifications. To gain a better understanding of what installation and configuration steps are required, I recommend reviewing the tutorials on Dmitrii Lezine’s blog (http://cloudidentityblog.com/2011/02/01/implementing-fim-2010-certificate-management-part-1/). I also recommend watching Brian Komar’s TechEd session mentioned earlier, as this goes into the basics of configuration for enrolling web server certificates. The main idea behind Brian’s approach is to use a dedicated user account as an enrolment agent and configure the account with the e-mail address of a distribution list that contains administrators who can respond to the renewal e-mail notification.

IMPORTANT: Before I started the development work, I made sure I could get FIM CM to deliver a standard e-mail message, which may be configured using the Renewal Policy section of a management policy. Once that was successful, the distribution method configured in the Renewal Policy was changed to ‘Do not distribute’ and responsibility for delivering the e-mail message was then down to the add-in. In doing this, it was easier to determine whether any problems were down to general FIM CM configuration or down to the code and its configuration. I highly recommend adopting the same approach if you use this add-in, as it will save a lot of troubleshooting time!

FIM CM 2010 Database Basics

For a bit of background I will quickly cover what happens when a web server certificate is enrolled through FIM CM in relation to the underlying SQL Server tables. An understanding of what happens under the covers is necessary to appreciate how the solution works.
Activities in FIM CM, such as certificate enrolment, are policy-based. The policy is expressed within a profile template which holds details of the workflows to be used at each stage in a certificate’s life, the templates to be used when enrolling certificates and an indication as to whether the profile template is for a smart card or a software certificate.
When creating a new request for a certificate, a row is added to the Requests table. Several columns are populated including a unique identifier for the request (request_uuid) and a column containing information gathered from data collection items (req_reg_data). When the certificate request is submitted, rows are added to the Profiles, ProfileCertificates and Certificates tables and the row recently added to the Requests table is updated. An abbreviated view of the database tables and the relationship between them is illustrated below:

 

 

 

A row is added to the Profiles table to indicate which profile template is to be used when the request is processed. The row gets a unique identifier (profile_uuid) and references the unique identifier of the profile template to be used in the request (pr_profile_template_uuid); several other columns are also populated. After the row is added to the Profiles table, the Requests table is updated, with the req_profile_uuid column of the new request being updated with the value of profile_uuid from the Profiles table. In doing this, FIM CM knows which profile template to use when enrolling certificates for the request.

When a certificate or certificates are issued (remember that a profile template can contain one or more certificate templates), a row is added to the Certificates table for each certificate and includes a unique identifier (cert_id), the certificate’s common name (cert_issued_common_name) and the certificate’s renewal date (cert_renew); several other columns are also populated. A row is also added to the ProfileCertificates table for each certificate and records the certificate’s unique identifier (pc_cert_id) and the profile from which it was derived (pc_profile_uuid); this latter value matches the value held in the req_profile_uuid column of the Requests table and the profile_uuid column of the Profiles table.

This activity is illustrated in the following diagrams. Note that some data is held as XML and as a result is not easily extracted unless using the FIM CM APIs. To begin with, a new enrolment request is created in the FIM CM portal and as a result a new row is added to the Requests table:

dbo.REQUESTS

request_uuid

req_data (XML)

req_reg_data (XML)

req_profile_uuid

req_type

req_target_user_uuid

abc-123-989

<XML>

Host Name=
   APPSRV1

Common Name=
   www.contoso.com

 

1 (Enroll)

bfe-789-456

After the request is successfully submitted and executed within the FIM CM portal, rows are added to the Profiles, ProfileCertificates and Certificates tables:

dbo.PROFILES

profile_uuid

pr_profile_template_uuid

aaa-444-121

ptu-333-901

 

dbo.PROFILECERTIFICATES

pc_profile_uuid

pc_cert_id

aaa-444-121

21

 

dbo.CERTIFICATES

cert_id

cert_issued_common_name

cert_renew

21

www.contoso.com

2014-02-17 18:45:07.000

 The Requests table is also updated to include a reference to the Profile used to enrol certificates:

dbo.REQUESTS

request_uuid

req_data (XML)

req_reg_data (XML)

req_profile_uuid

req_type

req_target_user_uuid

abc-123-989

<XML>

Host Name=
   APPSRV1

Common Name=
   www.contoso.com

aaa-444-121

1 (Enroll)

bfe-789-456

When a renewal request is generated by FIM CM due to a certificate entering its renewal period, a new request row is added to the Requests table. The req_data column contains the unique identifier of the profile from which the certificate was derived. This information may be used to tie the renewal request to the original enrolment request that contains some important data collection items. These are added to the body of the e-mail message along with other information to help identify the certificate that needs to be renewed.

dbo.REQUESTS

request_uuid

req_data (XML)

req_reg_data (XML)

req_profile_uuid

req_type

req_target_user_uuid

abc-123-989

<XML>

Host Name=
   APPSRV1

Common Name=
   www.contoso.com

aaa-444-121

1 (Enroll)

bfe-789-456

5df-686-849

Profile UUID=
   aaa-444-121

<XML>

 

3 (Renew)

bfe-789-456

So now seems like a good time to explain where all the information for the body of the e-mail message will come from.

E-mail Body

In the e-mail body, I wanted to include enough information for the expiring certificate to be identified and to indicate on which server the certificate was installed, so I decided on the following:

  • Certificate common name, e.g. www.contoso.com
  • Host name of the server where the certificate is installed, e.g. APPSRV1
  • Certificate serial number
  • Certificate thumbprint
  • Certificate template name

Some of this information has already been identified as being stored in one of the SQL Server tables, such as cert_issued_common_name, but other information, such as the host name of the server where the certificate is installed, is not maintained by FIM CM at all. In fact, after reviewing the FIM CM APIs, only the last three items in the list are accessible; the common name, while stored in the Certificates table is not presented through the APIs.
In order to address the issue of certificate common name and host name, I decided to add them as data collection items in the profile template definition and require them to be entered by the administrator creating the enrolment request. The values are stored in the req_reg_data column in the Requests table and are held in XML format.
Now that I’ve covered the basics of how FIM CM handles requests and where the data for the notification e-mail will be sourced, it is time to look at the code. I will discuss how the renewal request is detected and how to go about collecting all the information to send the reminder e-mail to the distribution list.

Detecting the Renewal Event

 I’ll now start to discuss the code that accompanies this post. The MSDN article, How to: Register for an Event Using a Notification Handler (http://msdn.microsoft.com/en-us/library/windows/desktop/bb468051.aspx), describes how to amend web.config to register for particular FIM CM events. In a default installation of FIM CM, web.config is located in C:\Program Files\Microsoft Forefront Identity Manager\2010\Certificate Management\web. The section to be amended is called ClmNotifications. The extract below shows a single registration for the DistributeSecrets event, which is the event that is triggered when a certificate enters its renewal period.

<ClmNotifications>
    <add event="DistributeSecrets" class="Technet.Clm.RenewalNotification.OnDistributeSecrets,Technet.Clm.RenewalNotification" />
</ClmNotifications>

In order to handle the event, the code must implement a notification handler class. This must implement two methods: Initialize and Notify. It is not necessary for the Initialize method to perform any processing, but it provides an opportunity to pass in a configuration string via the initializationData attribute of the notification registration. Note that this attribute is not used in the example above.

The Notify method must also be implemented. It is passed a Notification object that contains several properties. Of interest to us are NotificationType and Request. The notification type property indicates what type of event has been triggered. If the event is not a DistributeSecrets event, it can be ignored. The Request property is a reference to the FIM CM request object that triggered the event. This is passed as a parameter to the ProcessRequest method of the ClmRenewal class, which contains all the code necessary to find the original enrolment request, extract the data collection items for Host Name and certificate Common Name, and send an e-mail for each certificate in the associated profile template.

Processing the Renewal Request

After enabling .NET Remoting in the Initialise method, which takes its configuration from the application’s .config file, the next step is to find the original enrolment request, as this will contain details of the two data collection items. This is where the code becomes inefficient.

The GetEnrolRequest method is called, passing in the profile UUID from the Request object provided in the notification. The profile UUID identifies the profile used in the original enrolment request. If you recall the earlier discussion of the records that are written to the various FIM CM database tables, you will remember that a new record is written to the Profiles table in response to a new enrolment request being written to the Requests table. A column in the Requests table (req_profile_uuid) is then updated with the profile UUID of the record in the Profiles table. In doing this, the request and the profile are linked together.

In order to find the original enrolment request, which contains the data collection items, using only the FIM CM APIs, the following steps need to be performed:

  1. Use the Provisioning API’s FindOperations.FindRequests method to retrieve all rows from the Requests table where the target of the request is the enrolment agent account.
  2. Loop through each of the returned request objects and look for a match between the profile UUID, passed as a parameter to the GetEnrolRequest method, and the value held in the req_profile_uuid column; this will be the request we’re after. Note that the req_profile_uuid column is not accessed directly; it is exposed by the FIM CM APIs as the NewProfileUuid property of a request object.

In scoping the search operation in step 1, additional criteria are specified, in order to minimise the number of records returned and which must subsequently be searched through:

  • Retrieve only enrolment request records
  • Retrieve only completed requests
  • Retrieve only those records between when FIM CM was installed and today

Having found the original enrolment request, the next step is to obtain the profile record using the profile UUID from the Request object provided in the notification. This is easily obtainable using the FindOperations.GetProfile method of the Provisioning API. The original enrolment request and the profile are passed to the SendNotification method.
SendNotification obtains all the certificates associated with the profile using the Provisioning API’s FindOperations.FindCertificates method, calling the SendEmail method for each certificate returned. The enrolment request object is also passed to SendEmail so that the data collection items can be extracted and added to the body of the e-mail message. The data collection items are taken from the DataCollection property of the request. An e-mail message is sent for each certificate in the profile.

Optimisations

The code that retrieves the original enrolment request is inefficient as there is no way using the FIM CM APIs to retrieve a record from the Requests table based on a value in the req_profile_uuid column. The obvious way around this is to go direct to the tables using SQL statements, e.g.

SELECT request_uuid
  FROM dbo.Requests
 WHERE req_profile_uuid = request.OldProfileUuid

Having obtained the request UUID, the corresponding request object can then be obtained via the FindOperations.GetRequest method of the Provisioning API; the data collection items may then be extracted in the same manner as described previously.

I mentioned earlier that the common name of the certificate is not exposed by any of the FIM CM APIs, so I included it as a data collection item. A further optimisation that negates the need to capture the subject name of the certificate is to read it directly from the Certificates table, again using SQL, e.g.

SELECT c.cert_issued_common_name
  FROM dbo.Certificates AS c, dbo.ProfileCertificates AS pc 
 WHERE pc.pc_profile_uuid = request.OldProfileUuid
   AND pc.pc_cert_id = c.cert_id

Retrieving the certificate detail in this way has another advantage. I originally envisaged that the profile used to enrol a certificate through the FIM CM portal would contain only one certificate template, but this need not be the case. If a profile contained two or more certificate templates whose validity period was different, an e-mail message would be generated for all certificates in the profile. By retrieving the certificate’s subject name in this way, access is also gained to the cert_renew column of the Certificates table which could be checked to see if it was this particular certificate that caused the renewal notification in the first place. If it was, the e-mail message would be sent, if it wasn’t, sending an e-mail message for this certificate could be skipped.

I have yet to implement either of the optimisations outlined above and it is left as an exercise for the enthusiastic reader! However, when I do find the time to complete this work, I’ll update this article with the revised code.

Compilation, Installation and Configuration

The source code for the add-in can be found at the end of this article. To create a compiled DLL, perform the following tasks:

  1. Create a new Visual Studio 2010 C# class library project, making sure the version of .NET framework is set to 3.5.
  2. Name the project 'TechNet.Clm.RenewalNotification'.
  3. Click File / Save All… and save the project to a folder.
  4. Highlight all the text in the default Class1.cs file and replace it by pasting in the code from this article.
  5. Rename the Class1.cs file to be TechNet.Clm.RenewalNotification.cs.
  6. Add references to the project for Microsoft.Clm.Provision.dll and Microsoft.Clm.Notification.dll; it may be necessary to copy these files from the server running FIM CM and putting them in a temporary folder on the computer where Visual Studio is installed.
  7. Add a reference to the project for System.Configuration
  8. Build the project. 

The add-in’s configuration file can also be found at the end of this article. This will need a certain amount of customisation, as described below.

  1. Update the URL of the FIM CM server. In the example below, my FIM CM server is called FIMCM1. Note that if you have the FIM CM portal configured for HTTPS, you’ll need to make sure to specify https:// in the URL rather than http:// and also ensure that the name of the server matches the subject name in the certificate protecting the FIM CM portal. So you might need to specify the URL as http://FQDN/certificatemanagement...
  2. Update the EnrolmentAgentUuid setting. The value for this setting can be found in the UserNameCache table of the FIMCertificateManagement database. Find the account you are using as the enrolment agent in the unc_user_nt4_name column and copy and paste the unc_user_uuid value into the DLL.config file. If the enrolment agent account is not present, it can be added to the table by enrolling a certificate for that user using the FIM CM portal.
  3. Update the MailHost and MailHostPort settings to match the FQDN and port of the e-mail server.
  4. Update the MailFrom and MailTo settings. The MailFrom value can be anything you like as long as it is a valid e-mail address. However, MailTo needs to be the e-mail address of the distribution list configured against the enrolment agent account.
  5. Update the MailSubject setting to something appropriate.
  6. Update the MailBody setting to contain the text you want to appear in the body of the e-mail message. The replaceable parameter %Data_Collection_Items will be substituted for the Host Name and certificate Subject Name defined as data collection items in the management policy. %Serial_Number, %Thumbprint and %Certificate_Template are self-explanatory.

<client>
    <wellknown type="Microsoft.Clm.Provision.FindOperationsByCulture, Microsoft.Clm.Provision" url="http://fimcm1/certificatemanagement/remoterequests3.rem" />
    <wellknown type="Microsoft.Clm.Provision.RequestOperationsByCulture, Microsoft.Clm.Provision" url="http://fimcm1/certificatemanagement/remoterequests2.rem" />
    <wellknown type="Microsoft.Clm.Provision.PermissionOperationsByCulture, Microsoft.Clm.Provision" url="http://fimcm1/certificatemanagement/remoterequests4.rem" />
    <wellknown type="Microsoft.Clm.Provision.ExecuteOperationsByCulture, Microsoft.Clm.Provision" url="http://fimcm1/certificatemanagement/remoterequests5.rem" />
</client>

The DLL and the DLL.config files need to be copied to two separate locations:

  • C:\Program Files\Microsoft Forefront Identity Manager\2010\Certificate Management\web\bin
  • C:\Program Files\Microsoft Forefront Identity Manager\2010\Certificate Management\bin

It may be necessary to stop the Forefront Identity Manager CM Update Service before copying the files.
 
Finally, the web.config file needs amending to register for the DistributeSecrets event and to configure .NET remoting. The changes required are shown below.

<ClmNotifications>
    <add event="DistributeSecrets" class="Technet.Clm.RenewalNotification.OnDistributeSecrets,Technet.Clm.RenewalNotification" />
</ClmNotifications>

<system.runtime.remoting>
    <application>
       <service>
          <wellknown mode="Singleton" type="Microsoft.Clm.BusinessLayer.RemoteRequests, Microsoft.Clm.BusinessLayer" objectUri="remoterequests.rem" /> 
           <wellknown mode="SingleCall" type="Microsoft.Clm.Provision.RequestOperationsByCulture, Microsoft.Clm.Provision" objectUri="remoterequests2.rem" /> 
           <wellknown mode="SingleCall" type="Microsoft.Clm.Provision.FindOperationsByCulture, Microsoft.Clm.Provision" objectUri="remoterequests3.rem" /> 
           <wellknown mode="SingleCall" type="Microsoft.Clm.Provision.PermissionOperationsByCulture, Microsoft.Clm.Provision" objectUri="remoterequests4.rem" /> 
           <wellknown mode="SingleCall" type="Microsoft.Clm.Provision.ExecuteOperationsByCulture, Microsoft.Clm.Provision" objectUri="remoterequests5.rem" /> 
        </service>
         ...

Testing

In order to test the add-in, enrol a certificate for the enrolment agent account using the FIM CM portal. After the certificate has been successfully enrolled, use SQL Server Management Studio to open the Certificates table. Find the certificate that was just enrolled (it is likely to be the last one in the list) and amend the cert_renew date to a time that ensures the certificate is in its renewal period. During development of the add-in, I used a certificate template with a 4 hour validity period and a 3 hour renewal period, so I adjusted the cert_renew date back by 2 hours to ensure that the certificate would be within its renewal period. Restart the Forefront Identity Manager CM Update Service. This will make the service look for expiring certificates and run the add-in to deliver an e-mail message. Event messages are written to the FIM Certificate Management event log, which can be found under the Applications and Services Logs node in Event Viewer.

Once the add-in has delivered an e-mail message successfully through manual amendment of the cert_renewal date, it needs to be tested using the normal run cycle of the Forefront Identity Manager CM Update Service. By default, this runs every 5 hours, but its frequency can be reduced to a minimum of 1 hour by amending the Microsoft.Clm.Service.exe.config file that can be found in C:\Program Files\Microsoft Forefront Identity Manager\2010\Certificate Management\Bin. The setting to amend is called Microsoft.Clm.Service.Interval. The value is expressed in milliseconds, so an interval of 1 hour needs a value of 3600000. Additional information about configuring the FIM CM Service can be found here: http://technet.microsoft.com/en-us/library/ee534907(v=ws.10).aspx.

If everything works successfully, the result will be an e-mail message similar to the one shown below:

Final word

During the introduction, I mentioned that I was unable to find any examples of using the Notification API in this manner out on the Internet. As a consequence, the approach I’ve taken may not be the most optimal and I may be playing fast-and-loose with FIM CM! However, by sticking to the published APIs, I hope the risk of this has been minimised.
It is several years since I plied my trade as a developer, so I’m sure the code could be improved significantly and I’ll be happy to take feedback from anyone with suggestions for change.

C# Source Code, TechNet.Clm.RenewalNotification.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Reflection;
using System.Runtime.Remoting;
using System.Text;
using Microsoft.Clm.Shared;
using Microsoft.Clm.Shared.Requests;
using Microsoft.Clm.Shared.Profiles;
using Microsoft.Clm.Shared.Notifications;
using Microsoft.Clm.Provision;
 
 
 
namespace TechNet.Clm.RenewalNotification
{
    /// Notification handler class
    class OnDistributeSecrets : INotificationSink
    {
        void INotificationSink.Initialize(string data)
        {
            /// Initialize must be implemented, even if it does nothing
        }
 
        void INotificationSink.Notify(Notification notification)
        {
            if (notification.NotificationType != NotificationType.DistributeSecrets)
            {
                ClmRenewal.LogMessage(string.Format("Unexpected notification received: {0}.", notification.NotificationType), EventLogEntryType.Information, 2);
                throw new  ApplicationException(string.Format("Unexpected notification received: {0}.", notification.NotificationType));
            }
 
            /// Process the notification
            ClmRenewal.LogMessage(string.Format("Notification received: {0}", notification.NotificationType), EventLogEntryType.Information, 2);
            ClmRenewal.ProcessRequest(notification.Request);
        }
    }
 
 
 
    /// Renewal handler class
    internal static  class ClmRenewal
    {
        public static  void ProcessRequest(Request request)
        {
            LogMessage("Started custom renewal notification processing.", EventLogEntryType.Information, 1);
            LogMessage(string.Format("Renewal Uuid is {0}, OldProfileUuid is {1}.", request.Uuid, request.OldProfileUuid), EventLogEntryType.Information, 0);
 
            /// Initialise remoting
            try
            {
                Initialise();
            }
            catch (Exception ex)
            {
                LogMessage(string.Format("A problem occurred initialising remoting: {1}", ex.Message), EventLogEntryType.Error, 0);
                return;
            }
 
            /// Get the enrolment request for which the renewal was triggered
            Request ClmEnrolRequest = null;
            try
            {
                bool IsFound = GetEnrolRequest(request.OldProfileUuid,  ref  ClmEnrolRequest);
                if (!IsFound)
                {
                    LogMessage("No enrolment requests were found.", EventLogEntryType.Error, 0);
                    return;
                }
            }
            catch (Exception ex)
            {
                LogMessage(string.Format("A problem occurred trying to read the enrolment request: {1}", ex.Message), EventLogEntryType.Error, 0);
                return;
            }
 
            /// Get the profile
            Profile ClmProfile = null;
            try
            {
                ClmProfile = FindOperations.GetProfile(request.OldProfileUuid);
                LogMessage(String.Format("Found Profile for OldProfileUUID {0}", request.OldProfileUuid),EventLogEntryType.Information, 0);
            }
            catch (Exception ex)
            {
                LogMessage(String.Format("There was a problem obtaining the profile identified by '{0}'.{1}{2}", request.OldProfileUuid, Environment.NewLine, ex.Message), EventLogEntryType.Error, 0);
                return;
            }
 
            /// Send notification email
            SendNotification(ClmEnrolRequest, ClmProfile);
 
            LogMessage("Finished custom renewal notification processing.", EventLogEntryType.Information, 1);
        }
 
        /// Set the .NET Remoting configuration
        public static  void Initialise()
        {
            ExeConfigurationFileMap fileMap = new  ExeConfigurationFileMap();
            fileMap.ExeConfigFilename = Assembly.GetExecutingAssembly().Location + ".config";
 
            LogMessage(String.Format("Config file: {0}", fileMap.ExeConfigFilename), EventLogEntryType.Information, 0);
 
            System.Configuration.Configuration config;
            try
            {
                config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
            }
            catch(Exception ex)
            {
                LogMessage(String.Format("Failed to open config file. {0}", ex.Message), EventLogEntryType.Information, 0);
                throw ex;   
            }
 
            try
            {
                RemotingConfiguration.Configure(config.FilePath, false);
            }
            catch (Exception ex)
            {
                LogMessage(String.Format("Failed to configure remoting. {0}", ex.Message), EventLogEntryType.Information, 0);
                throw ex;   
            }
        }
 
        /// Find the original enrolment request
        public static  bool GetEnrolRequest(Guid profileUuid,  ref  Request clmRequest)
        {
            /// Set filters for rerieveing requests
            RequestType[] RequestTypes = new  RequestType[1] { RequestType.Enroll };
            RequestStatus[] RequestStatuses = new  RequestStatus[1] { RequestStatus.Completed };
            Guid[] OriginatorUsersUuid = new  Guid[0];
            Guid[] TargetUsersUuid = new  Guid[1] { new  Guid(GetEnrolmentAgentUuid()) };
            DateTime dt1 = new  DateTime(2011, 1, 1);
            DateTime dt2 = DateTime.Now;
 
            LogMessage(String.Format("Retrieved enrolment agent account UUID: {0}", TargetUsersUuid[0]), EventLogEntryType.Information, 0);
 
            ReadOnlyCollection<Request> ClmEnrolRequests = null;
            try
            {
                ClmEnrolRequests = FindOperations.FindRequests(RequestTypes, RequestStatuses, OriginatorUsersUuid, TargetUsersUuid, dt1, dt2);
            }
            catch (Exception ex)
            {
                LogMessage(String.Format("A problem was encountered retrieving enrolment requests.{0}{1}", Environment.NewLine, ex.Message), EventLogEntryType.Error, 0);
                throw ex;
            }
 
            LogMessage(string.Format("Enrolment request count: {0}", ClmEnrolRequests.Count), EventLogEntryType.Information, 0);
 
            /// Look at each request object to find the one with the correct profile UUID
            foreach (Request request in ClmEnrolRequests)
            {
                if (request.NewProfileUuid.Equals(profileUuid))
                {
                    clmRequest = request;
                    return true;
                }
            }
 
            return false;
        }
 
        /// Gather the certificate(s) affected by the renewal request and send a notification for each
        public static  void SendNotification(Request request, Profile profile)
        {
            ReadOnlyCollection<Microsoft.Clm.Shared.Certificates.X509ClmCertificate> ProfileCertificates = null;
            try
            {
                ProfileCertificates = FindOperations.FindCertificates(profile);
            }
            catch (Exception ex)
            {
                LogMessage(String.Format("Failure when retrieving profile certificates.{0}{1}", Environment.NewLine, ex.Message), EventLogEntryType.Error, 0);
                throw;
            }
 
            LogMessage(String.Format("Number of profile certificates found is {0}.", ProfileCertificates.Count), EventLogEntryType.Information, 0);
            foreach (Microsoft.Clm.Shared.Certificates.X509ClmCertificate X509Certificate in  ProfileCertificates)
            {
                LogMessage(String.Format("Processing profile certificate with serial number {0}.", X509Certificate.SerialNumber), EventLogEntryType.Information, 0);
                SendEmail(request, X509Certificate);
            }
        }
 
        /// Send an e-mail message about each certificate needing renewal
        public static  void SendEmail(Request request, Microsoft.Clm.Shared.Certificates.X509ClmCertificate certificate)
        {
            /// SMTP options
            string Host = GetConfigItem("MailHost");
            Int16 Port = Convert.ToInt16(GetConfigItem("MailHostPort"));
 
            /// Mail options
            string To = GetConfigItem("MailTo");
            string From = GetConfigItem("MailFrom");
            string Subject = GetConfigItem("MailSubject");
            string Body = GetConfigItem("MailBody");
 
            /// Replace placeholders with values
            string DataCollectionItems = null;
            DataCollection dc = request.DataCollection;
            foreach (DataCollectionItem dci in dc)
            {
                DataCollectionItems += String.Format("{0}: {1}{2}", dci.Name, dci.Value, Environment.NewLine);
            }
            Body = Body.Replace("%Data_Collection_Items", DataCollectionItems);
            Body = Body.Replace("%Serial_Number", certificate.SerialNumber);
            Body = Body.Replace("%Thumbprint", certificate.Thumbprint);
            Body = Body.Replace("%Certificate_Template", certificate.TemplateCommonName);
 
            /// Create and send mail message
            MailMessage mm = new  MailMessage(From, To, Subject, Body);
            SmtpClient sc = new  SmtpClient(Host, Port);
 
            try
            {
                sc.Send(mm);
            }
            catch (Exception ex)
            {
                LogMessage(string.Format("An error occurred sending the e-mail message: {0}", ex.Message), EventLogEntryType.Error, 0);
            }
 
            LogMessage(String.Format("Message sent for certificate with serial number {0}", certificate.SerialNumber), EventLogEntryType.Information, 0);
        }
 
        /// Get the UUID of the enrolment agent account from the configuration file
        public static  string GetEnrolmentAgentUuid()
        {
            return GetConfigItem("EnrolmentAgentUuid");
        }
 
        /// Read a setting from the configuration file
        public static  string GetConfigItem(string key)
        {
            ExeConfigurationFileMap fileMap = new  ExeConfigurationFileMap();
            fileMap.ExeConfigFilename = Assembly.GetExecutingAssembly().Location + ".config";
            Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
            return config.AppSettings.Settings[key].Value;
        }
 
        /// Write an entry to the event log
        public static  void LogMessage(string message, EventLogEntryType type, int id)
        {
            Debug.Print(message);
             
            if (!EventLog.SourceExists("TechNet.Clm.RenewalNotification"))
                EventLog.CreateEventSource("TechNet.Clm.RenewalNotification", "FIM Certificate Management");
 
            // Set DebugLevel to 4 in the .config file to get all messages
            if (Convert.ToInt16(type) >= Convert.ToInt16(GetConfigItem("DebugLevel")))
                EventLog.WriteEntry("TechNet.Clm.RenewalNotification", message, type, id);
        }
    }
}

Application configuration file, TechNet.Clm.RenewalNotification.dll.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.runtime.remoting>
    <application>
      <channels>
        <channel ref="http" useDefaultCredentials="true" port="0">
          <clientProviders>
            <formatter ref="binary" />
          </clientProviders>
        </channel>
      </channels>
      <client>
        <wellknown type="Microsoft.Clm.Provision.FindOperationsByCulture, Microsoft.Clm.Provision" url="http://fimcm1.corp.contoso.com/certificatemanagement/remoterequests3.rem" />
        <wellknown type="Microsoft.Clm.Provision.RequestOperationsByCulture, Microsoft.Clm.Provision" url="http://fimcm1.corp.contoso.com/certificatemanagement/remoterequests2.rem" />
        <wellknown type="Microsoft.Clm.Provision.PermissionOperationsByCulture, Microsoft.Clm.Provision" url="http://fimcm1.corp.contoso.com/certificatemanagement/remoterequests4.rem" />
        <wellknown type="Microsoft.Clm.Provision.ExecuteOperationsByCulture, Microsoft.Clm.Provision" url="http://fimcm1.corp.contoso.com/certificatemanagement/remoterequests5.rem" />
      </client>
    </application>
  </system.runtime.remoting>
  <appSettings>
    <add key="DebugLevel" value="4" />
    <add key="EnrolmentAgentUuid" value="fa04f13e-2482-48fb-9a1f-c9e518c605f0" />
    <add key="MailHost" value="smtp.corp.contoso.com" />
    <add key="MailHostPort" value="25" />
    <add key="MailFrom" value="webadmin@corp.contoso.com" />
    <add key="MailTo" value="webteam@corp.contoso.com" />
    <add key="MailSubject" value="Certificate Expiry Notification" />
    <add key="MailBody" value="This is an automated message generated by the Forefront Identity Manager 2010 Certificate Management system.
 
A certificate identified by the details below has entered its renewal period:
 
%Data_Collection_Items
Serial Number: %Serial_Number
Thumbprint: %Thumbprint
Certificate Template: %Certificate_Template
 
Further details about the certificate can be obtained through the FIM CM portal by selecting 'Find a certificate' from
the 'Manage Users And Certificates' section of the portal home page (http://clm.contoso.com/certificatemanagement).
 
Renew the certificate promptly to ensure that there is no interruption of service.
 
PLEASE NOTE: No further notifications regarding the status of this certificate will be issued." />
     
  </appSettings>
</configuration>