Solving the problem of automated distribution of credentials (group criteria)


It’s possible to leverage a powershell script in a composite write action to distribute credentials to a health service.  The following example will distribute the specified credentials to health services instances derived from a group of windows computer objects.

Instance groups containing other types may not work as expected without modifying the code.  Also, group system name must be used in this script.  I decided to use group system name to foster accountability in the implemention, which reduces mistakes that can easily be made by entering a group display name.

There is no reverse action.  If you specify a group that contains hundreds of windows computer objects and it was a mistake, you’ll have to manually delete hundreds of health service instances from your secure distribution in the Run As Account.

The script accepts the below parameters – all are required.  First set of variables (with a “$”) can be uncommented if executing without parameters, and the second set (with a “-“) is an example of parameter format you would specify if running with parameter names.

# $RootManagementServer = “srvrms01”
# $DatabaseServer = “srvsql01”
# $DatabaseName = “OperationsManager”
# $Group = “Microsoft.SystemCenter.AllComputersGroup”
# $Account = “Test Run As Account”
# $BinariesFolder = “C:\Program Files\System Center Operations Manager 2007\SDK Binaries”
# -RootManagementServer srvrms01
# -DatabaseServer srvsql01
# -DatabaseName OperationsManager
# -Group Microsoft.SystemCenter.AllComputersGroup
# -Account “Test Run As Account”
# -BinariesFolder “f:\Program Files\System Center Operations Manager 2007\SDK Binaries”
# -Debug true

param($RootManagementServer, $DatabaseServer, $DatabaseName, $Group, $Account, $BinariesFolder, $DebugFlag) #Local
 
#-------------------------------------------------------------------------------------Start Main
# This function controls program flow.
function Main
{
    $CollectionHSIDs = CreateCollectionHSIDs
    ValidateSnapIn
    $MG = ConnectToManagementGroup
    $CollectionHSOs = CreateCollectionHSOs
    $NumInstances = $CollectionHSOs.Count
    LoadHelperClass
    AddHealthServiceToAccountDistributionList
    Remove-ManagementGroupConnection $RootManagementServer
    QuitScript $QuitSuccess "Workflow completed successfully."
}#------------------------------------------------------------------------------------End Main
 
#-------------------------------------------------------------------------------------Start AddHealthServiceToAccountDistributionList
# This function adds health service instances to the Run As Account distribution list.
function AddHealthServiceToAccountDistributionList
{
    # Add health service objects to Run As Account distribution list.
    $data = $MG.ManagementGroup.GetMonitoringSecureData() | Where-Object {$_.Name -eq $Account}
    [Helper]::ApproveCredentialForDistribution($MG.ManagementGroup, $data, $CollectionHSOs)
    
    if ($DebugFlag) {DebugEvent "Sequence 6 - Completed function AddHealthServiceToAccountDistributionList"}
}#------------------------------------------------------------------------------------End AddHealthServiceToAccountDistributionList
 
#-------------------------------------------------------------------------------------Start CreateCollectionHSIDs
# This function queries the operational database for members of the specified windows 
# computer group and resolves the health service id of those instances. Then adds
# those health service id's to a collection.
# The use of system.data.sqlclient.sqlconnection requires Windows Server 2003 SP2+,
# Windows Server 2008 or Windows Server 2008 R2 (except core editions).
function CreateCollectionHSIDs
{
    # Setup the SQL connection
    $connection = new-object system.data.sqlclient.sqlconnection("Data Source=$DatabaseServer;Initial Catalog=$DatabaseName;Integrated Security=SSPI;");
    
    # Define TSQL
    [string]$query =
                "
                /*
                Get Health Service IDs
                Based on computer group (must be WC instances)
                */
                SELECT ME.Id AS 'HSID'
                from ManagedEntityGenericView AS ME INNER JOIN
                        ManagedTypeView AS MT ON ME.MonitoringClassId = MT.Id INNER JOIN
                        RelationshipGenericView AS REL ON ME.Path = REL.TargetMonitoringObjectDisplayName
                where REL.SourceMonitoringObjectFullName = '$Group' AND
                        MT.Name = 'Microsoft.SystemCenter.HealthService' AND
                        ME.IsDeleted <> 1
                "
    
    # Create the dataset
    $execute = new-object system.data.sqlclient.sqldataadapter ($query, $connection)
    $dataset = new-object system.data.dataset
    $execute.Fill($dataset) | Out-Null #speeds processing
 
    # Create an empty collection
    [System.Collections.ArrayList]$CollectionHSIDs = @()
    
    # Add each row in dataset to the collection
    foreach ($row in $dataset.Tables[0])
        {
        $CollectionHSIDs.Add($row) | Out-Null #speeds processing
        }
    
    if ($DebugFlag) {DebugEvent "Sequence 1 - Completed function CreateCollectionHSIDs"}
    
    # Pass collection to main script.
    return $CollectionHSIDs
}#------------------------------------------------------------------------------------End CreateCollectionHSIDs
 
#-------------------------------------------------------------------------------------Start CreateCollectionHSOs
# This function adds health service objects to a collection.
function CreateCollectionHSOs
{
    # Create an empty collection.
    [System.Collections.ArrayList]$CollectionHSOs = @()
    
    # Add each Health Service instance to the collection.
    foreach ($HSID in $CollectionHSIDs)
        {
        $CollectionHSOs.Add((Get-MonitoringObject -Id $HSID.HSID.ToString())) | Out-Null #speeds processing
        }
    
    if ($DebugFlag) {DebugEvent "Sequence 4 - Completed function CreateCollectionHSOs"}
    
    # Pass collection to main script.
    return $CollectionHSOs
}#------------------------------------------------------------------------------------End CreateCollectionHSOs
 
#-------------------------------------------------------------------------------------Start LoadHelperClass
# This function is used to test and load the helper class that enables distribution.
function LoadHelperClass
{
    # Create variable to test whether type is already added.
    $b=$false
    $b = Get-Type ([helper])
    trap [exception] {continue} #always throws exception if class isn't added.
    
    # Add Type Definition for helper class if not already added
    if (!$b)
        {
        Add-Type –typedefinition $src -ReferencedAssemblies ($BinariesFolder + "\Microsoft.EnterpriseManagement.OperationsManager.dll"), 'System.Xml'
        
        # Create variable to test whether type is already added.
        $b = Get-Type ([helper])
        trap [exception] {continue} #always throws exception if class isn't added.
        
        if (!$b)
            {        
            QuitScript $QuitFailure "Error in function LoadHelperClass: Unable to load helper class."
            }
        }
    
    if ($DebugFlag) {DebugEvent "Sequence 5 - Completed function LoadHelperClass"}
    
    # Recycle variable
    Remove-Variable b
}#------------------------------------------------------------------------------------End LoadHelperClass
 
#-------------------------------------------------------------------------------------Start AddAccountToProfile
# This function associates Run As Account to Run As Profile.
function AddAccountToProfile
{
    # This function not being used at this time.
}#------------------------------------------------------------------------------------End AddAccountToProfile
 
#-------------------------------------------------------------------------------------Start ValidateSnapIn
# This function is used to test and load the OM snapin.
function ValidateSnapIn
{
    # Create a variable to hold OM snapin
    $b=$false
    $b = Get-PSSnapin | Where-Object {$_.Name -like "*Microsoft.EnterpriseManagement.OperationsManager.Client*"}
    
    # Test variable.  If empty, add OM snapin.
    if (!$b)
        {
        Add-PSSnapin -Name "Microsoft.EnterpriseManagement.OperationsManager.Client" 
        
        # Refresh variable after snapin load attempt.
        $b = Get-PSSnapin | Where-Object {$_.Name -like "*Microsoft.EnterpriseManagement.OperationsManager.Client*"}
        
        # If snapin failed to load, quit script - cannot move forward.
        if (!$b)
            {
            QuitScript $QuitFailure "Error in function ValidateSnapIn: Unable to load OM snapin."
            }
        }
    
    if ($DebugFlag) {DebugEvent "Sequence 2 - Completed function ValidateSnapin"}
    
    # Recycle variable
    Remove-Variable -Name b
}#------------------------------------------------------------------------------------End ValidateSnapIn
 
#-------------------------------------------------------------------------------------Start ConnectToManagementGroup
# This function is used to connect to the root management server and set location.
function ConnectToManagementGroup
{
    # Create variable to hold connection.
    $b = (Get-ManagementGroupConnection).ManagementServerName
    
    # Test variable.  If empty, attempt to create the connection.
    if ($b -ne $RootManagementServer)
        {
        $NewConnection = New-ManagementGroupConnection -ConnectionString:$RootManagementServer
        
        # Refresh variable after connection attempt.
        $b = (Get-ManagementGroupConnection).ManagementServerName
        
        # If still unable to connect, quit script - cannot move forward.
        if ($b -ne $RootManagementServer)
            {
            QuitScript $QuitFailure "Error in function ConnectToManagementGroup: Unable to establish connection to $RootManagementServer."
            }
        # Now that we have a connection, set location to OM monitoring drive.
        Set-Location "OperationsManagerMonitoring::"
        }
    else
        {
        # Perhaps connection already existed, so we'll just move on.
        $NewConnection = Get-ManagementGroupConnection
        Set-Location "OperationsManagerMonitoring::"
        }
    
    if ($DebugFlag) {DebugEvent "Sequence 3 - Completed function ConnectToManagementGroup"}
    
    # Recycle variable
    Remove-Variable -Name b
    return $NewConnection
}#------------------------------------------------------------------------------------End ConnectToManagementGroup
 
#-------------------------------------------------------------------------------------Start QuitScript
# This function is used to set error levels and return details.
function QuitScript ($Level, $Detail)
{
    if ($Level -eq 1)
        {
        $Message = "$Detail Account distribution failed for $Account."
        Write-EventLog -LogName Application -Source OM-SecureDist -EventID 100 -EntryType Error -Message $Message
        exit
        }
    else
        {
        $Message = "$Detail Distribution occurred for $Account for $NumInstances health service instance(s)."
        Write-EventLog -LogName Application -Source OM-SecureDist -EventID 100 -EntryType Information -Message $Message
        exit
        }
}#------------------------------------------------------------------------------------End QuitScript
 
#-------------------------------------------------------------------------------------Start DebugEvent
# This function is used to write debug events.
function DebugEvent ($Detail)
{
    $Message = "$Account : $Detail"
    Write-EventLog -LogName Application -Source OM-SecureDist -EventID 101 -EntryType Information -Message $Message
}#------------------------------------------------------------------------------------End DebugEvent
 
#-------------------------------------------------------------------------------------Start Get-Type
# This function is used to test whether the helper class has been added.
# Successfully detects when type is added, but throws exception when it's not.
# http://solutionizing.net/2009/01/01/powershell-get-type-simplified-generics/
function global:Get-Type ($type = $(Throw "Please specify a type"))
{
    trap [System.Management.Automation.RuntimeException] { Throw ($_.Exception.Message) }
    
    if ($Args -and $Args.Count -gt 0)
        {
        $types = $Args[0] -as [type[]]
        if (-not $types)
            {
            $types = [type[]] $Args
            }
        if ($genericType = [type] ($type + '`' + $types.Count))
            {
            $genericType.MakeGenericType($types)
            }
        }
    else
        {
        [type] $type
        }
}#------------------------------------------------------------------------------------End Get-Type
 
#-------------------------------------------------------------------------------------Start variables
# Set variables
$QuitFailure = 1
$QuitSuccess = 0
 
# Source for helper class
$src = @"
using Microsoft.EnterpriseManagement;
using Microsoft.EnterpriseManagement.Monitoring;
using System.Collections.Generic;
public class Helper {
    public static void ApproveCredentialForDistribution(ManagementGroup mg, ISecuredData credential, MonitoringObject[] targets) {
        mg.ApproveCredentialForDistribution<MonitoringObject>(credential, new List<MonitoringObject>(targets));
    }
}
"@
 
# Set Debug Flag
if ($DebugFlag -and $DebugFlag -ne "true")
    {
    Remove-Variable DebugFlag
    }
 
#-------------------------------------------------------------------------------------End variables
 
#-------------------------------------------------------------------------------------Start EventLogSourceCheck
# Check if the OM-SecureDist source has been added to the Operations Manager log.
$b=$false
$b=[system.diagnostics.eventlog]::SourceExists("OM-SecureDist")
trap [Exception] {continue}
if (!$b)
    {
    # Create the source if it doesn't exist.
    # Requires elevated privileges.
    New-EventLog -Source OM-SecureDist -LogName Application
    }
# Recycle variable
Remove-Variable b
#-------------------------------------------------------------------------------------End EventLogSourceCheck
 
#-------------------------------------------------------------------------------------Start variables for local runtime
# $RootManagementServer = "srvrms01"
# $DatabaseServer = "srvsql01"
# $DatabaseName = "OperationsManager"
# $Group = "Microsoft.SystemCenter.AllComputersGroup"
# $Account = "Test Run As Account"
# $BinariesFolder = "C:\Program Files\System Center Operations Manager 2007\SDK Binaries"
# -RootManagementServer srvrms01
# -DatabaseServer srvsql01
# -DatabaseName OperationsManager
# -Group Microsoft.SystemCenter.AllComputersGroup
# -Account "Test Run As Account"
# -BinariesFolder "f:\Program Files\System Center Operations Manager 2007\SDK Binaries"
# -Debug true
#-------------------------------------------------------------------------------------End variables for local runtime
 
#-------------------------------------------------------------------------------------Start main program
Main

Always test scripts you find on the internet before implementing in production.

Also see the other one if you want to use literal expression.

Comments (1)

  1. Anonymous says:

    This is a continuation of a previous post .  The only difference is this accepts a literal expression

Skip to main content