How to create an anonymous pipe that gives access to everyone (813414)
The information in this article applies to:
- Microsoft Platform Software Development Kit (SDK) 1.0
SUMMARYThis article describes how to create an anonymous pipe that gives access to everyone. This article contains a sample application that creates an anonymous session pipe that is used for communication between the server and the client. This article describes the following in detail: Creating a server that writes the information at one end of the pipe. Registering the name of the pipe in the registry. Handling the Windows security when communicating over the pipe. Creating a client that reads the information from the other end of the pipe.
INTRODUCTIONThis article describes how to create a pipe server that sends and receives data across domains without requiring user names and passwords.
When a client connects to the pipe server, the server makes an access check, and then requests the client to authenticate. Depending on how the permissions of the pipe are defined, the access may be granted or denied. If the client user has an access token, the client can connect to the server. If the client does not have an access token, the client application must perform a logon to the server. However, sometimes you may have to give access to everyone. Therefore, you must create an anonymous, or null, session pipe. Because you are granting permissions to everyone, your pipe must not give access to any sensible information to the users.MORE INFORMATIONThe sample application in this article is a simple client/server application that uses an anonymous named pipe. The server incrementally writes numbers to the pipe that are read and printed by the client. Because the pipe is anonymous, the client may connect from other domains through an anonymous logon.
The sample application is explained as follows. ServerThe server creates the anonymous pipes, and then listens for clients. To create an anonymous pipe, make sure that you do the following: - Add the name of the pipe to the registry.
- Define the LPSECURITY_ATTRIBUTES attributes that are passed to the CreateNamedPipe function.
RegistryIn the registry of the server computer, the pipe name is added to the following registry subkey: \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\ The pipe name that is added to this registry entry is the name after the last backslash (\) character in the string that is used to open the pipe. For example, for the \\ myserver\pipe\AnonymousPipe pipe, you must add AnonymousPipe to the NullSessionPipes registry entry on the server computer. You can do this by using the RegisterNullSession function and the UnregisterNullSession function in the sample application. Note myserver is a placeholder for the name of your server. SecurityMany programmers use NULL Discretionary Access Control List (DACL) to give anonymous access to shared resources. However, there are no restrictions that are defined to access the shared resource. You must create a security identifier (SID) that gives the anonymous logon and the read-only access to the pipe. The SID that you create in the sample is known by every installation of Microsoft Windows, and is named AnonymousLogin. This SID is described as S-1-5-7. For more information about these numbers, see the Winnt.h file. When the pipe client connects to the server, an anonymous logon is performed by making a call to the WNetAddConnection2 function, and the AnonymousLogin SID will grant the access. In the sample application, a small class that is named SEC is created to handle the complex access structures. This behavior eases the freeing of allocated memory. The BuildSecurityAttributes function in this class builds the SID and sets the permissions. The permissions for the anonymous logon are set as read-only because the pipe is a read-only pipe. You do this by making a call to the AddAccessAllowedAce function. If you want to create a pipe that has both read and write permissions, you must change the permissions with the AddAccessAllowedAce function. The other SID must be defined by the owner who owns the named pipe, and this makes sure that the application has access to the named pipe. The owner SID is the user SID that is running the thread or the process. The GetUserSid function extracts the SID from the token of the user. The token is created when you log on and you specify your name and password that is used for all access checks. With the attributes defined, the server calls the CreateNamedPipe function, and then waits for incoming client calls. ClientThe client opens the pipe with a call to the CreateFile function. If an error occurs, the client checks if it is a logon failure error, an access denied error, or a bad password error. If an error occurs, perform an anonymous logon by calling
the WNetAddConnection2 function and passing an empty string as user name and password. When the null session is established, the client calls the CreateFile function again. After the client is connected, it prints a number each second. Sample applicationTo create an application that communicates between the processes by using an anonymous pipe that gives access to everyone, follow these steps: - Start Microsoft Visual C++ 6.0.
- On the File menu, click New.
- On the Projects tab, click Win32 Console Application, type MyApp in the Project name box, and then click OK.
- In the Win32 Console Application - Step 1 of 1 dialog box, click Finish.
- In the New Project Information window, click OK.
- On the File menu, click New. The New dialog box appears.
- Click C++ Source File. In the right pane, type MyApp in the File name box, and then click OK.
- In the left pane, click the FileView tab.
- Expand MyApp files, and then expand Source Files.
- Under Source Files, right-click MyApp.cpp, and then click Open. In the right pane, the code view of the MyApp.cpp file opens.
- Add the following code to the MyApp.cpp file:
#define UNICODE
#include <windows.h>
#include <stdio.h>
// Types
class SEC
{
// Class to handle security attributes
public:
SEC();
~SEC();
BOOL BuildSecurityAttributes( SECURITY_ATTRIBUTES* psa );
private:
BOOL GetUserSid( PSID* ppSidUser );
BOOL allocated;
PSECURITY_DESCRIPTOR psd;
PACL pACL;
PTOKEN_USER pTokenUser;
};
// Constants
static const WCHAR* PipeName = L"AnonymousPipe";
// Functions
static void Client( const WCHAR* server );
static void Server( void );
static void WriteToPipe( HANDLE hPipe );
static void ReadFromPipe( HANDLE hPipe );
static void DisplayError( WCHAR *pszAPI);
static BOOL WINAPI ConsoleCtrlHandler( DWORD CtrlType );
static BOOL EstablishNullSession( const WCHAR* computerName );
static BOOL RegisterNullSession( const WCHAR* pipeName );
static BOOL UnregisterNullSession( const WCHAR* pipeName );
static LONG RegInsertToMultiString( HKEY hKey, const WCHAR* valueName, const WCHAR* str );
static LONG RegRemoveFromMultiString( HKEY hKey, const WCHAR* valueName, const WCHAR* str );
static BOOL FindEntryInMultiString( const WCHAR* data, const WCHAR* entry, DWORD* offset );
void
wmain( int argc, WCHAR* argv[] )
{
if( argc < 2 )
{
wprintf( L"Usage: pipe.exe <server|client [server name for client]>\n" );
exit(1);
}
if( !SetConsoleCtrlHandler( ConsoleCtrlHandler, TRUE ) )
DisplayError( L"SetConsoleCtrlHandler" );
if( wcsicmp( argv[1], L"client") == 0 )
{
WCHAR server[512];
if( argc == 3 )
{
wcscpy( server, argv[2] );
}
else
{
wcscpy( server, L"." );
}
Client( server );
}
else
if( wcsicmp( argv[1], L"server" ) == 0 )
{
Server();
}
else
{
wprintf( L"Usage: pipe.exe <server|client [server name for client]>\n" );
exit(1);
}
}
//Runs the server
void
Server( void )
{
SEC sec;
HANDLE hPipe;
BOOL isConnected;
SECURITY_ATTRIBUTES sa;
WCHAR server[512];
swprintf( server, L"\\\\.\\pipe\\%s", PipeName );
if( !RegisterNullSession( PipeName ) )
{
wprintf( L"WARNING: could not register %s for Null Session, server will not be accessible from outside domain.\n", server );
wprintf( L"WARNING: only administrators can register a Null Session Pipe.\n" );
}
sec.BuildSecurityAttributes( &sa );
wprintf( L"INFORM: to stop, press Ctrl-C or Ctrl-Break.\n" );
while( 1 )
{
hPipe = CreateNamedPipe(
server,
PIPE_ACCESS_OUTBOUND, // read-only pipe
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
sizeof(DWORD),
0,
NMPWAIT_USE_DEFAULT_WAIT,
&sa );
if( hPipe == INVALID_HANDLE_VALUE )
DisplayError( L"CreatePipe" );
// Wait for the client to connect.
wprintf( L"INFORM: listening at %s, waiting for client to connect\n", server );
isConnected = ConnectNamedPipe( hPipe, NULL ) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if( isConnected )
{
wprintf( L"INFORM: client connected.\n" );
WriteToPipe( hPipe );
}
else
// The client could not connect. Therefore, close the pipe.
CloseHandle(hPipe);
}
}
//Runs the client
void
Client( const WCHAR* server )
{
HANDLE hPipe;
WCHAR fullName[512];
BOOL triedLogon = FALSE;
swprintf( fullName, L"\\\\%s\\pipe\\%s", server, PipeName );
wprintf( L"INFORM: connecting to %s\n", fullName );
while( 1 )
{
hPipe = CreateFile(
fullName,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
SECURITY_ANONYMOUS,
NULL);
if( hPipe == INVALID_HANDLE_VALUE )
{
DWORD lastError = GetLastError(); // An error ocurred, try to analyse it
if( (lastError == ERROR_LOGON_FAILURE) ||
(lastError == ERROR_ACCESS_DENIED) ||
(lastError == ERROR_INVALID_PASSWORD)) //Add other access errors here if it is required
{
if( triedLogon )
{
DisplayError( L"CreateFile" );
}
else
if( EstablishNullSession( server ) )
{
wprintf( L"INFORM: established Null session.\n" );
}
else
{
wprintf( L"WARNING: could not establish Null Session.\n" );
}
triedLogon = TRUE;
}
else
if( lastError == ERROR_PIPE_BUSY )
{
// All pipe instances are busy. Therefore, wait for 20 seconds.
wprintf( L"INFORM: pipe busy, trying for 20secs to reconnect.\n" );
if( !WaitNamedPipe( fullName, 20000) )
DisplayError( L"WaitNamedPipe" );
}
else
{
DisplayError( L"CreateFile" );
}
}
else
{
ReadFromPipe( hPipe );
}
}
}
/**
Tries to establish a NULL session to the remote computer. A NULL session allows access to NullSessionPipes of
the server.
Input parameters: computerName: name of the server to connect to
Output parameters: TRUE | FALSE
*/
BOOL
EstablishNullSession( const WCHAR* computerName )
{
NETRESOURCE nr;
WCHAR server[MAX_PATH];
DWORD ret;
if( wcscmp( computerName, L"." ) == 0 )
{
wcscpy( server, computerName );
}
else
{
wcscpy( server, L"\\\\" );
wcscat( server, computerName );
}
ZeroMemory( &nr, sizeof(nr) );
nr.dwType = RESOURCETYPE_ANY;
nr.lpRemoteName = server;
ret = WNetAddConnection2( &nr, L"", L"", 0);
if( ret != ERROR_SUCCESS )
{
DisplayError( L"WNetAddConnection2" );
return FALSE;
}
return TRUE;
}
//Called when the user presses Ctrl-C or Ctrl-Break in the console window.
BOOL WINAPI
ConsoleCtrlHandler( DWORD CtrlType )
{
// clean up the registry
UnregisterNullSession( PipeName );
return FALSE;
}
/** Writes to the pipe
Input parameters: hPipe: handle to an opened pipe
*/
void
WriteToPipe( HANDLE hPipe )
{
DWORD i = 0;
DWORD cbWrite, cbWritten;
cbWrite = sizeof(DWORD);
while( 1 )
{
if( !WriteFile( hPipe, &i, cbWrite, &cbWritten, NULL) )
{
if( cbWrite != cbWritten) break;
}
i++;
Sleep( 1000 );
}
// Flush the pipe to allow the client to read the contents of the pipe
// before disconnecting. Disconnect the pipe, and then close the
// handle to this pipe instance.
FlushFileBuffers( hPipe );
DisconnectNamedPipe( hPipe );
CloseHandle( hPipe );
wprintf( L"INFORM: client disconnected.\n" );
}
/** Reads from pipe
Input parameters: hPipe: handle to opened pipe. */
void
ReadFromPipe( HANDLE hPipe )
{
DWORD i, cbRead;
while( 1 )
{
if( !ReadFile( hPipe, &i, sizeof(DWORD), &cbRead, NULL) )
{
if( GetLastError() != ERROR_MORE_DATA )
{
CloseHandle( hPipe );
DisplayError( L"ReadFile" );
}
}
wprintf( L" %li ", i );
};
}
/** Displays an error and exits the process
Input parameters: pszAPI: name of the Win32 function that returned an error. */
void
DisplayError( WCHAR* pszAPI )
{
LPVOID lpvMessageBuffer;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpvMessageBuffer, 0, NULL);
//... now display this string
wprintf( L"ERROR: API = %s.\n", pszAPI);
wprintf( L" error code = %d.\n", GetLastError());
wprintf( L" message = %s.\n", (char *)lpvMessageBuffer);
// Free the buffer allocated by the system
LocalFree( lpvMessageBuffer );
ExitProcess( GetLastError() );
}
/** Constructor */
SEC::
SEC()
{
allocated = FALSE;
psd = NULL;
pACL = NULL;
pTokenUser = NULL;
}
/** Destructor */
SEC::
~SEC()
{
if( allocated )
{
if( psd ) HeapFree( GetProcessHeap(), 0, psd );
if( pACL ) HeapFree( GetProcessHeap(), 0 , pACL );
if( pTokenUser ) HeapFree( GetProcessHeap(), 0, pTokenUser );
allocated = FALSE;
}
}
/** Builds security attributes that allows read-only access to everyone
Input parameters: psa: security attributes to build
Output parameters: TRUE | FALSE */
BOOL SEC::
BuildSecurityAttributes( SECURITY_ATTRIBUTES* psa )
{
DWORD dwAclSize;
PSID pSidAnonymous = NULL; // Well-known AnonymousLogin SID
PSID pSidOwner = NULL;
if( allocated ) return FALSE;
SID_IDENTIFIER_AUTHORITY siaAnonymous = SECURITY_NT_AUTHORITY;
SID_IDENTIFIER_AUTHORITY siaOwner = SECURITY_NT_AUTHORITY;
do
{
psd = (PSECURITY_DESCRIPTOR) HeapAlloc( GetProcessHeap(),
HEAP_ZERO_MEMORY,
SECURITY_DESCRIPTOR_MIN_LENGTH);
if( psd == NULL )
{
DisplayError( L"HeapAlloc" );
break;
}
if( !InitializeSecurityDescriptor( psd, SECURITY_DESCRIPTOR_REVISION) )
{
DisplayError( L"InitializeSecurityDescriptor" );
break;
}
// Build anonymous SID
AllocateAndInitializeSid( &siaAnonymous, 1,
SECURITY_ANONYMOUS_LOGON_RID,
0,0,0,0,0,0,0,
&pSidAnonymous
);
if( !GetUserSid( &pSidOwner ) )
{
return FALSE;
}
// Compute size of ACL
dwAclSize = sizeof(ACL) +
2 * ( sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) ) +
GetLengthSid( pSidAnonymous ) +
GetLengthSid( pSidOwner );
pACL = (PACL)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwAclSize );
if( pACL == NULL )
{
DisplayError( L"HeapAlloc" );
break;
}
InitializeAcl( pACL, dwAclSize, ACL_REVISION);
if( !AddAccessAllowedAce( pACL,
ACL_REVISION,
GENERIC_ALL,
pSidOwner
))
{
DisplayError( L"AddAccessAllowedAce" );
break;
}
if( !AddAccessAllowedAce( pACL,
ACL_REVISION,
FILE_GENERIC_READ, //GENERIC_READ | GENERIC_WRITE,
pSidAnonymous
) )
{
DisplayError( L"AddAccessAllowedAce" );
break;
}
if( !SetSecurityDescriptorDacl( psd, TRUE, pACL, FALSE) )
{
DisplayError( L"SetSecurityDescriptorDacl" );
break;
}
psa->nLength = sizeof(SECURITY_ATTRIBUTES);
psa->bInheritHandle = TRUE;
psa->lpSecurityDescriptor = psd;
allocated = TRUE;
}while(0);
if( pSidAnonymous ) FreeSid( pSidAnonymous );
if( pSidOwner ) FreeSid( pSidOwner );
if( !allocated )
{
if( psd ) HeapFree( GetProcessHeap(), 0, psd );
if( pACL ) HeapFree( GetProcessHeap(), 0 , pACL );
}
return allocated;
}
/** Obtains the SID of the user running this thread or process.
Output parameters: ppSidUser: the SID of the current user,
TRUE | FALSE: could not obtain the user SID */
BOOL SEC::
GetUserSid( PSID* ppSidUser )
{
HANDLE hToken;
DWORD dwLength;
DWORD cbName = 250;
DWORD cbDomainName = 250;
if( !OpenThreadToken( GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken) )
{
if( GetLastError() == ERROR_NO_TOKEN )
{
if( !OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken) )
{
return FALSE;
}
}
else
{
return FALSE;
}
}
if( !GetTokenInformation( hToken, // handle of the access token
TokenUser, // type of information to retrieve
pTokenUser, // address of retrieved information
0, // size of the information buffer
&dwLength // address of required buffer size
))
{
if( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
{
pTokenUser = (PTOKEN_USER) HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength );
if( pTokenUser == NULL )
{
return FALSE;
}
}
else
{
return FALSE;
}
}
if( !GetTokenInformation( hToken, // handle of the access token
TokenUser, // type of information to retrieve
pTokenUser, // address of retrieved information
dwLength, // size of the information buffer
&dwLength // address of required buffer size
))
{
HeapFree( GetProcessHeap(), 0, pTokenUser );
pTokenUser = NULL;
return FALSE;
}
*ppSidUser = pTokenUser->User.Sid;
return TRUE;
}
/** Registers a pipe as a NULL SESSION pipe that may be accessed by unauthenticated users.
Input parameters: serverName: pipe to register
Output parameters: TRUE: pipe registered
| FALSE: could no register pipe. */
BOOL
RegisterNullSession( const WCHAR* pipeName )
{
LONG status;
HKEY hKey;
// Opens key
status = RegCreateKeyEx( HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Services\\lanmanserver\\parameters",
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_QUERY_VALUE | KEY_SET_VALUE,
NULL,
&hKey,
NULL);
if( status != ERROR_SUCCESS )
{
return FALSE;
}
status = RegInsertToMultiString( hKey, L"NullSessionPipes", pipeName );
RegCloseKey( hKey );
if( status != ERROR_SUCCESS )
{
return FALSE;
}
return TRUE;
}
/** Unregisters a pipe from the NULL SESSION pipe list.
Input parameters: pipeName: pipe to unregister
Output parameters: TRUE: pipe unregistered or pipe was not registered
| FALSE: could not unregister pipe
*/
BOOL
UnregisterNullSession( const WCHAR* pipeName )
{
LONG status;
HKEY hKey;
// Opens key
status = RegCreateKeyEx( HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Services\\lanmanserver\\parameters",
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_QUERY_VALUE | KEY_SET_VALUE,
NULL,
&hKey,
NULL);
if( status != ERROR_SUCCESS )
{
return FALSE;
}
status = RegRemoveFromMultiString( hKey, L"NullSessionPipes", pipeName );
RegCloseKey( hKey );
if( (status != ERROR_SUCCESS) && (status != ERROR_INVALID_NAME) )
{
return FALSE;
}
return TRUE;
}
/** Inserts a string to a MULTI_SZ value. If the string is already present, nothing will be added. This
avoids duplicated strings.
Input parameters: hKey: handle to open key,
valueName: name of the multistring value to which the str value will be appended,
str: string to append
<-- ERROR_SUCCESS
| any error code returned by RegQueryValueEx and RegSetValueEx */
LONG
RegInsertToMultiString( HKEY hKey, const WCHAR* valueName, const WCHAR* str )
{
LONG status;
BYTE* data;
DWORD newSize, oldSize, strSize;
// Obtains the current data size
status = RegQueryValueEx( hKey,
valueName,
NULL,
NULL,
NULL,
&oldSize );
if( status != ERROR_SUCCESS )
{
return status;
}
// Allocates memory to hold all the data
strSize = (wcslen(str) + 1) * sizeof(WCHAR);
newSize = oldSize + strSize;
data = (BYTE*)HeapAlloc( GetProcessHeap(), 0 , newSize );
if( data == NULL )
{
return ERROR_OUTOFMEMORY;
}
// Obtains the current data
status = RegQueryValueEx( hKey,
valueName,
NULL,
NULL,
data,
&oldSize );
if( status != ERROR_SUCCESS )
{
HeapFree( GetProcessHeap(), 0 , data );
return status;
}
// Looks if the data is already there
if( FindEntryInMultiString( (WCHAR*)data, str, NULL ) )
{
HeapFree( GetProcessHeap(), 0 , data );
return ERROR_SUCCESS;
}
// Appends our entry
CopyMemory( data+oldSize-2, str, strSize );
// Append another NULL terminator
data[newSize-2] = 0;
data[newSize-1] = 0;
// Sets the new data
status = RegSetValueEx( hKey,
valueName,
0,
REG_MULTI_SZ,
data,
newSize );
HeapFree( GetProcessHeap(), 0 , data );
return status;
}
/** Removes a string from a MULTI_SZ value.
Input parameters: hKey: handle to open key,
valueName: name of the multistring value to which the str value will be appended,
str: string to append
<-- ERROR_SUCCESS
| ERROR_INVALID_NAME: str was not found
| any error code returned by RegQueryValueEx and RegSetValueEx */
LONG
RegRemoveFromMultiString( HKEY hKey, const WCHAR* valueName, const WCHAR* str )
{
LONG status;
BYTE* data;
DWORD size, offset, strSize;
BYTE* p;
BOOL found = FALSE;
// Obtains the current data size
status = RegQueryValueEx( hKey,
valueName,
NULL,
NULL,
NULL,
&size );
if( status != ERROR_SUCCESS )
{
return status;
}
// Allocates memory to hold all the data
strSize = (wcslen(str) + 1) * sizeof(WCHAR);
data = (BYTE*)HeapAlloc( GetProcessHeap(), 0 , size );
if( data == NULL )
{
return ERROR_OUTOFMEMORY;
}
// Obtains the current data
status = RegQueryValueEx( hKey,
valueName,
NULL,
NULL,
data,
&size );
if( status != ERROR_SUCCESS )
{
HeapFree( GetProcessHeap(), 0 , data );
return status;
}
// Searches the entry to be removed
found = FindEntryInMultiString( (WCHAR*)data, str, &offset );
if( !found )
{
HeapFree( GetProcessHeap(), 0 , data );
return ERROR_INVALID_NAME;
}
// Writes over the entry, and then puts it back in the registry
p = data + offset;
CopyMemory( p, p + strSize, size - strSize - offset );
status = RegSetValueEx( hKey,
valueName,
0,
REG_MULTI_SZ,
data,
size - strSize );
HeapFree( GetProcessHeap(), 0 , data );
return status;
}
/** Input parameters: data: multi string in which the entry is to be found,
entry: to find
offset: if not NULL holds the number of bytes from the beginning of data where entry starts
Output parameters: TRUE: found entry
| FALSE: did not find entry */
BOOL
FindEntryInMultiString( const WCHAR* data, const WCHAR* entry, DWORD* offset )
{
BYTE* p;
DWORD read, tmp;
BOOL found = FALSE;
p = (BYTE*)data;
read = 0;
while( !( (*p == 0 ) && ((*(p+1)) == 0 ) ) )
{
if( wcscmp( (WCHAR*)p, entry ) == 0 )
{
found = TRUE;
break;
}
tmp = (wcslen( (WCHAR*)p ) + 1) * sizeof(WCHAR);
read += tmp ;
p += tmp;
}
if( offset )
{
if( found )
{
*offset = read;
}
else
{
*offset = 0;
}
}
return found;
} - To build the application, you must add the Mpr.lib file to your project. To do this, follow these steps:
- On the Project menu, click Settings. The Project Settings dialog box appears.
- Click the Link tab, and then append Mpr.lib to the string in the Object/library modules box.
- Click OK.
- On the Build menu, click Build MyApp.exe.
- To run the application, follow these steps:
- Click Start, click Run, type cmd in the Open box, and then click OK.
- At the command prompt, run the following command:
Path of MyApp\MyAPP server The server waits for the client to connect.
Note Path of MyApp is a placeholder for the path where the MyApp.exe file is saved. - Repeat step a, and then run the following command at the command prompt:
Path of MyApp\MyAPP client Computer_Name The client connects to the server, and then prints the numbers sequentially that are read from the pipe.
Note Computer_Name is a placeholder for the name of the client computer.
REFERENCESFor more information, visit the following Microsoft Developer Network (MSDN) Web sites:
Modification Type: | Minor | Last Reviewed: | 7/8/2005 |
---|
Keywords: | kbClient kbServer kbPipes kbSecurity kbhowto KB813414 kbAudDeveloper |
---|
|