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 there are the asp
administration files. 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 network, while the system is working. Every time when a new client
service will be started on a new computer, the server will be automatically advised
about it. If a client will be shut down or stopped for certain reasons, after restarting
it, the server will be automatically advised about it.
For making a replication of a resource from server
to clients, the only think you can do is to make a call to 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 server object.
In order to see a list of client computers that
are advised to server, you have to launch the GetActiveClients
method of server object.
Points involved:
- Components.
- Communication.
- Copy files.
- Win32 functions.
- Installation.
- Web calls.
Components.
There are 2 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 is solved the problem of
remote calls between computers as well as the problem of keeping in memory the client
lists. 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 3 parameters:
Path = the location
path where is the root path of the site (generally, this is �c:\IntePub\wwwroot�).
This is obtained with 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 server
computer.
ResourceName = the name of
the file or directory that was uploaded on 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 parameter.
GetActiveClients.( Ex: PrintClientsList.asp)
It will return a list of client computers
that are advised to server. Please, see a demo of how to use it in PrintClientsList.asp.
It doesn't have any parameter.
Talk.
It is used by the client services in
order 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 is solved
the problem of remote calls between computers as well as the problem of keeping
in memory the client lists. 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 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 in 2 parts:
DCOM
events.
That system is used to send events
from the server to all clients who are registered to
server via advice function.
The PutResource
, GetActiveClients
and Syncronizeserver
functions fire events who are received by the DCOM clients.
The server connection events class
responsible to distribute the specified events is CProxyIServerEvents (Fire_OnNewResource,
<code>Fire_OnGetActiveClients
,
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 fallowing 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 receive
the name of the server it launch the <code>LaunchObserver
function who advise the server about the existence of the client and create a
instance of CEventHandler class. The <code>CoCreateInstanceEx
function are used to access the server DCOM service component on the needed
machine(m_szServerName), without regarding at 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);
In the client space are
implemented threads who look at equal time intervals at the server if it is
down - <code>ComPing
.
If it is, the client must make readvise on the server to receive events after
the restart of the server.
Mailslot
calls.
That system is used to distribute
the server machine name and server share name to all clients machines who are
running in the network.
On the server side,
the <code>WorkerThreadSignalClients
function
launch at equal time interval the <code>CServiceModule::SignalClients
member function. The <code>PutSignal
function located in Signals.h file retrieve the name of machine server name and
write 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 <code>DoMailSlotServerMessageWrite
function, using
win32 API mailslots functions:
bool DoMailSlotServerMessageWrite ( LPTSTR lpsName, LPTSTR lpsMessage,
LPTSTR &szError )
{
...
wsprintf ( achMailSlotName, L"\\\\*\\mailslot\\%s", lpsName );
hFile = CreateFile (achMailSlotName,
GENERIC_WRITE,
FILE_SHARE_READ,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL );
fResult = WriteFile ( hFile,
achBuffer,
cbToWrite,
&cbWritten,
(LPOVERLAPPED) NULL );
...
}
On the
client side, the <code>WorkerThreadLookupSignals
are responsible to launch at equal time intervals the member function <code>CClient::LookupSignals
. The
<code>ReadSignals
uses the <code>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 create on the client side a mailslot with name given in lpsName
variable. That mailslot are visible on the network using the lpsName name.
wsprintf ( achMailSlotName, L"\\\\.\\mailslot\\%s", lpsName );
hSlot = CreateMailslot ( achMailSlotName,
0,
10 * 1000,
(LPSECURITY_ATTRIBUTES)NULL );
- The
DoMailSlotMessageRead
function read messages in the mailslot created in the DoMailSlot function.
That�s messages are wrote from the server component with
DoMailSlotServerMessageWrite function.
fResult = ReadFile ( hSlot,
achBuffer,
cbToRead,
&cbRead,
(LPOVERLAPPED)NULL );
- The
DoMailSlotClose
function close the mailslot opened by DoMailSlot function.
fResult = CloseHandle ( hSlot );
Copy files.
The directories and
files are effectively copied by client component to local path from server
network share. That fact is happening after one event fire by the server
component.
To copy that resources from
server to client machine is used the extended dos command, xcopy
. That command give the possibilities to
copy an entire directory or only that files who are newest on the server like
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 are
used the win32 CreateProcess
function.
In CEventHandler
class, RunShell
method is a wrapper to 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 3 points who are resolved
by win32 api calls encapsulated in 3 modules. That 3 modules functions are used
in CServiceModule::CServiceModule
constructor of 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 ok, etc.
If the administrator changes that user (or changes the password) and puts
the new one into a registry, the module looks again if the new user is ok.
If not, it is changed with the default one.
That�s 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 use 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 use the NetLocalGroupAddMembers
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 use the NetUserGetInfo
function to retrieve information
about a particular user account on a server.
bool AccountExist( LPTSTR szAccountDefault, bool &bExist, LPTSTR &szError )
- The
<code>AccountDelete
function use 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
first time when the service is started. The module looks at the user assigned
to the service, looks if it is ok and adds the launch service rights to that user.
That�s functionalities are encapsulated in ServiceRights.h
file. This module are a version of the basic win32 program privs.exe.
- The
OpenPolicy
function use the The LsaOpenPolicy
function opens 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 obtain the SID of the user/group. It used the LookupAccountName
function who 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 grant the SeServiceLogonRight
to users represented by pSid. It uses
the win api LsaAddAccountRights
function assigns 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.
In order to copy
the files between 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 first time the PutResource
method call.
That�s functionalities
are encapsulated in ShareMannager.h file.
- The
GetAccess
function get security descriptor to AccountName
. It use the LookupAccountName
function to retrieves 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 are used 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 use the NetShareAdd
win32 api function shares a server
resource.
bool ShareCreate( LPTSTR pszDirectoryToShare, LPTSTR pszShareName,
SECURITY_DESCRIPTOR sd, LPTSTR &szError )
- The
DeleteShare
function use 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.
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 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.
- In order 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 msdos
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 msdos window.
-
If everything is
ok, in the services control panel will appear a new service ->
�ReplicationClient�, running under the .\IXNET local user account.
- Run the service
by clicking on 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 to give credential rights to services in order to copy the files within the network.
Start
replication system.
After the successful installation of ReplicationServer and ReplicationClients services, the system
will wait the first call of PutResource(
Ex: replication.asp)
, to make the
initialization of system. After that, the first call of the method containing
the parameter "Directory" will create on the server computer a new share with
the same name "Directory". All clients will be advised about that share. For
security reasons, only the ".\IXNET" users have rights on 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. In order to do this,
just make a call to 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, Delphi.
Replication.asp.
This asp file gives a sample about how
to publish a new resource using the replication system.
set obj = server.CreateObject("ReplicationServer.Server.1")
directory = "CoFiles"
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 client
computer will be copied.
set obj = server.CreateObject("ReplicationServer.Server.1")
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.
set obj = server.CreateObject("ReplicationServer.Server.1")
str = obj.GetActiveClients
Response.Write str
Observations:
-
The
ReplicationServer and ReplicationClient services must run on different
computers.
-
On the computer
where are running one of that services must be created the directory C:\temp,
where the system put the log files: Log.txt and LogDos.txt.
-
Some settings
are kept between session in the registry using the
WriteSettingsPaths
and ReadSettingsPaths
functions. These
settings are automatically obtained by components. The only setting who are
needed to be writted in registry by the 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.