var EXPECTED_ARGUMENT_COUNT = 14;
var UNEXPECTED_ARGUMENT_COUNT_ERROR_ID = 101;
var INVALID_ARGUMENT_VALUE_WARNING_ID = 105;
var FILE_CREATION_WARNING_ID = 106;
var DISCOVERY_STARTED_INF_ID = 1470;
var DISCOVERY_ENDED_INF_ID = 1471;
var DISCOVERY_PROGRESS_INF_ID = 1472;
var EVENT_SOURCE = "MicrosoftExchangeServerRoleDiscovery.js";
var EVENT_TYPE_ERROR = 1;
var EVENT_TYPE_WARNING = 2;
var EVENT_TYPE_INFORMATION = 4;
var oAPI = new ActiveXObject("Mom.ScriptAPI");
var argumentsStr = "";
for (var i = 0; i < WScript.Arguments.length; i++)
{
argumentsStr = argumentsStr.concat("\"", WScript.Arguments(i), "\" ");
}
if (WScript.Arguments.length != EXPECTED_ARGUMENT_COUNT)
{
oAPI.LogScriptEvent(
EVENT_SOURCE,
UNEXPECTED_ARGUMENT_COUNT_ERROR_ID,
EVENT_TYPE_ERROR,
"Expected " + EXPECTED_ARGUMENT_COUNT + " arguments. But there were " + WScript.Arguments.length + " arguments. Exiting script.\n Arguments: " + argumentsStr);
WScript.Quit(-1);
}
var sourceType = WScript.Arguments(0);
var sourceId = WScript.Arguments(1);
var managedEntityId = WScript.Arguments(2);
var classId = WScript.Arguments(3);
var roleName = WScript.Arguments(4);
var computerPrincipalName = WScript.Arguments(5);
var computerNetbiosName = WScript.Arguments(6);
var computerActiveDirectorySite = WScript.Arguments(7);
var computerDnsName = WScript.Arguments(8);
var version = WScript.Arguments(9);
var installPath = WScript.Arguments(10);
var isVirtualNodeString = WScript.Arguments(11);
var verboseLoggingString = WScript.Arguments(12);
var startDelaySeconds = WScript.Arguments(13);
// Convert cluster related variables from string to bool.
var isVirtualNode = (isVirtualNodeString.toLowerCase() == "true") ? true : false;
var verboseLogging = (verboseLoggingString.toLowerCase() == "true") ? true : false;
var discoveryData = oAPI.CreateDiscoveryData(sourceType, sourceId, managedEntityId);
// ----------------------------------------------------------------------
// First, let's do a check to:
//
// 1) Check key computer properties to skip malformed entities from MOM (it does happen).
//
// 2) Exclude computers without DNSName (i.e. a Cluster Name that gets discovered
// by MOMv3 as a Windows Server but it is NOT the virtual server we want to monitor).
//
// 3) Prevent creating server roles on a virtual nodes.
//
// 4) Ensure Exchange is installed and is the version we expected.
// ----------------------------------------------------------------------
//
if ((computerPrincipalName.length == 0) ||
(computerNetbiosName.length == 0) ||
((roleName != "EdgeTransport" ) && (computerActiveDirectorySite.length == 0)) ||
(computerDnsName.length == 0) ||
isVirtualNode ||
!((installPath.length > 0) && version.match("^14(\.[0-9]+){3}$")))
{
LogEvent(
INVALID_ARGUMENT_VALUE_WARNING_ID,
EVENT_TYPE_ERROR,
"Cannot discover '" + roleName + "'. " +
"One or more key arguments contain invalid values:\n" +
" computerPrincipalName: " + computerPrincipalName + "\n" +
" computerNetbiosName: " + computerNetbiosName + "\n" +
" computerActiveDirectorySite: " + computerActiveDirectorySite + "\n" +
" computerDnsName: " + computerDnsName + "\n" +
" installPath: " + installPath + "\n" +
" version: " + version + "\n" +
"Exiting the discovery."
);
// Even if the discovery isn't met we still have to return an empty object,
// the script module must output a System.Discovery.Data object as expected by the discovery workflow,
// otherwise a warning event is logged by MOMv3.
oAPI.Return(discoveryData);
return;
}
ProcessCapacityThresholds();
LogEvent(DISCOVERY_PROGRESS_INF_ID, EVENT_TYPE_INFORMATION, infoOutput + "Getting CPU and IO capacities.");
// IO Capacity Maximum value to be uploaded to the MOM database
var ioMaximum = GetCapacityMaximumForIO();
// CPU Capacity Maximum value to be uploaded to the MOM database
var cpuMaximum = GetCapacityMaximumForCPU();
// Discover additional cluster related properties for MailboxRole.
//
if (classId == "$MPElement[Name='Microsoft.Exchange.2010.MailboxRole']$")
{
oServerRole.AddProperty("$MPElement[Name='Microsoft.Exchange.2010.MailboxRole']/IsVirtualServer$", isVirtualNode);
}
discoveryData.AddInstance(oServerRole);
// ----------------------------------------------------------------------
// Create the CommonRole instance and populate its properties.
// ----------------------------------------------------------------------
//
// Construct the CommonRole class's Name key property.
var commonRoleName = computerNetbiosName + " (Common)";
// Gets a deterministic SyncTime based on the name of the computer.
// SyncTime is used to control the start time of MOM modules,
// such as that of a collection rule.
// By using a distinct SyncTime for individual machines,
// we could reduce the load on the AD from intensive modules
// such as those based on scripts with many AD queries.
//
function GetSyncTime(computerName)
{
if (null == computerName)
return "00:00";
computerName = computerName.toString();
if (computerName.length == 0)
return "00:00";
// Finds the first occurrence of a single-digit number starting from the end of the name.
//
var firstSignificantDigit = 0;
for (var i = (computerName.length - 1); i >= 0; i--)
{
var testChar = computerName.charAt(i);
if (!isNaN(testChar))
{
firstSignificantDigit = parseInt(testChar, 10); // Convert a 10-based number.
break;
}
}
switch (firstSignificantDigit)
{
case 0:
return "00:00";
case 1:
return "00:15";
case 2:
return "00:30";
case 3:
return "00:45";
case 4:
return "01:00";
case 5:
return "01:15";
case 6:
return "01:30";
case 7:
return "01:45";
case 8:
return "02:00";
case 9:
return "02:15";
default:
return "00:00";
}
}
// The output value is in format "hh:mm" with leading zeros.
// Parameter "totalMinutes" - is time expressed in minutes.
function ConvertMinutesToTime(totalMinutes) {
var wholeHours = Number(Math.floor(totalMinutes / 60)).toFixed(0);
if (wholeHours.length == 1)
wholeHours = "0" + wholeHours; // Add a leading zero.
var remaindingMinutes = Number(totalMinutes % 60).toFixed(0);
if (remaindingMinutes.length == 1)
remaindingMinutes = "0" + remaindingMinutes; // Add a leading zero.
// Convert to a string of format "hh:mm";
return wholeHours + ":" + remaindingMinutes;
}
// Hash-iterating Function.
// Returns new hash state for given previous state "s" and next word "b".
function Hash(s, b) {
if ((128 & s) == 0)
return Math.round(Math.abs(2 * s + b));
else
return Math.round(Math.abs(2 * s + b) ^ 3397); // the constant is set based on experiments.
}
// Returns hash value of a string as integer value modulo "m".
// The hash is deterministic - the returned value is always the same for given input string.
function GetStrHash(str, m) {
var s = Number(0); // Initialize state of hash.
// Iterate hash state through characters of given string.
for (var i = 0; i < str.length; i++) {
var b = Number(str.charCodeAt(i));
s = Hash(s, b);
}
return (s % m);
}
//
// Returns deterministic synchronization time (one-minute slot of possible 60 one-minute slots of the day's first hour)
// based on computer name.
// This is used to control the start time of diagnostic cmdlets in script response rules. It is designed to prevent simultenious
// execution of test-* cmdlets against CAS in environments with limited mailbox sessions.
//
function GetCmdletSyncTime(computerName) {
var slots = Number(60); // maximum number of one-minute slots;
if (null == computerName)
return "00:00";
computerName = computerName.toString();
if (computerName.length == 0)
return "00:00";
// Assign the value based on computer name.
var timeInMinutes = GetStrHash(computerName, slots);
// Get a string in format "hh:mm" that SCOM recognizes.
return ConvertMinutesToTime(timeInMinutes);
}
// Global file paths that should be used only by the CapacityPlanning Model
var configPath = installPath + "Bin\\Monitoring\\";
var thresholdConfigFile = configPath + "ThresholdConfig.xml";
var jetStressFile = configPath + "JetStress.xml";
// Global strings that should be used only by the CapacityPlanning Model
var ioTag = "IoMaximum";
var cpuTag = "CpuMaximum";
var defaultIoMax = 0;
var defaultCpuMax = 75;
//
// Creates a config file that contains the default Capacity Maximums for CPU and IO
// User can edit this by hand if he wants
//
function CreateThresholdConfig(cpuMaximum, ioMaximum, updateFile) {
var retryCount = 0;
while (retryCount < 3) {
try {
var fso = new ActiveXObject("Scripting.FileSystemObject");
if (!fso.FileExists(thresholdConfigFile) || updateFile) {
// Checking for the two folders below installPath
if (!fso.FolderExists(configPath)) {
var parentFolder = fso.GetParentFolderName(configPath);
if (!fso.FolderExists(parentFolder)) {
fso.CreateFolder(parentFolder);
}
fso.CreateFolder(configPath);
}
var configFile = fso.CreateTextFile(thresholdConfigFile, true);
break;
}
catch (err) {
retryCount++;
if (retryCount >= 3) {
LogEvent(
FILE_CREATION_WARNING_ID,
EVENT_TYPE_WARNING,
"Attempt to create or write to config file " + thresholdConfigFile + " has failed for at least 3 times");
}
// Adding the sleep here to avoid two server role files try and create or write the threshold config together
WScript.Sleep(2000);
}
}
}
//
// Helper function to get the XMLDOM object
//
function GetXmlDoc() {
var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
return xmlDoc;
}
//
// Updates the threshold config file whenever the JetStress log is updated
//
function UpdateThresholdConfig(ioMax) {
var xmlDoc = GetXmlDoc();
var fileLoaded = xmlDoc.load(thresholdConfigFile);
var ioMaxCurr = 0;
var cpuMaxCurr = 0;
var isFileCorrupt = false;
if (fileLoaded) {
var xmlIoNodes = xmlDoc.documentElement.getElementsByTagName(ioTag);
var xmlCpuNodes = xmlDoc.documentElement.getElementsByTagName(cpuTag);
// Fallback to default value if the config value is corrupted
if (xmlIoNodes.length > 0) {
ioMaxCurr = parseFloat(xmlIoNodes[0].text);
// Update only if necessary
if ((ioMax != defaultIoMax && ioMax != ioMaxCurr) || isFileCorrupt) {
CreateThresholdConfig(cpuMaxCurr, ioMax, true);
}
}
}
//
// Gets the IO Node from the JetStress XML which has the value for IoMaximum
//
function GetIoNode(nodes) {
for (i = 0; i < nodes.length; i++) {
if (nodes[i].getAttribute("name") == "Achieved Transactional I/O per Second") {
return nodes[i];
}
}
return null;
}
//
// Creates a threshold config file when not present.
// After that reads the JetStress Log and updates the config file if required
//
function ProcessCapacityThresholds() {
// Initially setting the default cpuMaximum and ioMaximum to 75 and 0 respectively.
CreateThresholdConfig(defaultCpuMax, defaultIoMax, false);
var xmlDoc = GetXmlDoc();
var fileLoaded = xmlDoc.load(jetStressFile);
var value = defaultIoMax;
if (fileLoaded) {
var xmlNodes = xmlDoc.documentElement.getElementsByTagName("database-sizing");
if (xmlNodes.length > 0 && xmlNodes[0].hasChildNodes()) {
var xmlIoNode = GetIoNode(xmlNodes[0].childNodes);
if (xmlIoNode != null) {
var attrName = xmlIoNode.getAttribute("name");
var attrValue = xmlIoNode.getAttribute("value");
if (attrValue != null && !isNaN(parseFloat(attrValue))) {
value = parseFloat(attrValue);
}
}
}
}
if (value != defaultIoMax) {
UpdateThresholdConfig(value);
}
}
//
// Helper function to get the CapacityMaximum from the threshold config
//
function GetCapacityMaximum(categoryTag, defaultMaximum) {
var xmlDoc = GetXmlDoc();
var fileLoaded = xmlDoc.load(thresholdConfigFile);
var capacityMaximum = defaultMaximum;
if (fileLoaded) {
var xmlNodes = xmlDoc.documentElement.getElementsByTagName(categoryTag);
if (xmlNodes.length > 0 && xmlNodes[0].hasChildNodes()) {
capacityMaximum = parseFloat(xmlNodes[0].text);
//
// Returns the Capacity Maximum for CPU from the threshold config
//
function GetCapacityMaximumForCPU() {
var categoryTag = cpuTag;
var defaultMaximum = defaultCpuMax;
return GetCapacityMaximum(categoryTag, defaultMaximum);
}
//
// Returns the Capacity Maximum for IO from the threshold config
//
function GetCapacityMaximumForIO() {
var categoryTag = ioTag;
var defaultMaximum = defaultIoMax;
return GetCapacityMaximum(categoryTag, defaultMaximum);
}
//
// Log to EventLog if verboseLogging is set
//
function LogEvent(eventId, eventType, message)
{
if (verboseLogging)
{
oAPI.LogScriptEvent(EVENT_SOURCE, eventId, eventType, message);
}
}