토폴로지 검색 데이터 원본

Microsoft.Windows.CertificateServices.TopologyDiscovery.DataSource (DataSourceModuleType)

Element properties:

TypeDataSourceModuleType
IsolationAny
AccessibilityInternal
RunAsMicrosoft.SystemCenter.DatabaseWriteActionAccount
OutputTypeSystem.Discovery.Data

Member Modules:

ID Module Type TypeId RunAs 
DS DataSource Microsoft.Windows.TimedScript.DiscoveryProvider Default

Overrideable Parameters:

IDParameterTypeSelectorDisplay NameDescription
IntervalSecondsint$Config/IntervalSeconds$간격(초)간격(초)
SyncTimestring$Config/SyncTime$동기화 시간동기화 시간
TimeoutSecondsint$Config/TimeoutSeconds$제한 시간(초)제한 시간(초)
DebugFlagbool$Config/DebugFlag$디버그 플래그디버그 플래그

Source Code:

<DataSourceModuleType ID="Microsoft.Windows.CertificateServices.TopologyDiscovery.DataSource" Accessibility="Internal" RunAs="SC!Microsoft.SystemCenter.DatabaseWriteActionAccount" Batching="false">
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="IntervalSeconds" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="SyncTime" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="TimeoutSeconds" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="DebugFlag" type="xsd:boolean"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="IntervalSeconds" Selector="$Config/IntervalSeconds$" ParameterType="int"/>
<OverrideableParameter ID="SyncTime" Selector="$Config/SyncTime$" ParameterType="string"/>
<OverrideableParameter ID="TimeoutSeconds" Selector="$Config/TimeoutSeconds$" ParameterType="int"/>
<OverrideableParameter ID="DebugFlag" Selector="$Config/DebugFlag$" ParameterType="bool"/>
</OverrideableParameters>
<ModuleImplementation Isolation="Any">
<Composite>
<MemberModules>
<DataSource ID="DS" TypeID="Windows!Microsoft.Windows.TimedScript.DiscoveryProvider">
<IntervalSeconds>$Config/IntervalSeconds$</IntervalSeconds>
<SyncTime>$Config/SyncTime$</SyncTime>
<ScriptName>Topology10.0Discovery.vbs</ScriptName>
<Arguments>$MPElement$ $Target/Id$ $Target/Host/Property[Type='Windows!Microsoft.Windows.Computer']/PrincipalName$ $Config/DebugFlag$</Arguments>
<ScriptBody><Script>Option Explicit

'---------------------------------------------------------------------------------------------------------
'
' Microsoft Corporation
' Copyright (c) Microsoft Corporation. All rights reserved.
'
' Common - Certificate Services MP Common Library
' This script defines various useful functions and subroutines used by the Certificate
' Services Management Pack.
'
'---------------------------------------------------------------------------------------------------------

SetLocale("en-us")

const ForWriting = 2

'event severity constants for debug logging
const SCOM_ERROR=1
const SCOM_WARNING=2
const SCOM_INFORMATION=4

'event id of the debug events generated by the script
dim SCRIPT_EVENT_ID

'constants relating to CertificateAuthority.Request object
const CR_PROP_CANAME = 6
const PROPTYPE_STRING = 4

'constants needed for registry access
const HKEY_LOCAL_MACHINE = &amp;H80000002

'constants for communicating with CAs
const CR_OUT_BASE64HEADER = 0
const CR_OUT_BASE64 = &amp;H1
const CR_OUT_BINARY = &amp;H2
const CR_OUT_CHAIN = &amp;H100
const CR_OUT_CRLS = &amp;H200

'---------------------------------------------------------------------------
' Initializes the "library".
'---------------------------------------------------------------------------
sub InitializeCommon(iScriptEventID)
'set the script event id
SCRIPT_EVENT_ID = iScriptEventID
end sub

'---------------------------------------------------------------------------
' Used for storing error information and other things.
'---------------------------------------------------------------------------
Class Error
Private m_lNumber
Private m_sSource
Private m_sDescription
Private m_sHelpContext
Private m_sHelpFile
Public Sub Save()
m_lNumber = Err.number
m_sSource = Err.Source
m_sDescription = Err.Description
m_sHelpContext = Err.HelpContext
m_sHelpFile = Err.helpfile
End Sub
Public Sub Raise()
Err.Raise m_lNumber, m_sSource, m_sDescription, m_sHelpFile, m_sHelpContext
End Sub
Public Sub Clear()
m_lNumber = 0
m_sSource = ""
m_sDescription = ""
m_sHelpContext = ""
m_sHelpFile = ""
End Sub
Public Default Property Get Number()
Number = m_lNumber
End Property
Public Property Get Source()
Source = m_sSource
End Property
Public Property Get Description()
Description = m_sDescription
End Property
Public Property Get HelpContext()
HelpContext = m_sHelpContext
End Property
Public Property Get HelpFile()
HelpFile = m_sHelpFile
End Property
End Class

'---------------------------------------------------------------------------
' Implements a subset of typical dynamic list functionality.
'---------------------------------------------------------------------------
class List
private m_aData
private m_iCount
private m_iCapacityIncrement

'---------------------------------------------------------------------------
' Creates an empty list.
'---------------------------------------------------------------------------
private sub Class_Initialize()
'allocate a dynamic array of size 0
m_aData = Array(0)

'set counts
m_iCount = 0

'set default increment
m_iCapacityIncrement = 10
end sub

'---------------------------------------------------------------------------
' Destroys the list.
'---------------------------------------------------------------------------
private sub Class_Terminate()
'deallocate the array
redim m_aData(0)
end sub

'---------------------------------------------------------------------------
' Getter for data (no range checking).
'---------------------------------------------------------------------------
public property get Data(iIndex)
if IsObject(m_aData(iIndex)) then
set Data = m_aData(iIndex)
else
Data = m_aData(iIndex)
end if
end property

'---------------------------------------------------------------------------
' Setter for data (no range checking).
'---------------------------------------------------------------------------
public property let Data(iIndex, vValue)
if IsObject(vValue) then
set m_aData(iIndex) = vValue
else
m_aData(iIndex) = vValue
end if
end property

'---------------------------------------------------------------------------
' Gets the whole array.
'---------------------------------------------------------------------------
public property get DataArray()
DataArray = m_aData
end property

'---------------------------------------------------------------------------
' Gets the number of items currently stored in the list.
'---------------------------------------------------------------------------
public property get Count()
Count = m_iCount
end property

'---------------------------------------------------------------------------
' Gets the current capacity of the list. (Adding beyond capacity will cause
' the list to be resized/reallocated.
'---------------------------------------------------------------------------
public property get Capacity()
Capacity = UBound(m_aData)
end property

'---------------------------------------------------------------------------
' Gets number of items by which the array size will be increased when it
' needs to be reallocated.
'---------------------------------------------------------------------------
public property get CapacityIncrement()
CapacityIncrement = m_iCapacityIncrement
end property

'---------------------------------------------------------------------------
' Sets the number of items by which the array will be lengthened when it
' needs reallocating. (No effect if value is less than 1.)
'---------------------------------------------------------------------------
public property let CapacityIncrement(iValue)
if iValue &gt; 0 then
m_iCapacityIncrement = iValue
end if
end property

'---------------------------------------------------------------------------
' Adds an item to the list. (List will be resized/reallocated as needed.)
'---------------------------------------------------------------------------
public function Add(vItem)
'do we have space?
if m_iCount &gt;= UBound(m_aData) then
'no
'reallocate array
redim preserve m_aData(UBound(m_aData) + CapacityIncrement)
end if

'add it
Data(m_iCount) = vItem

'increment count
m_iCount = m_iCount + 1
end function
end class

'---------------------------------------------------------------------------
' Returns "&lt;sCurrent&gt;, &lt;sNew&gt;" if sCurrent is non-empty. Otherwise, returns
' sNew.
'---------------------------------------------------------------------------
function AppendToCSL(sCurrent, sNew)
dim sResult
sResult = sCurrent

'something already in the list?
if sCurrent &lt;&gt; "" then
'yes
'add a separator
sResult = sResult &amp; ", "
end if

'concat new item
sResult = sResult &amp; sNew

'return
AppendToCSL = sResult
end function

'---------------------------------------------------------------------------
' Converts a hex digit to an integer. (no error checking)
'---------------------------------------------------------------------------
function HexDigitToInt(hexDigit)
HexDigitToInt = (InStr ("0123456789ABCDEF", UCase(hexDigit))) - 1
end function

'---------------------------------------------------------------------------
' Converts a hex string to an integer. (no error checking)
'---------------------------------------------------------------------------
function HexStringToInt(hexString)
dim temp
dim i
temp = 0
for i = 1 to len(hexString)
temp = temp * 16
temp = temp + HexDigitToInt(mid(hexString, i, 1))
next
HexStringToInt = temp
end function

'---------------------------------------------------------------------------
' Retrieves a WMI object from the specified namespace.
'---------------------------------------------------------------------------
function GetWMIObject(ByVal sNamespace)
dim oWMI
dim e
set e = new Error

'get the object
on error resume next
set oWMI = GetObject(sNamespace)
e.Save
on error goto 0

'did it work?
if IsEmpty(oWMI) then
'no
ThrowScriptError "Unable to open WMI Namespace '" &amp; sNamespace &amp; "'. Check to see if the WMI service is enabled and running, and ensure this WMI namespace exists.", e
end if

set GetWMIObject = oWMI
end function

'---------------------------------------------------------------------------
' Retrieves a WMI registry object
'---------------------------------------------------------------------------
function GetRegistryObject(ByVal sComputerName)
set GetRegistryObject = GetWMIObject("winmgmts:\\" &amp; sComputerName &amp; "\root\default:StdRegProv")
end function

'---------------------------------------------------------------------------
' Retrieves a Cimv2 object needed by ServiceExists.
'---------------------------------------------------------------------------
function GetWMICimv2Object(ByVal sComputerName)
set GetWMICimv2Object = GetWMIObject("winmgmts:\\" &amp; sComputerName &amp; "\root\cimv2")
end function

'---------------------------------------------------------------------------
' Returns true if specified service exists on the machine.
'---------------------------------------------------------------------------
function ServiceExists(oCimv2, ByVal sServiceName)
dim oResult, bTemp, sQuery, e
set e = new Error

'run the query
sQuery = "Select * from win32_service where name = '" &amp; sServiceName &amp; "'"
on error resume next
set oResult = oCimv2.ExecQuery(sQuery)
e.Save
on error goto 0

'valid result?
if IsEmpty(oResult) or e.Number &lt;&gt; 0 then
'no
ThrowScriptError "The Query '" &amp; sQuery &amp; "' returned an invalid result set. Please check to see if this is a valid WMI Query.", e
end if

'check if service exists
on error resume next
bTemp = (oResult.Count &gt; 0)
e.Save
on error goto 0

'error getting instance count?
if e.Number &lt;&gt; 0 then
'yes
ThrowScriptError "The Query '" &amp; sQuery &amp; "' did not return any valid instances. Please check to see if this is a valid WMI Query.", e
end if

ServiceExists = bTemp
end function

'---------------------------------------------------------------------------
' Returns discovery snapshot to the agent.
'---------------------------------------------------------------------------
sub ReturnSnapshot(data)
data.IsSnapshot = true
call oAPI.Return(data)
WScript.Quit
end sub

'---------------------------------------------------------------------------
' Returns incremental discovery data to the agent.
'---------------------------------------------------------------------------
sub ReturnIncremental(data)
data.IsSnapshot = false
call oAPI.Return(data)
WScript.Quit
end sub

'---------------------------------------------------------------------------
' Returns name of the current script.
'---------------------------------------------------------------------------
function GetScriptName
dim oFSO

'get object (with error checking)
set oFSO = MOMCreateObject("Scripting.FileSystemObject")
GetScriptName = oFSO.GetFile(WScript.ScriptFullName).Name
set oFSO = nothing
end function

'---------------------------------------------------------------------------
' Creates an event in the log with the specified message and severity.
'---------------------------------------------------------------------------
sub LogEvent(message, severity)
dim oTempAPI, sScriptName

'get script name
sScriptName = GetScriptName

'create a temp mom api object
'(avoids having to pass the object every time or using a global)
set oTempAPI = MOMCreateObject("MOM.ScriptAPI")

'log event
on error resume next
call oTempAPI.LogScriptEvent(sScriptName, SCRIPT_EVENT_ID, severity, message)
on error goto 0

'if there's an error from the above, just eat it
end sub

'---------------------------------------------------------------------------
' Creates a COM object and returns it (with error checking).
'---------------------------------------------------------------------------
function MOMCreateObject(ByVal sName)
dim e
set e = new Error

'create the object
on error resume next
set MOMCreateObject = CreateObject(sName)
e.Save
on error goto 0

if e.Number &lt;&gt; 0 then
ThrowScriptError "Unable to create COM object '" &amp; sName &amp; "'", e
end if
end function

'---------------------------------------------------------------------------
' Creates an event and sends it back to the mom server without stopping
' the script.
'---------------------------------------------------------------------------
Function ThrowScriptErrorNoAbort(ByVal sMessage, ByVal oErr)
' Retrieve the name of this (running) script
Dim sScriptFileName
sScriptFileName = GetScriptName

If Not IsNull(oErr) Then
sMessage = sMessage &amp; " Cause: " &amp; oErr.Description
end if

On Error Resume Next
Dim oAPITemp
Set oAPITemp = CreateObject("MOM.ScriptAPI")
oAPITemp.LogScriptEvent sScriptFileName, SCRIPT_EVENT_ID, SCOM_ERROR, sMessage
On Error Goto 0

WScript.Echo sMessage
End Function

'---------------------------------------------------------------------------
' Creates an error event, sends it back to the mom server, and stops the
' script.
'---------------------------------------------------------------------------
Function ThrowScriptError(Byval sMessage, ByVal oErr)
On Error Resume Next
ThrowScriptErrorNoAbort sMessage, oErr
WScript.Quit
End Function

'---------------------------------------------------------------------------
' Creates a registry key to hold state information and returns its path.
'---------------------------------------------------------------------------
function CreateCacheRegKey(reg, identifier)
dim e, path
set e = new Error

'get the path
path = oAPI.GetScriptStateKeyPath("ADCS") &amp; "\" &amp; identifier

'create the key
on error resume next
call reg.CreateKey(HKEY_LOCAL_MACHINE, path)
e.Save
on error goto 0
if e.Number &lt;&gt; 0 then
ThrowScriptError "Unable to create registry key 'HKLM\" &amp; path &amp; "'.", e
end if

'return path
CreateCacheRegKey = path
end function

'---------------------------------------------------------------------------
' Gets the PrincipalName out of a CA config string.
'---------------------------------------------------------------------------
function GetPrincipalName(configString)
dim iBackslashIndex

'find the \
iBackslashIndex = InStr(1, configString, "\")

if iBackslashIndex = 0 then
'\ not found. something's weird
ThrowScriptError "Config string '" &amp; configString &amp; "' is not valid.", null
end if

'grab the principal name and return
GetPrincipalName = Left(configString, iBackslashIndex - 1)
end function

'---------------------------------------------------------------------------
' Gets the CAName out of a CA config string.
'---------------------------------------------------------------------------
function GetCAName(configString)
dim iBackslashIndex

'find the \
iBackslashIndex = InStr(1, configString, "\")

if iBackslashIndex = 0 then
'\ not found. something's weird
ThrowScriptError "Config string '" &amp; configString &amp; "' is not valid.", null
end if

'grab the ca name and return
GetCAName = Right(configString, Len(configString) - iBackslashIndex)
end function

'---------------------------------------------------------------------------
' Retrieves a CA Cert from the specified CA config string and returns it
' as a Base64-encoded string. Returns empty string if certificate could
' not be retrieved.
'---------------------------------------------------------------------------
function GetCACert(oCertRequest, configString)
dim e
set e = new Error
dim sCACert

'get CA cert
on error resume next
sCACert = oCertRequest.GetCACertificate(1, configString, CR_OUT_BASE64)
e.Save
on error goto 0

'error?
if e.Number &lt;&gt; 0 then
'no cert
GetCACert = ""
else
'return the cert
GetCACert = sCACert
end if
end function

'---------------------------------------------------------------------------
' Saves a string to a temp file and returns the file name.
'---------------------------------------------------------------------------
function SaveStringToTempFile(data)
dim sTempFileName
dim e
set e = new Error
dim oWriter

'generate temp file name (current directory is already set by the agent)
sTempFileName = oFSO.GetTempName()

'open text file for writing
on error resume next
set oWriter = oFSO.OpenTextFile(sTempFileName, ForWriting, true)
e.Save
on error goto 0
if e.Number &lt;&gt; 0 then
ThrowScriptError "Unable to open file '" &amp; sTempFileName &amp; "' for writing.", e
end if

'save it to file
on error resume next
oWriter.WriteLine(data)
e.Save
on error goto 0
if e.Number &lt;&gt; 0 then
ThrowScriptError "Unable to save data to file '" &amp; sTempFileName &amp; "'.", e
end if

'close the file
on error resume next
oWriter.Close
e.Save
on error goto 0
if e.Number &lt;&gt; 0 then
ThrowScriptError "Unable to close file '" &amp; sTempFileName &amp; "'.", e
end if
set oWriter = nothing

'return temp file name
SaveStringToTempFile = sTempFileName
end function
'---------------------------------------------------------------------------------------------------------
'
' Microsoft Corporation
' Copyright (c) Microsoft Corporation. All rights reserved.
'
' Topology Discovery - Certificate Services MP Topology Discovery Script
' This script discovers all instances of CA and Hierarchy classes.
' This is accomplished on the RMS by querying the operations db for all instances
' of CARole class.
'
' Parameters - sSourceID The GUID of the discovery object that launched the script.
' sManagedEntityID The GUID of the class targeted by the script.
' sComputerName The FQDN of the computer targeted by the script.
' bDebug True / False
'
' Dependencies - Schema of view ManagedTypeView and table ManagedTypeProperty
' - Operations database name in HKLM\SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Setup\DatabaseName
' - Operations database server name in HKLM\SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Setup\DatabaseServerName
' - Script Common.vbs from ADCS MP
' - Output format of certutil -verify command ran on an Exchange certificate
'---------------------------------------------------------------------------------------------------------

const adStateOpen = 1

const SCOM_SETUP_BASE_KEY = "SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Setup"
const SCOM_DB_NAME_VALUE = "DatabaseName"
const SCOM_DB_SERVER_VALUE = "DatabaseServerName"

const PROP_CA_NAME = "CAName"
const PROP_CONFIG_STRING = "ConfigString"
const PROP_CHAIN = "Chain"

dim sSourceID, sManagedEntityID, sComputerName, bDebug
dim oAPI, oDiscovery

'---------------------------------------------------------------------------
' Holder for CARole properties
'---------------------------------------------------------------------------
class CARole
public CAName
public ConfigString
public Chain
end class

'---------------------------------------------------------------------------
' Returns a DB connection string poiting to the operations db on the RMS.
' (Based on info from SCOM registry keys/values.)
'---------------------------------------------------------------------------
function GetOpsMgrDBConnectionString(reg)
dim sDBName, sDBServerName, e
set e = new Error

'get db name
on error resume next
reg.GetStringValue HKEY_LOCAL_MACHINE, SCOM_SETUP_BASE_KEY, SCOM_DB_NAME_VALUE, sDBName
e.Save
on error goto 0
if e.Number &lt;&gt; 0 then
ThrowScriptError "Unable to determine SCOM Database name. Please verify that Operations Manager is installed on this computer.", e
end if

'get db server name
on error resume next
reg.GetStringValue HKEY_LOCAL_MACHINE, SCOM_SETUP_BASE_KEY, SCOM_DB_SERVER_VALUE, sDBServerName
e.Save
on error goto 0
if e.Number &lt;&gt; 0 then
ThrowScriptError "Unable to determine SCOM Database Server name. Please verify that Operations Manager is installed on this computer.", e
end if

'generate connection string and return
GetOpsMgrDBConnectionString = "Provider=SQLOLEDB;Data Source=" &amp; sDBServerName &amp; ";Initial Catalog=" &amp; sDBName &amp; ";Integrated Security=SSPI;"
end function

'---------------------------------------------------------------------------
' Attempts to open a DB connection. If successful, returns the connection
' state
'---------------------------------------------------------------------------
function OpenDBConnection(connection, ByVal connectionString)
dim e
set e = new Error

'set up connection params
connection.ConnectionString = connectionString
connection.ConnectionTimeout = 30

'open the connection
on error resume next
connection.Open
e.Save
on error goto 0
if e.Number &lt;&gt; 0 then
ThrowScriptError "Unable to connect to the database with the specified configuration string. Please make sure that your connection string is valid and that your credentials are authorized to access the database.", e
end if

'return state
OpenDBConnection = connection.State
end function

'---------------------------------------------------------------------------
' Queries the database and returns a record set object if successful.
' (Record set must not be in opened state.)
'---------------------------------------------------------------------------
sub QueryDB(connection, rs, ByVal sql)
dim e
set e = new Error

'run query
on error resume next
rs.Open sql, connection, 3, 3
e.Save
on error goto 0

'error?
if e.Number &lt;&gt; 0 then
'indeed
ThrowScriptError "Query """ &amp; sql &amp; """ did not produce valid results. Please verify your SQL.", e
end if
end sub

'---------------------------------------------------------------------------
' Closes the record set (with error checking)
'---------------------------------------------------------------------------
sub CloseRecordSet(rs)
dim e
set e = new Error

'close it
on error resume next
rs.Close
e.Save
on error goto 0

'error?
if e.Number &lt;&gt; 0 then
ThrowScriptError "Unable to close the record set.", e
end if
end sub

'---------------------------------------------------------------------------
' Splits the comma-separated list of CA Names and returns the count.
'---------------------------------------------------------------------------
function ParseChain(sChain, ByRef iCACount)
dim aCANames

'split the string
aCANames = split(sChain, ",")

'figure out the count
iCACount = UBound(aCANames) + 1

'return
ParseChain = aCANAmes
end function

'---------------------------------------------------------------------------
' Discovers a chain of CAs and the Hierarchy from the CARole instance.
'---------------------------------------------------------------------------
sub DiscoverCAChain(sCAName, sConfigString, sChain, oOnlinenessMap)
dim sCACert, sCertFileName, aCANames, iCACount, i
dim sRootCAName 'root CA name
dim sLeafCAName 'CA Name of the leaf of the current sub-chain (not necessarily the leaf in the full chain)
dim e
set e = new Error
dim oCA, oEnterpriseContainsHierarchies, oHierarchyContainsCAs
dim oHierarchy, oEnterprise, oCARole, oCAContainsCARole
dim oSubordinateCA, oCAIsSubordinateToAnotherCA
dim sPrincipalName
dim bIsOnline

'have all the properties been fully discovered?
if IsNull(sCAName) or IsNull(sConfigString) or IsNull(sChain) then
'no

'log if debugging
if bDebug then
LogEvent "CARole has not been fully discovered yet. Skipping current chain.", SCOM_INFORMATION
end if

'exit the sub
exit sub
end if

'log progress
if bDebug then
LogEvent "Started discovery of CA with " &amp; PROP_CA_NAME &amp; " = '" &amp; sCAName &amp; "' and " &amp; PROP_CONFIG_STRING &amp; " = '" &amp; sConfigString &amp; "' and " &amp; PROP_CHAIN &amp; " = '" &amp; sChain &amp; "'.", SCOM_INFORMATION
end if

'get principal name
sPrincipalName = GetPrincipalName(sConfigString)

'discover enterprise (needed for relationships)
set oEnterprise = oDiscovery.CreateClassInstance("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.Enterprise']$")

'parse the chain property
aCANames = ParseChain(sChain, iCACount)

'discover hierarchy
sRootCAName = aCANames(iCACount - 1)
set oHierarchy = oDiscovery.CreateClassInstance("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.Hierarchy']$")
call oHierarchy.AddProperty("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.Hierarchy']/HierarchyID$", sRootCAName)
call oHierarchy.AddProperty("$MPElement[Name='System!System.Entity']/DisplayName$", "Hierarchy " &amp; sRootCAName)
oDiscovery.AddInstance(oHierarchy)

'discover EnterpriseContainsHierarchies relationship
set oEnterpriseContainsHierarchies = oDiscovery.CreateRelationshipInstance("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.EnterpriseContainsHierarchies']$")
oEnterpriseContainsHierarchies.Source = oEnterprise
oEnterpriseContainsHierarchies.Target = oHierarchy
oDiscovery.AddInstance(oEnterpriseContainsHierarchies)

'discover each CA
for i = 0 to iCACount - 1 step 1
'is it an online CA or not
bIsOnline = oOnlinenessMap.Exists(aCANames(i))

'create CA instance
set oCA = oDiscovery.CreateClassInstance("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.CA']$")
call oCA.AddProperty("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.CA']/CAName$", aCANames(i))
call oCA.AddProperty("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.CA']/IsOnline$", CStr(bIsOnline))
call oDiscovery.AddInstance(oCA)

'leaf of the current sub-chain?
if i = 0 then
'yes
'discover CARole instance
set oCARole = oDiscovery.CreateClassInstance("$MPElement[Name='Microsoft.Windows.CertificateServices.CARole.2016']$")
call oCARole.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", sPrincipalName)

'discover CAContainsCARole relationship
set oCAContainsCARole = oDiscovery.CreateRelationshipInstance("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.CAContainsCARole']$")
oCAContainsCARole.Source = oCA
oCAContainsCARole.Target = oCARole
call oDiscovery.AddInstance(oCAContainsCARole)
else
'no
'create CAIsSubordinateToAnotherCA relationship instance
set oCAIsSubordinateToAnotherCA = oDiscovery.CreateRelationshipInstance("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.CAIsSubordinateToAnotherCA']$")
oCAIsSubordinateToAnotherCA.Source = oSubordinateCA
oCAIsSubordinateToAnotherCA.Target = oCA
oDiscovery.AddInstance(oCAIsSubordinateToAnotherCA)
end if

'create HierarchyContainsCAs instance
set oHierarchyContainsCAs = oDiscovery.CreateRelationshipInstance("$MPElement[Name='CS!Microsoft.Windows.CertificateServices.HierarchyContainsCAs']$")
oHierarchyContainsCAs.Source = oHierarchy
oHierarchyContainsCAs.Target = oCA
oDiscovery.AddInstance(oHierarchyContainsCAs)

'log info
if bDebug then
LogEvent sCAName &amp; " discovered " &amp; aCANames(i) &amp; ".", SCOM_INFORMATION
end if

'update subordinate
set oSubordinateCA = oCA
next
end sub

'---------------------------------------------------------------------------
' Runs the discovery.
'---------------------------------------------------------------------------
sub RunDiscovery
dim sConnectionString

dim oReg
dim oConnection
dim oRS, sSQL
dim sCARoleId, sCARole2008Id, sCARole2008ViewName
dim oColumnNames
dim key, value, i
dim oCARoleList, oCARole
dim oOnlinenessMap

'create a registry object
set oReg = GetRegistryObject(sComputerName)

'create the db connection object
set oConnection = MOMCreateObject("ADODB.Connection")

'create a dictionary
set oOnlinenessMap = MOMCreateObject("Scripting.Dictionary")

'open db connection
if OpenDBConnection(oConnection, GetOpsMgrDBConnectionString(oReg)) &lt;&gt; adStateOpen then
'failed
ThrowScriptError "Connection to the Operations Database could not be opened. Please verify that Operations Manager is installed on this computer and that this script is run with the appropriate credentials to access the database.", e
else
'log success
if bDebug then
LogEvent "Successfully connected to Operations Database.", SCOM_INFORMATION
end if
end if

'create an object to hold query results
set oRS = MOMCreateObject("ADODB.RecordSet")

'get CARole.2016 and CARole GUIDs as well as the CARole.2016 view name from the db
sSQL = "SELECT [Id], [BaseMonitoringClassId], [ManagedTypeViewName] FROM [dbo].[ManagedTypeView] WHERE [Name] = 'Microsoft.Windows.CertificateServices.CARole.2016'"
QueryDB oConnection, oRS, sSQL
sCARoleId = oRS("BaseMonitoringClassId")
sCARole2008Id = oRS("Id")
sCARole2008ViewName = oRS("ManagedTypeViewName")
CloseRecordSet oRS

'log stuff
if bDebug then
LogEvent "CARole: " &amp; sCARoleId &amp; " CARole.2016: " &amp; sCARole2008Id &amp; " View Name: " &amp; sCARole2008ViewName, SCOM_INFORMATION
end if

'get view column names from the db
sSQL = "SELECT [ManagedTypePropertyName], [ColumnName] FROM [dbo].[ManagedTypeProperty] where [ManagedTypeId] = '" &amp; sCARoleId &amp; "'"
QueryDB oConnection, oRS, sSQL

'store in a dictionary
set oColumnNames = MOMCreateObject("Scripting.Dictionary")
do until oRS.eof
key = oRS("ManagedTypePropertyName")
value = oRS("ColumnName")
oColumnNames.Add key, value
oRS.MoveNext
loop
CloseRecordSet oRS

'do we have the right properties?
if oColumnNames.Exists(PROP_CA_NAME) and oColumnNames.Exists(PROP_CONFIG_STRING) and oColumnNames.Exists(PROP_CHAIN) then
if bDebug then
LogEvent "Required properties validated.", SCOM_INFORMATION
end if
else
ThrowScriptError "Operational Database does not contain the required properties. Please verify that the management pack is imported correctly.", e
end if

'finally get the property values from the db (sorted by CA Name)
sSQL = "SELECT [" &amp; oColumnNames(PROP_CA_NAME) &amp; "] AS '" &amp; PROP_CA_NAME &amp; "', [" &amp; oColumnNames(PROP_CONFIG_STRING) &amp; "] AS '" &amp; PROP_CONFIG_STRING &amp; "', [" &amp; oColumnNames(PROP_CHAIN) &amp; "] AS '" &amp; PROP_CHAIN &amp; "' FROM [dbo].[" &amp; sCARole2008ViewName &amp; "] ORDER BY [" &amp; oColumnNames(PROP_CA_NAME) &amp; "]"
QueryDB oConnection, oRS, sSQL

'create a list of CARole objects
set oCARoleList = new List
do until oRS.eof
'create and populate a new CARole obj
set oCARole = new CARole
oCARole.CAName = oRS(PROP_CA_NAME)
oCARole.ConfigString = oRS(PROP_CONFIG_STRING)
oCARole.Chain = oRS(PROP_CHAIN)

'add it to the list
oCARoleList.Add(oCARole)

'map it as an online CA
oOnlinenessMap.Item(oCARole.CAName) = true

'next instance
oRS.MoveNext
loop

CloseRecordSet oRS

'run through the list
for i = 0 to oCARoleList.Count - 1
set oCARole = oCARoleList.Data(i)

'discover the CA
DiscoverCAChain oCARole.CAName, oCARole.ConfigString, oCARole.Chain, oOnlinenessMap
next

ReturnSnapshot(oDiscovery)
end sub

'---------------------------------------------------------------------------
' Main functionality of the script
'---------------------------------------------------------------------------
sub main
dim e
set e = new Error

SetLocale("en-us")

'initialize the "library"
InitializeCommon(1401)

'get parameters
if WScript.Arguments.Count &lt;&gt; 4 then
ThrowScriptError "Invalid parameters passed to " &amp; GetScriptName &amp; ".", null
end if
sSourceID = WScript.Arguments(0)
sManagedEntityID = WScript.Arguments(1)
sComputerName = WScript.Arguments(2)
on error resume next
bDebug = CBool(WScript.Arguments(3))
e.Save
on error goto 0
if e.Number &lt;&gt; 0 then
ThrowScriptError "Debug flag must be a valid boolean value (true or false).", e
end if

'log params
if bDebug then
LogEvent "SourceID: " &amp; sSourceID &amp; " ManagedEntityID: " &amp; sManagedEntityID &amp; " ComputerName: " &amp; sComputerName &amp; " Debug: " &amp; bDebug, SCOM_INFORMATION
end if

'create required objects
set oAPI = MOMCreateObject("MOM.ScriptAPI")

'create discovery data
set oDiscovery = oAPI.CreateDiscoveryData(0, sSourceID, sManagedEntityID)

'run the discovery
RunDiscovery
end sub

main</Script></ScriptBody>
<TimeoutSeconds>$Config/TimeoutSeconds$</TimeoutSeconds>
</DataSource>
</MemberModules>
<Composition>
<Node ID="DS"/>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.Discovery.Data</OutputType>
</DataSourceModuleType>