Exchange 2007 Execute Diagnostic CmdLet

Microsoft.Exchange2007.ExecuteDiagnostic.DS (ProbeActionModuleType)

Element properties:

TypeProbeActionModuleType
IsolationAny
AccessibilityInternal
RunAsMicrosoft.Exchange2007.Account.LocalSystem
InputTypeSystem.BaseData
OutputTypeSystem.PropertyBagData

Member Modules:

ID Module Type TypeId RunAs 
PA ProbeAction Microsoft.Exchange2007.Script.CommonPropertyBag.PA Default

Source Code:

<ProbeActionModuleType ID="Microsoft.Exchange2007.ExecuteDiagnostic.DS" Accessibility="Internal" RunAs="ExLibrary!Microsoft.Exchange2007.Account.LocalSystem" Batching="false" PassThrough="false">
<Configuration>
<xsd:element name="command" type="xsd:string"/>
<xsd:element name="eventSourceName" type="xsd:string"/>
<xsd:element name="performanceData" type="xsd:boolean"/>
<xsd:element name="TimeoutSeconds" type="xsd:integer"/>
<xsd:element name="LogLevel" type="xsd:integer"/>
</Configuration>
<ModuleImplementation Isolation="Any">
<Composite>
<MemberModules>
<ProbeAction ID="PA" TypeID="Microsoft.Exchange2007.Script.CommonPropertyBag.PA">
<ScriptName>ExecuteDiagnostic.js</ScriptName>
<LogLevel>$Config/LogLevel$</LogLevel>
<Arguments>$Target/Host/Property[Type='Windows!Microsoft.Windows.Computer']/PrincipalName$ "$Config/command$" "$Config/eventSourceName$" $Config/performanceData$</Arguments>
<ScriptBody><Script>EXPECTED_ARGUMENT_COUNT = 5;
SCRIPT_NAME = "ExecuteDiagnostic.js";

// ----------------------------------------------------------------------------------------
// Constants for Error ID or Messages
// ----------------------------------------------------------------------------------------

// Events
var DEFAULT_EVENT_SOURCE = "MSExchange Monitoring"; // To be used only if mandatory parameters are not specified, or when attempting to start the monitoring service
var MISSING_MANDATORY_PARAM_ID = 300;
var MISSING_MANDATORY_PARAM_MSG = "Cannot execute Exchange diagnostic cmdlet. The mandatory script parameter on ";
var ATTEMPT_TO_START_MON_SVC_ID = 301;
var ATTEMPT_TO_START_MON_SVC_MSG = "Attempt %0 of %1 to start the MSExchangeMonitoring service.";
var ATTEMPT_TO_START_MON_SVC_RESULT_ID = 302;
var ATTEMPT_TO_START_MON_SVC_RESULT_MSG = "Attempt to start the MSExchangeMonitoring service using Win32_Service.StartService returned %0.";
var CANNOT_START_MON_SVC_ID = 303;
var MON_SVC_DISABLED_MSG = "The MSExchangeMonitoring service is disabled.";
var WMI_CANNOT_GET_MON_SVC_MSG = "WMI failed to get the MSExchangeMonitoring service object. Error: %0\n\n%1";
var MON_SVC_PROBABLY_NOT_INSTALLED_EXTRA_MSG = "This error typically indicates that the service is not installed.";
var NEXT_START_MON_SVC_DELAYED_MSG = "Previous attempts to start the MSExchangeMonitoring service failed. Monitoring is going to wait at least %0 minutes before trying again. " + "If the problem preventing the service to start is fixed and the service is manually started the script will start to run the cmdlets immediately.";

// Minimum time without attempts to start the monitoring service, after a consecutive failures
var MINUTES_UNTIL_NEXT_START_ATTEMPT = 240;

// The source of the events below is going to be the string passed in the eventSourceName parameter
var SUCCESSFUL_EXECUTION_ID = 400;
var SUCCESSFUL_EXECUTION_MSG = "Exchange diagnostic cmdlet invocation succeeded.";
var NO_EVENTS_FROM_CMDLET_ID = 401;
var NO_EVENTS_FROM_CMDLET_MSG = "The command did not return any monitoring event.";
var DIAG_CMDLET_CONTROLLER_ERROR_ID = 402;
var DIAG_CMDLET_CONTROLLER_ERROR_MSG = "The diagnostic cmdlet controller failed to execute the command.\n\nError: %0\n\n%1";
var COM_OBJ_CREATION_ERROR_ID = 403;
var COM_OBJ_CREATION_ERROR_MSG = "The creation of the COM object used to execute the diagnostic cmdlet failed, possible installation problem.\n\nError: %0";

// Important error codes returned when trying to run a cmdlet
var EPT_S_NOT_REGISTERED = -2147416359; // 0x800106D9 - typically means service not running (it can be stopped, disabled, not installed, etc)
var RPC_E_CANTCALLOUT_INEXTERNALCALL = -2147418107; // 0x80010005 - RPC denied access to the end point
var RPC_S_CALL_FAILED_DNE = -2147416385; // 0x800106BF - RPC call failed but the end point was reachable at a certain point in time
var RPC_S_CALL_FAILED = -2147416386; // 0x800106BE - generic RPC call failed
var RPC_S_INVALID_BOUND = -2147416378; // 0x800106C6 - RPC the array bounds are invalid
var CTL_E_ILLEGALFUNCTIONCALL = -2146828283; // 0x800A0005 - Invalid request

var WMI_HRESULT_FAILED_GETOBJECT = -2147217406; // 0x80041002

// Extra error information that is used to complement the event logged when the cmdlet command fails
var RPC_E_CANTCALLOUT_INEXTERNALCALL_EXTRA_MSG = "The script is not running as LocalSystem. The MSExchangeMonitoring service only accepts requests from LocalSystem.";
var RPC_S_CALL_FAILED_DNE_EXTRA_MSG = "This error typically indicates that the MSExchangeMonitoring service was stopped while processing the command.";
var RPC_S_INVALID_BOUND_EXTRA_MSG = "The command returned more events or performance counters values than allowed.";
var CTL_E_ILLEGALFUNCTIONCALL_EXTRA_MSG = "The MSExchangeMonitoring service do not support scripts and only a limited set of Exchange cmdlets (test-*) and parameters (positional parameters are not allowed). The accepted syntax is also more restricted than the one accepted by the Exchange Management Shell.";

// Strings to control failures to start the Monitoring service
var lastSvcStartFailureTimeRegEntry = "HKLM\\SOFTWARE\\Microsoft\\Microsoft Operations Manager\\3.0\\Modules\\" + WScript.ScriptName + "\\LastServiceStartFailureTime";

// Typed property bag constants
var ALERT_DATA_TYPE = 0;
var EVENT_DATA_TYPE = 1;
var PERF_DATA_TYPE = 2;
var STATE_DATA_TYPE = 3;

main();

function main()
{
CheckArgument();

var loggingComputer = WScript.Arguments(1);
var command = WScript.Arguments(2);
var eventSourceName = WScript.Arguments(3);
var performanceData = WScript.Arguments(4);

CheckRunningLocalSystem();

LogEvent(TRACING_ID, EVENT_TYPE_INFORMATION, "Script initialization");

// Create the COM object that allows to execute the diagnostic cmdlet
var diagnosticController = new ActiveXObject("DiagnosticCmdletController");

// Variables to control the loop that tries to run the diagnostic cmdlet
var maxAttemptsToStartMonitoringService = 3;
var totalAttemptsToStartMonitoringService = 0;
var notRun = true;

do
{
try
{
LogEvent(TRACING_ID, EVENT_TYPE_INFORMATION, "Try running DiagnosticCmdletController");

diagnosticController.Run(command, eventSourceName);

LogEvent(TRACING_ID, EVENT_TYPE_INFORMATION, "Finished running DiagnosticCmdletController");

notRun = false;
}
catch (err)
{
LogEvent(TRACING_ID, EVENT_TYPE_INFORMATION, "Exception caught running DiagnosticCmdletController. \n" +
"Error Number: " + err.number + "\nError Description: " + err.description);

// Check if the last attempt to start the service was already executed.
if (totalAttemptsToStartMonitoringService == maxAttemptsToStartMonitoringService)
{
// The appropriate event and message is going to be generated below (see case for EPT_S_NOT_REGISTERED).
LogEvent(TRACING_ID, EVENT_TYPE_INFORMATION, "Maximum attempt reached");

// Last attempt to start the service on this run of the script failed. Set a registry entry to indicate the time of it,
// this is going to be used to control when the next runs of the script should try again.
var timeLastAttemptFailure = new Date();
WriteRegistryValue(lastSvcStartFailureTimeRegEntry, timeLastAttemptFailure.toUTCString());
}

// Check if service is running
if ((err.number == EPT_S_NOT_REGISTERED) &amp;&amp; (totalAttemptsToStartMonitoringService &lt; maxAttemptsToStartMonitoringService))
{
if (!IsMonitoringServiceReadyToBeStarted(eventSourceName))
{
// The appropriate events were generated on the function above, here just return
return;
}
else
{
totalAttemptsToStartMonitoringService++;

CreateEvent(
DEFAULT_EVENT_SOURCE,
ATTEMPT_TO_START_MON_SVC_ID,
EVENT_TYPE_INFORMATION,
FormatText(ATTEMPT_TO_START_MON_SVC_MSG, new Array(totalAttemptsToStartMonitoringService, maxAttemptsToStartMonitoringService)),
loggingComputer);

AttemptToStartMonitoringService();
}
}
else
{
var extraErrorInfo = "";

switch (err.number)
{
case RPC_S_CALL_FAILED_DNE:
case RPC_S_CALL_FAILED:
extraErrorInfo = RPC_S_CALL_FAILED_DNE_EXTRA_MSG;
break;
case RPC_S_INVALID_BOUND:
extraErrorInfo = RPC_S_INVALID_BOUND_EXTRA_MSG;
break;
case CTL_E_ILLEGALFUNCTIONCALL:
extraErrorInfo = CTL_E_ILLEGALFUNCTIONCALL_EXTRA_MSG;
break;
case EPT_S_NOT_REGISTERED:
extraErrorInfo = FormatText(NEXT_START_MON_SVC_DELAYED_MSG, new Array(MINUTES_UNTIL_NEXT_START_ATTEMPT.toString()));
break;
}

CreateEvent(
eventSourceName,
DIAG_CMDLET_CONTROLLER_ERROR_ID,
EVENT_TYPE_ERROR,
FormatText(DIAG_CMDLET_CONTROLLER_ERROR_MSG, new Array(ConvertHResultToString(err.number) + " - " + err.description, extraErrorInfo)),
loggingComputer);

return;
}
}
} while (notRun);

// Check if performace data should be submitted and if so submit it
if (performanceData.toLowerCase() != "false")
{
var tmpPerfVBArray = new VBArray(diagnosticController.PerformanceCounters);
var perfCounters = tmpPerfVBArray.toArray();
for (i in perfCounters)
{
CreatePerformanceData(
eventSourceName, // Use the monitoring data source as the object
perfCounters[i].Counter,
perfCounters[i].Instance,
perfCounters[i].Value,
loggingComputer);
}
}

// Submit monitoring events
var tmpEventVBArray = new VBArray(diagnosticController.Events);
var events = tmpEventVBArray.toArray();
for (i in events)
{
CreateEvent(
eventSourceName, // Use the monitoring data source as the event source
events[i].Identifier,
events[i].Type,
events[i].Message,
loggingComputer);
}

// The diagnostic cmdlet should have returned at least one monitoring event, if not report this as an error.
if (events.length == 0)
{
CreateEvent(
eventSourceName,
NO_EVENTS_FROM_CMDLET_ID,
EVENT_TYPE_ERROR,
NO_EVENTS_FROM_CMDLET_MSG,
loggingComputer);
}
else
{
// If the code got to this point it means that the cmdlet was successfully executed and it returned
// some monitoring events, report the success to resolve any possible alert.
CreateEvent(
eventSourceName,
SUCCESSFUL_EXECUTION_ID,
EVENT_TYPE_INFORMATION,
SUCCESSFUL_EXECUTION_MSG,
loggingComputer);
}

scriptAPI.ReturnItems();
}

// Function that attempts to start the MSExchangeMonitoring service, it does not guarantee that the
// service is going to be running after its call.
function AttemptToStartMonitoringService()
{
var defaultWaitMSec = 8000;
var svcObj = GetObject("winmgmts://./root/cimv2:Win32_Service.Name='MSExchangeMonitoring'");

// Wait a random amount of time so scripts running at the same time do not try to start the
// service all at once.
WScript.Sleep(1000 + Math.round(defaultWaitMSec * Math.random()))

// MSExchangeMonitoring does not support pause, so its possible states are:
// "Running", "Stopped", "Start Pending", "Stop Pending", and "Unknown".
// Check the pending states first
if (svcObj.State.lastIndexOf("Pending") != -1)
{
// Give some time to the service so it can complete the state transition
//
WScript.Sleep(defaultWaitMSec);
}

// Check if during the wait the service was not started before attempting to start it
//
if (svcObj.State != "Running")
{
// Some of the possible return codes from StartService, check the documentation of
// Win32_Service.StartService for a complete list of possible return codes.
//
var successCode = 0;
var serviceAlreadyRunningCode = 10;

var returnCode = svcObj.StartService();
// Just report return code if it was not expected.
if ((returnCode != successCode) &amp;&amp; (returnCode != serviceAlreadyRunningCode))
{
CreateEvent(
DEFAULT_EVENT_SOURCE,
ATTEMPT_TO_START_MON_SVC_RESULT_ID,
EVENT_TYPE_INFORMATION, // This is not necessarily an error, report the return code just to help debug (if necessary)
FormatText(ATTEMPT_TO_START_MON_SVC_RESULT_MSG, new Array(returnCode.toString())),
loggingComputer);
}
WScript.Sleep(defaultWaitMSec);
}
}

// Function that checks if the MSExchangeMonitoring service is ready to be started. There are some
// circunstances that do not allow the service to be started (service not installed, service disabled, ...)
//
function IsMonitoringServiceReadyToBeStarted(eventSourceName)
{
var isReady = true; // Assume that the service is ready
var svcObj;

try
{
svcObj = GetObject("winmgmts://./root/cimv2:Win32_Service.Name='MSExchangeMonitoring'");
}
catch(err)
{
var extraErrorInfo = "";

if (err.number == WMI_HRESULT_FAILED_GETOBJECT)
{
extraErrorInfo = MON_SVC_PROBABLY_NOT_INSTALLED_EXTRA_MSG;
}

CreateEvent(
DEFAULT_EVENT_SOURCE,
CANNOT_START_MON_SVC_ID,
EVENT_TYPE_ERROR,
FormatText(WMI_CANNOT_GET_MON_SVC_MSG, new Array(ConvertHResultToString(err.number) + " - " + err.description, extraErrorInfo)),
loggingComputer);

isReady = false;
}

// Only check the other requirements if the previous check passed
//
if (isReady)
{
if (svcObj.StartMode == "Disabled")
{
// Service cannot be started because it is disabled
//
CreateEvent(
DEFAULT_EVENT_SOURCE,
CANNOT_START_MON_SVC_ID,
EVENT_TYPE_ERROR,
MON_SVC_DISABLED_MSG,
loggingComputer);
isReady = false;
}
}

if (isReady)
{
// An attempt to start the service should be made only if the last failure to start it happened some time ago
//
var timeLastAttemptFailure = ReadRegistryValue(lastSvcStartFailureTimeRegEntry);
// If the registry entry is not set it means that there is no record of a previous failure, so the monitoring service should be considered
// ready to be started, otherwise it is necessary to check when the last failure to start it happened.
//
if (null != timeLastAttemptFailure)
{
timeLastAttemptFailure = new Date(timeLastAttemptFailure);
var now = new Date();
var minutesAfterLastAttempt = (now - timeLastAttemptFailure) / 60000; // The result of the subtraction is in milliseconds, divide to get a number in minutes
if (minutesAfterLastAttempt &lt; MINUTES_UNTIL_NEXT_START_ATTEMPT)
{
var waitInMinutes = Math.round(MINUTES_UNTIL_NEXT_START_ATTEMPT - minutesAfterLastAttempt);
if (waitInMinutes &lt; 1)
waitInMinutes = 1;
isReady = false;
CreateEvent(
eventSourceName,
DIAG_CMDLET_CONTROLLER_ERROR_ID,
EVENT_TYPE_ERROR,
FormatText(NEXT_START_MON_SVC_DELAYED_MSG, new Array(waitInMinutes.toString())),
loggingComputer);
}
else
{
// Set the registry entry to the not set sentinel so a new interval can be started in case of new failure
//
WriteRegistryValue(lastSvcStartFailureTimeRegEntry, null);
}
}
}

return isReady;
}


// Truncates a message, if necessary, appending the appropriate suffix
//
function TruncatedMsg(msg, maxMsgLength, truncatedMsgSuffix, defaultMsgSuffix)
{
if ((msg.length + defaultMsgSuffix.length) &gt; maxMsgLength)
{
msg = msg.substring(0, maxMsgLength - (truncatedMsgSuffix.length)) + truncatedMsgSuffix;
}
else
{
msg = msg + defaultMsgSuffix;
}

return msg;
}

function CreateEvent(strSource, lngEventID, lngEventType, strMsg, strComputer)
{
CreateEventEx(strSource, lngEventID, lngEventType, strMsg, strComputer, true);
}

function CreateReportEvent(strSource, lngEventID, lngEventType, strMsg, strComputer)
{
CreateEventEx(strSource, lngEventID, lngEventType, strMsg, strComputer, false);
}

function CreateEventEx(strSource, lngEventID, lngEventType, strMsg, strComputer, blnIncRuleName)
{
var objNewEvent = scriptAPI.CreateTypedPropertyBag(EVENT_DATA_TYPE);
var MAX_MOM_MSG_LEN = 3000; // Old limit from MOM2K5, in which UI supports 3000 chars while the DB supports 3500
var truncatedMsgSuffix = "...";
var defaultMsgSuffix = "";

if (blnIncRuleName)
{
// should be pre-processed in either source or message instead
//if (command != null)
//defaultMsgSuffix += "\n\nDiagnostic command: \"" + command + "\"";
truncatedMsgSuffix += defaultMsgSuffix;
}
objNewEvent.AddValue("Message", TruncatedMsg(strMsg, MAX_MOM_MSG_LEN, truncatedMsgSuffix, defaultMsgSuffix));
objNewEvent.AddValue("EventNumber", lngEventID);
objNewEvent.AddValue("EventType", lngEventType);
objNewEvent.AddValue("EventSource", strSource);

if (strComputer == "" || strComputer == null)
{
strComputer = loggingComputer;
}
objNewEvent.AddValue("LoggingComputer", strComputer);

scriptAPI.AddItem(objNewEvent);
}

function CreatePerformanceData(strObjectName, strCounterName, strInstanceName, dblSampleValue, strSrcComputer)
{
var objPerfData = scriptAPI.CreateTypedPropertyBag(PERF_DATA_TYPE);

objPerfData.AddValue("ObjectName", strObjectName);
objPerfData.AddValue("CounterName", strCounterName);
objPerfData.AddValue("InstanceName", strInstanceName);
objPerfData.AddValue("Value", dblSampleValue);

if (strSrcComputer == "" || strSrcComputer == null)
{
strSrcComputer = loggingComputer;
}
objPerfData.AddValue("LoggingComputer", strSrcComputer);

scriptAPI.AddItem(objPerfData);
}
</Script></ScriptBody>
<TimeoutSeconds>$Config/TimeoutSeconds$</TimeoutSeconds>
</ProbeAction>
</MemberModules>
<Composition>
<Node ID="PA"/>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.PropertyBagData</OutputType>
<InputType>System!System.BaseData</InputType>
</ProbeActionModuleType>