Friday, 16 November 2012

Disable Certificate Revokation List (CRL) Checking in IIS 7.x for client certificates

Roughly a year ago I was pulling my hair out trying to sort out some SSL issues with IIS 6, one of which necessitated disabling CRL checking and I thought that I should find out how to do the same in IIS 7.x, so here it is (I realize that I should try to find out what has changed for IIS 8, now):

I created a domain certificate request from IIS, assigned the certificate to a website and then run the following command, which shows the current state of the binding.
C:\>netsh http show sslcert

SSL Certificate bindings:
-------------------------

    IP:port                 : 0.0.0.0:443
    Certificate Hash        : 86fc14086c953edac86b8d8f9022c8baae2ad6f6
    Application ID          : {4dc3e181-e14b-4a21-b022-59fc669b0914}
    Certificate Store Name  : MY
    Verify Client Certificate Revocation    : Enabled
    Verify Revocation Using Cached Client Certificate Only    : Disabled
    Usage Check    : Enabled
    Revocation Freshness Time : 0
    URL Retrieval Timeout   : 0
    Ctl Identifier          : (null)
    Ctl Store Name          : (null)
    DS Mapper Usage    : Disabled
    Negotiate Client Certificate    : Disabled
Annoyingly, there isn't a modify flag, which means that the certificate binding needs to be deleted first and then re-added.
So first, the certificate binding must be deleted:
C:\>netsh http delete sslcert ipport=0.0.0.0:443

SSL Certificate successfully deleted
Then it must be re-added:
C:\>netsh http add sslcert ipport=0.0.0.0:443 certhash=86fc14086c953edac86b8d8f9022c8baae2ad6f6
appid={4dc3e181-e14b-4a21-b022-59fc669b0914}
certstore=MY verifyclientcertrevocation=disable

SSL Certificate successfully added
A final check to ensure that this has worked:
C:\>netsh http show sslcert

SSL Certificate bindings:
-------------------------

    IP:port                 : 0.0.0.0:443
    Certificate Hash        : 86fc14086c953edac86b8d8f9022c8baae2ad6f6
    Application ID          : {4dc3e181-e14b-4a21-b022-59fc669b0914}
    Certificate Store Name  : MY
    Verify Client Certificate Revocation    : Disabled
    Verify Revocation Using Cached Client Certificate Only    : Disabled
    Usage Check    : Enabled
    Revocation Freshness Time : 0
    URL Retrieval Timeout   : 0
    Ctl Identifier          : (null)
    Ctl Store Name          : (null)
    DS Mapper Usage    : Disabled
    Negotiate Client Certificate    : Disabled
This is useful for situations where firewalls prevent checking of CRLs, it's no use if all servers have an up to date CRL (at least IE will not not let you use a revoked client certificate to authenticate with)

Wednesday, 14 November 2012

WinRM QuickConfig fails. Error Number: -2144108387 0x8033809D

In a previous post I talked about running a PowerShell script with different credentials on a remote server. This seemed to be working fine, except for one server, so just to be on the save side I tried reconfiguring winrm.

Ordinarily this is accomplished with a simple command:
winrm qc
 However, I was getting an error when trying to do this:
WinRM already is set up to receive requests on this machine.
WSManFault
    Message = WinRM cannot process the request. The following error occured while using Negotiate authentication: An unknown security error occurred.
 Possible causes are:
  -The user name or password specified are invalid.
  -Kerberos is used when no authentication method and no user name are specified.
  -Kerberos accepts domain user names, but not local user names.
  -The Service Principal Name (SPN) for the remote computer name and port does not exist.
  -The client and remote computers are in different domains and there is no trust between the two domains.
 After checking for the above issues, try the following:
  -Check the Event Viewer for events related to authentication.
  -Change the authentication method; add the destination computer to the WinRM TrustedHosts configuration setting or use HTTPS transport.
 Note that computers in the TrustedHosts list might not be authenticated.
   -For more information about WinRM configuration, run the following command: winrm help config.

Error number:  -2144108387 0x8033809D
An unknown security error occurred.
The solution suggested on various places was to set up SPNs for the server, which I did:
setspn -s HTTP/myserver
setspn -s HTTP/myserver.dev.com
setspn -s HTTPS/myserver
setspn -s HTTPS/myserver.dev.com
Alas, this made no difference, after a little bit more digging I realized what the problem was, I was trying to remote to the current server, i.e. from myserver I was trying to run a remote script on myserver [sic], so I added myserver to the TrustedHosts like this:

winrm set winrm/config/client '@{TrustedHosts="myserver"}'

Tuesday, 13 November 2012

Check-out/Check-in code programmatically in TFS 2010

We are using TFS 2010 to store MS Dynamics CRM 2011 solutions. I created a tool to export the solutions from the server to a file, but in an effort to automate and possibly speed up build deployments to test from our development environment I created a class with a few methods to check in and out files and directories.

The CommonHelper.WriteMessage and LogException methods, use a trace listener from System.Diagnostics to the write to a log file. The LogException method is in essence described in this post. It contains the code inside the catch statement.

Feel free to suggests improvements, as I'm not too sure this is the best way of doing it, all I can say is that it works [the excuse of the bad coder :-)].

 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.Text;
 using Microsoft.TeamFoundation.Client;
 using Microsoft.TeamFoundation.VersionControl.Client;
 using System.Configuration;
 using System.Net;
 using DeploymentTool;
 
 namespace Deployment.ExportSolutions
 {
     public class TFSOperations
     {
         string collectionUrl, projectPath;
         NetworkCredential credential;
 
         public TFSOperations(string url, string path)
         {
             collectionUrl = url;
             projectPath = path;
 
             //I know, I know but nobody cares.
             string user = ConfigurationManager.AppSettings["user"];
             string password = ConfigurationManager.AppSettings["password"];
             string domain = ConfigurationManager.AppSettings["domain"];
 
             credential = new NetworkCredential(user, password, domain);
         }
 
         public string CollectionUrl
         {
             get { return collectionUrl; }
             set { collectionUrl = value; }
         }
 
         public string ProjectPath
         {
             get { return projectPath; }
             set { projectPath = value; }
         }
 
         public enum PathType
         {
             File,
             Directory
         };
 
         public bool CheckOut(string Path, PathType PathType, int RecursionLevel = 2)
         {
             bool result = false;
 
             try
             {
                 CommonHelper.WriteMessage(string.Format("Check that {0} {1} exists.", (PathType)PathType, Path));
 
                 if (PathType.Equals(PathType.File) && File.Exists(Path))
                 {
                     result = CheckOut(Path, RecursionType.None);
                 }
                 else if (PathType.Equals(PathType.Directory) && Directory.Exists(Path))
                 {
                     result = CheckOut(Path, (RecursionType)RecursionLevel);
                 }
                 else
                 {
                     CommonHelper.WriteMessage(string.Format("{0} {1} does not exist.", (PathType)PathType, Path));
                 }
             }
             catch (Exception ex)
             {
                 CommonHelper.LogException(ex);
             }
 
             return result;
 
         }
 
         public bool CheckIn(string Path, PathType PathType, int RecursionLevel = 2, string Comment = "I'm too lazy to write my own comment")
         {
             bool result = false;
 
             try
             {
                 CommonHelper.WriteMessage(string.Format("Check that {0} {1} exists.", (PathType)PathType, Path));
 
                 if (PathType.Equals(PathType.File) && File.Exists(Path))
                 {
                     result = CheckIn(Path, RecursionType.None, Comment);
                 }
                 else if (PathType.Equals(PathType.Directory) && Directory.Exists(Path))
                 {
                     result = CheckIn(Path, (RecursionType)RecursionLevel, Comment);
                 }
                 else
                 {
                     CommonHelper.WriteMessage(string.Format("{0} {1} does not exist.", (PathType)PathType, Path));
                 }
             }
             catch (Exception ex)
             {
                 CommonHelper.LogException(ex);
             }
 
             return result;
         }
 
         private bool CheckOut(string Path, RecursionType RecursionLevel)
         {
             bool result = false;
 
             CommonHelper.WriteMessage(string.Format("Get All LocalWorkspaceInfos."));
 
             WorkspaceInfo[] wsis = Workstation.Current.GetAllLocalWorkspaceInfo();
 
             foreach (WorkspaceInfo wsi in wsis)
             {
                 //Ensure that all this processing is for the current server.
                 if (!wsi.ServerUri.DnsSafeHost.ToLower().Equals(collectionUrl.ToLower().Replace("http://", "").Split('/')[0]))
                 {
                     continue;
                 }
 
                 Workspace ws = GetWorkspace(wsi);
 
                 CommonHelper.WriteMessage(string.Format("Check-Out {0}.", Path));
 
                 ws.PendEdit(Path, (RecursionType)RecursionLevel);
 
                 CommonHelper.WriteMessage(string.Format("Checked-Out {0}.", Path));
 
                 result = true;
             }
 
             return result;
         }
        
         private bool CheckIn(string Path, RecursionType RecursionLevel, string Comment)
         {
             bool result = false;
 
             try
             {
 
                 CommonHelper.WriteMessage(string.Format("Get All LocalWorkspaceInfos."));
 
                 WorkspaceInfo[] wsis = Workstation.Current.GetAllLocalWorkspaceInfo();
 
                 foreach (WorkspaceInfo wsi in wsis)
                 {
                     //Ensure that all this processing is for the current server.
                     if (!wsi.ServerUri.DnsSafeHost.ToLower().Equals(collectionUrl.ToLower().Replace("http://", "").Split('/')[0]))
                     {
                         continue;
                     }
 
                     Workspace ws = GetWorkspace(wsi);
 
                     var pendingChanges = ws.GetPendingChangesEnumerable(Path, (RecursionType)RecursionLevel);
 
                     WorkspaceCheckInParameters checkinParamenters = new WorkspaceCheckInParameters(pendingChanges, Comment);
 
                     if (RecursionLevel == 0)
                     {
                         CommonHelper.WriteMessage(string.Format("Check-in {0}.", Path));
                     }
                     else
                     {
                         CommonHelper.WriteMessage(string.Format("Check-in {0} with recursion level {1}.", Path, (RecursionType)RecursionLevel));
                     }
 
                     ws.CheckIn(checkinParamenters);
 
                     if (RecursionLevel == 0)
                     {
                         CommonHelper.WriteMessage(string.Format("Checked-in {0}.", Path));
                     }
                     else
                     {
                         CommonHelper.WriteMessage(string.Format("Checked-in {0}  with recursion level {1}.", Path, (RecursionType)RecursionLevel));
                     }
 
                     result = true;
                 }
 
             }
             catch (Exception ex)
             {
                 CommonHelper.LogException(ex);
             }
 
             return result;
         }
 
         private Workspace GetWorkspace(WorkspaceInfo wsi)
         {
 
             TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(wsi.ServerUri, credential);
 
             CommonHelper.WriteMessage(string.Format("Get Workspace."));
 
             Workspace ws = wsi.GetWorkspace(tpc);
 
             return ws;
         }
 
     }
 }
 
 

Monday, 12 November 2012

Trace logging with the Enterprise Library for MS Dynamics CRM 2011 plug-ins (using MSMQ) - part 4

In part one of this series I described how to use the enterprise library for trace logging in MS Dynamics CRM 2011 plug-ins (it should also work for MS Dynamics CRM 4.0) and in part two, I described how to use add the necessary changes to the various configuration files using Wix, in the third post, I provided a small code sample of how to use the enterprise library.

In this fourth and final post I describe a rather interesting use of the Enterprise Library trace logging capabilities, feel free to contact me if you want a sample.

From version 4.0 of the enterprise library, not sure whether present in any other versions, there is the MSMQ Distributor Service, which picks up queue messages and then logs them to any trace listener. This means that something like this is possible (A picture is worth a thousand words and all that):

Pilfered from here
In our environment we will have four application servers running MS Dynamics CRM 2011 and we want to log everything to a database on the SQL Cluster that hosts the Dynamics CRM databases. For the web services we are logging directly to the database, but for the plug-ins and code based workflows we will be using MSMQ trace listeners to log to a private queue on the SQL Cluster and then have the MSMQ distributor service do the final mile of logging to the database.

Although the queue is private, Everyone will be able to write messages to it, which means that the plug-ins that run under the user context will be able to write messages to the queue or in other words, trace logging should work for all users regardless of their permissions, which is the key. We could log to the database with a sql user but that is not what we wanted.

I will not post the configuration file logging section that I used again, as it's already been posted here.
  1. Install MSMQ on all servers (Application and Database), which can be done using the script posted here
  2. Use the configuration file logging section posted here for the CrmAsyncService.exe.config and web.config. 
  3. Install MSMQ Distributor Service on Database Server.
    Installutil -i MsmqDistributor.exe
  4. Change MSMQ Distributor Service configuration, see sample below.
  5. Start MSMQ Distributor Service, e.g.:
    net start "Enterprise Library Distributor Service"
MSMQ Distributor Service Sample Configuration File.
 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <configSections>
     <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging" />
     <section name="msmqDistributorSettings" type="Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.Configuration.MsmqDistributorSettings, MsmqDistributor"/>
   </configSections>
   <loggingConfiguration tracingEnabled="true" defaultCategory="Trace" logWarningsWhenNoCategoriesMatch="true">
     <logFilters/>
     <categorySources>
       <add name="Trace" switchValue="All">
         <listeners>
           <add name="Database Trace Listener"/>
         </listeners>
       </add>
       <add name="Error" switchValue="All">
         <listeners>
           <add name="Database Trace Listener"/>
         </listeners>
       </add>
     </categorySources>
     <specialSources>
       <allEvents name="allEvents" switchValue="All"/>
       <notProcessed name="notProcessed" switchValue="All"/>
       <errors switchValue="All" name="Logging Errors &amp; Warnings">
         <listeners>
           <add name="Formatted EventLog TraceListener" />
         </listeners>
       </errors>
     </specialSources>
     <listeners>
       <add name="Formatted EventLog TraceListener"
         type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging"
         source="Enterprise Library Logging" formatter="Text Formatter"/>
       <add name="Database Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.Database.FormattedDatabaseTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging.Database, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Database.Configuration.FormattedDatabaseTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging.Database, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         databaseInstanceName="loggingdb" writeLogStoredProcName="WriteLog"
         addCategoryStoredProcName="AddCategory" formatter="Text Formatter" />
     </listeners>
     <formatters>
       <add name="Text Formatter"
         type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging"
         template="Timestamp: {timestamp}{newline}Message: {message}{newline}Category: {category}{newline}Priority: {priority}{newline}EventId: {eventid}{newline}Severity: {severity}{newline}Title:{title}{newline}Machine: {machine}{newline}App Domain: {appDomain}{newline}ProcessId: {processId}{newline}Process Name: {processName}{newline}Thread Name: {threadName}{newline}Win32 ThreadId:{win32ThreadId}{newline}Extended Properties: {dictionary({key} - {value}{newline})}" />
     </formatters>
   </loggingConfiguration>
   <connectionStrings>
     <add name="loggingdb" connectionString="Data Source=sql2k12a;Initial Catalog=Logging;Integrated Security=SSPI;"
       providerName="System.Data.SqlClient" />
   </connectionStrings>
   <msmqDistributorSettings
                 msmqPath=".\Private$\logging"
                 queueTimerInterval="1000"
                 serviceName="Enterprise Library Distributor Service" />
 </configuration>

Sunday, 11 November 2012

Trace logging with the Enterprise Library for MS Dynamics CRM 2011 plug-ins - part 3

In part one of this series I described how to use the enterprise library for trace logging in MS Dynamics CRM 2011 plug-ins (it should also work for MS Dynamics CRM 4.0) and in part two, I described how to use add the necessary changes to the various configuration files using Wix.

In this post, I provide a simple sample of how to use the Enterprise Library for trace logging.

Ensure that you add a reference to Enterprise Logging Application Block to your project, if this is not showing up, you probably don't have the Enterprise Library installed.

Sample Code:
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Diagnostics;
 using Microsoft.Practices.EnterpriseLibrary.Logging;
 using System.IO;
 
 namespace ELSample
 {
     class Program
     {
         static void Main(string[] args)
         {
             string file = @"c:\myfile.txt";
             
             try
             {
                 WriteLogEntry("Trace",string.Format("Start to read file {0}.", file)); 
 
                 using (StreamReader sr = new StreamReader(file))
                 {
                     //do something here.
                 }
 
                 WriteLogEntry("Trace", string.Format("Finished reading file {0}.", file)); 
             }
             catch (Exception ex)
             {
                 WriteLogEntry("Error", "Exception Occurred.");
                 WriteLogEntry("Error", string.Format("Source: {0}.", ex.Source));
                 WriteLogEntry("Error", string.Format("Type: {0}.", ex.GetType()));
                 WriteLogEntry("Error", string.Format("Message: {0}.", ex.Message));
 
                 if (ex.InnerException != null)
                 {
                     WriteLogEntry("Error", string.Format("Inner Exception: {0}.", ex.InnerException.Message));
                 }
             }
 
         }
 
         /// <summary>
         /// Write a log entry using the Enterprise Library
         /// </summary>
         /// <param name="LogCategory">This is the name of the Trace Listener, e.g. Trace, Error</param>
         /// <param name="LogMessage">Log Entry to write</param>
         public static void WriteLogEntry(string LogCategory, string LogMessage)
         {
             LogEntry entry = new LogEntry();
             entry.Categories.Add(LogCategory);
             entry.Message = LogMessage;
 
             StackTrace trace = new StackTrace();
 
             entry.Title = string.Format("{0}.{1}",
                 trace.GetFrame(1).GetMethod().ReflectedType.Name, trace.GetFrame(1).GetMethod().Name);
 
             Microsoft.Practices.EnterpriseLibrary.Logging.Logger.Write(entry);
         }
 
   
     }
 }
A few things to note:
  1. I know that the sample is not from a plug-in.
  2. Same config section, the logging part, as in the first post.
  3. If you turn a trace listener off, the WriteLogEntry method will not write any entries, but there will be some processing done, so it will run slower than without it, as with everything in life it's a trade off.
  4. It probably makes sense to have a separate method for logging exceptions.
  5. I normally only bother with two trace listeners, Trace and Error. The former is normally equivalent to Verbose logging, the latter to ... Error logging.

Saturday, 10 November 2012

Trace logging with the Enterprise Library for MS Dynamics CRM 2011 plug-ins - part 2

In part one of this series I described the changes needed to use the trace logging capabilities of the enterprise library, in this post I shall describe how to add these changes using Wix.

The more eagled eyed readers, will notice that the listeners are different from the last time, this is because we are actually using a private queue on the database server to log the Trace messages to. These are then processed by the Enterprise Library MSMQ Distributor service, I shall discuss this in detail in an upcoming post.

I've not managed to insert the configSections element at the beginning of CrmAsyncService.exe.config file, which is where it needs to be, so I just deleted what it was there, added the configSections element and re-added what was originally there. It feels too much like a hack but ...

 Wix Sample
 <?xml version="1.0" encoding="UTF-8"?>
 <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
   <Fragment >
 
     <Component Id="pluginlogging" Guid="BA77335E-ADF1-4037-9881-68E116BA543D" Directory="dirB50C76F5A59FB10334E162992F65AAAA" KeyPath="yes">
       <Condition>DYNAMICS</Condition>
       <util:XmlConfig Id="web.logging"  File="[DYNAMICSCRMWEB]\web.config" Action="create"
               ElementPath="/configuration/configSections" On="install"  Node="document" Sequence="1" VerifyPath="/configuration/configSections/section[\[]@name='loggingConfiguration'[\]]" >
         <![CDATA[<section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /> ]]>
       </util:XmlConfig>
 
       <util:XmlConfig Id="webloggingConfiguration" Action="create" ElementPath="/configuration"   File="[DYNAMICSCRMWEB]\web.config" Node="document" On="install" Sequence="2" VerifyPath="/configuration/loggingConfiguration/listeners">
         <![CDATA[<loggingConfiguration name="" tracingEnabled="true" defaultCategory="Trace">
     <listeners>
       <add name="Message Queuing Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.MsmqTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.MsmqTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         queuePath="FormatName:Direct=OS:[SQLHOSTNAME]\private$\logging" formatter="Binary Log Message Formatter"
         timeToReachQueue="10.00:00:00" timeToBeReceived="10.00:00:00"
         recoverable="true" useDeadLetterQueue="true" traceOutputOptions="None" />
        <add name="Event Log Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         source="Enterprise Library Logging" formatter="Text Formatter" />
     </listeners>
     <formatters>
       <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         template="Timestamp: {timestamp}{newline}&#xA;Message: {message}{newline}&#xA;Category: {category}{newline}&#xA;Priority: {priority}{newline}&#xA;EventId: {eventid}{newline}&#xA;Severity: {severity}{newline}&#xA;Title:{title}{newline}&#xA;Machine: {localMachine}{newline}&#xA;App Domain: {localAppDomain}{newline}&#xA;ProcessId: {localProcessId}{newline}&#xA;Process Name: {localProcessName}{newline}&#xA;Thread Name: {threadName}{newline}&#xA;Win32 ThreadId:{win32ThreadId}{newline}&#xA;Extended Properties: {dictionary({key} - {value}{newline})}"
         name="Text Formatter" />
       <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.BinaryLogFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         name="Binary Log Message Formatter" />
     </formatters>
     <categorySources>
       <add switchValue="All" name="Trace">
         <listeners>
           <add name="Message Queuing Trace Listener" />
         </listeners>
       </add>
       <add switchValue="All" name="Error">
         <listeners>
           <add name="Message Queuing Trace Listener" />
         </listeners>
       </add>
     </categorySources>
     <specialSources>
       <allEvents switchValue="All" name="All Events" />
       <notProcessed switchValue="All" name="Unprocessed Category" />
       <errors switchValue="All" name="Logging Errors &amp; Warnings">
         <listeners>
           <add name="Event Log Trace Listener" />
         </listeners>
       </errors>
     </specialSources>
   </loggingConfiguration>]]>
       </util:XmlConfig>
 
 
       <util:XmlConfig Id="asyncloggingConfiguratiodn" Action="delete" ElementPath="/configuration"   File="[DYNAMICSCRM]\Server\bin\CrmAsyncService.exe.config" Node="element"
                  On="install" Sequence="3" VerifyPath="runtime" />
 
       <util:XmlConfig Id="asynclogging" Action="create" ElementPath="/configuration"
                       File="[DYNAMICSCRM]\Server\bin\CrmAsyncService.exe.config" Node="document" On="install" Sequence="4" VerifyPath="/configuration/configSections/section[\[]@name='loggingConfiguration'[\]]" >
         <![CDATA[<configSections><section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /></configSections>]]>
       </util:XmlConfig>
 
       <util:XmlConfig Id="asyncloggingmlarky" Action="create" ElementPath="/configuration"
                       File="[DYNAMICSCRM]\Server\bin\CrmAsyncService.exe.config" Node="document" On="install" Sequence="5" VerifyPath="/configuration/runtime" >
         <![CDATA[<runtime><gcServer enabled="true"/>
         <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
           <dependentAssembly>
             <assemblyIdentity name="Microsoft.Crm.Sdk" publicKeyToken="31bf3856ad364e35" culture="neutral"/>
             <bindingRedirect oldVersion="4.0.0.0-5.0.0.0" newVersion="5.0.0.0"/>
           </dependentAssembly>
         </assemblyBinding>
       </runtime>]]>
       </util:XmlConfig>
 
       <util:XmlConfig Id="asyncloggingConfiguration" Action="create" ElementPath="/configuration"
                       File="[DYNAMICSCRM]\Server\bin\CrmAsyncService.exe.config" Node="document" On="install" Sequence="6" VerifyPath="/configuration/loggingConfiguration/listeners" >
         <![CDATA[<loggingConfiguration name="" tracingEnabled="true" defaultCategory="Trace">
     <listeners>
       <add name="Message Queuing Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.MsmqTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.MsmqTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         queuePath="FormatName:Direct=OS:[SQLHOSTNAME]\private$\logging" formatter="Binary Log Message Formatter"
         timeToReachQueue="10.00:00:00" timeToBeReceived="10.00:00:00"
         recoverable="true" useDeadLetterQueue="true" traceOutputOptions="None" />
        <add name="Event Log Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         source="Enterprise Library Logging" formatter="Text Formatter" />
     </listeners>
     <formatters>
       <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         template="Timestamp: {timestamp}{newline}&#xA;Message: {message}{newline}&#xA;Category: {category}{newline}&#xA;Priority: {priority}{newline}&#xA;EventId: {eventid}{newline}&#xA;Severity: {severity}{newline}&#xA;Title:{title}{newline}&#xA;Machine: {localMachine}{newline}&#xA;App Domain: {localAppDomain}{newline}&#xA;ProcessId: {localProcessId}{newline}&#xA;Process Name: {localProcessName}{newline}&#xA;Thread Name: {threadName}{newline}&#xA;Win32 ThreadId:{win32ThreadId}{newline}&#xA;Extended Properties: {dictionary({key} - {value}{newline})}"
         name="Text Formatter" />
       <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.BinaryLogFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
         name="Binary Log Message Formatter" />
     </formatters>
     <categorySources>
       <add switchValue="All" name="Trace">
         <listeners>
           <add name="Message Queuing Trace Listener" />
         </listeners>
       </add>
       <add switchValue="All" name="Error">
         <listeners>
           <add name="Message Queuing Trace Listener" />
         </listeners>
       </add>
     </categorySources>
     <specialSources>
       <allEvents switchValue="All" name="All Events" />
       <notProcessed switchValue="All" name="Unprocessed Category" />
       <errors switchValue="All" name="Logging Errors &amp; Warnings">
         <listeners>
           <add name="Event Log Trace Listener" />
         </listeners>
       </errors>
     </specialSources>
   </loggingConfiguration>]]>
       </util:XmlConfig>
       <File Id="fileA8FAD71029831AC3655E19B9142ABCD"  Source="..\..\bin\Microsoft.Practices.EnterpriseLibrary.Common.dll" >
         <CopyFile Id="cfile1" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfilea" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileA6A5ADE82FC3926C6591208B146ABCD"  Source="..\..\bin\Microsoft.Practices.EnterpriseLibrary.Data.dll" >
         <CopyFile Id="cfile2" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfileb" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileA87A152C5D90176A5F35A9ED31AABCD"  Source="..\..\bin\Microsoft.Practices.EnterpriseLibrary.Logging.Database.dll" >
         <CopyFile Id="cfile3" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfilec" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileA83ADD8942E77F370200E658EAEABCD"  Source="..\..\bin\Microsoft.Practices.EnterpriseLibrary.Logging.dll" >
         <CopyFile Id="cfile4" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfiled" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileAE5A8C078462AB1F516A8987676ABCD"  Source="..\..\bin\Microsoft.Practices.ServiceLocation.dll" >
         <CopyFile Id="cfile5" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfilee" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileA5CDA0533279F89ACC36D7BDCD6ABCD"  Source="..\..\bin\Microsoft.Practices.Unity.dll" >
         <CopyFile Id="cfile6" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfilef" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
       <File Id="fileAC309FAF27707DCE67A3664FA1FABCD"  Source="..\..\bin\Microsoft.Practices.Unity.Interception.dll" >
         <CopyFile Id="cfile7" DestinationProperty="DYNAMICSBIN"/>
         <CopyFile Id="cfileg" DestinationProperty="DYNAMICSWEBBIN"/>
       </File>
     </Component>
   </Fragment>
   <Fragment>
     <DirectoryRef  Id="INSTALLLOCATION">
       <Directory Id="dirB50C76F5A59FB10334E162992F65AAAA" />
     </DirectoryRef>
   </Fragment>
 </Wix>
Property Definition Sample
 <Property Id="DYNAMICSCRMWEB" Value="C:\Program Files\Microsoft Dynamics CRM\CRMWeb">
   <RegistrySearch Id="Dynamicscrmweb_RegKey" Type="raw" Root="HKLM" Key="SOFTWARE\Microsoft\MSCRM" Name="WebSitePath" Win64="yes"/>
 </Property>
 
 <Property Id="DYNAMICSCRM" >
   <RegistrySearch Id="Dynamics_RegKey" Type="raw" Root="HKLM" Key="SOFTWARE\Microsoft\MSCRM" Name="CRM_Server_InstallDir" Win64="yes"/>
 </Property>
 
 <Property Id="DYNAMICS" >
   <RegistrySearch Id="DynamicsgKey" Type="raw" Root="HKLM" Key="SOFTWARE\Microsoft\MSCRM" Name="roles" Win64="yes"/>
 </Property>
 
 <Property Id="DYNAMICSWEBBIN"/>
 
 <SetProperty Id="DYNAMICSWEBBIN" Value="[DYNAMICSCRMWEB]\bin" After="CostFinalize"/>
 
 <Property Id="DYNAMICSBIN"/>
 
 <SetProperty Id="DYNAMICSBIN" Value="[DYNAMICSCRM]\Server\bin" After="CostFinalize"/>
The next post of the series can be found here