text: //////////////////////////////////////////////////////////////////////////////
  // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
  // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  // PARTICULAR PURPOSE.
  //
  // Copyright (c) Microsoft Corporation. All rights reserved.
  //////////////////////////////////////////////////////////////////////////////
  
  #include <stdio.h>
  #include <conio.h>
  #include "Client.h"
  #include <strsafe.h>
  
  //////////////////////////////////////////////////////////////////////////////
  // print_result - Display HRESULTs
  //////////////////////////////////////////////////////////////////////////////
  void print_result(HRESULT hr)
  {
      if( S_OK == hr )
      {
          _cwprintf(L"[S_OK]\r\n");
      }
      else if( S_FALSE == hr )
      {
          _cwprintf(L"[S_FALSE]\r\n");
      }
      else 
      {
          _cwprintf(L"[ERROR: %x]\r\n", hr);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // CFileServiceEventNotify methods
  //////////////////////////////////////////////////////////////////////////////
  CFileServiceEventNotify::CFileServiceEventNotify()
      : m_cRef(1)
  {
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // FileChangeEvent - Invoked when the service sends this event
  //////////////////////////////////////////////////////////////////////////////
  HRESULT STDMETHODCALLTYPE CFileServiceEventNotify::FileChangeEvent(
      FILE_CHANGE_EVENT* pFileChangeEvent)
  {
      if( NULL == pFileChangeEvent )
      {
          return E_INVALIDARG;
      }
  
      // Simply print the event and return
      _cwprintf(L"\r\nReceived event: [%s: %s]\r\n", 
          pFileChangeEvent->EventType, 
          pFileChangeEvent->FileName);
  
      return S_OK;
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // CGetFileAsyncCallback methods
  //////////////////////////////////////////////////////////////////////////////
  CGetFileAsyncCallback::CGetFileAsyncCallback()
  :   m_cRef(1)
  ,   m_pFileServiceProxy(NULL)
  {
  }
  
  CGetFileAsyncCallback::~CGetFileAsyncCallback()
  {
      if( NULL != m_pFileServiceProxy )
      {
          m_pFileServiceProxy->Release();
          m_pFileServiceProxy = NULL;
      }
  }
  
  HRESULT CGetFileAsyncCallback::Init(
          IFileServiceProxy* pFileServiceProxy, 
          LPCWSTR pszFileName,
          LPCWSTR pszReceiveDirectory)
  {
      HRESULT hr = S_OK;
  
      // Validate parameters
      if( NULL == pFileServiceProxy )
      {
          return E_INVALIDARG;
      }
  
      if( NULL == pszFileName )
      {
          return E_INVALIDARG;
      }
  
      if( NULL == pszReceiveDirectory )
      {
          return E_INVALIDARG;
      }
  
      // AddRef or copy parameters
      m_pFileServiceProxy = pFileServiceProxy;
      m_pFileServiceProxy->AddRef();
  
      hr = ::StringCbCopyW(m_szFile, sizeof(m_szFile), pszReceiveDirectory);
  
      if( S_OK == hr )
      {
          hr = ::StringCbCatW(m_szFile, sizeof(m_szFile), pszFileName);
      }
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // ReceiveBinary - Creates a local file in the files-directory
  //                 from the inbound attachment
  //////////////////////////////////////////////////////////////////////////////
  HRESULT CGetFileAsyncCallback::ReceiveBinary(
      IWSDAttachment* pAttachment, 
      LPCWSTR pszLocalFileName)
  {
      HRESULT hr = S_OK;
      IWSDInboundAttachment* pStream = NULL;
      BYTE buffer[8192];
      ULONG cbBytesRead = 0;
      ULONG cbBytesLeft = 0;
      DWORD cbBytesWritten = 0;
      ULONG cbBytesTotal = 0;
      HANDLE hFile = NULL;
      DWORD dwErr = 0;
  
      // Validate parameters
      if( NULL == pAttachment )
      {
          return E_INVALIDARG;
      }
  
      if( NULL == pszLocalFileName )
      {
          return E_INVALIDARG;
      }
  
      if( 0 == wcscmp( pszLocalFileName, L"" ) )
      {
          return E_INVALIDARG;
      }
  
      // Make sure this attachment is an inbound attachment, and get that
      // interface
      hr = pAttachment->QueryInterface(
          __uuidof(IWSDInboundAttachment), (void**)&pStream);
  
      // Create a file to write attachment data into
      if( S_OK == hr )
      {
          _cwprintf(L"    Creating local file %s... ", pszLocalFileName);
          hFile = ::CreateFileW( pszLocalFileName, FILE_WRITE_DATA, 0, NULL,
              CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
  
          if ( INVALID_HANDLE_VALUE == hFile )
          {
              hFile = NULL;
  
              dwErr = ::GetLastError();
              hr = HRESULT_FROM_WIN32( dwErr );
          }
  
          print_result( hr );
      }
  
      // Read from the inbound attachment and write to file until finished
      while( S_OK == hr )
      {
          _cwprintf(L"    Reading attachment data... ");
          hr = pStream->Read( buffer, sizeof(buffer), &cbBytesRead );
  
          cbBytesLeft = cbBytesRead;
  
          // pStream->Read will return S_FALSE on the last read if the buffer
          // was not completely filled.  Hang onto the S_FALSE until the
          // end of this loop
          while( (S_OK == hr || S_FALSE == hr) && 0 < cbBytesLeft )
          {
              if( 0 == WriteFile( hFile,
                          buffer + (cbBytesRead - cbBytesLeft),
                          cbBytesLeft,
                          &cbBytesWritten, NULL ) )
              {
                  dwErr = ::GetLastError();
                  hr = HRESULT_FROM_WIN32( dwErr );
              }
  
              if( S_OK == hr || S_FALSE == hr )
              {
                  cbBytesLeft -= cbBytesWritten;
              }
          }
          print_result( hr );
  
          cbBytesTotal += (cbBytesRead - cbBytesLeft);
  
          // If that was the last read, reset hr and bail out
          if( S_FALSE == hr )
          {
              hr = S_OK;
              break;
          }
      }
  
      // Print success message if we got the file without any problems
      if( S_OK == hr )
      {
          _cwprintf( L"%s recieved (%d bytes).\r\n", pszLocalFileName,
                  cbBytesTotal );
      }
      else
      {
          _cwprintf( L"Error receiving %s.  Only recieved (%d bytes).\r\n",
                  pszLocalFileName, cbBytesTotal );
      }
  
      // cleanup
      if( NULL != hFile )
      {
          ::CloseHandle( hFile );
          hFile = NULL;
      }
  
      if( NULL != pStream )
      {
          pStream->Release();
          pStream = NULL;
      }
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // AsyncOperationComplete - Called when the GetFile operation is complete
  //////////////////////////////////////////////////////////////////////////////
  HRESULT STDMETHODCALLTYPE CGetFileAsyncCallback::AsyncOperationComplete(
      IWSDAsyncResult* pAsyncResult,
      IUnknown* pAsyncState)
  {
      UNREFERENCED_PARAMETER(pAsyncState);
  
      GET_FILE_RESPONSE* pResponse = NULL;
      HRESULT hr = S_OK;
  
      //
      // When the GetFile operation completes, we enter this callback.  It's
      // then our responsibility to call EndGetFile to actually retrieve
      // the results.
      //
      _cwprintf(L"Asynchronous GetFile operation completed.\r\n");
      hr = m_pFileServiceProxy->EndGetFile( pAsyncResult, &pResponse );
  
      // Call into our helper method to save the attachment to disk
      if( S_OK == hr && NULL != pResponse )
      {
          hr = ReceiveBinary( pResponse->Attachment, m_szFile );
      }
      else
      {
          _cwprintf(L"    GetFile operation failed or returned NULL response: ");
          print_result( hr );
      }
  
      // cleanup
      if( NULL != pResponse )
      {
          WSDFreeLinkedMemory( pResponse );
          pResponse = NULL;
      }
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // GetFileList - Invoke the GetFileList (i.e., "dir") service method
  //////////////////////////////////////////////////////////////////////////////
  HRESULT GetFileList(
      IFileServiceProxy* pFileServiceProxy)
  {
      GET_FILE_LIST_RESPONSE* pResponse = NULL;
      HRESULT hr = S_OK;
  
      if( NULL == pFileServiceProxy )
      {
          return E_INVALIDARG;
      }
  
      // Invoke GetFileList method on service
      _cwprintf(L"Invoking GetFileList method on service... ");
      hr = pFileServiceProxy->GetFileList( &pResponse );
      print_result( hr );
  
      // Print results
      if( S_OK == hr && NULL != pResponse )
      {
          PWCHAR_LIST *pList = pResponse->FileList;
  
          while( pList )
          {
              _cwprintf(L"%s\r\n", (NULL == pList->Element ? L"(null)" : pList->Element));
              pList = pList->Next;
          }
      }
  
      // cleanup
      if( NULL != pResponse )
      {
          WSDFreeLinkedMemory( pResponse );
          pResponse = NULL;
      }
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // GetFile - Invoke the GetFile service method
  //////////////////////////////////////////////////////////////////////////////
  HRESULT GetFile(
      IFileServiceProxy* pFileServiceProxy, 
      LPCWSTR pszFileName, 
      LPCWSTR pszReceiveDirectory)
  {
      GET_FILE_REQUEST params;
      HRESULT hr = S_OK;
      CGetFileAsyncCallback* pGetFileCallback = NULL;
      IWSDAsyncResult* pAsyncResult = NULL;
  
      if( NULL == pszFileName )
      {
          return E_INVALIDARG;
      }
  
      _cwprintf(L"Invoking GetFile method on service (file=%s)...\r\n",
              pszFileName);
  
      // Prepare parameters for service method
      params.filePath = pszFileName;
          
      //
      // Set up the async callback function.
      // pGetFileCallback->AsyncOperationComplete will get called when the stream
      // has finished downloading, and will save the results into a local file.
      //
      pGetFileCallback = new CGetFileAsyncCallback();
  
      if( NULL == pGetFileCallback )
      {
          hr = E_OUTOFMEMORY;
      }
  
      if( S_OK == hr )
      {
          _cwprintf(L"    Initializing callback structure... ");
          hr = pGetFileCallback->Init( pFileServiceProxy,
                  pszFileName, pszReceiveDirectory );
          print_result( hr );
      }
  
      // Invoke GetFile method on service
      if( S_OK == hr )
      {
          _cwprintf(L"    Starting GetFile operation... ");
          hr = pFileServiceProxy->BeginGetFile( &params, NULL,
                  pGetFileCallback, &pAsyncResult );
          print_result( hr );
      }
  
      // cleanup
      if( NULL != pGetFileCallback )
      {
          // Release pGetFileCallback now--if it was successful,
          // BeginGetFile has added its own reference
          pGetFileCallback->Release();
          pGetFileCallback = NULL;
      }
  
      if( NULL != pAsyncResult )
      {
          // pGetFileCallback will hold a reference to our async result
          // object, so we can release our reference now.
          pAsyncResult->Release();
          pAsyncResult = NULL;
      }
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // Help - Displays list of commands
  //////////////////////////////////////////////////////////////////////////////
  void Help()
  {
      _cwprintf(L"COMMANDS:\r\n");
      _cwprintf(L"   dir     Get the list of files on server\r\n");
      _cwprintf(L"   get     Retrieve a file from the server\r\n");
      _cwprintf(L"   quit    Exits program\r\n");
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // Usage - Displays command-line options
  //////////////////////////////////////////////////////////////////////////////
  void Usage(LPCWSTR pszAdditionalInformation)
  {
      _cwprintf(L"FileServiceClient.exe <files-directory> [<device-address>]\r\n");
      _cwprintf(L"Ex: FileServiceClient.exe c:\\temp urn:uuid:2BA7FF9A-CC7D-4781-B744-9347C5AC5A28\r\n");
  
      if( NULL != pszAdditionalInformation )
      {
          _cwprintf( L"%s\r\n", pszAdditionalInformation );
      }
  }
  
  //////////////////////////////////////////////////////////////////////////////
  // Main Entry Point
  //      argv[0] = executable name
  //      argv[1] = files-directory - the local directory to copy files to
  //      argv[2] = device address
  //////////////////////////////////////////////////////////////////////////////
  int _cdecl wmain(
      int argc, 
      __in_ecount(argc) LPWSTR* argv)
  {
      HRESULT hr = S_OK;
      LPCWSTR pszAdditionalInformation = NULL;
      WCHAR szReceiveDirectory[MAX_PATH];
      // szLocalAddress 46 bytes to hold UUID plus "urn:uuid:" and trailing 0
      WCHAR szLocalAddress[46];
      LPCWSTR pszDeviceAddress = NULL;
      size_t cchFileDirectoryLength = 0;            
      HANDLE hDir = NULL;
      UUID uuid = { 0 };
      CFileServiceProxy* pFileServiceProxy = NULL;
      CFileServiceEventNotify* pFileServiceEventNotify = NULL;
      bool bSubscribedToEvents = false;
      DWORD dwErr = 0;
  
      //////////////////////////////////////////////////////////////////////////
      // Validate command-line parameters
      //////////////////////////////////////////////////////////////////////////
      if( argc <= 2 )
      {
          hr = E_INVALIDARG;
      }
  
      // copy the files-directory from first argument (argv[1])
      if( S_OK == hr )
      {
          hr = ::StringCbCopyW( szReceiveDirectory, sizeof(szReceiveDirectory),
                  argv[1] );
      }
  
      // add a backslash to files-directory if it doesn't have one
      if( S_OK == hr )
      {
          hr = ::StringCchLengthW( szReceiveDirectory, sizeof(szReceiveDirectory),
                  &cchFileDirectoryLength );
      }
  
      if( S_OK == hr && cchFileDirectoryLength < 1 )
      {
          pszAdditionalInformation =
              L"<files-directory> must have non-zero length";
  
          hr = E_INVALIDARG;
      }
  
      if( S_OK == hr && szReceiveDirectory[cchFileDirectoryLength - 1] != L'\\')
      {
          hr = ::StringCbCatW( szReceiveDirectory, sizeof(szReceiveDirectory),
                  L"\\");
      }
  
      // Check if files-directory actually exists
      if( S_OK == hr )
      {
          hDir = ::CreateFileW( szReceiveDirectory, GENERIC_READ, 
              FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
              NULL );
  
          if( hDir == INVALID_HANDLE_VALUE )
          {
              hDir = NULL;
              pszAdditionalInformation = L"<files-directory> does not exist";
              hr = E_INVALIDARG;
          }
      }
  
      if( S_OK == hr )
      {
          // If the handle could be opened, the directory exists.  However,
          // we have no more use for the handle, so close it now.
          if( 0 == ::CloseHandle( hDir ))
          {
              dwErr = ::GetLastError();
              hr = HRESULT_FROM_WIN32( dwErr );
          }
  
          hDir = NULL;
      }
  
      // Get device address from second argument (argv[2])
      if( S_OK == hr )
      {
          pszDeviceAddress = argv[2];
      }
  
      //////////////////////////////////////////////////////////////////////////
      // Done with command-line validation--if we've hit an error, print
      // Usage now and return -1
      //////////////////////////////////////////////////////////////////////////
      if( S_OK != hr )
      {
          Usage( pszAdditionalInformation );
          return -1;
      }
  
      //////////////////////////////////////////////////////////////////////////
      // Build the proxy
      //////////////////////////////////////////////////////////////////////////
  
      // Generate local ID for our proxy
      if( S_OK == hr )
      {
          RPC_STATUS st = UuidCreate(&uuid);
  
          if( RPC_S_OK != st )
          {
              hr = E_FAIL;
          }
      }
  
      if( S_OK == hr )
      {
          hr = ::StringCbPrintfW(
              szLocalAddress, sizeof(szLocalAddress), 
              L"urn:uuid:%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
              uuid.Data1, uuid.Data2, uuid.Data3,
              uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3], 
              uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]); 
      }
  
      // Build the proxy
      if( S_OK == hr )
      {
          _cwprintf(L"Creating a proxy for device %s... ", pszDeviceAddress );
          hr = CreateCFileServiceProxy( pszDeviceAddress, szLocalAddress,
                  &pFileServiceProxy, NULL );
          print_result( hr );
      }
  
      // Build event sink for FileChange event
      if( S_OK == hr )
      {
          _cwprintf(L"Creating an event sink... ");
          pFileServiceEventNotify = new CFileServiceEventNotify();
  
          if( NULL == pFileServiceEventNotify )
          {
              hr = E_OUTOFMEMORY;
          }
          print_result( hr );
      }
     
      // Subscribe to FileChange event
      if( S_OK == hr )
      {
          _cwprintf(L"Subscribing to Events... ");
          hr = pFileServiceProxy->SubscribeToFileChangeEvent( pFileServiceEventNotify );
  
          if( S_OK == hr )
          {
              bSubscribedToEvents = true;
          }
          print_result( hr );
      }
  
      if( S_OK == hr )
      {
          _cwprintf(L"Proxy creation finished.\r\n");  
          Help();
      }
  
      //////////////////////////////////////////////////////////////////////////
      // Present interactive prompt
      //////////////////////////////////////////////////////////////////////////
      while( S_OK == hr )
      {
          WCHAR szCommand[MAX_PATH];
          size_t cbSizeRead = 0;
          LPWSTR pszArg = NULL;
  
          _cwprintf(L"\r\n>");       
  
          // Ignore result of _cgetws_s
          (void)_cgetws_s( szCommand,
                           sizeof(szCommand) / sizeof(WCHAR), &cbSizeRead );
  
          // Look for arguments in command
          pszArg = wcschr(szCommand, L' ');
          if( pszArg )
          {
              *pszArg = 0;
              pszArg++;
          }
  
          // Ignore result of towlower
          (void)towlower( *szCommand );
         
          // Match first character and perform appropriate operation
          if( 0 == wcscmp( szCommand, L"get" ) )
          {
              hr = GetFile( pFileServiceProxy, pszArg, szReceiveDirectory );
          }
          else if( 0 == wcscmp( szCommand, L"dir" ) )
          {
              hr = GetFileList( pFileServiceProxy );
          }
          else if( 0 == wcscmp( szCommand, L"quit" ) )
          {
              // Use S_FALSE to break out of the while loop and exit
              hr = S_FALSE;
          }
          else 
          {
              // Encountered an unknown command--print help
              Help();
          }
      }
  
      _cwprintf(L"Cleaning up resources... ");
  
      // cleanup
      if( NULL != pFileServiceEventNotify )
      {
          pFileServiceEventNotify->Release();
          pFileServiceEventNotify = NULL;
      }
  
      if( NULL != pFileServiceProxy )
      {
          if( bSubscribedToEvents )
          {
              pFileServiceProxy->UnsubscribeToFileChangeEvent();
          }
  
          pFileServiceProxy->Release();
          pFileServiceProxy = NULL;
      }
  
      _cwprintf(L"finished.\r\n");
  
      if( SUCCEEDED(hr) )
      {
          return 0;
      }
  
      return -1;
  }
  