Simple or basic runAs accounts in custom script discovery and monitors

Hi,

I am developing a powershell based managment pack to monitor cloud resources and I need authentication in place.

Have anyone used basic runAsAccount in a PS discovery or script? I have played around a little but I get this error event 1102

Rule/Monitor "Discovery" running for instance "CLASS" with id:"{A52346E3-5E4C-37EC-E870-97AB46DCA768}" cannot be initialized and will not be loaded. Management group "SCOM"

I have created a run as profile

    <SecureReferences>
      <SecureReference ID="Application.RunasProfile" Accessibility="Public" Context="Application.Class" />
    </SecureReferences>

And added parameters to my script datasource

            <!--RUNAS ACCOUNT PARAMETERS-->
            <Parameter>
              <Name>QueryUser</Name>
              <Value>$RunAs[Name="Application.RunasProfile"]/UserName$</Value>
            </Parameter>
            <Parameter>
              <Name>QueryPwd</Name>
              <Value>$RunAs[Name="Application.RunasProfile"]/Password$</Value>
            </Parameter>

Before using them as parameters in my script

param($sourceId, $managedEntityId, $computerName, $QueryUser, $QueryPwd, $ClientID, $TenantID)
$tenantId = $TenantID
$client_id = $ClientID
$username = $QueryUser
$password = Convertto-securestring $QueryPwd -asplaintext -force
$creds = New-Object System.Management.Automation.PSCredential ($username, $password)

HELP! my basic management pack gives me headaches

 

EDIT: Full class/discovery and script

<ManagementPackFragment SchemaVersion="2.0" xmlns:xsd="http://www.w3Org/2001/XMLSchema">
<!-- In this fragment you need to replace ##Application##, ##ApplicationComponent##, ##RegistryKey##, ##SourceClass## -->
<!-- This fragment will discover a class instance (or instances) based on existence of a registry KEY -->
<!-- This fragment depends on references:
Windows!	=	Microsoft.Windows.Library
System!		=	System.Library
-->
<TypeDefinitions>
<EntityTypes>
<ClassTypes>
<ClassType ID="Office365.CSPCustomer.Class" Base="Windows!Microsoft.Windows.ApplicationComponent" Accessibility="Internal" Abstract="false" Hosted="true" Singleton="false">
<Property ID="CustomerName" Key="true" Type="string" />
<Property ID="URL" Key="false" Type="string" />
<Property ID="CSPID" Key="false" Type="string" />
<Property ID="ManagedServices" Key="false" Type="string" />
</ClassType>
<!-- We choose Microsoft.Windows.LocalApplication as our generic base class -->

</ClassTypes>
<RelationshipTypes>

<RelationshipType

ID="Office365.CSPCustomer.Realtionship"

Base="System!System.Hosting"

Accessibility="Internal"

Abstract="false">

<Source ID="Source" Type="Office365.WatcherNode.Class" />

<Target ID="Target" Type="Office365.CSPCustomer.Class" />

</RelationshipType>

</RelationshipTypes>
</EntityTypes>

</TypeDefinitions>
<Monitoring>
<Discoveries>
<Discovery ID="Office365.CSPCustomer.Class.Discovery" Target="Office365.WatcherNode.Class" Enabled="true" ConfirmDelivery="false" Remotable="true" Priority="Normal">
<!-- We choose Microsoft.Windows.ServerOperatingSystem as the preferred target class to ensure this will run on all Windows Servers, but wont create duplicates on clusters -->
<Category>Discovery</Category>
<DiscoveryTypes>
<DiscoveryClass TypeID="Office365.CSPCustomer.Class" />
</DiscoveryTypes>
<DataSource ID="DS" TypeID="Windows!Microsoft.Windows.TimedPowerShell.DiscoveryProvider" RunAs="_CSP_Office365.CSPUser.RunasProfile">
<IntervalSeconds>14400</IntervalSeconds>
<SyncTime />
<ScriptName>GetCSPCustomer.ps1</ScriptName>
<ScriptBody>$IncludeFileContent/Scripts/GetCSPCustomer.ps1$</ScriptBody>
<Parameters>
<Parameter>
<Name>sourceID</Name>
<Value>$MPElement$</Value>
</Parameter>
<Parameter>
<Name>managedEntityID</Name>
<Value>$Target/Id$</Value>
</Parameter>
<Parameter>
<Name>computerName</Name>
<Value>$Target/Host/Property[Type='Windows!Microsoft.Windows.Computer']/PrincipalName$</Value>
</Parameter>
<!--RUNAS ACCOUNT PARAMETERS-->
<Parameter>
<Name>QueryUser</Name>
<Value>$RunAs[Name="_CSP_Office365.CSPUser.RunasProfile"]/UserName$</Value>
</Parameter>
<Parameter>
<Name>QueryPwd</Name>
<Value>$RunAs[Name="_CSP_Office365.CSPUser.RunasProfile"]/Password$</Value>
</Parameter>
<!--Use the client and tenant id from watcher node-->
<Parameter>
<Name>ClientID</Name>
<Value>$Target/Property[Type="Office365.WatcherNode.Class"]/ClientID$</Value>
</Parameter>
<Parameter>
<Name>TenantID</Name>
<Value>$Target/Property[Type="Office365.WatcherNode.Class"]/PartnerDomain$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>300</TimeoutSeconds>
<StrictErrorHandling>true</StrictErrorHandling>
</DataSource>
</Discovery>

<!-- Additional property discovery -->
<Discovery ID="Office365.CSPCustomer.Services.Class.Discovery" Target="Office365.WatcherNode.Class" Enabled="false" ConfirmDelivery="false" Remotable="true" Priority="Normal">
<Category>Discovery</Category>
<DiscoveryTypes>
<DiscoveryClass TypeID="Office365.CSPCustomer.Class" />
</DiscoveryTypes>
<DataSource ID="DS" TypeID="Windows!Microsoft.Windows.TimedPowerShell.DiscoveryProvider" RunAs="_CSP_Office365.CSPUser.RunasProfile">
<IntervalSeconds>21600</IntervalSeconds>
<SyncTime>08:20</SyncTime>
<ScriptName>GetCSPCustomerServices.ps1</ScriptName>
<ScriptBody>$IncludeFileContent/Scripts/GetCSPCustomerServices.ps1$</ScriptBody>
<Parameters>
<Parameter>
<Name>sourceID</Name>
<Value>$MPElement$</Value>
</Parameter>
<Parameter>
<Name>managedEntityID</Name>
<Value>$Target/Id$</Value>
</Parameter>
<Parameter>
<Name>computerName</Name>
<Value>$Target/Host/Property[Type='Windows!Microsoft.Windows.Computer']/PrincipalName$</Value>
</Parameter>
<!--RUNAS ACCOUNT PARAMETERS-->
<Parameter>
<Name>QueryUser</Name>
<Value>$RunAs[Name="_CSP_Office365.CSPUser.RunasProfile"]/UserName$</Value>
</Parameter>
<Parameter>
<Name>QueryPwd</Name>
<Value>$RunAs[Name="_CSP_Office365.CSPUser.RunasProfile"]/Password$</Value>
</Parameter>
<!--Use the client and tenant id from watcher node-->
<Parameter>
<Name>ClientID</Name>
<Value>$Target/Property[Type="Office365.WatcherNode.Class"]/ClientID$</Value>
</Parameter>
<Parameter>
<Name>TenantID</Name>
<Value>$Target/Property[Type="Office365.WatcherNode.Class"]/PartnerDomain$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>600</TimeoutSeconds>
<StrictErrorHandling>true</StrictErrorHandling>
</DataSource>
</Discovery>
</Discoveries>
</Monitoring>
<LanguagePacks>
<LanguagePack ID="ENU" IsDefault="true">
<DisplayStrings>
<DisplayString ElementID="Office365.CSPCustomer.Class">
<Name> CSP Customer</Name>
<Description> CSP customer</Description>
</DisplayString>
<!-- additional properties -->

<DisplayString ElementID="Office365.CSPCustomer.Class" SubElementID="CustomerName">
<Name>Customer Name</Name>
<Description></Description>
</DisplayString>
<DisplayString ElementID="Office365.CSPCustomer.Class" SubElementID="URL">
<Name>URL</Name>
<Description></Description>
</DisplayString>
<DisplayString ElementID="Office365.CSPCustomer.Class" SubElementID="CSPID">
<Name>CSPID</Name>
<Description></Description>
</DisplayString>
<DisplayString ElementID="Office365.CSPCustomer.Class" SubElementID="ManagedServices">
<Name>Managed Services</Name>
<Description>All customers services on CSP subscription</Description>
</DisplayString>
<DisplayString ElementID="Office365.CSPCustomer.Class.Discovery">
<Name> CSP customer Discovery</Name>
<Description>Discovers  CSP customer</Description>
</DisplayString>
<DisplayString ElementID="Office365.CSPCustomer.Services.Class.Discovery">
<Name> CSP customer services Discovery</Name>
<Description>Discovers the managed services for each CSP customer</Description>
</DisplayString>
</DisplayStrings>
<KnowledgeArticles></KnowledgeArticles>
</LanguagePack>
</LanguagePacks>
</ManagementPackFragment>

Discovery script

param($sourceId, $managedEntityId, $computerName, $QueryUser, $QueryPwd, $ClientID, $TenantID)

$starttime = get-date

#Create Discovery Property Bag
$api = new-object -comObject 'MOM.ScriptAPI'
$discoveryData = $api.CreateDiscoveryData(0, $SourceId, $ManagedEntityId)
$api.LogScriptEvent("GetCSPCustomers.ps1", 7001, 0, "Discovery Started. Trying to get data from CSP $TenantID as $QueryUser and ClientID: $ClientID")

$tenantId = $TenantID
$client_id = $ClientID
$username = $QueryUser
$password = Convertto-securestring $QueryPwd -asplaintext -force
$creds = New-Object System.Management.Automation.PSCredential ($username, $password)
$PartnerCenterPreFix = "https://api.partnercenter.microsoft.com/v1.0"
$ErrorActionPreference = "stop"

#AUTHENTICATE AGAINST THE PARTNER CENTER
#Using app + user method we get an CSP authentication token
function Get-PCAppUserAuthenticationBearer {
    <#
    .SYNOPSIS
       Function to retrieve App+User bearer token from Microsoft CSP API
    .DESCRIPTION
       This function connects to Azure AD to generate an oAuth token.
       Aquired token is then used against the partner center REST API to generate a App+User jwt token. https://api.partnercenter.microsoft.com/generatetoken

       You can read more about the authentication method here: https://msdn.microsoft.com/en-us/library/partnercenter/mt634709.aspx
    .PARAMETER ClientID
        The ClientID of the application used for authentication against Azure AD.
    .PARAMETER TenantId
        The TenantId of the Azure AD that you wish to authenticate against. Ie: test.onmicrosoft.com
    .PARAMETER Credential
        Pass a Powershell credential object or type in username and password
    .EXAMPLE
        Get-PCAppUserAuthenticationBearer -TenantID https://test.onmicrosoft.com -ClientID <Native App GUID> -username <[email protected]> -password <password>
        Returns a object containing the response from azure ad and a generated CSP bearer. Use the CSP bearer for further authenticating against the CSP API's and AAD token for reference

    .NOTES
        Version 1.0
        Martin Ehrnst
        September 2017
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TenantID,
        [Parameter(Mandatory = $true)]
        [string]$ClientID,
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]$Credential
    )
    #clear error variable
    $error.clear()

    $ErrorActionPreference = "Stop"
    $username = $Credential.UserName
    $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR(($Credential.Password))
    $StringPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)

    #try to access azure ad to generate a token
    try {
        $loginurl = "https://login.windows.net/$tenantId/oauth2/token"

        $params = @{resource = "https://api.partnercenter.microsoft.com"; grant_type = "password"; client_id = $ClientId; username = $username; password = $StringPassword; scope = "openid"}
        $res = Invoke-RestMethod -Uri $loginurl -Method POST -Body $params
        $oAuth = "Bearer " + $res.access_token
    }

    catch {
        write-error -message "$error"
    }

    try {

        $CSPAuthHeader = @{
            "Content-Type"  = "application/x-www-form-urlencoded"
            "Authorization" = $oAuth
        }
        $CspAuthBody = "grant_type=jwt_token"

        $CSPAppUserToken = (Invoke-restmethod -uri 'https://api.partnercenter.microsoft.com/generatetoken' -Method Post -Body $CspAuthBody -Headers $CSPAuthHeader).access_token
    
    }

    catch {
        write-error -message "$error"
    }

    $CspBearer = "Bearer " + $CSPAppUserToken

    $Tokens = @{

        "AzureAd"   = $res
        "CSPBearer" = $CspBearer
    }

    $tokens
}

$Token = (Get-PCAppUserAuthenticationBearer -TenantID $tenantId -ClientID $client_id -Credential $creds -Verbose).CSPBearer

$CSPheaders = @{
    "Authorization" = $Token
    "Content-Type"  = "text/json"
}

#GET ALL CUSTOMERS FROM OUR CSP PORTAL
#Only properties needed are selected.
$customerUri = $PartnerCenterPreFix + "/customers"
try {
    $Customers = (Invoke-RestMethod -Uri $customerUri -Method Get -Headers $CSPheaders).items | Select-Object id, @{N = 'domain'; E = {$_.companyProfile.domain}}, @{N = 'companyName'; E = {$_.companyProfile.companyName}}
}

catch {
    $api.LogScriptEvent("GetCSPCustomers.ps1", 7005, 0, "Failed to retrieve CSP Customers from API $_")
}

#Loop through the customers and add them as instances.
if ($Customers.count -gt "0") {
    $Count = $Customers.count
    $api.LogScriptEvent("GetCSPCustomers.ps1", 7002, 0, "Found $count Customers")
    foreach ($customer in $customers) {
        $Instance = $null
        $CompanyName = $Customer.companyName
        #Create a class instace
        $instance = $discoveryData.CreateClassInstance("$MPElement[Name='Office365.CSPCustomer.Class']$")
        #add class properties
        $instance.AddProperty("$MPElement[Name='Office365.CSPCustomer.Class']/CustomerName$", $CompanyName)
        $instance.AddProperty("$MPElement[Name='Office365.CSPCustomer.Class']/URL$", $customer.domain)
        $instance.AddProperty("$MPElement[Name='Office365.CSPCustomer.Class']/CSPID$", $customer.id)
        #add entity
        $instance.AddProperty("$MPElement[Name='System!System.Entity']/DisplayName$", $CompanyName + " (CSP Customer)")
        $instance.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $computerName) 

        #write data
        $api.LogScriptEvent("GetCSPCustomers.ps1", 7003, 0, "Adding $CompanyName")
        $discoveryData.AddInstance($instance) 
    }
}

#Log script execution time
$EndTime = Get-Date
$ScriptTime = ($EndTime - $StartTime).TotalSeconds

$api.LogScriptEvent("GetCSPCustomers.ps1", 7004, 0, "Discovery Ended - execution time $scripttime")
#Return data
$discoveryData

Hard to tell without the entire workflow (Discovery -> module) but the issue may be that you’ve set the Context of the Run as Profile to a particular class. If your discovery script is targeting anything other than that class, it won’t have access to your creds and will likely fail if the default action account on that agent is Local System.

Typically it’s much more useful to set the context to System!System.Entity, and then let the run as profile -> account binding filter to a particular class if need be.

Also, if the script would work using an alternate windows user, you can configure your Data source using the Run As attribute to just run as a Run As Profile account, which is a bit more secure and also means you don’t need to construct credentials in-script or mess with MP Parameter replacement strings.

Hope that helps!

1 Like

Forgot to update here, but i think i found a solution.

As my SCOM-servers isn\’t aware of the account used to query for the cloud resources you cannot specify a run as profile on the discovery provider as that will cause scom to run the whole workload under that user.

 RunAs=\"_CSP_Office365.CSPUser.RunasProfile\"

Without specifying it will only use the parameters as inputs

<Parameter>
<Name>CSPUsername</Name>
<Value>$RunAs[Name="RunAsProfile.Name"]/UserName$</Value>
</Parameter>
1 Like

Hi Vyper, thanks for your reply. I have added full code to my original post. The script it self can run under any user, but it will need to post a username and password to AzureAD to retrieve access token.
It is for that process I wanted to use a run as account.
I will update to use System.Entity and see if that makes a difference