Delphi 4 and the WNet API

Fritz explores Delphi 4.0, a multimachine, remote registry editing tool based on the Win32 WNet API. In the process, he examines the WNet API, discusses some of the differences between Delphi 4.0 and previous versions, and looks at authentication differences between workgroups and domains.


December 01, 1998
URL:http://www.drdobbs.com/tools/delphi-4-and-the-wnet-api/184410751

Dec98: Delphi 4 and the WNet API

Fritz is a Windows NT system developer specializing in networked and distributed applications for Nims Associates Inc. in Denver, Colorado. You can contact him at fritzl@ uswest.net.


Sidebar: Workgroups versus Domains

The Microsoft Windows NT registry provides a convenient means of consolidating configuration information for systems and applications. Even nicer is the ability to connect to remote registries and make changes from afar using regedt32. What's not so nice is that if you have a large number of computers to manage, you have to connect to each separately in order to make any edits -- even if all of their administrative logins are the same.

My usual programming domain is the realm of the invisible -- Windows NT services, server-side development, and network protocol implementations. For jobs such as these, just about any language will do. However, when an interface is called for, my tool of choice is Inprise's Delphi, which allows for rapid interface development and can implement system-level code like a champ. Object Pascal (Delphi's language) provides all of the object orientation most programmers will ever need (or use). It also provides a resource protection (try-finally) and exception handling (try-except) syntax that I still haven't found matched in any C++ environment.

I used Delphi to write my multimachine remote registry editing tool based on the Win32 WNet API. This set of functions supports workgroup/domain browsing in order to select and connect to computers. In this article, I'll focus on how that browser was implemented, first in Delphi 3.0 and later in Version 4.0. The source code and related files for the complete application are available electronically; see "Resource Center," page 3. I'll also examine the WNet API, discuss some of the differences between Delphi 4.0 and previous versions, and look at authentication differences between workgroups and domains.

Porting to Delphi 4.0

The registry editing application I present here was originally written under Delphi 3.0. When Version 4.0 was released, I quickly moved to upgrade the application. What I discovered was that Delphi 4.0's new features (additions to the IDE, changes to the Visual Component Library [VCL], and to the Object Pascal language itself) meant the port involved more than simply recompiling.

The most significant change I encountered relates to the new unsigned 32-bit data types DWORD and LongWord. In Delphi Versions 2 and 3, the data type integer was defined as a signed 32-bit value. Since DWORD and UINT, derived types, were defined as "integer," they were computationally identical to the base type. Since I rarely dealt with large integer values, I wasn't concerned by the distinction. Hindsight can be quite instructive.

With Delphi 4.0, variables and arguments declared by the WinAPI as DWORD or UINT are, in fact, unsigned integers. This leads to some interesting issues when porting code from previous versions of Delphi; for instance, how many of us have looked at the MessageBeep function in the API help and seen the line:

0xFFFFFFFF Standard beep using

the computer speaker

We all know that 0xFFFFFFFF means "-1," right? Yes, if you were dealing with signed integers, but the declaration of MessageBeep is:

function MessageBeep(uType:

UINT): BOOL; stdcall;

That UINT didn't used to matter, but using -1 with 4.0 as the parameter generates the compiler error: [Error]: Constant expression violates subrange bounds. A similar gotcha occurs when you try to compare a DWORD value with an integer value. For instance, operations like Example 1 generate warnings from the compiler, such as: [Warning]: Comparing signed and unsigned types - widened both operands.

Since many WinAPI functions and constants are defined as DWORD and UINT variables, chances are it'll take some time for you to work out all of the comparison and typecasting issues in larger projects.

Other new 4.0 language features include dynamic arrays, method overloading, default parameters, a 64-bit integer data type, and greater precision for the Real data type. Such features make Delphi a serious contender as a general-purpose language.

The WNet API

WNet, a high-level network API that comes with Windows 95/NT, allows for enumeration of network resources and authentication/connection to those resources. If you want to integrate network drive and printer mapping (or similar functionality) in your programs, WNet is the API to focus on.

WNet functions divide network resources into containers and connectable resources. Containers are groupings of other resources; for example, a workgroup or (NT) domain is a logical grouping of computers (see the accompanying text box entitled "Workgroups versus Domains"). On the other hand, a workstation or server is a container of shares. The shares (print, drive, database, and so forth) are connectable resources that can be accessed by authenticated users. Interestingly, you can connect to a computer without connecting to any particular resource. This is due to the administrative Default Shares, which can be seen in the Server Control Panel (see the names that end with "$" in Figure 1).

If your application only needs access to drive shares, the WNet WNetConnectionDialog function displays a dialog that lets you browse and connect, prompting for login information if needed. WNetDisconnectDialog lets you break the connection. In some instances, this can be a labor saver. However, I needed to work with more than one target machine at a time. To do this, I built a workgroup/domain browser for selecting multiple target machines.

The Workgroup/Domain Browser

When implementing a browser, you start by enumerating the containers visible to your computer. For my project, I had to do this in two stages.

  1. List top-level containers (Microsoft Windows Network and Novell Network).

  2. Enumerate the Windows network containers to find the available workgroup and domain names.

The lynchpin of most of the WNet API is the NETRESOURCE structure (TNetResource as defined in the Delphi Windows.DCU unit), which receives information about each object found. Listing One (listings are located at the end of this article) presents two enumeration loops -- WNetOpenEnum through WNetCloseEnum. I used Delphi resource protection blocks around both enumeration loops to make sure that enumeration handles (and whatever memory resources they represent) get released. By specifying RESOURCE_GLOBALNET and RESOURCETYPE_ANY when opening the enumeration handles, Listing One sees everything that's out there. To get the names of available objects, WNetEnumResource fills an array of NETRESOURCE records. Once this array is populated, I iterate through it to fill up my Containers listbox with the names provided by the lpRemoteName field of the record.

Once I had a list of the workgroups and domains on my network, the next step was to get the names of the machines within these containers when the container is named. Again it's WNetOpenEnum and WNetEnumResource that do the work. The difference this time is that you already know the name of the container you want to start from, and (at least in my case) I wanted to see the connectable objects within those containers -- computers. To view shared drives or printers, another enumeration for specific machines would be required.

To accomplish this, I first populated a NETRESOURCE record with some defaults, assigning dwUsage to RESOURCEUSAGE_CONNECTABLE to look for shared objects and the desired container name in the lpRemoteName field. The handle returned by WNetOpenEnum will force WNetEnumResources to see only those computers within this container. To list the names in Universal Naming Convention (UNC) style format (\\MACHINE, for instance), you use the RESOURCEDISPLAYTYPE_GENERIC flag (see Listing Two). The names are populated into the Computer Names listbox, and a browser as in Figure 2 is born.

Connecting

Connecting to other machines on the network is only a function call away -- WNetAddConnection2. Why the "2"? Because, according to win32.hlp, WNetAddConnection is a legacy function. Notice that all computer names are accessed using UNC style "\\" format for this function. (See the text box "Workgroups versus Domains" for details on what constitutes a valid name.)

The last element for making a connection is authentication information, commonly known as a "username and password." The utility I wrote only needed to know one set of authentication information, since the administrator accounts on our computers had identical logins (see Figure 3). Providing a NULL username and password will cause the connection to attempt to use the cached authentication information, that is, the username and password you entered when you logged onto your computer.

Once you know where you are going and who you need to be when you get there, it's time to reach out and touch someone. Once again, the first step is to populate a NETRESOURCE structure. In my experience, WNetAddConnection2 corrupts the contents of the NETRESOURCE structure after it attempts a connection. Since this tool is not mapping a network drive or printer, there is nothing required in the lpLocalName field of the structure. If it were such a connection, the code could set the local name to a drive letter or printer name and the last parameter, fdwConnection, to CONNECT_UPDATE_PROFILE in order to mark this connection as persistent, meaning that the system would attempt to reconnect on subsequent logins. My program then goes ahead with its business, closing the connection using WNetCancelConnection2 when finished.

Recall that I wrote the application to edit remote registries. A look at the registry functions reveals the RegConnectRegistry function that takes a machine name argument. What the API help file doesn't tell you is that you must either already have an authenticated connection to the machine or your current username and password (as cached in the network subsystem) must be able to log into the target machine. In my code, I use the Delphi TRegistry object for registry handling. It has a RegistryConnect method that is based upon the RegConnectRegistry function. It also provides a useful set of methods for the rest of the registry interface.

The remainder of the utility (available electronically) is a registry action editor that lets you define individual actions, optionally saving them as macros. By looping through the machine list, connecting to the remote machines, applying my registry actions, and finally disconnecting I can perform many administrative duties from the comfort of my office. And isn't that what sysadmins really want to do?

Conclusion

If you need to map drives, attach to printers, or perform administrative attachments, the WNet API provides a straightforward interface. Using Delphi, I was quickly able to write a functional tool for managing the registries and (by extension applications) on a large number of computers.

The same network techniques can be applied to a variety of management tasks. Of course, they can also be used to wreak havoc, too. To prevent misuse of the Windows NT remote management facilities, be sure to secure your administrative logins, apply all of the service packs and fixes appropriate for your environment, and encourage users who share resources to make informed decisions about access permissions.

Acknowledgment

Thanks to Tom Seago, who uses the tool presented here on a daily basis and provided invaluable assistance and testing during its development.

DDJ

Listing One

try    { do the enumeration of containers }
    if WnetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, nil, hEnum) <>
                     NO_ERROR then
                exit;
    { now enumerate the containers  }
    iBufSize := sizeof(aBuf);
    iEntries := 64;  { 64 entries at a gulp }
    while WNetEnumResource(hEnum, iEntries, @aBuf[0], iBufSize) = NO_ERROR
do begin
     for i := 0 to (iEntries-1) do begin
       if ((aBuf[i].dwUsage and RESOURCEUSAGE_CONTAINER) =
                RESOURCEUSAGE_CONTAINER) and
       (pos('Microsoft Windows', string(aBuf[i].lpRemoteName)) = 1) then begin
               { enum workgroups and domains }
               try
               if WnetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, 
                          @aBuf[i], hEnum2) <> NO_ERROR then begin
               showmessage('Error enumerating Microsoft Network Resources!');
                           exit;
                      end;          
                      iBufSize := sizeof(aBuf2);
                      iEntries2 := 64;
                      while WNetEnumResource(hEnum2, iEntries2, @aBuf2[0], 
                                               iBufSize) = NO_ERROR do begin
                      for j := 0 to (iEntries2 - 1) do
                       lbContainers.items.add(string(abuf2[j].lpRemoteName));
                           iEntries2 := 64;
                        end;
                     finally
                        WNetCloseEnum(hEnum2);
                     end;
                 end;
             end;
             iEntries := 64;
        end;
finally
        WnetCloseEnum(hEnum);
end;
     

Back to Article

Listing Two

try   screen.cursor := crHourGlass;
   with rNetRez do begin
      dwScope := RESOURCE_GLOBALNET;
      dwType  := RESOURCETYPE_ANY;
      dwDisplayType := RESOURCEDISPLAYTYPE_GENERIC; 
      dwUsage := RESOURCEUSAGE_CONNECTABLE; 
      lpLocalName := nil;
      lpRemoteName := pchar(lbContainers.items[lbContainers.itemindex]);
      lpComment := nil;
      lpProvider := nil;
  end;
  if WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, 
                                 @rNetRez, hEnum) <> NO_ERROR then begin
       ShowMessage('Could not enumerate container: ' + 
                                 string(rNetRez.lpRemoteName));
       exit;
   end;
     
   iBufsize := sizeof(abuf);
   iEntries := 64;
   while WNetEnumResource(hEnum, iEntries, @aBuf[0], iBufSize) = NO_ERROR
do begin
        for i := 0 to (iEntries -1) do
        lbMachines.items.add(string(aBuf[i].lpRemoteName));
        iEntries := 64;
   end;
finally
   WnetCloseEnum(hEnum);
   screen.cursor := crDefault;
end;
     

Back to Article

Listing Three

zeromemory(@rNetRez, sizeof(TNetResource));with rNetRez do begin
        dwType := RESOURCETYPE_ANY;
        lpLocalName := nil;
        lpProvider := nil;
end;
try
        sMachineName := lbMachines.items[i]; 
        rNetRez.lpRemoteName := pchar(sMachineName);
        iRet := WNetAddConnection2(rNetRez, pPassword, pUsername, 0);
        { 
                do stuff 
        }
finally
        WNetCancelConnection2(pchar(sMachineName), 0, true);
end;

Back to Article


Copyright © 1998, Dr. Dobb's Journal
Dec98: Delphi 4 and the WNet API

Delphi 4 and the WNet API

By Fritz Lowrey

Dr. Dobb's Journal December 1998

var
	dwTest :dword;
	iTest	:integer;
begin
	dwTest := 1;
	iTest := 1;
	if dwTest = iTest then
		ShowMessage('Equal')
	else
		ShowMessage('Not equal');
end;

Example 1: Code that will generate compiler warnings.


Copyright © 1998, Dr. Dobb's Journal
Dec98: Delphi 4 and the WNet API

Delphi 4 and the WNet API

By Fritz Lowrey

Dr. Dobb's Journal December 1998

Figure 1: Server control panel.


Copyright © 1998, Dr. Dobb's Journal
Dec98: Delphi 4 and the WNet API

Delphi 4 and the WNet API

By Fritz Lowrey

Dr. Dobb's Journal December 1998

Figure 2: The browser.


Copyright © 1998, Dr. Dobb's Journal
Dec98: Delphi 4 and the WNet API

Delphi 4 and the WNet API

By Fritz Lowrey

Dr. Dobb's Journal December 1998

Figure 3: Making the connection.


Copyright © 1998, Dr. Dobb's Journal
Dec98: Workgroups versus Domains

Dr. Dobb's Journal December 1998

Workgroups versus Domains


Windows NT and Windows 95 provide two different ways of logically grouping a network full of computers. These logical groups are the containers that are listed when the Microsoft Windows Network container is enumerated.

Workgroups are purely a naming convention. There is no shared authentication information between computers, each machine maintains its own Security Account Manager (SAM), which contains the account information visible in the User Manager. Think of this like a crowded party where folks from, say, Dallas hang out in one corner and folks from Boston in another. They are all in the same room but have grouped themselves for distinction.

A domain -- not to be confused with the TCP/IP concept by the same name -- is a means of keeping distinct, replicated, and shared authentication information. The computers themselves are still in a flat namespace, but they will attempt to validate user logins based upon the systems domain membership. A domain's authentication information is stored on NT Servers designated as primary or backup domain controllers.

A workstation's SAM does not disappear when that machine is a member of a domain. However, by default, logins attempt to authenticate against the domain controller. Interactive logins can still use only the local SAM by selecting the local computer name from the "Domain" list on the login dialog.

As far as enumerating network containers is concerned, workgroups and domains are, as far as I can tell, indistinguishable. Comments to the contrary are invited since it would sometimes be useful to know when different authentication information is required when connecting via domain-based accounts. Both contain computers as connectable objects.

The information provided during an enumeration of containers is derived from an internal browse info table. The mechanisms that support this table are truly Byzantine. If you are interested in reading up on the arcana of browsing, Chapter 3 of the Window NT Server 4.0 Resource Kit Windows NT Server Networking Guide has everything that you could ever hope, or fear, to know.

The Universal Naming Convention (UNC) is a useful abstraction for connecting to resources in a Windows network. These names take the familiar form \\MACHINE though they are most often used in conjunction with a shared resource name from the target machine, for example \\MACHINE\SHARE. As in most network naming conventions, a machine name must be unique, whether using workgroups or domains. In fact, if you are using IP, you can create a valid UNC using an IP address, \\11.22.33.44\ SHARE, for example. This is very useful if, for some reason, you can't browse to the intended target host (say it with me: Byzantine).

-- F.L.


Copyright © 1998, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.