Using ManagementObjectSearcher to get an exact BitLocker WMI value - c#

Good Day All,
I am having an issue with ManagementObjectSearcher. I am trying to query the exact value that i want but cannot find any reference to the precise syntax requirements and I continually receive an error when trying to finish out the code to be what I need it to be.
the specific portion of code that is presenting the issue is when I check for the drives Encryption state(I know for a fact that my disk is not encrypted on this machine, which is why that is the only value i have if'd currently). Any assistance in getting this code to pull the correct value would be greatly appreciated.
I've tried both the "=" method and the "LIKE" method with no change in output.
using Microsoft.Win32;
using System;
using System.Drawing;
using System.IO;
using System.Management;
using System.Windows.Forms;
public Form1()
{
InitializeComponent();
// Check for OS Version
string OSVer = Convert.ToString(Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "ProductName", null));
OSDialog.Text = OSVer;
// Check Architecture
if (Directory.Exists("C:\\Program Files (x86)"))
{
ArchitectureDialog.Text = "64 Bit";
}
else
{
ArchitectureDialog.Text = "32 Bit";
}
// Check Encryption
ManagementObjectSearcher Collect = new ManagementObjectSearcher("SELECT ProtectionStatus FROM Win32_EncryptableVolume WHERE DriveLetter = 'C:'");
string Encryption = Collect.ToString();
if (Encryption == "0")
{
EncryptionDialog.Text = "Disk is not Encrypted";
EncryptionDialog.ForeColor = Color.Green;
}
}
private void Cancel_Click(object sender, EventArgs e)
{
Close();
}

Getting BitLocker information from WMI requires elevated permissions. Your code has to be running as an admin and you have to ask for elevated privileges. So, I don't use ManagementObjectSearcher to obtain BitLocker info. Instead, I do something similar to the following (modified to your scenario - but not tested as shown):
ManagementObject GetBitLockerManager( string driveLetter )
{
var path = new ManagementPath( );
path.Server = string.Empty;
path.NamespacePath = #"\ROOT\CIMV2\Security\MicrosoftVolumeEncryption";
path.ClassName = "Win32_EncryptableVolume";
var options = new ConnectionOptions( );
options.Impersonation = ImpersonationLevel.Impersonate;
options.EnablePrivileges = true;
options.Authentication = AuthenticationLevel.PacketPrivacy;
var scope = new ManagementScope( path, options );
var mgmt = new ManagementClass( scope, path, new ObjectGetOptions( ) );
mgmt.Get( );
return mgmt
.GetInstances( )
.Cast<ManagementObject>( )
.FirstOrDefault
( vol =>
string.Compare
(
vol[ "DriveLetter" ] as string,
driveLetter,
true
) == 0
);
}

OK so I figured it out, thank you for all of the assistance provided. Code is below.
ManagementObjectSearcher Encryption = new ManagementObjectSearcher(#"root\cimv2\Security\MicrosoftVolumeEncryption", "SELECT * FROM Win32_EncryptableVolume");
foreach (ManagementObject QueryObj in Encryption.Get())
{
string EncryptionStatus = QueryObj.GetPropertyValue("ProtectionStatus").ToString();
if (EncryptionStatus == "0")
{
EncryptionDialog.Text = "Unencrypted";
}
else if (EncryptionStatus == "1")
{
EncryptionDialog.Text = "Encrypted - SysPrep will not complete";
}
else if (EncryptionStatus == "2")
{
EncryptionDialog.Text = "Cannot Determine Encryption";
}
}

Related

add printer to local computer using ManagementClass

I see references and hints that programmatically one can add a networked printer to a local computer using the ManagementClass and such. However I have not been able to find any actual tutorials on doing just this.
has anyone actually used the ManagementClass to do this?
I am doing this:
var connectionOption = new ConnectionOption();
var mgmScope = new ManagementScope("root\cimv2",connectionOptions);
var printerClass = new ManagementClass(mgmScope, new ManagementPath("Win32_Printer"),null);
var printerObj = printerClass.CreateInstance();
printerObj["DeviceID"] = prnName; //
printerObj["DriverName"] = drvName; // full path to driver
printerObj["PortName"] = "myTestPort:";
var options = new PutOptions {Type = PutType.UpdateOrCreate};
printerObj.Put(options);
All this does is create an error "Generic Failure"
I cant figure out what I am missing..... any help or thoughts about this would be appreciated.
I think I need to better explain what I am trying to do... when the printers needed are not tied to a print server, I need to:
create a tcpip raw port,
connect a printer via tcp/ip,
install drivers,
optionally set default.
I was hoping WMI could basically take care of all of this but it doesnt appear to be the case.
thanks!
The WMI Win32_Printer class provides a method called AddPrinterConnection to
add a network printer to the list of local printers. The code below
shows how to connect a network printer using the Win32_Printer class.
Please note, that under certain conditions the AddPrinterConnection fails
to connect the remote printer. In the example below I've listed the most
common error cases.
using (ManagementClass win32Printer = new ManagementClass("Win32_Printer"))
{
using (ManagementBaseObject inputParam =
win32Printer.GetMethodParameters("AddPrinterConnection"))
{
// Replace <server_name> and <printer_name> with the actual server and
// printer names.
inputParam.SetPropertyValue("Name", "\\\\<server_name>\\<printer_name>");
using (ManagementBaseObject result =
(ManagementBaseObject)win32Printer.InvokeMethod("AddPrinterConnection", inputParam, null))
{
uint errorCode = (uint)result.Properties["returnValue"].Value;
switch (errorCode)
{
case 0:
Console.Out.WriteLine("Successfully connected printer.");
break;
case 5:
Console.Out.WriteLine("Access Denied.");
break;
case 123:
Console.Out.WriteLine("The filename, directory name, or volume label syntax is incorrect.");
break;
case 1801:
Console.Out.WriteLine("Invalid Printer Name.");
break;
case 1930:
Console.Out.WriteLine("Incompatible Printer Driver.");
break;
case 3019:
Console.Out.WriteLine("The specified printer driver was not found on the system and needs to be downloaded.");
break;
}
}
}
}
I spent almost the week on that issue! My target is a bit more complicated, I want to wrap the C# code in a WCF service called by a SharePoint Add-In, and this WCF service should call remotely the client to install the printers. Thus I tried hard with WMI.
In the mean time, I managed to use rundll32 solution (which requires first to create the port), and also did a powershell variant, really the simplest:
$printerPort = "IP_"+ $printerIP
$printerName = "Xerox WorkCentre 6605DN PCL6"
Add-PrinterPort -Name $printerPort -PrinterHostAddress $printerIP
Add-PrinterDriver -Name $printerName
Add-Printer -Name $printerName -DriverName $printerName -PortName $printerPort
The really tricky part is to know the name of the printer, it should match the string in the INF file!
But back to C# solution: I created a class with printerName, printerIP and managementScope as attributes. And driverName = printerName
private string PrinterPortName
{
get { return "IP_" + printerIP; }
}
private void CreateManagementScope(string computerName)
{
var wmiConnectionOptions = new ConnectionOptions();
wmiConnectionOptions.Impersonation = ImpersonationLevel.Impersonate;
wmiConnectionOptions.Authentication = AuthenticationLevel.Default;
wmiConnectionOptions.EnablePrivileges = true; // required to load/install the driver.
// Supposed equivalent to VBScript objWMIService.Security_.Privileges.AddAsString "SeLoadDriverPrivilege", True
string path = "\\\\" + computerName + "\\root\\cimv2";
managementScope = new ManagementScope(path, wmiConnectionOptions);
managementScope.Connect();
}
private bool CheckPrinterPort()
{
//Query system for Operating System information
ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_TCPIPPrinterPort");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(managementScope, query);
ManagementObjectCollection queryCollection = searcher.Get();
foreach (ManagementObject m in queryCollection)
{
if (m["Name"].ToString() == PrinterPortName)
return true;
}
return false;
}
private bool CreatePrinterPort()
{
if (CheckPrinterPort())
return true;
var printerPortClass = new ManagementClass(managementScope, new ManagementPath("Win32_TCPIPPrinterPort"), new ObjectGetOptions());
printerPortClass.Get();
var newPrinterPort = printerPortClass.CreateInstance();
newPrinterPort.SetPropertyValue("Name", PrinterPortName);
newPrinterPort.SetPropertyValue("Protocol", 1);
newPrinterPort.SetPropertyValue("HostAddress", printerIP);
newPrinterPort.SetPropertyValue("PortNumber", 9100); // default=9100
newPrinterPort.SetPropertyValue("SNMPEnabled", false); // true?
newPrinterPort.Put();
return true;
}
private bool CreatePrinterDriver(string printerDriverFolderPath)
{
var endResult = false;
// Inspired from https://msdn.microsoft.com/en-us/library/aa384771%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
// and http://microsoft.public.win32.programmer.wmi.narkive.com/y5GB15iF/adding-printer-driver-using-system-management
string printerDriverInfPath = IOUtils.FindInfFile(printerDriverFolderPath);
var printerDriverClass = new ManagementClass(managementScope, new ManagementPath("Win32_PrinterDriver"), new ObjectGetOptions());
var printerDriver = printerDriverClass.CreateInstance();
printerDriver.SetPropertyValue("Name", driverName);
printerDriver.SetPropertyValue("FilePath", printerDriverFolderPath);
printerDriver.SetPropertyValue("InfName", printerDriverInfPath);
// Obtain in-parameters for the method
using (ManagementBaseObject inParams = printerDriverClass.GetMethodParameters("AddPrinterDriver"))
{
inParams["DriverInfo"] = printerDriver;
// Execute the method and obtain the return values.
using (ManagementBaseObject result = printerDriverClass.InvokeMethod("AddPrinterDriver", inParams, null))
{
// result["ReturnValue"]
uint errorCode = (uint)result.Properties["ReturnValue"].Value; // or directly result["ReturnValue"]
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681386(v=vs.85).aspx
switch (errorCode)
{
case 0:
//Trace.TraceInformation("Successfully connected printer.");
endResult = true;
break;
case 5:
Trace.TraceError("Access Denied.");
break;
case 123:
Trace.TraceError("The filename, directory name, or volume label syntax is incorrect.");
break;
case 1801:
Trace.TraceError("Invalid Printer Name.");
break;
case 1930:
Trace.TraceError("Incompatible Printer Driver.");
break;
case 3019:
Trace.TraceError("The specified printer driver was not found on the system and needs to be downloaded.");
break;
}
}
}
return endResult;
}
private bool CreatePrinter()
{
var printerClass = new ManagementClass(managementScope, new ManagementPath("Win32_Printer"), new ObjectGetOptions());
printerClass.Get();
var printer = printerClass.CreateInstance();
printer.SetPropertyValue("DriverName", driverName);
printer.SetPropertyValue("PortName", PrinterPortName);
printer.SetPropertyValue("Name", printerName);
printer.SetPropertyValue("DeviceID", printerName);
printer.SetPropertyValue("Location", "Front Office");
printer.SetPropertyValue("Network", true);
printer.SetPropertyValue("Shared", false);
printer.Put();
return true;
}
private void InstallPrinterWMI(string printerDriverPath)
{
bool printePortCreated = false, printeDriverCreated = false, printeCreated = false;
try
{
printePortCreated = CreatePrinterPort();
printeDriverCreated = CreatePrinterDriver(printerDriverPath);
printeCreated = CreatePrinter();
}
catch (ManagementException err)
{
if (printePortCreated)
{
// RemovePort
}
Console.WriteLine("An error occurred while trying to execute the WMI method: " + err.Message);
}
}
finally install the driver. I still need further tests if its works on a clean Windows. Did many install/uninstall of the driver during my tests
In order to do this I ended up having to do a 2 stepper...
first build up a command line to fire off:
rundll32.exe printui.dll,PrintUIEntry /if /b "test" /f x2DSPYP.inf /r 10.5.43.32 /m "845 PS"
Then spawn it:
public static string ShellProcessCommandLine(string cmdLineArgs,string path)
{
var sb = new StringBuilder();
var pSpawn = new Process
{
StartInfo =
{
WorkingDirectory = path,
FileName = "cmd.exe",
CreateNoWindow = true,
Arguments = cmdLineArgs,
RedirectStandardInput = true,
RedirectStandardOutput = true,
UseShellExecute = false
}
};
pSpawn.OutputDataReceived += (sender, args) => sb.AppendLine(args.Data);
pSpawn.Start();
pSpawn.BeginOutputReadLine();
pSpawn.WaitForExit();
return sb.ToString();
}
This seems to work... not the ideal method but for those printers not on a print server it seems to do the job.
I wanted to add answer as there is another way and Microsoft recommends using Microsoft.Management.Infrastructure now over System.Management.
Here is link to Microsoft documentation:
https://msdn.microsoft.com/en-us/library/microsoft.management.infrastructure.aspx
Please note that you have to add reference in c# project to the Microsoft.Management.Infrastructure dll:
Where the Microsoft.Management.Infrastructure
You may also need to configure winrm as well on machine
https://learn.microsoft.com/en-us/windows/desktop/winrm/installation-and-configuration-for-windows-remote-management
Here is sample code for adding a printer to local machine from a print server using c#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Generic;
using Microsoft.Management.Infrastructure.Options;
namespace MMITest
{
class Program
{
static void Main(string[] args)
{
using (var session = CimSession.Create("localhost"))
{
using (var cimMethodParameters = new CimMethodParametersCollection())
{
cimMethodParameters.Add(CimMethodParameter.Create("Name", #"\\PrintServerName\PrinterName", CimFlags.Parameter));
session.InvokeMethod(#"root\cimv2", "Win32_Printer", "AddPrinterConnection", cimMethodParameters);
}
}
Console.ReadLine();
}
}
}

Create a new Hyper-V VM (using WMI) with specific hardware

I'm wanting to create a new Hyper-V VM with a determined amount of RAM, network card, number of processor cores, and attach a VHD file to the IDE controller.
The problem is that the Msvm_ResourceAllocationSettingData is not very easy to work with. The code I'm using doesn't work (this is code to attach a VHD to an existing VM, however I would also like to use it when creating a new VHD too).
public void AttachVhd(IdeChannel ideChannel, String vhdPath) {
// Get VirtualSystemSettingData
ManagementObject vssd = this.GetVirtualSystemSettingData();
// Get the IDE Controller
ManagementObject ideController = this.GetResourceAllocationSettingData(ResourceType.IdeController, ResourceSubType.IdeController);
// Create synthetic disk:
ManagementObject syntheticDiskRasd = this.GetResourceAllocationSettingData(ResourceType.Disk, ResourceSubType.DiskSynthetic);
syntheticDiskRasd["Parent"] = ideController.Path;
syntheticDiskRasd["Address"] = ideChannel == IdeChannel.Primary ? "0" : "1";
this.AddVirtualSystemResources(syntheticDiskRasd);
// Attach it
ManagementObject vhdRasd = this.GetResourceAllocationSettingData(ResourceType.StorageExtent, ResourceSubType.Vhd); ;
vhdRasd["Parent"] = syntheticDiskRasd.Path;
vhdRasd["Connection"] = new String[] { vhdPath };
this.AddVirtualSystemResources( vhdRasd );
// Cleanup
vhdRasd.Dispose();
syntheticDiskRasd.Dispose();
ideController.Dispose();
vssd.Dispose();
}
private ManagementObject GetResourceAllocationSettingData(ResourceType resourceType, ResourceSubType resourceSubType)
{
String desiredSubType = ResourceSubTypeStrings.GetString(resourceSubType); // Scout.Common.Extensions.GetDescription( resourceType );
using(ManagementObjectCollection settingsDatas = _vm.GetRelated("Msvm_VirtualSystemSettingData"))
foreach(ManagementObject settingData in settingsDatas)
{
using(ManagementObjectCollection rasds = settingData.GetRelated("Msvm_ResourceAllocationSettingData"))
foreach(ManagementObject rasd in rasds)
{
ResourceType rasdResourceType = (ResourceType)(UInt16)rasd["ResourceType"];
String rasdResourceSubType = (String)rasd["ResourceSubType"];
String rasdOtherType = (String)rasd["OtherResourceType"];
if( rasdResourceType == resourceType && rasdResourceSubType == desiredSubType )
{
return rasd;
}
}
}
return null;
}
private void AddVirtualSystemResources(ManagementObject rasd)
{
using (ManagementObject vmService = HyperV.GetManagementService())
{
ManagementBaseObject inParams = vmService.GetMethodParameters("AddVirtualSystemResources");
inParams["TargetSystem"] = _vm;
inParams["ResourceSettingsData"] = rasd.GetText(TextFormat.CimDtd20);
ManagementBaseObject outParams = vmService.InvokeMethod("AddVirtualSystemResources", inParams, options: null);
String[] addedResources = (String[])outParams["NewResources"];
OperationReturnCode returnValue = (OperationReturnCode)(UInt32)outParams["ReturnValue"];
if (returnValue == OperationReturnCode.JobStarted)
{
String jobPath = (String)outParams["Job"];
HyperV.MonitorJob(jobPath);
}
else if (returnValue == OperationReturnCode.Completed)
{
}
else
{
throw new ApplicationException( returnValue.ToString() );
}
}
}
Rather than find your problem, can I point you to an example that works?
See WmiCalls.DeployVirtualMachine in my Apache CloudStack Hyper-V plugin
Post a comment if you need additional detail, and I will update the answer.

WMI MangementObject.InvokeMethod()

I'm trying to debug a section of code, which updates the crednetials used for a Windows service, which is not functioning. It doesn't throw an exception, it just doesn't pass the check to show that it was applied.
The MSDN describes the constructor as follows:
public Object InvokeMethod(
string methodName,
Object[] args
)
What I don't understand is where it looks for the methodName string which you pass in. My assumption was it would look in the code for the Service I am trying to update. However there is no Change method present in the service the code attempts to update.
This CodeProject tutorial also seems to indicate that there is a list of valid arguments for the methodName as it uses "create" and "delete" which aren't present in it's source code.
String serviceName = "Scan Data Service";
using (ManagementObject service = new ManagementObject(new ManagementPath("Win32_Service.Name='" + serviceName + "'")))
{
Object[] wmiParams = new Object[11];
if (arguments == null)
{
wmiParams[6] = "LocalSystem";
wmiParams[7] = "";
}
else
{
wmiParams[6] = arguments[0]; //Username
wmiParams[7] = arguments[1]; //Password
}
service.InvokeMethod("Change", wmiParams);
}
SelectQuery query = new SelectQuery("select startname from Win32_Service where name = '" + serviceName + "'");
bool updated = false;
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
{
foreach (ManagementObject service in searcher.Get())
{
String startName = service["startname"].ToString();
if ((String.Compare(startName, "LocalSystem", true) == 0 && arguments == null) ||
(arguments != null && String.Compare(startName, arguments[0], true) == 0))
{
updated = true;
}
}
}
Is there a list of methodName arguments for InvokeMethod() somewhere which are valid for all services? Or is there supposed to be something implemented in my Service code which has to be somehow read or reflected?
Resolved.
It depends what you're doing with WMI, as I was working with services it utilised the Win32_Service Class the method names it was targeting belong to this.
There are of course many more Win32 Classes you can utilise methods from for different tasks.

WMI call performance

I need to make a WMI call in the constructor of my service. But when I start/restart the system this call takes significant amount of time.
I am using the following code to get the path of the windows service....
Here I've used the EnumerationOptions to improve the query performance, now in order to use it I have to use the ManagementScope which is "root\civm2", every time I've to use "root'civm2" as Management scope,
Earlier I was using managementObjectCollection.Count to know whether it contains any items or not, now to improve the performance I am using managementObjectEnumerator.MoveNext, will it help, I've commented the count related code.
Is there any better way to improve the performance of the same code...
EnumerationOptions options = new EnumerationOptions();
// options.Rewindable = false; **// I HAVE TO COMMENT OUT THIS IN ORDER TO GET THE RESULTS....**
options.ReturnImmediately = true;
string query = string.Format("SELECT PathName FROM Win32_Service WHERE Name = '{0}'", "MyService");
ManagementScope ms12 = new ManagementScope(#"root\cimv2");
ms12.Connect();
using (var managementObjectSearcher = new ManagementObjectSearcher(query))
{
managementObjectSearcher.Scope = ms12;
managementObjectSearcher.Options = options;
var managementObjectCollection = managementObjectSearcher.Get();
//if (managementObjectCollection.Count > 0)
//{
var managementObjectEnumerator = managementObjectCollection.GetEnumerator();
if (managementObjectEnumerator.MoveNext())
{
var invalidChars = new Regex(string.Format(CultureInfo.InvariantCulture, "[{0}]", Regex.Escape(new string(Path.GetInvalidPathChars()))));
var path = invalidChars.Replace(managementObjectEnumerator.Current.GetPropertyValue("PathName").ToString(), string.Empty);
Console.WriteLine(path);
}
//}
else
{
Console.WriteLine("Else part...");
}
}
Am I using the scope and EnumerationOption in correct way??
Please guide.
As the answer to your another question suggest you can build the object path of the class and use the ManagementObject directly to improve the performance , now if you want to check if the ManagementObject return an instance you can use the private property IsBound.
string ServicePath = string.Format("Win32_Service.Name=\"{0}\"", "MyService");
var WMiObject = new ManagementObject(ServicePath);
PropertyInfo PInfo = typeof(ManagementObject).GetProperty("IsBound", BindingFlags.NonPublic | BindingFlags.Instance);
if ((bool)PInfo.GetValue(WMiObject, null))
{
string PathName = (string)WMiObject.GetPropertyValue("PathName");
var invalidChars = new Regex(string.Format(CultureInfo.InvariantCulture, "[{0}]", Regex.Escape(new string(Path.GetInvalidPathChars()))));
var path = invalidChars.Replace(PathName, string.Empty);
Console.WriteLine(path);
}
else
{
Console.WriteLine("Else part...");
}
It appears that in more recent versions of the .NET framework the binding happens as late as possible. At least this was the case for me when I was testing the existence of a specific shared folder.
Here is an update to #RRUZ 's solution which uses a try-catch instead of reflecting the IsBound internal property.
var servicePath = string.Format("Win32_Service.Name=\"{0}\"", "MyService");
string pathName = null;
try
{
var wmiObject = new ManagementObject(servicePath);
pathName = (string)wmiObject.GetPropertyValue("PathName");
}
catch {}
if (pathName != null)
{
var invalidChars = new Regex(string.Format(CultureInfo.InvariantCulture, "[{0}]", Regex.Escape(new string(Path.GetInvalidPathChars()))));
var path = invalidChars.Replace(pathName, string.Empty);
Console.WriteLine(path);
}
else
{
Console.WriteLine("Else part...");
}

GroupPrincipal.Members.Remove() doesn't work with a large AD group

I'm using the System.DirectoryServices.AccountManagement namespace classes to manage the membership of several groups. These groups control the population of our print accounting system and some of them are very large. I'm running into a problem removing any user from one of these large groups. I have a test program that illustrates the problem. Note that the group I'm testing is not nested, but user.IsMemberOf() also seems to have the same problem, whereas GetAuthorizationGroups() correctly shows the groups a user is a member of. The group in question has about 81K members, which is more than it should have since Remove() isn't working, and will normally be about 65K or so.
I'd be interested to hear from other people who have had this problem and have resolved it. I've got an open case with Microsoft, but the turn around on the call is slow since the call center is about 17 hours time difference so they don't arrive for work until about an hour before I usually leave for home.
using (var context = new PrincipalContext( ContextType.Domain ))
{
using (var group = GroupPrincipal.FindByIdentity( context, groupName ))
{
using (var user = UserPrincipal.FindByIdentity( context, userName ))
{
if (user != null)
{
var isMember = user.GetAuthorizationGroups()
.Any( g => g.DistinguishedName == group.DistinguishedName );
Console.WriteLine( "1: check for membership returns: {0}", isMember );
if (group.Members.Remove( user ))
{
Console.WriteLine( "user removed successfully" );
group.Save();
}
else
{
// do save in case Remove() is lying to me
group.Save();
Console.WriteLine( "user remove failed" );
var isStillMember = user.GetAuthorizationGroups()
.Any( g => g.DistinguishedName == group.DistinguishedName );
Console.WriteLine( "2: check for membership returns: {0}", isStillMember );
}
}
}
}
}
Turns out this is a bug in the GroupPrincipal.Members.Remove() code in which remove fails for a group with more than 1500 members. This has been fixed in .NET 4.0 Beta 2. I don't know if they have plans to back port the fix into 2.0/3.x.
The work around is to get the underlying DirectoryEntry, then use Invoke to execute the Remove command on the IADsGroup object.
var entry = group.GetUnderlyingObject() as DirectoryEntry;
var userEntry = user.GetUnderlyingObject() as DirectoryEntry;
entry.Invoke( "Remove", new object[] { userEntry.Path } );
This post helped point me in the right direction, just wanted to add some addition info.
It also works binding directly to the group, and you can use it for adding group members.
using (var groupEntry = new DirectoryEntry(groupLdapPath))
{
groupEntry.Invoke("remove", new object[] { memberLdapPath });
groupEntry.Invoke("add", new object[] { memberLdapPath });
}
Also be aware, with the standard 'member' attribute, you use the user or group distinguishedName, but invoke requires the path with LDAP:// prefix, otherwise it throws a vague InnerException:
Exception from HRESULT: 0x80005000
public bool RemoveUserFromGroup(string UserName, string GroupName)
{
bool lResult = false;
if (String.IsNullOrEmpty(UserName) || String.IsNullOrEmpty(GroupName)) return lResult;
try
{
using (DirectoryEntry dirEntry = GetDirectoryEntry())
{
using (DirectoryEntry dirUser = GetUser(UserName))
{
if (dirEntry == null || dirUser == null)
{
return lResult;
}
using (DirectorySearcher deSearch = new DirectorySearcher())
{
deSearch.SearchRoot = dirEntry;
deSearch.Filter = String.Format("(&(objectClass=group) (cn={0}))", GroupName);
deSearch.PageSize = 1000;
SearchResultCollection result = deSearch.FindAll();
bool isAlreadyRemoved = false;
String sDN = dirUser.Path.Replace("LDAP://", String.Empty);
if (result != null && result.Count > 0)
{
for (int i = 0; i < result.Count; i++)
{
using (DirectoryEntry dirGroup = result[i].GetDirectoryEntry())
{
String sGrDN = dirGroup.Path.Replace("LDAP://", String.Empty);
if (dirUser.Properties[Constants.Properties.PROP_MEMBER_OF].Contains(sGrDN))
{
dirGroup.Properties[Constants.Properties.PROP_MEMBER].Remove(sDN);
dirGroup.CommitChanges();
dirGroup.Close();
lResult = true;
isAlreadyRemoved = true;
break;
}
}
if (isAlreadyRemoved)
break;
}
}
}
}
}
}
catch
{
lResult= false;
}
return lResult;
}

Categories