/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Resources;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Threading;
namespace System.Management.Automation
{
///
/// EventLogLogProvider is a class to implement Msh Provider interface using EventLog technology.
///
/// EventLogLogProvider will be the provider to use if Monad is running in early windows releases
/// from 2000 to 2003.
///
/// EventLogLogProvider will be packaged in the same dll as Msh Log Engine since EventLog should
/// always be available.
///
///
internal class EventLogLogProvider : LogProvider
{
///
/// Constructor.
///
///
internal EventLogLogProvider(string shellId)
{
string source = SetupEventSource(shellId);
_eventLog = new EventLog();
_eventLog.Source = source;
_resourceManager = new ResourceManager("System.Management.Automation.resources.Logging", System.Reflection.Assembly.GetExecutingAssembly());
}
internal string SetupEventSource(string shellId)
{
string source;
// In case shellId == null, use the "Default" source.
if (String.IsNullOrEmpty(shellId))
{
source = "Default";
}
else
{
int index = shellId.LastIndexOf('.');
if (index < 0)
source = shellId;
else
source = shellId.Substring(index + 1);
// There may be a situation where ShellId ends with a '.'.
// In that case, use the default source.
if (String.IsNullOrEmpty(source))
source = "Default";
}
if (EventLog.SourceExists(source))
{
return source;
}
string message = String.Format(Thread.CurrentThread.CurrentCulture, "Event source '{0}' is not registered", source);
throw new InvalidOperationException(message);
}
///
/// This represent a handle to EventLog
///
private EventLog _eventLog;
private ResourceManager _resourceManager;
#region Log Provider Api
private const int EngineHealthCategoryId = 1;
private const int CommandHealthCategoryId = 2;
private const int ProviderHealthCategoryId = 3;
private const int EngineLifecycleCategoryId = 4;
private const int CommandLifecycleCategoryId = 5;
private const int ProviderLifecycleCategoryId = 6;
private const int SettingsCategoryId = 7;
private const int PipelineExecutionDetailCategoryId = 8;
///
/// Log engine health event
///
///
///
///
///
internal override void LogEngineHealthEvent(LogContext logContext, int eventId, Exception exception, Dictionary additionalInfo)
{
Hashtable mapArgs = new Hashtable();
IContainsErrorRecord icer = exception as IContainsErrorRecord;
if (null != icer && null != icer.ErrorRecord)
{
mapArgs["ExceptionClass"] = exception.GetType().Name;
mapArgs["ErrorCategory"] = icer.ErrorRecord.CategoryInfo.Category;
mapArgs["ErrorId"] = icer.ErrorRecord.FullyQualifiedErrorId;
if (icer.ErrorRecord.ErrorDetails != null)
{
mapArgs["ErrorMessage"] = icer.ErrorRecord.ErrorDetails.Message;
}
else
{
mapArgs["ErrorMessage"] = exception.Message;
}
}
else
{
mapArgs["ExceptionClass"] = exception.GetType().Name;
mapArgs["ErrorCategory"] = "";
mapArgs["ErrorId"] = "";
mapArgs["ErrorMessage"] = exception.Message;
}
FillEventArgs(mapArgs, logContext);
FillEventArgs(mapArgs, additionalInfo);
EventInstance entry = new EventInstance(eventId, EngineHealthCategoryId);
entry.EntryType = GetEventLogEntryType(logContext);
string detail = GetEventDetail("EngineHealthContext", mapArgs);
LogEvent(entry, mapArgs["ErrorMessage"], detail);
}
private static EventLogEntryType GetEventLogEntryType(LogContext logContext)
{
switch (logContext.Severity)
{
case "Critical":
case "Error":
return EventLogEntryType.Error;
case "Warning":
return EventLogEntryType.Warning;
default:
return EventLogEntryType.Information;
}
}
///
/// Log engine lifecycle event
///
///
///
///
internal override void LogEngineLifecycleEvent(LogContext logContext, EngineState newState, EngineState previousState)
{
int eventId = GetEngineLifecycleEventId(newState);
if (eventId == _invalidEventId)
return;
Hashtable mapArgs = new Hashtable();
mapArgs["NewEngineState"] = newState.ToString();
mapArgs["PreviousEngineState"] = previousState.ToString();
FillEventArgs(mapArgs, logContext);
EventInstance entry = new EventInstance(eventId, EngineLifecycleCategoryId);
entry.EntryType = EventLogEntryType.Information;
string detail = GetEventDetail("EngineLifecycleContext", mapArgs);
LogEvent(entry, newState, previousState, detail);
}
private const int _baseEngineLifecycleEventId = 400;
private const int _invalidEventId = -1;
///
/// Get engine lifecyle event id based on engine state
///
///
///
private static int GetEngineLifecycleEventId(EngineState engineState)
{
switch (engineState)
{
case EngineState.None:
return _invalidEventId;
case EngineState.Available:
return _baseEngineLifecycleEventId;
case EngineState.Degraded:
return _baseEngineLifecycleEventId + 1;
case EngineState.OutOfService:
return _baseEngineLifecycleEventId + 2;
case EngineState.Stopped:
return _baseEngineLifecycleEventId + 3;
}
return _invalidEventId;
}
private const int _commandHealthEventId = 200;
///
/// Provider interface function for logging command health event
///
///
///
///
internal override void LogCommandHealthEvent(LogContext logContext, Exception exception)
{
int eventId = _commandHealthEventId;
Hashtable mapArgs = new Hashtable();
IContainsErrorRecord icer = exception as IContainsErrorRecord;
if (null != icer && null != icer.ErrorRecord)
{
mapArgs["ExceptionClass"] = exception.GetType().Name;
mapArgs["ErrorCategory"] = icer.ErrorRecord.CategoryInfo.Category;
mapArgs["ErrorId"] = icer.ErrorRecord.FullyQualifiedErrorId;
if (icer.ErrorRecord.ErrorDetails != null)
{
mapArgs["ErrorMessage"] = icer.ErrorRecord.ErrorDetails.Message;
}
else
{
mapArgs["ErrorMessage"] = exception.Message;
}
}
else
{
mapArgs["ExceptionClass"] = exception.GetType().Name;
mapArgs["ErrorCategory"] = "";
mapArgs["ErrorId"] = "";
mapArgs["ErrorMessage"] = exception.Message;
}
FillEventArgs(mapArgs, logContext);
EventInstance entry = new EventInstance(eventId, CommandHealthCategoryId);
entry.EntryType = GetEventLogEntryType(logContext);
string detail = GetEventDetail("CommandHealthContext", mapArgs);
LogEvent(entry, mapArgs["ErrorMessage"], detail);
}
///
/// Log command life cycle event.
///
///
///
internal override void LogCommandLifecycleEvent(Func getLogContext, CommandState newState)
{
LogContext logContext = getLogContext();
int eventId = GetCommandLifecycleEventId(newState);
if (eventId == _invalidEventId)
return;
Hashtable mapArgs = new Hashtable();
mapArgs["NewCommandState"] = newState.ToString();
FillEventArgs(mapArgs, logContext);
EventInstance entry = new EventInstance(eventId, CommandLifecycleCategoryId);
entry.EntryType = EventLogEntryType.Information;
string detail = GetEventDetail("CommandLifecycleContext", mapArgs);
LogEvent(entry, logContext.CommandName, newState, detail);
}
private const int _baseCommandLifecycleEventId = 500;
///
/// Get command lifecycle event id based on command state
///
///
///
private static int GetCommandLifecycleEventId(CommandState commandState)
{
switch (commandState)
{
case CommandState.Started:
return _baseCommandLifecycleEventId;
case CommandState.Stopped:
return _baseCommandLifecycleEventId + 1;
case CommandState.Terminated:
return _baseCommandLifecycleEventId + 2;
}
return _invalidEventId;
}
private const int _pipelineExecutionDetailEventId = 800;
///
/// Log pipeline execution detail event.
///
/// This may end of logging more than one event if the detail string is too long to be fit in 64K.
///
///
///
internal override void LogPipelineExecutionDetailEvent(LogContext logContext, List pipelineExecutionDetail)
{
List details = GroupMessages(pipelineExecutionDetail);
for (int i = 0; i < details.Count; i++)
{
LogPipelineExecutionDetailEvent(logContext, details[i], i + 1, details.Count);
}
}
private const int MaxLength = 16000;
private List GroupMessages(List messages)
{
List result = new List();
if (messages == null || messages.Count == 0)
return result;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < messages.Count; i++)
{
if (sb.Length + messages[i].Length < MaxLength)
{
sb.AppendLine(messages[i]);
continue;
}
result.Add(sb.ToString());
sb = new StringBuilder();
sb.AppendLine(messages[i]);
}
result.Add(sb.ToString());
return result;
}
///
/// Log one pipeline execution detail event. Detail message is already chopped up so that it will
/// fit in 64K.
///
///
///
///
///
private void LogPipelineExecutionDetailEvent(LogContext logContext, String pipelineExecutionDetail, int detailSequence, int detailTotal)
{
int eventId = _pipelineExecutionDetailEventId;
Hashtable mapArgs = new Hashtable();
mapArgs["PipelineExecutionDetail"] = pipelineExecutionDetail;
mapArgs["DetailSequence"] = detailSequence;
mapArgs["DetailTotal"] = detailTotal;
FillEventArgs(mapArgs, logContext);
EventInstance entry = new EventInstance(eventId, PipelineExecutionDetailCategoryId);
entry.EntryType = EventLogEntryType.Information;
string pipelineInfo = GetEventDetail("PipelineExecutionDetailContext", mapArgs);
LogEvent(entry, logContext.CommandLine, pipelineInfo, pipelineExecutionDetail);
}
private const int _providerHealthEventId = 300;
///
/// Provider interface function for logging provider health event
///
///
///
///
///
internal override void LogProviderHealthEvent(LogContext logContext, string providerName, Exception exception)
{
int eventId = _providerHealthEventId;
Hashtable mapArgs = new Hashtable();
mapArgs["ProviderName"] = providerName;
IContainsErrorRecord icer = exception as IContainsErrorRecord;
if (null != icer && null != icer.ErrorRecord)
{
mapArgs["ExceptionClass"] = exception.GetType().Name;
mapArgs["ErrorCategory"] = icer.ErrorRecord.CategoryInfo.Category;
mapArgs["ErrorId"] = icer.ErrorRecord.FullyQualifiedErrorId;
if (icer.ErrorRecord.ErrorDetails != null
&& !String.IsNullOrEmpty(icer.ErrorRecord.ErrorDetails.Message))
{
mapArgs["ErrorMessage"] = icer.ErrorRecord.ErrorDetails.Message;
}
else
{
mapArgs["ErrorMessage"] = exception.Message;
}
}
else
{
mapArgs["ExceptionClass"] = exception.GetType().Name;
mapArgs["ErrorCategory"] = "";
mapArgs["ErrorId"] = "";
mapArgs["ErrorMessage"] = exception.Message;
}
FillEventArgs(mapArgs, logContext);
EventInstance entry = new EventInstance(eventId, ProviderHealthCategoryId);
entry.EntryType = GetEventLogEntryType(logContext);
string detail = GetEventDetail("ProviderHealthContext", mapArgs);
LogEvent(entry, mapArgs["ErrorMessage"], detail);
}
///
/// Log provider lifecycle event.
///
///
///
///
internal override void LogProviderLifecycleEvent(LogContext logContext, string providerName, ProviderState newState)
{
int eventId = GetProviderLifecycleEventId(newState);
if (eventId == _invalidEventId)
return;
Hashtable mapArgs = new Hashtable();
mapArgs["ProviderName"] = providerName;
mapArgs["NewProviderState"] = newState.ToString();
FillEventArgs(mapArgs, logContext);
EventInstance entry = new EventInstance(eventId, ProviderLifecycleCategoryId);
entry.EntryType = EventLogEntryType.Information;
string detail = GetEventDetail("ProviderLifecycleContext", mapArgs);
LogEvent(entry, providerName, newState, detail);
}
private const int _baseProviderLifecycleEventId = 600;
///
/// Get provider lifecycle event id based on provider state.
///
///
///
private static int GetProviderLifecycleEventId(ProviderState providerState)
{
switch (providerState)
{
case ProviderState.Started:
return _baseProviderLifecycleEventId;
case ProviderState.Stopped:
return _baseProviderLifecycleEventId + 1;
}
return _invalidEventId;
}
private const int _settingsEventId = 700;
///
/// Log settings event.
///
///
///
///
///
internal override void LogSettingsEvent(LogContext logContext, string variableName, string value, string previousValue)
{
int eventId = _settingsEventId;
Hashtable mapArgs = new Hashtable();
mapArgs["VariableName"] = variableName;
mapArgs["NewValue"] = value;
mapArgs["PreviousValue"] = previousValue;
FillEventArgs(mapArgs, logContext);
EventInstance entry = new EventInstance(eventId, SettingsCategoryId);
entry.EntryType = EventLogEntryType.Information;
string detail = GetEventDetail("SettingsContext", mapArgs);
LogEvent(entry, variableName, value, previousValue, detail);
}
#endregion Log Provider Api
#region EventLog helper functions
///
/// This is the helper function for logging an event with localizable message
/// to event log. It will trace all exception thrown by eventlog.
///
///
///
private void LogEvent(EventInstance entry, params object[] args)
{
try
{
_eventLog.WriteEvent(entry, args);
}
catch (ArgumentException)
{
return;
}
catch (InvalidOperationException)
{
return;
}
catch (Win32Exception)
{
return;
}
}
#endregion
#region Event Arguments
///
/// Fill event arguments with logContext info.
///
/// In EventLog Api, arguments are passed in as an array of objects.
///
/// An ArrayList to contain the event arguments
/// The log context containing the info to fill in
private static void FillEventArgs(Hashtable mapArgs, LogContext logContext)
{
mapArgs["Severity"] = logContext.Severity;
mapArgs["SequenceNumber"] = logContext.SequenceNumber;
mapArgs["HostName"] = logContext.HostName;
mapArgs["HostVersion"] = logContext.HostVersion;
mapArgs["HostId"] = logContext.HostId;
mapArgs["HostApplication"] = logContext.HostApplication;
mapArgs["EngineVersion"] = logContext.EngineVersion;
mapArgs["RunspaceId"] = logContext.RunspaceId;
mapArgs["PipelineId"] = logContext.PipelineId;
mapArgs["CommandName"] = logContext.CommandName;
mapArgs["CommandType"] = logContext.CommandType;
mapArgs["ScriptName"] = logContext.ScriptName;
mapArgs["CommandPath"] = logContext.CommandPath;
mapArgs["CommandLine"] = logContext.CommandLine;
mapArgs["User"] = logContext.User;
mapArgs["Time"] = logContext.Time;
}
///
/// Fill event arguments with additionalInfo stored in a string dictionary.
///
/// An arraylist to contain the event arguments
/// A string dictionary to fill in
private static void FillEventArgs(Hashtable mapArgs, Dictionary additionalInfo)
{
if (additionalInfo == null)
{
for (int i = 0; i < 3; i++)
{
string id = ((int)(i + 1)).ToString("d1", CultureInfo.CurrentCulture);
mapArgs["AdditionalInfo_Name" + id] = "";
mapArgs["AdditionalInfo_Value" + id] = "";
}
return;
}
string[] keys = new string[additionalInfo.Count];
string[] values = new string[additionalInfo.Count];
additionalInfo.Keys.CopyTo(keys, 0);
additionalInfo.Values.CopyTo(values, 0);
for (int i = 0; i < 3; i++)
{
string id = ((int)(i + 1)).ToString("d1", CultureInfo.CurrentCulture);
if (i < keys.Length)
{
mapArgs["AdditionalInfo_Name" + id] = keys[i];
mapArgs["AdditionalInfo_Value" + id] = values[i];
}
else
{
mapArgs["AdditionalInfo_Name" + id] = "";
mapArgs["AdditionalInfo_Value" + id] = "";
}
}
return;
}
#endregion Event Arguments
#region Event Message
private string GetEventDetail(string contextId, Hashtable mapArgs)
{
return GetMessage(contextId, mapArgs);
}
private string GetMessage(string messageId, Hashtable mapArgs)
{
if (_resourceManager == null)
return "";
string messageTemplate = _resourceManager.GetString(messageId);
if (String.IsNullOrEmpty(messageTemplate))
return "";
return FillMessageTemplate(messageTemplate, mapArgs);
}
private static string FillMessageTemplate(string messageTemplate, Hashtable mapArgs)
{
StringBuilder message = new StringBuilder();
int cursor = 0;
while (true)
{
int startIndex = messageTemplate.IndexOf('[', cursor);
if (startIndex < 0)
{
message.Append(messageTemplate.Substring(cursor));
return message.ToString();
}
int endIndex = messageTemplate.IndexOf(']', startIndex + 1);
if (endIndex < 0)
{
message.Append(messageTemplate.Substring(cursor));
return message.ToString();
}
message.Append(messageTemplate.Substring(cursor, startIndex - cursor));
cursor = startIndex;
string placeHolder = messageTemplate.Substring(startIndex + 1, endIndex - startIndex - 1);
if (mapArgs.Contains(placeHolder))
{
message.Append(mapArgs[placeHolder]);
cursor = endIndex + 1;
}
else
{
message.Append("[");
cursor++;
}
}
}
#endregion Event Message
}
}