Solved

Obtaining PC Names for PC's on a Windows 2000 Server

Posted on 2004-04-05
20
259 Views
Last Modified: 2012-05-04
Hi,

We have a Network running a Hundred or so Servers (All running Win2000) over a single domain, ie. DOMAIN, with each Server having a name ie. N123, N124 etc...

There are a list of Servers that our area looks after (About 20).

We are wanting to conduct an audit on PC's that are on each server ie... \\N123\W123456,  \\N123\W123457 etc...., as we want to replace pc's below a certain spec.

That is, we want to find ALL current pc's that exist on each server, and also return some basic info re: these pc's ie. PCName (Nodename), IP Address, CPU Speed, RAM Size etc......

Is there a way we can obtain a list of ALL pc's on a Server ?


Regards
PJE
0
Comment
Question by:pjelias
  • 9
  • 7
  • 4
20 Comments
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10762813
try this example:

Create a new application, & place a TMemo on the main form.

This example iterates all the computers on the net and adds them to memo

Shane



unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure NetErrorHandler( dwResult  : DWORD; const Msg : String);
    procedure DisplayStruct(lpnr : PNETRESOURCE);
    function EnumerateFunc(lpnr : PNETRESOURCE) : Boolean;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.NetErrorHandler(
   dwResult  : DWORD;
   const Msg : String);
begin
    Application.MessageBox(PChar(Msg + ' Error #' + IntToStr(dwResult)),'Error', MB_OK);
end;

procedure TForm1.DisplayStruct(lpnr : PNETRESOURCE);
var
    p : PChar;
begin
    p := lpnr^.lpRemoteName;
    if (p[0] = '\') and (p[1] = '\') then
        p := p + 2;
    memo1.lines.Add(p);
    Caption := IntToStr(memo1.lines.Count);
end;



function TForm1.EnumerateFunc(lpnr : PNETRESOURCE) : Boolean;
var
    dwResult     : DWORD;
    dwResultEnum : DWORD;
    hEnum        : THandle;
    cbBuffer     : DWORD;             // 16K is a good size
    cEntries     : DWORD;             // enumerate all possible entries
    lpnrLocal    : PNETRESOURCE;      // pointer to enumerated structures
    PtrResource  : PNetResource;
    i            : DWORD;
begin
    cbBuffer := 16384;
    cEntries := DWORD(-1);

    if Application.Terminated then begin
        Result := FALSE;
        Exit;
    end;

    dwResult := WNetOpenEnum(RESOURCE_GLOBALNET,
                             RESOURCETYPE_ANY,
                             RESOURCEUSAGE_CONTAINER, // enumerate allresources
                             lpnr,                    // NULL first timethis function is called
                             hEnum);                  // handle to resource

    if dwResult <> NO_ERROR then begin
        NetErrorHandler(dwResult, 'WNetOpenEnum');
        Result := FALSE;
        Exit;
    end;

    repeat
        // Allocate memory for NETRESOURCE structures.
        GetMem(lpnrLocal, cbBuffer);

        dwResultEnum := WNetEnumResource(hEnum,       // resource handle
                                         cEntries,    // defined locally as0xFFFFFFFF
                                         lpnrLocal,   // LPNETRESOURCE
                                         cbBuffer);   // buffer size

        if dwResultEnum = NO_ERROR then begin
            for i := 0 to cEntries - 1 do begin
                PtrResource := PNETRESOURCE(PChar(lpnrLocal) + i *SizeOf(lpnrLocal^));
                if PtrResource^.dwDisplayType = RESOURCEDISPLAYTYPE_SERVER then
                    DisplayStruct(PtrResource);
                Application.ProcessMessages;
                // If this NETRESOURCE is a container, call the function
                // recursively.
                if (RESOURCEUSAGE_CONTAINER = (PtrResource^.dwUsage and RESOURCEUSAGE_CONTAINER)) and
                   (PtrResource^.dwDisplayType <>RESOURCEDISPLAYTYPE_SERVER) then begin
                    if (not EnumerateFunc(PtrResource)) then begin
                        // TextOut(HandleDC, 10, 10, 'EnumerateFunc returned FALSE.', 29);
                    end;
                end;
            end;
        end
        else if dwResultEnum <> ERROR_NO_MORE_ITEMS then begin
            NetErrorHandler(dwResultEnum, 'WNetEnumResource');
            break;
        end;
    until dwResultEnum = ERROR_NO_MORE_ITEMS;

    FreeMem(lpnrLocal);

    dwResult := WNetCloseEnum(hEnum);

    if dwResult <> NO_ERROR then begin
        NetErrorHandler(dwResult, 'WNetCloseEnum');
        Result := FALSE;
        Exit;
    end;

    Result := TRUE;
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
 EnumerateFunc(nil);
end;

end.
0
 

Author Comment

by:pjelias
ID: 10762948
Tried code above, takes a very long time and then an error appears.

Is there a way I can test a specific server ie. N010
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10762977
Hmmm, i dont have 60 machines here, but i do have 12, and I tested this and it worked fast and fine for me.
I added the NetErrorHandler to show any errors.



0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10762979
0
 
LVL 17

Expert Comment

by:geobul
ID: 10768244
Hi,

What do you mean by 'PCs on a server'? If 'PCs' means computers (workstations) then they are in the domain not on a server.

Regards, Geo
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 10768271
Geol,

I think he means all the stations are Win2000 server machines.

Shane
0
 

Author Comment

by:pjelias
ID: 10771111
We have a single Domain called SERVICE, which ALL pc's are connected to (About 10,000), each site has at least 1 server that belongs to this domain (Over 200 servers in total), then we have WORKSTATIONS within each site that have drives mapped to a Server (ie. F,G,H,I drives for all WORKSTATIONS at Site 1 are mapped to N001, drives for all WORKSTATIONS at Site 2 are mapped to N002 etc....), these WORKSTATIONS are physically located within the same site as the server and therefore connected to this server, each site is then connected to external sites (Head Office Servers) via routers etc....

I basically want to get PC Names (Nodenames) for ALL WORKSTATIONS within a given site. The DOMAIN has too many pc's (10,000+) connected, and too many servers to scan. We only look after 20 odd servers (out of a possible 200+), and therefore ONLY want to get a list of WORKSTATIONS attached to SERVERS under our control.

Therefore, if I selected N001 as the server, ALL WORKSTATIONS connected to this server would be displayed. This could possibly be done by scanning IP Addresses in a SUBNET or Gateway ie.

N001 = Gateway[1] =  '123.165.131'
            Gateway[2] =  '123.165.132'

For A:=1 to Gateway.Count
   For I:=1 to 255
     IP:=Gateway[A]+IntToStr(I)
     // Get Nodename for IP - Code here
   End;
End;

ps. ALL workstations and Servers are currently running Windows 2000

Hope this clears up problem
0
 
LVL 17

Expert Comment

by:geobul
ID: 10772517
OK. I got the picture. If, for instance, every one of your sites (a server and its workstations) belongs to a different subnet then it probably could be done. Is this true? What I mean is:

site     subnet          
site1   123.165.131.0/24  (server=123.165.131.1; WS1=123.165.131.2; WS2=123.165.131.3; etc.)
site2   123.165.132.0/24
etc.

And you have a list of IP addresses (or computer names) of these 20 or so servers you are interested in.

Regards, Geo
0
 

Author Comment

by:pjelias
ID: 10772573
Basically yes

Regards
PJE
0
 
LVL 17

Accepted Solution

by:
geobul earned 250 total points
ID: 10773621
A basic form with Listbox for the servers, RichEdit for workstations and two buttons:

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    RichEdit1: TRichEdit;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses unit2;

function LoadIPs(const filename: string; List: TStrings): boolean;
begin
  result := true;
  try
    List.LoadFromFile(filename);
  except
    result := false;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if not LoadIPs('c:\Servers.txt',ListBox1.Items) then begin
    ShowMessage('Error loading the list');
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if ListBox1.ItemIndex > -1 then begin
    Screen.Cursor := crHourglass;
    try
      if not EnumWS(ListBox1.Items[ListBox1.ItemIndex], RichEdit1.Lines) then begin
        Screen.Cursor := crDefault;
        ShowMessage('Error enumerating workstations');
      end;
    finally
      Screen.Cursor := crDefault;
    end;
  end;
end;

end.

// and unit2
unit Unit2;

interface

uses Windows, SysUtils, Classes;

  function EnumWS(const StartingIP: string; List: TStrings): boolean;

implementation

uses Winsock;

function RPos(Substr: string; S: string): Integer;
var
  rs: string;
  i: integer;
begin
  rs := s;
  i := Pos(Substr, rs);
  result := i;
  while i > 0 do begin
    rs := Copy(rs, i+1, Length(rs));
    i := Pos(Substr, rs);
    result := result + i;
  end;
end;

function GetRemoteHostName(IPAddr : string) : string;
var
  wsdata : TWSAData;
  he : PHostEnt;
  ip : TInAddr;
begin
  WSAStartup(MakeWord(1, 1), wsdata);
  ip.S_addr := inet_addr(PChar(IPAddr));
  he := gethostbyaddr(@ip.S_un_b, 4, AF_INET);
  if he<>nil then begin
    Result := string(he.h_name)
  end else begin
    Result := '';
  end;
  WSACleanup();
end;

function EnumWS(const StartingIP: string; List: TStrings): boolean;
var
  i, k: integer;
  sIP, IP, s: string;
begin
  result := true;
  sIP := StartingIP;
  IP := '';
  List.Clear;

  i := RPos('.', sIP);
  if i > 0 then begin
    IP := Copy(sIP, 1, i); // first part x.x.x.
    sIP := Copy(sIP, i+1, Length(sIP)); // last part
    try
      k := StrToInt(sIP);
    except
      result := false;
      exit;
    end;

    for i := k + 1 to 255 do begin
      sIP := IP + IntToStr(i);
      // you may speed up the process significantly here by pinging the sIP first and if the ping is successful then ask for the name
      s := GetRemoteHostName(sIP);
      if s <> '' then begin
        List.Add(s + ' ' + sIP);
      end;
    end;
  end else result := false;
end;

end.

// c:\servers.txt contains:
123.165.131.1
123.165.132.1

Regards, Geo
0
Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

 

Author Comment

by:pjelias
ID: 10781553
Geo,

copied your code (cut and paste, so as not to make any mistakes), also created a servers.txt file.

Appears to work well, BUT, if the IP Address does not exist then process appears to take forever.

Is there something that can be done to speed up this problem ie. Timeout after a quicker period, or return a N/A if time exceeds a certain period etc....

I have tried setting a Minimim and Maximum rather than scanning from x to 255 (therefore for i = Min to Max), and this appears to work better, BUT, how do I determine the Minimum and Maximum IP Addresses for each server, if these are NOT know.


Regards
PJE
0
 
LVL 17

Expert Comment

by:geobul
ID: 10782037
Hi,

I already said that:
>// you may speed up the process significantly here by pinging the sIP first and if the ping is successful then ask for the name

gethostbyaddr doesn't take forever but long enough for a single IP which doesn't exist. There are a lot of PING examples here in EE. Here is one of them using Indy's TIdcmClient component:

1. Drop one on your form

2. Add OnReply event of the TIdcmClient component:

procedure TForm1.IdIcmpClient1Reply(ASender: TComponent;
  const AReplyStatus: TReplyStatus);
begin
  if AReplyStatus.BytesReceived > 0 then begin
    PingFlag := true;
  end else begin
    PingFlag := false;
  end;
end;

3. In unit2 add a variable in the interface section:

var
  PingFlag: boolean;

4. Change EnumWS function like:

....
    IdIcmpClient1.ReceiveTimeout := 1000; // waiting one second per IP // <-- added
    for i := k + 1 to 255 do begin
      sIP := IP + IntToStr(i);
      IdIcmpClient1.Host := sIP; // <-- added
      IdIcmpClient1.Ping; // <-- added
      if PingFlag then begin // <-- added      
        s := GetRemoteHostName(sIP);
        if s <> '' then begin
          List.Add(s + ' ' + sIP);
        end;
      end; // <-- added
    end;
...

Regards, Geo
0
 

Author Comment

by:pjelias
ID: 10788826
Geo,

thanks for help, will have to try the last bit when I get back to work Next Tuesday (as it is a long weekend).

Regards
PJE
0
 
LVL 17

Expert Comment

by:geobul
ID: 10789571
Thanks for the points. It will be a long weekend to me also (Orthodox Easter).

Just an idea: you could change the ping timeout to be a parameter entered by the user (in milliseconds) because for a single subnet with small amount of workstations (20 for instance) it will take 240 seconds to scan the whole range of 255 IP addresses. If your network is fast enough you may decrease this period to 100 seconds setting the timeout to 400 ms (or less). There is a risk of omiting some slow WSs in that case though.

There is also a possibility to stop the scan after several (let say 10) not found IPs in a row if you can be sure that workstations have consecutive IPs.

Regards, Geo
0
 

Author Comment

by:pjelias
ID: 10809767
Geo,

A couple of minor mods, and all works well.

Much appreciated
PJE
0
 
LVL 17

Expert Comment

by:geobul
ID: 10811109
The pleasure was mine :-)
0
 

Author Comment

by:pjelias
ID: 10819143
Geo,

Is there any way to get PC details from the PC Names returned. In particular, CPU and RAM specs.

Happy to post another question with points.

As I initially mentioned, we are conducting an audit on PC's and want to find pc's below a certain spec, so that they can be replaced


Regards
PJE
0
 

Author Comment

by:pjelias
ID: 10819418
Geo,

I may have found a way using msinfo32, but open to any other suggestions


Regards
PJE
0
 

Author Comment

by:pjelias
ID: 10819545
HELP,

we have asked a particular site to turn on every pc in the building.

When I run my utility a certain number of pc's are displayed. I thought great all pc's have been found.

We then compared this list against an old list we had, and found that a number of pc's existed in the OLD list, which did not appear in the NEW LIST. So we pinged these pc's and sure enough they existed.

I then ran my utility again, and the pc's appeared, BUT others had disappeared.

It appears that everytime I run the UTILITY, the list changes (may be slightly).

What should I do, possibly increase my TIMEOUT value ???


Regards
PJE
0
 
LVL 17

Expert Comment

by:geobul
ID: 10866714
Hi,

Sorry for the delay. I got a week off and went on a vacation (without any computers there :-)

Yes, perhaps you should increase the timeout. As I said earlier using a short timeout some computers with slow connections may not be found. You could be changing the timeout dynamically also:
- first ping a particular IP using 200 ms for instance (depending on your network speed it might be from 100 to 500 ms);
- if the IP doesn't answer then try with 1000 ms for instance (or more);
- if there is no answer again consider that IP as not found.

Regards, Geo
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

Suggested Solutions

Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
This video discusses moving either the default database or any database to a new volume.
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

758 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now