//*************************************************************
// $ScriptName: "Microsoft Exchange 2010 - Execute Diagnostic Cmdlet" $
//
// Execute an Exchange diagnostic cmdlet and insert the returned monitoring
// data into the Operations Manager database.
//
//*************************************************************
// 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 position %0 is empty.";
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.";
// The source of the events below is going to be the string passed in the monitoringDataSource 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 RPC_S_SERVER_TOO_BUSY = -2147416389; // 0x800106BB - The RPC server is too busy to complete this operation.
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 RPC_S_SERVER_TOO_BUSY_EXTRA_MSG = "The MSExchangeMonitoring service has too many outstanding cmdlets being executed.";
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";
// Minimum time without attempts to start the monitoring service, after a consecutive failures
//
var MINUTES_UNTIL_NEXT_START_ATTEMPT = 240;
// Typed property bag constants
var ALERT_DATA_TYPE = 0;
var EVENT_DATA_TYPE = 1;
var PERF_DATA_TYPE = 2;
var STATE_DATA_TYPE = 3;
// Global variable pointing to the OpsMgr07 ScriptAPI
//
var oAPI = new ActiveXObject("MOM.ScriptAPI");
// Event Constants
var EVENT_TYPE_SUCCESS = 0;
var EVENT_TYPE_ERROR = 1;
var EVENT_TYPE_WARNING = 2;
var EVENT_TYPE_INFORMATION = 4;
// Global variable used to identify the target computer - it must be set with the FQDN of the target computer,
// this information must be the first parameter of the script
//
var _globalTargetComputer = WScript.Arguments(0);
function LoggingComputer()
{
return _globalTargetComputer;
}
// Global variable used by the CreateEventEx to report the exact diagnostic cmdlet used by the rule, if one was specified
//
var _globalCmdletCommandScriptParam = null;
// Truncates a message, if necessary, appending the appropriate suffix
//
function TruncatedMsg(msg, maxMsgLength, truncatedMsgSuffix, defaultMsgSuffix)
{
if ((msg.length + defaultMsgSuffix.length) > maxMsgLength)
{
msg = msg.substring(0, maxMsgLength - (truncatedMsgSuffix.length)) + truncatedMsgSuffix;
}
else
{
msg = msg + defaultMsgSuffix;
}
function CreateEventEx(strSource, lngEventID, lngEventType, strMsg, strInstanceName, strComputer, blnIncRuleName)
{
var objNewEvent = oAPI.CreateTypedPropertyBag(EVENT_DATA_TYPE);
// For OpsMgr 2007 R2, the limit can be higher; but for SP1 it is around 128K for some monitors.
// We could have lifted the limit altogether but this runs the risk that a task may be logging too
// much data and causing the monitor to fail. For this reason, having a higher limit as opposed
// to none is safer -- as we would rather have the message get truncated then to risk the monitor failing.
// This higher limit should be adequate for including verbose and call stacks in the event message.
var MAX_MOM_MSG_LEN = 128000;
var truncatedMsgSuffix = "...";
var defaultMsgSuffix = "";
//////////////////////////////////////
// This function trims leading and trailing
// white spaces in a string
function Trim(string)
{
if (string == null)
return null;
//trim leading white space
string = string.replace( /^\s+/g, "" );
//trim trailing white space
string = string.replace( /\s+$/g, "" );
return string;
}
///////////////////////////////////////////////
//EventText -Replace escaped character %# with
//elements in the input array "param"
//
//This function assumes a zero base array
// e.g. EventText("%0 is the server. %1 is the client", new Array("Exchange", "Outlook"))
// would return "Exchange is a server. Outlook is a client"
//
// but EventText("%1 is the server. %2 is the client", new Array("Exchange", "Outlook"))
// would return "Outlook is a server. %2 is a client"
function EventText(eventText, param)
{
var index;
for (index in param)
{
eventText = eventText.replace("%"+index, param[index]);
}
return eventText;
}
var NO_REGENTRY_SENTINEL = "...";
function GetRegEntry(strRegEntry)
{
var strEntryValue = "";
var objShell = new ActiveXObject("WScript.Shell");
var strEntryValue = NO_REGENTRY_SENTINEL;
var ERROR_FILE_NOT_FOUND = -2147024894; //ERROR_FILE_NOT_FOUND = 0x00000002
try {
strEntryValue = objShell.RegRead(strRegEntry);
} catch(err) {
if (err.number != ERROR_FILE_NOT_FOUND)
throw err;
strEntryValue = NO_REGENTRY_SENTINEL;
}
// Deals with the case when the value was created but not touched, in this case RegRead will return the registry entry
if (typeof(strEntryValue) == "string")
if (strRegEntry.toLowerCase() == strEntryValue.toLowerCase())
strEntryValue = NO_REGENTRY_SENTINEL;
return (strEntryValue);
}
function SetRegEntry(strRegEntry, value)
{
var objShell = new ActiveXObject("WScript.Shell");
objShell.RegWrite(strRegEntry, value);
}
// Gets a parameter that is mandatory for the script, if the parameter is empty (or not specified) creates the
// appropriate event to report the problem. The parameter is always trimmed before being returned
// to the caller. It returns null if the parameter is missing or empty.
//
function GetMandatoryScriptParameter(parameterPosition)
{
var parameterValue = null;
try
{
parameterValue = Trim(WScript.Arguments(parameterPosition));
}
catch(err) { }
// 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(monitoringDataSource)
{
var isReady = true; // Assume that the service is ready
var svcObj;
// 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,
"");
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 = GetRegEntry(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 (NO_REGENTRY_SENTINEL != 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 < MINUTES_UNTIL_NEXT_START_ATTEMPT)
{
var waitInMinutes = Math.round(MINUTES_UNTIL_NEXT_START_ATTEMPT - minutesAfterLastAttempt);
if (waitInMinutes < 1)
waitInMinutes = 1;
isReady = false;
CreateEvent(
monitoringDataSource,
DIAG_CMDLET_CONTROLLER_ERROR_ID,
EVENT_TYPE_ERROR,
EventText(NEXT_START_MON_SVC_DELAYED_MSG, new Array(waitInMinutes.toString())),
"");
}
else
{
// Set the registry entry to the not set sentinel so a new interval can be started in case of new failure
//
SetRegEntry(lastSvcStartFailureTimeRegEntry, NO_REGENTRY_SENTINEL);
}
}
}
return isReady;
}
// Checks if the script is running as local system
//
function IsRunningAsSystem()
{
var retval = false;
var WshNetwork = new ActiveXObject("WScript.Network");
// Use the well-known SID of the system account ("S-1-5-18") to get the correspondent object
var WMISystemAcct = GetObject("WinMgmts:root/cimv2:Win32_SID='S-1-5-18'");
// WshNetwork.UserName gives the account running the current thread
//
// WMISystemAcct.AccountName gets the localized name of the system account
//
// No worries with string case in the comparison below since, if the account is
// system, the name is extracted from the same location for both objects
if (WshNetwork.UserName == WMISystemAcct.AccountName)
retval = true;
return(retval);
}
// 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) && (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)
EventText(ATTEMPT_TO_START_MON_SVC_RESULT_MSG, new Array(returnCode.toString())),
"");
}
WScript.Sleep(defaultWaitMSec);
}
}
function main()
{
// TODO: add usage information, parameter order etc ...
// Get the mandatory parameters
_globalCmdletCommandScriptParam = GetMandatoryScriptParameter(1); // Mapped from cmdletCommand on OpsMgr data source
var monitoringDataSource = GetMandatoryScriptParameter(2); // Mapped from monitoringDataSource on OpsMgr data source
// Check if all mandory parameters were specified
if ((_globalCmdletCommandScriptParam == null) || (monitoringDataSource == null))
{
// No need to create event reporting the problem since GetMandatoryScriptParameter
// already created one for each missing parameter, just bail.
return;
}
// Get the optional parameters
var returnPerfDataForReports = null;
try
{
returnPerfDataForReports = Trim(WScript.Arguments(3)); // Mapped from returnPerfDataForReports on OpsMgr data source
} catch(err) { }
if (returnPerfDataForReports == null)
returnPerfDataForReports = "true";
// Create the COM object that allows to execute the diagnostic cmdlet
//
try
{
var diagnosticCmdletController = new ActiveXObject("DiagnosticCmdletController");
}
catch (err)
{
CreateEvent(
monitoringDataSource,
COM_OBJ_CREATION_ERROR_ID,
EVENT_TYPE_ERROR,
EventText(COM_OBJ_CREATION_ERROR_MSG, new Array(HResultToString(err.number) + " - " + err.description)),
"");
return;
}
// Variables to control the loop that tries to run the diagnostic cmdlet
//
var maxAttemptsToStartMonitoringService = 3;
var totalAttemptsToStartMonitoringService = 0;
var notRun = true;
//
// The maximum amount of time to sleep before execution of cmdlet.
// This will add deeper granularity together with "CmdletSyncTime" property (Bug 228540).
//
// The "19 seconds" value is based on the following limitation, assuming that SCOM fires test-cmdlets sequentially:
// 10 (test-task calls) * 19 (maximum sleep time) + 10 (test-task calls) * 10 (average maximum duration of execution) < 300 seconds (the interval with which tasks run repeatedly).
//
// There are 10 different test-task calls:
// ----------------------------------
// 1. Test-ActiveSyncConnectivity
// 2. Test-EcpConnectivity (Internal)
// 3. Test-EcpConnectivity (External)
// 4. Test-ImapConnectivity
// 5. Test-OutlookConnectivity (AutoDiscover)
// 6. Test-OutlookConnectivity (Enterprise AutoDiscover)
// 7. Test-OwaConnectivity (Internal)
// 8. Test-OwaConnectivity (External)
// 9. Test-PopConnectivity
// 10. Test-WebServicesConnectivity
//
var maxCmdletSleepTime = 19000; // 19 seconds.
// Sleep random amount of time within a range of [0; 19) seconds before actual execution of cmdlet.
WScript.Sleep(Math.round(maxCmdletSleepTime * Math.random()));
do
{
try
{
diagnosticCmdletController.Run(_globalCmdletCommandScriptParam, monitoringDataSource);
notRun = false;
}
catch (err)
{
// Check if the last attempt to start the service was already executed.
//
if (totalAttemptsToStartMonitoringService == maxAttemptsToStartMonitoringService)
{
// 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();
SetRegEntry(lastSvcStartFailureTimeRegEntry, timeLastAttemptFailure.toUTCString());
// The appropriate event and message is going to be generated below (see case for EPT_S_NOT_REGISTERED).
//
}
if ((err.number == EPT_S_NOT_REGISTERED) && (totalAttemptsToStartMonitoringService < maxAttemptsToStartMonitoringService))
{
if (!IsMonitoringServiceReadyToBeStarted(monitoringDataSource))
{
// 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,
EventText(ATTEMPT_TO_START_MON_SVC_MSG, new Array(totalAttemptsToStartMonitoringService, maxAttemptsToStartMonitoringService)),
"");
AttemptToStartMonitoringService();
}
}
else
{
var extraErrorInfo = "";
switch (err.number)
{
case RPC_E_CANTCALLOUT_INEXTERNALCALL:
if (!IsRunningAsSystem())
extraErrorInfo = RPC_E_CANTCALLOUT_INEXTERNALCALL_EXTRA_MSG;
break;
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 RPC_S_SERVER_TOO_BUSY:
extraErrorInfo = RPC_S_SERVER_TOO_BUSY_EXTRA_MSG;
break;
case CTL_E_ILLEGALFUNCTIONCALL:
extraErrorInfo = CTL_E_ILLEGALFUNCTIONCALL_EXTRA_MSG;
break;
case EPT_S_NOT_REGISTERED:
extraErrorInfo = EventText(NEXT_START_MON_SVC_DELAYED_MSG, new Array(MINUTES_UNTIL_NEXT_START_ATTEMPT.toString()));
break;
}
// Check if performace data should be submitted and if so submit it
if (returnPerfDataForReports.toLowerCase() != "false")
{
var tmpPerfVBArray = new VBArray(diagnosticCmdletController.PerformanceCounters);
var perfCounters = tmpPerfVBArray.toArray();
for (i in perfCounters)
{
CreatePerformanceData(
monitoringDataSource, // Use the monitoring data source as the object
perfCounters[i].Counter,
perfCounters[i].Instance,
perfCounters[i].Value,
"");
}
}
// Submit monitoring events
var tmpEventVBArray = new VBArray(diagnosticCmdletController.Events);
var events = tmpEventVBArray.toArray();
for (i in events)
{
CreateEvent(
monitoringDataSource, // Use the monitoring data source as the event source
events[i].Identifier,
events[i].Type,
events[i].Message,
events[i].InstanceName,
"");
}
// The diagnostic cmdlet should have returned at least one monitoring event, if not report this as an error.
if (events.length == 0)
{
CreateEvent(
monitoringDataSource,
NO_EVENTS_FROM_CMDLET_ID,
EVENT_TYPE_ERROR,
NO_EVENTS_FROM_CMDLET_MSG,
"");
}
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(
monitoringDataSource,
SUCCESSFUL_EXECUTION_ID,
EVENT_TYPE_SUCCESS,
SUCCESSFUL_EXECUTION_MSG,
"");
}
}