What port is that system on anyways? C#, SNMP and Dell Switch Ports

If any of you have ever tried hunting down a port when the network goes out on a punch down only to realize your documentation hasn’t been kept up on will appreciate this one.  After digging through many unanswered threads, the very few well written blogs on the subject I managed to find a decent method for correlating a switch port to a host name.

It’s not something you would use all the time, but when you need it, you need it.

I have to give thanks to the creators of SnmpSharpNet and to the article that helped me find the correct OID for Dell Switch Ports by Geoff Garside as they were essential in creating this project.

While this article pertains primarily to Dell Switches, since that’s what I had to work with, it could easily be setup to work with HP by changing the OID and possibly others by tweaking the SNMP portion. The OID for Dell switches, at least 3500 and 5500 Series based on experience and other articles is 1.3.6.1.2.1.17.7.1.2.2.1.2.1 though there is a good chance it will work with any of them.

I decided on a WPF application this time around since you can create a list of custom objects and have it displayed on a DataGrid in a single line of code, very cool stuff.

The CODE:

using SnmpSharpNet;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace PortMapper
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        List<KeyValuePair<string, string>> portList = new List<KeyValuePair<string, string>>();
        List<Portmap> portMaps = new List<Portmap>();

        public class Portmap
        {
            public string Hostname { get; set; }
            public string Port { get; set; }
            public string IP { get; set; }
            public string MAC { get; set; }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            IPAddress ip = IPAddress.Parse("192.168.0.2");
            IPAddress ip2 = IPAddress.Parse("192.168.0.3");
            SnmpWalk(ip, "community", "1.3.6.1.2.1.17.7.1.2.2.1.2.1", "1");
            SnmpWalk(ip2, "community","1.3.6.1.2.1.17.7.1.2.2.1.2.1", "2");
            DhcpQuery("netsh", "dhcp server \\\\servername scope 192.168.0.0 show clients", "192");
            List<Portmap> gridResults = new List<Portmap>();
            //Example of filtering uplink ports from other switches
            foreach(Portmap portMap in portMaps)
            {
                if (portMap.Port != "1/2/48" && portMap.Port != "2/1/48")
                {
                    gridResults.Add(portMap);
                }
            }
            PortMapGrid.ItemsSource = gridResults;
        }
        
        //Use NETSH to retrieve a list of DHCP MAC and IP addresses
        private void DhcpQuery(string cmd, string args, string subnet)
        {
            ProcessStartInfo procStartInfo = new ProcessStartInfo();
            procStartInfo.RedirectStandardOutput = true;
            procStartInfo.UseShellExecute = false;
            procStartInfo.FileName = cmd;
            procStartInfo.Arguments = args;
            procStartInfo.CreateNoWindow = true;
            string output;
            using (Process proc = Process.Start(procStartInfo))
            {
                output = proc.StandardOutput.ReadToEnd();
                proc.WaitForExit();
            }

            //Find valid leases in command output
            string[] lines = output.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
            List<string> leases = new List<string>();
            foreach (string line in lines)
            {
                if (line.StartsWith(subnet))
                {
                   leases.Add(line);
                }
            }

            //Create threads
            Thread[] threadArray = new Thread[leases.Count];
            int threadcount = 0;

            //Loop each Dhcp Lease
            foreach (string line in leases)
            {
                string[] pieces = line.Split('-');
                string ipAddress = pieces[0].Trim();
                string mac = "";
                string hostname = "";
                foreach (string piece in pieces)
                {
                    if (piece.Trim().Length == 2)
                    {
                        mac += piece;
                    }
                }

                ThreadStart start = delegate
                {
                    hostname = GetHost(ipAddress);
                    foreach (KeyValuePair<string, string> port in portList)
                    {
                        if (port.Key.ToUpper().Trim() == mac.ToUpper().Trim())
                        {
                            Portmap portMap = new Portmap();
                            portMap.IP = ipAddress;
                            portMap.MAC = mac.ToUpper();
                            portMap.Port = port.Value;
                            portMap.Hostname = hostname;
                            portMaps.Add(portMap);
                        }
                    }
                };
                threadArray[threadcount] = new Thread(start);
                threadArray[threadcount].Start();
                threadcount = threadcount + 1;
            }

            //Join all threads in the array to wait for results
            for (int i = 0; i < threadcount; i++)
            {
                threadArray[i].Join();
            }
        }

        //SNMPWALK the ports on a switch or stack of switches. Ports will be labeled SwitchNum/Stack Number/Port Numbers.
        private void SnmpWalk(IPAddress ip, string snmpCommunity, string oid, string switchNum)
        {
            UdpTarget target = new UdpTarget(ip);

            // SNMP community name
            OctetString community = new OctetString(snmpCommunity);
            // Define agent parameters class
            AgentParameters param = new AgentParameters(community);
            // Set SNMP version to 1
            param.Version = SnmpVersion.Ver1;


            // Define Oid that is the root of the MIB tree you wish to retrieve
            Oid rootOid = new Oid(oid);

            // This Oid represents last Oid returned by the SNMP agent
            Oid lastOid = (Oid)rootOid.Clone();

            // Pdu class used for all requests
            Pdu pdu = new Pdu(PduType.GetNext);

            // Loop through results
            while (lastOid != null)
            {
                // When Pdu class is first constructed, RequestId is set to a random value
                // that needs to be incremented on subsequent requests made using the
                // same instance of the Pdu class.
                if (pdu.RequestId != 0)
                {
                    pdu.RequestId += 1;
                }
                // Clear Oids from the Pdu class.
                pdu.VbList.Clear();
                // Initialize request PDU with the last retrieved Oid
                pdu.VbList.Add(lastOid);
                // Make SNMP request
                SnmpV1Packet result = (SnmpV1Packet)target.Request(pdu, param);
                // You should catch exceptions in the Request if using in real application.

                // If result is null then agent didn't reply or we couldn't parse the reply.
                if (result != null)
                {
                    // ErrorStatus other then 0 is an error returned by 
                    // the Agent - see SnmpConstants for error definitions
                    if (result.Pdu.ErrorStatus != 0)
                    {
                        // agent reported an error with the request
                        Console.WriteLine("Error in SNMP reply. Error {0} index {1}",
                            result.Pdu.ErrorStatus,
                            result.Pdu.ErrorIndex);
                        lastOid = null;
                        break;
                    }
                    else
                    {
                        // Walk through returned variable bindings
                        foreach (Vb v in result.Pdu.VbList)
                        {
                            // Check that retrieved Oid is "child" of the root OID
                            if (rootOid.IsRootOf(v.Oid))
                            {
                                //Convert OID to MAC
                                string[] macs = v.Oid.ToString().Split('.');
                                string mac = "";
                                int counter = 0;
                                foreach (string chunk in macs)
                                {
                                    if (counter >= macs.Length - 6)
                                    {
                                        mac += string.Format("{0:X2}", int.Parse(chunk));
                                    }
                                    counter += 1;
                                }

                                //Assumes a 48 port switch (52 actual). You need to know these values to correctly iterate through a stack of switches.
                                int dellSwitch = 1 + int.Parse(v.Value.ToString()) / 52;
                                int port = int.Parse(v.Value.ToString()) - (52 * (dellSwitch - 1));
                                KeyValuePair<string, string> Port = new KeyValuePair<string, string>(mac, switchNum + "/" + dellSwitch.ToString() + "/" + port.ToString());
                                portList.Add(Port);

                                //Exit Loop
                                lastOid = v.Oid;
                            }
                            else
                            {
                                //End of the requested MIB tree. Set lastOid to null and exit loop
                                lastOid = null;
                            }
                        }
                    }
                }
                else
                {
                    Console.WriteLine("No response received from SNMP agent.");
                }
            }
            target.Close();
        }

        private string GetHost(string ipAddress)
        {
            try
            {
                IPHostEntry entry = Dns.GetHostEntry(ipAddress);
                return entry.HostName;
            }
            catch
            {
                return "";
            }
        }
    }
}

SharePoint 2013 REST Services: Downloading a file and working with Json via the HttpClient

I was recently asked about how you would find and download a file using the HttpClient and REST.  I decided to try and accomplish the task in a good old fashion Console Application using the standard .NET libraries instead of the Windows Store this time and realized that even with my past experience and previous code to work from this was no small task and it was only with help from the three following sites I was able to come up with the code below. The lack of the Json classes in the Windows Store libraries makes working with REST responses far more involved, though a bit easier to leverage once you have all the pieces in place when working with a standard .NET application:

Json Serialization Tutorial:

http://www.codeproject.com/Articles/272335/JSON-Serialization-and-Deserialization-in-ASP-NET

Derive .NET classes for serialization from raw Json data:

http://json2csharp.com/

View Json Data in a structure format:

http://jsonviewer.stack.hu/

Once you get your Json responses to your REST API calls you need to take a look at them and create classes to feed to the JsonSerializer.  This leaves you with objects that perfectly mirror the Json data structure as you would view it in the jsonviewer for extracting data.   Once you have all that it’s pretty easy to call the REST API and retrieve the data you need to download files.

Of coarse, the Microsoft.SharePoint.Client library could do the same task in a fraction of the time, but I am often asked how to work with the REST services without using those libraries.

The CODE:

    public class JsonHelper
    {
        /// JSON Serialization
        public static string JsonSerializer<T>(T t)
        {
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
            MemoryStream ms = new MemoryStream();
            ser.WriteObject(ms, t);
            string jsonString = Encoding.UTF8.GetString(ms.ToArray());
            ms.Close();
            return jsonString;
        }
        /// JSON Deserialization
        public static T JsonDeserialize<T>(string jsonString)
        {
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
            MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
            T obj = (T)ser.ReadObject(ms);
            return obj;
        }
    }

    //Custom Json Classes
    public class RootObject
    {
        public D d { get; set; }
    }
    public class D
    {
        public GetContextWebInformation GetContextWebInformation { get; set; }
        public List<Result> results { get; set; }
    }
    public class GetContextWebInformation
    {
        public int FormDigestTimeoutSeconds { get; set; }
        public string FormDigestValue { get; set; }
        public string LibraryVersion { get; set; }
        public string SiteFullUrl { get; set; }
        public string WebFullUrl { get; set; }
    }
    public class Result
    {
        public ContentType ContentType { get; set; }
        public string EncodedAbsUrl { get; set; }
        public string FileLeafRef { get; set; }
        public Folder Folder { get; set; }
        public int FileSystemObjectType { get; set; }
        public int Id { get; set; }
        public string ContentTypeId { get; set; }
        public string Title { get; set; }
        public int? ImageWidth { get; set; }
        public int? ImageHeight { get; set; }
        public string ImageCreateDate { get; set; }
        public object Description { get; set; }
        public object Keywords { get; set; }
        public string OData__dlc_DocId { get; set; }
        public int ID { get; set; }
        public string Created { get; set; }
        public int AuthorId { get; set; }
        public string Modified { get; set; }
        public int EditorId { get; set; }
        public object OData__CopySource { get; set; }
        public int? CheckoutUserId { get; set; }
        public string OData__UIVersionString { get; set; }
        public string GUID { get; set; }
    }

    class Program
    {
        static void Main()
        {

            string url = "https://sharepoint.site/";
            string filename = "2010-07-23 13.32.22.jpg"; ;
            string digest = "";
            HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
            client.BaseAddress = new System.Uri(url);
            string cmd = "_api/contextinfo";
            client.DefaultRequestHeaders.Add("Accept", "application/json;odata=verbose");
            client.DefaultRequestHeaders.Add("ContentType", "application/json");
            client.DefaultRequestHeaders.Add("ContentLength", "0");
            StringContent httpContent = new StringContent("");
            HttpResponseMessage response = client.PostAsync(cmd, httpContent).Result;
            if (response.IsSuccessStatusCode)
            {
                string content = response.Content.ReadAsStringAsync().Result;
                RootObject sp = JsonHelper.JsonDeserialize<RootObject>(content);
                digest = sp.d.GetContextWebInformation.FormDigestValue;
            }
            client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
            client.BaseAddress = new System.Uri(url);
            client.DefaultRequestHeaders.Clear();
            client.DefaultRequestHeaders.Add("Accept", "application/json;odata=verbose");
            client.DefaultRequestHeaders.Add("X-RequestDigest", digest);
            client.DefaultRequestHeaders.Add("X-HTTP-Method", "GET");
            string uri = "_api/web/lists/GetByTitle('Your Pictures')/Items?$select=ID,FileLeafRef,EncodedAbsUrl&$filter=FileLeafRef eq '" + filename + "'";
            HttpResponseMessage response2 = client.GetAsync(uri).Result;
            response2.EnsureSuccessStatusCode();
            if (response2.IsSuccessStatusCode)
            {
                string listItems = response2.Content.ReadAsStringAsync().Result;
                RootObject sp = JsonHelper.JsonDeserialize<RootObject>(listItems);
                foreach (Result result in sp.d.results)
                {
                    MemoryStream stream = (MemoryStream)client.GetAsync(result.EncodedAbsUrl).Result.Content.ReadAsStreamAsync().Result;
                    using (FileStream fileStream = System.IO.File.Create(@"C:\" + result.FileLeafRef))
                    {
                        stream.WriteTo(fileStream);
                    }
                }
            }
            else
            {
                var content = response.Content.ReadAsStringAsync();
            }
        }
    }

SharePoint 2013 REST Services using C# and the HttpClient (for Windows Store Apps)

SharePoint Web Services have come a long way in the last few versions.  I have spent a lot of time working with the SharePoint Object Model And recently started to use the Client Object Model which are both wonderful libraries with great documentation and numerous articles about them.

However, if you have delved into mobile app development recently you will realize that Windows Store projects only give a very specific subset of the core .NET libraries and that Exchange Web Services and the SharePoint Client libraries and many others are not available.  Now this does introduce some difficulty but in SharePoint 2013 REST services will allow you to accomplish almost any task in SharePoint.  The problem is that Microsoft or the development community has decided that any of us working with REST services directly will need to use JavaScript, at least if we want any examples of what these requests would look like.  While I do not mind doing minor tasks in Jscript I vastly prefer C# for doing larger applications and after doing dozens of searches, reading over a hundred articles and posts, I finally broke down and reverse engineered some Jscript examples and massively reworked what few C# examples existed for SharePoint REST.

I will be showing four main things in this post.  How to create a digest, which is used in the headers of any REST query passed from the HttpClient for authentication.  I will demonstrate how to get a list of items and how to work with the Json Objects using only native Windows Store libraries.  I will demonstrate a file upload and finally how to create a new list item with a lookup column.

The Examples:

Creating your digest:

String retVal = "";
try
{
string url = "https://YourSite.com/";
HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.BaseAddress = new System.Uri(url);
string cmd = "_api/contextinfo";

client.DefaultRequestHeaders.Add("Accept", "application/json;odata=verbose");
client.DefaultRequestHeaders.Add("ContentType", "application/json");
client.DefaultRequestHeaders.Add("ContentLength", "0");
StringContent httpContent = new StringContent("");
var response = client.PostAsync(cmd, httpContent).Result;
if (response.IsSuccessStatusCode)
{
string content = response.Content.ReadAsStringAsync().Result;
JsonObject val = JsonValue.Parse(content).GetObject();
JsonObject d = val.GetNamedObject("d");
JsonObject wi = d.GetNamedObject("GetContextWebInformation");
retVal = wi.GetNamedString("FormDigestValue");
}
}
catch
{ }
return retVal;

Getting a list of items

In this examples and all that follow the object digest would be passed from the return in the function above:
string url = "https://YourSite.com/Subsite/";
HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.BaseAddress = new System.Uri(url);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json;odata=verbose");
client.DefaultRequestHeaders.Add("X-RequestDigest", digest);
client.DefaultRequestHeaders.Add("X-HTTP-Method", "POST");
StringContent strContent = new StringContent("");
HttpResponseMessage response = await client.PostAsync("_api/web/lists/GetByTitle('Your List Title')/GetItems(query=@v1)?@v1={'ViewXml':''}", strContent);
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
var content = response.Content.ReadAsStringAsync();
JsonObject d = JsonValue.Parse(content.Result).GetObject();
JsonObject results = d["d"].GetObject();
JsonArray jobs = results["results"].GetArray();
foreach (JsonValue job in jobs)
{
JobBox.Items.Add(job.GetObject()["Job"].GetObject().GetNamedString("Label"));
}
}
else
{
var content = response.Content.ReadAsStringAsync();
}

Upload a File

FileOpenPicker picker = new FileOpenPicker();
picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
picker.ViewMode = PickerViewMode.Thumbnail;
// Filter to include a sample subset of file types.
picker.FileTypeFilter.Clear();
picker.FileTypeFilter.Add(".bmp");
picker.FileTypeFilter.Add(".png");
picker.FileTypeFilter.Add(".jpeg");
picker.FileTypeFilter.Add(".jpg");
// Open the file picker.
StorageFile path = await picker.PickSingleFileAsync();
if (path != null)
{
string url = "https://YourSite.com/Subsite/";
HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.BaseAddress = new System.Uri(url);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json;odata=verbose");
client.DefaultRequestHeaders.Add("X-RequestDigest", digest);
client.DefaultRequestHeaders.Add("X-HTTP-Method", "POST");
client.DefaultRequestHeaders.Add("binaryStringRequestBody", "true");
IRandomAccessStream fileStream = await path.OpenAsync(FileAccessMode.Read);
var reader = new DataReader(fileStream.GetInputStreamAt(0));
await reader.LoadAsync((uint)fileStream.Size);
Byte[] content = new byte[fileStream.Size];
reader.ReadBytes(content);
ByteArrayContent file = new ByteArrayContent(content);
HttpResponseMessage response = await client.PostAsync("_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/Files/add(url=@TargetFileName,overwrite='true')?@TargetLibrary='Project Photos'&@TargetFileName='TestUpload.jpg'", file);
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{ }
}

Create List Item with a multivalue lookup field

string url = "https://YourSite.com/Subsite/";
HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.BaseAddress = new System.Uri(url);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("X-RequestDigest", digest);
client.DefaultRequestHeaders.Add("X-HTTP-Method", "POST");
HttpContent content = new StringContent("{ '__metadata': { 'type': 'SP.Data.ReportListItem' }, 'Title': 'NewTitle', 'PhotosId': { 'results': [2] }, 'Details': 'Another successful day!' }");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("odata", "verbose"));
HttpResponseMessage response = await client.PostAsync("_api/web/lists/GetByTitle('Report')/items", content);
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
}
else
{
}

The Json for lookups is a bit tricky.  You have to specify the ID of the item in the other list or of the user if its a people picker.  Also the column name here has ‘Id’ appended onto the end of it.  If the column already ends in Id then you will end up with Id twice.  In a multivalue scenario the Json looks like this:

‘PhotosId’: { ‘results’: [2,3] }

Singe Value:

‘PhotosId’: 2

Also you need to figure out the type represented here:

‘SP.Data.ReportListItem’

This is broken up as SP . Data . YourListName ListItem.  I have not tested this with Lists that have a space in their titles so I would test that or avoid spaces.

Updating a list item by ID

client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.BaseAddress = new System.Uri(url);
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("X-RequestDigest", digest);
client.DefaultRequestHeaders.Add("X-HTTP-Method", "MERGE");
client.DefaultRequestHeaders.Add("IF-MATCH", "*");
HttpContent strContent = new StringContent(String.Concat("{ '__metadata': { 'type': 'SP.List' }, 'Title': '", filename, "' }"));
strContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
strContent.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("odata", "verbose"));
HttpResponseMessage updateResponse = await client.PostAsync(String.Concat("_api/web/lists/GetByTitle('Project Photos')/Items(", id, ")"), strContent);
updateResponse.EnsureSuccessStatusCode();
if (updateResponse.IsSuccessStatusCode)
{}

Hopefully everyone finds this article helpful.  I do not wish to spend the rest of my days writing JavaScript every time I need to work on applications for mobility and I assume that at least a few other developers share my sentiments.

Keeping an eye on PKI

Introduction

As Microsoft recently saw with Azure and I have personally run into many times, SSL certificates are becoming more and more of key piece of infrastructure that can be very difficult to keep a handle on.  It’s one of the few technologies that serves mission critical roles from granting access to secured websites to encrypting traffic for LDAP and other protocols that has a built in count down timer to disaster.  Now several monitoring solutions implement solutions for monitoring certificates on hosts by defining a certificates port and days ahead of time to alert you but if you are dealing with heavily secured systems that is not always ideal and it requires that the person setting up the service remembers to put that check in place.

I propose that there is a better way, at least if you are lucky enough to be using an in-house Microsoft Certificate Authority.  I recently developed an application to keep our server list in sync with what systems we actually have online in the datacenter and the idea of tracking expired certificates somehow worked its way into the project toward the end.  The idea is that we connect to the CA and ask it what certs are up for expiration.  Now that would be that if all certificates were important enough to be notified about via email but you probably don’t want to know every time a user’s certificate expires and auto renews.  Luckily most mission critical SSL certificates are from a narrowly used template the “Web Server” template or a customized version of this template.

The CODE – C# with .NET 4.5

Your code sheet must be using Interop.CERTADMINLib.dll – this can be found on any system that can manage your CA

//Configure Certificate Authority settings and prepare error parameters and containers
string strServer = "YourCA.company.domain";
string strCAName = "YourCAName";
TimeSpan twoWeeks = new System.TimeSpan(14, 0, 0, 0);
TimeSpan aMonth = new System.TimeSpan(30, 0, 0, 0);
StringBuilder certList = new StringBuilder();
StringBuilder critcertList = new StringBuilder();
int pkiErr = 0;
int critpkiErr = 0;

try
{
//Configure CA Variables
CCertView certView = null;
IEnumCERTVIEWROW certViewRow = null;
IEnumCERTVIEWCOLUMN certViewColumn = null;

// Connecting to the Certificate Authority
certView = new CCertView();
certView.OpenConnection(strServer + "\\" + strCAName);

//Configure required columns
certView.SetResultColumnCount(5);
var Index0 = certView.GetColumnIndex(0, "CommonName");
var Index1 = certView.GetColumnIndex(0, "NotAfter");
var Index2 = certView.GetColumnIndex(0, "Certificate Template");
var Index3 = certView.GetColumnIndex(0, "Request ID");
var Index4 = certView.GetColumnIndex(0, "Revocation Reason");
certView.SetResultColumn(Index0);
certView.SetResultColumn(Index1);
certView.SetResultColumn(Index2);
certView.SetResultColumn(Index3);
certView.SetResultColumn(Index4);

//Open the view and iterate column values
certViewRow = certView.OpenView();
for (int x = 0; certViewRow.Next() != -1; x++)
{
certViewColumn = certViewRow.EnumCertViewColumn();
string certName = "Missing certificate name";
DateTime certExpire = DateTime.Now;
string certTemplate = "";
string certID = "";
bool certRev = false;

while (certViewColumn.Next() != -1)
{
if (certViewColumn.GetValue(1) != null)
{
if (certViewColumn.GetDisplayName() == "Issued Common Name")
{
certName = certViewColumn.GetValue(1).ToString();
}
if (certViewColumn.GetDisplayName() == "Certificate Expiration Date")
{
certExpire = (DateTime)certViewColumn.GetValue(1);
}
if (certViewColumn.GetDisplayName() == "Certificate Template")
{
certTemplate = certViewColumn.GetValue(1).ToString();
}
if (certViewColumn.GetDisplayName() == "Request ID")
{
certID = certViewColumn.GetValue(1).ToString();
}
if (certViewColumn.GetDisplayName() == "Revocation Reason")
{
certRev = true;
}
}
}
//Check for certs expiring a month out and notify the CA administrator once
if (certExpire == DateTime.Now.Add(aMonth) &amp; certTemplate == "WebServer" | certTemplate == "CustomTemplateInternalID")
{
pkiErr = pkiErr + 1;
certList.AppendLine("ID: " + certID + " " + certName + " will expire on: " + certExpire.ToString() + Environment.NewLine);
}
//Check for certs expiring two weeks out and notify EVERYONE everyday
else if (certExpire &gt;= DateTime.Now &amp; certExpire &lt;= DateTime.Now.Add(twoWeeks) &amp; certRev == false) { if (certTemplate == "WebServer" | certTemplate == "CustomTemplateInternalID") { critpkiErr = critpkiErr + 1; critcertList.AppendLine("ID: " + certID + " " + certName + " will expire on: " + certExpire.ToString() + Environment.NewLine); } } } //close CA connection certViewRow.Reset(); certView = null; //Define mail message MailMessage mail = new MailMessage(); SmtpClient SmtpServer = new SmtpClient("yourSMTP.Server"); mail.From = new MailAddress("pkimanager@noreply.nrp"); mail.Subject = "PKI Alert!"; // Send e-mail message with errors if (pkiErr &gt;= 1)
{
mail.To.Add("Someone@your.company");
mail.Body = certList.ToString();
SmtpServer.Send(mail);
}
else if (critpkiErr &gt;= 1)
{
mail.To.Add("LotsofPeople@your.company");
mail.Body = critcertList.ToString();
SmtpServer.Send(mail);
}
}
catch (Exception e)
{
//Do something to make your CA talk to this application
}

If you run this small app once a day it will quickly spit out any certs that are about to expire which will need to be renewed, revoked or both to clean up the alert.  You will need to use an account that has privileges to read the CA but I recall this being rather painless.  Hopefully you will find this useful as a centralized check for SSL expiration rather than host based checks that are always prone to false positives and require significantly more work to maintain.