Common Data source module for Property Bag output (Shell Out version).

Microsoft.LS.2019.PropertyBag.Common.DS.ShellOut (DataSourceModuleType)

Element properties:

TypeDataSourceModuleType
IsolationAny
AccessibilityPublic
RunAsMicrosoft.LS.2019.RunAsAccount
OutputTypeSystem.PropertyBagData

Member Modules:

ID Module Type TypeId RunAs 
DS DataSource System.CommandExecuterPropertyBagSource Default

Source Code:

<DataSourceModuleType ID="Microsoft.LS.2019.PropertyBag.Common.DS.ShellOut" Accessibility="Public" RunAs="Microsoft.LS.2019.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="EventParam1" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="EventParam2" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="EventParam3" type="xsd:string"/>
</Configuration>
<ModuleImplementation Isolation="Any">
<Composite>
<MemberModules>
<DataSource ID="DS" TypeID="System!System.CommandExecuterPropertyBagSource">
<IntervalSeconds>$Config/IntervalSeconds$</IntervalSeconds>
<ApplicationName>%windir%\system32\windowspowershell\v1.0\powershell.exe</ApplicationName>
<WorkingDirectory/>
<CommandLine>-command "&amp; .\$Config/ScriptFileName$"</CommandLine>
<SecureInput/>
<TimeoutSeconds>$Config/TimeoutSeconds$</TimeoutSeconds>
<RequireOutput>true</RequireOutput>
<Files>
<File>
<Name>$Config/ScriptFileName$</Name>
<Contents><Script>
#############################################################################
# 1. Initialize MOM api.
# 2. Import SkypeForBusiness Powershell module.
# 3. Execute Script Body.
# 4. Log Events based on Event Event Params
#############################################################################

#CONSTANTS###################################################################
$LOG_REG_KEY = "HKLM:\Software\Microsoft\Real-Time Communications\Health"
$INTERNAL_ERROR_REG_KEY = "HKCU:\Software\Microsoft\Real-Time Communications\Health\InternalAlerts"
$EVENT_ERROR = 1
$EVENT_WARNING = 2
$EVENT_INFORMATION = 4
$EVENT_ID = $Config/EventId$
$EVENT_PARAM_1 = "$Config/EventParam1$"
$EVENT_PARAM_2 = "$Config/EventParam2$"
$EVENT_PARAM_3 = "$Config/EventParam3$"
$EVENT_SCRIPT_NAME = "$Config/ScriptFileName$"
$FEATURE_KEY = "[{0}][{1}][{2}][{3}]" -f $EVENT_SCRIPT_NAME, $EVENT_PARAM_1 ,$EVENT_PARAM_2, $EVENT_PARAM_3
$INTERVAL_SECONDS = $Config/IntervalSeconds$
$TIMEOUT_SECONDS = $Config/TimeoutSeconds$
#############################################################################

#LOGS &amp; EXCEPTIONS###########################################################
$CS_MODULE_NAME = "SkypeForBusiness"
$CS_MODULE_ADDED = "SkypeForBusiness Module is added";
$CS_MODULE_ALREADY_LOADED = "SkypeForBusiness Module is already loaded. No need to reload."
$CS_MODULE_FAILED_LOAD = "Failed to load SkypeForBusiness Module. Error: {0}."
$EXCEPTION_MESSAGE = "An internal exception occurred during script execution. Line: {0}, Exception : {1}."
$PROPERTY_VALUE = "Value of {0} is {1}."
$NO_NULL = "Error: {0} can not be null."
$MESSAGE_LENGTH_HIT_LIMIT = "MESSAGE TRUNCATED DUE TO SIZE LIMIT. Max Limit is [{0}]."
#############################################################################

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

$Script:detailLogMessage = new-object System.Text.StringBuilder
function DETAILTRACE ($message)
{
$Script:detailLogMessage.Append($message) | out-null
$Script:detailLogMessage.Append("`n") | out-null
}

function IsCSModuleLoaded()
{
(get-module $CS_MODULE_NAME ) -ne $null
}

function LoadCSModule()
{
# Script execution are managed by SCOM powershell module.
# MonitoringHost.exe is hosting powershell runspaces.
# SCOM might run the scripts on the same process and reuse the app domain if neccesary.
# Double check on wheter module is loaded or not.
if (IsCSModuleLoaded)
{
TRACE ($CS_MODULE_ALREADY_LOADED)
}
else
{
# Refresh PSModulePath in case new build is deployed into different folder but SCOM Agent is not restarted
$psPaths = $env:PSModulePath -split ";"
$env:PSModulePath = ($psPaths + ([Environment]::GetEnvironmentVariable("PSModulePath", "Machine") -split ";" | ?{$psPaths -notcontains $_})) -join ";"
TRACE("PS Module Path: " + $env:PSModulePath)

# Due to possible race condition between steps of checking module is loaded and loading module steps,
# we need to double check after module loading attempt.
$moduleImported = $false
$retryCount = 0

while (!$moduleImported -and $retryCount -lt 10)
{
try
{
Import-module $CS_MODULE_NAME -ErrorAction Stop
$moduleImported = $true
}
catch
{
$retryCount= $retryCount + 1
TRACE ("Retrying Import Module" + $retryCount)
}
}
if (IsCSModuleLoaded)
{
TRACE ($CS_MODULE_ADDED)
}
else
{
$lastError = $error[0]
TRACE ($CS_MODULE_FAILED_LOAD -f $lastError)
}
}
}

function GetPropertyBag()
{
$Script:PropertyBag
}

# 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 For Error Logging##########################################
# We need to log a success event if there is a previous failure.
# if this function returns false then either previous run was success, expired or not exist.
# if this function returns true then previous run was error.
function ReadResultFromRegKey($regPath, $feature)
{
$result = $false
if (test-path $regPath)
{
$InternalErrorKey = (Get-Item $regPath)
if ($InternalErrorKey -ne $null)
{
$prevExecutionResult = $InternalErrorKey.GetValue($feature)
if ($prevExecutionResult -ne $null)
{
$decomposedResult = @($prevExecutionResult.Split(";"))
if ($decomposedResult.Count -eq 2)
{
$prevExecutionDateTimeTicks = [Int64]::Parse($decomposedResult[0])
$prevExecutionResult = $decomposedResult[1] -as [string]
$maxExpirationTicks = [TimeSpan]::FromSeconds($INTERVAL_SECONDS + $TIMEOUT_SECONDS).Ticks
$currentExpirationTicks = [System.DateTime]::UtcNow.Ticks - $prevExecutionDateTimeTicks
# Check whether registry key entry is expired.
if ($currentExpirationTicks -le $maxExpirationTicks)
{
$result = ($prevExecutionResult -eq $true)
}
}
}
}
}
$result
DETAILTRACE ("Read Result {0} From Regkey [Path={1} Property={2}]" -f $result, $regPath, $feature)
return
}

# [$result = $true] =&gt; Success. [$result = $false] =&gt; Error.
function WriteResultIntoRegKey($regPath, $feature, $result)
{
if (!(test-path $regPath))
{
new-item $regPath -force | out-null
}
$regValue = "{0};{1}" -f ([System.DateTime]::UtcNow.Ticks), $result
new-itemProperty -Path $regPath -Name $feature -Value $regValue -force | out-null
DETAILTRACE ("Wrote [Result {0}] To Regkey [Path={1} Property={2}]" -f $regValue, $regPath ,$feature)
}

function ReadPreviousAndUpdateCurrentResultRegKey($regPath, $feature, $currentResult)
{
$previousResult = ReadResultFromRegKey $regPath $feature
WriteResultIntoRegKey $regPath $feature $currentResult
$previousResult
}

# Create an Operations Manager EventLog reference.
$EventLog = New-Object System.Diagnostics.EventLog("Operations Manager")
# Set the Source to Health Service Script. This is used in the expression in the rule.
$EventLog.Source = "Health Service Script"
function WriteResultIntoEventLogIfNecessary($IsExceptionOccurredInCurrentRun)
{
$logFlag = $null
if (test-path $LOG_REG_KEY)
{
$logFlag = (Get-ItemProperty $LOG_REG_KEY).LogOpsMgr
}

$IsInternalExceptionOccurredInPreviousRun = ReadPreviousAndUpdateCurrentResultRegKey $INTERNAL_ERROR_REG_KEY $FEATURE_KEY $IsExceptionOccurredInCurrentRun

# Three conditions should be satisfied to skip event logging.
# * There should not be Internal Exception in previous run.
# * There should not be Internal Exception in current run.
# * Tracing Registry key is not ON.
if (($IsInternalExceptionOccurredInPreviousRun -eq $false) -and
($IsExceptionOccurredInCurrentRun -eq $false) -and
(($logFlag -ne 1) -and ($logFlag -ne 2)))
{
return
}

if($IsExceptionOccurredInCurrentRun -eq $true)
{
$EVENT_SEVERITY = $EVENT_ERROR
}
else
{
$EVENT_SEVERITY = $EVENT_INFORMATION
}

# Create an EventInstance reference. We pass a Task Category of None ( as indicated by 0 )
$EventInstance = New-Object System.Diagnostics.EventInstance($EVENT_ID, 0 , $EVENT_SEVERITY)

$DisplayName = $EVENT_SCRIPT_NAME
$EventLogMessage = ""
if($logFlag -eq 2)
{
$logMessage.Append($detailLogMessage) | out-null
}
# 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 = 30000
if($logMessage.Length -gt $maxLimit)
{
TRACE ($MESSAGE_LENGTH_HIT_LIMIT -f $maxLimit)
$EventLogMessage = $logMessage.ToString().Substring(0, $maxLimit)
}
else
{
$EventLogMessage = $logMessage.ToString()
}

$eventData = @(
$DisplayName ,
$EventLogMessage ,
$EVENT_PARAM_1 ,
$EVENT_PARAM_2 ,
$EVENT_PARAM_3
)

$EventLog.WriteEvent($EventInstance, $eventData)
}
#############################################################################


#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"
$exceptionOccurred = $false
try
{
# Terminate Execution in Error Conditions.
$ErrorActionPreference = "Stop"
LoadCSModule

$Script:PropertyBag = $MOMapi.CreatePropertyBag()

$Config/ScriptBody$

$MOMapi.Return($Script:PropertyBag)
}
catch
{
TRACE ($EXCEPTION_MESSAGE -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.ToString())
$exceptionOccurred = $true;
}

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

WriteResultIntoEventLogIfNecessary $exceptionOccurred
</Script></Contents>
<Unicode>true</Unicode>
</File>
</Files>
</DataSource>
</MemberModules>
<Composition>
<Node ID="DS"/>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.PropertyBagData</OutputType>
</DataSourceModuleType>