Using SCO to do a daily check on your RunAs Account Distribution

While working with Kevin Holman, we thought we would combine our two posts on RunAs Account Distribution into a daily runbook that can be run every day, 12 hours,  or whatever you prefer through SCO.  So to acheive this I am going to use script from here and modify it a small bit and take some of my automation so that it will work inside of SCO.

In this post I am going to walk you through how to do achieve this when you have put all your RunAs Accounts and Groups into a .CSV file. 
The runbook will run 2 times a day to verify that you have up to date membership/distribution and don't have to have a scheduled task or anything else running.

THE OVERALL RUNBOOK

1. START TIME ACTIVITY
First we need to build our Monitor Date Time activity.  I want this to run 12 hours so it will look like this(you can run this to your own desires to meet your SLA):

2. VERIFY FILE EXISTANCE OF .CSV FILE
We need to create our .CSV on the RB Server(make sure it is on the server running the runbook and not the designer) :) It will look like this and you're always free to change it up but make sure you change the PowerShell that goes along with this!
Again, I created mine in the C:\SCOM Folder so make sure to update this as well when you do yours.

Once this has been created we need to do a validation in the runbook to make sure that the file exists:

2.1 POTENTIAL ERROR HANDLING
Here you can see that I just added different ways of alerting or notifying that file didn't exist:

3. GET DATA FROM CSV
Next we need to import the .CSV Data and break it into useable data for SCO data bus:

SCRIPT:
$FilePath= 'UPDATE TO POINT TO NAME AND PATH OF FILE FROM PREVIOUS ACTIVITY'
$Data = Import-CSV $FilePath
$Array = @()
ForEach ($Row in $Data)
{
   $AccountName= $Row.'Account'
   $Group = $Row.'SCOMGroup'
    $str = $AccountName +","+ $Group
    $Array += $str+";"
}

PUBLISHED DATA:
Array -> String -> Array

4. DEPLOY/VALIDATE ACCOUNTS
Once we have this we can actually do the account Distribution by breaking it into RunAs Account and associated SCOM Group you wish to use ( script from Kevin's blog modified slightly to pass in the data).

SCRIPT:
Import-Module OperationsManager
New-SCOMManagementGroupConnection YOUR MGMT SERVER

#Get all the health service class instances in the management group
$HealthServiceClass = Get-SCOMclass -DisplayName "Health Service"| Get-SCOMClassInstance

$Details = 'UPDATE TO ARRAY FROM PREVIOUS ACTIVITY'
$Info = $Details.Split(';')
foreach ($Account in $Info)
{
    If (![string]::IsNullOrEmpty($Account))
    {
        $Account = $Account.Trim()       
        $split = $Account.split(',')
        $RunAsDisplayName = $split[0].Trim()
        $DistGroupName = $split[1].Trim()               

        #Log an event that our script is starting
        Write-EventLog –LogName 'Operations Manager' –Source “Health Service Script” –EntryType Information –EventID 3250  –Message “RunAs HealthService Distribution Script Starting for account: ($RunAsDisplayName) and group: ($DistGroupName)”

        #Get the SCOM Run As account by display name
        $RunAs = Get-SCOMRunAsAccount $RunAsDisplayName

        #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

        #Exit script and log error if group returns no valid instances of Windows Computer property displayname
        IF ($DistComputerNames -eq $null)
        {
            #Log an event that our script is ending in error
            Write-EventLog –LogName 'Operations Manager' –Source “Health Service Script” –EntryType Warning –EventID 3252  –Message “RunAs HealthService Distribution Script ended in error.  The group ($DistGroupName) was not found or contained no objects!”
        }
ELSE
        {
            #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 -ne $null)
            {
                #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 $DistAgents -Property 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)
                {
                    #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 -ne $null)
                    {
                        #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

                        #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
            $RunAsDistOld = (Get-SCOMRunAsDistribution $RunAs).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 ($RunAsDistOld.count -ge 1)
            {
                Foreach ($RunAsOld in $RunAsDistOld)
                {
                    IF ($DistAgents.DisplayName -notcontains $RunAsOld.DisplayName)
                    {
                        $DistAgents += $RunAsOld
                    }
                }
            }

            $DistAgents = $DistAgents | sort-object -Property DisplayName -Unique

            #Set (replace) the RunAs distribution list for the defined RunAs account to whatever we found in DistAgents
            Set-SCOMRunAsDistribution ($RunAs) -MoreSecure -SecureDistribution $DistAgents

            #Log event that script is complete, additional variables for logging purposes
            $RunAsName = $RunAs.Name
            $DistAgentsCount = $DistAgents.count
            $RunAsDistOldCount = $RunAsDistOld.count
            If ($UnknownObjects.Count -lt 1)
            {
                $UnknownObjects = 'NONE'
            }

            Write-EventLog –LogName 'Operations Manager' –Source “Health Service Script” –EntryType Information –EventID 3251  –Message “RunAs HealthService Distribution Script Complete. Account: $RunAsName, Configured $DistAgentscount Healthservice objects.  Previously there were $RunAsDistOldcount objects configured.  Unknown Objects identified: $UnknownObjects.  Health service objects added: $DistAgents”
        }
    }
}

PUBLISHED DATA(Now this is not necessary, I just put this here so SCO can show me what is being added/exists):
DistAgents -> String -> DistAgents
RusAsDistOld -> String -> RunAsDistOld

NOW RENAME YOUR RUNBOOK AND OFF YOU GO!!!!

HERE IS SOME OF THE OUTPUT FROM MINE:
You can see that I have removed the SCORCH server from my Orchestrator Account and the RB will automatically added it back in for me.
Removed:

SCO Added:

Runbook executes and output....you can see my output because I have logging turned on (ONLY RECOMMENDED FOR TROUBLESHOOTING PURPOSES AND INITIAL BUILD OF RB....DO NOT LEAVE TURNED ON!)

After execution in SCO RB Server OpsMgr Event Log:

When I have a BAD group it will stop on the Bad Group and log.  Now this will be on the server that the runbook is executing(Just keep that in mind when troubleshooting):

Disclaimer:

This example is provided “AS IS” with no warranty expressed or implied. Run at your own risk. The opinions and views expressed in this blog are those of the author and do not necessarily state or reflect those of Microsoft.

**Always test in your lab first** Do this at your own risk!! The author will not be held responsible for any damage you incur when making these changes!

Please let me know how this goes for you.  Kevin and I would love to hear!

Account.csv