Automating Run As Account Distribution – Finally!


I wrote a post explaining Run As accounts a while back here:

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 Account display name and Group display name are CASE SENSITIVE! 


The group used should contain WINDOWS COMPUTER objects.


#================================================================================= # Health Service Run As Account by Group Distribution Script # by Kevin Holman # # This script takes a SCOM group of WINDOWS COMPUTERS by name, and distributes a RunAs account # to all Healthservice members of the group including nodes of clusters # # Version 2.1 #================================================================================= # Manual Testing section - put stuff here for manually testing script - typically parameters: #================================================================================= #================================================================================= # Constants section - modify stuff here: #================================================================================= # Assign script name variable for use in event logging $ScriptName = "Example.RunAsDistributor.Rule.ps1" $EventID = "3250" #================================================================================= # Starting Script section - All scripts get this #================================================================================= # Gather the start time of the script $StartTime = Get-Date #Set variable to be used in logging events $whoami = whoami # Load MOMScript API $momapi = New-Object -comObject MOM.ScriptAPI #Log script event that we are starting task $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Script is starting. `n Running as ($whoami).") #================================================================================= # Connect to local SCOM Management Group Section - If required #================================================================================= # I have found this to be the most reliable method to load SCOM modules for scripts running on Management Servers # Clear any previous errors $Error.Clear() # Import the OperationsManager module and connect to the management group $SCOMPowerShellKey = "HKLM:\SOFTWARE\Microsoft\System Center Operations Manager\12\Setup\Powershell\V2" $SCOMModulePath = Join-Path (Get-ItemProperty $SCOMPowerShellKey).InstallDirectory "OperationsManager" Import-module $SCOMModulePath New-DefaultManagementGroupConnection "localhost" IF ($Error) { $momapi.LogScriptEvent($ScriptName,$EventID,1,"`n FATAL ERROR: Unable to load OperationsManager module or unable to connect to Management Server. `n Terminating script. `n Error is: ($Error).") EXIT } #================================================================================= # Begin MAIN script section #================================================================================= # Begin Function 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($ScriptName,$EventID,0,"`n The main function is starting. `n RunAs Account Display Name: ($ActRunAsDisplayName). `n Group Display Name: ($DistGroupName). `n RunAs Account Domain: ($ActRunAsDomain). `n RunAs Account Username: ($ActRunAsUser).") #Choose a group by displayname. #This group contains Windows Computers objects for agent managed computers and for cluster virtual server names. [array]$DistComputerNames = Get-SCOMGroup -DisplayName $DistGroupName | Get-SCOMClassInstance | Select-Object -Property DisplayName $Distcompnamecount = $DistComputerNames.count $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Items in Group is: ($Distcompnamecount) for account: ($ActRunAsDisplayName) and group: ($DistGroupName).") #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($ScriptName,$EventID,1,"`n FATAL ERROR: `n The group was not found or contained no objects. `n Account: ($ActRunAsDisplayName) and Group: ($DistGroupName). `n Terminating script.") EXIT } #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 $} | 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($ScriptName,$EventID,0,"`n RunAs HealthService Distribution Completed. `n Account displayname: ($ActRunAsDisplayName). `n Group displayname: ($DistGroupName). `n Configured ($DistAgentscount) Healthservice objects. `n Previously there were ($ActRunAsDistOldcount) objects configured. `n Unknown Objects identified: ($UnknownObjects). `n Health service objects added: ($DistAgents).") } #================================================================================= #End Function #Get all the health service class instances in the management group. $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Getting all HealthService class instances into an array. `n This may take a long time.") $HealthServiceClass = Get-SCOMClass -DisplayName "Health Service"| Get-SCOMClassInstance $HSCount = $HealthServiceClass.count #Log an event for how long this took and how many healthservice objects were returned $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Completed getting all HealthService class instances into an array. `n Returned ($HSCount) Health Service objects.") # Add multiple "Main" lines for each account and group pair that needs dynamic distribution # THIS IS CASE SENSITIVE!!! # Example: #DistributeRunAsAccounts "Your Custom RunAs Account Display Name" "Your Group Display Name" DistributeRunAsAccounts "Test RunAs Account" "Test RunAs Group" #================================================================================= # End MAIN script section # End of script section #================================================================================= #Log an event for script ending and total execution time. $EndTime = Get-Date $ScriptTime = ($EndTime - $StartTime).TotalSeconds $momapi.LogScriptEvent($ScriptName,$EventID,0,"`n Script Completed. `n Script Runtime: ($ScriptTime) seconds.") #================================================================================= # End of script



  • 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.
  • Version 2.1 – 9/10/2017 – rewrite to add error checking, logging, standardize on my latest script template.


You can download the example MP here:



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:


I am using a “vanilla” profile association:


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:




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)


And in the console I see these accounts distributed:


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. 



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:

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!

Comments (29)

  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. Khaled_Hamad says:

    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

  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.


  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


    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


    Thanks in advance.

  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

  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.


  20. Dominique says:

    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?

  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 SQL Discovery Account…?


  22. Dominique DUCHEMIN says:

    What was the equivalent in SCOM 2007 R2?

    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,

      1. Bruno Gabrielli says:

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

  23. svobodma says:

    Thanks a lot, it solved my problem 🙂

  24. John_Curtiss says:

    in windows server 2016, it seems Microsoft has removed “users” from the default configuration of the “allow log on locally” permission. it’s now only Administrators and Backup Operators. so we have to grant our runas accounts that permission somehow on the servers to which we’re distributing the runas account. is there a best practice on this? or are we assuming that our runas accounts are admins on their respective target servers?

    1. Kevin Holman says:

      It is assumed that runas accounts will be granted whatever rights necessary, to accomplish the workflows that execute under them. This includes local group memberships, or explicit defined access to the registry, perfmon, events, WMI, filesystem objects, etc.

      Log on locally is a requirement for any runas account.

      1. John_Curtiss says:

        I was wrong anyway. It’s exchange that removes the log on locally permission from “users”. I had the same issue on exchange 2010 servers.

Skip to main content