You may be using Direct Membership Rules in your Microsoft Configuration Manager environment, but should you really for critical production collections?
No is the answer, there I said it. Mainly because they require actual effort and overhead to maintain, and secondly because there have been times where these memberships just plain disappear, for many different reasons, but primarily if the ConfigMgr Client is reinstalled on the device.
So what do we do? Aggressive PowerShell scripts obviously.
The Approach #
We’ll need to connect to the Configuration Manager PowerShell module, which is easy enough from the Primary Site server, then carry out the following actions:
- Get the Device Collections with Direct Members
- Capture the Direct Members
- Create a new Query Membership Rule with the members
- Remove the existing Direct Members
- Logging?
So let’s get at it…
Connecting via PowerShell #
From the Configuration Manager server we can leverage the PowerShell Module, import it and then connect to the PSDrive where we get to run all the Configuration Manager commands
Import-module ($Env:SMS_ADMIN_UI_PATH.Substring(0, $Env:SMS_ADMIN_UI_PATH.Length - 5) + '\ConfigurationManager.psd1')
$SiteCode = Get-PSDrive -PSProvider CMSITE
Set-location $SiteCode":"
Getting the Collections #
I thought I’d be kind and give a couple of options to the Collection gathering, attack all of the Device Collections or, which is probably more sensible, bring up a list of collections to work with.
We’re doing this through a selection option at the start of the script and using Get-CMDeviceCollection:
if ($Choice_Number -eq '1') {
Write-Host "Getting Device Collections with Direct Membership Rules..." -ForegroundColor Yellow
$Collections = @(Get-CMDeviceCollection | Where-Object { $_.CollectionRules -like '*SMS_CollectionRuleDirect*' } | Select-Object Name, CollectionID, CollectionRules | Out-GridView -PassThru -Title 'Wait for all Collections to load, then select the Device Collections you want to convert. Use The ENTER Key or Mouse \ OK Button.')
}
if ($Choice_Number -eq '2') {
Write-Host "Getting Device Collections with Direct Membership Rules..." -ForegroundColor Yellow
$Collections = Get-CMDeviceCollection | Where-Object { $_.CollectionRules -like '*SMS_CollectionRuleDirect*' } | Select-Object Name, CollectionID, CollectionRules
}
We need to now identify whether these collection have Direct Members, so for each collection we can look at the CollectionRules
attribute, and see if it contains SMS_CollectionRuleDirect
:
foreach ($Collection in $Collections) {
if ($Collection.CollectionRules -like "*SMS_CollectionRuleDirect*") {
}
}
Now we know this collection has Direct Members, let’s get them using Get-CMDeviceCollectionDirectMembershipRule:
$DirectMembers = Get-CMDeviceCollectionDirectMembershipRule -CollectionName $Collection.Name
Cool, easy part done.
Building the Query #
An easy replacement for the Direct Membership Rule is to use a List of Values Query based on System Name, so we’re going to use that, we’re going to use this as our basis for the new query once we’ve captured the Direct Members in a suitable format.
CCM Client Computer Name #
select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System inner join SMS_G_System_SYSTEM on SMS_G_System_SYSTEM.ResourceId = SMS_R_System.ResourceId where SMS_G_System_SYSTEM.Name in ("COMPUTER1", "COMPUTER2")
AD Client Computer Name #
select SMS_R_System.ResourceId, SMS_R_System.ResourceType, SMS_R_System.Name, SMS_R_System.SMSUniqueIdentifier, SMS_R_System.ResourceDomainORWorkgroup, SMS_R_System.Client from SMS_R_System where SMS_R_System.Name in ("COMPUTER1", "COMPUTER2")
We also need to get the names of the Direct Members, throw them into an Array as part of the foreach
loop, then squish the Array to a String and combine the template query and the members:
$RuleName = "Direct Membership Replacement Query"
$QueryPart = "select SMS_R_System.ResourceId, SMS_R_System.ResourceType, SMS_R_System.Name, SMS_R_System.SMSUniqueIdentifier, SMS_R_System.ResourceDomainORWorkgroup, SMS_R_System.Client from SMS_R_System where SMS_R_System.Name in ("
if ($Collection.CollectionRules -like "*SMS_CollectionRuleDirect*") {
$DirectMembers = Get-CMDeviceCollectionDirectMembershipRule -CollectionName $Collection.Name
$MembersArray = @()
foreach ($DirectMember in $DirectMembers) {
$MembersArray += $DirectMember.RuleName
}
$Members = '"{0}"' -f ($MembersArray -join '","')
$QueryExpression = $QueryPart + $Members + ")"
}
This gives us the complete query string which we can use later on to create the Query Membership Rule.
Updating the Rules #
Using Add-CMDeviceCollectionQueryMembershipRule we can create the new rule in the Collection using the variables we created earlier:
Add-CMDeviceCollectionQueryMembershipRule -CollectionName $Collection.Name -QueryExpression $QueryExpression -RuleName $RuleName
And after successfully creating the rule we can use Remove-CMDeviceCollectionDirectMembershipRule to loop through the existing Direct Members and remove them:
foreach ($DirectMember in $DirectMembers) {
Try {
Remove-CMDeviceCollectionDirectMembershipRule -CollectionName $Collection.Name -ResourceID $DirectMember.ResourceID -Force
}
Catch {
}
}
Running the Script #
Now we have all the bits we needed in place (cough logging cough), we can launch the script and use the option to select a Collection first as a test before we run it across the entire environment.
#Load Configuration Manager PowerShell Module
Import-Module ($Env:SMS_ADMIN_UI_PATH.Substring(0, $Env:SMS_ADMIN_UI_PATH.Length - 5) + '\ConfigurationManager.psd1')
#Get SiteCode
$SiteCode = Get-PSDrive -PSProvider CMSITE
Set-Location $SiteCode":"
Clear-Host
$RuleName = 'Direct Membership Replacement Query'
# CCM Client Name
#$QueryPart = 'select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System inner join SMS_G_System_SYSTEM on SMS_G_System_SYSTEM.ResourceId = SMS_R_System.ResourceId where SMS_G_System_SYSTEM.Name in ('
# AD Name
$QueryPart = 'select SMS_R_System.ResourceId, SMS_R_System.ResourceType, SMS_R_System.Name, SMS_R_System.SMSUniqueIdentifier, SMS_R_System.ResourceDomainORWorkgroup, SMS_R_System.Client from SMS_R_System where SMS_R_System.Name in ('
$Merged = New-Object System.Collections.ArrayList
Write-Host '********************************************************************************'
Write-Host '**** Welcome to the Direct Membership Device Collection Converter Tool ****' -ForegroundColor Green
Write-Host '**** This Script will convert Direct Memberships to Query Based Membership ****' -ForegroundColor Cyan
Write-Host '*******************************************************************************'
Write-Host
Write-Host ' Please Choose one of the options below: ' -ForegroundColor Yellow
Write-Host
Write-Host ' (1) Manually select the Device Collections your want to convert... ' -ForegroundColor Green
Write-Host
Write-Host ' (2) Run the Script on all Device Collection in your environment... ' -ForegroundColor Green
Write-Host
Write-Host ' (E) EXIT SCRIPT ' -ForegroundColor Red
Write-Host
$Choice_Number = ''
$Choice_Number = Read-Host -Prompt 'Based on which option you want to run, please type 1, 2 or E to exit the test, then click enter '
while ( !($Choice_Number -eq '1' -or $Choice_Number -eq '2' -or $Choice_Number -eq 'E')) {
$Choice_Number = Read-Host -Prompt 'Invalid Option, Based on which option you want to run, please type 1, 2 or E to exit the test, then click enter '
}
if ($Choice_Number -eq 'E') {
Break
}
if ($Choice_Number -eq '1') {
Write-Host "Getting Device Collections with Direct Membership Rules..." -ForegroundColor Yellow
$Collections = @(Get-CMDeviceCollection | Where-Object { $_.CollectionRules -like '*SMS_CollectionRuleDirect*' } | Select-Object Name, CollectionID, CollectionRules | Out-GridView -PassThru -Title 'Wait for all Collections to load, then select the Device Collections you want to convert. Use The ENTER Key or Mouse \ OK Button.')
}
if ($Choice_Number -eq '2') {
Write-Host "Getting Device Collections with Direct Membership Rules..." -ForegroundColor Yellow
$Collections = Get-CMDeviceCollection | Where-Object { $_.CollectionRules -like '*SMS_CollectionRuleDirect*' } | Select-Object Name, CollectionID, CollectionRules
}
if (!$Collections) {
Write-Host 'No Collections selected, please run the script again...' -ForegroundColor Red
Break
}
foreach ($Collection in $Collections) {
if ($Collection.CollectionRules -like '*SMS_CollectionRuleDirect*') {
$Output = New-Object -Type PSCustomObject
$Output | Add-Member -type NoteProperty -Name ID -Value $Collection.CollectionID
$Output | Add-Member -type NoteProperty -Name Collection -Value $Collection.Name
Write-Host "The Collection $($Collection.Name) contains Direct Members..." -ForegroundColor Cyan
Write-Host
$DirectMembers = Get-CMDeviceCollectionDirectMembershipRule -CollectionName $Collection.Name
Write-Host "Getting direct members for Collection $($Collection.Name)..." -ForegroundColor Cyan
Write-Host
$MembersArray = @()
foreach ($DirectMember in $DirectMembers) {
Write-Host "Direct Member $($DirectMember.RuleName) found." -ForegroundColor Yellow
$MembersArray += $DirectMember.RuleName
}
$Members = '"{0}"' -f ($MembersArray -join '","')
$Output | Add-Member -type NoteProperty -Name Members -Value $Members
$QueryExpression = $QueryPart + $Members + ')'
Try {
Write-Host
Write-Host "Adding Query based rule to Collection $($Collection.Name) to replace direct membership..." -ForegroundColor Cyan
Write-Host
Add-CMDeviceCollectionQueryMembershipRule -CollectionName $Collection.Name -QueryExpression $QueryExpression -RuleName $RuleName
Write-Host 'Successfully added the query.' -ForegroundColor Green
Write-Host
$Output | Add-Member -type NoteProperty -Name Success -Value True
Write-Host 'Removing Direct Membership Rules' -ForegroundColor Cyan
Write-Host
foreach ($DirectMember in $DirectMembers) {
Try {
Remove-CMDeviceCollectionDirectMembershipRule -CollectionName $Collection.Name -ResourceID $DirectMember.ResourceID -Force
Write-Host "Successfully removed $($Directmember.RuleName)." -ForegroundColor Green
Write-Host
}
Catch {
Write-Host "Failed to remove $($Directmember.RuleName)." -ForegroundColor Red
Write-Host
}
}
}
Catch {
Write-Host "Failed to convert Direct Membership to query for Collection $($Collection.Name)" -ForegroundColor Red
$Output | Add-Member -type NoteProperty -Name Success -Value False
}
$Merged.Add($Output) | Out-Null
}
}
Write-Host
Write-Host 'Results of the Collection Conversion...' -ForegroundColor Green
$Merged
Let’s run it on a test collection we know has direct members:
Run the script from an Elevated PowerShell prompt on your Primary Site Server:
Set-CollectionDirectToQueryMembership.ps1
Oh the Choices #
Doing so will prompt you with options, oooo exciting, here we’ll select Option 1:
Doing so will open a grid view of the collections in your Configuration Manager environment, this may take a while to load, and you should wait for them to load before selecting and confirming your Collections:
Search for the Collection you want to update and select OK:
Converting the Rules #
Now the magic happens, including a nice little output table of the results of the conversion:
The Results Speak for Themselves #
Now the script has completed, we can go and check on the results on the Device Collection itself, look no Direct Members any more:
With the new Query in place:
Summary #
With this small but powerful script, you can remove all Direct Membership rules and use the more sustainable (and less likely to cause you problems), Query Rule on either Collections you know have Direct Members, or if you’re feeling brave, your entire set of Device Collections.
This saves you having to manually create the Rules, or even the collections, which would mean re-deploying configuration, updates, applications etc. to a new Collection.
What a time saver.