Automating Run As Account Distribution – Finally!


 

I wrote a post explaining Run As accounts a while back here:  http://blogs.technet.com/b/kevinholman/archive/2010/09/08/configuring-run-as-accounts-and-profiles-in-r2-a-sql-management-pack-example.aspx

One of the biggest pain points in distributing RunAs accounts, is managing the Health Service distribution of the credential.  When you associate a RunAs profile to an account, this tells agents that are covered by the association, to try and use the RunAs account for any workflows where it is called for.  However, this will fail if you haven’t distributed the account first, and will result in Alerts being generated like the following:

 

System Center Management Health Service Credentials Not Found Alert Message

An account specified in the Run As profile “Microsoft.SQLServer.SQLProbeAccount” cannot be resolved.

 

These are important alerts because they tell you when a healthservice has been associated in a profile, but is missing the credential distribution.

Previously – you had to manually find these accounts, and manually distribute the credential to the healthservice.  This was a maintenance intensive process.

Over the years, there have been posted a few examples to blogs about how to automate this.  Most of them dealt with getting these alerts, then responding with a script to distribute the RunAs account to the MonitoringObject that the alert was generated from.

I never liked this approach, because it was error prone to me.  It is possible you made a mistake in your profile association, and over-scoped the computers which should try and load the RunAs account.  Using alerts also mean that you’d never see which servers got a distribution, so you would not be aware of a misconfiguration.  Additionally, you might see a large number of “Log on Locally” failed alerts from any agent where the credential was distributed, but the credential doesn’t have rights.

Lastly – my customers often need to customize where the account would be distributed.  Perhaps, in the example of SQL, the IT DBA team only supported a portion of the total SQL servers discovered by SCOM.  Or perhaps they needed multiple different RunAs accounts for different SQL servers.  They would likely create groups of Windows Computer objects to represent these different SQL servers….. so what I often propose is to use the GROUP to determine RunAs account distribution.

This group methodology allows for a completely hands-free configuration of your RunAs account distribution, and you can delegate the group membership to the respective teams, perhaps by using a Registry key for them to determine group membership.

So that brings us to using groups instead of alerts.  There are actually a few blog posts out there on how to do this, however I always found them to be good base example scripts, but lacking in features needed.  For instance, what if our group contained a Cluster virtual server, such as the “SQL Computers” group often will…. we need to distribute the RunAs credential to the NODE health services, not the virtual.  The script needs to account for that.

Additionally, many of the scripts will simply REPLACE the RunAs distribution when the run.  However, there will almost always be one-off scenarios where you need to quickly add a healthservice to the distribution, even though it might now reside in the core group.  Therefore the script should provide for a way to gather the existing list of distributed health services, and only add news ones where necessary.

With the collaboration of several Microsoft support PFE’s (Matt Taylor, Scott Murray, Mark Manty, Tim McFadden, and Russ Slaten) here is a script that performs this task.

The PowerShell script has a function, and the only thing you will need to customize is at the end of the script, just modify the line calling the function DistributeRunAsAccounts with your RunAs account name and Group name.  If you have multiple accounts to distribute, just add another line and call the function again as you see in my example. 

 

The group used should contain WINDOWS COMPUTER objects.

 

# Health Service Run As Account by Group Distribution Script # by Kevin Holman # Collaboration by Matt Taylor, Scott Murray, Mark Manty, Tim McFadden, Russ Slaten # # This script takes a SCOM group by name, and distributes a RunAs account # to all members of the group including nodes of clusters # # Version 2.0 Function DistributeRunAsAccounts ($ActRunAsDisplayName, $DistGroupName) { #Get the SCOM Run As account by display name $ActRunAs = Get-SCOMRunAsAccount $ActRunAsDisplayName $ActRunAsDomain = $ActRunAs.Domain $ActRunAsUser = $ActRunAs.UserName $momapi.LogScriptEvent("RunAsHSDist.ps1",3252,0,"Debug: The main function is starting for account display name: ($ActRunAsDisplayName) which resolved to: ($ActRunAsDomain\$ActRunAsUser) and group display name: ($DistGroupName) executed as ($WhoamI)") #Choose a group by displayname. #This group contains Windows Computers objects for agent managed computers and for cluster virtual server names. $DistComputerNames = Get-SCOMGroup -DisplayName $DistGroupName | Get-SCOMClassInstance | Select-Object -Property DisplayName $Distcompnamecount = $DistComputerNames.count $momapi.LogScriptEvent("RunAsHSDist.ps1",3253,0,"Debug: Items in Group equal: ($Distcompnamecount) for account: ($ActRunAsName) and group: ($DistGroupName) executed as ($WhoamI)") #Exit script and log error if group returns no valid instances of Windows Computer property displayname IF ($DistComputerNames.count -lt 1) { #Log an event that our script is ending in error $momapi.LogScriptEvent("RunAsHSDist.ps1",3254,2,"RunAs HealthService Distribution Script ended in error. The group was not found or contained no objects. for account: ($ActRunAsName) and group: ($DistGroupName) executed as ($WhoamI)") Break } #Set DistAgents to empty $DistAgents = @() #Get an array of Health Service Objects which match the displayname of the group membership objects Foreach ($DistComputerName in $DistComputerNames) { $DistAgents += $HealthServiceClass | where {$_.DisplayName -eq $DistComputerName.DisplayName} } IF ($DistAgents.count -gt 0) { #Compare lists (diff) to get a list of all Computers in the group that do not have a matching HealthService. #Assume these are Network Name values for clustered resource groups $DistClusters = Compare-Object $DistComputerNames.DisplayName $DistAgents.DisplayName -PassThru } ELSE { #Assume that the group only contained Windows Cluster objects and no agents. $DistClusters = $DistComputerNames } # If there are no clusters in the group skip the cluster node section of the script IF ($DistClusters.count -ge 1) { #Set UknownObject array equal to null to ensure it is empty from any previous script runs $UnknownObjects = @() #Get the relationship ID we need to find nodes that host a cluster $rid = Get-SCOMRelationship -DisplayName 'Health Service manages Entity' #Get all Cluster virtual server class instances in an array $vsclass = Get-SCOMClass -name 'Microsoft.Windows.Cluster.VirtualServer' | Get-scomclassinstance #Create a loop to find the node names of each cluster and add those names to an array foreach ($clname in $DistClusters) { #write-host 'clname: '$clname #Get the Virtual Server class instance for each cluster name $vs = $vsclass | where-object {$_.DisplayName -eq $clname} #Continue with the script if we got a match for a virtual server class IF ($vs.count -gt 0) { #write-host 'virtual server: '$vs.DisplayName #Get the nodes in an array which have a health service relationship managing the virtual server cluster name $nodes = Get-SCOMRelationshipInstance -TargetInstance $vs | Where-Object {$_.relationshipid -eq $rid.id} | Select-Object -Property SourceObject #write-host 'nodes: '$nodes.sourceobject #Get an array of SCOM Agent objects which match the displayname of objects in the nodenames array and check first to ensure no duplicates Foreach ($node in $nodes) { If ($DistAgents -notcontains $node.SourceObject) { $DistAgents += $HealthServiceClass | where {$_.DisplayName -eq $node.SourceObject} } } } #If objects in the group do not match the displayname for a virtual server class nor a healthservice displayname add them to unknown objects array ELSE { $UnknownObjects += $clname } } } #Get the current RunAs account distribution as it exists today and save it in an array #Comment out this entire section if you want to ignore the current distribution and ONLY go with what it in the group $ActRunAsDistOld = (Get-SCOMRunAsDistribution $ActRunAs).securedistribution #Check and see if there is at least one health service distribution defined - ignore if none found #If at least one distribution is found, check for duplicates then add non dupes to the DistAgents array IF ($ActRunAsDistOld.count -ge 1) { Foreach ($ActRunAsOld in $ActRunAsDistOld) { IF ($DistAgents.DisplayName -notcontains $ActRunAsOld.DisplayName) { $DistAgents += $ActRunAsOld } } } #Set (replace) the RunAs distribution list for the defined RunAs account to whatever we found in DistAgents Set-SCOMRunAsDistribution ($ActRunAs) -MoreSecure -SecureDistribution $DistAgents #Log event that script is complete, additional variables for logging purposes $DistAgentsCount = $DistAgents.count $ActRunAsDistOldCount = $ActRunAsDistOld.count If ($UnknownObjects.Count -lt 1) { $UnknownObjects = 'NONE' } $momapi.LogScriptEvent("RunAsHSDist.ps1",3255,0,"RunAs HealthService Distribution Script Complete. Resolved Account name: $ActRunAsName, Configured $DistAgentscount Healthservice objects. Previously there were $ActRunAsDistOldcount objects configured. Unknown Objects identified: $UnknownObjects. Health service objects added: $DistAgents. For account displayname: ($ActRunAsDisplayName) and group displayname: ($DistGroupName) executed as ($WhoamI)") } #End Function #Begin Main Body of Script $StartTime = Get-Date $SCOMServer = "localhost" $WhoamI = whoami #Load the OM API $momapi = New-Object -comObject "MOM.ScriptAPI" #Log an event that our script is starting $momapi.LogScriptEvent("RunAsHSDist.ps1",3250,0,"RunAs HealthService Distribution Script Starting. Executed as ($WhoamI)") #Import OM Module Import-Module OperationsManager; New-SCOMManagementGroupConnection $SCOMServer #Get all the health service class instances in the management group. This may take a long time $HSStartTime = Get-Date $HealthServiceClass = Get-SCOMclass -DisplayName "Health Service"| Get-SCOMClassInstance $HSCount = $HealthServiceClass.count $HSEndTime = Get-Date $HSTime = ($HSEndTime - $HSStartTime).TotalSeconds #Log an event for how long this took and how many healthservice objects were returned $momapi.LogScriptEvent("RunAsHSDist.ps1",3251,0,"Debug: Returned ($HSCount) health service objects in ($HStime) seconds. Executed as ($WhoamI)") #Add multiple "Main" lines for each account and group pair that needs dynamic distribution DistributeRunAsAccounts "SQL Monitoring RunAs Account" "SQL Server Computers" #DistributeRunAsAccounts "SQL Monitoring RunAs Account DMZ Domain" "DMZ Computers" #DistributeRunAsAccounts "Your Custom RunAs Account" "Your Group" $EndTime = Get-Date $ScriptTime = ($EndTime - $StartTime).TotalSeconds #Log an event for script ending and total execution time. $momapi.LogScriptEvent("RunAsHSDist.ps1",3260,0,"Debug: Script Ending. Total runtime ($ScriptTime) seconds. Executed as ($WhoamI)") #End Script

 

 

Notes:

  • Version 1.3 script updated to add error handling of null value arrays when an object in the group doesn’t match any virtual clusters, and added collection of these object names in the final script as unknown objects
  • Version 1.4 script updated to add error handling when a group contains NO windows computers, or when a group contains only cluster objects and no corresponding agents
  • Version 1.5 – additional error handling
  • Version 1.6 – 10/26/15 – edited $RunAs out of script to be able to use this in a MP.  $RunAs is a reserved variable in SCOM.
  • Version 2.0 – 4/3/2016 – significant rewrite to place the main execution in a function, and call it multiple times for multiple accounts, and added better debug logging.

 

 

 

This PowerShell script can be executed by Orchestrator, Task Scheduler, or even SCOM in a Management Pack.

I will show an example of using this for the SQL Run As account.  My current RunAs account has not been distributed:

image

I am using a “vanilla” profile association:

image

This results in alerts from all my SQL servers where a SQL Role was discovered, and where they try to load a discovery or monitoring workflow that leverages a Runas Account:

 

image

 

When I execute the script – I see the following events in the OpsMgr event log:

 

Event ID:      3250
Description:  RunAsHSDist.ps1 : RunAs HealthService Distribution Script Starting.  Executed as (domain\account)

Event ID:      3251
Description: RunAsHSDist.ps1 : Debug:  Returned (36) health service objects in (0.2031229) seconds.   Executed as (domain\account)

Event ID:      3252
Description: RunAsHSDist.ps1 : Debug: The main function is starting for account display name: (SQL Monitoring RunAs Account OpsMgr Domain) which resolved to: (DOMAIN\RunAsAccount) and group display name: (SQL Server Computers) executed as (domain\account)

Event ID:      3253
Description: RunAsHSDist.ps1 : Debug: Items in Group equal: (9) for account: (SQL Monitoring RunAs Account OpsMgr Domain) and group: (SQL Server Computers) executed as (domain\account)

Event ID:      3255
Description: RunAsHSDist.ps1 : RunAs HealthService Distribution Script Complete. Resolved Account name: SQL Monitoring RunAs Account OpsMgr Domain, Configured 10 Healthservice objects.  Previously there were 10 objects configured.  Unknown Objects identified: NONE.  Health service objects added: SQL1.domain.com SQL2.domain.com SQL3.domain.com SQL4.domain.com NODE1.domain.com NODE2.domain.com  NODE3.domain.com  NODE4.domain.com  NODE5.domain.com.  For account displayname: (SQL Monitoring RunAs Account OpsMgr Domain) and group displayname: (SQL Server Computers) executed as (domain\account)

Event ID:      3260
Description: RunAsHSDist.ps1 : Debug:  Script Ending.  Total runtime (1.7187875) seconds.   Executed as (domain\account)

 

 

And in the console I see these accounts distributed:

image

And the previous alerts auto-closed since they came from a Monitor which auto-resolved them when it went back to healthy.

 

I welcome your feedback if you have any issues.  Stay tuned for additional links for how to use this and other script examples in Orchestrator.

 

image

This is very cool!  As an alternative to this script – Matt Taylor has blogged about how to create a Orchestrator RunBook and automate the health service RunAs distribution based on alerts that get generated.  Keep in mind – that assumes your profile associations are 100% correct and any agent generating the alert actually does need the credential distributed.  Check it out here:

http://blogs.technet.com/b/scom_atlas/archive/2015/05/22/using-scorch-to-auto-deploy-scom-runas-accounts.aspx

Then – he created another runbook which will let you take a CSV file as the source for your groups and accounts by name, and use the method in this blog post to auto-distribute the creds using Orchestrator.  It is a FANTASTIC example of the power of Orchestrator and SCOM together!

http://blogs.technet.com/b/scom_atlas/archive/2015/05/22/using-sco-to-do-a-daily-check-on-your-runas-account-distribution.aspx


Comments (25)

  1. Kevin Holman says:

    This post is new Naeblis – give them time. This will be a huge timesaver for people, for sure!

  2. Kevin Holman says:

    @Khaled –

    The only way that can happen is if you used a group that contained no objects, or you misspelled the displayname of the group.

  3. Kevin Holman says:

    @Khaled – make sure you are using a group that contains ONLY Windows Computer objects.

  4. Thanks a lot. It worked, but there is an error appeared in powershell (I’m beginner in powershell)

    Compare-Object : Cannot bind argument to parameter ‘ReferenceObject’ because it is null.
    At C:Automating_RunAs_Account_Distribution.ps1:42 char:31
    + $DistClusters = Compare-Object <<<< $DistComputerNames.DisplayName $DistAgents.DisplayName -PassThru
    + CategoryInfo : InvalidData: (:) [Compare-Object], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.CompareObje
    ctCommand

  5. Naeblis says:

    Why people are not jumping for joy is beyond me. this is great I will test this out. Thank you Kevin, Tim, and crew for this great tool.

  6. Marnix Wolf says:

    Will certainly cross post it 🙂

  7. Kevin Greene says:

    Awesome work guys – this will definitely save time on deployments and ongoing admin 🙂

  8. Anon says:

    Sportscenter would call this is a "WebGem"

  9. Hjortby says:

    great work! Will be put into use here 🙂

  10. Anonymous says:

    RunAs accounts can be very cumbersome and annoying if they have not been deployed correctly! I think

  11. Anonymous says:

    While working with Kevin Holman, we thought we would combine our two posts on RunAs Account Distribution

  12. MedeBay says:

    This is exactly what we needed to be able to monitor SQL 2012 and 2014. We already have SQL groups populated from a CMDB, now we can use those same dynamic groups to distribute their run as account to.

    But I do have one questioncommentrequest….. It appears that if a server is removed from the group specified in the script it does not get removed from the account distribution list. Any possibility of modifying the script to remove items not currently in
    the group?

  13. Kevin Holman says:

    @MedeBay –

    I already covered that in the script – see the section:

    #Get the current RunAs account distribution as it exists today and save it in an array
    #Comment out this entire section if you want to ignore the current distribution and ONLY go with what it in the group

    All you need to do is comment out the section where I gather the existing distribution membership, then it will always replace.

  14. Kevin Holman says:

    I tested this with a customer today with 735 SQL servers and a healthy mix of multi-node clusters. Worked flawlessly.

  15. Keithk2 says:

    Thanks you for the effort on this script. Unfortunately for me it isn’t working out so well. I am keeping things pretty vanilla. I have a simple group called "SQL Computers" with three explicitly added Windows computer objects. I also have a simple run
    as account name called "SQL monitoring account". When I run the script (on the RMSe) with the updated references it just doesn’t work. When I look at the Ops log from where I ran the script I see an event 3250:

    "RunAsHSDist.ps1 : RunAs HealthService Distribution Script Starting for account: (SQL Monitoring Account) and group: (SQL Computers) "

    followed by an event 3252:

    "RunAsHSDist.ps1 : RunAs HealthService Distribution Script ended in error. The group was not found or contained no objects".

    I have tried every scenario to get this working recreating everything, but same result. Any suggestions would be appreciated.

    Keith

  16. Keith says:

    Ok I was able to resolve. Apparently the group I was using contained other objects besides windows computer objects. Thanks!

  17. Marlin says:

    Hi Kevin.

    What would this Script look like for SCOM 2007 R2. I made changes from the 2012 to 2007 but keep receiving errors:

    Get-RunAsAccount : Cannot bind positional parameters because no names were given.
    At C:scriptsAutomating Run As Account Distribution.ps1:31 char:10
    + $RunAs = Get-RunAsAccount $RunAsDisplayName
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidArgument: (:) [Get-RunAsAccount], ParameterBindingException
    + FullyQualifiedErrorId : AmbiguousPositionalParameterNoName,Microsoft.EnterpriseManagement.OperationsManager.ClientShell.GetRunAsAccou

    ntCmdlet

    Get-MonitoringObjectGroup : A parameter cannot be found that matches parameter name ‘DisplayName’.
    At C:scriptsAutomating Run As Account Distribution.ps1:35 char:48
    + $DistComputerNames = Get-MonitoringObjectGroup -DisplayName $DistGroupName | Get …
    + ~~~~~~~~~~~~
    + CategoryInfo : InvalidArgument: (:) [Get-MonitoringObjectGroup], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.EnterpriseManagement.OperationsManager.ClientShell.GetMonitoringObjectGroupC

    mdlet

    Thanks in advance.
    Marlin

  18. Pallavi says:

    Hi Kevin,

    Is it possible to append servers to an already existing Run-as-account and not disturb the current Run-as-account group. We already have a Run-as-account group having 1000+ objects. Our requirement is to distribute future SQL servers coming to SCOM.

    Any help is higly apppreciated..!!

    Thanks in Advance
    Pallavi

  19. Kevin Holman says:

    @Pallavi –

    I think you missed reading this part in my post:

    "Additionally, many of the scripts will simply REPLACE the RunAs distribution when the run. However, there will almost always be one-off scenarios where you need to quickly add a healthservice to the distribution, even though it might now reside in the core
    group. Therefore the script should provide for a way to gather the existing list of distributed health services, and only add news ones where necessary."

    My script does indeed append – it does not replace. Of course, you should test this first and get a backup of the membership via PowerShell just in case you have a problem running the script if it doesn’t behave correctly.

    1. varalakshmi says:

      Hi Kevin,

      Thanks a lot for this.In our environment we are using single SCOM platform for two domains(CATE&DEV).Recent we have installed SQL 2014 MP and we created two run as accounts one for CATE domain and one for DEV domain.Am in a confusion how to distribute these run as accounts to CATE and DEV servers.Since SQL 2014 Computers group contains both CATE&DEV SQL servers when am mapping run as account in profile am getting error.How to create a groups to separate CATE SQL server and DEV sql server??If i create a two groups manually how about newly added SQL servers?It will be a additional work for me always to add servers manually.
      Kindly suggest us how to proceed for this.

      Thanks
      Varalakshmi

  20. Dominique says:

    Hello,
    Great blog again thanks Kevin!
    In my case it is a little bit different as the Profile (SQL) was distributed with "less secure" first then now I use "More Secure" and all servers with no SQL are getting the alert as the MP(Rules for discovery and monitoring) were distributed previously and
    could not find the profiles/accounts anymore due to the strengthening of the security..
    Any idea how to clean all these alerts? Is it automatic the next time the agent will check for MPs?
    Thanks,
    Dom

  21. Dominique says:

    Hello Kevin,

    Excellent article to add Run As to new servers. Thanks,
    How to remove the RunAs Account from servers which do not need it?
    e.g.: The Run As Account Distribution is changed from "Less Secure" where it was distributed to all servers then now it is "More Secure" and the distribution is limited to few servers.
    How could I handle this as apparently the RunAs account still exist on All Servers even not listed in the New Distribution list of the RunAs Account…
    It is the SQL MP 6.6.4.0 SQL Discovery Account…?

    Thanks,
    Dom

  22. Dominique DUCHEMIN says:

    Hello,
    What was the equivalent in SCOM 2007 R2?
    Thanks,
    Dom

    1. Bruno Gabrielli says:

      Hi Kevin,
      good to see another great piece of content from you. Definitely “I am with the Kevin Holman”. I have one issue with the script. The instruction at line 107 ($DistAgents += $ActRunAsOld;) is failing with this output
      Method invocation failed because [Microsoft.EnterpriseManagement.Monitoring.MonitoringObject] doesn’t contain a method named ‘op_Addition’.
      At line:8 char:17
      + $DistAgents += $ActRunAsOld;
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : InvalidOperation: (op_Addition:String) [], RuntimeException
      + FullyQualifiedErrorId : MethodNotFound

      Any idea why and how to resolve it?

      Thanks in advance,
      Bruno.

      1. Bruno Gabrielli says:

        Nm, I found out it was due to a bad copy/paste …

Skip to main content