Memory Leak when using PrincipalSearcher.FindAll() - c#

I too have a long running service using plugins and appdomains and am having a memory leak due to using directoryservices. Note that I am using system.directoryservices.accountmanagement but it is my understanding that it uses the same underlying ADSI API's and hence is prone to the same memory leaks.
I've looked at all the CLR memory counters and the memory isn't being leaked there, and is all returned either on a forced GC or when I unload the appdomain. The leak is in private bytes which continually grow. I searched on here and have seen some issues related to a memory leak when using the ADSI API's but they seem to indicate that simply iterating over the directorysearcher fixes the problem. But as you can see in the code below, I am doing that in a foreach block and still the memory is being leaked. Any suggestions? Here is my method:
public override void JustGronkIT()
{
using (log4net.ThreadContext.Stacks["NDC"].Push(GetMyMethodName()))
{
Log.Info("Inside " + GetMyMethodName() + " Method.");
System.Configuration.AppSettingsReader reader = new System.Configuration.AppSettingsReader();
//PrincipalContext AD = null;
using (PrincipalContext AD = new PrincipalContext(ContextType.Domain, (string)reader.GetValue("Domain", typeof(string))))
{
UserPrincipal u = new UserPrincipal(AD);
u.Enabled = true;
//u.Surname = "ju*";
using (PrincipalSearcher ps = new PrincipalSearcher(u))
{
myADUsers = new ADDataSet();
myADUsers.ADUsers.MinimumCapacity = 60000;
myADUsers.ADUsers.CaseSensitive = false;
foreach (UserPrincipal result in ps.FindAll())
{
myADUsers.ADUsers.AddADUsersRow(result.SamAccountName, result.GivenName, result.MiddleName, result.Surname, result.EmailAddress, result.VoiceTelephoneNumber,
result.UserPrincipalName, result.DistinguishedName, result.Description);
}
ps.Dispose();
}
Log.Info("Number of users: " + myADUsers.ADUsers.Count);
AD.Dispose();
u.Dispose();
}//using AD
}//Using log4net
}//JustGronkIT
I made the following changes to the foreach loop and it's better but private bytes still grows and is never reclaimed.
foreach (UserPrincipal result in ps.FindAll())
{
using (result)
{
try
{
myADUsers.ADUsers.AddADUsersRow(result.SamAccountName, result.GivenName, result.MiddleName, result.Surname, result.EmailAddress, result.VoiceTelephoneNumber, result.UserPrincipalName, result.DistinguishedName, result.Description);
result.Dispose();
}
catch
{
result.Dispose();
}
}
}//foreach

I hit a big memory leak because, like you I wrote something like...
foreach (GroupPrincipal result in searcher.FindAll())
{
results.Add(result.Name);
}
But the trick is that FindAll itself returns an object that must be disposed...
using (var searchResults = searcher.FindAll())
{
foreach (GroupPrincipal result in searchResults)
{
results.Add(result.Name);
}
}

I'm fairly sure that this is a known error ( http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/6a09b8ff-2687-40aa-a278-e76576c458e0 ).
The workaround? Use the DirectoryServices library...

I spoke too soon, simply being aggressive with calling Dispose() did NOT solve the problem over the long run. The real solution? Stop using both directoryservices and directoryservices.accountmanagement and use System.DirectoryServices.Protocols instead and do a paged search of my domain because there's no leak on Microsoft's side for that assembly.
As requested, here's some code to illustrate the solution I came up with. Note that I also use a plugin architecture and appDomain's and I unload the appdomain when I am done with it, though I think given that there's no leak in DirectoryServices.Protocols you don't have to do that. I only did it because I thought using appDomains would solve my problem, but since it wasn't a leak in managed code but in un-managed code, it didn't do any good.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices.Protocols;
using System.Data.SqlClient;
using System.Data;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Text.RegularExpressions;
using log4net;
using log4net.Config;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
namespace ADImportPlugIn {
public class ADImport : PlugIn
{
private ADDataSet myADUsers = null;
LdapConnection _LDAP = null;
MDBDataContext mdb = null;
private Orgs myOrgs = null;
public override void JustGronkIT()
{
string filter = "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))";
string tartgetOU = #"yourdomain.com";
string[] attrs = {"sAMAccountName","givenName","sn","initials","description","userPrincipalName","distinguishedName",
"extentionAttribute6","departmentNumber","wwwHomePage","manager","extensionName", "mail","telephoneNumber"};
using (_LDAP = new LdapConnection(Properties.Settings.Default.Domain))
{
myADUsers = new ADDataSet();
myADUsers.ADUsers.MinimumCapacity = 60000;
myADUsers.ADUsers.CaseSensitive = false;
try
{
SearchRequest request = new SearchRequest(tartgetOU, filter, System.DirectoryServices.Protocols.SearchScope.Subtree, attrs);
PageResultRequestControl pageRequest = new PageResultRequestControl(5000);
request.Controls.Add(pageRequest);
SearchOptionsControl searchOptions = new SearchOptionsControl(System.DirectoryServices.Protocols.SearchOption.DomainScope);
request.Controls.Add(searchOptions);
while (true)
{
SearchResponse searchResponse = (SearchResponse)_LDAP.SendRequest(request);
PageResultResponseControl pageResponse = (PageResultResponseControl)searchResponse.Controls[0];
foreach (SearchResultEntry entry in searchResponse.Entries)
{
string _myUserid="";
string _myUPN="";
SearchResultAttributeCollection attributes = entry.Attributes;
foreach (DirectoryAttribute attribute in attributes.Values)
{
if (attribute.Name.Equals("sAMAccountName"))
{
_myUserid = (string)attribute[0] ?? "";
_myUserid.Trim();
}
if (attribute.Name.Equals("userPrincipalName"))
{
_myUPN = (string)attribute[0] ?? "";
_myUPN.Trim();
}
//etc with each datum you return from AD
}//foreach DirectoryAttribute
//do something with all the above info, I put it into a dataset
}//foreach SearchResultEntry
if (pageResponse.Cookie.Length == 0)//check and see if there are more pages
break; //There are no more pages
pageRequest.Cookie = pageResponse.Cookie;
}//while loop
}//try
catch{}
}//using _LDAP
}//JustGronkIT method
}//ADImport class
} //namespace

UserPrincipal implements IDisposable. Try calling Dispose on result inside the foreach loop.
I also found this SO question, but there was no agreement on the answer.

After much frustration and some hints gathered here I came up with a solution. I also discovered an interesting thing about a difference in how using a using block with a DirectoryServices resource vs a DataContext as noted in the code snippet below. I probably don't need to use a Finalizer but I did so anyway just to be safe. I have found that by doing what is outlined below, my memory is stable across runs whereas before I would have to kill the application twice a day to free resources.
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
namespace myPlugins
{
public class ADImport : Plugin
{
//I defined these outside my method so I can call a Finalizer before unloading the appDomain
private PrincipalContext AD = null;
private PrincipalSearcher ps = null;
private DirectoryEntry _LDAP = null; //used to get underlying LDAP properties for a user
private MDBDataContext _db = null; //used to connect to a SQL server, also uses unmanaged resources
public override GronkIT()
{
using (AD = new PrincipalContext(ContextType.Domain,"my.domain.com"))
{
UserPrincipal u = new UserPrincipal(AD);
u.Enabled=true;
using(ps = new PrincipalSearcher(u))
{
foreach(UserPrincipal result in ps.FindAll())
{
using (result)
{
_LDAP = (DirectoryEntry)result.GetUnderlyingObject();
//do stuff with result
//do stuff with _LDAP
result.Dispose(); //even though I am using a using block, if I do not explicitly call Dispose, it's never disposed of
_LDAP.Dispose(); //even though I am using a using block, if I do not explicitly call Dispose, it's never disposed of
}
}
}
}
}
public override JustGronkIT()
{
using(_db = new MDBDataContext("myconnectstring"))
{
//do stuff with SQL
//Note that I am using a using block and connections to SQL are properly disposed of when the using block ends
}
}
~ADImport()
{
AD.Dispose(); //This works, does not throw an exception
AD = null;
ps.Dispose(); //This works, does not throw an exception
ps = null;
_LDAP.Dispose(); //This works, does not throw an exception
_LDAP = null;
_db.Dispose(); //This throws an exception saying that you can not call Dispose on an already disposed of object
}
}
}

That code works fine for me. I just dispose every instance. In my project i call this method every two minutes. After i call garbage collector outside.
public class AdUser
{
public string SamAccountName { get; set; }
public string DisplayName { get; set; }
public string Mail { get; set; }
}
public List<AdUser> GetAllUsers()
{
List<AdUser> users = new List<AdUser>();
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, Environment.UserDomainName))
{
using PrincipalSearcher searcher = new PrincipalSearcher(new UserPrincipal(context));
using PrincipalSearchResult<Principal> allResults = searcher.FindAll();
foreach (Principal result in allResults)
{
using DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
AdUser user = new AdUser()
{
SamAccountName = (string)de.Properties["samAccountName"].Value,
DisplayName = (string)de.Properties["displayName"].Value,
Mail = (string)de.Properties["mail"].Value
};
users.Add(user);
result.Dispose();
}
}
return users;
}
First few iterations of calling method above there are seems to be a memory allocation, but after that it's not leaking.
Consider calling memory clean up after each iteration.
GC.Collect();

Related

Obtaining Google e-mail signature block for signed-in user using c#

Converting a VB.Net application to c#. Having an issue with the following code. The function authenticates a user with the Google web service and attempts to obtain the user's e-mail address and signature block to be used in later code.
In VB.Net this all seems to work correctly. In c#, the line foreach (var itm in result.SendAs) generates an error
'Func' does not contain a definition for 'SendAs' and no accessible extension method 'SendAs' accepting a first argument of type 'Func' could be found.
What am I missing?
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Gmail.v1;
using Google.Apis.Services;
using Google.Apis.Util.Store;
...
public static async Task<bool> DoAuthenticationAsync()
{
ClientSecrets secrets;
using (var strm = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
{
secrets = GoogleClientSecrets.FromStream(strm).Secrets;
}
try
{
// this is the magic black box that does the authenticating.
Common.credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(secrets, Common.googleScopes, "user", CancellationToken.None, new FileDataStore("Google.API.Auth", false));
var init = new BaseClientService.Initializer();
init.HttpClientInitializer = Common.credential;
var svc = new GmailService(init);
// this grabs the list of all e-mail aliases for the signed-in user and selects the primary
var result = svc.Users.Settings.SendAs.List("me").Execute;
foreach (var itm in result.SendAs)
{
if (itm.IsPrimary.HasValue)
{
if (itm.IsPrimary)
{
// save as the signature blob to use.
Common.mySignature = itm.Signature;
Common.myEMail = itm.SendAsEmail;
break;
}
}
}
}
catch (Exception)
{
return false;
}
return true;
}
Not entirely sure why this works, but, I modified the above code block by as follows and the problem resolved itself.
public static async Task<bool> DoAuthenticationAsync()
{
ClientSecrets secrets;
// this obtains the application secrets data we need to indicate our application is asking to authenticate.
using (var strm = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
{
secrets = GoogleClientSecrets.FromStream(strm).Secrets;
}
try
{
// this is the magic black box that does the authenticating.
Common.credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(secrets, Common.googleScopes, "user", CancellationToken.None, new FileDataStore("Google.API.Auth", false));
var init = new BaseClientService.Initializer();
init.HttpClientInitializer = Common.credential;
var svc = new GmailService(init);
// this grabs the list of all e-mail aliases for the signed-in user and selects the primary
ListSendAsResponse result = svc.Users.Settings.SendAs.List("me").Execute();
foreach (SendAs itm in result.SendAs)
{
if (itm.IsPrimary.HasValue)
{
if (itm.IsPrimary == true)
{
// save as the signature blob to use.
Common.mySignature = itm.Signature;
Common.myEMail = itm.SendAsEmail;
break;
}
}
}
}
catch (Exception)
{
return false;
}
return true;
}
This change is simply a forcing of a couple variables into concrete types instead of using the "var" declarations.

Dynamic Compilation in .NET Core 6

I have been experimenting with the C# dynamic compilation as described in Laurent's excellent blog: https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/ (Merci Laurent!!)
I copied and pasted the code into a single file and all in the Main method to understand the control flow better.
I have however been unable to work out why the unloading of the DLL consistently fails (i.e. the WeakReference is still live). Laurent's code (as published on GitHub) does unload the DLL while my copy-pasted monolithic code does not.
Could someone help me spot where I have gone wrong?
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace CoreCompile
{
public class CompilerTest
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
string sourcePath = args.Length > 0 ? args[0] : #"D:\DynamicRun\Sources\DynamicProgram.cs";
string sourceCode = File.ReadAllText(sourcePath);
string assemblyPath = Path.ChangeExtension(Path.GetFileNameWithoutExtension(sourcePath), "DLL");
var codeString = SourceText.From(sourceCode);
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10);
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);
var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location)
};
Assembly.GetEntryAssembly()?.GetReferencedAssemblies().ToList()
.ForEach(a => references.Add(MetadataReference.CreateFromFile(Assembly.Load(a).Location)));
var csCompilation = CSharpCompilation.Create(assemblyPath,
new[] { parsedSyntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.ConsoleApplication,
optimizationLevel: OptimizationLevel.Release,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
WeakReference assemblyLoadContextWeakRef = null;
using (var peStream = new MemoryStream())
{
var result = csCompilation.Emit(peStream);
if (result.Success)
{
Console.WriteLine("Compilation done without any error.");
peStream.Seek(0, SeekOrigin.Begin);
var compiledAssembly = peStream.ToArray();
string[] arguments = new[] { "France" };
using (var asm = new MemoryStream(compiledAssembly))
{
var assemblyLoadContext = new SimpleUnloadableAssemblyLoadContext();
var assembly = assemblyLoadContext.LoadFromStream(asm);
var entry = assembly.EntryPoint;
_ = entry != null && entry.GetParameters().Length > 0
? entry.Invoke(null, new object[] { arguments })
: entry.Invoke(null, null);
assemblyLoadContext.Unload();
assemblyLoadContextWeakRef = new WeakReference(assemblyLoadContext);
} // using
}
else
{
Console.WriteLine("Compilation done with error.");
var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
foreach (var diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
} // using
if (assemblyLoadContextWeakRef != null)
{
for (var i = 0; i < 8 && assemblyLoadContextWeakRef.IsAlive; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine(assemblyLoadContextWeakRef.IsAlive ? "Unloading failed!" : "Unloading success!");
}
} // Main
} // class
internal class SimpleUnloadableAssemblyLoadContext : AssemblyLoadContext
{
public SimpleUnloadableAssemblyLoadContext()
: base(true)
{
}
protected override Assembly Load(AssemblyName assemblyName)
{
return null;
}
}
} // namespace
Forcing objects to be garbage collected is something of a dark art. You need to ensure that the garbage collector will not be able to locate any variables from your Main method, otherwise the objects will be kept alive.
For example, a debug build and a release build will behave differently, as release builds will throw away variables as soon as they are no longer required.
From your example, your local variable assemblyLoadContext will still be in scope, particularly in a debug build. As you might place a break point at the end of the method in order to examine any local variable.
Perhaps the simplest thing you could do is move most of your code to a separate method. Once that method returns, all local variables should be out of scope and undetectable by the garbage collector.

CRM Plugin will not update or do anything to change a fields value

So I have written a plugin to do some simple calculations and and update fields based upon certain conditions. The plugin compiles and doesn't cause any errors while profiling or create any instances where I can debug my code which is frustrating. Anyways without further ado here it is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BPT.PluginCommon.BaseClasses;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Xrm;
namespace Engage.Crm.Plugins
{
public class VoidPayment : BPTPluginBase
{
bpt_DuesHeader oDuesHeader = new bpt_DuesHeader();
Account org = new Account();
public override void HandleAfterOp()
{
try
{
base.HandleAfterOp();
var crmContext = new XrmServiceContext(this.OrganizationService);
if (this.PluginExecutionContext.MessageName == MessageName.Create ||
this.PluginExecutionContext.MessageName == MessageName.Update)
{
if (this.InputTargetEntity.Attributes.Contains("gih_void"))
{
var Void = (bool) this.InputTargetEntity.Attributes["gih_void"];
var voidReason = (OptionSetValue) this.InputTargetEntity.Attributes["gih_voidreason"];
var totalPayments = (Money) this.InputTargetEntity.Attributes["bpt_TotalPayments"];
var amountBilled =
crmContext.bpt_DuesHeaderSet.Where(
o => o.bpt_DuesHeaderId == this.PluginExecutionContext.PrimaryEntityId)
.ToList()
.Sum(o => o.bpt_TotalAmountBilled == null ? 0 : o.bpt_TotalAmountBilled.Value);
if (Void)
{
this.oDuesHeader.bpt_TotalAdjustments = new Money(amountBilled);
this.oDuesHeader.bpt_TotalAmountBilled =
new Money(oDuesHeader.bpt_TotalAdjustments.Value + totalPayments.Value);
this.oDuesHeader.bpt_Balance = new Money(amountBilled);
if (voidReason.Value == 914020000)
//should be dropped not default option
{
oDuesHeader.gih_terminationdate = DateTime.Now;
}
}
OrganizationService.Update(oDuesHeader);
}
}
}
catch (Exception ex)
{
this.TracingService.Trace(this.ToString() + " {0}", "Exception: {0}", ex.ToString());
throw;
}
}
}
}
Sorry code is not formating well! Help! The plugin is registered as post-operation and synchronous. Any insight would be helpful and if a moderator could help format the code that would be greatly appreciated because it is not letting me add four spaces in certain places.
Plugins in CRM are created once, and then used multiple time, maybe even simultaneously, so besides setting the Id, don't use class level fields. You're creating a race condition that could really do some unwanted changes.

Notify when new appdomain created in the process

Consider next situation. I have injected my managed dll into the process using EasyHook. EasyHook injects dll using separate AppDomain. Now I need a way to get notifications about creation of new AppDomain in the current process.
So the question is there a way do get notifications when a new AppDomain was created in the process?
There is no event or easy way to do it, there is a COM interrupt that allows you to get a list of app domains loaded but any events etc are all hidden from us on private interfaces.
There is two ways you could do this but both require you to actively seek the information i.e. there is no event to register too.
Using Performance Counters.
Using mscoree COM interrupt.
Both there options can complement each other but it depends what level of information you need.
Using Performance Counters
CLR has numerous performance counters available but the one we care about resides in the category ".Net CLR Loading" and it is the counter called "Total Appdomains".
Using the System.Diagnostics namespace you can get the number of app domains per instance/process running in you machine.
Like the code below:
PerformanceCounter toPopulate = new PerformanceCounter(".Net CLR Loading", "Total Appdomains", "ConsoleApplication2.vshost", true);
Console.WriteLine("App domains listed = {0}", toPopulate.NextValue().ToString());
(please note the example needs the application instance name if you create your own app make sure to change this)
You can wrap this on a loop and raise an even for your app when the number changes.
(Not elegant but there is no way around it at the moment)
Using mscoree COM interrupt
Further more if you want to List all the app domains in a process you need to make use the MSCOREE.TBL library which is a COM library used by the CLRHost.
You can find the library at C:\WINDOWS\Microsoft.NET\Framework\vXXXXXX\mscoree.tlb
using mscoree;
If you are using it on window 7 or above you must make sure that the embed assembly type in the reference properties is turned off as this assembly can not be embedded like that.
See further information on this stack post: Interop type cannot be embedded
See the code below to see how you can return and list all app domains in a process (this will return the actual AppDomain instances for each app domain).
The original stack post for this can be found here: List AppDomains in Process
public static List<AppDomain> GetAppDomains()
{
List<AppDomain> _IList = new List<AppDomain>();
IntPtr enumHandle = IntPtr.Zero;
CorRuntimeHostClass host = new mscoree.CorRuntimeHostClass();
try
{
host.EnumDomains(out enumHandle);
object domain = null;
while (true)
{
host.NextDomain(enumHandle, out domain);
if (domain == null) break;
AppDomain appDomain = (AppDomain)domain;
_IList.Add(appDomain);
}
return _IList;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
return null;
}
finally
{
host.CloseEnum(enumHandle);
Marshal.ReleaseComObject(host);
}
}
Now that you can see how many app domains exist in a process and list them let put that to the test.
Below is a fully working example using both techniques.
using System;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.Linq;
using System.Printing;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Xps.Packaging;
using System.Runtime.InteropServices;
using mscoree;
using System.Diagnostics;
using System.Threading;
namespace ConsoleApplication2
{
class AppDomainWorker
{
public void DoSomeWork()
{
while (true)
{
for (int i = 0; i < 1000; i++)
{
var hello = "hello world".GetHashCode();
}
Thread.Sleep(500);
}
}
}
class Program
{
[STAThread]
static void Main(string[] args)
{
PerformanceCounter toPopulate = new PerformanceCounter(".Net CLR Loading", "Total Appdomains", "ConsoleApplication2.vshost", true);
Console.WriteLine("App domains listed = {0}", toPopulate.NextValue().ToString());
for (int i = 0; i < 10; i++)
{
AppDomain domain = AppDomain.CreateDomain("App Domain " + i);
domain.DoCallBack(() => new Thread(new AppDomainWorker().DoSomeWork).Start());
Console.WriteLine("App domains listed = {0}", toPopulate.NextValue().ToString());
}
Console.WriteLine("List all app domains");
GetAppDomains().ForEach(a => {
Console.WriteLine(a.FriendlyName);
});
Console.WriteLine("running, press any key to stop");
Console.ReadKey();
}
public static List<AppDomain> GetAppDomains()
{
List<AppDomain> _IList = new List<AppDomain>();
IntPtr enumHandle = IntPtr.Zero;
CorRuntimeHostClass host = new mscoree.CorRuntimeHostClass();
try
{
host.EnumDomains(out enumHandle);
object domain = null;
while (true)
{
host.NextDomain(enumHandle, out domain);
if (domain == null) break;
AppDomain appDomain = (AppDomain)domain;
_IList.Add(appDomain);
}
return _IList;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
return null;
}
finally
{
host.CloseEnum(enumHandle);
Marshal.ReleaseComObject(host);
}
}
}
}
I hope this is helpful and if you need any further help let us know.

check if WMI namespace exists from c#

I want to check if a certain feature is installed on a certain machine.
I have a powershell code that checks this, and now I want to check this from .net code.
I can see that in the cmdlet, the code checks if there is an invalid namespace error.
When searching the web, I found the following code:
ManagementClass myClass = new ManagementClass(scope, path, getOptions);
try
{
myClass.get();
}
catch (System.Management.Exception ex)
{
if (ex.ErrorCode == ManagementStatus.InvalidNamespace)
{
return true;
}
}
...
I want to clean this code a bit, so basically I have 2 questions:
Is there another way to check for an InvalidNamespace error? (The code I've copied was later used to invoke some method within myClass, so I wonder if I can somehow achieve my goal in a more direct way)
Do I really need the parameter getOptions?
To get all the wmi namespaces, you must first connect to the root namespace and then query for all the __NAMESPACE instances, and for each instance recursively repeat this process. about the getOptions parameter which is a ObjectGetOptions class is not necessary in this case, so can be null.
Check this code to get all the wmi namespaces (you can populate a list with that info and then check if the namespace exist in the machine)
using System;
using System.Collections.Generic;
using System.Text;
using System.Management;
namespace MyConsoleApplication
{
class Program
{
static private void GetWmiNameSpaces(string root)
{
try
{
ManagementClass nsClass = new ManagementClass( new ManagementScope(root), new ManagementPath("__namespace"), null);
foreach (ManagementObject ns in nsClass.GetInstances())
{
string namespaceName = root + "\\" + ns["Name"].ToString();
Console.WriteLine(namespaceName);
//call the funcion recursively
GetWmiNameSpaces(namespaceName);
}
}
catch (ManagementException e)
{
Console.WriteLine(e.Message);
}
}
static void Main(string[] args)
{
//set the initial root to search
GetWmiNameSpaces("root");
Console.ReadKey();
}
}
}

Categories

Resources