Common Discovery Data source module (Shell Out version).

Microsoft.LS.2015.Discovery.Common.DS.ShellOut (DataSourceModuleType)

Element properties:

TypeDataSourceModuleType
IsolationAny
AccessibilityInternal
RunAsMicrosoft.LS.2015.RunAsAccount
OutputTypeSystem.Discovery.Data

Member Modules:

ID Module Type TypeId RunAs 
DS DataSource System.CommandExecuterDiscoveryDataSource Default

Source Code:

<DataSourceModuleType ID="Microsoft.LS.2015.Discovery.Common.DS.ShellOut" Accessibility="Internal" RunAs="Microsoft.LS.2015.RunAsAccount" Batching="false">
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="IntervalSeconds" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="SyncTime" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ScriptName" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ScriptFileName" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ScriptBody" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="TimeoutSeconds" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="EventId" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="TargetComputer" type="xsd:string"/>
</Configuration>
<ModuleImplementation Isolation="Any">
<Composite>
<MemberModules>
<DataSource ID="DS" TypeID="System!System.CommandExecuterDiscoveryDataSource">
<IntervalSeconds>$Config/IntervalSeconds$</IntervalSeconds>
<ApplicationName> %windir%\system32\windowspowershell\v1.0\powershell.exe</ApplicationName>
<WorkingDirectory/>
<CommandLine>-Command ".\$Config/ScriptFileName$"</CommandLine>
<SecureInput/>
<TimeoutSeconds>$Config/TimeoutSeconds$</TimeoutSeconds>
<RequireOutput>true</RequireOutput>
<Files>
<File>
<Name>$Config/ScriptFileName$</Name>
<Contents><Script>
$SourceId = '$MPElement$'
$ManagedEntityId = '$Target/Id$'
$TargetComputer = '$Config/TargetComputer$'
#############################################################################
# 1. Initialize MOM api.
# 2. Import SkypeForBusiness Powershell module.
# 3. Check for parameters.
# 4. Execute Script Body.
# 5. Return Discovery Data.
# 6. Log Events for Traces.
#############################################################################

#CONSTANTS###################################################################
$LOG_REG_KEY = "HKLM:\Software\Microsoft\Real-Time Communications\Health"
$EVENT_ERROR = 1
$EVENT_WARNING = 2
$EVENT_INFORMATION = 4
$EVENT_ID = $Config/EventId$
$EVENT_SCRIPT_NAME = "$Config/ScriptFileName$"
#############################################################################

#LOGS &amp; EXCEPTIONS###########################################################
$PROPERTY_VALUE = "Value of {0} is {1}."
$NO_NULL = "Error: {0} can not be null."
$DISCOVERY_SUCCESS = "{0} discovery completed for {1}."
$DISCOVERY_EXCEPTION_MESSAGE = "An exception occurred during discovery script, Exception : {0}."
$MESSAGE_LENGTH_HIT_LIMIT = "MESSAGE TRUNCATED DUE TO SIZE LIMIT. Max Limit is [{0}]."

#############################################################################

#Helper Functions############################################################
$Script:logMessage = new-object System.Text.StringBuilder
function TRACE ($message)
{
$Script:logMessage.Append($message) | out-null
$Script:logMessage.Append("`n") | out-null
}

# Unfortunately, foreach loop is iterating over $null values. Thank you, Powershell!!!
# In other words;
# foreach {$x in $null} {"test"} =&gt; Is printing "test" one time.
# to prevent this we need to use below format.
# foreach (... in strip-null (..)) {}
# E.g foreach {$x in strip-null($null)} {"test"} =&gt; Does not print "test".
# http://www.techtalkz.com/microsoft-windows-powershell/165444-foreach-null-interates-block.html
function strip-null($obj)
{
if ($obj -ne $null) { $obj }
}

#Helper Functions
#############################################################################
# 1. Iterate over topology document
# 2. Check current machine has registrar role.
# 3. Check registrar role has monitoring store dependency.
#############################################################################
function DiscoverCDROrQoEWatcherNode($target)
{
# 2. Find Current machine
$currentMachineFqdn = $TargetComputer
# Local machine discovery requires only access to local topology data.
$topology = Get-CsTopology -LocalStore
$currentMachine = $topology.Machines | where {$_.Fqdn -eq $currentMachineFqdn}
if (!$currentMachine)
{
throw "Current machine {0} does not exist in the topology." -f $currentMachineFqdn
}

$registrarService = $currentMachine.Cluster.InstalledServices[[Microsoft.Rtc.Management.Core.RoleName]::Registrar]
if ($registrarService -ne $null)
{
$machineFqdn = $currentMachineFqdn
$role = $DiscoveryData.CreateClassInstance("$MPElement[Name='Microsoft.LS.2015.Service.Registrar']$");
$role.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $machineFqdn);
CheckAndDiscoverCDRQoEWatcherNode $role $machineFqdn $registrarService $target
}
}

#Function implements logic to discover CDR, QoE nodes
#Discover database connection properties and submit discovery for CDR and QoE watcher nodes.
#discoveryTarget parameter specifies whether to discover CDR or QoE
function CheckAndDiscoverCDRQoEWatcherNode($role, $machineFqdn, $registrarService, $discoveryTarget)
{
#In w15 mode, Register will have a direct dependency on Monitoring Store.
TRACE ("Found a registrar role with fqdn '{0}'. Attempting to create CDR/QoE Watcher Node. " -f $machineFqdn)
foreach ($ds in $RegistrarService.DependentServices )
{
#Find if Registrar has dependent MonitoringStore Whose dependency usage is not Mirror
if (($ds.Service.RoleId.Name.ToString() -eq [Microsoft.Rtc.Management.Core.RoleName]::MonitoringStore) -and
($ds.DependencyId.UseTag -ne [Microsoft.Rtc.Management.Deploy.DependencyUsage]::Mirror))
{
TRACE ("Found a dependent Monitoring Store role. Creating CDR/QoE Watcher Nodes.")
$DbServerInstance = ""
$DbMirrorInstance = ""
$SqlInst = $ds.Service.InstalledOn
# Format : "ClusterId\DbInstance"
$DbServerInstance = $SqlInst.SqlInstanceId.ToString()
$ClusterId = $SqlInst.Cluster.ClusterId.ToString()
# FQDN of the machine that has the SQL server instance might be different
# from the machine where the MonitoringServer role is installed.
$SqlInstClusterFQDN = $SqlInst.Cluster.Fqdn
# Convert $SqlInst.SqlInstanceId from "ClusterId\DbInstance" to "FQDN\DbInstance"
# Ex&gt; "co1:3\Archinst" =&gt; "Client.Vdomain.com\Archinst"
$DbServerInstance = $DbServerInstance.Replace($ClusterId, $SqlInstClusterFQDN )
if([System.String]::IsNullOrEmpty($DbServerInstance))
{
TRACE ("Monitoring Store not found")
return
}
else
{
TRACE ("Monitoring Store '{0}' found." -f $DbServerInstance)
}

$MirrorService = $RegistrarService.GetDependentService($ds.Service.RoleId.Name, [Microsoft.Rtc.Management.Deploy.DependencyUsage]::Mirror)
if($mirrorService)
{
$MirrorInst = $MirrorService.InstalledOn
$DbMirrorInstance = $MirrorInst.SqlInstanceId.ToString()
$MirrorClusterId = $MirrorInst.Cluster.ClusterId.ToString()
$DbMirrorInstance = $DbMirrorInstance.Replace($MirrorClusterId, $MirrorInst.Cluster.Fqdn )
TRACE ("Mirror Instance '{0}' found." -f $DbMirrorInstance)
}
else
{
TRACE ("Mirror Instance is not found.")
}

$SqlConnection = New-Object System.Data.SqlClient.SqlConnection

if ($discoveryTarget -eq "CDR")
{
$SqlConnection.ConnectionString = "Server="+$DbServerInstance+";Database=lcscdr;Integrated Security=True"
}
elseif ($discoveryTarget -eq "QoE")
{
$SqlConnection.ConnectionString = "Server="+$DbServerInstance+";Database=QoeMetrics;Integrated Security=True"
}
else
{
TRACE ("Invalid discoveryTarget.")
return
}

if(-not [System.String]::IsNullOrEmpty($DbMirrorInstance))
{
$SqlConnection.ConnectionString += ";Failover Partner="+$DbMirrorInstance
}

TRACE ("Executing sql command for connection string '{0}'." -f $SqlConnection.ConnectionString)

$SqlCmd = $SqlConnection.CreateCommand()
$SqlCmd.CommandTimeout = 100000
$SqlCmd.CommandText = "exec GetFrontEndFqdnForTask @_TaskId=4" #TaskId = 4 represents SCOM task
$SqlConnection.Open()
$SqlDataReader = $SqlCmd.ExecuteReader()
if($SqlDataReader.Read())
{
$assignedFqdn = $SqlDataReader[0]
TRACE ("Assigned FQDN to do the purge task is '{0}'. We will select this fqdn as the active watcher node for CDR/Qoe Monitoring" -f $assignedFqdn)

if([System.String]::IsNullOrEmpty($assignedFqdn))
{
TRACE ("GetFrontEndFqdnForTask proc was unable to return a valid FQDN. Continuing with this registrar as a Watcher Node. (This might result in duplicate watchers)")
}
else
{
if(-not [String]::Equals($assignedFqdn, $machineFqdn, [StringComparison]::OrdinalIgnoreCase))
{
TRACE ("Monitoring Store has a different assigned FQDN. We simply return")
return
}
else
{
TRACE ("Assigned FQDN to do purging is '{0}'. Machine fqdn is '{1}'. There is a match. We select this machine as the watcher node." -f $assignedFqdn, $machinefqdn)
}
}
}
else
{
TRACE ("GetFrontEndFqdnForTask proc was unable to return a valid FQDN. Continuing with this registrar as a Watcher Node. (This might result in duplicate watchers)")
}

$service = Get-CsService | where {$_.ServiceId -eq $ds.Service.ServiceId}
$reportingService = Get-CsReportingConfiguration | ?{$_.Identity.Contains($service.Identity)}

$reportingServiceUrl = $null

if($reportingService -ne $null)
{
$reportingServiceUrl = $reportingService.ReportingUrl
}

# Create Watcher node for Call Failures.
if ($discoveryTarget -eq "CDR")
{
TRACE ("Creating CDR Watcher Node with a value '{0}'." -f $machineFqdn)
$cdrWatcherNode = $DiscoveryData.CreateClassInstance("$MPElement[Name='Microsoft.LS.2015.WatcherNode.CDR']$");
$cdrWatcherNode.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $machineFqdn);
$cdrWatcherNode.AddProperty("$MPElement[Name='Microsoft.LS.2015.WatcherNode.CDR']/ReportingServiceUrl$", $reportingServiceUrl);
$cdrWatcherNode.AddProperty("$MPElement[Name='Microsoft.LS.2015.WatcherNode.CDR']/SQLInstance$", $DbServerInstance);
if(-not [System.String]::IsNullOrEmpty($DbMirrorInstance))
{
$cdrWatcherNode.AddProperty("$MPElement[Name='Microsoft.LS.2015.WatcherNode.CDR']/MirrorSQLInstance$", $DbMirrorInstance);
}
$DiscoveryData.AddInstance($cdrWatcherNode);

$RegistrarRoleContainsCDRWatcher =
$DiscoveryData.CreateRelationshipInstance("$MPElement[Name='Microsoft.LS.2015.Relationship.RegistrarServerRoleContainsCDRWatcher']$");
$RegistrarRoleContainsCDRWatcher.Source = $role
$RegistrarRoleContainsCDRWatcher.Target = $cdrWatcherNode
$DiscoveryData.AddInstance($RegistrarRoleContainsCDRWatcher);
TRACE ("CDR WatcherNodes Discovered.")
}
elseif ($discoveryTarget -eq "QoE")
{
# Discover QoE Monitoring Node
TRACE ("Creating QoE Watcher Node with a value '{0}'." -f $machineFqdn)
$QoEMonitoring = $DiscoveryData.CreateClassInstance("$MPElement[Name='Microsoft.LS.2015.QoE.Monitoring']$")
$QoEMonitoring.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $machineFqdn)
$QoEMonitoring.AddProperty("$MPElement[Name='System!System.Entity']/DisplayName$", "QoE Monitoring")
$QoEMonitoring.AddProperty("$MPElement[Name='Microsoft.LS.2015.QoE.Monitoring']/SQLInstance$", $DbServerInstance)
if(-not [System.String]::IsNullOrEmpty($DbMirrorInstance))
{
$QoEMonitoring.AddProperty("$MPElement[Name='Microsoft.LS.2015.QoE.Monitoring']/MirrorSQLInstance$", $DbMirrorInstance)
}
$QoEMonitoring.AddProperty("$MPElement[Name='Microsoft.LS.2015.QoE.Monitoring']/ReportingServiceUrl$", $reportingServiceUrl)
$DiscoveryData.AddInstance($QoEMonitoring);

$RegistrarRoleContainsQoEMonitoring =
$DiscoveryData.CreateRelationshipInstance("$MPElement[Name='Microsoft.LS.2015.Relationship.RegistrarServerRoleContainsQoEMonitoring']$");
$RegistrarRoleContainsQoEMonitoring.Source = $role
$RegistrarRoleContainsQoEMonitoring.Target = $QoEMonitoring
$DiscoveryData.AddInstance($RegistrarRoleContainsQoEMonitoring);

TRACE ("QoE WatcherNodes Discovered.")
}
else
{
TRACE ("Unknown discovery target")
return
}
}
}
}

function GetDiscoveryData()
{
$Script:SCOMDiscoveryData
}
#############################################################################

#SCRIPT INFO#################################################################
$currentUser = whoami
# Check the execution policy of scripts and see if it is what is expected for our scripts to run if not
# set it to RemoteSigned which is the least supported execution policy this is a fix for RTMA-1762
$supportedExecutionPolicies="RemoteSigned","Unrestricted","Bypass"
$executionPolicy = get-executionpolicy
if(!($supportedExecutionPolicies -contains $executionPolicy))
{
set-executionpolicy RemoteSigned
$executionPolicy = get-executionpolicy
}

TRACE ("`n")
TRACE ("--------------------------------------------------------------------------------")
TRACE ("-Script Name: {0}" -f "$Config/ScriptName$")
TRACE ("-Run as account: {0}" -f $currentUser)
TRACE ("-Execution Policy: {0}" -f $executionPolicy)
TRACE ("--------------------------------------------------------------------------------")

#############################################################################
$Script:MOMapi = New-Object -comObject "MOM.ScriptApi"
try
{
# Terminate Execution in Error Conditions.
$ErrorActionPreference = "Stop"

if(!$SourceId) { throw $NO_NULL -f "Source Id" }
TRACE ($PROPERTY_VALUE -f "Source Id", $SourceId)

if(!$ManagedEntityId) { throw $NO_NULL -f "ManagedEntity Id" }
TRACE ($PROPERTY_VALUE -f "ManagedEntity Id", $ManagedEntityId)

if(!$TargetComputer) { throw $NO_NULL -f "Target Computer" }
TRACE ($PROPERTY_VALUE -f "Target Computer", $TargetComputer)

$Script:SCOMDiscoveryData = $MOMapi.CreateDiscoveryData(0, $SourceId, $ManagedEntityId)

$Config/ScriptBody$

$MOMApi.Return($Script:SCOMDiscoveryData)
}
catch
{
TRACE ($DISCOVERY_EXCEPTION_MESSAGE -f $_.Exception.ToString())
$exceptionOccurred = $true;
if ($Script:SCOMDiscoveryData)
{
# Unset IsSnapHost flag. Should not delete perivious discoveries, if not able to complete intialization phase.
$Script:SCOMDiscoveryData.IsSnapshot = $false
$MOMApi.Return($Script:SCOMDiscoveryData)
}
}

TRACE ("--------------------------------------------------------------------------------")

if($exceptionOccurred)
{
$EVENT_SEVERITY = $EVENT_ERROR
}
else
{
$EVENT_SEVERITY = $EVENT_INFORMATION
}

# We need to safeguard ourself against the message limit. The documented message limit is 32786 bytes (~32K).
# http://msdn.microsoft.com/en-us/library/aa363679(VS.85).aspx
# however, when we use mom api to log event it adds some characters to the messages.
# Thus, we restrict the message limit here to 30720(30K).
# if the message is greater than this, we simply truncate it and update the log message.
$maxLimit = 30720
if($logMessage.Length -gt $maxLimit)
{
TRACE ($MESSAGE_LENGTH_HIT_LIMIT -f $maxLimit)
$MOMapi.LogScriptEvent($EVENT_SCRIPT_NAME, $EVENT_ID, $EVENT_SEVERITY , $logMessage.ToString().Substring(0, $maxLimit))
}
else
{
$MOMapi.LogScriptEvent($EVENT_SCRIPT_NAME, $EVENT_ID, $EVENT_SEVERITY , $logMessage.ToString())
}
</Script></Contents>
</File>
</Files>
</DataSource>
</MemberModules>
<Composition>
<Node ID="DS"/>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.Discovery.Data</OutputType>
</DataSourceModuleType>