Link to home
Start Free TrialLog in
Avatar of pjelias
pjelias

asked on

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

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
Avatar of shaneholmes
shaneholmes

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.
Avatar of pjelias

ASKER

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
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.



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
Geol,

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

Shane
Avatar of pjelias

ASKER

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
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
Avatar of pjelias

ASKER

Basically yes

Regards
PJE
ASKER CERTIFIED SOLUTION
Avatar of geobul
geobul

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of pjelias

ASKER

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
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
Avatar of pjelias

ASKER

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
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
Avatar of pjelias

ASKER

Geo,

A couple of minor mods, and all works well.

Much appreciated
PJE
The pleasure was mine :-)
Avatar of pjelias

ASKER

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
Avatar of pjelias

ASKER

Geo,

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


Regards
PJE
Avatar of pjelias

ASKER

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
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