TFS Power Shell Discovery Data Source

TeamFoundationServer2017.TfsPowerShellDiscoveryDataSource (DataSourceModuleType)

Data Source used to locate details about a Candidate using PowerShell

Element properties:

TypeDataSourceModuleType
IsolationOwnProcess
AccessibilityPublic
RunAsTeamFoundationServer2017.Tfs2017UserProfile
OutputTypeSystem.Discovery.Data

Member Modules:

ID Module Type TypeId RunAs 
TfsPowerShellDiscoveryProvider DataSource Microsoft.Windows.TimedPowerShell.DiscoveryProvider Default

Overrideable Parameters:

IDParameterTypeSelectorDisplay NameDescription
BaseInstallPathstring$Config/BaseInstallPath$Base Install PathBy default the assemblies necessary to complete the discovery and monitoring are installed under the \%ProgramData\% directory. You can set this to any valid path to install them in a different location.
IntervalSecondsint$Config/IntervalSeconds$Interval SecondsThe interval that this Discovery is run
ScriptTraceEnabledint$Config/ScriptDebugEnabled$Script Trace EnabledTo enable detailed logging of this Discovery set this to 1, setting it to 0 will disable logging. The logs are sent to the Windows Event Log and can be found with the other Operations Manager logs.
UseSslint$Config/UseSsl$Use SSLIf SSL must be used to communicate with TFS set this to 1, if not set it 0. This override is only applicable to Application Tier and Proxy Servers.
Portint$Config/Port$PortThe Port that is used to communicate with this component of TFS. This override is only applicable to Application Tier and Proxy Servers.

Source Code:

<DataSourceModuleType ID="TeamFoundationServer2017.TfsPowerShellDiscoveryDataSource" Accessibility="Public" RunAs="TeamFoundationServer2017.Tfs2017UserProfile" Batching="false">
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="Component" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="ClassName" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="BaseInstallPath" type="xsd:string"/>
<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="ScriptDebugEnabled" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="UseSsl" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="Port" type="xsd:integer"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="BaseInstallPath" Selector="$Config/BaseInstallPath$" ParameterType="string"/>
<OverrideableParameter ID="IntervalSeconds" Selector="$Config/IntervalSeconds$" ParameterType="int"/>
<OverrideableParameter ID="ScriptTraceEnabled" Selector="$Config/ScriptDebugEnabled$" ParameterType="int"/>
<OverrideableParameter ID="UseSsl" Selector="$Config/UseSsl$" ParameterType="int"/>
<OverrideableParameter ID="Port" Selector="$Config/Port$" ParameterType="int"/>
</OverrideableParameters>
<ModuleImplementation Isolation="OwnProcess">
<Composite>
<MemberModules>
<DataSource ID="TfsPowerShellDiscoveryProvider" TypeID="Windows!Microsoft.Windows.TimedPowerShell.DiscoveryProvider">
<IntervalSeconds>$Config/IntervalSeconds$</IntervalSeconds>
<SyncTime/>
<ScriptName>TfsDiscovery.ps1</ScriptName>
<ScriptBody><Script>param($sourceId, $managedEntityId, $component, $baseInstallPath, $className, $port, $useSsl, $scriptDebugEnabled)
###
# This discovery will check if a specific Tfs component is installed, query it's topography via a C# Class
# To insure errors don't go on noticed $ErrorActionPreference will be set to Stop.
###
# $sourceId/$managedEntityId - GUID used to identify this discovery, simply passed through to the SCOM Script API
# Discovery Object
# $component - Tfs Component ("ApplicationTier", "VersionControlProxy") to look for
# $baseInstallPath - An override location to install the binary - if not provided a default will be used, see Compute-InstallPath
# $className - The name of the class (from with in ManagementPack namespace/assembly) that should be used for this discovery
# $port - The port used for the Tfs Instance (default to 8080 for ATs and 8081 for Proxies, and ignored for everything else)
# $useSsl - A flag to control how we should communicate with tfs, when true we use https, when false (the default) we use http
# $scriptDebugEnabled - A flag to control if debug information should be logged
# Errors and Warnings are always logged

#
# This maps Names like TfsAppComponentBase to their full Management name, which SCOM will then convert to its specific GUID
# It will include both types and properties
$Script:ScomGuidMap = @{
# SCOM GUID MAP - START
"[TfsAdministrationWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsAdministrationWebService']$"
"[TfsAuthorizationWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsAuthorizationWebService']$"
"[TfsAppComponentBase]" = "$MPElement[Name='TeamFoundationServer2017.TfsAppComponentBase']$"
"[TfsApplicationTierServer]" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServer']$"
"[TfsApplicationTierServer]/ATComputerName" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServer']/ATComputerName$"
"[TfsApplicationTierServer]/ObjectPath" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServer']/ObjectPath$"
"[TfsApplicationTierServer]/InstallLocation" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServer']/InstallLocation$"
"[TfsApplicationTierServer]/ConfigDatabaseConnectionString" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServer']/ConfigDatabaseConnectionString$"
"[TfsApplicationTierServer]/UseSsl" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServer']/UseSsl$"
"[TfsApplicationTierServer]/Port" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServer']/Port$"
"[TfsApplicationTierServer]/VirtualDirectory" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServer']/VirtualDirectory$"
"[TfsApplicationTierServer]/JobAgentServiceName" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServer']/JobAgentServiceName$"
"[TfsApplicationTierServerCandidate]" = "$MPElement[Name='TeamFoundationServer2017.TfsApplicationTierServerCandidate']$"
"[TfsBaseWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsBaseWebService']$"
"[TfsBaseWebService]/ObjectPath" = "$MPElement[Name='TeamFoundationServer2017.TfsBaseWebService']/ObjectPath$"
"[TfsBaseWebService]/ServiceName" = "$MPElement[Name='TeamFoundationServer2017.TfsBaseWebService']/ServiceName$"
"[TfsBaseWebService]/Port" = "$MPElement[Name='TeamFoundationServer2017.TfsBaseWebService']/Port$"
"[TfsBaseWebService]/VirtualDirectory" = "$MPElement[Name='TeamFoundationServer2017.TfsBaseWebService']/VirtualDirectory$"
"[TfsBaseWebService]/UseSsl" = "$MPElement[Name='TeamFoundationServer2017.TfsBaseWebService']/UseSsl$"
"[TfsBuildWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsBuildWebService']$"
"[TfsCandidateBase]" = "$MPElement[Name='TeamFoundationServer2017.TfsCandidateBase']$"
"[TfsCandidateBase]/Component" = "$MPElement[Name='TeamFoundationServer2017.TfsCandidateBase']/Component$"
"[TfsInstallation]" = "$MPElement[Name='TeamFoundationServer2017.TfsInstallation']$"
"[TfsInstallation]/TfsInstanceID" = "$MPElement[Name='TeamFoundationServer2017.TfsInstallation']/TfsInstanceID$"
"[TfsInstallation]/ATServerNames" = "$MPElement[Name='TeamFoundationServer2017.TfsInstallation']/ATServerNames$"
"[TfsProjectCollection]" = "$MPElement[Name='TeamFoundationServer2017.TfsProjectCollection']$"
"[TfsProjectCollection]/ProjectCollectionName" = "$MPElement[Name='TeamFoundationServer2017.TfsProjectCollection']/ProjectCollectionName$"
"[TfsProjectCollection]/ObjectPath" = "$MPElement[Name='TeamFoundationServer2017.TfsProjectCollection']/ObjectPath$"
"[TfsProjectCollection]/BaseWebServiceUri" = "$MPElement[Name='TeamFoundationServer2017.TfsProjectCollection']/BaseWebServiceUri$"
"[TfsProjectCollection]/TfsInstanceID" = "$MPElement[Name='TeamFoundationServer2017.TfsProjectCollection']/TfsInstanceID$"
"[TfsProxy]" = "$MPElement[Name='TeamFoundationServer2017.TfsProxy']$"
"[TfsProxy]/ProxyName" = "$MPElement[Name='TeamFoundationServer2017.TfsProxy']/ProxyName$"
"[TfsProxy]/ObjectPath" = "$MPElement[Name='TeamFoundationServer2017.TfsProxy']/ObjectPath$"
"[TfsProxy]/Port" = "$MPElement[Name='TeamFoundationServer2017.TfsProxy']/Port$"
"[TfsProxy]/UseSsl" = "$MPElement[Name='TeamFoundationServer2017.TfsProxy']/UseSsl$"
"[TfsProxy]/Uris" = "$MPElement[Name='TeamFoundationServer2017.TfsProxy']/Uris$"
"[TfsProxyCandidate]" = "$MPElement[Name='TeamFoundationServer2017.TfsProxyCandidate']$"
"[TfsRegistrationWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsRegistrationWebService']$"
"[TfsVersionControlWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsVersionControlWebService']$"
"[TfsWarehouse]" = "$MPElement[Name='TeamFoundationServer2017.TfsWarehouse']$"
"[TfsWarehouse]/TfsInstanceID" = "$MPElement[Name='TeamFoundationServer2017.TfsWarehouse']/TfsInstanceID$"
"[TfsWarehouse]/ObjectPath" = "$MPElement[Name='TeamFoundationServer2017.TfsWarehouse']/ObjectPath$"
"[TfsWebAccess]" = "$MPElement[Name='TeamFoundationServer2017.TfsWebAccess']$"
"[TfsWebAccess]/TfsInstanceID" = "$MPElement[Name='TeamFoundationServer2017.TfsWebAccess']/TfsInstanceID$"
"[TfsWebAccess]/ObjectPath" = "$MPElement[Name='TeamFoundationServer2017.TfsWebAccess']/ObjectPath$"
"[TfsWorkItemTrackingWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsWorkItemTrackingWebService']$"
"[TfsATServerHostsBuildWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsATServerHostsBuildWebService']$"
"[TfsATServerHostsVersionControlWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsATServerHostsVersionControlWebService']$"
"[TfsATServerHostsWorkItemTrackingWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsATServerHostsWorkItemTrackingWebService']$"
"[TfsATServerHostsAdministrationWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsATServerHostsAdministrationWebService']$"
"[TfsATServerHostsAuthorizationWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsATServerHostsAuthorizationWebService']$"
"[TfsATServerHostsRegistrationWebService]" = "$MPElement[Name='TeamFoundationServer2017.TfsATServerHostsRegistrationWebService']$"
"[TfsATServerHostsWebAccess]" = "$MPElement[Name='TeamFoundationServer2017.TfsATServerHostsWebAccess']$"
"[TfsInstallationContainsATServer]" = "$MPElement[Name='TeamFoundationServer2017.TfsInstallationContainsATServer']$"
"[TfsInstallationContainsProjectCollection]" = "$MPElement[Name='TeamFoundationServer2017.TfsInstallationContainsProjectCollection']$"
"[TfsInstallationContainsWarehouse]" = "$MPElement[Name='TeamFoundationServer2017.TfsInstallationContainsWarehouse']$"
"[TfsProxyReferencesProjectCollection]" = "$MPElement[Name='TeamFoundationServer2017.TfsProxyReferencesProjectCollection']$"
"[TfsProxyReferenceTfsInstallation]" = "$MPElement[Name='TeamFoundationServer2017.TfsProxyReferenceTfsInstallation']$"
"[System!System.Entity]" = "$MPElement[Name='System!System.Entity']$"
"[System!System.Entity]/DisplayName" = "$MPElement[Name='System!System.Entity']/DisplayName$"
"[Windows!Microsoft.Windows.Computer]" = "$MPElement[Name='Windows!Microsoft.Windows.Computer']$"
"[Windows!Microsoft.Windows.Computer]/PrincipalName" = "$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$"
# SCOM GUID MAP - END
}

#
# This maps Constants to their value
$Script:ScomConstMap = @{
# SCOM CONST MAP - START
"CONST#JobAgentServiceName" = "TFSJobAgent"
# SCOM CONST MAP - END
}

#
# Constants
[string] $Script:MessagePreFix = "TfsDiscovery.ps1"

#
#
# COMMON HEADER - START

# Sets $Script:mpVersion, $Script:tfsVersion, $Script:mpVersionEnd and $Script:baseRegesityKey
# PRODUCT CONSTANTS - START
[string] $Script:mpVersion = "15.113.26130.0"
[string] $Script:tfsVersion = "15.0"
[int] $Script:mpVersionEnd = 0
[string] $Script:baseRegesityKey = "Software\Microsoft\TeamFoundationServer\15.0"
# PRODUCT CONSTANTS - END

[string] $Script:defaultBaseInstallPath = "$env:ProgramData\Microsoft Team Foundation Server $Script:tfsVersion Management Pack\"

[int] $Script:EventLogId = 9000 + $Script:mpVersionEnd

[int] $Script:severityInfo = 0
[int] $Script:severityError = 1
[int] $Script:severityWarn = 2

#
# List of Included Assemblies
$Script:InstallFiles = @(
# FILE RESOURCES - START
"$FileResource[Name='Res.ManagementPack.exe']/Path$"
"$FileResource[Name='Res.Microsoft.TeamFoundation.Client.dll']/Path$"
"$FileResource[Name='Res.Microsoft.TeamFoundation.Common.dll']/Path$"
"$FileResource[Name='Res.Microsoft.TeamFoundation.Core.WebApi.dll']/Path$"
"$FileResource[Name='Res.Microsoft.TeamFoundation.VersionControl.Client.dll']/Path$"
"$FileResource[Name='Res.Microsoft.VisualStudio.Services.Client.Interactive.dll']/Path$"
"$FileResource[Name='Res.Microsoft.VisualStudio.Services.Common.dll']/Path$"
"$FileResource[Name='Res.Microsoft.VisualStudio.Services.WebApi.dll']/Path$"
# FILE RESOURCES - END
)

#
#cached values
$Script:MomSciptApi = $null

#
# Script Parameters
[bool] $Script:detailedLogEnabled = $false

#
# Allow cleaning up after ourselves
$originalErrorActionPreference = $ErrorActionPreference

#
# Sets $Script:detailedLogEnabled
function SetDetailedLogEnabled
{
param($detailedLogEnabled)

[int] $val = 0
if ([int]::TryParse($detailedLogEnabled, [ref] $val))
{
$Script:detailedLogEnabled = ($val -ne 0)
}
}

#
# Determines the install path
function Get-InstallPath
{
param ([string] $baseInstallPath)

[string] $installPath = Compute-InstallPath -baseInstallPath $baseInstallPath
Validate-Install -installPath $installPath

return $installPath
}

#
# Computes the Install Path for the MP binary files
function Compute-InstallPath
{
param ([string] $baseInstallPath)

if ([String]::IsNullOrEmpty($baseInstallPath))
{
$baseInstallPath = $Script:defaultBaseInstallPath
}

[string] $result = [System.IO.Path]::Combine($baseInstallPath, $Script:mpVersion) + "\"

Log-Message -message ("Computed Install Path: {0}" -f $result) -severity $Script:severityInfo

return $result
}

#
# Verifies that the binary files has been placed in the provided install directory
# It can fail if we can't a lock using the mutex for the install path
function Validate-Install
{
param ([string] $installPath)

Log-Message -message ("Entered InstallIfNeeded: {0}" -f $installPath) -severity $Script:severityInfo

## Need void here to prevent powershell from adding a space to the caller outout
[void] [System.Threading.Mutex]$mutex
[bool] $gotMutex = $false
try
{
[string] $mutextName = ("Global\{0}" -f $installPath.Replace("\",";"))
Log-Message -message ("Creating Mutex: {0}" -f $mutextName) -severity $Script:severityInfo
[bool]$wasCreated = $false
$mutex = New-Object System.Threading.Mutex($true, $mutextName, [ref] $wasCreated)
if ($wasCreated)
{
Log-Message -message "Created Mutex" -severity $Script:severityInfo
$gotMutex = $true
}
else
{
Log-Message -message "Waiting for up to 3 secs for Mutext" -severity $Script:severityInfo
try
{
$gotMutex = $mutex.WaitOne(3000)
}
catch [System.Threading.AbandonedMutexException]
{
## We have no problem just taking over an Abandoned Mutex
Log-Message -message "Mutex was Previously Abandoned" -severity $Script:severityInfo

$gotMutex = $true
}
Log-Message -message ("gotMutex: {0}" -f $gotMutex) -severity $Script:severityInfo
}

if ($gotMutex)
{
Validate-Install-UnderMutex -installPath $installPath
}
else
{
throw [Exception] ("Failed get Mutext to update {0}" -f $installPath)
}

}
finally
{
if ($gotMutex)
{
$mutex.ReleaseMutex()
}
if ($mutex -ne $null)
{
$mutex.Dispose()
}
}

Log-Message -message "Exiting InstallIfNeeded" -severity $Script:severityInfo
}

#
# Does the work of Verifying that the binaries files have been placed.
# Assumes the caller already obtained the needed Mutext to prevent raise conditions
function Validate-Install-UnderMutex
{
param ([string] $installPath)

Log-Message -message ("Entered Validate-Install-UnderMutex: {0}" -f $installPath) -severity $Script:severityInfo

#
# Make sure the directory is there.
[void] (New-Item -Force -ItemType directory -Path $installPath)

foreach ($file in $Script:InstallFiles)
{
$fileName = [IO.Path]::GetFileName($file)
if (-not(Test-Path -PathType Leaf -Path ($installPath + $fileName)))
{
[string] $tempName = $fileName + '.temp'
if (Test-Path -PathType Leaf -Path ($installPath + $tempName))
{
Log-Message -message ("Removing old temp file {0}{1}" -f $installPath, $tempName) -severity $Script:severityInfo
[void] (Remove-Item -Path ($installPath + $tempName) -Force)
}

Log-Message -message ("Copying to temp location {0} -&gt; {1}{2}" -f $file, $installPath, $tempName) -severity $Script:severityInfo
[void] (Copy-Item -Path $file -Destination ($installPath + $tempName))

Log-Message -message ("Rename {0}{1} -&gt; {2}" -f $installPath, $tempName, $fileName) -severity $Script:severityInfo
[void] (Rename-Item -Path ($installPath + $tempName) -NewName $fileName)
}
}

Log-Message -message ("Exiting Validate-Install-UnderMutex: {0}" -f $installPath) -severity $Script:severityInfo
}

#
# Adds information about the current user to the log
function Log-User
{
$owner = (Get-WmiObject win32_process | Where-Object {$_.ProcessId -eq $pid }).getowner()

$message = "User Info"
$message += [Environment]::NewLine + "Win32_ComputerSystem -&gt; user name: " + (Get-WmiObject Win32_ComputerSystem).username
$message += [Environment]::NewLine + '$env:username:' + $env:username
$message += [Environment]::NewLine + "Owner: " + $owner.Domain + "\" + $owner.User

Log-Message -message $message -severity $Script:severityInfo
}

#
# Calls ManagementPack.exe and logs the response. Additionally parse out the log and returns the pay load
# $forDiscovery should be $true when running a Discovery and $false for monitors
function Run-ManagementPack
{
param ([string] $baseInstallPath, [bool] $forDiscovery, [string] $className, [string] $params)

Log-Message -message "Entered Run-ManagementPack: $baseInstallPath $forDiscovery $className $params" -severity $Script:severityInfo

[string] $installPath = Get-InstallPath -baseInstallPath $baseInstallPath
[string] $fullPath = [System.IO.Path]::Combine($installPath, "ManagementPack.exe")

[string] $action = $null
if ($forDiscovery)
{
$action = "Discover"
}
else
{
$action = "Monitor"
}

[string] $command = (InQuotes -str $fullPath) + " /Action:$action /Class:$className $params"
Log-Message -message "Calling $command" -severity $Script:severityInfo
[array] $output = Invoke-Expression "&amp; $command"
[int] $exitCode = $LASTEXITCODE

[string] $response = [string]::Join([System.Environment]::NewLine, $output)

[int] $severity = $Script:severityInfo
[string] $message = "Exit Code $exitCode from Calling $command $([System.Environment]::NewLine)$response"
# $exitCode = 0 - No errors, log severity info
# $exitCode = 1 - At least one error and we were able get some data, log severity Error
# $exitCode = 2 - Failed to get data, throw
# $exitCode = Anything Else - Unexpected error, throw
if (($exitCode -eq 0) -or ($exitCode -eq 1))
{
if ($status -gt 0)
{
$severity = $Script:severityError
}
Log-Message -message $message -severity $severity
}
else
{
throw [Exception] $message
}

# At this point response should hold a log and a payload, return the payload
[string] $logBarrior = "**[[&lt;&lt;END OF LOG&gt;&gt;]]**"
[int] $index = $response.IndexOf($logBarrior)

if ($index -lt 0)
{
throw [Exception] "Failed to find $logBarrior in response: $response"
}

[string] $result = $response.SubString($index + $logBarrior.Length)

if ($result.StartsWith([System.Environment]::NewLine))
{
$result = $result.Substring([System.Environment]::NewLine.Length)
}

Log-Message -message "Exiting Run-ManagementPack: $baseInstallPath $forDiscovery $className $params" -severity $Script:severityInfo

return $result
}

function InQuotes
{
param ([string] $str)
return '"' + $str + '"'
}

#
# Pulls a specific values from the windows registry
function Get-RegistryValue
{
param ([string] $path, [string] $name)

$message = "Reading Registry " + $path + "[" + $name + "]="
$result = $null
try
{
$result = (Get-ItemProperty -Path $path -Name $name -ErrorAction Stop).$name
$message += $result
}
catch
{
$message += "{" + $_ +"}"
}

Log-Message -message $message -severity $Script:severityInfo
return $result
}

#
# Logs messages to the windows event log
function Log-Message
{
param ([string] $message, [int] $severity)

switch ($severity)
{
$Script:severityInfo {Write-Host ($message) } # Info
$Script:severityWarn {Write-Host ($message) -ForegroundColor Yellow } # Warning
default # Error
{
$severity = $Script:severityError
Write-Host ($message) -ForegroundColor Red
}
}

if (($severity -eq $Script:severityInfo) -and (-not $Script:detailedLogEnabled))
{
# Don't record info to the event log
return
}

$header = $Script:mpVersion + "|" + $Script:MessagePreFix

$script = $MyInvocation.MyCommand.Name

$chunks = Get-Chunks -message $message

$api = Get-MomScriptApi

if ($chunks.Count -eq 1)
{
$api.LogScriptEvent($script, $Script:EventLogId, $severity, $header + $message)
}
else
{
$api.LogScriptEvent($script, $Script:EventLogId, $severity, ("{0} Broken into {1} Chunks" -f $header, $chunks.Count))
for ($i = 0; $i -lt $chunks.Count; $i++)
{
$api.LogScriptEvent($script, $Script:EventLogId, $severity, $header + $chunks[$i])
}
}
}

#
# Takes a string that could be very long and breaks it into smaller chunks
function Get-Chunks
{
param ([string] $message)
$chunkSize = 20480 #20K
$result = @{}

for ($i = 0; $i -lt $message.Length; $i += $chunkSize)
{
$length = $i + $chunkSize
if ($length -gt $message.Length)
{
$length = $message.Length - $i
}
else
{
$length = $chunkSize
}

$result[$result.Count] = $message.SubString($i, $length)
}

return $result
}

#
# Wrapper to obtain and cache the value to MOM COM Script API
function Get-MomScriptApi
{
if ($Script:MomSciptApi -eq $null)
{
$Script:MomSciptApi = new-object -comObject 'MOM.ScriptAPI'
}
return $Script:MomSciptApi
}

# COMMON HEADER - END
#
#

[int] $Script:EventLogId = 9000 + $Script:mpVersionEnd

#
# Start of Specific Functions
########################################################################################


#
# Returns the SCOM Script API Discovery Data object populated with what ever data we can find.
# It return null if the component is not configured
function Get-DiscoveryData
{
param ([string] $sourceId, [string] $managedEntityId, [string] $component, [string] $baseInstallPath, [string] $className, [int] $port, [bool] $useSsl)

if (-not([String]::IsNullOrEmpty($component)))
{
#
# $component will be set to null for things like VsoAgent, which Don't have an "IsConfigured" node in the registry
if (-not(Get-IsConfigured $component))
{
#
# This it is not Configured there nothing for us to do, but the initial discovery provider should have found that already
Log-Message -message ("Component Not Configured on this machine: {0}" -f $component) -severity $Script:severityWarn
return $null
}
}

[string] $xmlContent = Get-DiscoveryXml -baseInstallPath $baseInstallPath -className $className -port $port -useSsl $useSsl
if ([String]::IsNullOrEmpty($xmlContent))
{
return $null
}

return Unpack-DiscoveryXml -xmlContent $xmlContent -sourceId $sourceId -managedEntityId $managedEntityId
}

#
# Given the XML returned by the C# Class, this will Create the SCOM Script API Discovery Data object and fill it in.
# The XML is validated, violations result in an exception being thrown
function Unpack-DiscoveryXml
{
param ([string] $xmlContent, [string] $sourceId, [string] $managedEntityId)

$xml =[xml]$xmlContent

$api = Get-MomScriptApi

$discoveryData = $api.CreateDiscoveryData(0, $sourceId, $managedEntityId)

#
# this will map reference IDs to the instances of class discoveries
$refMap = @{}

$classInstances = $xml.SelectNodes("/discoveryData/classInstances/classInstance")
Log-Message -message ("Starting Class Instances {0}" -f $classInstances.Count) -severity $Script:severityInfo

foreach ($classInstance in $classInstances)
{
$refId = $classInstance.refId
if ($refMap.ContainsKey($refId))
{
throw [Exception] ("Duplicate refId found {0}!" -f $refId)
}

$type = $classInstance.type
if (-not($Script:ScomGuidMap.ContainsKey($type)))
{
throw [Exception] ("Unknown type {0}!" -f $type)
}
$typeGuid = $Script:ScomGuidMap[$type]

$properties = $classInstance.SelectNodes("property")

$message = ("Creating {0} as {1}/{2} with {3} properties." -f $refId, $type, $typeGuid, $properties.Count)
Log-Message -message $message -severity $Script:severityInfo

$instance = $discoveryData.CreateClassInstance($typeGuid)
$refMap[$refId] = $instance

foreach ($property in $properties)
{
$path = $property.path

if (-not($Script:ScomGuidMap.ContainsKey($path)))
{
throw [Exception] ("Unknown path {0}!" -f $path)
}
$pathGuid = $Script:ScomGuidMap[$path]

$value = $property.InnerText

if ($Script:ScomConstMap.ContainsKey($value))
{
$message = ("Mapped {0} to {1} for {2}." -f $value, $Script:ScomConstMap[$value], $path)
Log-Message -message $message -severity $Script:severityInfo
$value = $Script:ScomConstMap[$value]
}

$message = ("Add {0}/{1} set to {2}." -f $path, $pathGuid, $value)
Log-Message -message $message -severity $Script:severityInfo
$instance.AddProperty($pathGuid, $value)
}

$discoveryData.AddInstance($instance)
}

$relationships = $xml.SelectNodes("/discoveryData/relationships/relationship")
Log-Message -message ("Starting Relationships {0}" -f $relationships.Count) -severity $Script:severityInfo

foreach ($relationship in $relationships)
{
$type = $relationship.type
if (-not($Script:ScomGuidMap.ContainsKey($type)))
{
throw [Exception] ("Unknown type {0}!" -f $type)
}
$typeGuid = $Script:ScomGuidMap[$type]

$sourceRefId = $relationship.sourceRefId
if ([String]::IsNullOrEmpty($sourceRefId))
{
throw [Exception] ("Source refId missing from {0}!" -f $type)
}
if (-not($refMap.ContainsKey($sourceRefId)))
{
throw [Exception] ("Source refId {0} not found for {1}!" -f $sourceRefId, $type)
}
$targetRefId = $relationship.targetRefId
if ([String]::IsNullOrEmpty($targetRefId))
{
throw [Exception] ("Target refId missing from {0}!" -f $type)
}
if (-not($refMap.ContainsKey($targetRefId)))
{
throw [Exception] ("Target refId {0} not found for {1}!" -f $targetRefId, $type)
}

$message = ("Adding Type {0}/{1} with source {2} and target {3}" -f $type, $typeGuid, $sourceRefId, $targetRefId)
Log-Message -message $message -severity $Script:severityInfo

$instance = $discoveryData.CreateRelationshipInstance($typeGuid)
$instance.Source = $refMap[$sourceRefId]
$instance.Target = $refMap[$targetRefId]
$discoveryData.AddInstance($instance)
}

return $discoveryData
}

#
# This will query the component using the C# exe and return a string containing XML that describes the objects
# and relationships found.
# If a fatal error is found an exception is thrown.
function Get-DiscoveryXml
{
param ([string] $baseInstallPath, [string] $className, [int] $port, [bool] $useSsl)

[string] $params = "/Port:$port /UseSsl:$useSSl"
[string] $xmlContent = Run-ManagementPack -baseInstallPath $baseInstallPath -forDiscovery $true -className $className -params $params

return $xmlContent
}

#
# Checks the registry to determine if the specific component is configured
function Get-IsConfigured
{
param ([string] $component)

$path = "HKLM:\" + $Script:baseRegesityKey + "\InstalledComponents\" + $component
$value = Get-RegistryValue -path $path -name "IsConfigured"
$result = ($value -eq 1)
$message = "IsConfigured for " + $component + ": " + $result
Log-Message -message $message -severity $Script:severityInfo
return $result
}

########################################################################################
# End of Functions
#

#
# Our return value
$data = $null

try
{
#
# Setup
$ErrorActionPreference = "Stop"

SetDetailedLogEnabled -detailedLogEnabled $scriptDebugEnabled

$Script:MessagePreFix = ("{0}|{1}|{2}|{3}|{4}|{5}|{6}|{7}|{8}" -f $Script:MessagePreFix, $sourceId, $managedEntityId, $component, $baseInstallPath, $className, $port, $useSsl, [Environment]::NewLine)

#
# Log that we got here and validate input
$message = ""
foreach ($guidItem in $Script:ScomGuidMap.Keys)
{
$message += ('"{0}" = "{1}"{2}' -f $guidItem, $Script:ScomGuidMap[$guidItem], [Environment]::NewLine)
}
foreach ($file in $Script:InstallFiles)
{
$message += ("{0}{1}" -f $file, [Environment]::NewLine)
}
Log-Message -message $message -severity $Script:severityInfo

Log-Message -message ("First Param sourceId:{0}" -f $sourceId) -severity $Script:severityInfo
If ([String]::IsNullOrEmpty($sourceId))
{
throw [Exception] "sourceId missing!"
}
Log-Message -message ("Second Param managedEntityId:{0}" -f $managedEntityId) -severity $Script:severityInfo
If ([String]::IsNullOrEmpty($managedEntityId))
{
throw [Exception] "managedEntityId missing!"
}

Log-Message -message ("Third Param component:{0}" -f $component) -severity $Script:severityInfo

Log-Message -message ("Forth Param baseInstallPath:{0}" -f $baseInstallPath) -severity $Script:severityInfo

Log-Message -message ("Fifth Param className:{0}" -f $className) -severity $Script:severityInfo
If ([String]::IsNullOrEmpty($className))
{
throw [Exception] "className missing!"
}

Log-Message -message ("Sixth Param port:{0}" -f $port) -severity $Script:severityInfo
[int] $val = 0
if ([int]::TryParse($port, [ref] $val))
{
$port = $val
}
else
{
throw [Exception] ("Port ({0}) could not be parsed to an int" -f $port)
}

Log-Message -message ("Eighth Param useSsl:{0}" -f $useSsl) -severity $Script:severityInfo
$val = 0
if ([int]::TryParse($useSsl, [ref] $val))
{
$useSsl = ($val -eq 1)
}
else
{
$useSsl = $false
}

Log-Message -message ("Ninth Param scriptDebugEnabled:{0}" -f $scriptDebugEnabled) -severity $Script:severityInfo

Log-User

#
# Do the work.
$data = Get-DiscoveryData -sourceId $sourceId -managedEntityId $managedEntityId -component $component -baseInstallPath $baseInstallPath -className $className -port $port -useSsl $useSsl
}
catch
{
Log-Message -message ("{0}{1}{2}" -f $_.InvocationInfo.PositionMessage, [Environment]::NewLine, $_.Exception) -severity $Script:severityError
}
finally
{
#
# Clean up after our self
$ErrorActionPreference = $originalErrorActionPreference
}
return $data

</Script></ScriptBody>
<Parameters>
<Parameter>
<Name>sourceID</Name>
<Value>$MPElement$</Value>
</Parameter>
<Parameter>
<Name>managedEntityID</Name>
<Value>$Target/Id$</Value>
</Parameter>
<Parameter>
<Name>component</Name>
<Value>$Config/Component$</Value>
</Parameter>
<Parameter>
<Name>baseInstallPath</Name>
<Value>$Config/BaseInstallPath$</Value>
</Parameter>
<Parameter>
<Name>className</Name>
<Value>$Config/ClassName$</Value>
</Parameter>
<Parameter>
<Name>port</Name>
<Value>$Config/Port$</Value>
</Parameter>
<Parameter>
<Name>useSsl</Name>
<Value>$Config/UseSsl$</Value>
</Parameter>
<Parameter>
<Name>scriptDebugEnabled</Name>
<Value>$Config/ScriptDebugEnabled$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>300</TimeoutSeconds>
</DataSource>
</MemberModules>
<Composition>
<Node ID="TfsPowerShellDiscoveryProvider"/>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.Discovery.Data</OutputType>
</DataSourceModuleType>