I am not well enough versed in the world of WMI to know what's going wrong, but it LOOKS like things should work but don't.
I was able to ADD a resource using the following code:
private void Add_SCCM(DirectoryObject selectedObject)
{
string computerName = selectedObject.Name;
try
{
// Connect to SCCM WMI namespace
ManagementScope scope = new ManagementScope(@"\\MyCOMPUTER\root\sms\site_XXX");
scope.Connect();
// Query ResourceID of the computer
string query = $"SELECT ResourceID FROM SMS_R_System WHERE Name = '{computerName}'";
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, new ObjectQuery(query));
ManagementObjectCollection results = searcher.Get();
string resourceId = null;
foreach (ManagementObject result in results)
{
resourceId = result["ResourceID"]?.ToString();
}
if (!string.IsNullOrEmpty(resourceId))
{
// Check if the computer is already in the collection as a direct rule
string ruleQuery = $"SELECT * FROM SMS_FullCollectionMembership WHERE CollectionID = '{SCCM_COLLID}' AND ResourceID = '{resourceId}'";
ManagementObjectSearcher ruleSearcher = new ManagementObjectSearcher(scope, new ObjectQuery(ruleQuery));
ManagementObjectCollection ruleResults = ruleSearcher.Get();
if (ruleResults.Count > 0)
{
string infoTitle = "Information";
string infoMessage = $"Computer {computerName} is already part of the SCCM collection {SCCM_COLLID}. Skipping.";
MessageBox.Show(infoMessage, infoTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
return; // Exit without adding
}
// Instantiate a direct membership rule
ManagementClass ruleClass = new ManagementClass(scope, new ManagementPath("SMS_CollectionRuleDirect"), null);
ManagementObject ruleInstance = ruleClass.CreateInstance();
ruleInstance["ResourceClassName"] = "SMS_R_System";
ruleInstance["ResourceID"] = resourceId;
ruleInstance["RuleName"] = computerName;
// Add the membership rule to the SCCM collection
ManagementPath collectionPath = new ManagementPath($"SMS_Collection.CollectionID='{SCCM_COLLID}'");
ManagementObject collection = new ManagementObject(scope, collectionPath, null);
ManagementBaseObject inParams = collection.GetMethodParameters("AddMembershipRule");
inParams["CollectionRule"] = ruleInstance;
collection.InvokeMethod("AddMembershipRule", inParams, null);
string successTitle = "Success";
string successMessage = $"Successfully added {computerName} to SCCM collection {SCCM_COLLID}";
MessageBox.Show(successMessage, successTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
string errorTitle = "Error";
string errorMessage = $"Failed to find ResourceID for computer: {computerName}";
MessageBox.Show(errorMessage, errorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
string errorTitle = "Error";
string errorMessage = $"Error adding {computerName} to SCCM collection {SCCM_COLLID}: {ex.Message}";
MessageBox.Show(errorMessage, errorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
But there doesn't seem to be a "GetRules" method on a collection, so that I can select that rule and then delete it using the DeleteMembership Method on SMS_Collection Class.
ChatGPT suggested this, but it results in an invalid query...
// Query the collection rule
string query = $"SELECT * FROM SMS_CollectionRuleDirect WHERE ResourceID IN " +
$"(SELECT ResourceID FROM SMS_R_System WHERE Name = '{computerName}')";
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, new ObjectQuery(query));
ManagementObjectCollection results = searcher.Get();
foreach (ManagementObject rule in results)
{
rule.Delete();
}
I am deep in the weeds trying to understand the WMI calls. Any help would be appreciated.
Thanks.
If you know for sure that there is a direct rule (from some other query or because you would never be in the situation where you need this code if there is not one) you can do it just like you did for adding (only of course call DeleteMembershipRule instead of AddMembershipRule)
// Instantiate a direct membership rule
ManagementClass ruleClass = new ManagementClass(scope, new ManagementPath("SMS_CollectionRuleDirect"), null);
ManagementObject ruleInstance = ruleClass.CreateInstance();
ruleInstance["ResourceClassName"] = "SMS_R_System";
ruleInstance["ResourceID"] = resourceId;
ruleInstance["RuleName"] = computerName;
// Delete the membership rule from the SCCM collection
ManagementPath collectionPath = new ManagementPath($"SMS_Collection.CollectionID='{SCCM_COLLID}'");
ManagementObject collection = new ManagementObject(scope, collectionPath, null);
ManagementBaseObject inParams = collection.GetMethodParameters("DeleteMembershipRule");
inParams["CollectionRule"] = ruleInstance;
collection.InvokeMethod("DeleteMembershipRule", inParams, null);
If you can not be sure of that you can iterate over all the existing rules like this (sample would delete all)
// Delete all CollectionRules of a collection
ManagementBaseObject[] smsCollection = (ManagementBaseObject[])collection["CollectionRules"];
foreach (ManagementBaseObject col in smsCollection) {
ManagementBaseObject inParams = collection.GetMethodParameters("DeleteMembershipRule");
inParams["CollectionRule"] = col;
collection.InvokeMethod("DeleteMembershipRule", inParams, null);
}
If you only want to delete specific ones you can in the loop then check the properties of all the rules by accessing them like this:
col["Rulename"]
col["ResourceID"]
and then just call DeleteMembershipRule when you found a rule you want removed.
You can also check col.ClassPath
if it really is SMS_CollectionRuleDirect
if you want to be sure (although I do not think that if you check for a Rulename or ResourceID that equals one of your known records it could ever be something else really)