We where talking to a customer about how to avoid waiting for Active Directory group synchronization to occur and place a device in the correct collections faster than “until the next synchronization”.

The main problem with this setup was caused by the fact that they used a group-in-group membership to identify collection memberships and apparently SCCM 2012 don’t include indirect changes to group membership as delta changes (I have not tested this in details yet).

So we came up with the idea to just create a direct membership to place the device in the collections instantly to make sure that it was there when the first policy is loaded on the client.

While that would work for cutting the synchronization time down to near nothing, it creates a new problem, any changes made to the group membership would not affect the collection correctly as a member would not be removed if a direct membership is still in place. So to fix this we need a process to clean up this mess.

The solution was fairly simple to do using a PowerShell script

Please note that the script is meant to run on the server using current credentials. The script was tested on SCCM 2012 SP1 and with PowerShell 3.0 and is provided as is, any comments and input are welcome.

Now let’s start coding …

 

First let’s create a function to and make the script simple to call using a few arguments.

PARAM(
    [Parameter(Mandatory=$True,HelpMessage="Please Enter Site Server Site code")]
        $SiteCode,
    [Parameter(Mandatory=$True,HelpMessage="Please Enter CollectionID")]
        $CollectionID
     )

function CleanupDirectMembershipsInCollection($SiteCode, $CollectionID)
{

 

Next we need to get the collection object from SCCM. Then we create two arrays for later use. Next we need to find all direct members of the collection, we store these in the $directmembers variable.

    # Get collection from WMI
    $coll = Get-WmiObject -Namespace root\sms\site_$SiteCode -Class sms_collection -Filter "CollectionID = '$CollectionID'"

    # Continue if we got a collection
    if ($coll) {

        Write-Host "Found collection $($coll.Name)"

        # Load lazy properties
        $coll.get()

        # Create arrays for holding members
        $members = @()
        $directmembers = @()

        # Get all ResourceIDs that are member by a DirectRule
        $directmembers += Get-WmiObject -Namespace root\sms\site_$SiteCode -Query "SELECT * FROM SMS_CollectionMember_A WHERE CollectionID = '$CollectionID'" | Where IsDirect -eq $True | Select ResourceID
        Write-Host "Found $($directmembers.count) direct members"

 

Before doing a lot of work we check to see if there was any directmembers, if not there is no reason to continue this process.

        # Continue if there are any direct members
        if ($directmembers.Count -gt 0) {

 

The we loop through all rules that contains a query. The query expression is run directly using WMI to get the members.

Any resources found using the query is then added to the $members array (if not already there).

            # Get all collection rules
            foreach ($item in $coll.CollectionRules) {

                # Continue if the rule is a query based rule
                if ($item.__CLASS -eq "SMS_CollectionRuleQuery") {

                    # Get the query
                    $query = $item.queryExpression
                    Write-Host "Found query rule called $($item.RuleName)"

                    # Get the result of the query from SCCM
                    $result = Get-WmiObject -Namespace root\sms\site_$SiteCode -Query $query
                    Write-Host "Got result of query"

                    # Loop through all resources in the result
                    foreach ($resource in $result) {

                        # If the resourceID is not yet in the members array
                        if ($members -notcontains $resource.ResourceId) {

                            # Add it to the array
                            $members += $resource.ResourceID
                        }
                    }
                }
            }

            # Summary for the query based rules
            Write-Host "Found $($members.count) members using queries"

 

Now that we have all directmembers and all members from any queries, we can do the cleanup.

We loop through all the direct rules and check if the referenced resource is found in the members array (that contains all members from any query)

If we find the resource, we can safely delete the direct rule from the collection.

            # Loop through all direct rules
            foreach ($item in $coll.CollectionRules) {
                if ($item.__CLASS -eq "SMS_CollectionRuleDirect") {
                    Write-Host "Found direct rule for $($Item.ResourceID)"

                    # If the ResourceID is found in members
                    if ($members -contains $item.ResourceID) {

                        # Remove the direct rule for this resourceID
                        $coll.DeleteMembershipRule($item) | Out-Null
                        Write-Host "DirectRule for $($member.ResourceID) was deleted"
                    }
                }
            }

 

Finally we request a membership refresh on the collection so that SCCM is in sync.

            # Finally request a refresh of the Collection just for good measure
            $Coll.RequestRefresh($true) | Out-Null
            write-host "Colletion refresh for $($Coll.Name) was requested"

 

The last bit we need is to call the function using the arguments supplied

CleanupDirectMembershipsInCollection -SiteCode $SiteCode -CollectionID $CollectionID

 

That’s it folks, now the collection only contains members that are either query-based or direct members and not both.

To call the script using a syntax like this, you will of cause have to replace sitecode and collectionid with your values.

collectioncleanup.ps1 –sitecode pri –collectionid PRI00032

 

The complete PowerShell script can be downloaded from the link below

[download id=”136″ format=”1″]