Selectively Setting Default Action Accounts in SCOM

Disclaimer: I have only tested this in SCOM 2012, but it should be possible to work in SCOM 2007 R2 with some alteration of the PowerShell syntax.

Sometimes there is a need for different agents in the same management group to have different default action accounts. This can especially be true when agents are in multiple domains. Organizations may also with to limit the use of default action accounts other than LOCAL SYSTEM to only those agents that need them.

The solution that I found was to add a property to the Health Service class. In a separate management pack I created the following class:

        <ClassType ID="My.Example.MP.HealthService" Accessibility="Public" Abstract="false" Base="SC!Microsoft.SystemCenter.HealthService" Hosted="true" Singleton="false">
          <Property ID="NeedsActionAccount" Type="string" Key="false" CaseSensitive="false" Length="10" MinLength="0" />
        </ClassType>

Then you need to make a discovery that sets the property on your agents. I used a VBScript like this, but you could just as easily use a filtered registry discovery or a PowerShell discovery.

strSourceId = "$MPElement$"
strManagedEntityId = "$Target/Id$"
strTargetComputer = "$Target/Host/Property[Type='Windows!Microsoft.Windows.Computer']/PrincipalName$"

Set objShell = CreateObject("Wscript.Shell")
Set objSystemEnv = objShell.Environment("SYSTEM")

Const HKEY_LOCAL_MACHINE = &H80000002

Set oReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")

FoundService = False

If oReg.EnumKey(HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Service123", arrSubKeys) = 0 Then
  FoundService = True
End If

If oReg.EnumKey(HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Services\ServiceXYZ\Performance", arrSubKeys) = 0 Then
  FoundService = True
End If

Set oAPI = CreateObject("MOM.ScriptAPI")
Set oDiscoveryData = oAPI.CreateDiscoveryData(0, strSourceId, strManagedEntityId)
Set oInst = oDiscoveryData.CreateClassInstance("$MPElement[Name='My.Example.MP.HealthService']$")
call oInst.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", strTargetComputer)

If ( FoundService ) Then
  call oInst.AddProperty("$MPElement[Name='My.Example.MP.HealthService']/NeedsActionAccount$", "Yes")
Else
  call oInst.AddProperty("$MPElement[Name='My.Example.MP.HealthService']/NeedsActionAccount$", "No")
End If

Call oDiscoveryData.AddInstance(oInst)

Call oAPI.Return(oDiscoveryData)

Now the interesting part is the script that actually sets the action accounts in a self-sustaining way. I scheduled the following script to run a few times a day. As you can see above I made the decision about whether or not an action account was needed based on the presence of some installed software rather than depending on a server's hostname. Generally I avoid making any monitoring devision based on hostname whenever possible. This PowerShell script not only sets accounts where they are needed, but returns agents to LOCAL SYSTEM when action accounts are not needed.

Import-Module OperationsManager
New-SCOMManagementGroupConnection -ComputerName: "localhost"

$mg = Get-SCOMManagementGroup
$hsClass = Get-SCOMClass -name "Microsoft.SystemCenter.HealthService"

 

##### LOCAL SYSTEM #####

$Instances = Get-SCOMClassInstance -class $hsClass | WHERE {$_.DisplayName -like '*.lcl' `
                                                       -and $_."[Microsoft.SystemCenter.HealthService].ActionAccountIdentity".ToString() -ne 'SYSTEM' `
                                                       -and $_."[Microsoft.SystemCenter.HealthService].IsAgent".ToString() -eq 'True' `
                                                       -and $_."[My.Example.MP.HealthService].NeedsActionAccount".ToString() -eq 'No'}

If ($Instances -ne $null)
{

  $newAccount = Get-SCOMRunAsAccount -Name "Local System Action Account"

  Foreach ($Instance in $Instances)
  {
    $secureRef = $mg.GetMonitoringSecureDataHealthServiceReferenceByHealthServiceId($Instance.Id)
    $currentAccount = $mg.GetMonitoringSecureData($secureRef[0].MonitoringSecureDataId)
    $secureRef[0].MonitoringSecureDataId = $newAccount.Id
    $secureRef[0].Update()
  }

}

 

##### Domain asdf.lcl #####

$Instances = Get-SCOMClassInstance -class $hsClass | WHERE {$_.DisplayName -like '*.asdf.lcl' `
                                                       -and $_."[Microsoft.SystemCenter.HealthService].ActionAccountIdentity".ToString() -eq 'SYSTEM' `
                                                       -and $_."[Microsoft.SystemCenter.HealthService].IsAgent".ToString() -eq 'True' `
                                                       -and $_."[My.Example.MP.HealthService].NeedsActionAccount".ToString() -eq 'Yes'}

If ($Instances -ne $null)
{

  $newAccount = Get-SCOMRunAsAccount -Name "ASDF\MyAccount"

  Foreach ($Instance in $Instances)
  {
    $secureRef = $mg.GetMonitoringSecureDataHealthServiceReferenceByHealthServiceId($Instance.Id)
    $currentAccount = $mg.GetMonitoringSecureData($secureRef[0].MonitoringSecureDataId)
    $secureRef[0].MonitoringSecureDataId = $newAccount.Id
    $secureRef[0].Update()
  }

}

 

##### Domain qwerty.lcl #####

$Instances = Get-SCOMClassInstance -class $hsClass | WHERE {$_.DisplayName -like '*.qwerty.lcl' `
                                                       -and $_."[Microsoft.SystemCenter.HealthService].ActionAccountIdentity".ToString() -eq 'SYSTEM' `
                                                       -and $_."[Microsoft.SystemCenter.HealthService].IsAgent".ToString() -eq 'True' `
                                                       -and $_."[My.Example.MP.HealthService].NeedsActionAccount".ToString() -eq 'Yes'}

If ($Instances -ne $null)
{

  $newAccount = Get-SCOMRunAsAccount -Name "QWERTY\MyAccount"

  Foreach ($Instance in $Instances)
  {
    $secureRef = $mg.GetMonitoringSecureDataHealthServiceReferenceByHealthServiceId($Instance.Id)
    $currentAccount = $mg.GetMonitoringSecureData($secureRef[0].MonitoringSecureDataId)
    $secureRef[0].MonitoringSecureDataId = $newAccount.Id
    $secureRef[0].Update()
  }

}

The real trick here is the note properties. The property I added to the Health Service class shows up as a note property in powershell. Note properties aren't explicitly typed, so that's why I'm constantly calling the ToString() method.