Adding users or devices to collection using the AddMembershipRules method

This content originally appeared on moyerteam.com (no longer active) and has been reproduced with permission of the author, Jamie Moyer.

There are several means to add users / computers to a collection in ConfigMgr. I had a question come in around how to speed up the addition of direct user rules to a given collection. This blog will discuss some options.

There is the Add-CMUserCollectionDirectMembershipRule cmdlet. This cmdlet adds a rule that adds a specific user resource to a given collection.

This cmdlet works great, but if you need to add a lot of rules, its slow.

The alternative?
Use the AddMembershipRules Method in the SMS_Collection class to add an array of rules vs. one at a time.

How much of a difference does it make? See my test results below, adding 18 rules to a test collection:

Add-CMUserCollectionDirectMembershipRule cmdlet: 24 seconds
AddMembershipRules: 3 seconds

Sample Script:

PARAM ([string]$SccmServer = "cm01",
       [string]$CollectionID = "CAS0000E",
       [array] $UserNames = 'LITWARE\!ac,LITWARE\Administrator'   
       )

Function Main
{
    $sccmProviderLocation = Get-WmiObject -query "select * from SMS_ProviderLocation where ProviderForLocalSite = true" -Namespace "root\sms" -computername $SccmServer
    $SiteCode = $sccmProviderLocation.SiteCode   
    $SccmNamespace = "root\sms\site_$SiteCode"  

    Add-SCCMDirUserCollectionRules -SccmServer $SccmServer -SccmNamespace $SccmNamespace -CollectionID $CollectionID -UserNames $UserNames.split(',')
}

Function Add-SCCMDirUserCollectionRules 
{ 
    [CmdletBinding()] 	
    PARAM ( 	
        [Parameter(Mandatory=$true)][string] $SccmServer, 	
        [Parameter(Mandatory=$true)][string] $SccmNamespace, 	
        [Parameter(ValueFromPipelineByPropertyName=$true)][String] $CollectionID, 	
        [Parameter(ValueFromPipeline=$true)][array] $UserNames
    ) 	  	
        $coll = [wmi]"\\$($SccmServer)\$($SccmNamespace):SMS_Collection.CollectionID='$CollectionID'"	
        $ruleClass = [WMICLASS]"\\$($SccmServer)\$($SccmNamespace):SMS_CollectionRuleDirect"	
        [array]$rules = $null

        #1. Iterate over passed in array of users
        #2. Call Get-SCCMUserInfo function to get resourceid and name
        #3. Create a new instance of SMS_CollectionRuleDirect for each user
        #4. Add the instance of SMS_CollectionRuleDirect to the $rules array
        $UserNames | ForEach-Object {
            $userinfo = Get-SCCMUserInfo -SccmServer $SccmServer -SccmNamespace $SccmNamespace -UserName $_
            if($userinfo -ne $null)
            {
                $NewRuleName=$UserRule.name 	
                $NewRuleResourceID = $UserRule.ResourceID 	
                $newRule = $ruleClass.CreateInstance() 	

                $newRule.RuleName = $($userinfo.name)
                $newRule.ResourceClassName = "SMS_R_User"	
                $newRule.ResourceID = $($userinfo.resourceid)
                $rules += $newRule
            }
        }    

        #Add all the rules in the array       
        #See: http://msdn.microsoft.com/en-us/library/hh949023.aspx         
        $coll.AddMembershipRules($rules) | Out-Null

        #Refresh the collection
        $coll.requestrefresh() 	| Out-Null
} 	

Function Get-SCCMUserInfo
{ 
    [CmdletBinding()] 	
    PARAM ( 	
        [Parameter(Mandatory=$true)][string] $SccmServer, 	
        [Parameter(Mandatory=$true)][string] $SccmNamespace, 	
        [Parameter(ValueFromPipeline=$true)][string]$UserName
    ) 
    $userInfo = gwmi -ComputerName $SccmServer -Namespace $SccmNamespace -Class "SMS_R_User" -Filter "UniqueUserName = '$($($UserName).replace('\','\\'))'" | select name,resourceid
    $userInfo
}	

#call main
. main

UPDATE: But I want to add device rules vs. user rules….

PARAM ([string]$SccmServer = "cm02",
       [string]$CollectionID = "PRI0243",
       [array] $MachineNames = 'CM01,CM02'       
       )


Function Main
{
    $sccmProviderLocation = Get-WmiObject -query "select * from SMS_ProviderLocation where ProviderForLocalSite = true" -Namespace "root\sms" -computername $SccmServer
    $SiteCode = $sccmProviderLocation.SiteCode   
    $SccmNamespace = "root\sms\site_$SiteCode"  
    Add-SCCMDirectMachineRules -SccmServer $SccmServer -SccmNamespace $SccmNamespace -CollectionID $CollectionID -MachineNames $MachineNames.split(',')
      	

}

Function Add-SCCMDirectMachineRules 
{ 
    [CmdletBinding()] 	
    PARAM ( 	
        [Parameter(Mandatory=$true)][string] $SccmServer, 	
        [Parameter(Mandatory=$true)][string] $SccmNamespace, 	
        [Parameter(ValueFromPipelineByPropertyName=$true)][String] $CollectionID, 	
        [Parameter(ValueFromPipeline=$true)][array] $MachineNames
    ) 	  	
        $coll = [wmi]"\\$($SccmServer)\$($SccmNamespace):SMS_Collection.CollectionID='$CollectionID'"	
        $ruleClass = [WMICLASS]"\\$($SccmServer)\$($SccmNamespace):SMS_CollectionRuleDirect"	
        [array]$rules = $null

        #1. Iterate over passed in array of users
        #2. Call Get-SCCMMachineInfo function to get resourceid and name
        #3. Create a new instance of SMS_CollectionRuleDirect for each machine
        #4. Add the instance of SMS_CollectionRuleDirect to the $rules array
        $MachineNames | ForEach-Object {
            $MachineInfo = Get-SCCMMachineInfo -SccmServer $SccmServer -SccmNamespace $SccmNamespace -MachineName $_
            if($MachineInfo -ne $null)
            {
                $NewRuleName=$UserRule.name 	
                $NewRuleResourceID = $UserRule.ResourceID 	
                $newRule = $ruleClass.CreateInstance() 	  	
                $newRule.RuleName = $($MachineInfo.name)
                $newRule.ResourceClassName = "SMS_R_System"	
                $newRule.ResourceID = $($MachineInfo.resourceid)
                $rules += $newRule
            }
        }    
            
        #Add all the rules in the array       
        #See: http://msdn.microsoft.com/en-us/library/hh949023.aspx         
        $coll.AddMembershipRules($rules) | Out-Null

        #Refresh the collection
        $coll.requestrefresh() 	| Out-Null
} 	

Function Get-SCCMMachineInfo
{ 
    [CmdletBinding()] 	
    PARAM ( 	
        [Parameter(Mandatory=$true)][string] $SccmServer, 	
        [Parameter(Mandatory=$true)][string] $SccmNamespace, 	
        [Parameter(ValueFromPipeline=$true)][string]$MachineName
    ) 
    $MachineInfo = gwmi -ComputerName $SccmServer -Namespace $SccmNamespace -Class "SMS_R_System" -Filter "Name = '$($MachineName)'" | select name,resourceid
    $MachineInfo
}	

#call main
. main