Alternative solution for DiagnosticsAreaEventSource.EnsureConfiguredAreasRegistered

When you are working with the SharePoint logger from the SPGuidance library and you have created a custom DiagnosticsArea (and some categories for it) you should, according to the documentation, ensure that the DiagnosticsArea is also registered as an event source in the Windows event log so that when you use the SPGuidance logger for tracing messages to operations, the message is also written in a correct manner to the Windows event log.

This can be done is several ways, as described by Patrick Boom and Chris Keyser in these posts:

However, one pitfall in these two solutions (SPTimerJob or PowerShell) is firstly that the SPTimerJob will run under the farm account which, by default, does not have access to write to the event log and secondly if you go with the PowerShell (or some other script/exe solution) the issue is that you have to run it on each server in the farm.

A third option

The basics of the solution provided by PnP is that you can use a static method called EnsureConfiguredAreasRegistered to register the areas. That method looks like this:

As you can see there is no “magic” going on here, the code just retrieves the areas by using the IConfigManager (they are stored as an application setting on the farm) and adds them through the static CreateEventSource method on the EventLog class. However, that code is only executed on the server on which you call the method on. The CreateEventSource method has an overload (actually all the methods on the EventLog class has it) that will except a different machine name.

So, I was thinking that we can enumerate all the servers in the farm and execute the same kind of code but taking into account that we supply the machine name in the process.

Show me

Let´s “imagine” that we have already added the DiagnosticsAreas through the SPGuidance library and then we execute this code:

        public static void EnsureEventLogSources()
        {

            EnsureUserIsAdmin();

            var areas = new DiagnosticsAreaCollection(SPG.ConfigManager);
            var servers = SPFarm.Local.Servers.Where(server => server.Role != SPServerRole.Invalid);
            bool update = false;

            foreach (SPServer server in servers)
            {
                foreach (DiagnosticsArea area in areas)
                {
                    var data = new EventSourceCreationData(area.Name, Microsoft.Practices.SharePoint.Common.Constants.EventLogName)
                    {
                        MachineName = server.IsCurrentServer() ? "." : server.Address
                    };

                    try
                    {
                        if (EventLog.SourceExists(data.Source, data.MachineName) == false)
                        {
                            try
                            {
                                EventLog.CreateEventSource(data);
                            }
                            catch (Exception e)
                            {
                                throw "An unknown error occured when trying to create the event source '{0}'"
                                        .AsException(e, data.Source);
                            }
                        }
                        else
                        {
                            if (area.DiagnosticsCategories.Count == 0)
                            {
                                try
                                {
                                    EventLog.DeleteEventSource(data.Source, data.MachineName);
                                    areas.Remove(area);
                                    update = true;
                                }
                                catch (Exception e)
                                {
                                    throw "An unknown error occured when trying to delete the event source '{0}'"
                                            .AsException(e, data.Source);
                                }
                            }
                        }
                    }
                    catch (SecurityException se)
                    {
                        throw "A SecurityException was caught. Ensure that the user '{0}' has access to the Windows event log or switch user."
                                .AsException(se, Environment.UserName);
                    }
                }
            }

            if (update)
            {
                areas.SaveConfiguration();
            }
        }

Explain it to me

I should start off with saying that this code can be executed both in a feature receiver and in a script/exe. However, if you execute it in the context of a feature receiver you should be aware that the feature shouldn´t be activated by default since that code will run in the context of the owstimer.exe and that account (the farm account) will not have administrator access on the servers by default.

Sidenote

At least, when I tested this code on my developer box it worked fine (ie the code was actually executed with my logged on account which had the administrator role).
When I tested it on a farm with two servers it executed it with the owstimer.exe process. I don´t actually know why this happened, and didn´t find any good explanation, but I can guess that when you deploy a WSP it schedules a job to run the features that are to be executed on all the WFE servers and since my dev box only has one server it didn´t have to schedule a job. Enough about that…

Back to the code

That means that you should activate the feature from the UI and then log on to the central admin (it should be a web application scoped feature) site as the setup user account which will then be the account that w3wp.exe will impersonate and it will have the admin role on the servers.

The code first checks if you´re user has the administrator role either through http context, through the impersonating w3wp.exe process, or the logged on windows account (if you execute it from the command line or PS). That piece of code looks like this:

        private static void EnsureUserIsAdmin()
        {
            WindowsPrincipal principal;

            if (HttpContext.Current == null)
            {
                principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
            }
            else
            {
                principal = HttpContext.Current.User as WindowsPrincipal;
            }

            if (principal == null)
            {
                throw "WindowsPrincipal could not be determined"
                    .AsException();
            }

            if (principal.IsInRole(WindowsBuiltInRole.Administrator) == false)
            {
                throw "The current user ('{0}') is not an administrator on the machine and therefore cannot perform the desired action"
                        .AsException(principal.Identity.Name);
            }

        }

It then uses the EventSourceCreationData; and creates an event source for each area it finds in the DiagnosticsAreaCollection.

The code is executed on each server in the server farm that isn´t “invalid” (the DB server is invalid) and here we take into account if the server the code is executed on is actually the same (or local) computer. That extension method looks like this:

        public static bool IsCurrentServer(this SPServer instance)
        {
            return instance.Name.Equals(Environment.MachineName, StringComparison.OrdinalIgnoreCase);
        }

Finally it creates the event source if that is needed and otherwise it removes those areas that do not have any categories left.

Important!
Yes, when you remove categories from an area like the image below (from MSDN docs) shows:

It suggests that you should remove the area.
If the code should be able to remove areas with no categories, leave it be…just remove the areas and the code I´m supplying will do that for you.

Summing up

Although the code works, it is a bit tricky and you have to be aware of the account you execute the code under. The good part about this is that you can run the code with PowerShell in the installation process which I still would recommend and you do that with the setup user account (which is the recommended practice) which makes it more integrated into the build process. I don´t like code that has to executed on each server in the farm since that often makes things hard to maintain.

, , , ,

  1. Alternative solution for DiagnosticsAreaEventSource.EnsureConfiguredAreasRegistered

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

%d bloggers like this: