Other Posts in LDAP

  1. Getting a User's Email Address from Active Directory
  2. Getting Names and Email Addresses from Active Directory Groups
  3. Getting All Contact Information from Active Directory
  4. Active Directory Utility for C#/ASP.Net
  5. Scanning a Network for Computers in C#

Scanning a Network for Computers in C#

5/3/2010

I was tasked recently with creating an app that would scan a network for any and all computers in it using C#. Well not exactly... It was more anything in our domain, but for the most part that should be anything on our network. Anyway, it turns out that it's extremely simple and really only took 3 steps:

  • Get the list of computer names from Active Directory
  • Do a DNS lookup on those names
  • And report the results

That's all that I needed to do. It was especially easy because of some helper classes that I had laying around to deal with AD. A while back I posted two classes to help with AD. They're a bit out of date as I've added extra functionality since then, the newer version of the classes looks like this:

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Collections.Generic;
  25: using System.DirectoryServices;
  26: #endregion
  27:  
  28: namespace Utilities.LDAP
  29: {
  30:     /// <summary>
  31:     /// Class for helping with AD
  32:     /// </summary>
  33:     public class Directory : IDisposable
  34:     {
  35:         #region Constructors
  36:         /// <summary>
  37:         /// Constructor
  38:         /// </summary>
  39:         /// <param name="UserName">User name used to log in</param>
  40:         /// <param name="Password">Password used to log in</param>
  41:         /// <param name="Path">Path of the LDAP server</param>
  42:         /// <param name="Query">Query to use in the search</param>
  43:         public Directory(string Query, string UserName, string Password, string Path)
  44:         {
  45:             Entry = new DirectoryEntry(Path, UserName, Password, AuthenticationTypes.Secure);
  46:             this.Path = Path;
  47:             this.UserName = UserName;
  48:             this.Password = Password;
  49:             this.Query = Query;
  50:             Searcher = new DirectorySearcher(Entry);
  51:             Searcher.Filter = Query;
  52:             Searcher.PageSize = 1000;
  53:         }
  54:         #endregion
  55:  
  56:         #region Public Functions
  57:         /// <summary>
  58:         /// Finds a user by his user name
  59:         /// </summary>
  60:         /// <param name="UserName">User name to search by</param>
  61:         /// <returns>The user's entry</returns>
  62:         public Entry FindUserByUserName(string UserName)
  63:         {
  64:             List<Entry> Entries = FindUsers("samAccountName=" + UserName);
  65:             if (Entries.Count > 0)
  66:             {
  67:                 return Entries[0];
  68:             }
  69:             return null;
  70:         }
  71:  
  72:         /// <summary>
  73:         /// Finds all active users
  74:         /// </summary>
  75:         /// <param name="Filter">Filter used to modify the query</param>
  76:         /// <param name="args">Additional arguments (used in string formatting</param>
  77:         /// <returns>A list of all active users' entries</returns>
  78:         public List<Entry> FindActiveUsers(string Filter, params object[] args)
  79:         {
  80:             Filter = string.Format(Filter, args);
  81:             Filter = string.Format("(&((userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$)))({0}))", Filter);
  82:             return FindUsers(Filter);
  83:         }
  84:  
  85:         /// <summary>
  86:         /// Finds all users
  87:         /// </summary>
  88:         /// <param name="Filter">Filter used to modify the query</param>
  89:         /// <param name="args">Additional arguments (used in string formatting</param>
  90:         /// <returns>A list of all users meeting the specified Filter</returns>
  91:         public List<Entry> FindUsers(string Filter, params object[] args)
  92:         {
  93:             Filter = string.Format(Filter, args);
  94:             Filter = string.Format("(&(objectClass=User)(objectCategory=Person)({0}))", Filter);
  95:             Searcher.Filter = Filter;
  96:             return FindAll();
  97:         }
  98:  
  99:         /// <summary>
 100:         /// Finds all computers
 101:         /// </summary>
 102:         /// <param name="Filter">Filter used to modify the query</param>
 103:         /// <param name="args">Additional arguments (used in string formatting</param>
 104:         /// <returns>A list of all computers meeting the specified Filter</returns>
 105:         public List<Entry> FindComputers(string Filter, params object[] args)
 106:         {
 107:             Filter = string.Format(Filter, args);
 108:             Filter = string.Format("(&(objectClass=computer)({0}))", Filter);
 109:             Searcher.Filter = Filter;
 110:             return FindAll();
 111:         }
 112:  
 113:         /// <summary>
 114:         /// Finds all active users and groups
 115:         /// </summary>
 116:         /// <param name="Filter">Filter used to modify the query</param>
 117:         /// <param name="args">Additional arguments (used in string formatting</param>
 118:         /// <returns>A list of all active groups' entries</returns>
 119:         public List<Entry> FindActiveUsersAndGroups(string Filter, params object[] args)
 120:         {
 121:             Filter = string.Format(Filter, args);
 122:             Filter = string.Format("(&((userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$)))({0}))", Filter);
 123:             return FindUsersAndGroups(Filter);
 124:         }
 125:  
 126:         /// <summary>
 127:         /// Finds all users and groups
 128:         /// </summary>
 129:         /// <param name="Filter">Filter used to modify the query</param>
 130:         /// <param name="args">Additional arguments (used in string formatting</param>
 131:         /// <returns>A list of all users and groups meeting the specified Filter</returns>
 132:         public List<Entry> FindUsersAndGroups(string Filter, params object[] args)
 133:         {
 134:             Filter = string.Format(Filter, args);
 135:             Filter = string.Format("(&(|(&(objectClass=Group)(objectCategory=Group))(&(objectClass=User)(objectCategory=Person)))({0}))", Filter);
 136:             Searcher.Filter = Filter;
 137:             return FindAll();
 138:         }
 139:  
 140:         /// <summary>
 141:         /// Finds all active groups
 142:         /// </summary>
 143:         /// <param name="Filter">Filter used to modify the query</param>
 144:         /// <param name="args">Additional arguments (used in string formatting</param>
 145:         /// <returns>A list of all active groups' entries</returns>
 146:         public List<Entry> FindActiveGroups(string Filter, params object[] args)
 147:         {
 148:             Filter = string.Format(Filter, args);
 149:             Filter = string.Format("(&((userAccountControl:1.2.840.113556.1.4.803:=512)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(cn=*$)))({0}))", Filter);
 150:             return FindGroups(Filter);
 151:         }
 152:  
 153:         /// <summary>
 154:         /// Finds all groups
 155:         /// </summary>
 156:         /// <param name="Filter">Filter used to modify the query</param>
 157:         /// <param name="args">Additional arguments (used in string formatting</param>
 158:         /// <returns>A list of all groups meeting the specified Filter</returns>
 159:         public List<Entry> FindGroups(string Filter, params object[] args)
 160:         {
 161:             Filter = string.Format(Filter, args);
 162:             Filter = string.Format("(&(objectClass=Group)(objectCategory=Group)({0}))", Filter);
 163:             Searcher.Filter = Filter;
 164:             return FindAll();
 165:         }
 166:  
 167:         /// <summary>
 168:         /// Returns a group's list of members
 169:         /// </summary>
 170:         /// <param name="GroupName">The group's name</param>
 171:         /// <returns>A list of the members</returns>
 172:         public List<Utilities.LDAP.Entry> FindActiveGroupMembers(string GroupName)
 173:         {
 174:             try
 175:             {
 176:                 List<Utilities.LDAP.Entry> Entries = this.FindGroups("cn=" + GroupName);
 177:                 if (Entries.Count < 1)
 178:                     return new List<Utilities.LDAP.Entry>();
 179:  
 180:                 return this.FindActiveUsersAndGroups("memberOf=" + Entries[0].DistinguishedName);
 181:             }
 182:             catch
 183:             {
 184:                 return new List<Utilities.LDAP.Entry>();
 185:             }
 186:         }
 187:  
 188:         /// <summary>
 189:         /// Finds all entries that match the query
 190:         /// </summary>
 191:         /// <returns>A list of all entries that match the query</returns>
 192:         public List<Entry> FindAll()
 193:         {
 194:             List<Entry> ReturnedResults = new List<Entry>();
 195:             SearchResultCollection Results = Searcher.FindAll();
 196:             foreach (SearchResult Result in Results)
 197:             {
 198:                 ReturnedResults.Add(new Entry(Result.GetDirectoryEntry()));
 199:             }
 200:             Results.Dispose();
 201:             return ReturnedResults;
 202:         }
 203:  
 204:         /// <summary>
 205:         /// Finds one entry that matches the query
 206:         /// </summary>
 207:         /// <returns>A single entry matching the query</returns>
 208:         public Entry FindOne()
 209:         {
 210:             SearchResult Result = Searcher.FindOne();
 211:             return new Entry(Result.GetDirectoryEntry());
 212:         }
 213:  
 214:         /// <summary>
 215:         /// Closes the directory
 216:         /// </summary>
 217:         public void Close()
 218:         {
 219:             Entry.Close();
 220:         }
 221:  
 222:         /// <summary>
 223:         /// Checks to see if the person was authenticated
 224:         /// </summary>
 225:         /// <returns>true if they were authenticated properly, false otherwise</returns>
 226:         public bool Authenticate()
 227:         {
 228:             try
 229:             {
 230:                 if (!Entry.Guid.ToString().ToLower().Trim().Equals(""))
 231:                 {
 232:                     return true;
 233:                 }
 234:             }
 235:             catch
 236:             {
 237:             }
 238:             return false;
 239:         }
 240:         #endregion
 241:  
 242:         #region Properties
 243:         /// <summary>
 244:         /// Path of the server
 245:         /// </summary>
 246:         public string Path
 247:         {
 248:             get { return _Path; }
 249:             set
 250:             {
 251:                 _Path = value;
 252:                 if (Entry != null)
 253:                 {
 254:                     Entry.Close();
 255:                 }
 256:                 Entry = new DirectoryEntry(_Path, _UserName, _Password, AuthenticationTypes.Secure);
 257:                 Searcher = new DirectorySearcher(Entry);
 258:                 Searcher.Filter = Query;
 259:                 Searcher.PageSize = 1000;
 260:             }
 261:         }
 262:  
 263:         /// <summary>
 264:         /// User name used to log in
 265:         /// </summary>
 266:         public string UserName
 267:         {
 268:             get { return _UserName; }
 269:             set
 270:             {
 271:                 _UserName = value;
 272:                 if (Entry != null)
 273:                 {
 274:                     Entry.Close();
 275:                 }
 276:                 Entry = new DirectoryEntry(_Path, _UserName, _Password, AuthenticationTypes.Secure);
 277:                 Searcher = new DirectorySearcher(Entry);
 278:                 Searcher.Filter = Query;
 279:                 Searcher.PageSize = 1000;
 280:             }
 281:         }
 282:  
 283:         /// <summary>
 284:         /// Password used to log in
 285:         /// </summary>
 286:         public string Password
 287:         {
 288:             get { return _Password; }
 289:             set
 290:             {
 291:                 _Password = value;
 292:                 if (Entry != null)
 293:                 {
 294:                     Entry.Close();
 295:                 }
 296:                 Entry = new DirectoryEntry(_Path, _UserName, _Password, AuthenticationTypes.Secure);
 297:                 Searcher = new DirectorySearcher(Entry);
 298:                 Searcher.Filter = Query;
 299:                 Searcher.PageSize = 1000;
 300:             }
 301:         }
 302:  
 303:         /// <summary>
 304:         /// The query that is being made
 305:         /// </summary>
 306:         public string Query
 307:         {
 308:             get { return _Query; }
 309:             set
 310:             {
 311:                 _Query = value;
 312:                 Searcher.Filter = _Query;
 313:             }
 314:         }
 315:  
 316:         /// <summary>
 317:         /// Decides what to sort the information by
 318:         /// </summary>
 319:         public string SortBy
 320:         {
 321:             get { return _SortBy; }
 322:             set
 323:             {
 324:                 _SortBy = value;
 325:                 Searcher.Sort.PropertyName = _SortBy;
 326:                 Searcher.Sort.Direction = SortDirection.Ascending;
 327:             }
 328:         }
 329:         #endregion
 330:  
 331:         #region Private Variables
 332:         private string _Path = "";
 333:         private string _UserName = "";
 334:         private string _Password = "";
 335:         private DirectoryEntry Entry = null;
 336:         private string _Query = "";
 337:         private DirectorySearcher Searcher = null;
 338:         private string _SortBy = "";
 339:         #endregion
 340:  
 341:         #region IDisposable Members
 342:  
 343:         public void Dispose()
 344:         {
 345:             if (Entry != null)
 346:             {
 347:                 Entry.Dispose();
 348:                 Entry = null;
 349:             }
 350:             if (Searcher != null)
 351:             {
 352:                 Searcher.Dispose();
 353:                 Searcher = null;
 354:             }
 355:         }
 356:  
 357:         #endregion
 358:     }
 359: }
 360:  
 361: /*
 362: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
 363: 
 364: Permission is hereby granted, free of charge, to any person obtaining a copy
 365: of this software and associated documentation files (the "Software"), to deal
 366: in the Software without restriction, including without limitation the rights
 367: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 368: copies of the Software, and to permit persons to whom the Software is
 369: furnished to do so, subject to the following conditions:
 370: 
 371: The above copyright notice and this permission notice shall be included in
 372: all copies or substantial portions of the Software.
 373: 
 374: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 375: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 376: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 377: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 378: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 379: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 380: THE SOFTWARE.*/
 381:  
 382: #region Usings
 383: using System;
 384: using System.Collections.Generic;
 385: using System.DirectoryServices;
 386: #endregion
 387:  
 388: namespace Utilities.LDAP
 389: {
 390:     /// <summary>
 391:     /// Directory entry class
 392:     /// </summary>
 393:     public class Entry : IDisposable
 394:     {
 395:         #region Constructors
 396:         /// <summary>
 397:         /// Constructor
 398:         /// </summary>
 399:         /// <param name="DirectoryEntry">Directory entry for the item</param>
 400:         public Entry(DirectoryEntry DirectoryEntry)
 401:         {
 402:             this._DirectoryEntry = DirectoryEntry;
 403:         }
 404:         #endregion
 405:  
 406:         #region Properties
 407:         /// <summary>
 408:         /// Actual base directory entry
 409:         /// </summary>
 410:         public DirectoryEntry DirectoryEntry
 411:         {
 412:             get { return _DirectoryEntry; }
 413:             set { _DirectoryEntry = value; }
 414:         }
 415:  
 416:         /// <summary>
 417:         /// Email property for this entry
 418:         /// </summary>
 419:         public string Email
 420:         {
 421:             get { return (string)GetValue("mail"); }
 422:             set { SetValue("mail", value); }
 423:         }
 424:  
 425:         /// <summary>
 426:         /// distinguished name property for this entry
 427:         /// </summary>
 428:         public string DistinguishedName
 429:         {
 430:             get { return (string)GetValue("distinguishedname"); }
 431:             set { SetValue("distinguishedname", value); }
 432:         }
 433:  
 434:         /// <summary>
 435:         /// country code property for this entry
 436:         /// </summary>
 437:         public string CountryCode
 438:         {
 439:             get { return (string)GetValue("countrycode"); }
 440:             set { SetValue("countrycode", value); }
 441:         }
 442:  
 443:         /// <summary>
 444:         /// company property for this entry
 445:         /// </summary>
 446:         public string Company
 447:         {
 448:             get { return (string)GetValue("company"); }
 449:             set { SetValue("company", value); }
 450:         }
 451:  
 452:         /// <summary>
 453:         /// MemberOf property for this entry
 454:         /// </summary>
 455:         public List<string> MemberOf
 456:         {
 457:             get
 458:             {
 459:                 List<string> Values = new List<string>();
 460:                 PropertyValueCollection Collection = DirectoryEntry.Properties["memberof"];
 461:                 foreach (object Item in Collection)
 462:                 {
 463:                     Values.Add((string)Item);
 464:                 }
 465:                 return Values;
 466:             }
 467:         }
 468:  
 469:         /// <summary>
 470:         /// display name property for this entry
 471:         /// </summary>
 472:         public string DisplayName
 473:         {
 474:             get { return (string)GetValue("displayname"); }
 475:             set { SetValue("displayname", value); }
 476:         }
 477:  
 478:         /// <summary>
 479:         /// initials property for this entry
 480:         /// </summary>
 481:         public string Initials
 482:         {
 483:             get { return (string)GetValue("initials"); }
 484:             set { SetValue("initials", value); }
 485:         }
 486:  
 487:         /// <summary>
 488:         /// title property for this entry
 489:         /// </summary>
 490:         public string Title
 491:         {
 492:             get { return (string)GetValue("title"); }
 493:             set { SetValue("title", value); }
 494:         }
 495:  
 496:         /// <summary>
 497:         /// samaccountname property for this entry
 498:         /// </summary>
 499:         public string SamAccountName
 500:         {
 501:             get { return (string)GetValue("samaccountname"); }
 502:             set { SetValue("samaccountname", value); }
 503:         }
 504:  
 505:         /// <summary>
 506:         /// givenname property for this entry
 507:         /// </summary>
 508:         public string GivenName
 509:         {
 510:             get { return (string)GetValue("givenname"); }
 511:             set { SetValue("givenname", value); }
 512:         }
 513:  
 514:         /// <summary>
 515:         /// cn property for this entry
 516:         /// </summary>
 517:         public string CN
 518:         {
 519:             get { return (string)GetValue("cn"); }
 520:             set { SetValue("cn", value); }
 521:         }
 522:  
 523:         /// <summary>
 524:         /// name property for this entry
 525:         /// </summary>
 526:         public string Name
 527:         {
 528:             get { return (string)GetValue("name"); }
 529:             set { SetValue("name", value); }
 530:         }
 531:  
 532:         /// <summary>
 533:         /// office property for this entry
 534:         /// </summary>
 535:         public string Office
 536:         {
 537:             get { return (string)GetValue("physicaldeliveryofficename"); }
 538:             set { SetValue("physicaldeliveryofficename", value); }
 539:         }
 540:  
 541:         /// <summary>
 542:         /// telephone number property for this entry
 543:         /// </summary>
 544:         public string TelephoneNumber
 545:         {
 546:             get { return (string)GetValue("telephonenumber"); }
 547:             set { SetValue("telephonenumber", value); }
 548:         }
 549:         #endregion
 550:  
 551:         #region Public Functions
 552:         /// <summary>
 553:         /// Saves any changes that have been made
 554:         /// </summary>
 555:         public void Save()
 556:         {
 557:             _DirectoryEntry.CommitChanges();
 558:         }
 559:  
 560:         /// <summary>
 561:         /// Gets a value from the entry
 562:         /// </summary>
 563:         /// <param name="Property">Property you want the information about</param>
 564:         /// <returns>an object containing the property's information</returns>
 565:         public object GetValue(string Property)
 566:         {
 567:             PropertyValueCollection Collection = DirectoryEntry.Properties[Property];
 568:             if (Collection != null)
 569:             {
 570:                 return Collection.Value;
 571:             }
 572:             return null;
 573:         }
 574:  
 575:         /// <summary>
 576:         /// Sets a property of the entry to a specific value
 577:         /// </summary>
 578:         /// <param name="Property">Property of the entry to set</param>
 579:         /// <param name="Value">Value to set the property to</param>
 580:         public void SetValue(string Property, object Value)
 581:         {
 582:             PropertyValueCollection Collection = DirectoryEntry.Properties[Property];
 583:             if (Collection != null)
 584:             {
 585:                 Collection.Value = Value;
 586:             }
 587:         }
 588:         #endregion
 589:  
 590:         #region Private Variables
 591:         private DirectoryEntry _DirectoryEntry;
 592:         #endregion
 593:  
 594:         #region IDisposable Members
 595:  
 596:         public void Dispose()
 597:         {
 598:             if (_DirectoryEntry != null)
 599:             {
 600:                 _DirectoryEntry.Dispose();
 601:                 _DirectoryEntry = null;
 602:             }
 603:         }
 604:  
 605:         #endregion
 606:     }
 607: }

The two classes are Directory and Entry. Directory is what is used to connect to Active Directory and the Entry class actually holds each entry's info (also note that they are IDisposable, so make sure to dispose of them when done). Anyway, most of the code is of little use in this instance. I mean the directory class has the ability to search for users, groups, etc. There's really only one function of much use: FindComputers. This function allows us to search for any computer that fits specific criteria that we pass into it. Or in our instance, anything in our domain:

   1: using(Utilities.LDAP.Directory TempDirectory = new Utilities.LDAP.Directory("", "USERNAME", "PASSWORD", "LDAPLOCATION"))
   2: {
   3:     List<Utilities.LDAP.Entry> Entries = TempDirectory.FindComputers("name=*");
   4: }

The code above uses the Directory class to find all computers within AD, giving us back a list of those entries. This isn't the most useful code at this point but it gets us pass step one. We have our list of machines. So on to step 2, doing the DNS lookup. One of the great things about .Net is that a lot of functionality is already there for you. In this case all we have to do is take the name of each entry and feed it to Dns.GetHostEntry (which is in the System.Net namespace). It in turn gives us an IPAddress class, which holds every address that the machine is using (IPv4, IPv6, etc.). However, if you've tried it in the past you will notice that if it can't find the machine, it takes forever to run (well 14 seconds anyway). And if you're searching for 5000 machines and any decent number of them are offline, that is going to, well, suck. There are options, you can either modify your registry or you can use multithreading to get the job done. I went with the second option:

   1: using(Utilities.LDAP.Directory TempDirectory = new Utilities.LDAP.Directory("", "USERNAME", "PASSWORD", "LDAPLOCATION"))
   2: {
   3:     List<Utilities.LDAP.Entry> Entries = TempDirectory.FindComputers("name=*");
   4:     for (int x = 0; x < Entries.Count; ++x)
   5:     {
   6:         try
   7:         {
   8:             using (Utilities.LDAP.Entry Entry = Entries[x])
   9:             {
  10:                 DNSSearch TempWorker = new DNSSearch(Entry.Name);
  11:                 TempWorker.Finished = new EventHandler<Utilities.Events.EventArgs.OnEndEventArgs>(FinishedSearch);
  12:                 TempWorker.Start();
  13:             }
  14:         }
  15:         catch { }
  16:     }
  17: }

The code above just adds to our AD search, adding a loop, creating a DNSSearch class for each item, passing in the name, setting an eventhandler, and calling start. The DNSSearch class looks like this:

   1: public class DNSSearch:Worker<Machine,string>
   2: {
   3:     public DNSSearch(string Params)
   4:         : base(Params)
   5:     {
   6:     }
   7:  
   8:     protected override Machine Work(string Params)
   9:     {
  10:         Machine TempMachine = new Machine();
  11:         TempMachine.Name = Params;
  12:         TempMachine.Addresses = new List<string>();
  13:         try
  14:         {
  15:             IPHostEntry HostEntry = Dns.GetHostEntry(Params);
  16:             foreach (IPAddress Address in HostEntry.AddressList)
  17:             {
  18:                 TempMachine.Addresses.Add(Address.AddressFamily + ": " + Address.ToString());
  19:             }
  20:         }
  21:         catch { }
  22:         return TempMachine;
  23:     }
  24: }

The class inherits from a Worker class, which I showed here. Although there are some slight updates to it, so you may want to get the latest version from my utility library here. Anyway, the base class wants to know what we are getting as input and what to expect as output. In our case it's expecting a string for input and a class called Machine for output. The machine class looks like this:

   1: public class Machine
   2: {
   3:     public string Name { get; set; }
   4:     public List<string> Addresses { get; set; }
   5:  
   6: }

It's really just a holder for the info. So when we call Start on the DNSSearch object, back in the original function, the base class starts the thread and calls Work in DNSSearch. The Work function creates a Machine object, sets the name of the machine and calls Dns.GetHostEntry, filling out the address info for us. When it's done it returns the machine object. This gets sent to our Finished EventHandler. In the original function we set the Finished event to a function called FinishedSearch:

   1: public static List<Machine> MachineList { get; set; }
   2:  
   3: public static void FinishedSearch(object sender, Utilities.Events.EventArgs.OnEndEventArgs e)
   4: {
   5:     lock (typeof(List<Machine>))
   6:     {
   7:         if (MachineList == null)
   8:             MachineList = new List<Machine>();
   9:         MachineList.Add((Machine)e.Content);
  10:     }
  11: }

The function simply locks our list of machines, sets it up if need be and adds our machine object to said list and then unlocks it. So how do we get this info back to our form, or web page, or whatever? Simple:

   1: public static List<Machine> SearchNetwork()
   2: {
   3:     if (MachineList == null)
   4:     {
   5:         using(Utilities.LDAP.Directory TempDirectory = new Utilities.LDAP.Directory("", "USERNAME", "PASSWORD", "LDAPLOCATION"))
   6:         {
   7:             List<Utilities.LDAP.Entry> Entries = Temp.FindComputers("name=*");
   8:             for (int x = 0; x < Entries.Count; ++x)
   9:             {
  10:                 try
  11:                 {
  12:                     using (Utilities.LDAP.Entry Entry = Entries[x])
  13:                     {
  14:                         DNSSearch TempWorker = new DNSSearch(Entry.Name);
  15:                         TempWorker.Finished = new EventHandler<Utilities.Events.EventArgs.OnEndEventArgs>(FinishedSearch);
  16:                         TempWorker.Start();
  17:                     }
  18:                 }
  19:                 catch { }
  20:             }
  21:         }
  22:     }
  23:     return MachineList;
  24: }

By returning the MachineList, which was static, we have access to it as it's being updated. So we can do something like this in a function:

   1: List<Machine> Machines = SearchNetwork();
   2: lock (typeof(List<Machine>))
   3: {
   4:     foreach (Machine TempMachine in Machines)
   5:     {
   6:         if (TempMachine.Addresses.Count > 0)
   7:         {
   8:             //The machine is online
   9:         }
  10:         else
  11:         {
  12:             //The machine is offline
  13:         }
  14:     }
  15: }

Note that we have to lock it because the threads are going to continue to update our list, but it doesn't stop us from locking it and using it ourselves. An even better idea might be to use a list with events telling you when something is added... Sort of like the code I created here. Using something like that, you could have a list on a form update in real time as items are added to the list. Anyway, I know I threw a bunch of code up here but it's quite simple once you get down to it. Anyway, try it out, leave feedback, and happy coding.



Comments