Discover COM: Connection Points Versus Mailslots in Replication Directory

Environment: COM, DCOM, COM+

Introduction

This module is designed to solve the old problem of directory replication. In fact, we want to put a resource (file or directory) on a server machine and obtain the same structure of directory on the client machine that was advising to the server.

This fact happens automatically and instantly.

How It Works

That module was built out of necessity to automatically and instantaneously reflect the resource uploaded by ASP from a server file to other client computers. The server component must be installed on the same computer where the ASP administration files are. The client component must be installed on each Web client computer where we want to replicate the server share folder.

The initialization process between server and clients is done automatically after the server and client services start.

It is possible to dynamically add or remove client computers from the network, while the system is working. Every time a new client service is started on a new computer, the server is automatically advised about it. If a client is shut down or stopped for certain reasons, after restarting it, the server ise automatically advised about it.

To make a replication of a resource from the server to clients, all you need to do is to make a call to the PutResource method of server component. This must happen after making an ASP upload with a new resource (file or directory).

If a total resynchronization of files between server and clients is needed, you have to run the Syncronize method of the server object.

To see a list of client computers that are advised to the server, you have to launch the GetActiveClients method of the server object.

Points Involved

  1. Components
  2. Communication
  3. Copy files
  4. Win32 functions
  5. Installation
  6. Web calls

Components

There are two modules involved: a server component and a client (observer) component.

The server component

The server component is a DCOM exe file component encapsulated in a Windows service. This way, it has solved the problem of remote calls between computers as well as the problem of keeping the client lists in memory. In addition, a service gives the administrator more facilities, such as stop/run/pause/disable services, and inspects the log service file.

The server component is a multithreading, thread-safe system.

Server component methods

PutResource (Ex: replication.asp)

It launches a replication of a file or directory. It is made of three parameters:

  • Path = the location path where the root path of the site is (generally, this is “c:\IntePub\wwwroot”). This is obtained with the Server.MapPath(“/”) method.
  • Directory = the directory name under the path where the asp makes uploads of the resources (ex: “Images”, “CoFiles”). This is the name that will be used to share that directory on the server computer.
  • ResourceName = the name of the file or directory that was uploaded on the server under the Path\Directory location. That resource will be instantly copied on the client computers.
Syncronize (Ex: Resync.asp)

It makes a total resynchronization between server and client files. It doesn’t have any parameters.

GetActiveClients (Ex: PrintClientsList.asp)

It will return a list of client computers that are advised to the server. Please see a demo of how to use it in PrintClientsList.asp. It doesn’t have any parameters.

Talk

It is used by the client services to see whether the service is started or not.

PutClientName

They are used by the client services that tell the server about their names.

The Client (Observer) Component

The client (observer) component is a DCOM exe file component encapsulated in a Windows service. This way, it has solved the problem of remote calls between computers as well as the problem of keeping the client lists in memory. In addition, a service gives the administrator more facilities, such as stop/run/pause/disable services, and inspects the log service file.

The client component is a multithreading, thread-safe system.

This component service has to run on each computer machine where the directory list from the server is replicated.

Communication

The server and client services are DCOM components. For that reason, they are simultaneously seen on the network. The server automatically maintains a list of clients. Each client automatically makes an entry point to the server list by using the advice ATL function. The server tells all the clients about the existence of a new resource (after the call ‘obj.PutResource path, directory, resource’) using that list and the connection points ATL system.

That communication task is divided into two parts:

DCOM events

That system is used to send events from the server to all clients who are registered to server via the advice function.

The PutResource, GetActiveClients, and Syncronizeserver functions fire events that are received by the DCOM clients.

The server connection events class responsible for distributing the specified events is CProxyIServerEvents (Fire_OnNewResource, Fire_OnGetActiveClients, and Fire_OnSyncronize). That class and fire functions are builded automatically by the Connection Points wizard.

On the client side, the fire events are caught by the CEventHandler class. Here, the following macro code distributes the calls from the server to the specified method of client:

BEGIN_SINK_MAP(CEventHandler)
    SINK_ENTRY_EX(0, DIID_IServerEvents, 1, OnNewResource)
    SINK_ENTRY_EX(0, DIID_IServerEvents, 2, OnSyncronize)
    SINK_ENTRY_EX(0, DIID_IServerEvents, 3, OnGetActiveClients)
END_SINK_MAP()

When the client receives the name of the server, it launches the LaunchObserver function that advises the server about the existence of the client and creates an instance of the CEventHandler class. The CoCreateInstanceEx function is used to access the server’s DCOM service component on the needed machine(m_szServerName), without regarding the dcomcnfg utility:

COSERVERINFO              psi = { 0, m_szServerName, 0, 0 };
MULTI_QI                  mqi = { &IID_IUnknown, NULL, 0 };
m_pHandler                = new CEventHandler(this);
hr  = CoCreateInstanceEx( CLSID_Server, NULL, CLSCTX_SERVER,
                          &psi, 1, &mqi );
if (FAILED(hr) )          return false;
hr                        = mqi.hr;
if (FAILED(hr))           return false;
m_pUnk                    = (IUnknown*)mqi.pItf;
m_pHandler->DispEventAdvise(m_pUnk);

On the client space are implemented threads that look at equal time intervals at the server if it is down—ComPing. If it is, the client must re-advise on the server to receive events after the server restarts.

Mailslot Calls

That system is used to distribute the server machine name and server share name to all client machines that are running on the network.

On the server side, the WorkerThreadSignalClients function launches at an equal time interval the CServiceModule::SignalClients member function. The PutSignal function, located the in Signals.h file, retrieves the name of machine server name and writes that name to all mail slots opened by the clients on the network.

bool     PutSignal(LPTSTR szMailslotName, LPTSTR &szError)
{
    ...
    if ( !GetComputerName(szMessage, szError) )
         return false;
    if ( !DoMailSlotServerMessageWrite(szMailslotName,
                                       szMessage,
                                       szError))
         return false;
    ....
}

This is done in the DoMailSlotServerMessageWrite function, using the win32 API mailslots functions:

bool DoMailSlotServerMessageWrite ( LPTSTR lpsName,
                                    LPTSTR lpsMessage,
                                    LPTSTR &szError  )
{
  ...
  // Create a string for the mailslot name.
  // (all in global domain)
  wsprintf (      achMailSlotName, L"\\\\*\\mailslot\\%s",
                  lpsName );

  // Open a handle to the mailslot.
  hFile = CreateFile (achMailSlotName,     // file name
    GENERIC_WRITE,                         // access mode
    FILE_SHARE_READ,                       // share mode
    (LPSECURITY_ATTRIBUTES) NULL,          // SD
    OPEN_EXISTING,                         // how to create
    FILE_ATTRIBUTE_NORMAL,                 // file attributes
    (HANDLE) NULL );                       // handle to template
                                           // file
  // Send a mailslot message.
  fResult = WriteFile (    hFile,          // handle to file
    achBuffer,                             // data buffer
    cbToWrite,                             // number of bytes to
                                           // write
    &cbWritten,                            // number of bytes
                                           // written
    (LPOVERLAPPED) NULL );                 // overlapped buffer
  ...
}

On the client side, the WorkerThreadLookupSignals are responsible to launch at equal time intervals the member function CClient::LookupSignals. ReadSignals uses the GetSignals function located in Signals.h to receive messages on the client mailslot.

bool GetSignals(LPTSTR szMailslotName,
                LPTSTR &szMessage,
                LPTSTR &szError)
{
  if ( !DoMailSlot(szMailslotName, hSlot, szError))
    goto err;
  if ( !DoMailSlotMessageRead(szMailslotName, hSlot,
                              szMessage, szError))
    goto err;
  if ( !DoMailSlotClose(hSlot, szError))
    goto err;
}
  • The DoMailSlot function creates on the client side a mailslot with the name given in the lpsName variable. That mailslot is visible on the network by using the lpsName name.
  • // Create a string for the mailslot name.
    wsprintf ( achMailSlotName, L"\\\\.\\mailslot\\%s", lpsName );
    // Create the mailslot.
    hSlot = CreateMailslot ( achMailSlotName, // mailslot name
      0,                                      // maximum message
                                              // size
      10 * 1000,                              // read time-out
                                              // interval
      (LPSECURITY_ATTRIBUTES)NULL );          // inheritance
                                              // option
    
  • The DoMailSlotMessageRead function reads messages in the mailslot created in the DoMailSlot function. Those messages are written from the server component with the DoMailSlotServerMessageWrite function.
  • fResult = ReadFile (    hSlot,       // handle to file
      achBuffer,                         // data buffer
      cbToRead,                          // number of bytes to read
      &cbRead,                           // number of bytes read
      (LPOVERLAPPED)NULL  );             // overlapped buffer
    
  • The DoMailSlotClose function closes the mailslot opened by DoMailSlot function.
  • // Close the handle to our mailslot.
    fResult = CloseHandle ( hSlot );
    

Copy Files

The directories and files are effectively copied by the client component to the local path from the server network share. That fact happens after one event fire by the server component.

To copy those resources from the server, the client machine uses the extended DOS command xcopy. That command gives the possibilities to copy an entire directory or only the files tha are newest on the server, as on the client machine.

sprintf(szCmdExec,
"cmd.exe /C xcopy \"%s\" \"%s\" /E /R /K /Y /I /D
        >>c:\\temp\\LogReplicationClientDos.txt",
  szServer, szLocal);
RunShell(szCmdExec);

To launch that command, use the win32 CreateProcess function. In the CEventHandler class, the RunShell method is a wrapper to the win32 CreateProcess function:

void     CEventHandler::RunShell(_bstr_t bstrCmdExec)
{
  STARTUPINFO          si;
  PROCESS_INFORMATION  pi;
  memset(&si,0,sizeof(STARTUPINFO));
  si.cb                = sizeof(STARTUPINFO);
  si.wShowWindow       = SW_SHOW;
  bool bbb             = CreateProcess(NULL, bstrCmdExec,
                                       NULL, NULL,
                                       false, 0, NULL, NULL,
                                       &si,&pi);
  CloseHandle(pi.hThread);
  CloseHandle(pi.hProcess);
}

Win32 Functions

Here are three points that are resolved by win32 API calls encapsulated in three modules. Those three modules functions are used in the CServiceModule::CServiceModule constructor of the DCOM service:

Manage user

Both ReplicationServer and ReplicationClient run under a default local user account (.\IXNET with password ixnet) that is automatically created when installing the services. The module looks whether the user exists, whether the password is okay, and so forth. If the administrator changes that user (or changes the password) and puts the new one into a registry, the module looks again whether the new user is okay. If not, it is changed with the default one.

Those functionalities are encapsulated in AccountMannager.h file.

  • The gethostname function returns the standard host name for the local machine.
  • bool GetComputerNameLocal( LPTSTR &szName, LPTSTR &szError )
    
  • The GetComputerNameEx function retrieves a NetBIOS or DNS name associated with the local computer.
  • bool GetDomainNameLocal( LPTSTR &szName, LPTSTR &szError )
    
  • The AccountCreate function uses the NetUserAdd Win API function to add a user account and assigns a password and privilege level:
  • bool AccountCreate( LPTSTR szAccountDefault,
                        LPTSTR szDomainName,
                        LPTSTR szAccountPassword,
                        LPTSTR &szError )
    
  • The AccountInsertInGroup function uses the NetLocalGroupAddMembers the function to add membership of one or more existing user accounts or global group accounts to an existing local group:
  • bool AccountInsertInGroup( LPTSTR szAccountDefault,
                               LPTSTR szLocalGroup,
                               LPTSTR &szError )
    
  • The AccountExist function uses the NetUserGetInfo function to retrieve information about a particular user account on a server.
  • bool AccountExist( LPTSTR szAccountDefault, bool
                       &bExist, LPTSTR &szError )
    
  • The AccountDelete function uses the NetUserDel function to delete a user account from a server.
  • bool AccountDelete( LPTSTR szAccountDefault, LPTSTR &szError )
    

Manage Service Credentials

When a service runs under user credential rights, it must have these rights. This is done only the first time when the service is started. The module looks at the user assigned to the service, looks if it is okay, and adds the launch service rights to that user.

Those functionalities are encapsulated in the ServiceRights.h file. This module is a version of the basic win32 program privs.exe.

  • The OpenPolicy function uses the The LsaOpenPolicy function to open a handle to the Policy object on a local or remote system. To administer the local security policy of a local or remote system, you must call the LsaOpenPolicy function to establish a session with that system’s LSA subsystem.
  • NTSTATUS OpenPolicy( LPWSTR ServerName, DWORD DesiredAccess,
                         PLSA_HANDLE PolicyHandle)
    
  • The GetAccountSid function obtains the SID of the user/group. It uses the LookupAccountName function that retrieves a security identifier (SID) for the account and the name of the domain on which the account was found.
  • BOOL GetAccountSid( LPTSTR SystemName,
                        LPTSTR AccountName,
                        PSID *Sid)
    
  • The SetPrivilegeOnAccount function grants the SeServiceLogonRight to users represented by pSid. It uses the win API LsaAddAccountRights function to assign one or more privileges to an account. If the account does not exist, LsaAddAccountRights creates it.
  • NTSTATUS SetPrivilegeOnAccount( LSA_HANDLE PolicyHandle,
                                    PSID AccountSid,
                                    LPWSTR PrivilegeName,
                                    BOOL bEnable)
    

Manage Share

To copy the files between the server computer and client computers, a network share of server repository files must be done. This is done automatically when the server ReplicationService calls the PutResource method call the first time.

Those functionalities are encapsulated in the ShareMannager.h file.

  • The GetAccess function gets the security descriptor to AccountName. It uses the LookupAccountName function to retrieve a security identifier (SID) for the account and the name of the domain on which the account was found. After that, the InitializeAcl function creates a new ACL structure and the AddAccessAllowedAce function is used to add an access-allowed ACE to that ACL. Finally, we use the InitializeSecurityDescriptor and SetSecurityDescriptorDacl functions to set information in that discretionary access-control list (ACL).
  • bool GetAccess( LPTSTR AccountName, SECURITY_DESCRIPTOR &sd,
                    LPTSTR &szError )
    
  • The ShareCreate function uses the NetShareAdd win32 API function to share a server resource.
  • bool ShareCreate( LPTSTR pszDirectoryToShare,
                      LPTSTR pszShareName,
                      SECURITY_DESCRIPTOR sd,
                      LPTSTR &szError )
    
  • The DeleteShare function uses the NetShareDel function to delete a share name from a server’s list of shared resources.
  • bool DeleteShare(LPTSTR pszPathRoot, LPTSTR pszDirectory,
                     LPTSTR &szError)
    

Installation

Server service component installation

  • Copy the “ReplicationServer.exe” file into a directory from the server computer (a component directory, with enough rights).
  • Register ReplicationServer like a service, by running the command
    ReplicationServer.exe /service
    

    in an MS-DOS window.

  • If everything is okay, a new service ,-> “ReplicationServer”, will appear in the services control panel, running under the .\IXNET local user account.
  • Run the service by clicking the “Start Service” button on the services control panel.

Client service component installation

  • Edit the Registry and put/edit the path where the structure of server directory will be replicated. The default is: [HKEY_LOCAL_MACHINE\SOFTWARE\Articles\Replication Directory\Paths] with the key: "COFilesPath"="C:\\Temp\\COFiles\\".
  • Copy the “ReplicationServer.exe” file into a directory from the server computer (a component directory, with enough rights).
  • Copy the “ReplicationClient.exe” file into the same directory.
  • To run ReplicationClient, you need to find some information about the server component. This is done by registering the server component on each client computer; run in an MS-DOS window the command
  • ReplicationServer.exe /regserver
    

    Attention: the ReplicationServer must run like a service only to the server computer!!!

  • Register ReplicationClient like a service running the command
    ReplicationClient.exe /service
    

    in an MS-DOS window.

  • If everything is okay, a new service, -> “ReplicationClient”, will appear in the services control panel, running under the .\IXNET local user account.
  • Run the service by clicking the “Start Service” button on the services control panel.

Modification of User Account

Both ReplicationServer and ReplicationClient run under a default local user account (.\IXNET with password ixnet) that is automatically created when installing the services. If you need to change it or to put a domain user, edit the Registry key.

[HKEY_LOCAL_MACHINE\SOFTWARE\Articles\Replication Directory\Admin]

It is necessary that the user gives credential rights to services to copy the files within the network.

Start Replication System

After the successful installation of ReplicationServer and ReplicationClients services, the system will wait for the first call of PutResource (Ex: replication.asp), to make the system initialization. After that, the first call of the method containing the parameter “Directory” will create a new share with the same name “Directory” on the server computer. All clients will be advised about that share. For security reasons, only the “.\IXNET” users have rights to that share.

Web Calls

The idea of this system is to replicate immediately to another computers a file or an entire directory structure that is uploaded by ASP on the repository server. To do this, just make a call to the PutResource method after a successful upload. Anyway, it is possible that the system to work independently in ASP, with calls made by other modules: SQL, C++, VB, and Delphi.

Replication.asp

This ASP file gives a sample about how to publish a new resource using the replication system.

'create an instance of our server service object
set obj = server.CreateObject("ReplicationServer.Server.1")

'this is the share repository directory
directory = "CoFiles"

'this is the local path to the share repository directory
path = Server.MapPath("/")

obj.PutResource path, directory, "atl"

'"atl" must be a name of a file or directory
' located in: path\ImagesDirectory\

'The parameter of PutResource method: RootPath, ImagesDirectory,
'ResourceFileName.

Resync.asp

This ASP file gives a sample about how to make a total resynchronization of files between server and client computers. All the files that are new on the server share and aren’t on the client computer will be copied.

'create an instance of our server
set obj = server.CreateObject("ReplicationServer.Server.1")

'launch synchronize method service object
obj.Syncronize

PrintClientsList.asp

This ASP file gives a sample about how to obtain a list of current client computers that are running the ReplicationClient service and that are online.

'create an instance of our server service object
set obj = server.CreateObject("ReplicationServer.Server.1")

str = obj.GetActiveClients   'launch GetActiveClients method
Response.Write str           'write the list

Observations

  • The ReplicationServer and ReplicationClient services must run on different computers.
  • On the computer that is running one of that services the directory C:\temp must be created, where the system put the log files: Log.txt and LogDos.txt.
  • Some settings are kept between sessions in the Registry using the WriteSettingsPaths and ReadSettingsPaths functions. These settings are automatically obtained by components. The only settings that need to be written in the Registry by hand is \Paths\CoFilesPath—the local location of replication directory—on each client machine.
  • To compile, the projects must have the path “C:\Program Files\Microsoft SDK\include” and “C:\Program Files\Microsoft SDK\lib” on the first position on the Options->Directories Include and Library files.

Downloads

Download demo project – 376 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read