README.TXT

The \Mssdk\Samples\SysMgmt directory contains samples for the following
Platform SDK components:

* Microsoft Management Console (MMC)
* Task Scheduler
* Windows 95 Migration DLLs for Windows NT 5 setup (beta)
* Microsoft System Management Server
* Setup and Microsoft Installer Service (beta)

The build environment (headers, libraries, and minimum build
tools) for the Platform SDK must be installed in order to build
any of the samples in the \Mssdk\Samples directory. The build
environment can be installed from the readme.htm or by running
setup.exe on the root of the Platform SDK compact disc. The most
recent version of the build environment is also available at
http://www.microsoft.com/msdn/sdk/platform.htm.

Some components may also require you to install runtimes, depending
on the platform on which you are trying to run the sample.
Redistributables are available in the \Mssdk\Redist subdirectory
on the Platform SDK compact disc.

Documentation for the components listed above can be installed
from the Platform SDK compact disc, or can be viewed online at
http://premium.microsoft.com/msdn/library/. This is a free service;
registration is necessary.

If you do not see any subdirectories in the \Mssdk\Samples\SysMgmt
node, you'll need to install the Setup and Systems Management components
from the Platform SDK compact disc or download and install them
from http://www.microsoft.com/msdn/sdk/platform.htm.


README.TXT

Licensing API

In order to use these samples, copy lsapi32.dll and mslsp32.dll from
mstools\bin to the %systemroot%\system32 directory on Windows NT or
to the %windir%\system directory on Windows 95.


MD4.H

#ifndef _MD4_H_
#define _MD4_H_

/* MD4.H - header file for MD4C.C
 */

/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
   rights reserved.

   License to copy and use this software is granted provided that it
   is identified as the "RSA Data Security, Inc. MD4 Message-Digest
   Algorithm" in all material mentioning or referencing this software
   or this function.

   License is also granted to make and use derivative works provided
   that such works are identified as "derived from the RSA Data
   Security, Inc. MD4 Message-Digest Algorithm" in all material
   mentioning or referencing the derived work.

   RSA Data Security, Inc. makes no representations concerning either
   the merchantability of this software or the suitability of this
   software for any particular purpose. It is provided "as is"
   without express or implied warranty of any kind.

   These notices must be retained in any copies of any part of this
   documentation and/or software.
 */

/* This code differs from the MD4 implementation contained in Internet
   RFC-1320 in the following respects:

   1. "global.h" is no longer needed.

   2. PROTO_LIST was removed from the function prototypes.

   3. Comments on the use of the main calls added to aid developers.
 */

/* ---------------------------------------------------------------------
 * The procedure for using the following function calls to compute a
 * digest is as follows:
 *
 *   MD4_CTX context;
 *      // create a storage context that persistes between calls.
 *
 *   MD4_Init (&context);
 *      // initialize context's initial digest and byte-count
 *
 *   MD4Update (&context, inputString, inputLength);
 *      // input first or only block of data to be digested.
 *
 *   MD4Update (&context, inputString, inputLength);
 *      // input subsequent blocks or last block of data to be digested.
 *
 *   MD4Final (digest, &context);
 *      // compute and return final 16-byte digest
 *
 * --------------------------------------------------------------------- */

typedef unsigned long UINT4;

/* POINTER defines a generic pointer type */
typedef unsigned char *POINTER;

/* MD4 context. */
typedef struct {
  UINT4 state[4];                                   /* state (ABCD) */
  UINT4 count[2];        /* number of bits, modulo 2^64 (lsb first) */
  unsigned char buffer[64];                         /* input buffer */
} MD4_CTX;

/* ---------------------------------------------------------------------
 * This function initializes the context for the message digest.
 * It must be called as the first step and before processing input data.
 * --------------------------------------------------------------------- */

void MD4Init ( MD4_CTX *context ) ;              /* context */

/* ---------------------------------------------------------------------
 * This function accepts input data of length "inputLen" and digests it.
 * All data to be digested is input via this function and no other.
 * The running byte count and any fragment of undigested data is stored
 * in the context for retention between calls.
 * --------------------------------------------------------------------- */

void MD4Update ( MD4_CTX *context,              /* context */
                 POINTER input,                 /* input block */
                 unsigned int inputLen ) ;      /* length of input block */

/* ---------------------------------------------------------------------
 * This function accepts not data, but finishes up the digest and
 * returns the 16 byte resulting message digest.  Finishing up includes
 * taking any undigested fragment stored in the context, padding the
 * message, appending the length and then digesting the resulting string.
 * --------------------------------------------------------------------- */

void MD4Final ( unsigned char *digest,          /* 16-byte message digest */
                MD4_CTX   *context ) ;          /* context */

/* ---------------------------------------------------------------------

   The MD4 test suite results, contained in appendix A.5 of RFC-1320,
   are as listed below.  They are printed by the "mddriver.c" test
   driver contained in appendix A.4 of RFC-1320.

   MD4 test suite:
   MD4 ("") = 31d6cfe0d16ae931b73c59d7e0c089c0
   MD4 ("a") = bde52cb31de33e46245e05fbdbd6fb24
   MD4 ("abc") = a448017aaf21d8525fc10ae87aa6729d
   MD4 ("message digest") = d9130a8164549fe818874806e1c7014b
   MD4 ("abcdefghijklmnopqrstuvwxyz") = d79e1c308aa5bbcdeea8ed63df412da9
   MD4 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
         = 043f8582f241db351ce627e153e7f0e4
   MD4 ("123456789012345678901234567890123456789012345678901234567890123
         45678901234567890") = e33b4ddc9c38f2199c3e7b164fcc0536

 * --------------------------------------------------------------------- */

#endif


MD4C.C

/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm
 */

/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved.

   License to copy and use this software is granted provided that it
   is identified as the "RSA Data Security, Inc. MD4 Message-Digest
   Algorithm" in all material mentioning or referencing this software
   or this function.

   License is also granted to make and use derivative works provided
   that such works are identified as "derived from the RSA Data
   Security, Inc. MD4 Message-Digest Algorithm" in all material
   mentioning or referencing the derived work.

   RSA Data Security, Inc. makes no representations concerning either
   the merchantability of this software or the suitability of this
   software for any particular purpose. It is provided "as is"
   without express or implied warranty of any kind.

   These notices must be retained in any copies of any part of this
   documentation and/or software.
 */

/* This code differs from the MD4 implementation contained in Internet
   RFC-1320 in the following respects:

   1. Faster boolean computations for the "F" and "G" functions, as
      suggested by Richard Schoeppel, rcs@cs.arizona.edu, have been included.
      The original code contained in the RFC is retained as comments.

   2. PROTO_LIST was removed from the function prototypes.
 */

#include "md4.h"

/* Constants for MD4Transform routine.
 */
#define S11 3
#define S12 7
#define S13 11
#define S14 19
#define S21 3
#define S22 5
#define S23 9
#define S24 13
#define S31 3
#define S32 9
#define S33 11
#define S34 15

static void MD4Transform ( UINT4 *state, unsigned char *block ) ;
static void Encode ( unsigned char *output,
                     UINT4 *input, unsigned int len ) ;
static void Decode ( UINT4 *output,
                     unsigned char *input, unsigned int len ) ;
static void MD4_memcpy ( POINTER output, POINTER input, unsigned int len ) ;
static void MD4_memset ( POINTER output, int value, unsigned int len ) ;

static unsigned char PADDING[64] = {
  0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

/* F, G and H are basic MD4 functions.
   Faster methods suggested by Richard Schoeppel, rcs@cs.arizona.edu
 */

/* #define F(x, y, z) (((x) & (y)) | ((~x) & (z)))              original code */
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))               /* faster method */
/* #define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) original code */
#define G(x, y, z) (((x) & (y)) | ((z) & ((x) | (y))))       /* faster method */
#define H(x, y, z) ((x) ^ (y) ^ (z))

/* ROTATE_LEFT rotates x left n bits.
 */
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))

/* FF, GG and HH are transformations for rounds 1, 2 and 3 */
/* Rotation is separate from addition to prevent recomputation */

#define FF(a, b, c, d, x, s) { \
    (a) += F ((b), (c), (d)) + (x); \
    (a) = ROTATE_LEFT ((a), (s)); \
  }
#define GG(a, b, c, d, x, s) { \
    (a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; \
    (a) = ROTATE_LEFT ((a), (s)); \
  }
#define HH(a, b, c, d, x, s) { \
    (a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; \
    (a) = ROTATE_LEFT ((a), (s)); \
  }

/* MD4 initialization. Begins an MD4 operation, writing a new context.
 */

void MD4Init ( MD4_CTX *context )               /* context */

{
  context->count[0] = context->count[1] = 0;

  /* Load magic initialization constants.
   */
  context->state[0] = 0x67452301;
  context->state[1] = 0xefcdab89;
  context->state[2] = 0x98badcfe;
  context->state[3] = 0x10325476;
}

/* MD4 block update operation. Continues an MD4 message-digest
     operation, processing another message block, and updating the
     context.
 */

void MD4Update ( MD4_CTX *context,              /* context */
                 POINTER input,                 /* input block */
                 unsigned int inputLen )        /* length of input block */

{
  unsigned int i, index, partLen;

  /* Compute number of bytes mod 64 */
  index = (unsigned int)((context->count[0] >> 3) & 0x3F);
  /* Update number of bits */
  if ((context->count[0] += ((UINT4)inputLen << 3))
      < ((UINT4)inputLen << 3))
    context->count[1]++;
  context->count[1] += ((UINT4)inputLen >> 29);

  partLen = 64 - index;

  /* Transform as many times as possible.
   */
  if (inputLen >= partLen) {
    MD4_memcpy
      ((POINTER)&context->buffer[index], (POINTER)input, partLen);
    MD4Transform (context->state, context->buffer);

    for (i = partLen; i + 63 < inputLen; i += 64)
      MD4Transform (context->state, &input[i]);

    index = 0;
  }
  else
    i = 0;

  /* Buffer remaining input */
  MD4_memcpy
    ((POINTER)&context->buffer[index], (POINTER)&input[i],
     inputLen-i);
}

/* MD4 finalization. Ends an MD4 message-digest operation, writing the
     the message digest and zeroizing the context.
 */

void MD4Final ( unsigned char *digest,          /* 16-byte message digest */
                MD4_CTX *context )              /* context */

{
  unsigned char bits[8];
  unsigned int index, padLen;

  /* Save number of bits */
  Encode (bits, context->count, 8);

  /* Pad out to 56 mod 64.
   */
  index = (unsigned int)((context->count[0] >> 3) & 0x3f);
  padLen = (index < 56) ? (56 - index) : (120 - index);
  MD4Update (context, PADDING, padLen);

  /* Append length (before padding) */
  MD4Update (context, bits, 8);
  /* Store state in digest */
  Encode (digest, context->state, 16);

  /* Zeroize sensitive information.
   */
  MD4_memset ((POINTER)context, 0, sizeof (*context));

}

/* MD4 basic transformation. Transforms state based on block.
 */

static void MD4Transform ( UINT4 *state,        /* 16-byte state */
                           unsigned char *block ) /* 64-byte block */

{
  UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];

  Decode (x, block, 64);

  /* Round 1 */
  FF (a, b, c, d, x[ 0], S11); /* 1 */
  FF (d, a, b, c, x[ 1], S12); /* 2 */
  FF (c, d, a, b, x[ 2], S13); /* 3 */
  FF (b, c, d, a, x[ 3], S14); /* 4 */
  FF (a, b, c, d, x[ 4], S11); /* 5 */
  FF (d, a, b, c, x[ 5], S12); /* 6 */
  FF (c, d, a, b, x[ 6], S13); /* 7 */
  FF (b, c, d, a, x[ 7], S14); /* 8 */
  FF (a, b, c, d, x[ 8], S11); /* 9 */
  FF (d, a, b, c, x[ 9], S12); /* 10 */
  FF (c, d, a, b, x[10], S13); /* 11 */
  FF (b, c, d, a, x[11], S14); /* 12 */
  FF (a, b, c, d, x[12], S11); /* 13 */
  FF (d, a, b, c, x[13], S12); /* 14 */
  FF (c, d, a, b, x[14], S13); /* 15 */
  FF (b, c, d, a, x[15], S14); /* 16 */

  /* Round 2 */
  GG (a, b, c, d, x[ 0], S21); /* 17 */
  GG (d, a, b, c, x[ 4], S22); /* 18 */
  GG (c, d, a, b, x[ 8], S23); /* 19 */
  GG (b, c, d, a, x[12], S24); /* 20 */
  GG (a, b, c, d, x[ 1], S21); /* 21 */
  GG (d, a, b, c, x[ 5], S22); /* 22 */
  GG (c, d, a, b, x[ 9], S23); /* 23 */
  GG (b, c, d, a, x[13], S24); /* 24 */
  GG (a, b, c, d, x[ 2], S21); /* 25 */
  GG (d, a, b, c, x[ 6], S22); /* 26 */
  GG (c, d, a, b, x[10], S23); /* 27 */
  GG (b, c, d, a, x[14], S24); /* 28 */
  GG (a, b, c, d, x[ 3], S21); /* 29 */
  GG (d, a, b, c, x[ 7], S22); /* 30 */
  GG (c, d, a, b, x[11], S23); /* 31 */
  GG (b, c, d, a, x[15], S24); /* 32 */

  /* Round 3 */
  HH (a, b, c, d, x[ 0], S31); /* 33 */
  HH (d, a, b, c, x[ 8], S32); /* 34 */
  HH (c, d, a, b, x[ 4], S33); /* 35 */
  HH (b, c, d, a, x[12], S34); /* 36 */
  HH (a, b, c, d, x[ 2], S31); /* 37 */
  HH (d, a, b, c, x[10], S32); /* 38 */
  HH (c, d, a, b, x[ 6], S33); /* 39 */
  HH (b, c, d, a, x[14], S34); /* 40 */
  HH (a, b, c, d, x[ 1], S31); /* 41 */
  HH (d, a, b, c, x[ 9], S32); /* 42 */
  HH (c, d, a, b, x[ 5], S33); /* 43 */
  HH (b, c, d, a, x[13], S34); /* 44 */
  HH (a, b, c, d, x[ 3], S31); /* 45 */
  HH (d, a, b, c, x[11], S32); /* 46 */
  HH (c, d, a, b, x[ 7], S33); /* 47 */
  HH (b, c, d, a, x[15], S34); /* 48 */

  state[0] += a;
  state[1] += b;
  state[2] += c;
  state[3] += d;

  /* Zeroize sensitive information.
   */
  MD4_memset ((POINTER)x, 0, sizeof (x));
}

/* Encodes input (UINT4) into output (unsigned char). Assumes len is
     a multiple of 4.
 */

static void Encode ( unsigned char *output,
                     UINT4 *input,
                     unsigned int len )

{
  unsigned int i, j;

  for (i = 0, j = 0; j < len; i++, j += 4) {
    output[j] = (unsigned char)(input[i] & 0xff);
    output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
    output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
    output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
  }
}

/* Decodes input (unsigned char) into output (UINT4). Assumes len is
     a multiple of 4.
 */

static void Decode ( UINT4 *output,
                     unsigned char *input,
                     unsigned int len )

{
  unsigned int i, j;

  for (i = 0, j = 0; j < len; i++, j += 4)
    output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
      (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
}

/* Note: Replace "for loop" with standard memcpy if possible.
 */

static void MD4_memcpy ( POINTER output,    /* target string to copy to */
                         POINTER input,     /* source string to copy from */
                         unsigned int len ) /* number of bytes to copy */

{
  unsigned int i;

  for (i = 0; i < len; i++)
    output[i] = input[i];
}

/* Note: Replace "for loop" with standard memset if possible.
 */

static void MD4_memset ( POINTER output,     /* first location to set */
                         int value,          /* value to set string to */
                         unsigned int len )  /* number of bytes to set */

{
  unsigned int i;

  for (i = 0; i < len; i++)
    ((char *)output)[i] = (char)value;
}


CHALLENG.C

/*
 *  This is a part of the Microsoft Source Code Samples.
 *  Copyright 1996 - 1998 Microsoft Corporation.
 *  All rights reserved.
 *
 *  The following example demonstrates how license updates are made and
 *  how the challenge mechanism is used at request and update times.  The
 *  application developer should analyze the application to determine the
 *  appropriate time to perform an update.  For example, this application
 *  prints a message 20 times and the appropriate time to do an update
 *  was decided to be done after every 5th message was printed.
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "windows.h"

/*
 * Include the MD4 header for computing the challenge.
 */
#include "md4.h"

/*
 * Include the LSAPI header file.
 */
#include "lsapi.h"

/*
 * Define the product name, product version, and publisher name for
 * use with the licensing calls.
 */
#define MYAPP_PRODUCT_NAME    "sample_product"
#define MYAPP_PRODUCT_VERSION "2.0"
#define MYAPP_PUBLISHER_NAME  "sample_publishers"

/*
 * Define the strings used to log a comment with the license system.
 */
#define MYAPP_REQUEST_LOG_COMMENT "Comment for the LSRequest call"
#define MYAPP_RELEASE_LOG_COMMENT "Comment for the LSRelease call"
#define MYAPP_UPDATE_LOG_COMMENT  "Comment for the LSUpdate call"

/*
 * The digest length is defined to be 16 according to the LSAPI spec.
 */
#define DIGESTLEN                16

/*
 * Define the number of secrets
 */
#define NUMBER_OF_SECRETS         4

/*
 * Define macros to indicate to the challenge routines whether we are
 * doing an LSRequest or an LSUpdate
 */
#define LS_REQUEST 0
#define LS_UPDATE  1

/*
 * Global variable used to determine if we are on a little endian machine
 * or not.
 */
int          littleEndian = 0;

/*
 * Forward declarations.
 */
void PrintErrors( LS_HANDLE handle, LS_STATUS_CODE errorCode );
LS_STATUS_CODE getChallenge(
      LS_CHALLENGE    FAR *challenge,
      LS_STR          FAR *theString,
      LS_ULONG        unitsConsumed,
      LS_ULONG        unitsReserved,
      LS_STR          FAR *logcomment,
      LS_ULONG        R,
      LS_ULONG        X,
      LS_ULONG        typeOfCall);
int checkChallenge(
      LS_CHALLENGE    FAR *challenge,
      LS_STR          FAR *theString,
      LS_ULONG        unitsConsumed ,
      LS_ULONG        unitsReserved ,
      LS_STR          FAR *logComment ,
      LS_ULONG        unitsGranted ,
      LS_STATUS_CODE  status ,
      LS_ULONG        R,
      LS_ULONG        X,
      LS_ULONG        typeOfCall);
void swap( LS_STR FAR *byte1, LS_STR FAR *byte2 );
int PlaceInt( char FAR *buffer, LS_ULONG value );

__cdecl main()
{
   LS_STATUS_CODE     status;
   unsigned long      unitsGranted = 0;
   LS_CHALLENGE       challenge;
   LS_HANDLE          licenseHandle = (LS_HANDLE) NULL;
   LS_ULONG           R;
   LS_ULONG           X;
   int                i;
   int                endianTester = 1 ;
   LS_STR             challengeString[100];
   char               FAR *endianChar = NULL;
   LS_ULONG           Secrets[4] = { 0x7778797A, 0x31323334, 1633837924, 926431536 };

   /************ the following code is for the LSAPI beta only! ************/
   char               szProviderPath[ MAX_PATH ];
   UINT               nChars;

   /* install if necessary */
   nChars = GetSystemDirectory( szProviderPath, sizeof( szProviderPath ) );
   if ( 0 == nChars )
   {
      printf( "Can't get system directory, error %d.\n", GetLastError() );
   }

   lstrcat( szProviderPath, "\\mslsp32.dll" );
   status = LSInstall( szProviderPath );
   if ( LS_SUCCESS != status )
   {
      printf( "Cannot install LSAPI, error 0x%08lx.\n", status );
   }

   /* add licenses for our product */
   status = LSLicenseUnitsSet( LS_ANY,
                               MYAPP_PUBLISHER_NAME,
                               MYAPP_PRODUCT_NAME,
                               MYAPP_PRODUCT_VERSION,
                               LS_LICENSE_TYPE_NODE,
                               LS_NULL,
                               1,
                               sizeof( Secrets ) / sizeof( *Secrets ),
                               Secrets );
   if ( LS_SUCCESS != status )
   {
      printf( "Cannot install licenses, error 0x%lx.\n", status );
   }
   /************ the above code is for the LSAPI beta only! ************/

   /*
    * test endianness and set global variable by casting an int
    * as bytes and look at the first byte
    */
   endianChar = (char FAR *) &endianTester ;
   if ( *endianChar )
        littleEndian = 1 ;

   /*
    * Calculate the string for the challenge on the LSRequest call.
    */
   sprintf( challengeString, "%s%s%s%s%s",
            "LSRequest",
            LS_ANY,
            MYAPP_PUBLISHER_NAME,
            MYAPP_PRODUCT_NAME,
            MYAPP_PRODUCT_VERSION );

   /*
    * Pick the random number R and randomly pick a number for the index X
    * between 1 and the number of secrets.
    */
   srand( (unsigned)time( NULL ) );
   R = rand();
   X = (int)(NUMBER_OF_SECRETS * ((float)rand()/(float)RAND_MAX)) + 1;

   /*
    * Calculate the message digest for the challenge argument.
    */
   status = getChallenge(
         &challenge,
         challengeString,
         0,                    /* Unused argument for LSRequest call */
         LS_DEFAULT_UNITS,
         MYAPP_REQUEST_LOG_COMMENT,
         R,
         X,
         LS_REQUEST );
   if ( LS_SUCCESS != status )
      {
      PrintErrors( licenseHandle, status );
      return(1);
      }

   /*
    * Make the grant request call
    */
   status = LSRequest(
         (LS_STR FAR *) LS_ANY,
         (LS_STR FAR *) MYAPP_PUBLISHER_NAME,
         (LS_STR FAR *) MYAPP_PRODUCT_NAME,
         (LS_STR FAR *) MYAPP_PRODUCT_VERSION,
         LS_DEFAULT_UNITS,
         (LS_STR FAR *) MYAPP_REQUEST_LOG_COMMENT,
         &challenge,
         &unitsGranted,
         &licenseHandle);
   if ( LS_SUCCESS != status )
      {
      PrintErrors( licenseHandle, status );
      LSFreeHandle( licenseHandle );
      return(1);
      }

   /*
    * Check whether the challenge succeeded.
    */
   if ( !checkChallenge(
         &challenge,
         challengeString,
         0,                    /* Unused argument for LSRequest call */
         LS_DEFAULT_UNITS,
         MYAPP_REQUEST_LOG_COMMENT,
         unitsGranted,
         status,
         R,
         X,
         LS_REQUEST ) )
      {
      printf("Challenge failed!!!\n");
      /*
       * Since we failed, we should still release the grant and free
       * the license handle.
       */
      status = LSRelease( licenseHandle,
                          LS_DEFAULT_UNITS,
                          (LS_STR FAR *) MYAPP_RELEASE_LOG_COMMENT);
      if ( LS_SUCCESS != status )
         PrintErrors( licenseHandle, status );

      LSFreeHandle( licenseHandle );
      return(1);
      }

   /*
    * Now we can start the application.  Print "Hello, World" 20 times
    * performing an update after every 5th time.
    */
   for ( i = 0; i<20; i++ )
      {
      printf("Hello, World.\n");

      if ( 0 == i%5 )
         {
         /*
          * Calculate the challenge string for the challenge on
          * the LSUpdate call.
          */
         sprintf( challengeString, "%s", "LSUpdate");

         /*
          * Pick the random number R using the process id for the seed
          * to the random number generator.  Also randomly pick a number
          * for the index X between 1 and NUMBER_OF_SECRETS.
          */
         srand( (unsigned)time( NULL ) );
         R = rand();
         X = (int)(NUMBER_OF_SECRETS * ((float)rand()/(float)RAND_MAX)) + 1;

         /*
          * Calculate the message digest for the challenge argument.
          */
         status = getChallenge(
               &challenge,
               challengeString,
               LS_DEFAULT_UNITS,
               LS_DEFAULT_UNITS,
               MYAPP_UPDATE_LOG_COMMENT,
               R,
               X,
               LS_UPDATE );
         if ( LS_SUCCESS != status )
            {
            PrintErrors( licenseHandle, status );
            status = LSRelease( licenseHandle,
                                LS_DEFAULT_UNITS,
                                (LS_STR FAR *) MYAPP_RELEASE_LOG_COMMENT);
            LSFreeHandle( licenseHandle );
            return(1);
            }

         /*
          * Make the update call.
          */
         status = LSUpdate( licenseHandle,
                            LS_DEFAULT_UNITS,
                            LS_DEFAULT_UNITS,
                            (LS_STR FAR *) MYAPP_UPDATE_LOG_COMMENT,
                            &challenge,
                            &unitsGranted);
         if ( LS_SUCCESS != status )
            {
            PrintErrors( licenseHandle, status );
            status = LSRelease( licenseHandle,
                                LS_DEFAULT_UNITS,
                                (LS_STR FAR *) MYAPP_RELEASE_LOG_COMMENT);
            LSFreeHandle( licenseHandle );
            return( 1 );
            }

         /*
          * Check whether the challenge succeeded.
          */
         if ( !checkChallenge(
               &challenge,
               challengeString,
               LS_DEFAULT_UNITS,
               LS_DEFAULT_UNITS,
               MYAPP_UPDATE_LOG_COMMENT,
               unitsGranted,
               status,
               R,
               X,
               LS_UPDATE ) )
            {
            printf("Challenge failed!!!\n");
            /*
             * Release the grant.
             */
            status = LSRelease( licenseHandle,
                                LS_DEFAULT_UNITS,
                                (LS_STR FAR *) MYAPP_RELEASE_LOG_COMMENT);
            if ( LS_SUCCESS != status )
               PrintErrors( licenseHandle, status );

            LSFreeHandle( licenseHandle );
            return(1);
            }
         }  /* End of if ( 0 == i%5 ) */

      }  /* End of for loop. */

   /*
    * Release the grant.
    */
   status = LSRelease( licenseHandle,
                       LS_DEFAULT_UNITS,
                       (LS_STR FAR *) MYAPP_RELEASE_LOG_COMMENT);
   if ( LS_SUCCESS != status )
      {
      PrintErrors( licenseHandle, status );
      LSFreeHandle( licenseHandle );
      return( 1 );
      }

   /*
    * Free the license handle.
    */
   LSFreeHandle( licenseHandle );
   return(0);
}

/*
 * Print the error message.
 */
void PrintErrors( LS_HANDLE handle, LS_STATUS_CODE errorCode )
{
   LS_STATUS_CODE   status;
   char             errorText[200];

   status = LSGetMessage( handle, errorCode, (LS_STR FAR *)errorText, 200);
   if ( LS_TEXT_UNAVAILABLE == status )
      printf("Error: No message catalog available.\n");
   else
      if ( LS_UNKNOWN_STATUS == status )
         printf("Error: Unknown error code was used.\n");
      else
         printf("Error: %s\n", errorText);
}

/*
 * Use the algorithmic approach to calculate the message digest to send
 * to the server.
 */
LS_STATUS_CODE getChallenge(
      LS_CHALLENGE    FAR *challenge,
      LS_STR          FAR *theString,
      LS_ULONG        unitsConsumed,
      LS_ULONG        unitsReserved,
      LS_STR          FAR *logComment,
      LS_ULONG        R,
      LS_ULONG        X,
      LS_ULONG        typeOfCall)
{
   MD4_CTX        MD;
   char           secret[4];
   LS_STR         hashString[200], FAR *hashStringPtr;
   int            bufferLen, bytes, i;

   /*
    * Set the protocol, index, random number, and size on the challenge
    * argument.
    */
   challenge->Protocol = LS_BASIC_PROTOCOL;
   challenge->ChallengeData.SecretIndex = X;
   challenge->ChallengeData.Random = R;

   /*
    * Get the secret.  NOTE: Greater steps should be taken to obsure the
    * secrets than is shown.  Also, more secrets should be used than 4.
    * Several methods are shown on how the secret can be specified.
    */
   switch ( X )
      {
      case 1:
         PlaceInt( secret, 0x7778797A );
         break;
      case 2:
         PlaceInt( secret, 0x31323334 );
         break;
      case 3:
         PlaceInt( secret, 1633837924 );
         break;
      case 4:
         PlaceInt( secret, 926431536 );
         break;
      default:
         printf("Index out of range\n");
         return( 0 );
         break;
      }

   /*
    * Compute the hash string to be used on input to the MD4 algorithm.
    */
   strcpy( hashString, theString );
   hashStringPtr = hashString + strlen( theString );
   bufferLen = strlen( theString );

   /*
    * Add units consumed only for LSUpdate call
    */
   if ( LS_UPDATE == typeOfCall )
      {
      bytes = PlaceInt( hashStringPtr, (LS_ULONG) unitsConsumed );
      hashStringPtr += bytes;
      bufferLen += bytes;
      }

   bytes = PlaceInt( hashStringPtr, (LS_ULONG) unitsReserved );
   hashStringPtr += bytes;
   bufferLen += bytes;

   strcpy( hashStringPtr, logComment );
   bufferLen += strlen( logComment );
   hashStringPtr += strlen( logComment );

   bytes = PlaceInt( hashStringPtr, (LS_ULONG) R );
   hashStringPtr += bytes;
   bufferLen += bytes;

   bytes = PlaceInt( hashStringPtr, (LS_ULONG) X );
   hashStringPtr += bytes;
   bufferLen += bytes;

   for ( i=0; i<4; i++ )
      {
      *hashStringPtr = secret[i];
      hashStringPtr++;
      bufferLen++;
      }

   /*
    * Make the MD4 calls.
    */
   MD4Init(&MD);
   MD4Update(
      (MD4_CTX *) &MD,
      (unsigned char *) hashString,
      (unsigned int) bufferLen );
   MD4Final(
      challenge->ChallengeData.MsgDigest.MessageDigest,
      (MD4_CTX *) &MD);

   challenge->Size = sizeof( challenge->ChallengeData );
   return( LS_SUCCESS );
}

/*
 * Check the return challenge argument.
 */
int checkChallenge(
      LS_CHALLENGE    FAR *challenge,
      LS_STR          FAR *theString,
      LS_ULONG        unitsConsumed ,
      LS_ULONG        unitsReserved ,
      LS_STR          FAR *logComment ,
      LS_ULONG        unitsGranted ,
      LS_STATUS_CODE  status ,
      LS_ULONG        R,
      LS_ULONG        X,
      LS_ULONG        typeOfCall)
{
   char           secret[4];
   MD4_CTX        MD;
   LS_STR         hashString[200];
   LS_STR         digest[DIGESTLEN], FAR *hashStringPtr;
   int            i, bufferLen, bytes, j;

   /*
    * Get the secret.  NOTE: Greater steps should be taken to obsure the
    * secrets than is shown.  Also, more secrets should be used than 4.
    * Several methods are shown on how the secret can be specified.
    */
   switch ( X )
      {
      case 1:
         PlaceInt( secret, 0x7778797A );
         break;
      case 2:
         PlaceInt( secret, 0x31323334 );
         break;
      case 3:
         PlaceInt( secret, 1633837924 );
         break;
      case 4:
         PlaceInt( secret, 926431536 );
         break;
      default:
         printf("Index out of range\n");
         return( 0 );
         break;
      }

   /*
    * Compute the hash string.
    */
   strcpy( hashString, theString );
   hashStringPtr = hashString + strlen( theString );
   bufferLen = strlen( theString );

   /*
    * Add units consumed only for LSUpdate call
    */
   if ( LS_UPDATE == typeOfCall )
      {
      bytes = PlaceInt( hashStringPtr, (LS_ULONG) unitsConsumed );
      hashStringPtr += bytes;
      bufferLen += bytes;
      }

   bytes = PlaceInt( hashStringPtr, (LS_ULONG) unitsReserved );
   hashStringPtr += bytes;
   bufferLen += bytes;

   strcpy( hashStringPtr, logComment );
   bufferLen += strlen( logComment );
   hashStringPtr += strlen( logComment );

   bytes = PlaceInt( hashStringPtr, (LS_ULONG) unitsGranted );
   hashStringPtr += bytes;
   bufferLen += bytes;

   bytes = PlaceInt( hashStringPtr, (LS_ULONG) status );
   hashStringPtr += bytes;
   bufferLen += bytes;

   bytes = PlaceInt( hashStringPtr, (LS_ULONG) R );
   hashStringPtr += bytes;
   bufferLen += bytes;

   bytes = PlaceInt( hashStringPtr, (LS_ULONG) X );
   hashStringPtr += bytes;
   bufferLen += bytes;

   for ( i=0; i<4; i++ )
      {
      *hashStringPtr = secret[i];
      hashStringPtr++;
      bufferLen++;
      }

   /*
    * Compute the message digest.
    */
   MD4Init(&MD);
   MD4Update(
      (MD4_CTX *) &MD,
      (unsigned char *) hashString,
      (unsigned int) bufferLen );
   MD4Final(
      digest,
      (MD4_CTX *) &MD);

   /*
    * Compare the digest with the one on the challenge
    */
   j = 0 ;
   for( i = 0; i < DIGESTLEN; i++ )
    {
      if( digest[i] != challenge->ChallengeData.MsgDigest.MessageDigest[i] )
         break ;
      j++ ;
    }

   if( j == DIGESTLEN )
      return( 1 );

   return( 0 );
}

/*
 * swap bytes
 */
void swap( LS_STR FAR *byte1, LS_STR FAR *byte2 )
{
   LS_STR           tmp;

   tmp = *byte2;
   *byte2 = *byte1;
   *byte1 = tmp;
}

/*
 * Places integer in byte buffer, swapping bytes if necessary
 */
int PlaceInt( char FAR *buffer, LS_ULONG value )
{
   if ( littleEndian )
      {
      swap(&(((char FAR *) &value)[0]), &(((char FAR *) &value)[3]));
      swap(&(((char FAR *) &value)[1]), &(((char FAR *) &value)[2]));
      }

   memcpy( buffer, &value, sizeof( LS_ULONG ) );

   return( (int) sizeof( LS_ULONG ) );
}


SIMPLE.C

/*
 *  This is a part of the Microsoft Source Code Samples.
 *  Copyright 1996 - 1998 Microsoft Corporation.
 *  All rights reserved.
 *
 *  The following example uses the minimum amount of code required to
 *  utilize any licensing system.  This example is provided on the SDK
 *  and can be used to familiarize oneself with a licensing system or
 *  as a template for an application.  Note that the code exits if a
 *  grant cannot be obtained.  Recall that this is strictly an
 *  application's decision and is not dictated by any licensing system
 *  which adheres to the LSAPI standard.
 */

#include <stdio.h>
#include <stdlib.h>
#include "windows.h"

/*
 * Include the LSAPI header file.
 */
#include "lsapi.h"

/*
 * Define the product name, product version, and publisher name for
 * use with the licensing calls.
 */
#define MYAPP_PRODUCT_NAME    "sample_product"
#define MYAPP_PRODUCT_VERSION "1.0"
#define MYAPP_PUBLISHER_NAME  "sample_publishers"

/*
 * Define the strings used to log a comment with the license system.
 */
#define MYAPP_REQUEST_LOG_COMMENT "Comment for the LSRequest call"
#define MYAPP_RELEASE_LOG_COMMENT "Comment for the LSRelease call"

/*
 * Forward declarations
 */
void PrintErrors( LS_HANDLE handle, LS_STATUS_CODE errorCode );

__cdecl main()
{
   /*
    * LSAPI Variables
    */
   LS_ULONG        unitsGranted;
   LS_HANDLE       licenseHandle;
   LS_STATUS_CODE  status;

   /************ the following code is for the LSAPI beta only! ************/
   char            szProviderPath[ MAX_PATH ];
   UINT            nChars;

   /* install if necessary */
   nChars = GetSystemDirectory( szProviderPath, sizeof( szProviderPath ) );
   if ( 0 == nChars )
   {
      printf( "Can't get system directory, error %d.\n", GetLastError() );
   }

   lstrcat( szProviderPath, "\\mslsp32.dll" );
   status = LSInstall( szProviderPath );
   if ( LS_SUCCESS != status )
   {
      printf( "Cannot install LSAPI, error 0x%08lx.\n", status );
   }

   /* add licenses for our product */
   status = LSLicenseUnitsSet( LS_ANY,
                               MYAPP_PUBLISHER_NAME,
                               MYAPP_PRODUCT_NAME,
                               MYAPP_PRODUCT_VERSION,
                               LS_LICENSE_TYPE_NODE,
                               LS_NULL,
                               1,
                               0,
                               NULL );
   if ( LS_SUCCESS != status )
   {
      printf( "Cannot install licenses, error 0x%lx.\n", status );
   }
   /************ the above code is for the LSAPI beta only! ************/

   /*
    * Make the call to request a grant
    */
   status = LSRequest(
         LS_ANY,                               /* Use any licensing system */
         (LS_STR FAR *)MYAPP_PUBLISHER_NAME,   /* Publisher name           */
         (LS_STR FAR *)MYAPP_PRODUCT_NAME,     /* Product name             */
         (LS_STR FAR *)MYAPP_PRODUCT_VERSION,  /* Version number           */
         LS_DEFAULT_UNITS,                     /* Let license figure units */
         (LS_STR FAR *)MYAPP_REQUEST_LOG_COMMENT, /* Log comment         */
         0,                                    /* No Challenge             */
         &unitsGranted,                        /* # units granted          */
         &licenseHandle );                     /* license context          */

   /*
    * Check whether we got a successful grant.  If not, then call a routine
    * which prints out the error message, free the license handle, and do
    * not continue running the application.
    */
   if ( LS_SUCCESS != status )
      {
      PrintErrors( licenseHandle, LS_USE_LAST );
      LSFreeHandle( licenseHandle );
      return(1);
      }

   /*
    * Continue with the application.
    */
   printf("Hello World\n");

   /*
    * We are now done with the application, so we must make a call
    * to release the grant.
    */
   status = LSRelease(
         licenseHandle,                        /* License context          */
         LS_DEFAULT_UNITS,                     /* Let license figure units */
         (LS_STR FAR *)MYAPP_RELEASE_LOG_COMMENT);/* Log comment         */
   if ( LS_SUCCESS != status )
      {
      PrintErrors( licenseHandle, status );
      LSFreeHandle( licenseHandle );
      return( 1 );
      }

   /*
    * Free the license handle.
    */
   LSFreeHandle( licenseHandle );

   exit( 0 );
}

void PrintErrors( LS_HANDLE handle, LS_STATUS_CODE errorCode )
{
   LS_STATUS_CODE   status;
   char             errorText[200];

   errorText[0] = 0;

   status = LSGetMessage( handle, errorCode, (LS_STR FAR *)errorText, 200);
   if ( LS_TEXT_UNAVAILABLE == status )
      printf("Error: No message catalog available.\n");
   else
      if ( LS_UNKNOWN_STATUS == status )
         printf("Error: Unknown error code was used.\n");
      else
         printf("Error: %s\n", errorText);
}


README.TXT

The files in this directory contain the ATL code generation wizard for MMC snapins. This is an unsupported preview release of a wizard that will be part of Visual Studio '98. To install into a VC5 development environment, copy the file snapwiz.dll into your <devstudio path>\sharedide\template directory and run regsvr32 on it. Copy the files in the template.atl directory to <devstudio path>\sharedide\template\atl. Finally, copy the files in the include directory to <devstudio path>\VC\atl\include.
After going through the wizard there are three things you must do to the generated code before it compiles correctly. They are:

1) in the project settings dialog, select the c++ settings tab and in the section for preprocessor define _UNICODE
2) create a toolbar resource as described in the compile error
3) expand the nodetype GUID as described in the compile error

Aside from those 3 steps the code generation wizard will provide you with a working snapin framework, although you will have to customize the resulting code before being able to interact with the snapin.


ATLSNAP.CPP

void __cdecl operator delete(void* p)
{
_free_dbg(p, _NORMAL_BLOCK);
}

void* __cdecl operator new(size_t nSize, LPCSTR lpszFileName, int nLine)
{
return _malloc_dbg(nSize, _NORMAL_BLOCK, lpszFileName, nLine);
}
#include <atlsnap.h>

const IID IID_ISnapInDataInterface = {0x1FABD781,0xECDA,0x11D0,{0xAA,0xCE,0x00,0xAA,0x00,0xC0,0x01,0x89}};

const UINT CSnapInBaseData::m_CCF_NODETYPE= RegisterClipboardFormat(CCF_NODETYPE);;
const UINT CSnapInBaseData::m_CCF_SZNODETYPE= RegisterClipboardFormat(CCF_SZNODETYPE);
const UINT CSnapInBaseData::m_CCF_DISPLAY_NAME= RegisterClipboardFormat(CCF_DISPLAY_NAME);
const UINT CSnapInBaseData::m_CCF_SNAPIN_CLASSID= RegisterClipboardFormat(CCF_SNAPIN_CLASSID);
const UINT CSnapInBaseData::m_CCF_SCOPEDATAITEM= RegisterClipboardFormat(_T("CCF_SCOPEDATAITEM"));
const UINT CSnapInBaseData::m_CCF_RESULTDATAITEM = RegisterClipboardFormat(_T("CCF_RESULTDATAITEM"));


ATLSNAP.H

#ifndef __ATL_SNAPIN_H__
#define __ATL_SNAPIN_H__

#include <mmc.h>
#include <commctrl.h>

#ifdef _DEBUG
void __cdecl operator delete(void* p);
void* __cdecl operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#undef new
#define new new(__FILE__, __LINE__)

#endif

#ifdef __cplusplus
extern "C"{
#endif

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __ISnapInDataInterface_INTERFACE_DEFINED__
#define __ISnapInDataInterface_INTERFACE_DEFINED__

EXTERN_C const IID IID_ISnapInDataInterface;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("1FABD781-ECDA-11D0-AACE-00AA00C00189")
    ISnapInDataInterface : public IUnknown
    {
    public:
        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Notify(
            MMC_NOTIFY_TYPE event,
            long arg,
            long param,
            BOOL bComponentData,
            IConsole __RPC_FAR *pConsole,
            IHeaderCtrl __RPC_FAR *pHeader,
            IToolbar __RPC_FAR *pToolbar) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetDispInfo(
            SCOPEDATAITEM __RPC_FAR *pScopeDataItem) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetResultViewType(
            LPOLESTR __RPC_FAR *ppVIewType,
            long __RPC_FAR *pViewOptions) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetDisplayInfo(
            RESULTDATAITEM __RPC_FAR *pResultDataItem) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE AddMenuItems(
            LPCONTEXTMENUCALLBACK piCallback,
            long __RPC_FAR *pInsertionAllowed) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Command(
            long lCommandID) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE CreatePropertyPages(
            LPPROPERTYSHEETCALLBACK lpProvider,
            long handle,
IUnknown* pUnk) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE QueryPagesFor( void) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SetControlbar(
            IControlbar __RPC_FAR *pControlbar,
            IExtendControlbar __RPC_FAR *pExtendControlbar) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ControlbarNotify(
            IControlbar __RPC_FAR *pControlbar,
            IExtendControlbar __RPC_FAR *pExtendControlbar,
            MMC_NOTIFY_TYPE event,
            long arg,
            long param) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetScopeData(
            SCOPEDATAITEM __RPC_FAR *__RPC_FAR *pScopeDataItem) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetResultData(
            RESULTDATAITEM __RPC_FAR *__RPC_FAR *pResultDataItem) = 0;

    };

#else /* C style interface */

    typedef struct ISnapInDataInterfaceVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            ISnapInDataInterface __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            ISnapInDataInterface __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            ISnapInDataInterface __RPC_FAR * This);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Notify )(
            ISnapInDataInterface __RPC_FAR * This,
            MMC_NOTIFY_TYPE event,
            long arg,
            long param,
            BOOL bComponentData,
            IConsole __RPC_FAR *pConsole,
            IHeaderCtrl __RPC_FAR *pHeader,
            IToolbar __RPC_FAR *pToolbar);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetDispInfo )(
            ISnapInDataInterface __RPC_FAR * This,
            SCOPEDATAITEM __RPC_FAR *pScopeDataItem);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetResultViewType )(
            ISnapInDataInterface __RPC_FAR * This,
            LPOLESTR __RPC_FAR *ppVIewType,
            long __RPC_FAR *pViewOptions);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetDisplayInfo )(
            ISnapInDataInterface __RPC_FAR * This,
            RESULTDATAITEM __RPC_FAR *pResultDataItem);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *AddMenuItems )(
            ISnapInDataInterface __RPC_FAR * This,
            LPCONTEXTMENUCALLBACK piCallback,
            long __RPC_FAR *pInsertionAllowed);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Command )(
            ISnapInDataInterface __RPC_FAR * This,
            long lCommandID);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *CreatePropertyPages )(
            ISnapInDataInterface __RPC_FAR * This,
            LPPROPERTYSHEETCALLBACK lpProvider,
            long handle,
IUnknown* pUnk);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryPagesFor )(
            ISnapInDataInterface __RPC_FAR * This);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *SetControlbar )(
            ISnapInDataInterface __RPC_FAR * This,
            IControlbar __RPC_FAR *pControlbar,
            IExtendControlbar __RPC_FAR *pExtendControlbar);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *ControlbarNotify )(
            ISnapInDataInterface __RPC_FAR * This,
            IControlbar __RPC_FAR *pControlbar,
            IExtendControlbar __RPC_FAR *pExtendControlbar,
            MMC_NOTIFY_TYPE event,
            long arg,
            long param);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetScopeData )(
            ISnapInDataInterface __RPC_FAR * This,
            SCOPEDATAITEM __RPC_FAR *__RPC_FAR *pScopeDataItem);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetResultData )(
            ISnapInDataInterface __RPC_FAR * This,
            RESULTDATAITEM __RPC_FAR *__RPC_FAR *pResultDataItem);

        END_INTERFACE
    } ISnapInDataInterfaceVtbl;

    interface ISnapInDataInterface
    {
        CONST_VTBL struct ISnapInDataInterfaceVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define ISnapInDataInterface_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define ISnapInDataInterface_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define ISnapInDataInterface_Release(This)\
    (This)->lpVtbl -> Release(This)

#define ISnapInDataInterface_Notify(This,event,arg,param,bComponentData,pConsole,pHeader,pToolbar)\
    (This)->lpVtbl -> Notify(This,event,arg,param,bComponentData,pConsole,pHeader,pToolbar)

#define ISnapInDataInterface_GetDispInfo(This,pScopeDataItem)\
    (This)->lpVtbl -> GetDispInfo(This,pScopeDataItem)

#define ISnapInDataInterface_GetResultViewType(This,ppVIewType,pViewOptions)\
    (This)->lpVtbl -> GetResultViewType(This,ppVIewType,pViewOptions)

#define ISnapInDataInterface_GetDisplayInfo(This,pResultDataItem)\
    (This)->lpVtbl -> GetDisplayInfo(This,pResultDataItem)

#define ISnapInDataInterface_AddMenuItems(This,piCallback,pInsertionAllowed)\
    (This)->lpVtbl -> AddMenuItems(This,piCallback,pInsertionAllowed)

#define ISnapInDataInterface_Command(This,lCommandID)\
    (This)->lpVtbl -> Command(This,lCommandID)

#define ISnapInDataInterface_CreatePropertyPages(This,lpProvider,handle, pUnk)\
    (This)->lpVtbl -> CreatePropertyPages(This,lpProvider,handle, pUnk)

#define ISnapInDataInterface_QueryPagesFor(This)\
    (This)->lpVtbl -> QueryPagesFor(This)

#define ISnapInDataInterface_SetControlbar(This,pControlbar,pExtendControlbar)\
    (This)->lpVtbl -> SetControlbar(This,pControlbar,pExtendControlbar)

#define ISnapInDataInterface_ControlbarNotify(This,pControlbar,pExtendControlbar,event,arg,param)\
    (This)->lpVtbl -> ControlbarNotify(This,pControlbar,pExtendControlbar,event,arg,param)

#define ISnapInDataInterface_GetScopeData(This,pScopeDataItem)\
    (This)->lpVtbl -> GetScopeData(This,pScopeDataItem)

#define ISnapInDataInterface_GetResultData(This,pResultDataItem)\
    (This)->lpVtbl -> GetResultData(This,pResultDataItem)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_Notify_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    MMC_NOTIFY_TYPE event,
    long arg,
    long param,
    BOOL bComponentData,
    IConsole __RPC_FAR *pConsole,
    IHeaderCtrl __RPC_FAR *pHeader,
    IToolbar __RPC_FAR *pToolbar);

void __RPC_STUB ISnapInDataInterface_Notify_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_GetDispInfo_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    SCOPEDATAITEM __RPC_FAR *pScopeDataItem);

void __RPC_STUB ISnapInDataInterface_GetDispInfo_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_GetResultViewType_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    LPOLESTR __RPC_FAR *ppVIewType,
    long __RPC_FAR *pViewOptions);

void __RPC_STUB ISnapInDataInterface_GetResultViewType_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_GetDisplayInfo_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    RESULTDATAITEM __RPC_FAR *pResultDataItem);

void __RPC_STUB ISnapInDataInterface_GetDisplayInfo_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_AddMenuItems_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    LPCONTEXTMENUCALLBACK piCallback,
    long __RPC_FAR *pInsertionAllowed);

void __RPC_STUB ISnapInDataInterface_AddMenuItems_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_Command_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    long lCommandID);

void __RPC_STUB ISnapInDataInterface_Command_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_CreatePropertyPages_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    LPPROPERTYSHEETCALLBACK lpProvider,
    long handle,
IUnknown* pUnk);

void __RPC_STUB ISnapInDataInterface_CreatePropertyPages_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_QueryPagesFor_Proxy(
    ISnapInDataInterface __RPC_FAR * This);

void __RPC_STUB ISnapInDataInterface_QueryPagesFor_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_SetControlbar_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    IControlbar __RPC_FAR *pControlbar,
    IExtendControlbar __RPC_FAR *pExtendControlbar);

void __RPC_STUB ISnapInDataInterface_SetControlbar_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_ControlbarNotify_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    IControlbar __RPC_FAR *pControlbar,
    IExtendControlbar __RPC_FAR *pExtendControlbar,
    MMC_NOTIFY_TYPE event,
    long arg,
    long param);

void __RPC_STUB ISnapInDataInterface_ControlbarNotify_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_GetScopeData_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    SCOPEDATAITEM __RPC_FAR *__RPC_FAR *pScopeDataItem);

void __RPC_STUB ISnapInDataInterface_GetScopeData_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ISnapInDataInterface_GetResultData_Proxy(
    ISnapInDataInterface __RPC_FAR * This,
    RESULTDATAITEM __RPC_FAR *__RPC_FAR *pResultDataItem);

void __RPC_STUB ISnapInDataInterface_GetResultData_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __ISnapInDataInterface_INTERFACE_DEFINED__ */

#ifdef __cplusplus
}
#endif

#pragma comment(lib, "mmc.lib")

#ifndef CPropertyPageImpl
#pragma comment(lib, "comctl32.lib")

template <class T>
class ATL_NO_VTABLE CPropertyPageImpl : public CDialogImplBase
{
public:
PROPSHEETPAGE m_psp;

operator PROPSHEETPAGE*() { return &m_psp; }

// Construction
CPropertyPageImpl(LPCTSTR lpszTitle = NULL)
{
// initialize PROPSHEETPAGE struct
memset(&m_psp, 0, sizeof(PROPSHEETPAGE));
m_psp.dwSize = sizeof(PROPSHEETPAGE);
m_psp.dwFlags = PSP_USECALLBACK;
m_psp.hInstance = _Module.GetResourceInstance();
m_psp.pszTemplate = MAKEINTRESOURCE(T::IDD);
m_psp.pfnDlgProc = (DLGPROC)T::StartDialogProc;
m_psp.pfnCallback = T::PropPageCallback;
m_psp.lParam = (LPARAM)this;

if(lpszTitle != NULL)
{
m_psp.pszTitle = lpszTitle;
m_psp.dwFlags |= PSP_USETITLE;
}
}

static UINT CALLBACK PropPageCallback(HWND hWnd, UINT uMsg, LPPROPSHEETPAGE ppsp)
{
if(uMsg == PSPCB_CREATE)
{
_ASSERTE(hWnd == NULL);
CDialogImplBase* pPage = (CDialogImplBase*)ppsp->lParam;
_Module.AddCreateWndData(&pPage->m_thunk.cd, pPage);
}

return 1;
}

HPROPSHEETPAGE Create()
{
return ::CreatePropertySheetPage(&m_psp);
}

BOOL EndDialog(int)
{
// do nothing here, calling ::EndDialog will close the whole sheet
_ASSERTE(FALSE);
return FALSE;
}

// Operations
void CancelToClose()
{
_ASSERTE(::IsWindow(m_hWnd));
_ASSERTE(GetParent() != NULL);

::SendMessage(GetParent(), PSM_CANCELTOCLOSE, 0, 0L);
}
void SetModified(BOOL bChanged = TRUE)
{
_ASSERTE(::IsWindow(m_hWnd));
_ASSERTE(GetParent() != NULL);

if(bChanged)
::SendMessage(GetParent(), PSM_CHANGED, (WPARAM)m_hWnd, 0L);
else
::SendMessage(GetParent(), PSM_UNCHANGED, (WPARAM)m_hWnd, 0L);
}
LRESULT QuerySiblings(WPARAM wParam, LPARAM lParam)
{
_ASSERTE(::IsWindow(m_hWnd));
_ASSERTE(GetParent() != NULL);

return ::SendMessage(GetParent(), PSM_QUERYSIBLINGS, wParam, lParam);
}

BEGIN_MSG_MAP(CPropertyPageImpl< T >)
MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
END_MSG_MAP()

// Message handler
LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
_ASSERTE(::IsWindow(m_hWnd));
NMHDR* pNMHDR = (NMHDR*)lParam;

// don't handle messages not from the page/sheet itself
if(pNMHDR->hwndFrom != m_hWnd && pNMHDR->hwndFrom != ::GetParent(m_hWnd))
{
bHandled = FALSE;
return 1;
}

T* pT = (T*)this;
LRESULT lResult = 0;
// handle default
switch(pNMHDR->code)
{
case PSN_SETACTIVE:
//? other values
lResult = pT->OnSetActive() ? 0 : -1;
break;
case PSN_KILLACTIVE:
lResult = !pT->OnKillActive();
break;
case PSN_APPLY:
lResult = pT->OnApply() ? PSNRET_NOERROR : PSNRET_INVALID_NOCHANGEPAGE;
break;
case PSN_RESET:
pT->OnReset();
break;
case PSN_QUERYCANCEL:
lResult = !pT->OnQueryCancel();
break;
case PSN_WIZNEXT:
//? other values
lResult = !pT->OnWizardNext();
break;
case PSN_WIZBACK:
lResult = !pT->OnWizardBack();
break;
case PSN_WIZFINISH:
lResult = !pT->OnWizardFinish();
break;
case PSN_HELP:
/**/lResult = pT->OnHelp();
break;
default:
bHandled = FALSE;// not handled
}

return lResult;
}

// Overridables
BOOL OnSetActive()
{
return TRUE;
}
BOOL OnKillActive()
{
return TRUE;
}
BOOL OnApply()
{
return TRUE;
}
void OnReset()
{
}
BOOL OnQueryCancel()
{
return TRUE;    // ok to cancel
}
BOOL OnWizardBack()
{
return TRUE;
}
BOOL OnWizardNext()
{
return TRUE;
}
BOOL OnWizardFinish()
{
return TRUE;
}
BOOL OnHelp()
{
return TRUE;
}
};
#endif

template <class T>
class ATL_NO_VTABLE CSnapInDataObjectImpl : public IDataObject
{
public:
STDMETHOD(GetData)(FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
{
ATLTRACENOTIMPL(_T("SnapInDataObjectImpl::GetData\n"));
}

STDMETHOD(GetDataHere)(FORMATETC* pformatetc, STGMEDIUM* pmedium)
{
ATLTRACE(_T("SnapInDataObjectImpl::GetDataHere\n"));
if (pmedium == NULL)
return E_POINTER;

HRESULT hr = DV_E_TYMED;
// Make sure the type medium is HGLOBAL
if (pmedium->tymed == TYMED_HGLOBAL)
{
// Create the stream on the hGlobal passed in
LPSTREAM pStream;
hr = CreateStreamOnHGlobal(pmedium->hGlobal, FALSE, &pStream);
if (SUCCEEDED(hr))
{
T* pT = static_cast<T*>(this);
hr = pT->FillData(pformatetc->cfFormat, pStream);
pStream->Release();
}
}

return hr;
}

STDMETHOD(QueryGetData)(FORMATETC* /* pformatetc */)
{
ATLTRACENOTIMPL(_T("SnapInDataObjectImpl::QueryGetData\n"));
}
STDMETHOD(GetCanonicalFormatEtc)(FORMATETC* /* pformatectIn */,FORMATETC* /* pformatetcOut */)
{
ATLTRACENOTIMPL(_T("SnapInDataObjectImpl::GetCanonicalFormatEtc\n"));
}
STDMETHOD(SetData)(FORMATETC* /* pformatetc */, STGMEDIUM* /* pmedium */, BOOL /* fRelease */)
{
ATLTRACENOTIMPL(_T("SnapInDataObjectImpl::SetData\n"));
}
STDMETHOD(EnumFormatEtc)(DWORD /* dwDirection */, IEnumFORMATETC** /* ppenumFormatEtc */)
{
ATLTRACENOTIMPL(_T("SnapInDataObjectImpl::EnumFormatEtc\n"));
}
STDMETHOD(DAdvise)(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink,
DWORD *pdwConnection)
{
ATLTRACENOTIMPL(_T("SnapInDataObjectImpl::SetData\n"));
}
STDMETHOD(DUnadvise)(DWORD dwConnection)
{
ATLTRACENOTIMPL(_T("SnapInDataObjectImpl::SetDatan\n"));
}
STDMETHOD(EnumDAdvise)(IEnumSTATDATA **ppenumAdvise)
{
ATLTRACENOTIMPL(_T("SnapInDataObjectImpl::SetData\n"));
}
};

template <class T, class C>
class ATL_NO_VTABLE IComponentDataImpl : public IComponentData
{
public :
CComQIPtr<IConsoleNameSpace, &IID_IConsoleNameSpace> m_spConsoleNameSpace;
CComQIPtr<IConsole, &IID_IConsole> m_spConsole;

    STDMETHOD(Initialize)(LPUNKNOWN pUnknown)
{
ATLTRACE(_T("IComponentDataImpl::Initialize\n"));

if (pUnknown == NULL)
{
ATLTRACE(_T("IComponentData::Initialize called with pUnknown == NULL\n"));
return E_UNEXPECTED;
}

m_spConsoleNameSpace = pUnknown;
if (m_spConsoleNameSpace == NULL)
{
ATLTRACE(_T("QI for IConsoleNameSpace failed\n"));
return E_UNEXPECTED;
}

m_spConsole = pUnknown;
if (m_spConsole == NULL)
{
ATLTRACE(_T("QI for IConsole failed\n"));
return E_UNEXPECTED;
}

return S_OK;
}

STDMETHOD(CreateComponent)(LPCOMPONENT *ppComponent)
{
ATLTRACE(_T("IComponentDataImpl::CreateComponent\n"));
if (ppComponent == NULL)
{
ATLTRACE(_T("IComponentData::CreateComponent called with ppComponent == NULL\n"));
return E_UNEXPECTED;
}

*ppComponent = NULL;

CComObject< C >* pComponent;
HRESULT hr = CComObject< C >::CreateInstance(&pComponent);
if (FAILED(hr))
{
ATLTRACE(_T("IComponentData::CreateComponent : Could not create IComponent object\n"));
return hr;
}

return pComponent->QueryInterface(IID_IComponent, (void**)ppComponent);
}

    STDMETHOD(Notify)(
        LPDATAOBJECT lpDataObject,
        MMC_NOTIFY_TYPE event,
        long arg,
        long param)
{
ATLTRACE(_T("IComponentDataImpl::Notify\n"));
if (lpDataObject == NULL)
{
ATLTRACE(_T("IComponentData::Notify called with lpDataObject == NULL\n"));
return E_UNEXPECTED;
}

CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData(lpDataObject);
if (spData == NULL)
{
ATLTRACE(_T("QI for ISnapInDataInterface failed\n"));
return E_UNEXPECTED;
}
return spData->Notify(event, arg, param, TRUE, m_spConsole, NULL, NULL);
}

    STDMETHOD(Destroy)(void)
{
ATLTRACE(_T("IComponentDataImpl::Destroy\n"));

m_spNode.Release();
m_spConsole.Release();
m_spConsoleNameSpace.Release();
return S_OK;
}

    STDMETHOD(QueryDataObject)(long cookie,
        DATA_OBJECT_TYPES type,
        LPDATAOBJECT  *ppDataObject)
{
ATLTRACE(_T("IComponentDataImpl::QueryDataObject\n"));
_ASSERTE(m_spNode != NULL);

if (ppDataObject == NULL)
{
ATLTRACE(_T("IComponentData::QueryDataObject called with ppDataObject == NULL\n"));
return E_UNEXPECTED;
}

*ppDataObject = NULL;

if (cookie == NULL)
return m_spNode->QueryInterface(IID_IDataObject, (void**)(ppDataObject));

IUnknown* pUnk = (IUnknown*) cookie;
return pUnk->QueryInterface(IID_IDataObject, (void**)(ppDataObject));
}

    STDMETHOD(GetDisplayInfo)(SCOPEDATAITEM *pScopeDataItem)
{
ATLTRACE(_T("IComponentDataImpl::GetDisplayInfo\n"));

if (pScopeDataItem == NULL)
{
ATLTRACE(_T("IComponentData::GetDisplayInfo called with pScopeDataItem == NULL\n"));
return E_UNEXPECTED;
}

IUnknown* pUnk = (IUnknown*) pScopeDataItem->lParam;
if (pUnk == NULL)
pUnk = m_spNode;

CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData(pUnk);
if (spData == NULL)
{
ATLTRACE(_T("QI for ISnapInDataInterface failed\n"));
return E_UNEXPECTED;
}
return spData->GetDispInfo(pScopeDataItem);
}

    STDMETHOD(CompareObjects)(LPDATAOBJECT lpDataObjectA,
        LPDATAOBJECT lpDataObjectB)
{
ATLTRACENOTIMPL(_T("IComponentDataImpl::CompareObjects\n"));
return S_FALSE;
    }

protected:
CComPtr<IUnknown> m_spNode;
};

template <class T>
class ATL_NO_VTABLE IComponentImpl : public IComponent
{
public:
//Review all of these may not be required
CComPtr<IConsole> m_spConsole;
CComQIPtr<IHeaderCtrl, &IID_IHeaderCtrl> m_spHeaderCtrl;
CComPtr<IImageList> m_spImageList;
CComPtr<IConsoleVerb> m_spConsoleVerb;

    STDMETHOD(Initialize)(LPCONSOLE lpConsole)
{
ATLTRACE(_T("IComponentImpl::Initialize\n"));

if (lpConsole == NULL)
{
ATLTRACE(_T("lpConsole is NULL\n"));
return E_UNEXPECTED;
}

m_spConsole = lpConsole;

m_spHeaderCtrl = lpConsole;
if (m_spHeaderCtrl == NULL)
{
ATLTRACE(_T("QI for IHeaderCtrl failed\n"));
return E_UNEXPECTED;
}

HRESULT hr = m_spConsole->SetHeader(m_spHeaderCtrl);
if (FAILED(hr))
{
ATLTRACE(_T("IConsole::SetHeader failed (HRESULT = %x)\n"), hr);
return hr;
}

hr = lpConsole->QueryResultImageList(&m_spImageList);
if (FAILED(hr))
{
ATLTRACE(_T("IConsole::QueryResultImageList failed (HRESULT = %x)\n"), hr);
return hr;
}

lpConsole->QueryConsoleVerb(&m_spConsoleVerb) ;
if (FAILED(hr))
{
ATLTRACE(_T("IConsole::QueryConsoleVerb failed (HRESULT = %x)\n"), hr);
return hr;
}

return S_OK;
}

STDMETHOD(Notify)(LPDATAOBJECT lpDataObject,
        MMC_NOTIFY_TYPE event,
        long arg,
        long param)
{
ATLTRACE(_T("IComponentImpl::Notify\n"));
if (lpDataObject == NULL)
{
ATLTRACE(_T("IComponent::Notify called with lpDataObject==NULL \n"));
return E_UNEXPECTED;
}

CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData(lpDataObject);
if (spData == NULL)
{
ATLTRACE(_T("QI for ISnapInDataInterface failed\n"));
return E_UNEXPECTED;
}
return spData->Notify(event, arg, param, FALSE, m_spConsole, m_spHeaderCtrl, NULL);
}

    STDMETHOD(Destroy)(long cookie)
{
ATLTRACE(_T("IComponentImpl::Destroy\n"));

m_spConsoleVerb = NULL;
m_spImageList = NULL;
m_spHeaderCtrl.Release();
m_spConsole.Release();

return S_OK;
}

    STDMETHOD(QueryDataObject)(long cookie,
        DATA_OBJECT_TYPES type,
        LPDATAOBJECT  *ppDataObject)
{
ATLTRACE(_T("IComponentImpl::QueryDataObject\n"));

if (ppDataObject == NULL)
{
ATLTRACE(_T("IComponent::QueryDataObject called with ppDataObject==NULL \n"));
return E_UNEXPECTED;
}

if (cookie == NULL)
{
ATLTRACE(_T("IComponent::QueryDataObject called with cookie==NULL \n"));
return E_UNEXPECTED;
}

*ppDataObject = NULL;

IUnknown* pUnk = (IUnknown *) cookie;
return pUnk->QueryInterface(IID_IDataObject, (void**)ppDataObject);
}

    STDMETHOD(GetResultViewType)(long cookie,
        LPOLESTR  *ppViewType,
        long  *pViewOptions)
{
ATLTRACE(_T("IComponentImpl::GetResultViewType\n"));

if (cookie == NULL)
{
*ppViewType = NULL;
*pViewOptions = MMC_VIEW_OPTIONS_NONE;
return S_OK;
}

CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData((IUnknown*)cookie);
return spData->GetResultViewType(ppViewType, pViewOptions);
}

    STDMETHOD(GetDisplayInfo)(RESULTDATAITEM *pResultDataItem)
{
ATLTRACE(_T("IComponentImpl::GetDisplayInfo\n"));
if (pResultDataItem == NULL)
{
ATLTRACE(_T("IComponent::GetDisplayInfo called with pResultDataItem==NULL\n"));
return E_UNEXPECTED;
}

IUnknown* pUnk = (IUnknown*) pResultDataItem->lParam;
if (pUnk == NULL)
return E_UNEXPECTED;

CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData(pUnk);
if (spData == NULL)
{
ATLTRACE(_T("QI for ISnapInDataInterface failed\n"));
return E_UNEXPECTED;
}
return spData->GetDisplayInfo(pResultDataItem);
}

    STDMETHOD(CompareObjects)( LPDATAOBJECT lpDataObjectA,
        LPDATAOBJECT lpDataObjectB)
{
ATLTRACENOTIMPL(_T("IComponentImpl::CompareObjects\n"));
}
};

template <class T, class D>
class ATL_NO_VTABLE IResultDataCompareImpl : public IResultDataCompare
{
public:
    STDMETHOD(Compare)(long lUserParam,
        long cookieA,
        long cookieB,
        int *pnResult)
{
ATLTRACENOTIMPL(_T("IResultDataCompareImpl::Compare"));
}
};

template <class T>
class ATL_NO_VTABLE IExtendContextMenuImpl : public IExtendContextMenu
{
public:
    STDMETHOD(AddMenuItems)(LPDATAOBJECT piDataObject,
        LPCONTEXTMENUCALLBACK piCallback,
        long *pInsertionAllowed)
{
ATLTRACE(_T("IExtendContextMenuImpl::AddMenuItems\n"));
if (piDataObject == NULL)
{
ATLTRACE(_T("IExtendContextMenu::AddMenuItems called with piDataObject==NULL\n"));
return E_UNEXPECTED;
}
CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData(piDataObject);
if (spData == NULL)
{
ATLTRACE(_T("QI for ISnapInDataInterface failed\n"));
return E_UNEXPECTED;
}
return spData->AddMenuItems(piCallback, pInsertionAllowed);
}

    STDMETHOD(Command)(long lCommandID,
        LPDATAOBJECT piDataObject)
{
ATLTRACE(_T("IExtendContextMenuImpl::Command\n"));
if (piDataObject == NULL)
{
ATLTRACE(_T("IExtendContextMenu::Command called with piDataObject==NULL\n"));
return E_UNEXPECTED;
}
CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData(piDataObject);
if (spData == NULL)
{
ATLTRACE(_T("QI for ISnapInDataInterface failed\n"));
return E_UNEXPECTED;
}
return spData->Command(lCommandID);
}
};

template<class T>
class ATL_NO_VTABLE IExtendPropertySheetImpl : public IExtendPropertySheet
{
public:
STDMETHOD(CreatePropertyPages)(LPPROPERTYSHEETCALLBACK lpProvider,

long handle,
        LPDATAOBJECT lpIDataObject)
{
ATLTRACE(_T("IExtendPropertySheetImpl::CreatePropertyPages\n"));
if (lpIDataObject == NULL)
{
ATLTRACE(_T("IExtendPropertySheetImpl::CreatePropertyPages called with lpIDataObject==NULL\n"));
return E_UNEXPECTED;
}
CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData(lpIDataObject);
if (spData == NULL)
{
ATLTRACE(_T("QI for ISnapInDataInterface failed\n"));
return E_UNEXPECTED;
}
return spData->CreatePropertyPages(lpProvider, handle, this);
}

    STDMETHOD(QueryPagesFor)(LPDATAOBJECT lpDataObject)
{
ATLTRACE(_T("IExtendPropertySheetImpl::QueryPagesFor\n"));
if (lpDataObject == NULL)
{
ATLTRACE(_T("IExtendPropertySheetImpl::QueryPagesFor called with lpDataObject==NULL\n"));
return E_UNEXPECTED;
}
CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData(lpDataObject);
if (spData == NULL)
{
ATLTRACE(_T("QI for ISnapInDataInterface failed\n"));
return E_UNEXPECTED;
}
return spData->QueryPagesFor();
}
};

template <class T>
class ATL_NO_VTABLE IExtendControlbarImpl : public IExtendControlbar
{
CComPtr <IControlbar> m_spControlbar;
public:
STDMETHOD(SetControlbar)(LPCONTROLBAR pControlbar)
{
ATLTRACE(_T("IExtendControlbarImpl::SetControlbar\n"));
if (pControlbar == NULL)
{
ATLTRACE(_T("IExtendControlbar::SetControlbar called with pControlbar==NULL\n"));
return E_UNEXPECTED;
}
m_spControlbar = pControlbar;
return S_OK;
}

    STDMETHOD(ControlbarNotify)(MMC_NOTIFY_TYPE event,
        long arg,
        long param)
{
ATLTRACE(_T("IExtendControlbarImpl::ControlbarNotify\n"));
CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> spData;
if (event == MMCN_BTN_CLICK)
{
spData = ((IDataObject*) arg);
}
else if (event == MMCN_SELECT)
{
BOOL bScope = (BOOL) LOWORD(arg);
if (bScope)
{
LPDATAOBJECT* ppDataobject = (LPDATAOBJECT*) param;
if (ppDataobject[0])
{
spData = ppDataobject[0];
if (spData != NULL)
spData->ControlbarNotify(m_spControlbar, this, event, arg, param);
}

if (ppDataobject[1])
{
spData = ppDataobject[1];
}
}
else
spData = (IDataObject*) param;
}
if (spData == NULL)
{
ATLTRACE(_T("QI for ISnapInDataInterface failed\n"));
return E_UNEXPECTED;
}
return spData->ControlbarNotify(m_spControlbar, this, event, arg, param);
}
};

template <class T>
class ATL_NO_VTABLE ISnapinAboutImpl : public ISnapinAbout
{
public:
STDMETHOD(GetSnapinDescription)(LPOLESTR *lpDescription)
{
ATLTRACENOTIMPL(_T("ISnapinAboutImpl::GetSnapinDescription\n"));
return S_OK;
}

    STDMETHOD(GetProvider)(LPOLESTR *lpName)
{
ATLTRACENOTIMPL(_T("ISnapinAboutImpl::GetProvider\n"));
return S_OK;
}

    STDMETHOD(GetSnapinVersion)(LPOLESTR *lpVersion)
{
ATLTRACENOTIMPL(_T("ISnapinAboutImpl::GetSnapinVersion\n"));
return S_OK;
}

    STDMETHOD(GetSnapinImage)(HICON *hAppIcon)
{
ATLTRACENOTIMPL(_T("ISnapinAboutImpl::GetSnapinImage\n"));
return S_OK;
}

    STDMETHOD(GetStaticFolderImage)(HBITMAP *hSmallImage,
        HBITMAP *hSmallImageOpen,
        HBITMAP *hLargeImage,
        COLORREF *cMask)
{
ATLTRACENOTIMPL(_T("ISnapinAboutImpl::GetStaticFolderImage\n"));
return S_OK;
}
};

class CSnapInBaseData
{
public:
static const UINT m_CCF_NODETYPE;
static const UINT m_CCF_SZNODETYPE;
static const UINT m_CCF_DISPLAY_NAME;
static const UINT m_CCF_SNAPIN_CLASSID;
static const UINT m_CCF_SCOPEDATAITEM;
static const UINT m_CCF_RESULTDATAITEM;
CComDynamicUnkArray m_scopeonlyitems;
CComDynamicUnkArray m_resultonlyitems;
CComDynamicUnkArray* m_pParentList;
OLECHAR* m_pszDisplayName;

SCOPEDATAITEM m_scopeDataItem;
RESULTDATAITEM m_resultDataItem;
CSnapInBaseData()
{
m_pszDisplayName = NULL;

memset(&m_scopeDataItem, 0, sizeof(SCOPEDATAITEM));

m_scopeDataItem.mask = SDI_STR | SDI_IMAGE | SDI_OPENIMAGE | SDI_PARAM | SDI_PARENT;
m_scopeDataItem.displayname = MMC_CALLBACK;
m_scopeDataItem.nImage = 0;
m_scopeDataItem.nOpenImage = 1;

memset(&m_resultDataItem, 0, sizeof(RESULTDATAITEM));

m_resultDataItem.mask = RDI_STR | RDI_IMAGE | RDI_PARAM;
m_resultDataItem.str = MMC_CALLBACK;
m_resultDataItem.nImage = 2;

m_pParentList = NULL;
}

~CSnapInBaseData()
{
delete m_pszDisplayName;
m_pszDisplayName = NULL;
for (IUnknown** pp = m_scopeonlyitems.begin(); pp < m_scopeonlyitems.end(); pp++)
{
if (*pp)
{
IUnknown* pUnk = *pp;
m_scopeonlyitems.Remove((DWORD)*pp);
pUnk->Release();
}
}
for (pp = m_resultonlyitems.begin(); pp < m_resultonlyitems.end(); pp++)
{

if (*pp)
{
IUnknown* pUnk = *pp;
m_resultonlyitems.Remove((DWORD)*pp);
pUnk->Release();
}
}
}

void InitParentList(CComDynamicUnkArray* pParent)
{
m_pParentList = pParent;
}

};

#define SNAPINMENUID(id) \
public: \
static const UINT GetMenuID() \
{ \
static const UINT IDMENU = id; \
return id; \
}

#define BEGIN_SNAPINCOMMAND_MAP(theClass) \
typedef theClass _theClass; \
HRESULT ProcessCommand(UINT nID, IDataObject* pDataObject) \
{

#define PRIMARY_SNAPINCOMMAND_ENTRY(id, func) \
if (id == nID) \
return func();

#define PRIMARY_SNAPINCOMMAND_RANGE_ENTRY(id1, id2, func) \
if (id1 >= nID && nID <= id2) \
return func(nID);

#define EXTENSION_SNAPINCOMMAND_ENTRY(id, func) \
if (id == nID) \
return func(pDataObject);

#define EXTENSION_SNAPINCOMMAND_RANGE_ENTRY(id1, id2, func) \
if (id1 >= nID && nID <= id2) \
return func(nID, pDataObject);

#define END_SNAPINCOMMAND_MAP() \
return ISnapInDataInterfaceImpl< _theClass >::ProcessCommand(nID, pDataObject); \
}

struct CSnapInToolBarData
{
WORD wVersion;
WORD wWidth;
WORD wHeight;
WORD wItemCount;
//WORD aItems[wItemCount]

WORD* items()
{ return (WORD*)(this+1); }
};

#define RT_TOOLBAR  MAKEINTRESOURCE(241)

struct CSnapInToolbarInfo
{
public:
TCHAR** m_pStrToolTip;
TCHAR** m_pStrButtonText;
UINT* m_pnButtonID;
UINT m_idToolbar;
UINT m_nButtonCount;
IToolbar* m_pToolbar;

~CSnapInToolbarInfo()
{
if (m_pStrToolTip)
{
for (UINT i = 0; i < m_nButtonCount; i++)
{
delete m_pStrToolTip[i];
m_pStrToolTip[i] = NULL;
}
delete [] m_pStrToolTip;
m_pStrToolTip = NULL;
}

if (m_pStrButtonText)
{
for (UINT i = 0; i < m_nButtonCount; i++)
{
delete m_pStrButtonText[i];
m_pStrButtonText[i] = NULL;
}

delete [] m_pStrButtonText;
m_pStrButtonText = NULL;
}
if (m_pnButtonID)
{
delete m_pnButtonID;
m_pnButtonID = NULL;
}

m_nButtonCount = 0;
if (m_pToolbar)
m_pToolbar->Release();
m_pToolbar = NULL;
}
};

#define BEGIN_SNAPINTOOLBARID_MAP(theClass) \
public: \
static CSnapInToolbarInfo* GetToolbarInfo() \
{ \
static CSnapInToolbarInfo m_toolbarInfo[] = \
{

#define SNAPINTOOLBARID_ENTRY(id) \
{ NULL, NULL, NULL, id, 0, NULL},

#define END_SNAPINTOOLBARID_MAP() \
{ NULL, NULL, NULL, 0, 0, NULL} \
}; \
return m_toolbarInfo; \
}

template <class T>
class ISnapInDataInterfaceImpl : public ISnapInDataInterface,
public CSnapInBaseData
{
public:
    STDMETHOD(Notify)( MMC_NOTIFY_TYPE event,
        long arg,
        long param,
BOOL bComponentData,
IConsole* pConsole,
IHeaderCtrl* pHeader,
IToolbar* pToolbar)
{
ATLTRACE(_T("ISnapInDataInterfaceImpl::Notify"));
if (event == MMCN_RENAME)
{
T* pT = static_cast<T*>(this);
OLECHAR* pTemp = new OLECHAR[lstrlen((OLECHAR*)param) + 1];
if (pTemp == NULL)
return S_FALSE;
lstrcpy(pTemp, (OLECHAR*)param);
if (!pT->Rename(pTemp))
{
delete []pTemp;
return S_FALSE;
}

delete []m_pszDisplayName;
m_pszDisplayName = pTemp;
return S_OK;
}

if (event == MMCN_ADD_IMAGES)
{
CComPtr<IImageList> spImageList;
spImageList = (IImageList*)arg;

return S_OK;
}

if (event == MMCN_SHOW)
{
int n = 0;
CComQIPtr<IResultData, &IID_IResultData> spResultData(pConsole);
pHeader->InsertColumn(1, _T("File"),  LVCFMT_LEFT, MMCLV_AUTO);
return EnumerateResultChildren(spResultData);
}

if (event == MMCN_EXPAND)
{
CComQIPtr<IConsoleNameSpace, &IID_IConsoleNameSpace> spConsoleNameSpace(pConsole);
return EnumerateScopeChildren(param, spConsoleNameSpace);
}

if (event == MMCN_SELECT)
{
CComPtr<IConsoleVerb> spVerb;
pConsole->QueryConsoleVerb(&spVerb) ;

spVerb->SetVerbState(MMC_VERB_PROPERTIES, ENABLED, TRUE);
return S_OK;
}

return E_UNEXPECTED;
}

    STDMETHOD(GetDispInfo)(SCOPEDATAITEM *pScopeDataItem)
{
ATLTRACE(_T("ISnapInDataInterfaceImpl::GetDispInfo"));
if (pScopeDataItem->mask & SDI_STR)
pScopeDataItem->displayname = m_pszDisplayName;
if (pScopeDataItem->mask & SDI_IMAGE)
pScopeDataItem->nImage = m_scopeDataItem.nImage;
if (pScopeDataItem->mask & SDI_OPENIMAGE)
pScopeDataItem->nOpenImage = m_scopeDataItem.nOpenImage;
if (pScopeDataItem->mask & SDI_CHILDREN)
pScopeDataItem->cChildren = m_scopeonlyitems.end() - m_scopeonlyitems.begin();
return S_OK;

}

    STDMETHOD(GetResultViewType)(LPOLESTR *ppVIewType,
        long *pViewOptions)
{
ATLTRACENOTIMPL(_T("ISnapInDataInterfaceImpl::GetResultViewType"));
}

    STDMETHOD(GetDisplayInfo)(RESULTDATAITEM *pResultDataItem)
{
ATLTRACE(_T("ISnapInDataInterfaceImpl::GetDisplayInfo"));
T* pT = static_cast<T*> (this);

if (pResultDataItem->bScopeItem)
{
if (pResultDataItem->mask & RDI_STR)
{
pResultDataItem->str = pT->GetResultPaneInfo(pResultDataItem->nCol);
}
if (pResultDataItem->mask & RDI_IMAGE)
{
pResultDataItem->nImage = m_scopeDataItem.nImage;
}

return S_OK;
}
if (pResultDataItem->mask & RDI_STR)
{
pResultDataItem->str = pT->GetResultPaneInfo(pResultDataItem->nCol);
}
if (pResultDataItem->mask & RDI_IMAGE)
{
pResultDataItem->nImage = m_resultDataItem.nImage;
}
return S_OK;
}

    STDMETHOD(AddMenuItems)(LPCONTEXTMENUCALLBACK piCallback,
        long *pInsertionAllowed)
{
ATLTRACE(_T("ISnapInDataInterfaceImpl::AddMenuItems"));
long insertionID;
T* pT = static_cast<T*> (this);
UINT menuID = pT->GetMenuID();
if (menuID == 0)
return S_OK;
HMENU hMenu = LoadMenu(_Module.GetResourceInstance(), MAKEINTRESOURCE(menuID));
if (hMenu)
{
for (int i = 0; 1; i++)
{
HMENU hSubMenu = GetSubMenu(hMenu, i);
if (hSubMenu == NULL)
break;

MENUITEMINFO menuItemInfo;
memset(&menuItemInfo, 0, sizeof(menuItemInfo));
menuItemInfo.cbSize = sizeof(menuItemInfo);

switch (i)
{
case 0:
if (! (*pInsertionAllowed & CCM_INSERTIONALLOWED_TOP) )
continue;
insertionID = CCM_INSERTIONPOINTID_PRIMARY_TOP;
break;

case 1:
if (! (*pInsertionAllowed & CCM_INSERTIONALLOWED_NEW) )
continue;
insertionID = CCM_INSERTIONPOINTID_PRIMARY_NEW;
break;

case 2:;
if (! (*pInsertionAllowed & CCM_INSERTIONALLOWED_TASK) )
continue;
insertionID = CCM_INSERTIONPOINTID_PRIMARY_TASK;
break;
case 3:;
if (! (*pInsertionAllowed & CCM_INSERTIONALLOWED_VIEW) )
continue;
insertionID = CCM_INSERTIONPOINTID_PRIMARY_VIEW;
break;
default:
{
insertionID = 0;
continue;
// Review
// Determine what to do here.
menuItemInfo.fMask = MIIM_TYPE ;
menuItemInfo.fType = MFT_STRING;
TCHAR buf[128];
menuItemInfo.dwTypeData = buf;
menuItemInfo.cch = 128;
if (!GetMenuItemInfo(hMenu, i, TRUE, &menuItemInfo))
continue;
//insertionID = _ttol(buf);
}
break;
}

menuItemInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
menuItemInfo.fType = MFT_STRING;
TCHAR buf[128];
menuItemInfo.dwTypeData = buf;

for (int j = 0; 1; j++)
{
menuItemInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
menuItemInfo.fType = MFT_STRING;
menuItemInfo.cch = 128;
TCHAR strStatusBar[257];

if (!GetMenuItemInfo(hSubMenu, j, TRUE, &menuItemInfo))
break;

pT->UpdateMenuState(menuItemInfo.wID, buf, &menuItemInfo.fState);
LoadString(_Module.GetResourceInstance(), menuItemInfo.wID, strStatusBar, 257);

CONTEXTMENUITEM contextMenuItem;
memset(&contextMenuItem, 0, sizeof(contextMenuItem));
contextMenuItem.strName = buf;
contextMenuItem.strStatusBarText = strStatusBar;
contextMenuItem.lCommandID = menuItemInfo.wID;
contextMenuItem.lInsertionPointID = insertionID;
contextMenuItem.fFlags = menuItemInfo.fState;

piCallback->AddItem(&contextMenuItem);
}
}
DestroyMenu(hMenu);
}

return S_OK;
}

    STDMETHOD(Command)(long lCommandID)
{
ATLTRACE(_T("ISnapInDataInterfaceImpl::Command\n"));
T* pT = static_cast<T*>(this);
return pT->ProcessCommand(lCommandID, NULL);
}

    STDMETHOD(CreatePropertyPages)(LPPROPERTYSHEETCALLBACK lpProvider,
        long handle, IUnknown* pUnk)
{
ATLTRACENOTIMPL(_T("ISnapInDataInterfaceImpl::CreatePropertyPages"));
}

    STDMETHOD(QueryPagesFor)(void)
{
ATLTRACENOTIMPL(_T("ISnapInDataInterfaceImpl::QueryPagesFor"));
}

    STDMETHOD(SetControlbar)(IControlbar *pControlbar, IExtendControlbar* pExtendControlBar)
{
ATLTRACE(_T("ISnapInDataInterfaceImpl::SetControlbar\n"));
T* pT = static_cast<T*>(this);

CSnapInToolbarInfo* pInfo = pT->GetToolbarInfo();
if (pInfo == NULL)
return S_OK;

for( ; pInfo->m_idToolbar; pInfo++)
{
if (pInfo->m_pToolbar)
continue;

HBITMAP hBitmap = LoadBitmap(_Module.GetResourceInstance(), MAKEINTRESOURCE(pInfo->m_idToolbar));
if (hBitmap == NULL)
return S_OK;

HRSRC hRsrc = ::FindResource(_Module.GetResourceInstance(), MAKEINTRESOURCE(pInfo->m_idToolbar), RT_TOOLBAR);
if (hRsrc == NULL)
return S_OK;

HGLOBAL hGlobal = LoadResource(_Module.GetResourceInstance(), hRsrc);
if (hGlobal == NULL)
return S_OK;

CSnapInToolBarData* pData = (CSnapInToolBarData*)LockResource(hGlobal);
if (pData == NULL)
return S_OK;
_ASSERTE(pData->wVersion == 1);

pInfo->m_nButtonCount = pData->wItemCount;
pInfo->m_pnButtonID = new UINT[pInfo->m_nButtonCount];
MMCBUTTON *pButtons = new MMCBUTTON[pData->wItemCount];

pInfo->m_pStrToolTip = new TCHAR* [pData->wItemCount];
if (pInfo->m_pStrToolTip == NULL)
continue;

for (int i = 0, j = 0; i < pData->wItemCount; i++)
{
pInfo->m_pStrToolTip[i] = NULL;
memset(&pButtons[i], 0, sizeof(MMCBUTTON));
pInfo->m_pnButtonID[i] = pButtons[i].idCommand = pData->items()[i];
if (pButtons[i].idCommand)
{
pButtons[i].nBitmap = j++;
// get the statusbar string and allow modification of the button state
TCHAR strStatusBar[512];
LoadString(_Module.GetResourceInstance(), pButtons[i].idCommand, strStatusBar, 512);

pInfo->m_pStrToolTip[i] = new TCHAR[lstrlen(strStatusBar) + 1];
if (pInfo->m_pStrToolTip[i] == NULL)
continue;
lstrcpy(pInfo->m_pStrToolTip[i], strStatusBar);
pButtons[i].lpTooltipText = pInfo->m_pStrToolTip[i];
pButtons[i].lpButtonText = _T("");
pT->SetToolbarButtonInfo(pButtons[i].idCommand, &pButtons[i].fsState, &pButtons[i].fsType);
}
else
{
pButtons[i].lpTooltipText = _T("");
pButtons[i].lpButtonText = _T("");
pButtons[i].fsType = TBSTYLE_SEP;
}
}

        HRESULT hr = pControlbar->Create(TOOLBAR, pExtendControlBar, reinterpret_cast<LPUNKNOWN*>(&pInfo->m_pToolbar));
if (FAILED(hr))
continue;

hr = pInfo->m_pToolbar->AddBitmap(pData->wItemCount, hBitmap, pData->wWidth, pData->wHeight, RGB(192, 192, 192));
if (FAILED(hr))
{
pInfo->m_pToolbar->Release();
pInfo->m_pToolbar = NULL;
continue;
}

hr = pInfo->m_pToolbar->AddButtons(pData->wItemCount, pButtons);
if (FAILED(hr))
{
pInfo->m_pToolbar->Release();
pInfo->m_pToolbar = NULL;
}

delete [] pButtons;
}
return S_OK;
}

    STDMETHOD(ControlbarNotify)(IControlbar *pControlbar,
        IExtendControlbar *pExtendControlbar,
MMC_NOTIFY_TYPE event,
        long arg, long param)
{
ATLTRACE(_T("ISnapInDataInterfaceImpl::ControlbarNotify\n"));
T* pT = static_cast<T*>(this);
CComQIPtr<IDataObject, &IID_IDataObject> spData(this);

SetControlbar(pControlbar, pExtendControlbar);

if(event == MMCN_SELECT)
{
BOOL bScope = (BOOL) LOWORD(arg);
BOOL bSelect = (BOOL) HIWORD (arg);

if (!bScope)
{
CSnapInToolbarInfo* pInfo = pT->GetToolbarInfo();
if (pInfo == NULL)
return S_OK;

if (!bSelect)
return S_OK;

for(; pInfo->m_idToolbar; pInfo++)
{
for (UINT i = 0; i < pInfo->m_nButtonCount; i++)
{
if (pInfo->m_pnButtonID[i])
{
for (int j = ENABLED; j <= BUTTONPRESSED; j++)
{
pInfo->m_pToolbar->SetButtonState(pInfo->m_pnButtonID[i],
(MMC_BUTTON_STATE)j,
pT->UpdateToolbarButton(pInfo->m_pnButtonID[i], (MMC_BUTTON_STATE)j));
}
}
}
}
}
else
{
LPDATAOBJECT* pData = (LPDATAOBJECT*) param;
if (pData[0] == pData[1])
return S_OK;
if (spData == pData[0])
{
CSnapInToolbarInfo* pInfo = pT->GetToolbarInfo();
if (pInfo == NULL)
return S_OK;

for(; pInfo->m_idToolbar; pInfo++)
{
pControlbar->Detach(pInfo->m_pToolbar);
}
return S_OK;
}

CSnapInToolbarInfo* pInfo = pT->GetToolbarInfo();
if (pInfo == NULL)
return S_OK;

for(; pInfo->m_idToolbar; pInfo++)
{
pControlbar->Attach(TOOLBAR, pInfo->m_pToolbar);
for (UINT i = 0; i < pInfo->m_nButtonCount; i++)
{
if (pInfo->m_pnButtonID[i])
{
for (int j = ENABLED; j <= BUTTONPRESSED; j++)
{
pInfo->m_pToolbar->SetButtonState(pInfo->m_pnButtonID[i],
(MMC_BUTTON_STATE)j,
pT->UpdateToolbarButton(pInfo->m_pnButtonID[i], (MMC_BUTTON_STATE)j));
}
}
}
}
return S_OK;
}
}
return pT->ProcessCommand((UINT) param, NULL);
}

STDMETHOD(GetScopeData)(SCOPEDATAITEM **pScopeDataItem)
{
if (pScopeDataItem == NULL)
return E_FAIL;

*pScopeDataItem = &m_scopeDataItem;
return S_OK;
}

    STDMETHOD(GetResultData)(RESULTDATAITEM **pResultDataItem)
{
if (pResultDataItem == NULL)
return E_FAIL;

*pResultDataItem = &m_resultDataItem;
return S_OK;
}

void UpdateMenuState(UINT id, LPTSTR pBuf, UINT *flags)
{
return;
}

void SetToolbarButtonInfo(UINT id, BYTE *fsState, BYTE *fsType)
{
*fsState = TBSTATE_ENABLED;
*fsType = TBSTYLE_BUTTON;
}

BOOL UpdateToolbarButton(UINT id, BYTE fsState)
{
if (fsState == ENABLED)
return TRUE;
return FALSE;
}

HRESULT ProcessCommand(UINT nID, IDataObject* pDataObject)
{
ATLTRACE(_T("No handler for item with ID %d\n"), nID);
return S_OK;
}

HRESULT FillData(CLIPFORMAT cf, LPSTREAM pStream)
{
HRESULT hr = DV_E_CLIPFORMAT;
ULONG uWritten;

T* pT = static_cast<T*> (this);

if (cf == m_CCF_NODETYPE)
{
hr = pStream->Write(pT->GetNodeType(), sizeof(GUID), &uWritten);
return hr;
}

if (cf == m_CCF_SZNODETYPE)
{
hr = pStream->Write(pT->GetSZNodeType(), (lstrlen((LPCTSTR)pT->GetSZNodeType()) + 1 )* sizeof(TCHAR), &uWritten);
return hr;
}

if (cf == m_CCF_DISPLAY_NAME)
{
hr = pStream->Write(pT->GetDisplayName(), (lstrlen((LPCTSTR)pT->GetDisplayName()) + 1) * sizeof(TCHAR), &uWritten);
return hr;
}

if (cf == m_CCF_SNAPIN_CLASSID)
{
hr = pStream->Write(pT->GetSnapInCLSID(), sizeof(CLSID), &uWritten);
return hr;
}

if (cf == m_CCF_SCOPEDATAITEM)
{
hr = pStream->Write(&m_scopeDataItem, sizeof(SCOPEDATAITEM), &uWritten);
return hr;
}

if (cf == m_CCF_RESULTDATAITEM)
{
hr = pStream->Write(&m_resultDataItem, sizeof(RESULTDATAITEM), &uWritten);
return hr;
}

return hr;
}
OLECHAR* GetResultPaneInfo(int nCol)
{
if (nCol == 0 && m_pszDisplayName)
return m_pszDisplayName;

return L"Override GetResultPaneInfo in your derived class";
}

void PopulateChildren()
{
return;
}

void AddScopeItem(IUnknown* p)
{
m_scopeonlyitems.Add(p);
}

void AddResultItem(IUnknown* p)
{
m_resultonlyitems.Add(p);
}

void RemoveScopeItem(IUnknown* p)
{
m_scopeonlyitems.Remove((DWORD)p);
}

void RemoveResultItem(IUnknown* p)
{
m_resultonlyitems.Remove((DWORD)p);
}

HRESULT EnumerateScopeChildren(long param, IConsoleNameSpace* pNameSpace)
{
T* pT = static_cast<T*>(this);

pT->PopulateChildren();

for (IUnknown** pp = m_scopeonlyitems.begin(); pp < m_scopeonlyitems.end(); pp++)
{
if (*pp == NULL)
continue;

CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> pData(*pp);
if (pData == NULL)
continue;

SCOPEDATAITEM* pscopeData;
pData->GetScopeData(&pscopeData);

if (pscopeData == NULL)
continue;

pscopeData->relativeID = param;
HRESULT hr = pNameSpace->InsertItem(pscopeData);
if (FAILED(hr))
return hr;
}

return S_OK;
}

HRESULT EnumerateResultChildren(IResultData* pResultData)
{
T* pT = static_cast<T*>(this);

pT->PopulateChildren();

for (IUnknown** pp = m_resultonlyitems.begin(); pp < m_resultonlyitems.end(); pp++)
{
if (*pp == NULL)
continue;

CComQIPtr<ISnapInDataInterface, &IID_ISnapInDataInterface> pData(*pp);
if (pData == NULL)
continue;

RESULTDATAITEM* pResultDataItem;
pData->GetResultData(&pResultDataItem);

if (pResultDataItem == NULL)
continue;

HRESULT hr = pResultData->InsertItem(pResultDataItem);
if (FAILED(hr))
return hr;
}

return S_OK;
}

static CSnapInToolbarInfo* GetToolbarInfo()
{
return NULL;
}

static const UINT GetMenuID()
{
return 0;
}

ISnapInDataInterfaceImpl()
{
}

BOOL Rename(OLECHAR* pNewName)
{
return TRUE;
}

};

#endif //__ATL_SNAPIN_H__


SNAPIN.CPP

#include "stdafx.h"
#include "[!ProjectName].h"
#include "[!HeaderName]"

[!crlf]
/////////////////////////////////////////////////////////////////////////////
// [!ClassName]ComponentData

[!if(VC)]
static const GUID GUID_NODETYPE =
[!SnapInNodeType_GUIDFMT];
[!else]
#error Please expand the following and remove this line
// [!SnapInNodeType]
static const GUID GUID_NODETYPE =
{0xXXXXXXXX,0xXXXX,0xXXXX,{0xXX,0xXX,0xXX,0xXX,0xXX,0xXX,0xXX,0xXX}};
[!endif]

const GUID*  [!ClassName]Data::m_NODETYPE = &GUID_NODETYPE;
const TCHAR* [!ClassName]Data::m_SZNODETYPE = _T("[!SnapInNodeType]");
const TCHAR* [!ClassName]Data::m_SZDISPLAY_NAME = _T("[!ClassName]");
const CLSID* [!ClassName]Data::m_SNAPIN_CLASSID = &CLSID_[!CoClassName];


SNAPIN.H

#ifndef __[!UpperShortName]_H_
#define __[!UpperShortName]_H_

#include "resource.h"
#include <atlsnap.h>

[!if=(IExtendPropertySheet, "TRUE)]
class [!ClassName]Page : public CPropertyPageImpl<[!ClassName]Page>
{
public :
[!ClassName]Page(TCHAR* pTitle = NULL) : CPropertyPageImpl<[!ClassName]Page> (pTitle)
{
}
enum { IDD = [!IDD_PROPPAGE] };
BEGIN_MSG_MAP([!ClassName]Page)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
CHAIN_MSG_MAP(CPropertyPageImpl<[!ClassName]Page>)
END_MSG_MAP()

LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
::SendMessage(GetParent(), PSM_SETWIZBUTTONS, 0, PSWIZB_FINISH);
return 1;
}
};
[!endif]

class [!ClassName]Data : public CSnapInDataObjectImpl<[!ClassName]Data>,
public ISnapInDataInterfaceImpl<[!ClassName]Data>,
public CComObjectRoot
{
static const GUID* m_NODETYPE;
static const TCHAR* m_SZNODETYPE;
static const TCHAR* m_SZDISPLAY_NAME;
static const CLSID* m_SNAPIN_CLASSID;

public:
static CComPtr<IControlbar> m_spControlBar;

public:
BEGIN_COM_MAP([!ClassName]Data)
COM_INTERFACE_ENTRY(IDataObject)
COM_INTERFACE_ENTRY(ISnapInDataInterface)
END_COM_MAP()

[!if=(IExtendContextMenu, "TRUE)]
BEGIN_SNAPINCOMMAND_MAP([!ClassName]Data)
END_SNAPINCOMMAND_MAP()
[!else]
[!if=(IExtendControlBar, "TRUE)]
BEGIN_SNAPINCOMMAND_MAP([!ClassName]Data)
END_SNAPINCOMMAND_MAP
[!endif]
[!endif]

[!if=(IExtendContextMenu, "TRUE)]
SNAPINMENUID([!IDR_MENU])
[!endif]

[!if=(IExtendControlBar, "TRUE)]
BEGIN_SNAPINTOOLBARID_MAP([!ClassName]Data)
[!if(VC)]
SNAPINTOOLBARID_ENTRY([!IDR_TOOLBAR])
[!else]
#error Please create a toolbar resource with the id [!IDR_TOOLBAR]
SNAPINTOOLBARID_ENTRY([!IDR_TOOLBAR])
[!endif]
END_SNAPINTOOLBARID_MAP()
[!endif]

[!ClassName]Data()
{
}
~[!ClassName]Data()
{
}

[!if=(IExtendPropertySheet, "TRUE)]
    STDMETHOD(CreatePropertyPages)(LPPROPERTYSHEETCALLBACK lpProvider,
        long handle, IUnknown* pUnk)
{
[!ClassName]Page* pPage = new [!ClassName]Page(_T("[!ShortName]"));
lpProvider->AddPage(pPage->Create());
return S_OK;
}

    STDMETHOD(QueryPagesFor)(void)
{
return S_OK;
}
[!endif]
void* GetNodeType()
{
return (void*)m_NODETYPE;
}

void* GetSZNodeType()
{
return (void*)m_SZNODETYPE;
}

void* GetDisplayName()
{
return (void*)m_SZDISPLAY_NAME;
}

void* GetSnapInCLSID()
{
return (void*)m_SNAPIN_CLASSID;
}

void PopulateChildren()
{
}
};

class [!ClassName]Component : public CComObjectRootEx<CComSingleThreadModel>,
public IComponentImpl<[!ClassName]Component>,
[!if=(IExtendPropertySheet, "TRUE)]
public IExtendPropertySheetImpl<[!ClassName]Component>,
[!endif]
[!if=(IExtendContextMenu, "TRUE)]
    public IExtendContextMenuImpl<[!ClassName]Component>,
[!endif]
[!if=(IExtendControlBar, "TRUE)]
public IExtendControlbarImpl<[!ClassName]Component>,
[!endif]
    public IPersistStream
{
public:

BEGIN_COM_MAP([!ClassName]Component)
    COM_INTERFACE_ENTRY(IComponent)
[!if=(IExtendPropertySheet, "TRUE)]
    COM_INTERFACE_ENTRY(IExtendPropertySheet)
[!endif]
[!if=(IExtendContextMenu, "TRUE)]
    COM_INTERFACE_ENTRY(IExtendContextMenu)
[!endif]
[!if=(IExtendControlBar, "TRUE)]
    COM_INTERFACE_ENTRY(IExtendControlbar)
[!endif]
    COM_INTERFACE_ENTRY(IPersistStream)
END_COM_MAP()

public:
[!ClassName]Component()
{
}
    STDMETHOD(GetClassID)(CLSID *pClassID)
{
ATLTRACENOTIMPL("CSnapInComponent::GetClassID");
}
STDMETHOD(IsDirty)()
{
ATLTRACENOTIMPL("CSnapInComponent::IsDirty");
}

    STDMETHOD(Load)(IStream *pStm)
{
ATLTRACENOTIMPL("CSnapInComponent::Load");
}
    STDMETHOD(Save)(IStream *pStm, BOOL fClearDirty)
{
ATLTRACENOTIMPL("CSnapInComponent::Save");
}
    STDMETHOD(GetSizeMax)(ULARGE_INTEGER *pcbSize)
{
ATLTRACENOTIMPL("CSnapInComponent::GetSizeMax");
}

};

class [!ClassName]ComponentData :  public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<[!ClassName]ComponentData, &CLSID_[!CoClassName]>,
public IComponentDataImpl<[!ClassName]ComponentData, [!ClassName]Component>,
[!if=(IExtendPropertySheet, "TRUE)]
public IExtendPropertySheetImpl<[!ClassName]Component>,
[!endif]
[!if=(IExtendContextMenu, "TRUE)]
    public IExtendContextMenuImpl<[!ClassName]Component>,
[!endif]
[!if=(IExtendControlBar, "TRUE)]
public IExtendControlbarImpl<[!ClassName]Component>,
[!endif]
    public IPersistStream
{
public:
[!ClassName]ComponentData ()
{
CComObject <[!ClassName]Data> *pData;
CComObject <[!ClassName]Data>::CreateInstance(&pData);
lstrcpy(pData->m_pszDisplayName, OLESTR("[!ClassName]"));
pData->InitParentList(NULL);
pData->QueryInterface(IID_IUnknown, (void**)&m_spNode);
}

BEGIN_COM_MAP([!ClassName]ComponentData)
    COM_INTERFACE_ENTRY(IComponentData)
[!if=(IExtendPropertySheet, "TRUE)]
    COM_INTERFACE_ENTRY(IExtendPropertySheet)
[!endif]
[!if=(IExtendContextMenu, "TRUE)]
    COM_INTERFACE_ENTRY(IExtendContextMenu)
[!endif]
[!if=(IExtendControlBar, "TRUE)]
    COM_INTERFACE_ENTRY(IExtendControlbar)
[!endif]
    COM_INTERFACE_ENTRY(IPersistStream)
END_COM_MAP()

DECLARE_REGISTRY_RESOURCEID([!IDR_REGISTRYID])
DECLARE_NOT_AGGREGATABLE([!ClassName]ComponentData)

    STDMETHOD(GetClassID)(CLSID *pClassID)
{
ATLTRACENOTIMPL("CSnapInObject::GetClassID");
}
STDMETHOD(IsDirty)()
{
ATLTRACENOTIMPL("CSnapInObject::IsDirty");
}

    STDMETHOD(Load)(IStream *pStm)
{
ATLTRACENOTIMPL("CSnapInObject::Load");
}
    STDMETHOD(Save)(IStream *pStm, BOOL fClearDirty)
{
ATLTRACENOTIMPL("CSnapInObject::Save");
}
    STDMETHOD(GetSizeMax)(ULARGE_INTEGER *pcbSize)
{
ATLTRACENOTIMPL("CSnapInObject::GetSizeMax");
}

    STDMETHOD(Initialize)(LPUNKNOWN pUnknown)
{
HRESULT hr = IComponentDataImpl<[!ClassName]ComponentData, [!ClassName]Component >::Initialize(pUnknown);
if (FAILED(hr))
return hr;

CComPtr<IImageList> spImageList;

if (m_spConsole->QueryScopeImageList(&spImageList) != S_OK)
{
ATLTRACE(_T("IConsole::QueryScopeImageList failed\n"));
return E_UNEXPECTED;
}

// Load bitmaps associated with the scope pane
// and add them to the image list
// Loads the default bitmaps generated by the wizard
// Change as needed
HBITMAP hBitmap16 = LoadBitmap(_Module.GetResourceInstance(), MAKEINTRESOURCE([!IDB_SNAPIN16]));
if (hBitmap16 == NULL)
return S_OK;

HBITMAP hBitmap32 = LoadBitmap(_Module.GetResourceInstance(), MAKEINTRESOURCE([!IDB_SNAPIN32]));
if (hBitmap32 == NULL)
return S_OK;

if (spImageList->ImageListSetStrip((long*)hBitmap16,
(long*)hBitmap32, 0, RGB(0, 128, 128)) != S_OK)
{
ATLTRACE(_T("IImageList::ImageListSetStrip failed\n"));
return E_UNEXPECTED;
}
return S_OK;
}
};
#endif


SNAPIN.IDL

[
uuid([!SnapInGUID]),
helpstring("[!TypeName]")
]
coclass [!CoClassName]
{
[default] interface IUnknown;
}


README.TXT

Microsoft Management Console SDK Release Notes

******Sample******
In the <SDK Root>\samples\sysmgmt\mmc directory is the MMC snapin sample.
The sample requires ATL (ActiveX Template Library).  If you have the Platform
SDK you can build the sample by type "nmake -a" or "nmake -a nodebug=1" in
the sample directory or you can load the snapin.mak in to Visual C++.

Be sure to install the MMC runtime before trying to test the sample. Once
you have MMC installed and built the sample, you can test the sample by
running "mmc.exe" and then choosing "Add/Remove Snap-in..." from the console
menu.  After adding the sample, you should have a "Files" node with Company
and User data.

******Extensions******
Extensions are implemented in the sample application included with the
MMC SDK but they are not discussed in the SDK documentation. This is
a temporary situation and more information will be available soon.

******MMC Programming Tutorial******
We are currently working on a tutorial that discusses implementing
the MMC's COM interfaces and other programming elements in a
"how-to" way. The tutorial will provide other helpful information
and sample code to simplify the task of developing a snap-in. It will
be available within the near future.

******Other Helpful Documentation******
The MMC SDK Overview contains suggestions for finding additional
programming information that relates to developing snap-ins.
In terms of convenience, you will find virtually everything you need
to know about COM and Win32 in the Microsoft Platform SDK. Topics
like structured storage and data objects are beyond the scope of the
MMC SDK but are covered thoroughly in the material available there.

******Our website******
The current MMC executable and the most recent MMC-related information
is available on our website. You can reach us at the following URL:

http://www.microsoft.com/management


CSNAPIN.CPP

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

#include "stdafx.h"
#include "Service.h"
#include "CSnapin.h"
#include "DataObj.h"
#include "afxdlgs.h"
#include "resource.h"
#include "genpage.h"  // Step 3

#include <atlimpl.cpp>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// All data is static for the sample
FOLDER_DATA FolderData[NUM_FOLDERS] =
{
    {L"User Data", L"1111", L"Info about users", USER},
    {L"Company Data", L"2222", L"Info about Companies", COMPANY},
    {L"Virtual Data", L"3333", L"Info about virtual items", VIRTUAL},
    {L"", L"", L"",STATIC}
};

FOLDER_DATA ExtFolderData[NUM_FOLDERS] =
{
    {L"1:", L"1111", L"Info about users", EXT_USER},
    {L"2:", L"2222", L"Info about Companies", EXT_COMPANY},
    {L"3:", L"3333", L"Infor about virtual items", EXT_VIRTUAL},
    {L"", L"", L"",STATIC}
};

static MMCBUTTON SnapinButtons[] =
{
 { 0, 1, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Folder", L"New Folder" },
 { 1, 2, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Inbox",  L"Mail Inbox"},
 { 2, 3, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Outbox", L"Mail Outbox" },
 { 3, 4, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Send",   L"Send Message" },
 { 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP,    L" ",      L"" },
 { 4, 5, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Trash",  L"Trash" },
 { 5, 6, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Open",   L"Open Folder"},
 { 6, 7, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"News",   L"Today's News" },
 { 7, 8, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"INews",  L"Internet News" },

};

static MMCBUTTON SnapinButtons2[] =
{
 { 0, 10, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Compose",   L"Compose Message" },
 { 1, 20, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Print",     L"Print Message" },
 { 2, 30, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Find",      L"Find Message" },
 { 0, 0,  TBSTATE_ENABLED, TBSTYLE_SEP,    L" ",         L"" },
 { 3, 40, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Inbox",     L"Inbox" },
 { 4, 50, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Smile",     L"Smile :-)" },
 { 5, 60, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Reply",     L"Reply" },
 { 0, 0,  TBSTATE_ENABLED, TBSTYLE_SEP   , L" ",         L"" },
 { 6, 70, TBSTATE_ENABLED, TBSTYLE_BUTTON, L"Reply All", L"Reply All" },

};

enum
{
    // Identifiers for each of the commands/views to be inserted into the context menu.
    IDM_COMMAND1,
    IDM_COMMAND2,
    IDM_SAMPLE_OCX_VIEW,
    IDM_SAMPLE_WEB_VIEW
};

static int n_count = 0;

//#define ODS OutputDebugString
#define ODS OutputDebugString

#ifdef DBX
  void DbxPrint(LPTSTR pszFmt, ...)
  {
      va_list va;
      va_start (va, pszFmt);
      TCHAR buf[250];
      wsprintf(buf, pszFmt, va);
      OutputDebugString(buf);
      va_end(va);
  }
  //#define DBX_PRINT     DbxPrint
  inline void __DummyTrace(LPTSTR, ...) { }
  #define DBX_PRINT     1 ? (void)0 : ::__DummyTrace
#else
  inline void __DummyTrace(LPTSTR, ...) { }
  #define DBX_PRINT     1 ? (void)0 : ::__DummyTrace
#endif

//
// The sample snap-in only has 1 property type and it's the workstation name
//

//
// Extracts the coclass guid format from the data object
//
template <class TYPE>
TYPE* Extract(LPDATAOBJECT lpDataObject, unsigned int cf)
{
    ASSERT(lpDataObject != NULL);

    TYPE* p = NULL;

    STGMEDIUM stgmedium = { TYMED_HGLOBAL, NULL };
    FORMATETC formatetc = { cf, NULL,
                            DVASPECT_CONTENT, -1, TYMED_HGLOBAL
                          };

    // Allocate memory for the stream
    int len = (cf == CDataObject::m_cfWorkstation) ?
        ((MAX_COMPUTERNAME_LENGTH+1) * sizeof(TYPE)) : sizeof(TYPE);

    stgmedium.hGlobal = GlobalAlloc(GMEM_SHARE, len);

    // Get the workstation name from the data object
    do
    {
        if (stgmedium.hGlobal == NULL)
            break;

        if (FAILED(lpDataObject->GetDataHere(&formatetc, &stgmedium)))
            break;

        p = reinterpret_cast<TYPE*>(stgmedium.hGlobal);

        if (p == NULL)
            break;

    } while (FALSE);

    return p;
}

BOOL IsMMCMultiSelectDataObject(IDataObject* pDataObject)
{
    if (pDataObject == NULL)
        return FALSE;

    static UINT s_cf = 0;
    if (s_cf == 0)
    {
        USES_CONVERSION;
        s_cf = RegisterClipboardFormat(W2T(CCF_MMC_MULTISELECT_DATAOBJECT));
    }

    FORMATETC fmt = {s_cf, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};

    return (pDataObject->QueryGetData(&fmt) == S_OK);
}

// Data object extraction helpers
CLSID* ExtractClassID(LPDATAOBJECT lpDataObject)
{
    return Extract<CLSID>(lpDataObject, CDataObject::m_cfCoClass);
}

GUID* ExtractNodeType(LPDATAOBJECT lpDataObject)
{
    return Extract<GUID>(lpDataObject, CDataObject::m_cfNodeType);
}

wchar_t* ExtractWorkstation(LPDATAOBJECT lpDataObject)
{
    return Extract<wchar_t>(lpDataObject, CDataObject::m_cfWorkstation);
}

INTERNAL* ExtractInternalFormat(LPDATAOBJECT lpDataObject)
{
    return Extract<INTERNAL>(lpDataObject, CDataObject::m_cfInternal);
}

HRESULT _QueryDataObject(long cookie, DATA_OBJECT_TYPES type,
                         CComponentDataImpl* pImpl, LPDATAOBJECT* ppDataObject)
{
    ASSERT(ppDataObject != NULL);
    ASSERT(pImpl != NULL);

    CComObject<CDataObject>* pObject;

    CComObject<CDataObject>::CreateInstance(&pObject);
    ASSERT(pObject != NULL);

    // Save cookie and type for delayed rendering
    pObject->SetType(type);
    pObject->SetCookie(cookie);

#ifdef _DEBUG
    pObject->SetComponentData(pImpl);
#endif

    // Store the coclass with the data object
    pObject->SetClsid(pImpl->GetCoClassID());

    return  pObject->QueryInterface(IID_IDataObject,
                    reinterpret_cast<void**>(ppDataObject));
}

DWORD GetItemType(long cookie)
{
    // folder = CFoder* is cookie
    // result = RESULT_DATA* is the cookie

    return (*reinterpret_cast<DWORD*>(cookie));
}

/////////////////////////////////////////////////////////////////////////////
// Return TRUE if we are enumerating our main folder

BOOL CSnapin::IsEnumerating(LPDATAOBJECT lpDataObject)
{
    BOOL bResult = FALSE;

    ASSERT(lpDataObject);
    GUID* nodeType = ExtractNodeType(lpDataObject);

    // Is this my main node (static folder node type)
    if (::IsEqualGUID(*nodeType, cNodeTypeStatic) == TRUE)
        bResult = TRUE;

    // Free resources
    ::GlobalFree(reinterpret_cast<HANDLE>(nodeType));

    return bResult;
}

/////////////////////////////////////////////////////////////////////////////
// CSnapin's IComponent implementation

STDMETHODIMP CSnapin::GetResultViewType(long cookie,  LPOLESTR* ppViewType, long* pViewOptions)
{

    // guid for custom view
    static WCHAR* szCalendarGUID = L"{8E27C92B-1264-101C-8A2F-040224009C02}";
    static WCHAR* szMicrosoftURL = L"www.microsoft.com";

    *pViewOptions = MMC_VIEW_OPTIONS_MULTISELECT;

    // if list view
    if (m_CustomViewID == VIEW_DEFAULT_LV)
    {
        m_bVirtualView = FALSE;

       // if static folder not selected
        if (cookie != NULL)
        {
            // See if virtual data folder is selected
            CFolder* pFolder = reinterpret_cast<CFolder*>(cookie);
            ASSERT(pFolder->itemType == SCOPE_ITEM);
            FOLDER_TYPES ftype = pFolder->GetType();

            m_bVirtualView = (ftype == VIRTUAL || ftype == EXT_VIRTUAL);

            if (m_bVirtualView)
                *pViewOptions |= MMC_VIEW_OPTIONS_OWNERDATALIST;
        }

        return S_FALSE;
    }

    WCHAR* pszView = (m_CustomViewID == VIEW_CALENDAR_OCX) ? szCalendarGUID :
                                                             szMicrosoftURL;
    UINT uiByteLen = (wcslen(pszView) + 1) * sizeof(WCHAR);
    LPOLESTR psz = (LPOLESTR)::CoTaskMemAlloc(uiByteLen);

    USES_CONVERSION;

    if (psz != NULL)
    {
       wcscpy(psz, pszView);
       *ppViewType = psz;
       return S_OK;
    }

    return S_FALSE;
}

STDMETHODIMP CSnapin::Initialize(LPCONSOLE lpConsole)
{
    DBX_PRINT(_T(" ----------  CSnapin::Initialize<0x08x>\n"), this);
    ASSERT(lpConsole != NULL);
    m_bInitializedC = true;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    // Save the IConsole pointer
    m_pConsole = lpConsole;
    m_pConsole->AddRef();

    // Load resource strings
    LoadResources();

    // QI for a IHeaderCtrl
    HRESULT hr = m_pConsole->QueryInterface(IID_IHeaderCtrl,
                        reinterpret_cast<void**>(&m_pHeader));

    // Give the console the header control interface pointer
    if (SUCCEEDED(hr))
        m_pConsole->SetHeader(m_pHeader);

    m_pConsole->QueryInterface(IID_IResultData,
                        reinterpret_cast<void**>(&m_pResult));

    hr = m_pConsole->QueryResultImageList(&m_pImageResult);
    ASSERT(hr == S_OK);

    hr = m_pConsole->QueryConsoleVerb(&m_pConsoleVerb);
    ASSERT(hr == S_OK);

    return S_OK;
}

STDMETHODIMP CSnapin::Notify(LPDATAOBJECT lpDataObject, MMC_NOTIFY_TYPE event, long arg, long param)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    if (IS_SPECIAL_DATAOBJECT(lpDataObject))
    {
        if (event == MMCN_BTN_CLICK)
        {
            if (m_CustomViewID != VIEW_DEFAULT_LV)
            {
                switch (param)
                {
                case MMC_VERB_REFRESH:
                    ::AfxMessageBox(_T("MMCN_BTN_CLICK::MMC_VERB_REFRESH"));
                    _OnRefresh(lpDataObject);
                    break;

                case MMC_VERB_PROPERTIES:
                    ::AfxMessageBox(_T("MMCN_BTN_CLICK::MMC_VERB_PROPERTIES"));
                    break;

                default:
                    ::AfxMessageBox(_T("MMCN_BTN_CLICK::param unknown"));
                    break;
                }
            }
        }
        else
        {
            switch (event)
            {
            case MMCN_REFRESH:
                ::AfxMessageBox(_T("MMCN_BTN_CLICK::MMCN_REFRESH"));
                _OnRefresh(lpDataObject);
                break;
            }
        }

        return S_OK;
    }

    HRESULT hr = S_OK;
    long cookie;

    if (event == MMCN_PROPERTY_CHANGE)
    {
        hr = OnPropertyChange(lpDataObject);
    }
    else if (event == MMCN_VIEW_CHANGE)
    {
        hr = OnUpdateView(lpDataObject);
    }
    else if (event == MMCN_DESELECT_ALL)
    {
        DBX_PRINT(_T("CSnapin::Notify -> MMCN_DESELECT_ALL \n"));
    }
    else if (event == MMCN_COLUMN_CLICK)
    {
        DBX_PRINT(_T("CSnapin::Notify -> MMCN_COLUMN_CLICK \n"));
    }
    else if (event == MMCN_SNAPINHELP)
    {
        AfxMessageBox(_T("CSnapin::Notify ->MMCN_SNAPINHELP"));
    }
    else
    {
        INTERNAL* pInternal = NULL;

        if (IsMMCMultiSelectDataObject(lpDataObject) == FALSE)
        {
            pInternal = ExtractInternalFormat(lpDataObject);

            if (pInternal == NULL)
            {
                ASSERT(FALSE);
                return S_OK;
            }

            if (pInternal)
                cookie = pInternal->m_cookie;
        }

        switch(event)
        {
        case MMCN_ACTIVATE:
            break;

        case MMCN_CLICK:
            hr = OnResultItemClk(pInternal->m_type, cookie);
            break;

        case MMCN_DBLCLICK:
            if (pInternal->m_type == CCT_RESULT)
                Command(IDM_COMMAND1, lpDataObject);
            else
                hr = S_FALSE;

            break;

        case MMCN_ADD_IMAGES:
            OnAddImages(cookie, arg, param);
            break;

        case MMCN_SHOW:
            hr = OnShow(cookie, arg, param);
            break;

        case MMCN_MINIMIZED:
            hr = OnMinimize(cookie, arg, param);
            break;

        case MMCN_INITOCX:
            ::MessageBox(NULL, _T("MMCN_INITOCX"), _T("TRACE"), MB_OK);
            ASSERT(param != 0);
            break;

        case MMCN_DESELECT_ALL:
        case MMCN_SELECT:
            HandleStandardVerbs((event == MMCN_DESELECT_ALL),
                                arg, lpDataObject);
            break;

        case MMCN_PASTE:
            AfxMessageBox(_T("CSnapin::MMCN_PASTE"));
            break;

        case MMCN_DELETE:
            AfxMessageBox(_T("CSnapin::MMCN_DELETE"));
            break;

        case MMCN_CONTEXTHELP:
            hr = OnContextHelp(lpDataObject);
            break;

        case MMCN_REFRESH:
            AfxMessageBox(_T("CSnapin::MMCN_REFRESH"));
            _OnRefresh(lpDataObject);
            break;

        case MMCN_RENAME:
            ODS(_T("\n\n\t\tCSnapin::MMCN_RENAME\n\n"));
            break;

        // Note - Future expansion of notify types possible
        default:
            hr = E_UNEXPECTED;
            break;
        }

        if (pInternal != NULL)
        {
            ::GlobalFree(reinterpret_cast<HANDLE>(pInternal));
        }
    }

    if (m_pResult)
        m_pResult->SetDescBarText(L"hello world");

    return hr;
}

void CSnapin::_OnRefresh(LPDATAOBJECT pDataObject)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    INTERNAL* pInternal = ExtractInternalFormat(pDataObject);
    if (pInternal == NULL)
        return;

    if (pInternal->m_type == CCT_SCOPE)
    {
        CComponentDataImpl* pData = dynamic_cast<CComponentDataImpl*>(m_pComponentData);
        CFolder* pFolder = pData->FindObject(pInternal->m_cookie);
        ::AfxMessageBox(pInternal->m_cookie ? pFolder->m_pszName : _T("Files"));
    }
    else
    {
        RESULT_DATA* pData = reinterpret_cast<RESULT_DATA*>(pInternal->m_cookie);
        ::AfxMessageBox(pData->szName);
    }
}

HRESULT CSnapin::OnContextHelp(LPDATAOBJECT pdtobj)
{
    TCHAR name[128];
    GetItemName(pdtobj, name);

    TCHAR buf[200];
    wsprintf(buf, _T("Context help requested for item: %s"), name);
    ::MessageBox(NULL, buf, _T("TRACE"), MB_OK);

    return S_OK;
}

STDMETHODIMP CSnapin::Destroy(long cookie)
{
    DBX_PRINT(_T(" ----------  CSnapin::Destroy<0x08x>\n"), this);
    ASSERT(m_bInitializedC);
    m_bDestroyedC = true;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    // Release the interfaces that we QI'ed
    if (m_pConsole != NULL)
    {
        // Tell the console to release the header control interface
        m_pConsole->SetHeader(NULL);
        SAFE_RELEASE(m_pHeader);

        SAFE_RELEASE(m_pResult);
        SAFE_RELEASE(m_pImageResult);

        // Release the IConsole interface last
        SAFE_RELEASE(m_pConsole);
        SAFE_RELEASE(m_pComponentData); // QI'ed in IComponentDataImpl::CreateComponent

        SAFE_RELEASE(m_pConsoleVerb);
    }

    return S_OK;
}

typedef CArray<GUID, const GUID&> CGUIDArray;

void GuidArray_Add(CGUIDArray& rgGuids, const GUID& guid)
{
    for (int i=rgGuids.GetUpperBound(); i >= 0; --i)
    {
        if (rgGuids[i] == guid)
            break;
    }

    if (i < 0)
        rgGuids.Add(guid);
}

HRESULT CSnapin::QueryMultiSelectDataObject(long cookie, DATA_OBJECT_TYPES type,
                                            LPDATAOBJECT* ppDataObject)
{
    ASSERT(ppDataObject != NULL);
    if (ppDataObject == NULL)
        return E_POINTER;

    CGUIDArray rgGuids;

    if (m_bVirtualView == TRUE)
    {
        GuidArray_Add(rgGuids, cNodeTypeCompany);
    }
    else
    {
        // Determine the items selected
        ASSERT(m_pResult != NULL);
        RESULTDATAITEM rdi;
        ZeroMemory(&rdi, sizeof(rdi));
        rdi.mask = RDI_STATE;
        rdi.nIndex = -1;
        rdi.nState = TVIS_SELECTED;

        while (m_pResult->GetNextItem(&rdi) == S_OK)
        {
            FOLDER_TYPES fType;
            DWORD* pdw = reinterpret_cast<DWORD*>(rdi.lParam);

            if (*pdw == SCOPE_ITEM)
            {
                CFolder* pFolder = reinterpret_cast<CFolder*>(rdi.lParam);
                fType = pFolder->m_type;
            }
            else
            {
                ASSERT(*pdw == RESULT_ITEM);
                RESULT_DATA* pData = reinterpret_cast<RESULT_DATA*>(rdi.lParam);
                fType = pData->parentType;
            }

            const GUID* pguid;
            switch (fType)
            {
            case STATIC:
                pguid = &cNodeTypeStatic;
                break;

            case COMPANY:
                pguid = &cNodeTypeCompany;
                break;

            case USER:
                pguid = &cNodeTypeUser;
                break;

            case EXT_COMPANY:
                pguid = &cNodeTypeExtCompany;
                break;

            case EXT_USER:
                pguid = &cNodeTypeExtUser;
                break;

            case VIRTUAL:
            case EXT_VIRTUAL:
                pguid = &cNodeTypeVirtual;
                break;

            default:
                return E_FAIL;
            }

            GuidArray_Add(rgGuids, *pguid);
        }
    }

    CComObject<CDataObject>* pObject;
    CComObject<CDataObject>::CreateInstance(&pObject);
    ASSERT(pObject != NULL);

    // Save cookie and type for delayed rendering
    pObject->SetType(type);
    pObject->SetCookie(cookie);
    pObject->SetMultiSelDobj();

    CComponentDataImpl* pImpl = dynamic_cast<CComponentDataImpl*>(m_pComponentData);

#ifdef _DEBUG
    pObject->SetComponentData(pImpl);
#endif

    // Store the coclass with the data object
    pObject->SetClsid(pImpl->GetCoClassID());
    UINT cb = rgGuids.GetSize() * sizeof(GUID);
    GUID* pGuid = new GUID[rgGuids.GetSize()];
    CopyMemory(pGuid, rgGuids.GetData(), cb);
    pObject->SetMultiSelData((BYTE*)pGuid, cb);

    return  pObject->QueryInterface(IID_IDataObject,
                    reinterpret_cast<void**>(ppDataObject));
    return S_OK;
}

STDMETHODIMP CSnapin::QueryDataObject(long cookie, DATA_OBJECT_TYPES type,
                        LPDATAOBJECT* ppDataObject)
{
    if (cookie == MMC_MULTI_SELECT_COOKIE)
        return QueryMultiSelectDataObject(cookie, type, ppDataObject);

    ASSERT(type == CCT_RESULT);

#ifdef _DEBUG
    if (cookie != MMC_MULTI_SELECT_COOKIE &&
        m_bVirtualView == FALSE)
    {
        DWORD dwItemType = GetItemType(cookie);
        ASSERT(dwItemType == RESULT_ITEM);
    }
#endif

    // Delegate it to the IComponentData
    ASSERT(m_pComponentData != NULL);
    CComponentDataImpl* pImpl = dynamic_cast<CComponentDataImpl*>(m_pComponentData);
    ASSERT(pImpl != NULL);
    return _QueryDataObject(cookie, type, pImpl, ppDataObject);
}

/////////////////////////////////////////////////////////////////////////////
// CSnapin's implementation specific members

DEBUG_DECLARE_INSTANCE_COUNTER(CSnapin);

CSnapin::CSnapin()
: m_bIsDirty(TRUE), m_bInitializedC(false), m_bDestroyedC(false)
{
    DEBUG_INCREMENT_INSTANCE_COUNTER(CSnapin);
    Construct();
}

CSnapin::~CSnapin()
{
#if DBG==1
    ASSERT(dbg_cRef == 0);
#endif

    DEBUG_DECREMENT_INSTANCE_COUNTER(CSnapin);

    SAFE_RELEASE(m_pToolbar1);
    SAFE_RELEASE(m_pToolbar2);

    SAFE_RELEASE(m_pMenuButton1);

    SAFE_RELEASE(m_pControlbar);

    // Make sure the interfaces have been released
    ASSERT(m_pConsole == NULL);
    ASSERT(m_pHeader == NULL);
    ASSERT(m_pToolbar1 == NULL);
    ASSERT(m_pToolbar2 == NULL);

    delete m_pbmpToolbar1;
    delete m_pbmpToolbar2;

    ASSERT(!m_bInitializedC || m_bDestroyedC);

    Construct();
}

void CSnapin::Construct()
{
#if DBG==1
    dbg_cRef = 0;
#endif

    m_pConsole = NULL;
    m_pHeader = NULL;

    m_pResult = NULL;
    m_pImageResult = NULL;
    m_pComponentData = NULL;
    m_pToolbar1 = NULL;
    m_pToolbar2 = NULL;
    m_pControlbar = NULL;

    m_pMenuButton1 = NULL;

    m_pbmpToolbar1 = NULL;
    m_pbmpToolbar2 = NULL;

    m_pConsoleVerb = NULL;

    m_CustomViewID = VIEW_DEFAULT_LV;
    m_bVirtualView = FALSE;
    m_dwVirtualSortOptions = 0;
}

void CSnapin::LoadResources()
{
    // Load strings from resources
    m_column1.LoadString(IDS_NAME);
    m_column2.LoadString(IDS_SIZE);
    m_column3.LoadString(IDS_TYPE);
}

HRESULT CSnapin::InitializeHeaders(long cookie)
{
    HRESULT hr = S_OK;

    ASSERT(m_pHeader);

    USES_CONVERSION;

    // Put the correct headers depending on the cookie
    // Note - cookie ignored for this sample
    m_pHeader->InsertColumn(0, T2COLE(m_column1), LVCFMT_LEFT, 180);     // Name
    m_pHeader->InsertColumn(1, T2COLE(m_column2), LVCFMT_RIGHT, 90);     // Size
    m_pHeader->InsertColumn(2, T2COLE(m_column3), LVCFMT_LEFT, 160);     // Type

    return hr;
}

HRESULT CSnapin::InitializeBitmaps(long cookie)
{
    ASSERT(m_pImageResult != NULL);

    ::CBitmap bmp16x16;
    ::CBitmap bmp32x32;

    // Load the bitmaps from the dll
    bmp16x16.LoadBitmap(IDB_16x16);
    bmp32x32.LoadBitmap(IDB_32x32);

    // Set the images
    m_pImageResult->ImageListSetStrip(reinterpret_cast<long*>(static_cast<HBITMAP>(bmp16x16)),
                      reinterpret_cast<long*>(static_cast<HBITMAP>(bmp32x32)),
                       0, RGB(255, 0, 255));

    return S_OK;
}

WCHAR* StringFromFolderType(FOLDER_TYPES type)
{
    static WCHAR* s_szStatic    = L"Static";
    static WCHAR* s_szCompany   = L"Company";
    static WCHAR* s_szUser      = L"User";
    static WCHAR* s_szVirtual   = L"Virtual";
    static WCHAR* s_szUnknown   = L"Unknown";

    switch (type)
    {
    case STATIC:    return s_szStatic;
    case COMPANY:   return s_szCompany;
    case USER:      return s_szUser;
    case VIRTUAL:   return s_szVirtual;
    default:        return s_szUnknown;
    }
}

STDMETHODIMP CSnapin::GetDisplayInfo(LPRESULTDATAITEM pResult)
{
    static WCHAR* s_szSize = L"200";

    ASSERT(pResult != NULL);

    if (pResult)
    {
        if (pResult->bScopeItem == TRUE)
        {
            CFolder* pFolder = reinterpret_cast<CFolder*>(pResult->lParam);
            if (pResult->mask & RDI_STR)
            {
                if (pResult->nCol == 0)
                    pResult->str = pFolder->m_pszName;
                else if (pResult->nCol == 1)
                    pResult->str = (LPOLESTR)s_szSize;
                else
                    pResult->str = (LPOLESTR)StringFromFolderType(pFolder->m_type);

                ASSERT(pResult->str != NULL);

                if (pResult->str == NULL)
                    pResult->str = (LPOLESTR)L"";
            }
        }
        else
        {
            RESULT_DATA* pData;

            // if virtual, derive result item from index
            // else lParam is the item pointer
            if (m_bVirtualView)
                pData = GetVirtualResultItem(pResult->nIndex);
            else
                pData= reinterpret_cast<RESULT_DATA*>(pResult->lParam);

            if (pResult->mask & RDI_STR)
            {
                if (pResult->nCol == 0)
                    pResult->str = (LPOLESTR)pData->szName;
                else if(pResult->nCol == 1)
                    pResult->str = (LPOLESTR)pData->szSize;
                else
                    pResult->str = (LPOLESTR)pData->szType;

                ASSERT(pResult->str != NULL);

                if (pResult->str == NULL)
                    pResult->str = (LPOLESTR)L"";
            }

            // MMC can request image and indent for virtual data
            if (pResult->mask & RDI_IMAGE)
                pResult->nImage = 4;
        }
    }

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// IExtendContextMenu Implementation

// Array of menu item commands to be inserted into the contest menu.
// Note - the first item is the menu text, // CCM_SPECIAL_DEFAULT_ITEM
// the second item is the status string
static CONTEXTMENUITEM menuItems[] =
{
    {
        L"Command 1", L"Sample extension menu added by snapin (Command 1)",
        IDM_COMMAND1, CCM_INSERTIONPOINTID_PRIMARY_TOP, 0, 0
    },
    {
        L"Command 2", L"Sample extension menu added by snapin (Command 2)",
        IDM_COMMAND2, CCM_INSERTIONPOINTID_PRIMARY_TOP, 0, 0
    },
    { NULL, NULL, 0, 0, 0 }
};

// Array of view items to be inserted into the context menu.
static CONTEXTMENUITEM viewItems[] =
{
    {
        L"Calendar", L"Sample OCX custom view",
        IDM_SAMPLE_OCX_VIEW, CCM_INSERTIONPOINTID_PRIMARY_VIEW, 0, 0
    },
    {
        L"Microsoft", L"Sample WEB custom view",
        IDM_SAMPLE_WEB_VIEW, CCM_INSERTIONPOINTID_PRIMARY_VIEW, 0, 0
    },
    { NULL, NULL, 0, 0, 0 },
};

// guid for custom view
static GUID CLSID_SmGraphControl =
        {0xC4D2D8E0L,0xD1DD,0x11CE,0x94,0x0F,0x00,0x80,0x29,0x00,0x43,0x47};

STDMETHODIMP CSnapin::AddMenuItems(LPDATAOBJECT pDataObject,
                                    LPCONTEXTMENUCALLBACK pContextMenuCallback,
                                    long *pInsertionAllowed)
{
    viewItems[0].fFlags = (m_CustomViewID == VIEW_CALENDAR_OCX) ? MF_CHECKED : 0;
    viewItems[1].fFlags = (m_CustomViewID == VIEW_MICROSOFT_URL) ? MF_CHECKED : 0;

    return dynamic_cast<CComponentDataImpl*>(m_pComponentData)->
            AddMenuItems(pDataObject, pContextMenuCallback, pInsertionAllowed);
}

STDMETHODIMP CSnapin::Command(long nCommandID, LPDATAOBJECT pDataObject)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    INTERNAL* pInternal = ExtractInternalFormat(pDataObject);
    if (pInternal == NULL)
        return E_FAIL;

    if (pInternal->m_type == CCT_SCOPE)
    {
        // Handle view specific commands here
        switch (nCommandID)
        {
        case IDM_SAMPLE_WEB_VIEW:
        case IDM_SAMPLE_OCX_VIEW:
            m_CustomViewID = (nCommandID == IDM_SAMPLE_OCX_VIEW) ?
                                    VIEW_CALENDAR_OCX : VIEW_MICROSOFT_URL;

            // Ask console to reslelect the node to force a new view
            if (pInternal->m_cookie == 0)
            {
                CComponentDataImpl* pCCDI =
                    dynamic_cast<CComponentDataImpl*>(m_pComponentData);

                ASSERT(pCCDI != NULL);

                m_pConsole->SelectScopeItem(pCCDI->m_pStaticRoot);
            }
            else
            {
                CFolder* pFolder = reinterpret_cast<CFolder*>(pInternal->m_cookie);
                m_pConsole->SelectScopeItem(pFolder->m_pScopeItem->ID);
            }
            break;

        case MMCC_STANDARD_VIEW_SELECT:
            m_CustomViewID = VIEW_DEFAULT_LV;
            break;

        default:
            // Pass non-view specific commands to ComponentData
            return dynamic_cast<CComponentDataImpl*>(m_pComponentData)->
                Command(nCommandID, pDataObject);
        }
    }
    else if (pInternal->m_type == CCT_RESULT)
    {

        // Handle each of the commands.
        switch (nCommandID)
        {
        case IDM_COMMAND1:
        case IDM_COMMAND2:
        {
            m_pResult->ModifyViewStyle(MMC_SINGLESEL, (MMC_RESULT_VIEW_STYLE)0);

            RESULTDATAITEM rdi;
            ZeroMemory(&rdi, sizeof(rdi));

            rdi.mask = RDI_STATE;
            rdi.nState = LVIS_SELECTED;
            rdi.nIndex = -1;
            m_pResult->GetNextItem(&rdi);

            int iSel = rdi.nIndex;

            RESULT_DATA* pData;

            // if virtual view, derive result item from the index
            if (m_bVirtualView)
            {
                pData = GetVirtualResultItem(iSel);
            }
            // else get the cookie (which is result item ptr)
            else

{
                ZeroMemory(&rdi, sizeof(rdi));
                rdi.mask = RDI_PARAM;
                rdi.nIndex = iSel;
                HRESULT hr = m_pResult->GetItem(&rdi);
                ASSERT(SUCCEEDED(hr));
                ASSERT(rdi.lParam != 0);

                pData = reinterpret_cast<RESULT_DATA*>(rdi.lParam);
            }

            CString strBuf = (nCommandID == IDM_COMMAND1) ?
                _T("\t Command 1 executed.\n\n") : _T("\t Command 2 executed.\n\n");

            strBuf += pData->szName;
            strBuf += _T(" is the currently selected item.");

            AfxMessageBox(strBuf);

            // change image in list
            if (!m_bVirtualView)
            {
                ZeroMemory(&rdi, sizeof(rdi));
                rdi.mask = RDI_IMAGE;
                rdi.nIndex = iSel;
                rdi.nImage = 3;
                HRESULT hr = m_pResult->SetItem(&rdi);
                ASSERT(SUCCEEDED(hr));
            }
        }
        break;

        default:
            ASSERT(FALSE); // Unknown command!
            break;
        }
    }
    else
    {
        ASSERT(0);
    }

    ::GlobalFree(reinterpret_cast<HANDLE>(pInternal));

    return S_OK;
}

STDMETHODIMP CSnapin::GetClassID(CLSID *pClassID)
{
    ASSERT(pClassID != NULL);

    // Copy the CLSID for this snapin
    *pClassID = CLSID_Snapin;

    return E_NOTIMPL;
}

STDMETHODIMP CSnapin::IsDirty()
{
    // Always save / Always dirty.
    return ThisIsDirty() ? S_OK : S_FALSE;
}

STDMETHODIMP CSnapin::Load(IStream *pStm)
{
    DBX_PRINT(_T(" ----------  CSnapin::Load<0x08x>\n"), this);
    ASSERT(m_bInitializedC);

    ASSERT(pStm);
    // Read the string
    char psz[10];
    ULONG nBytesRead;
    HRESULT hr = pStm->Read(psz, 10, &nBytesRead);

    // Verify that the read succeeded
    ASSERT(SUCCEEDED(hr) && nBytesRead == 10);

    // check to see if the string is the correct string
    ASSERT(strcmp("987654321", psz) == 0);

    ClearDirty();

    return SUCCEEDED(hr) ? S_OK : E_FAIL;
}

STDMETHODIMP CSnapin::Save(IStream *pStm, BOOL fClearDirty)
{
    DBX_PRINT(_T(" ----------  CSnapin::Save<0x08x>\n"), this);
    ASSERT(m_bInitializedC);

    ASSERT(pStm);

    // Write the string
    ULONG nBytesWritten;
    HRESULT hr = pStm->Write("987654321", 10, &nBytesWritten);

    // Verify that the write operation succeeded
    ASSERT(SUCCEEDED(hr) && nBytesWritten == 10);
    if (FAILED(hr))
        return STG_E_CANTSAVE;

    if (fClearDirty)
        ClearDirty();
    return S_OK;
}

STDMETHODIMP CSnapin::GetSizeMax(ULARGE_INTEGER *pcbSize)
{
    ASSERT(pcbSize);

    // Set the size of the string to be saved
    ULISet32(*pcbSize, 10);

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// IComponentData implementation

DEBUG_DECLARE_INSTANCE_COUNTER(CComponentDataImpl);

CComponentDataImpl::CComponentDataImpl()
    : m_bIsDirty(TRUE), m_pScope(NULL), m_pConsole(NULL),
      m_bInitializedCD(false), m_bDestroyedCD(false)
{
    DEBUG_INCREMENT_INSTANCE_COUNTER(CComponentDataImpl);

#ifdef _DEBUG
    m_cDataObjects = 0;
#endif
}

CComponentDataImpl::~CComponentDataImpl()
{
    DEBUG_DECREMENT_INSTANCE_COUNTER(CComponentDataImpl);

    ASSERT(m_pScope == NULL);

    ASSERT(!m_bInitializedCD || m_bDestroyedCD);

    // Some snap-in is hanging on to data objects.
    // If they access, it will crash!!!
    ASSERT(m_cDataObjects <= 1);
}

STDMETHODIMP CComponentDataImpl::Initialize(LPUNKNOWN pUnknown)
{
    DBX_PRINT(_T(" ----------  CComponentDataImpl::Initialize<0x08x>\n"), this);
    m_bInitializedCD = true;

    ASSERT(pUnknown != NULL);
    HRESULT hr;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    // MMC should only call ::Initialize once!
    ASSERT(m_pScope == NULL);
    pUnknown->QueryInterface(IID_IConsoleNameSpace,
                    reinterpret_cast<void**>(&m_pScope));

    // add the images for the scope tree
    ::CBitmap bmp16x16;
    LPIMAGELIST lpScopeImage;

    hr = pUnknown->QueryInterface(IID_IConsole, reinterpret_cast<void**>(&m_pConsole));
    ASSERT(hr == S_OK);

    hr = m_pConsole->QueryScopeImageList(&lpScopeImage);

    ASSERT(hr == S_OK);

    // Load the bitmaps from the dll
    bmp16x16.LoadBitmap(IDB_16x16);

    // Set the images
    lpScopeImage->ImageListSetStrip(reinterpret_cast<long*>(static_cast<HBITMAP>(bmp16x16)),
                      reinterpret_cast<long*>(static_cast<HBITMAP>(bmp16x16)),
                       0, RGB(255, 0, 255));

    lpScopeImage->Release();

    return S_OK;
}

STDMETHODIMP CComponentDataImpl::CreateComponent(LPCOMPONENT* ppComponent)
{
    ASSERT(ppComponent != NULL);

    CComObject<CSnapin>* pObject;
    CComObject<CSnapin>::CreateInstance(&pObject);
    ASSERT(pObject != NULL);

    // Store IComponentData
    pObject->SetIComponentData(this);

    return  pObject->QueryInterface(IID_IComponent,
                    reinterpret_cast<void**>(ppComponent));
}

STDMETHODIMP CComponentDataImpl::Notify(LPDATAOBJECT lpDataObject, MMC_NOTIFY_TYPE event, long arg, long param)
{
    ASSERT(m_pScope != NULL);
    HRESULT hr;

    // Since it's my folder it has an internal format.
    // Design Note: for extension.  I can use the fact, that the data object doesn't have
    // my internal format and I should look at the node type and see how to extend it.
    if (event == MMCN_PROPERTY_CHANGE)
    {
        hr = OnProperties(param);
    }
    else
    {

        INTERNAL* pInternal = ExtractInternalFormat(lpDataObject);

        if (pInternal == NULL)
        {
            return S_OK;
        }

        long cookie = pInternal->m_cookie;
        ::GlobalFree(reinterpret_cast<HANDLE>(pInternal));

        switch(event)
        {
        case MMCN_PASTE:
            AfxMessageBox(_T("CSnapin::MMCN_PASTE"));
            break;

        case MMCN_DELETE:
            hr = OnDelete(cookie);
            break;

        case MMCN_REMOVE_CHILDREN:
            hr = OnRemoveChildren(arg);
            break;

        case MMCN_RENAME:
            hr = OnRename(cookie, arg, param);
            break;

        case MMCN_EXPAND:
            hr = OnExpand(lpDataObject, arg, param);
            break;

        default:
            break;
        }

    }

    return hr;
}

STDMETHODIMP CComponentDataImpl::Destroy()
{
    DBX_PRINT(_T(" ----------  CComponentDataImpl::Destroy<0x08x>\n"), this);
    ASSERT(m_bInitializedCD);
    m_bDestroyedCD = true;

    // Delete enumerated scope items
    DeleteList();

    SAFE_RELEASE(m_pScope);
    SAFE_RELEASE(m_pConsole);

    return S_OK;
}

STDMETHODIMP CComponentDataImpl::QueryDataObject(long cookie, DATA_OBJECT_TYPES type, LPDATAOBJECT* ppDataObject)
{
#ifdef _DEBUG
    if (cookie == 0)
    {
        ASSERT(type != CCT_RESULT);
    }
    else
    {
        ASSERT(type == CCT_SCOPE);

        DWORD dwItemType = GetItemType(cookie);
        ASSERT(dwItemType == SCOPE_ITEM);
    }
#endif

    return _QueryDataObject(cookie, type, this, ppDataObject);
}

///////////////////////////////////////////////////////////////////////////////
//// IPersistStream interface members

STDMETHODIMP CComponentDataImpl::GetClassID(CLSID *pClassID)
{
    ASSERT(pClassID != NULL);

    // Copy the CLSID for this snapin
    *pClassID = CLSID_Snapin;

    return E_NOTIMPL;
}

STDMETHODIMP CComponentDataImpl::IsDirty()
{
    // Always save / Always dirty.
    return ThisIsDirty() ? S_OK : S_FALSE;
}

STDMETHODIMP CComponentDataImpl::Load(IStream *pStm)
{
    DBX_PRINT(_T(" ----------  CComponentDataImpl::Load<0x08x>\n"), this);

    ASSERT(pStm);
    ASSERT(m_bInitializedCD);

    // Read the string
    char psz[10];
    ULONG nBytesRead;
    HRESULT hr = pStm->Read(psz, 10, &nBytesRead);

    // Verify that the read succeeded
    ASSERT(SUCCEEDED(hr) && nBytesRead == 10);

    // check to see if the string is the correct string
    ASSERT(strcmp("123456789", psz) == 0);

    ClearDirty();

    return SUCCEEDED(hr) ? S_OK : E_FAIL;
}

STDMETHODIMP CComponentDataImpl::Save(IStream *pStm, BOOL fClearDirty)
{
    DBX_PRINT(_T(" ----------  CComponentDataImpl::Save<0x08x>\n"), this);

    ASSERT(pStm);
    ASSERT(m_bInitializedCD);

    // Write the string
    ULONG nBytesWritten;
    HRESULT hr = pStm->Write("123456789", 10, &nBytesWritten);

    // Verify that the write operation succeeded
    ASSERT(SUCCEEDED(hr) && nBytesWritten == 10);
    if (FAILED(hr))
        return STG_E_CANTSAVE;

    if (fClearDirty)
        ClearDirty();
    return S_OK;
}

STDMETHODIMP CComponentDataImpl::GetSizeMax(ULARGE_INTEGER *pcbSize)
{
    ASSERT(pcbSize);

    // Set the size of the string to be saved
    ULISet32(*pcbSize, 10);

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//// Notify handlers for IComponentData

HRESULT CComponentDataImpl::OnDelete(long cookie)
{
    return S_OK;
}

HRESULT CComponentDataImpl::OnRemoveChildren(long arg)
{
    return S_OK;
}

HRESULT CComponentDataImpl::OnRename(long cookie, long arg, long param)
{
    if (arg == 0)
        return S_OK;

    LPOLESTR pszNewName = reinterpret_cast<LPOLESTR>(param);
    if (pszNewName == NULL)
        return E_INVALIDARG;

    CFolder* pFolder = reinterpret_cast<CFolder*>(cookie);
    ASSERT(pFolder != NULL);
    if (pFolder == NULL)
        return E_INVALIDARG;

    pFolder->SetName(pszNewName);

    return S_OK;
}

HRESULT CComponentDataImpl::OnExpand(LPDATAOBJECT lpDataObject, long arg, long param)
{
    if (arg == TRUE)
    {
        // Did Initialize get called?
        ASSERT(m_pScope != NULL);
        EnumerateScopePane(lpDataObject, param);
    }

    return S_OK;
}

HRESULT CComponentDataImpl::OnSelect(long cookie, long arg, long param)
{
    return E_UNEXPECTED;
}

HRESULT CComponentDataImpl::OnProperties(long param)
{
    if (param == NULL)
    {
        return S_OK;
    }

    ASSERT(param != NULL);
    CFolder* pFolder = new CFolder();

    // Create a new folder object
    pFolder->Create( reinterpret_cast<LPOLESTR>(param), 0, 0, STATIC, FALSE);

    // The static folder in the last item in the list
    POSITION pos = m_scopeItemList.GetTailPosition();
    if (pos == 0)
    {
      //  CreateFolderList();
        pos = m_scopeItemList.GetTailPosition();
    }

    ASSERT(pos);

    // Add it to the internal list
    if (pos)
    {
        CFolder* pItem = m_scopeItemList.GetAt(pos);
        m_scopeItemList.InsertBefore(pos, pFolder);

        pFolder->m_pScopeItem->relativeID = pItem->m_pScopeItem->relativeID;

        // Set the folder as the cookie
        pFolder->m_pScopeItem->mask |= SDI_PARAM;
        pFolder->m_pScopeItem->lParam = reinterpret_cast<LPARAM>(pFolder);
        pFolder->SetCookie(reinterpret_cast<long>(pFolder));
        m_pScope->InsertItem(pFolder->m_pScopeItem);
    }

    ::GlobalFree(reinterpret_cast<void*>(param));

    return S_OK;
}

void CComponentDataImpl::CreateFolderList(LPDATAOBJECT lpDataObject)
{
    CFolder* pFolder;

    ASSERT(lpDataObject != NULL);

    wchar_t* pWkStation = ExtractWorkstation(lpDataObject);
    ASSERT(pWkStation != NULL);

    CLSID* pCoClassID = ExtractClassID(lpDataObject);
    ASSERT(pCoClassID != NULL);

    // Determine which folder set to use based on context information
    FOLDER_DATA* pFolderSet = FolderData;
    BOOL bExtend = FALSE;

    if (!IsEqualCLSID(*pCoClassID, GetCoClassID()))
    {
        pFolderSet = ExtFolderData;
        bExtend = TRUE;
        TRACE(_T("Using Extension Data\n"));
    }

    ASSERT(m_scopeItemList.GetCount() == 0);
    wchar_t buf[100];

    for (int i=0; i < NUM_FOLDERS; i++)
    {
        pFolder = new CFolder();
        buf[0] = NULL;

        USES_CONVERSION;

        wcscpy(buf, pFolderSet[i].szName);

        // Add context info to the folder name
        if (bExtend)
            wcscat(buf, pWkStation);

        // Create the folder objects with static data
        pFolder->Create(buf, FOLDER_IMAGE_IDX, OPEN_FOLDER_IMAGE_IDX,
            pFolderSet[i].type, FALSE);

        m_scopeItemList.AddTail(pFolder);
    }

    // mark cookie for last item
    pFolder->SetCookie(NULL);

    // Free memory from data object extraction
    ::GlobalFree(reinterpret_cast<HGLOBAL>(pWkStation));
    ::GlobalFree(reinterpret_cast<HGLOBAL>(pCoClassID));
}

void CComponentDataImpl::EnumerateScopePane(LPDATAOBJECT lpDataObject, HSCOPEITEM pParent)
{
    int i;

    ASSERT(m_pScope != NULL); // make sure we QI'ed for the interface
    ASSERT(lpDataObject != NULL);

    INTERNAL* pInternal = ExtractInternalFormat(lpDataObject);

    if (pInternal == NULL)
        return ;

    long cookie = pInternal->m_cookie;

    // Only the static node has enumerated children
    if (cookie != NULL)
        return ;

    ::GlobalFree(reinterpret_cast<HANDLE>(pInternal));

    // Initialize folder list if empty
    if (m_scopeItemList.GetCount() == 0)
        CreateFolderList(lpDataObject);

    // Enumerate the scope pane
    // return the folder object that represents the cookie
    // Note - for large list, use dictionary
    CFolder* pStatic = FindObject(cookie);

    ASSERT(!pStatic->IsEnumerated());
    // Note - Each cookie in the scope pane represents a folder.
    // A released product may have more then one level of children.
    // This sample assumes the parent node is one level deep.

    ASSERT(pParent != NULL);

    // Cache the HSCOPEITEM of the static root.
    if (cookie == NULL)
        m_pStaticRoot = pParent;

    POSITION pos = m_scopeItemList.GetHeadPosition();
    CFolder* pFolder;

    for (i=0; (i < (NUM_FOLDERS - 1)) && (pos != NULL); i++)
    {
        pFolder = m_scopeItemList.GetNext(pos);
        ASSERT(pFolder);

        // Set the parent
        pFolder->m_pScopeItem->relativeID = pParent;

        // Set the folder as the cookie
        pFolder->m_pScopeItem->mask |= SDI_PARAM;
        pFolder->m_pScopeItem->lParam = reinterpret_cast<LPARAM>(pFolder);
        pFolder->SetCookie(reinterpret_cast<long>(pFolder));
        m_pScope->InsertItem(pFolder->m_pScopeItem);

        // Note - On return, the ID member of 'm_pScopeItem'
        // contains the handle to the newly inserted item!
        ASSERT(pFolder->m_pScopeItem->ID != NULL);
    }

    // Last folder added is the static folder
    pStatic->Set(TRUE);     // folder has been enumerated
    pStatic->m_pScopeItem->relativeID = pParent;
}

void CComponentDataImpl::DeleteList()
{
    POSITION pos = m_scopeItemList.GetHeadPosition();

    while (pos)
        delete m_scopeItemList.GetNext(pos);
}

CFolder* CComponentDataImpl::FindObject(long cookie)
{
    POSITION pos = m_scopeItemList.GetHeadPosition();
    CFolder* pFolder = NULL;

    while(pos)
    {
        pFolder = m_scopeItemList.GetNext(pos);

        if (*pFolder == cookie)
            return pFolder;
    }

    return NULL;
}

STDMETHODIMP CComponentDataImpl::GetDisplayInfo(SCOPEDATAITEM* pScopeDataItem)
{
    ASSERT(pScopeDataItem != NULL);
    if (pScopeDataItem == NULL)
        return E_POINTER;

    CFolder* pFolder = reinterpret_cast<CFolder*>(pScopeDataItem->lParam);

    ASSERT(pScopeDataItem->mask & SDI_STR);
    pScopeDataItem->displayname = pFolder->m_pszName;

    //ASSERT(pScopeDataItem->displayname != NULL);

    return S_OK;
}

STDMETHODIMP CComponentDataImpl::CompareObjects(LPDATAOBJECT lpDataObjectA, LPDATAOBJECT lpDataObjectB)
{
    if (lpDataObjectA == NULL || lpDataObjectB == NULL)
        return E_POINTER;

    // Make sure both data object are mine
    INTERNAL* pA;
    INTERNAL* pB;
    HRESULT hr = S_FALSE;

    pA = ExtractInternalFormat(lpDataObjectA);
    pB = ExtractInternalFormat(lpDataObjectA);

   if (pA != NULL && pB != NULL)
        hr = (*pA == *pB) ? S_OK : S_FALSE;

    ::GlobalFree(reinterpret_cast<HANDLE>(pA));
    ::GlobalFree(reinterpret_cast<HANDLE>(pB));

    return hr;
}

/////////////////////////////////////////////////////////////////////////////
// IExtendPropertySheet2 Implementation

HRESULT CComponentDataImpl::DoInsertWizard(LPPROPERTYSHEETCALLBACK lpProvider)
{
    CStartUpWizard* pWizard = new CStartUpWizard;
    CStartupWizard1* pWizard1 = new CStartupWizard1;

    MMCPropPageCallback(&pWizard->m_psp97);
    MMCPropPageCallback(&pWizard1->m_psp97);

    HPROPSHEETPAGE hPage = CreatePropertySheetPage(&pWizard->m_psp97);

    if (hPage == NULL)
        return E_UNEXPECTED;

    lpProvider->AddPage(hPage);

    hPage = CreatePropertySheetPage(&pWizard1->m_psp97);

    if (hPage == NULL)
        return E_UNEXPECTED;

    lpProvider->AddPage(hPage);

    return S_OK;
}

STDMETHODIMP CComponentDataImpl::GetWatermarks(HBITMAP* lphWatermark, HBITMAP* lphHeader,
                                               HPALETTE* lphPalette, BOOL* pbStretch)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    *lphHeader = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BANNER));
    *lphWatermark = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_WATERMARK));
    *pbStretch = TRUE; // force the watermark bitmap to stretch

    return S_OK;
}

STDMETHODIMP CComponentDataImpl::CreatePropertyPages(LPPROPERTYSHEETCALLBACK lpProvider,
                    long handle,
                    LPDATAOBJECT lpIDataObject)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    // Look at the data object and determine if this an extension or a primary
    ASSERT(lpIDataObject != NULL);

    // Look at the data object and see if the snap-in manager is asking for pages
    INTERNAL* pInternal= ExtractInternalFormat(lpIDataObject);

    if (pInternal != NULL)
    {
        DATA_OBJECT_TYPES type = pInternal->m_type;
        FREE_DATA(pInternal);

        if (type == CCT_SNAPIN_MANAGER)
        {
            HRESULT hr = DoInsertWizard(lpProvider);
            return hr;
        }
    }

    CLSID* pCoClassID = ExtractClassID(lpIDataObject);

    if(pCoClassID == NULL)
    {
        ASSERT(FALSE);
        return E_UNEXPECTED;
    }

    CPropertyPage* pBasePage;

    // Determine which
    // Note: Should check the node type, but the sample only has 1
    if (IsEqualCLSID(*pCoClassID, GetCoClassID()))
    {
        // Create the primary property page
        CGeneralPage* pPage = new CGeneralPage();
        pPage->m_hConsoleHandle = handle;
        pBasePage = pPage;
    }
    else
    {

        // Create the extension property page
        CExtensionPage* pPage = new CExtensionPage();
        pBasePage = pPage;

        wchar_t* pWkStation = ExtractWorkstation(lpIDataObject);

        if (pWkStation == NULL)
        {
            ASSERT(FALSE);
            return E_FAIL;
        }

        // Save the workstation name
        pPage->m_szText = pWkStation;
        FREE_DATA(pWkStation);

    }

    FREE_DATA(pCoClassID);

    // Object gets deleted when the page is destroyed
    ASSERT(lpProvider != NULL);

    HRESULT hr = MMCPropPageCallback(&pBasePage->m_psp);

    if (SUCCEEDED(hr))
    {

        HPROPSHEETPAGE hPage = CreatePropertySheetPage(&pBasePage->m_psp);

        if (hPage == NULL)
            return E_UNEXPECTED;

        lpProvider->AddPage(hPage);
    }

    return hr;
}

STDMETHODIMP CComponentDataImpl::QueryPagesFor(LPDATAOBJECT lpDataObject)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    // Get the node type and see if it's one of mine

    // if (nodetype == one of mine)
    //      do this
    // else
    //      see which node type it is and answer the question

    return S_OK;
}

BOOL CComponentDataImpl::IsScopePaneNode(LPDATAOBJECT lpDataObject)
{
    BOOL bResult = FALSE;
    INTERNAL* pInternal = ExtractInternalFormat(lpDataObject);

    if (pInternal->m_cookie == NULL &&
        (pInternal->m_type == CCT_SCOPE || pInternal->m_type == CCT_RESULT))
        bResult = TRUE;

    FREE_DATA(pInternal);

    return bResult;
}

///////////////////////////////////////////////////////////////////////////////
// IExtendContextMenu implementation
//
STDMETHODIMP CComponentDataImpl::AddMenuItems(LPDATAOBJECT pDataObject,
                                    LPCONTEXTMENUCALLBACK pContextMenuCallback,
                                    long *pInsertionAllowed)
{
    HRESULT hr = S_OK;

    // Note - snap-ins need to look at the data object and determine
    // in what context, menu items need to be added. They must also
    // observe the insertion allowed flags to see what items can be
    // added.

    if (IsMMCMultiSelectDataObject(pDataObject) == TRUE)
        return S_FALSE;

    INTERNAL* pInternal = ExtractInternalFormat(pDataObject);
    BOOL bCmd1IsDefault = (pInternal->m_type == CCT_RESULT);

    if (bCmd1IsDefault)
        menuItems[0].fSpecialFlags = CCM_SPECIAL_DEFAULT_ITEM;
    else
        menuItems[0].fSpecialFlags = 0;

    // Loop through and add each of the menu items
    if (*pInsertionAllowed & CCM_INSERTIONALLOWED_TOP)
    {
        for (LPCONTEXTMENUITEM m = menuItems; m->strName; m++)
        {
            hr = pContextMenuCallback->AddItem(m);

            if (FAILED(hr))
                break;
        }
    }

    // Loop through and add each of the view items
    if (*pInsertionAllowed & CCM_INSERTIONALLOWED_VIEW)
    {
        for (LPCONTEXTMENUITEM m = viewItems; m->strName; m++)
        {
            hr = pContextMenuCallback->AddItem(m);

            if (FAILED(hr))
                break;
        }
    }

    return hr;
}

STDMETHODIMP CComponentDataImpl::Command(long nCommandID, LPDATAOBJECT pDataObject)
{
    // Note - snap-ins need to look at the data object and determine
    // in what context the command is being called.

    // Handle each of the commands.
    switch (nCommandID)
    {
    case IDM_COMMAND1:
    {
        INTERNAL* pi = ExtractInternalFormat(pDataObject);
        ASSERT(pi);
        ASSERT(pi->m_type != CCT_RESULT);
        CFolder* pFolder = reinterpret_cast<CFolder*>(pi->m_cookie);
        if (pFolder)
            m_pConsole->SelectScopeItem(pFolder->m_pScopeItem->ID);
        break;
    }
    case IDM_COMMAND2:
        ASSERT(m_pConsole);
        m_pConsole->MessageBox(L"Snapin Menu Comand Selected",
                                    menuItems[nCommandID].strName, MB_OK, NULL);
        break;

    default:
        ASSERT(FALSE); // Unknown command!
        break;
    }

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// IExtendControlbar implementation
//

STDMETHODIMP CSnapin::SetControlbar(LPCONTROLBAR pControlbar)
{
    TRACE(_T("CSnapin::SetControlbar(%ld)\n"),pControlbar);

    // Please don't delete this. Required to make sure we pick up the bitmap
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    if (pControlbar != NULL)
    {

        // Hold on to the controlbar interface.
        if (m_pControlbar)
        {
            m_pControlbar->Release();
        }

        m_pControlbar = pControlbar;
        m_pControlbar->AddRef();

        HRESULT hr=S_FALSE;

        if (!m_pMenuButton1)
        {
            hr = m_pControlbar->Create(MENUBUTTON, this,
                                reinterpret_cast<LPUNKNOWN*>(&m_pMenuButton1));
            ASSERT(SUCCEEDED(hr));
        }

        if (m_pMenuButton1)
        {
            // Unlike toolbar buttons, menu buttons need to be added every time.
            hr = m_pMenuButton1->AddButton(FOLDEREX_MENU, L"FolderEx", L"Extended Folder Menu");
            ASSERT(SUCCEEDED(hr));
            hr = m_pMenuButton1->AddButton(FILEEX_MENU, L"FileEx", L"Extended File Menu");
            ASSERT(SUCCEEDED(hr));
        }

        // Create the Toolbar 1
        if (!m_pToolbar1)
        {
            hr = m_pControlbar->Create(TOOLBAR, this, reinterpret_cast<LPUNKNOWN*>(&m_pToolbar1));
            ASSERT(SUCCEEDED(hr));

            // Add the bitmap
            m_pbmpToolbar1 = new ::CBitmap;
            m_pbmpToolbar1->LoadBitmap(IDB_TOOLBAR1);
            hr = m_pToolbar1->AddBitmap(11, *m_pbmpToolbar1, 16, 16, RGB(255, 0, 255));
            ASSERT(SUCCEEDED(hr));

            // Add the buttons to the toolbar
            hr = m_pToolbar1->AddButtons(ARRAYLEN(SnapinButtons), SnapinButtons);
            ASSERT(SUCCEEDED(hr));

        }

        // TOOLBAR 2

            // Create the Toolbar 2
        if (!m_pToolbar2)
        {
            hr = m_pControlbar->Create(TOOLBAR, this, reinterpret_cast<LPUNKNOWN*>(&m_pToolbar2));
            ASSERT(SUCCEEDED(hr));

            // Add the bitmap
            m_pbmpToolbar2 = new ::CBitmap;
            m_pbmpToolbar2->LoadBitmap(IDB_TOOLBAR2);
            hr = m_pToolbar2->AddBitmap(36, *m_pbmpToolbar2, 16, 16, RGB(192,192,192));
            ASSERT(SUCCEEDED(hr));

            // Add the buttons to the toolbar
            hr = m_pToolbar2->AddButtons(ARRAYLEN(SnapinButtons2), SnapinButtons2);
            ASSERT(SUCCEEDED(hr));

        }

    }
    else
    {
        SAFE_RELEASE(m_pControlbar);
    }

    return S_OK;
}

void CSnapin::OnButtonClick(LPDATAOBJECT pdtobj, int idBtn)
{
    TCHAR name[128];
    GetItemName(pdtobj, name);

    TCHAR buf[200];
    wsprintf(buf, _T("Toolbar button<%d> was clicked. \nThe currently selected result item is <%s>"), idBtn, name);
    ::MessageBox(NULL, buf, _T("TRACE"), MB_OK);
}

STDMETHODIMP CSnapin::ControlbarNotify(MMC_NOTIFY_TYPE event, long arg, long param)
{
    HRESULT hr=S_FALSE;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    switch (event)
    {
    case MMCN_BTN_CLICK:
        //TCHAR szMessage[MAX_PATH];
        //wsprintf(szMessage, _T("CommandID %ld"),param);
        //AfxMessageBox(szMessage);
        OnButtonClick(reinterpret_cast<LPDATAOBJECT>(arg), param);
        break;

    case MMCN_DESELECT_ALL:
    case MMCN_SELECT:
        HandleExtToolbars((event == MMCN_DESELECT_ALL), arg, param);
        break;

    case MMCN_MENU_BTNCLICK:
        HandleExtMenus(arg, param);
        break;

    default:
        break;
    }

    return S_OK;
}

// This compares two data objects to see if they are the same object.
// return
//    S_OK if equal otherwise S_FALSE
//
// Note: check to make sure both objects belong to the snap-in.
//

STDMETHODIMP CSnapin::CompareObjects(LPDATAOBJECT lpDataObjectA, LPDATAOBJECT lpDataObjectB)
{
    return S_FALSE;
}

// This compare is used to sort the item's in the listview
//
// Parameters:
//
// lUserParam - user param passed in when IResultData::Sort() was called
// cookieA - first item to compare
// cookieB - second item to compare
// pnResult [in, out]- contains the col on entry,
//          -1, 0, 1 based on comparison for return value.
//
// Note: Assum sort is ascending when comparing.

STDMETHODIMP CSnapin::Compare(long lUserParam, long cookieA, long cookieB, int* pnResult)
{
    if (pnResult == NULL)
    {
        ASSERT(FALSE);
        return E_POINTER;
    }

    // check col range
    int nCol = *pnResult;
    ASSERT(nCol >=0 && nCol< 3);

    *pnResult = 0;

    USES_CONVERSION;

    LPTSTR szStringA;
    LPTSTR szStringB;

    RESULT_DATA* pDataA = reinterpret_cast<RESULT_DATA*>(cookieA);
    RESULT_DATA* pDataB = reinterpret_cast<RESULT_DATA*>(cookieB);

    ASSERT(pDataA != NULL && pDataB != NULL);

    if (nCol == 0)
    {
        szStringA = OLE2T(pDataA->szName);
        szStringB = OLE2T(pDataB->szName);
    }
    else if(nCol == 1)
    {
        szStringA = OLE2T(pDataA->szSize);
        szStringB = OLE2T(pDataB->szSize);
    }
    else
    {

szStringA = OLE2T(pDataA->szType);
        szStringB = OLE2T(pDataB->szType)   ;
    }

    ASSERT(szStringA != NULL);
    ASSERT(szStringB != NULL);

    *pnResult = _tcscmp(szStringA, szStringB);

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// IResultOwnerData implementation
//
STDMETHODIMP CSnapin::FindItem (LPRESULTFINDINFO pFindInfo, int* pnFoundIndex)
{
    // find next item that matches the string (exact or partial)
    // if matched found, set FoundIndex and return S_OK

    // For the sample all items are named by their index number
    // so we don't do a real string search. Also, to simplify the code
    // the routine assumes a partial match search with wrap, which is what
    // keyboard navigation calls use.
    ASSERT((pFindInfo->dwOptions & (RFI_PARTIAL | RFI_WRAP)) == (RFI_PARTIAL | RFI_WRAP));

    USES_CONVERSION;

    TCHAR* lpszFind = OLE2T(pFindInfo->psz);

    TRACE(_T("CSnapin::FindItem(\"%s\")"), lpszFind);

    // convert search string to number
    int nMatchVal = 0;
    TCHAR* pch = lpszFind;
    while (*pch >= _T('0') && *pch <= _T('9') && nMatchVal < NUM_VIRTUAL_ITEMS)
        nMatchVal = nMatchVal * 10 + (*pch++ - _T('0'));

    // if string has a non-decimal char or is too large, it won't match anything
    if (*pch != 0 || nMatchVal >= NUM_VIRTUAL_ITEMS)
        return S_FALSE;

    // if ascending sequence
    if (!(m_dwVirtualSortOptions & RSI_DESCENDING))
    {
        int nStartVal = pFindInfo->nStart;

        // if match is less than start (but not zero), locate first value above start that matches
        // otherwise the match number itself it the answer
        if (nMatchVal < nStartVal && nMatchVal != 0)
        {
             // find scale factor to reach value >= start value
            int nScale = 1;
            while (nMatchVal * nScale < nStartVal)
                nScale *= 10;

            // check special case of start value beginning with the match digits
            int nTestVal = (nStartVal * 10 - nMatchVal * nScale) < nScale ? nStartVal : nMatchVal * nScale;

            // if not too big it's the match, else the match value is the match
            if (nTestVal < NUM_VIRTUAL_ITEMS)
                nMatchVal = nTestVal;
        }
    }
    else  // descending sequence
    {
        // convert start index to start value
        int nStartVal = (NUM_VIRTUAL_ITEMS - 1) - pFindInfo->nStart;

        if (nMatchVal != 0)
        {
            // if match number > start, we will have to wrap to find a match
            // so use max index as our target
            int nTargetVal = (nMatchVal > nStartVal) ? NUM_VIRTUAL_ITEMS - 1 : nStartVal;

            // find scale factor that gets closest without going over target
            int nScale = 1;
            while (nMatchVal * nScale * 10 < nTargetVal)
                nScale *= 10;

            // check special case of target value beginning with the match digits
            nMatchVal = (nTargetVal - nMatchVal * nScale) < nScale ? nTargetVal : (nMatchVal + 1) * nScale - 1;
        }

        // convert match value back to an item index
        nMatchVal = (NUM_VIRTUAL_ITEMS - 1) - nMatchVal;
    }

    *pnFoundIndex = nMatchVal;

    return S_OK;

}

STDMETHODIMP CSnapin::CacheHint (int nStartIndex, int nEndIndex)
{
    // If advantageous, use this hint to pre-fetch the result item info that
    // is about to be requested.
    TRACE(_T("CSnapin::CacheHint(%d,%d)\n"), nStartIndex, nEndIndex);

    return S_OK;

}

STDMETHODIMP CSnapin::SortItems (int nColumn, DWORD dwSortOptions, long lUserParam)
{
    // sort request for user owned result items
    // if item order changed return S_OK, else S_FALSE

    // Sample only sorts on the first column (item name)

    if ((nColumn == 0) && (m_dwVirtualSortOptions != dwSortOptions))
    {
        m_dwVirtualSortOptions = dwSortOptions;
        return S_OK;
    }

    return S_FALSE;

}

void CSnapin::HandleStandardVerbs(bool bDeselectAll, long arg,
                                  LPDATAOBJECT lpDataObject)
{
    if (m_CustomViewID != VIEW_DEFAULT_LV)
    {
        m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, HIDDEN, FALSE);
        m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, ENABLED, TRUE);

        m_pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, HIDDEN, FALSE);
        m_pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, ENABLED, TRUE);

        return;
    }

    if (!bDeselectAll && lpDataObject == NULL)
        return;

    // You should crack the data object and enable/disable/hide standard
    // commands appropriately.  The standard commands are reset everytime you get
    // called. So you must reset them back.

    WORD bScope = LOWORD(arg);
    WORD bSelect = HIWORD(arg);

#if 0
    TCHAR buf[40];
    wsprintf(buf, _T("      %4d - CSnapin::OnSelect<%d, %d>\n"), ++n_count, bScope, bSelect);
    ODS(buf);
#else
    DBX_PRINT(_T("      %4d - CSnapin::OnSelect<%d, %d>\n"), ++n_count, bScope, bSelect);
#endif

    if (!bDeselectAll && IsMMCMultiSelectDataObject(lpDataObject) == TRUE)
    {
        m_pConsoleVerb->SetVerbState(MMC_VERB_DELETE, HIDDEN, FALSE);
        m_pConsoleVerb->SetVerbState(MMC_VERB_DELETE, ENABLED, TRUE);
        return;
    }

    INTERNAL* pInternal = lpDataObject ? ExtractInternalFormat(lpDataObject) : NULL;

    if (bDeselectAll || !bSelect)
    {
        if (bScope)
        {
            m_pConsoleVerb->SetVerbState(MMC_VERB_OPEN, ENABLED, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_COPY, ENABLED, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_PASTE, ENABLED, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_DELETE, ENABLED, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, ENABLED, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_RENAME, ENABLED, FALSE);

            m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, HIDDEN, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, ENABLED, TRUE);
        }
        else
        {
            // Result pane background
            m_pConsoleVerb->SetVerbState(MMC_VERB_PASTE, HIDDEN, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_PASTE, ENABLED, TRUE);

            if (pInternal && pInternal->m_cookie == 0)
            {
                m_pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, HIDDEN, FALSE);
                m_pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, ENABLED, TRUE);
            }

            m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, HIDDEN, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, ENABLED, TRUE);
        }

        return;
    }

    if (m_pConsoleVerb && pInternal)
    {
        if (pInternal->m_type == CCT_SCOPE)
        {
            // Standard funcitonality support by scope items
            m_pConsoleVerb->SetVerbState(MMC_VERB_OPEN, HIDDEN, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_OPEN, ENABLED, TRUE);

            m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, HIDDEN, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, ENABLED, TRUE);

            // Enable properties for static node only.
            if (pInternal->m_cookie == 0)
            {
                m_pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, ENABLED, TRUE);
                //m_pConsoleVerb->SetDefaultVerb(MMC_VERB_PROPERTIES);
                m_pConsoleVerb->SetDefaultVerb(MMC_VERB_OPEN);
            }
            else
            {
                m_pConsoleVerb->SetDefaultVerb(MMC_VERB_OPEN);
            }

            // Standard funcitonality NOT support by scope items
            m_pConsoleVerb->SetVerbState(MMC_VERB_DELETE, HIDDEN, TRUE);

        }
        else
        {
            // Standard funcitonality support by result items
            m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, HIDDEN, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, ENABLED, TRUE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_DELETE, HIDDEN, FALSE);
            m_pConsoleVerb->SetVerbState(MMC_VERB_DELETE, ENABLED, TRUE);
            m_pConsoleVerb->SetDefaultVerb(MMC_VERB_NONE);

            // Standard funcitonality NOT support by result items
        }

        m_pConsoleVerb->SetVerbState(MMC_VERB_RENAME, ENABLED, TRUE);

        // Standard funcitonality NOT support by all items
        m_pConsoleVerb->SetVerbState(MMC_VERB_COPY, HIDDEN, TRUE);
        m_pConsoleVerb->SetVerbState(MMC_VERB_PASTE, HIDDEN, TRUE);
    }
}

void EnableToolbar(LPTOOLBAR pToolbar, MMCBUTTON rgSnapinButtons[], int nRgSize,
                   BOOL bEnable)
{
    for (int i=0; i < nRgSize; ++i)
    {
        if (rgSnapinButtons[i].idCommand != 0)
            pToolbar->SetButtonState(rgSnapinButtons[i].idCommand, ENABLED,
                                     bEnable);
    }
}

void EnableMenuBtns(LPMENUBUTTON pMenuBtn, MMCBUTTON rgSnapinButtons[], int nRgSize,
                   BOOL bEnable)
{
    for (int i=0; i < nRgSize; ++i)
    {
        if (rgSnapinButtons[i].idCommand != 0)
            pMenuBtn->SetButtonState(rgSnapinButtons[i].idCommand, ENABLED,
                                     bEnable);
    }
}

void CSnapin::HandleExtToolbars(bool bDeselectAll, long arg, long param)
{
    INTERNAL* pInternal = NULL;
    HRESULT hr;

    BOOL bScope = (BOOL) LOWORD(arg);
    BOOL bSelect = (BOOL) HIWORD(arg);

#if 1
{
    if (param)
    {
        LPDATAOBJECT pDataObject = reinterpret_cast<LPDATAOBJECT>(param);
        pInternal = ExtractInternalFormat(pDataObject);
    }

    TCHAR buf[200];
    wsprintf(buf, _T("      %4d - CExtendControlbar::OnSelect<%d, %d> = %d\n"),
             ++n_count, bScope, bSelect, pInternal ? pInternal->m_cookie : 0);
    ODS(buf);
}
#else
    DBX_PRINT(_T("      %4d - CExtendControlbar::OnSelect<%d, %d>\n"), ++n_count, bScope, bSelect);
#endif

    if (bDeselectAll || bSelect == FALSE)
    {
        ASSERT(m_pToolbar1);
        EnableToolbar(m_pToolbar1, SnapinButtons,
                      ARRAYLEN(SnapinButtons), FALSE);

        ASSERT(m_pToolbar2);
        EnableToolbar(m_pToolbar2, SnapinButtons2,
                      ARRAYLEN(SnapinButtons2), FALSE);

        ASSERT(m_pMenuButton1 != NULL);
        m_pMenuButton1->SetButtonState(FOLDEREX_MENU, ENABLED, FALSE);
        m_pMenuButton1->SetButtonState(FILEEX_MENU, ENABLED, FALSE);

        return;
    }

    ASSERT(bSelect == TRUE);
    bool bFileExBtn = false;
    if (bScope == TRUE)
    {
        LPDATAOBJECT pDataObject = reinterpret_cast<LPDATAOBJECT>(param);

        pInternal = ExtractInternalFormat(pDataObject);
        if (pInternal == NULL)
            return;

        CFolder* pFolder = reinterpret_cast<CFolder*>(pInternal->m_cookie);

        if (pInternal->m_cookie == 0)
        {
            if (IsPrimaryImpl() == TRUE)
            {
                // Attach the toolbars to the window
                hr = m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar1);
                ASSERT(SUCCEEDED(hr));

                hr = m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar2);
                ASSERT(SUCCEEDED(hr));
            }
        }
        else if ((IsPrimaryImpl() == TRUE && pFolder->GetType() == COMPANY) ||
                 (IsPrimaryImpl() == FALSE && pFolder->GetType() == EXT_COMPANY))
        {
            // Detach the toolbar2 from the window
            hr = m_pControlbar->Detach((LPUNKNOWN)m_pToolbar2);
            ASSERT(SUCCEEDED(hr));

            // Attach the toolbar1 to the window
            hr = m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar1);
            ASSERT(SUCCEEDED(hr));
        }
        else if ((IsPrimaryImpl() == TRUE && pFolder->GetType() == USER) ||
                 (IsPrimaryImpl() == FALSE && pFolder->GetType() == EXT_USER))
        {
            // Detach the toolbar1 from the window
            hr = m_pControlbar->Detach((LPUNKNOWN)m_pToolbar1);
            ASSERT(SUCCEEDED(hr));

            // Attach the toolbar2 to the window
            hr = m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar2);
            ASSERT(SUCCEEDED(hr));
        }
        else
        {
            // Detach the toolbars from the window
            hr = m_pControlbar->Detach((LPUNKNOWN)m_pToolbar1);
            ASSERT(SUCCEEDED(hr));

            hr = m_pControlbar->Detach((LPUNKNOWN)m_pToolbar2);
            ASSERT(SUCCEEDED(hr));
        }

        FREE_DATA(pInternal);

        EnableToolbar(m_pToolbar1, SnapinButtons,
                      ARRAYLEN(SnapinButtons), FALSE);

        EnableToolbar(m_pToolbar2, SnapinButtons2,
                      ARRAYLEN(SnapinButtons2), FALSE);
    }
    else // result item selected.
    {
        LPDATAOBJECT pDataObject = reinterpret_cast<LPDATAOBJECT>(param);

        if (pDataObject != NULL)
            pInternal = ExtractInternalFormat(pDataObject);

        if (pInternal == NULL)
            return;

        if (pInternal->m_type == CCT_RESULT)
        {
            bFileExBtn = true;

            ASSERT(m_pToolbar1);
            EnableToolbar(m_pToolbar1, SnapinButtons,
                          ARRAYLEN(SnapinButtons), TRUE);

            m_pToolbar1->SetButtonState(1, ENABLED,       FALSE);
            m_pToolbar1->SetButtonState(2, CHECKED,       TRUE);
            m_pToolbar1->SetButtonState(3, HIDDEN,        TRUE);
            m_pToolbar1->SetButtonState(4, INDETERMINATE, TRUE);
            m_pToolbar1->SetButtonState(5, BUTTONPRESSED, TRUE);

            // Above is the correct way
            ASSERT(m_pToolbar2);
            m_pToolbar2->SetButtonState(20, CHECKED,       TRUE);
            m_pToolbar2->SetButtonState(30, HIDDEN,        TRUE);
            m_pToolbar2->SetButtonState(40, INDETERMINATE, TRUE);
            m_pToolbar2->SetButtonState(50, BUTTONPRESSED, TRUE);

            EnableToolbar(m_pToolbar2, SnapinButtons2,
                          ARRAYLEN(SnapinButtons2), TRUE);
        }
        else // sub folder slected
        {
            CFolder* pFolder = reinterpret_cast<CFolder*>(pInternal->m_cookie);

            ASSERT(m_pControlbar);

            if (pInternal->m_cookie == 0)
            {
                if (IsPrimaryImpl() == TRUE)
                {
                    // Attach the toolbars to the window
                    hr = m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar1);
                    ASSERT(SUCCEEDED(hr));

                    hr = m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar2);
                    ASSERT(SUCCEEDED(hr));
                }
            }
            else if ((IsPrimaryImpl() == TRUE && pFolder->GetType() == COMPANY) ||
                     (IsPrimaryImpl() == FALSE && pFolder->GetType() == EXT_COMPANY))
            {
                // Detach the toolbar2 from the window
                hr = m_pControlbar->Detach((LPUNKNOWN)m_pToolbar2);
                ASSERT(SUCCEEDED(hr));

                // Attach the toolbar1 to the window
                hr = m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar1);
                ASSERT(SUCCEEDED(hr));
            }
            else if ((IsPrimaryImpl() == TRUE && pFolder->GetType() == USER) ||
                     (IsPrimaryImpl() == FALSE && pFolder->GetType() == EXT_USER))
            {
                // Detach the toolbar1 from the window
                hr = m_pControlbar->Detach((LPUNKNOWN)m_pToolbar1);
                ASSERT(SUCCEEDED(hr));

                // Attach the toolbar2 to the window
                hr = m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar2);
                ASSERT(SUCCEEDED(hr));
            }
            else
            {
                // Detach the toolbars from the window
                hr = m_pControlbar->Detach((LPUNKNOWN)m_pToolbar1);
                ASSERT(SUCCEEDED(hr));

                hr = m_pControlbar->Detach((LPUNKNOWN)m_pToolbar2);
                ASSERT(SUCCEEDED(hr));
            }

            ASSERT(m_pToolbar1);
            EnableToolbar(m_pToolbar1, SnapinButtons,
                          ARRAYLEN(SnapinButtons), TRUE);

            m_pToolbar1->SetButtonState(1, ENABLED,       FALSE);
            m_pToolbar1->SetButtonState(2, CHECKED,       TRUE);
            m_pToolbar1->SetButtonState(3, ENABLED,       TRUE);
            m_pToolbar1->SetButtonState(4, INDETERMINATE, TRUE);
            m_pToolbar1->SetButtonState(5, BUTTONPRESSED, TRUE);

            ASSERT(m_pToolbar2);
            EnableToolbar(m_pToolbar2, SnapinButtons2,
                          ARRAYLEN(SnapinButtons2), TRUE);

            // Above is the correct way
            m_pToolbar2->SetButtonState(20, CHECKED,       FALSE);
            m_pToolbar2->SetButtonState(30, ENABLED,       TRUE);
            m_pToolbar2->SetButtonState(40, INDETERMINATE, FALSE);
            m_pToolbar2->SetButtonState(50, BUTTONPRESSED, TRUE);
        }
    }

    if (m_pMenuButton1)
    {
        // Always make sure the menuButton is attached
        m_pControlbar->Attach(MENUBUTTON, m_pMenuButton1);

        if (bFileExBtn)
        {
            m_pMenuButton1->SetButtonState(FILEEX_MENU, HIDDEN, FALSE);
            m_pMenuButton1->SetButtonState(FOLDEREX_MENU, HIDDEN, TRUE);
            m_pMenuButton1->SetButtonState(FILEEX_MENU, ENABLED, TRUE);
        }
        else
        {
            m_pMenuButton1->SetButtonState(FOLDEREX_MENU, HIDDEN, FALSE);
            m_pMenuButton1->SetButtonState(FILEEX_MENU, HIDDEN, TRUE);
            m_pMenuButton1->SetButtonState(FOLDEREX_MENU, ENABLED, TRUE);
        }
    }
}

void CSnapin::HandleExtMenus(long arg, long param)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    LPDATAOBJECT* ppDataObject = reinterpret_cast<LPDATAOBJECT*>(arg);
    LPMENUBUTTONDATA pMenuData = reinterpret_cast<LPMENUBUTTONDATA>(param);

    if (ppDataObject == NULL || pMenuData == NULL)
    {
        ASSERT(FALSE);
        return;
    }

    ::CMenu menu;
    ::CMenu* pMenu = NULL;

    switch (pMenuData->idCommand)
    {
    case FOLDEREX_MENU:
        menu.LoadMenu(FOLDEREX_MENU);
        pMenu = menu.GetSubMenu(0);
        break;

    case FILEEX_MENU:
        menu.LoadMenu(FILEEX_MENU);
        pMenu = menu.GetSubMenu(0);
        break;

    default:
        ASSERT(FALSE);
    }

    if (pMenu == NULL)
        return;

    pMenu->TrackPopupMenu(TPM_RETURNCMD | TPM_NONOTIFY, pMenuData->x, pMenuData->y, AfxGetMainWnd());

}

void CSnapin::GetItemName(LPDATAOBJECT pdtobj, LPTSTR pszName)
{
    ASSERT(pszName != NULL);
    pszName[0] = 0;

    INTERNAL* pInternal = ExtractInternalFormat(pdtobj);
    ASSERT(pInternal != NULL);
    if (pInternal == NULL)
        return;

    OLECHAR *pszTemp;

    if (pInternal->m_type == CCT_RESULT)
    {
        RESULT_DATA* pData;
        // if virtual, derive result item from index
        // else cookie is the item pointer
        if (m_bVirtualView)
            pData = GetVirtualResultItem(pInternal->m_cookie);
        else
            pData = reinterpret_cast<RESULT_DATA*>(pInternal->m_cookie);

        ASSERT(pData != NULL);
        pszTemp = pData->szName;
    }
    else
    {
        CFolder* pFolder = reinterpret_cast<CFolder*>(pInternal->m_cookie);
        if (pFolder == 0)
            pszTemp = L"Static folder";
        else
            pszTemp = OLE2T(pFolder->m_pszName);
    }

    USES_CONVERSION;

    lstrcpy(pszName, OLE2T(pszTemp));
}


CSNAPIN.H

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1998 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

// CSnapin.h : Declaration of the CSnapin

#include "resource.h"       // main symbols

#ifndef __mmc_h__
#include <mmc.h>
#endif

class CFolder;

// Note - This is the offset in my image list that represents the folder
const FOLDER_IMAGE_IDX = 0;
const OPEN_FOLDER_IMAGE_IDX = 5;

/////////////////////////////////////////////////////////////////////////////
// Snapin

//
// helper methods extracting data from data object
//
INTERNAL *   ExtractInternalFormat(LPDATAOBJECT lpDataObject);
wchar_t *    ExtractWorkstation(LPDATAOBJECT lpDataObject);
GUID *       ExtractNodeType(LPDATAOBJECT lpDataObject);
CLSID *      ExtractClassID(LPDATAOBJECT lpDataObject);

class CComponentDataImpl:
    public IComponentData,
    public IExtendPropertySheet,
    public IExtendContextMenu,
    public IPersistStream,
    public CComObjectRoot
{
BEGIN_COM_MAP(CComponentDataImpl)
    COM_INTERFACE_ENTRY(IComponentData)
    COM_INTERFACE_ENTRY(IExtendPropertySheet)
    COM_INTERFACE_ENTRY(IExtendContextMenu)
    COM_INTERFACE_ENTRY(IPersistStream)
END_COM_MAP()

    friend class CSnapin;
    friend class CDataObject;

    CComponentDataImpl();
    ~CComponentDataImpl();

public:
    virtual const CLSID& GetCoClassID() = 0;
    virtual const BOOL IsPrimaryImpl() = 0;

public:
// IComponentData interface members
    STDMETHOD(Initialize)(LPUNKNOWN pUnknown);
    STDMETHOD(CreateComponent)(LPCOMPONENT* ppComponent);
    STDMETHOD(Notify)(LPDATAOBJECT lpDataObject, MMC_NOTIFY_TYPE event, long arg, long param);
    STDMETHOD(Destroy)();
    STDMETHOD(QueryDataObject)(long cookie, DATA_OBJECT_TYPES type, LPDATAOBJECT* ppDataObject);
    STDMETHOD(GetDisplayInfo)(SCOPEDATAITEM* pScopeDataItem);
    STDMETHOD(CompareObjects)(LPDATAOBJECT lpDataObjectA, LPDATAOBJECT lpDataObjectB);
    STDMETHOD(DoInsertWizard)(LPPROPERTYSHEETCALLBACK lpProvider);
    STDMETHOD(GetWatermarks)(HBITMAP* lphWatermark, HBITMAP* lphHeader, HPALETTE* lphPalette, BOOL* pbStretch);

// IExtendPropertySheet interface
public:
    STDMETHOD(CreatePropertyPages)(LPPROPERTYSHEETCALLBACK lpProvider,
                        long handle,
                        LPDATAOBJECT lpIDataObject);
    STDMETHOD(QueryPagesFor)(LPDATAOBJECT lpDataObject);

// IExtendContextMenu
public:
    STDMETHOD(AddMenuItems)(LPDATAOBJECT pDataObject, LPCONTEXTMENUCALLBACK pCallbackUnknown,
                            long *pInsertionAllowed);
    STDMETHOD(Command)(long nCommandID, LPDATAOBJECT pDataObject);

public:
// IPersistStream interface members
    STDMETHOD(GetClassID)(CLSID *pClassID);
    STDMETHOD(IsDirty)();
    STDMETHOD(Load)(IStream *pStm);
    STDMETHOD(Save)(IStream *pStm, BOOL fClearDirty);
    STDMETHOD(GetSizeMax)(ULARGE_INTEGER *pcbSize);

    bool m_bInitializedCD;
    bool m_bLoadedCD;
    bool m_bDestroyedCD;

// Notify handler declarations
private:
    HRESULT OnDelete(long cookie);
    HRESULT OnRemoveChildren(long arg);
    HRESULT OnRename(long cookie, long arg, long param);
    HRESULT OnExpand(LPDATAOBJECT lpDataObject, long arg, long param);
    HRESULT OnSelect(long cookie, long arg, long param);
    HRESULT OnProperties(long param);

#if DBG==1
public:
    ULONG InternalAddRef()
    {
        return CComObjectRoot::InternalAddRef();
    }
    ULONG InternalRelease()
    {
        return CComObjectRoot::InternalRelease();
    }
#endif // DBG==1

// Scope item creation helpers
private:
    CFolder* FindObject(long cookie);
    void CreateFolderList(LPDATAOBJECT lpDataObject);            // scope item cookie helper
    void DeleteList();
    void EnumerateScopePane(LPDATAOBJECT lpDataObject, HSCOPEITEM pParent);
    BOOL IsScopePaneNode(LPDATAOBJECT lpDataObject);

private:
    LPCONSOLENAMESPACE      m_pScope;       // My interface pointer to the scope pane
    LPCONSOLE               m_pConsole;     // My interface pointer to the console
    HSCOPEITEM              m_pStaticRoot;
    BOOL                    m_bIsDirty;

    void SetDirty(BOOL b = TRUE) { m_bIsDirty = b; }
    void ClearDirty() { m_bIsDirty = FALSE; }
    BOOL ThisIsDirty() { return m_bIsDirty; }

    void AddScopeItemToResultPane(long cookie);

private:
    CList<CFolder*, CFolder*> m_scopeItemList;

#ifdef _DEBUG
    friend class CDataObject;
    int     m_cDataObjects;

#endif
};

class CComponentDataPrimaryImpl : public CComponentDataImpl,
    public CComCoClass<CComponentDataPrimaryImpl, &CLSID_Snapin>
{
public:
    DECLARE_REGISTRY(CSnapin, _T("Snapin.Snapin.1"), _T("Snapin.Snapin"), IDS_SNAPIN_DESC, THREADFLAGS_APARTMENT)
    virtual const CLSID & GetCoClassID() { return CLSID_Snapin; }
    virtual const BOOL IsPrimaryImpl() { return TRUE; }
};

class CComponentDataExtensionImpl : public CComponentDataImpl,
    public CComCoClass<CComponentDataExtensionImpl, &CLSID_Extension>
{
public:
    DECLARE_REGISTRY(CSnapin, _T("Extension.Extension.1"), _T("Extension.Extension"), IDS_SNAPIN_DESC, THREADFLAGS_APARTMENT)
    virtual const CLSID & GetCoClassID(){ return CLSID_Extension; }
    virtual const BOOL IsPrimaryImpl() { return FALSE; }
};

enum CUSTOM_VIEW_ID
{
    VIEW_DEFAULT_LV = 0,
    VIEW_CALENDAR_OCX = 1,
    VIEW_MICROSOFT_URL = 2,
};

class CSnapin :
    public IComponent,
    public IExtendContextMenu,   // Step 3
    public IExtendControlbar,
    public IResultDataCompare,
    public IResultOwnerData,
    public IPersistStream,
    public CComObjectRoot
{
public:
    CSnapin();
    ~CSnapin();

BEGIN_COM_MAP(CSnapin)
    COM_INTERFACE_ENTRY(IComponent)
    COM_INTERFACE_ENTRY(IExtendContextMenu)   // Step 3
    COM_INTERFACE_ENTRY(IExtendControlbar)
    COM_INTERFACE_ENTRY(IResultDataCompare)
    COM_INTERFACE_ENTRY(IResultOwnerData)
    COM_INTERFACE_ENTRY(IPersistStream)
END_COM_MAP()

    friend class CDataObject;
    static long lDataObjectRefCount;

// IComponent interface members
public:
    STDMETHOD(Initialize)(LPCONSOLE lpConsole);
    STDMETHOD(Notify)(LPDATAOBJECT lpDataObject, MMC_NOTIFY_TYPE event, long arg, long param);
    STDMETHOD(Destroy)(long cookie);
    STDMETHOD(GetResultViewType)(long cookie,  LPOLESTR* ppViewType, long* pViewOptions);
    STDMETHOD(QueryDataObject)(long cookie, DATA_OBJECT_TYPES type,
                        LPDATAOBJECT* ppDataObject);

    STDMETHOD(GetDisplayInfo)(RESULTDATAITEM*  pResultDataItem);
    STDMETHOD(CompareObjects)(LPDATAOBJECT lpDataObjectA, LPDATAOBJECT lpDataObjectB);

// IResultDataCompare
    STDMETHOD(Compare)(long lUserParam, long cookieA, long cookieB, int* pnResult);

// IResultOwnerData
    STDMETHOD(FindItem)(LPRESULTFINDINFO pFindInfo, int* pnFoundIndex);
    STDMETHOD(CacheHint)(int nStartIndex, int nEndIndex);
    STDMETHOD(SortItems)(int nColumn, DWORD dwSortOptions, long lUserParam);

// IExtendControlbar
    STDMETHOD(SetControlbar)(LPCONTROLBAR pControlbar);
    STDMETHOD(ControlbarNotify)(MMC_NOTIFY_TYPE event, long arg, long param);

public:
// IPersistStream interface members
    STDMETHOD(GetClassID)(CLSID *pClassID);
    STDMETHOD(IsDirty)();
    STDMETHOD(Load)(IStream *pStm);
    STDMETHOD(Save)(IStream *pStm, BOOL fClearDirty);
    STDMETHOD(GetSizeMax)(ULARGE_INTEGER *pcbSize);

    // Only for debug purpose
    bool m_bInitializedC;
    bool m_bLoadedC;
    bool m_bDestroyedC;

// Helpers for CSnapin
public:
    void SetIComponentData(CComponentDataImpl* pData);
    void GetItemName(LPDATAOBJECT lpDataObject, LPTSTR pszName);
    BOOL IsPrimaryImpl()
    {
        CComponentDataImpl* pData =
            dynamic_cast<CComponentDataImpl*>(m_pComponentData);
        ASSERT(pData != NULL);
        if (pData != NULL)
            return pData->IsPrimaryImpl();

        return FALSE;
    }

#if DBG==1
public:
    int dbg_cRef;
    ULONG InternalAddRef()
    {
        ++dbg_cRef;
        return CComObjectRoot::InternalAddRef();
    }
    ULONG InternalRelease()
    {
        --dbg_cRef;
        return CComObjectRoot::InternalRelease();
    }
#endif // DBG==1

// Notify event handlers
protected:
    HRESULT OnFolder(long cookie, long arg, long param);
    HRESULT OnAddImages(long cookie, long arg, long param);
    HRESULT OnShow(long cookie, long arg, long param);
    HRESULT OnActivate(long cookie, long arg, long param);
    HRESULT OnMinimize(long cookie, long arg, long param);
    HRESULT OnPropertyChange(LPDATAOBJECT lpDataObject); // Step 3
    HRESULT OnUpdateView(LPDATAOBJECT lpDataObject);
    HRESULT OnResultItemClk(DATA_OBJECT_TYPES type, long cookie);
    HRESULT OnContextHelp(LPDATAOBJECT lpDataObject);
    void OnButtonClick(LPDATAOBJECT pdtobj, int idBtn);

    HRESULT QueryMultiSelectDataObject(long cookie, DATA_OBJECT_TYPES type,
                                   LPDATAOBJECT* ppDataObject);

// IExtendContextMenu
public:
    STDMETHOD(AddMenuItems)(LPDATAOBJECT pDataObject, LPCONTEXTMENUCALLBACK pCallbackUnknown,
                            long *pInsertionAllowed);
    STDMETHOD(Command)(long nCommandID, LPDATAOBJECT pDataObject);

// End step 3

// Helper functions
protected:
    BOOL IsEnumerating(LPDATAOBJECT lpDataObject);
    void Construct();
    void LoadResources();
    HRESULT InitializeHeaders(long cookie);

    void Enumerate(long cookie, HSCOPEITEM pParent);
    void EnumerateResultPane(long cookie);

// Result pane helpers
    void AddResultItems(RESULT_DATA* pData, int nCount, int imageIndex);
    void AddUser();
    void AddCompany();
    void AddExtUser();
    void AddExtCompany();
    void AddVirtual();
    RESULT_DATA* GetVirtualResultItem(int iIndex);

    HRESULT InitializeBitmaps(long cookie);

// UI Helpers
    void HandleStandardVerbs(bool bDeselectAll, long arg, LPDATAOBJECT lpDataObject);
    void HandleExtToolbars(bool bDeselectAll, long arg, long param);
void HandleExtMenus(long arg, long param);
    void _OnRefresh(LPDATAOBJECT pDataObject);

// Interface pointers
protected:
    LPCONSOLE           m_pConsole;   // Console's IFrame interface
    LPHEADERCTRL        m_pHeader;  // Result pane's header control interface
    LPCOMPONENTDATA     m_pComponentData;
    LPRESULTDATA        m_pResult;      // My interface pointer to the result pane
    LPIMAGELIST         m_pImageResult; // My interface pointer to the result pane image list
    LPTOOLBAR           m_pToolbar1;    // Toolbar for view
    LPTOOLBAR           m_pToolbar2;    // Toolbar for view
    LPCONTROLBAR        m_pControlbar;  // control bar to hold my tool bars
    LPCONSOLEVERB       m_pConsoleVerb; // pointer the console verb
    LPMENUBUTTON        m_pMenuButton1; // Menu Button for view

    ::CBitmap*    m_pbmpToolbar1;     // Imagelist for the first toolbar
    ::CBitmap*    m_pbmpToolbar2;     // Imagelist for the first toolbar

// Header titles for each nodetype(s)
protected:
    CString m_column1;      // Name
    CString m_column2;      // Size
    CString m_column3;      // Type

private:
    BOOL            m_bIsDirty;
    CUSTOM_VIEW_ID  m_CustomViewID;
    BOOL            m_bVirtualView;
    DWORD           m_dwVirtualSortOptions;

    void SetDirty(BOOL b = TRUE) { m_bIsDirty = b; }
    void ClearDirty() { m_bIsDirty = FALSE; }
    BOOL ThisIsDirty() { return m_bIsDirty; }
};

inline void CSnapin::SetIComponentData(CComponentDataImpl* pData)
{
    ASSERT(pData);
    ASSERT(m_pComponentData == NULL);
    LPUNKNOWN pUnk = pData->GetUnknown();
    HRESULT hr;

    hr = pUnk->QueryInterface(IID_IComponentData, reinterpret_cast<void**>(&m_pComponentData));

    ASSERT(hr == S_OK);
}

class CSnapinAboutImpl :
    public ISnapinAbout,
    public CComObjectRoot,
    public CComCoClass<CSnapinAboutImpl, &CLSID_About>
{
public:
    CSnapinAboutImpl();
    ~CSnapinAboutImpl();

public:
DECLARE_REGISTRY(CSnapin, _T("Snapin.About.1"), _T("Snapin.About"), IDS_SNAPIN_DESC, THREADFLAGS_BOTH)

BEGIN_COM_MAP(CSnapinAboutImpl)
    COM_INTERFACE_ENTRY(ISnapinAbout)
END_COM_MAP()

public:
    STDMETHOD(GetSnapinDescription)(LPOLESTR* lpDescription);
    STDMETHOD(GetProvider)(LPOLESTR* lpName);
    STDMETHOD(GetSnapinVersion)(LPOLESTR* lpVersion);
    STDMETHOD(GetSnapinImage)(HICON* hAppIcon);
    STDMETHOD(GetStaticFolderImage)(HBITMAP* hSmallImage,
                                    HBITMAP* hSmallImageOpen,
                                    HBITMAP* hLargeImage,
                                    COLORREF* cLargeMask);

// Internal functions
private:
    HRESULT AboutHelper(UINT nID, LPOLESTR* lpPtr);
};

#define FREE_DATA(pData) \
    ASSERT(pData != NULL); \
    do { if (pData != NULL) \
        GlobalFree(pData); } \
    while(0);

#define ARRAYLEN(x) (sizeof(x) / sizeof((x)[0]))


DATAOBJ.CPP

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

#include "stdafx.h"
#include "Service.h"
#include "CSnapin.h"
#include "DataObj.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////////
// Sample code to show how to Create DataObjects
// Minimal error checking for clarity

///////////////////////////////////////////////////////////////////////////////
// Snap-in NodeType in both GUID format and string format
// Note - Typically there is a node type for each different object, sample
// only uses one node type.

unsigned int CDataObject::m_cfNodeType       = 0;
unsigned int CDataObject::m_cfNodeTypeString = 0;
unsigned int CDataObject::m_cfDisplayName    = 0;
unsigned int CDataObject::m_cfCoClass        = 0;

unsigned int CDataObject::m_cfInternal       = 0;
unsigned int CDataObject::m_cfMultiSel       = 0;

// Extension information
// The only additional clipboard format supported is to get the workstation name.
unsigned int CDataObject::m_cfWorkstation    = 0;

/////////////////////////////////////////////////////////////////////////////
// CDataObject implementations

CDataObject::CDataObject()
{
USES_CONVERSION;

m_cfNodeType       = RegisterClipboardFormat(W2T(CCF_NODETYPE));
m_cfNodeTypeString = RegisterClipboardFormat(W2T(CCF_SZNODETYPE));
m_cfDisplayName    = RegisterClipboardFormat(W2T(CCF_DISPLAY_NAME));
m_cfCoClass        = RegisterClipboardFormat(W2T(CCF_SNAPIN_CLASSID));
    m_cfMultiSel       = RegisterClipboardFormat(W2T(CCF_OBJECT_TYPES_IN_MULTI_SELECT));

#ifdef UNICODE
m_cfInternal       = RegisterClipboardFormat(W2T((LPTSTR)SNAPIN_INTERNAL));
m_cfWorkstation    = RegisterClipboardFormat(W2T((LPTSTR)SNAPIN_WORKSTATION));
#else
m_cfInternal       = RegisterClipboardFormat(W2T(SNAPIN_INTERNAL));
m_cfWorkstation    = RegisterClipboardFormat(W2T(SNAPIN_WORKSTATION));
#endif //UNICODE

    #ifdef _DEBUG
        m_ComponentData = NULL;
        dbg_refCount = 0;
    #endif

    m_pbMultiSelData = 0;
    m_cbMultiSelData = 0;
    m_bMultiSelDobj = FALSE;
}

STDMETHODIMP CDataObject::GetData(LPFORMATETC lpFormatetc, LPSTGMEDIUM lpMedium)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    HRESULT hr = DV_E_CLIPFORMAT;

    if (lpFormatetc->cfFormat == m_cfMultiSel)
    {
        ASSERT(m_internal.m_cookie == MMC_MULTI_SELECT_COOKIE);
        if (m_internal.m_cookie != MMC_MULTI_SELECT_COOKIE)
            return E_FAIL;

        //return CreateMultiSelData(lpMedium);

        ASSERT(m_pbMultiSelData != 0);
        ASSERT(m_cbMultiSelData != 0);

        lpMedium->tymed = TYMED_HGLOBAL;
        lpMedium->hGlobal = ::GlobalAlloc(GMEM_SHARE|GMEM_MOVEABLE,
                                          (m_cbMultiSelData + sizeof(DWORD)));
        if (lpMedium->hGlobal == NULL)
            return STG_E_MEDIUMFULL;

        BYTE* pb = reinterpret_cast<BYTE*>(::GlobalLock(lpMedium->hGlobal));
        *((DWORD*)pb) = m_cbMultiSelData / sizeof(GUID);
        pb += sizeof(DWORD);
        CopyMemory(pb, m_pbMultiSelData, m_cbMultiSelData);

        ::GlobalUnlock(lpMedium->hGlobal);

        hr = S_OK;
    }

    return hr;
}

STDMETHODIMP CDataObject::GetDataHere(LPFORMATETC lpFormatetc, LPSTGMEDIUM lpMedium)
{
    HRESULT hr = DV_E_CLIPFORMAT;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    // Based on the CLIPFORMAT write data to the stream
    const CLIPFORMAT cf = lpFormatetc->cfFormat;

    if (cf == m_cfNodeType)
    {
        hr = CreateNodeTypeData(lpMedium);
    }
    else if (cf == m_cfCoClass)
    {
        hr = CreateCoClassID(lpMedium);
    }
    else if(cf == m_cfNodeTypeString)
    {
        hr = CreateNodeTypeStringData(lpMedium);
    }
    else if (cf == m_cfDisplayName)
    {
        hr = CreateDisplayName(lpMedium);
    }
    else if (cf == m_cfInternal)
    {
        hr = CreateInternal(lpMedium);
    }
    else if (cf == m_cfWorkstation)
    {
        hr = CreateWorkstationName(lpMedium);
    }

    return hr;
}

STDMETHODIMP CDataObject::EnumFormatEtc(DWORD dwDirection, LPENUMFORMATETC* ppEnumFormatEtc)
{
    return E_NOTIMPL;
}

/////////////////////////////////////////////////////////////////////////////
// CDataObject creation members

HRESULT CDataObject::Create(const void* pBuffer, int len, LPSTGMEDIUM lpMedium)
{
    HRESULT hr = DV_E_TYMED;

    // Do some simple validation
    if (pBuffer == NULL || lpMedium == NULL)
        return E_POINTER;

    // Make sure the type medium is HGLOBAL
    if (lpMedium->tymed == TYMED_HGLOBAL)
    {
        // Create the stream on the hGlobal passed in
        LPSTREAM lpStream;
        hr = CreateStreamOnHGlobal(lpMedium->hGlobal, FALSE, &lpStream);

        if (SUCCEEDED(hr))
        {
            // Write to the stream the number of bytes
            unsigned long written;
            hr = lpStream->Write(pBuffer, len, &written);

            // Because we told CreateStreamOnHGlobal with 'FALSE',
            // only the stream is released here.
            // Note - the caller (i.e. snap-in, object) will free the HGLOBAL
            // at the correct time.  This is according to the IDataObject specification.
            lpStream->Release();
        }
    }

    return hr;
}

HRESULT CDataObject::CreateMultiSelData(LPSTGMEDIUM lpMedium)
{
    ASSERT(m_internal.m_cookie == MMC_MULTI_SELECT_COOKIE);

    ASSERT(m_pbMultiSelData != 0);
    ASSERT(m_cbMultiSelData != 0);

    return Create(reinterpret_cast<const void*>(m_pbMultiSelData),
                  m_cbMultiSelData, lpMedium);
}

HRESULT CDataObject::CreateNodeTypeData(LPSTGMEDIUM lpMedium)
{
    // Create the node type object in GUID format
    const GUID* pcObjectType = NULL;

    if (m_internal.m_cookie == NULL)
    {
        pcObjectType = &cNodeTypeStatic;
    }
    else if (m_internal.m_type == CCT_SCOPE)
    {
        CFolder* pFolder = reinterpret_cast<CFolder*>(m_internal.m_cookie);
        ASSERT(pFolder != NULL);
        if (pFolder == NULL)
            return E_UNEXPECTED;

        switch (pFolder->GetType())
        {
        case COMPANY:
            pcObjectType = &cNodeTypeCompany;
            break;

        case USER:
            pcObjectType = &cNodeTypeUser;
            break;

        case EXT_COMPANY:
            pcObjectType = &cNodeTypeExtCompany;
            break;

        case EXT_USER:
            pcObjectType = &cNodeTypeExtUser;
            break;

        case VIRTUAL:
        case EXT_VIRTUAL:
            pcObjectType = &cNodeTypeExtUser;
            break;

        default:
            pcObjectType = &cNodeTypeDynamic;
            break;
        }
    }
    else if (m_internal.m_type == CCT_RESULT)
    {
        // RESULT_DATA* pData = reinterpret_cast<RESULT_DATA*>(m_internal.m_cookie);

        pcObjectType = &cObjectTypeResultItem;
    }

    return Create(reinterpret_cast<const void*>(pcObjectType), sizeof(GUID),
                  lpMedium);
}

HRESULT CDataObject::CreateNodeTypeStringData(LPSTGMEDIUM lpMedium)
{
    // Create the node type object in GUID string format
    const WCHAR* cszObjectType = NULL;

    if (m_internal.m_cookie == NULL)
    {
        cszObjectType = cszNodeTypeStatic;
    }
    else if (m_internal.m_type == CCT_SCOPE)
    {
        CFolder* pFolder = reinterpret_cast<CFolder*>(m_internal.m_cookie);
        ASSERT(pFolder != NULL);
        if (pFolder == NULL)
            return E_UNEXPECTED;

        switch (pFolder->GetType())
        {
        case COMPANY:
            cszObjectType = cszNodeTypeCompany;
            break;

        case USER:
            cszObjectType = cszNodeTypeUser;
            break;

        case EXT_COMPANY:
            cszObjectType = cszNodeTypeExtCompany;
            break;

        case EXT_USER:
            cszObjectType = cszNodeTypeExtUser;
            break;

        default:
            cszObjectType = cszNodeTypeDynamic;
            break;
        }
    }
    else if (m_internal.m_type == CCT_RESULT)
    {
        // RESULT_DATA* pData = reinterpret_cast<RESULT_DATA*>(m_internal.m_cookie);

        cszObjectType = cszObjectTypeResultItem;
    }
    return Create(cszObjectType, ((wcslen(cszObjectType)+1) * sizeof(WCHAR)), lpMedium);
}

HRESULT CDataObject::CreateDisplayName(LPSTGMEDIUM lpMedium)
{
    // This is the display named used in the scope pane and snap-in manager

    // Load the name from resource
    // Note - if this is not provided, the console will used the snap-in name

    CString szDispName;
    szDispName.LoadString(IDS_NODENAME);

USES_CONVERSION;

#ifdef UNICODE
return Create(szDispName, ((szDispName.GetLength()+1) * sizeof(WCHAR)), lpMedium);
#else
return Create(T2W(szDispName), ((szDispName.GetLength()+1) * sizeof(WCHAR)), lpMedium);
#endif
}

HRESULT CDataObject::CreateInternal(LPSTGMEDIUM lpMedium)
{
    return Create(&m_internal, sizeof(INTERNAL), lpMedium);
}

HRESULT CDataObject::CreateWorkstationName(LPSTGMEDIUM lpMedium)
{
    TCHAR pzName[MAX_COMPUTERNAME_LENGTH+1] = {0};
    DWORD len = MAX_COMPUTERNAME_LENGTH+1;

    if (GetComputerName(pzName, &len) == FALSE)
        return E_FAIL;

    // Add 1 for the NULL and calculate the bytes for the stream
//#ifdef UNICODE
USES_CONVERSION;
    return Create(T2W(pzName), ((len+1)* sizeof(WCHAR)), lpMedium);
//#else
//    return Create(pzName, ((len+1)* sizeof(WCHAR)), lpMedium);
//#endif

}

HRESULT CDataObject::CreateCoClassID(LPSTGMEDIUM lpMedium)
{
    // Create the CoClass information
    return Create(reinterpret_cast<const void*>(&m_internal.m_clsid), sizeof(CLSID), lpMedium);
}


DATAOBJ.H

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

#ifndef _DATAOBJ_H
#define _DATAOBJ_H

class CDataObject : public IDataObject, public CComObjectRoot
{
    friend class CSnapin;

// ATL Maps
DECLARE_NOT_AGGREGATABLE(CDataObject)
BEGIN_COM_MAP(CDataObject)
COM_INTERFACE_ENTRY(IDataObject)
END_COM_MAP()

// Construction/Destruction
    CDataObject();
    ~CDataObject()
    {
    #ifdef _DEBUG
        m_ComponentData = NULL;
    #endif

        delete [] m_pbMultiSelData;
    };

// Clipboard formats that are required by the console
public:
    static unsigned int    m_cfNodeType;        // Required by the console
    static unsigned int    m_cfNodeTypeString;  // Required by the console
    static unsigned int    m_cfDisplayName;     // Required by the console
    static unsigned int    m_cfCoClass;         // Required by the console
    static unsigned int    m_cfMultiSel;        // Required by the console

    static unsigned int    m_cfInternal;        // Step 3
static unsigned int   m_cfWorkstation;     // Published information

// Standard IDataObject methods
public:
// Implemented
    STDMETHOD(GetData)(LPFORMATETC lpFormatetcIn, LPSTGMEDIUM lpMedium);
    STDMETHOD(GetDataHere)(LPFORMATETC lpFormatetc, LPSTGMEDIUM lpMedium);
    STDMETHOD(EnumFormatEtc)(DWORD dwDirection, LPENUMFORMATETC* ppEnumFormatEtc);

    ULONG InternalAddRef()
    {
#ifdef _DEBUG
        ASSERT(m_ComponentData != NULL);
        ++(m_ComponentData->m_cDataObjects);
        if (m_bMultiSelDobj == TRUE)
            AddRefMultiSelDobj();
#endif
        return CComObjectRoot::InternalAddRef();
    }
    ULONG InternalRelease()
    {
#ifdef _DEBUG
        ASSERT(m_ComponentData != NULL);
        --(m_ComponentData->m_cDataObjects);
        if (m_bMultiSelDobj == TRUE)
            ReleaseMultiSelDobj();
#endif
        return CComObjectRoot::InternalRelease();
    }

// Not Implemented
private:
    STDMETHOD(QueryGetData)(LPFORMATETC lpFormatetc)
    { return E_NOTIMPL; };

    STDMETHOD(GetCanonicalFormatEtc)(LPFORMATETC lpFormatetcIn, LPFORMATETC lpFormatetcOut)
    { return E_NOTIMPL; };

    STDMETHOD(SetData)(LPFORMATETC lpFormatetc, LPSTGMEDIUM lpMedium, BOOL bRelease)
    { return E_NOTIMPL; };

    STDMETHOD(DAdvise)(LPFORMATETC lpFormatetc, DWORD advf,
                LPADVISESINK pAdvSink, LPDWORD pdwConnection)
    { return E_NOTIMPL; };

    STDMETHOD(DUnadvise)(DWORD dwConnection)
    { return E_NOTIMPL; };

    STDMETHOD(EnumDAdvise)(LPENUMSTATDATA* ppEnumAdvise)
    { return E_NOTIMPL; };

// Implementation
public:
    void SetType(DATA_OBJECT_TYPES type) // Step 3
    { ASSERT(m_internal.m_type == CCT_UNINITIALIZED); m_internal.m_type = type; }

    // This is used only as a diagnostic in debug builds to track if
    // anyone is hanging on to any data objects that's have been handed out
    // Snapin's should view context data objects as ephemeral.
#ifdef _DEBUG
public:
    void SetComponentData(CComponentDataImpl* pCCD)
    {
        ASSERT(m_ComponentData == NULL && pCCD != NULL); m_ComponentData = pCCD;
    } ;
private:
    CComponentDataImpl* m_ComponentData;
#endif

public:
    void SetCookie(long cookie) { m_internal.m_cookie = cookie; } // Step 3
    void SetString(LPTSTR lpString) { m_internal.m_string = lpString; }
    void SetClsid(const CLSID& clsid) { m_internal.m_clsid = clsid; }

    void SetMultiSelData(BYTE* pbMultiSelData, UINT cbMultiSelData)
    {
        m_pbMultiSelData = pbMultiSelData;
        m_cbMultiSelData = cbMultiSelData;
    }

    void SetMultiSelDobj()
    {
        m_bMultiSelDobj = TRUE;
    }

#ifdef _DEBUG
    UINT dbg_refCount;

    void AddRefMultiSelDobj()
    {
        ASSERT(m_bMultiSelDobj == TRUE);
        ++dbg_refCount;
    }

    void ReleaseMultiSelDobj()
    {
        ASSERT(m_bMultiSelDobj == TRUE);
        --dbg_refCount;
        //if (dbg_refCount == 0)
        //    ::MessageBox(NULL, _T("Final release on multi-sel-dobj"), _T("Sample snapin"), MB_OK);
    }
#endif

private:
    HRESULT CreateNodeTypeData(LPSTGMEDIUM lpMedium);
    HRESULT CreateNodeTypeStringData(LPSTGMEDIUM lpMedium);
    HRESULT CreateDisplayName(LPSTGMEDIUM lpMedium);
    HRESULT CreateInternal(LPSTGMEDIUM lpMedium); // Step 3
    HRESULT CreateWorkstationName(LPSTGMEDIUM lpMedium);
    HRESULT CreateCoClassID(LPSTGMEDIUM lpMedium);
    HRESULT CreateMultiSelData(LPSTGMEDIUM lpMedium);

    HRESULT Create(const void* pBuffer, int len, LPSTGMEDIUM lpMedium);

    INTERNAL m_internal;    // Step 3

    BYTE* m_pbMultiSelData;
    UINT m_cbMultiSelData;
    BOOL m_bMultiSelDobj;
};

#endif


EVENTS.CPP

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

#include "stdafx.h"
#include "Service.h"
#include "CSnapin.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// Primary Data
RESULT_DATA StaticRootData[NUM_NAMES] =
{
    {RESULT_ITEM, USER, L"Bill", L"990", L"CEO"},
    {RESULT_ITEM, USER, L"Jill", L"991", L"Director"},
    {RESULT_ITEM, USER, L"Hill", L"992", L"President"},
    {RESULT_ITEM, USER, L"Will", L"993", L"Chairman"},
};

RESULT_DATA UserData[NUM_NAMES] =
{
    {RESULT_ITEM, USER, L"Joe", L"100", L"Employee"},
    {RESULT_ITEM, USER, L"Harry", L"101", L"Manager"},
    {RESULT_ITEM, USER, L"Jane", L"102", L"Vice President"},
    {RESULT_ITEM, USER, L"Sue", L"103", L"Developer"},
};

RESULT_DATA CompanyData[NUM_COMPANY] =
{
    {RESULT_ITEM, COMPANY, L"Taxes", L"43", L"IRS Documents"},
    {RESULT_ITEM, COMPANY, L"Medical", L"345", L"HMO"},
    {RESULT_ITEM, COMPANY, L"Dental", L"121", L"Plus Plan"},
    {RESULT_ITEM, COMPANY, L"Insurance", L"2332", L"Dollars are Us"},
    {RESULT_ITEM, COMPANY, L"401K", L"1000", L"Up to you"},
    {RESULT_ITEM, COMPANY, L"Legal", L"78", L"Yes"}
};

// Extension data
RESULT_DATA UserDataExt[NUM_NAMES] =
{
    {RESULT_ITEM, EXT_USER, L"Charles", L"200", L"Employee"},
    {RESULT_ITEM, EXT_USER, L"Jill", L"201", L"Manager"},
    {RESULT_ITEM, EXT_USER, L"John", L"202", L"Vice President"},
    {RESULT_ITEM, EXT_USER, L"Tami", L"203", L"Developer"},
};

RESULT_DATA CompanyDataExt[NUM_COMPANY] =
{
    {RESULT_ITEM, EXT_COMPANY, L"Payroll", L"99", L"Corporate Payroll"},
    {RESULT_ITEM, EXT_COMPANY, L"Health", L"568", L"HMO"},
    {RESULT_ITEM, EXT_COMPANY, L"Health Club", L"834", L"Plus Plan"},
    {RESULT_ITEM, EXT_COMPANY, L"Insurance", L"1101", L"Dollars are Us"},
    {RESULT_ITEM, EXT_COMPANY, L"401K", L"1543", L"Up to you"},
    {RESULT_ITEM, EXT_COMPANY, L"Legal", L"27", L"Yes"}
};

// We use a single structure for all virtual items, so
// the name field points to this buffer that is changed
// on the fly.
WCHAR VirtualItemName[MAX_ITEM_NAME];

RESULT_DATA VirtualData[1] =
{
    {RESULT_ITEM, VIRTUAL, VirtualItemName, L"100", L"Virtual"}
};

/////////////////////////////////////////////////////////////////////////////
// Event handlers for IFrame::Notify

HRESULT CSnapin::OnFolder(long cookie, long arg, long param)
{
    ASSERT(FALSE);

    return S_OK;
}

HRESULT CSnapin::OnAddImages(long cookie, long arg, long param)
{
    if (arg == 0)
        return E_INVALIDARG;

    // if cookie is from a different snapin
    // if (IsMyCookie(cookie) == FALSE)
    if (0)
    {
        // add the images for the scope tree only
        ::CBitmap bmp16x16;
        ::CBitmap bmp32x32;
        LPIMAGELIST lpImageList = reinterpret_cast<LPIMAGELIST>(arg);

        // Load the bitmaps from the dll
        bmp16x16.LoadBitmap(IDB_16x16);
        bmp32x32.LoadBitmap(IDB_32x32);

        // Set the images
        lpImageList->ImageListSetStrip(
                        reinterpret_cast<long*>(static_cast<HBITMAP>(bmp16x16)),
                        reinterpret_cast<long*>(static_cast<HBITMAP>(bmp32x32)),
                        0, RGB(255, 0, 255));

        lpImageList->Release();
    }
    else
    {
        InitializeBitmaps(cookie);
    }
    return S_OK;
}

HRESULT CSnapin::OnShow(long cookie, long arg, long param)
{
    // Note - arg is TRUE when it is time to enumerate
    if (arg == TRUE)
    {
        // if list view on display
        if (m_CustomViewID == VIEW_DEFAULT_LV)
        {
            // Show the headers for this nodetype
            InitializeHeaders(cookie);
            Enumerate(cookie, param);
        }
        // BUBBUG - Demonstration to should how you can attach
        // and a toolbar when a particular nodes gets focus.
        // warning this needs to be here as the toolbars are
        // currently hidden when the previous node looses focus.
        // This should be update to show the user how to hide
        // and show toolbars. (Detach and Attach).

        //m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar1);
        //m_pControlbar->Attach(TOOLBAR, (LPUNKNOWN) m_pToolbar2);
    }
    else
    {

        // BUGBUG - Demonstration this to show how to hide toolbars that
        // could be particular to a single node.
        // currently this is used to hide the toolbars the console
        // does not do any toolbar clean up.

        //m_pControlbar->Detach(m_pToolbar1);
        //m_pControlbar->Detach(m_pToolbar2);
        // Free data associated with the result pane items, because
        // your node is no longer being displayed.
        // Note: The console will remove the items from the result pane
    }

    return S_OK;
}

HRESULT CSnapin::OnActivate(long cookie, long arg, long param)
{
    return S_OK;
}

HRESULT CSnapin::OnResultItemClk(DATA_OBJECT_TYPES type, long cookie)
{
    RESULT_DATA* pResult;

    if (m_bVirtualView == TRUE)
    {
        pResult = GetVirtualResultItem(cookie);
    }
    else if (cookie != 0)
    {
        DWORD* pdw = reinterpret_cast<DWORD*>(cookie);
        if (*pdw == RESULT_ITEM)
        {
            pResult = reinterpret_cast<RESULT_DATA*>(cookie);
        }
    }

    return S_OK;
}

HRESULT CSnapin::OnMinimize(long cookie, long arg, long param)
{
    return S_OK;
}

HRESULT CSnapin::OnPropertyChange(LPDATAOBJECT lpDataObject)
{

    return S_OK;
}

HRESULT CSnapin::OnUpdateView(LPDATAOBJECT lpDataObject)
{
    return S_OK;
}

void CSnapin::Enumerate(long cookie, HSCOPEITEM pParent)
{
    EnumerateResultPane(cookie);
}

void CSnapin::EnumerateResultPane(long cookie)
{
    ASSERT(m_pResult != NULL); // make sure we QI'ed for the interface
    ASSERT(m_pComponentData != NULL);

    // Our static folders must be displayed in the result pane
    // by use because the console doesn't do it.
    CFolder* pFolder = dynamic_cast<CComponentDataImpl*>(m_pComponentData)->FindObject(cookie);

    // The dynamic folder must be in our list
    ASSERT(pFolder != NULL);

    FOLDER_TYPES type = pFolder->GetType();

    switch(type)
    {
    case STATIC:
        AddResultItems(&StaticRootData[0], NUM_NAMES, 4);
        break;

    case COMPANY:
        AddCompany();
        break;

    case USER:
        AddUser();
        break;

    case EXT_USER:
        AddExtUser();
        break;

    case EXT_COMPANY:
        AddExtCompany();
        break;

    case VIRTUAL:
    case EXT_VIRTUAL:
        AddVirtual();
        break;

    default:
        break;
    }
}

void CSnapin::AddResultItems(RESULT_DATA* pData, int nCount, int imageIndex)
{
    ASSERT(m_pResult);

    RESULTDATAITEM resultItem;
    memset(&resultItem, 0, sizeof(RESULTDATAITEM));

    for (int i=0; i < nCount; i++)
    {
        resultItem.mask = RDI_STR | RDI_IMAGE | RDI_PARAM;
        resultItem.str = MMC_CALLBACK;
        resultItem.nImage = imageIndex;
        resultItem.lParam = reinterpret_cast<LPARAM>(pData + i);
        m_pResult->InsertItem(&resultItem);
    }

    m_pResult->Sort(0,0,-1);

}

void CSnapin::AddUser()
{
    AddResultItems(&UserData[0], NUM_NAMES, 4);
}

void CSnapin::AddCompany()
{
    AddResultItems(&CompanyData[0], NUM_COMPANY, 3);
}

void CSnapin::AddExtUser()
{
    AddResultItems(&UserDataExt[0], NUM_NAMES, 4);
}

void CSnapin::AddExtCompany()
{
    AddResultItems(&CompanyDataExt[0], NUM_COMPANY, 3);
}

void CSnapin::AddVirtual()
{
    // for virtual data, just set the item count
    m_pResult->SetItemCount(NUM_VIRTUAL_ITEMS, MMCLV_UPDATE_NOINVALIDATEALL);
}

RESULT_DATA* CSnapin::GetVirtualResultItem(int iIndex)
{
    // reverse order for descending sort
    if (m_dwVirtualSortOptions & RSI_DESCENDING)
        iIndex = (NUM_VIRTUAL_ITEMS - 1) - iIndex;

    // Create a name from the index
    swprintf(VirtualItemName, L"%d", iIndex);

    // return pointer to the virtual result item
    return &VirtualData[0];

}


GENPAGE.CPP

// genpage.cpp : implementation file
//

#include "stdafx.h"
#include "Service.h"
#include "csnapin.h"
#include "resource.h"
#include "afxdlgs.h"
#include "genpage.h"
#include "dataobj.h"
#include "prsht.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CGeneralPage property page

IMPLEMENT_DYNCREATE(CGeneralPage, CPropertyPage)

CGeneralPage::CGeneralPage() : CPropertyPage(CGeneralPage::IDD)
{

//{{AFX_DATA_INIT(CGeneralPage)
m_szName = _T("");
//}}AFX_DATA_INIT

    m_hConsoleHandle = NULL;
    m_bUpdate = FALSE;

}

CGeneralPage::~CGeneralPage()
{
}

void CGeneralPage::DoDataExchange(CDataExchange* pDX)
{
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CGeneralPage)
DDX_Control(pDX, IDC_NEW_FOLDER, m_EditCtrl);
DDX_Text(pDX, IDC_NEW_FOLDER, m_szName);
DDV_MaxChars(pDX, m_szName, 64);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CGeneralPage, CPropertyPage)
//{{AFX_MSG_MAP(CGeneralPage)
ON_WM_DESTROY()
ON_EN_CHANGE(IDC_NEW_FOLDER, OnEditChange)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CGeneralPage message handlers

void CGeneralPage::OnDestroy()
{
    // Note - This needs to be called only once.
    // If called more than once, it will gracefully return an error.
    MMCFreeNotifyHandle(m_hConsoleHandle);

    CPropertyPage::OnDestroy();

    // Delete the CGeneralPage object
    delete this;
}

void CGeneralPage::OnEditChange()
{
    // Page is dirty, mark it.
SetModified();
    m_bUpdate = TRUE;
}

BOOL CGeneralPage::OnApply()
{
if (m_bUpdate == TRUE)
    {

USES_CONVERSION;
        // Simple string cookie, could be anything!
        LPWSTR lpString =
            reinterpret_cast<LPWSTR>(
          ::GlobalAlloc(GMEM_SHARE,
                        (sizeof(wchar_t) *
                        (m_szName.GetLength() + 1))
                        ));

        wcscpy(lpString, T2COLE(m_szName));

        // Send a property change notify to the console
        MMCPropertyChangeNotify(m_hConsoleHandle, reinterpret_cast<long>(lpString));
        m_bUpdate = FALSE;
    }

return CPropertyPage::OnApply();
}
/////////////////////////////////////////////////////////////////////////////
// CExtensionPage property page

IMPLEMENT_DYNCREATE(CExtensionPage, CPropertyPage)

CExtensionPage::CExtensionPage() : CPropertyPage(CExtensionPage::IDD)
{
//{{AFX_DATA_INIT(CExtensionPage)
m_szText = _T("");
//}}AFX_DATA_INIT
}

CExtensionPage::~CExtensionPage()
{
}

void CExtensionPage::DoDataExchange(CDataExchange* pDX)
{
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CExtensionPage)
DDX_Control(pDX, IDC_EXT_TEXT, m_hTextCtrl);
DDX_Text(pDX, IDC_EXT_TEXT, m_szText);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CExtensionPage, CPropertyPage)
//{{AFX_MSG_MAP(CExtensionPage)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CExtensionPage message handlers

BOOL CExtensionPage::OnInitDialog()
{
CPropertyPage::OnInitDialog();

    m_hTextCtrl.SetWindowText(m_szText);
return TRUE;  // return TRUE unless you set the focus to a control
              // EXCEPTION: OCX Property Pages should return FALSE
}

/////////////////////////////////////////////////////////////////////////////
// CStartUpWizard property page

// NOTICE: need to override this because CPropertyPage::AssertValid()
// would otherwise assert
IMPLEMENT_DYNCREATE(CBaseWizard, CPropertyPage)

CBaseWizard::CBaseWizard(UINT id) : CPropertyPage(id)
{
    // NOTICE: need to do this because MFC was compiled with NT 4.0
// headers that had a different size
    ZeroMemory(&m_psp97, sizeof(PROPSHEETPAGE));

    memcpy(&m_psp97, &m_psp, m_psp.dwSize);
    m_psp97.dwSize = sizeof(PROPSHEETPAGE);
}

void CBaseWizard::OnDestroy()
{
CPropertyPage::OnDestroy();
    delete this;
}

BEGIN_MESSAGE_MAP(CBaseWizard, CPropertyPage)
//{{AFX_MSG_MAP(CStartupWizard1)
ON_WM_DESTROY()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

IMPLEMENT_DYNCREATE(CStartUpWizard, CBaseWizard)

CStartUpWizard::CStartUpWizard() : CBaseWizard(CStartUpWizard::IDD)
{
//{{AFX_DATA_INIT(CStartUpWizard)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
    m_psp97.dwFlags |= PSP_HIDEHEADER;
}

CStartUpWizard::~CStartUpWizard()
{
}

void CStartUpWizard::DoDataExchange(CDataExchange* pDX)
{
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CStartUpWizard)
// NOTE: the ClassWizard will add DDX and DDV calls here
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CStartUpWizard, CBaseWizard)
//{{AFX_MSG_MAP(CStartUpWizard)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CStartUpWizard message handlers

BOOL CStartUpWizard::OnInitDialog()
{
CPropertyPage::OnInitDialog();

return TRUE;  // return TRUE unless you set the focus to a control
              // EXCEPTION: OCX Property Pages should return FALSE
}

BOOL CStartUpWizard::OnSetActive()
{
// TODO: Add your specialized code here and/or call the base class

// TODO: Add your specialized code here and/or call the base class
    HWND hwnd = GetParent()->m_hWnd;
    ::SendMessage(hwnd, PSM_SETWIZBUTTONS, 0, PSWIZB_NEXT);

return CPropertyPage::OnSetActive();
}
/////////////////////////////////////////////////////////////////////////////
// CStartupWizard1 property page

IMPLEMENT_DYNCREATE(CStartupWizard1, CBaseWizard)

CStartupWizard1::CStartupWizard1() : CBaseWizard(CStartupWizard1::IDD)
{
//{{AFX_DATA_INIT(CStartupWizard1)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT

    m_psp97.dwFlags |= PSP_USEHEADERTITLE | PSP_USEHEADERSUBTITLE;
    m_psp97.pszHeaderTitle = _T("This is the title line");
m_psp97.pszHeaderSubTitle = _T("This is the sub-title line");
}

CStartupWizard1::~CStartupWizard1()
{
}

void CStartupWizard1::DoDataExchange(CDataExchange* pDX)
{
CPropertyPage::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CStartupWizard1)
// NOTE: the ClassWizard will add DDX and DDV calls here
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CStartupWizard1, CBaseWizard)
//{{AFX_MSG_MAP(CStartupWizard1)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CStartupWizard1 message handlers

BOOL CStartupWizard1::OnInitDialog()
{
CPropertyPage::OnInitDialog();

// TODO: Add extra initialization here

return TRUE;  // return TRUE unless you set the focus to a control
              // EXCEPTION: OCX Property Pages should return FALSE
}

BOOL CStartupWizard1::OnSetActive()
{
// TODO: Add your specialized code here and/or call the base class
    HWND hwnd = GetParent()->m_hWnd;
    ::SendMessage(hwnd, PSM_SETWIZBUTTONS, 0, PSWIZB_FINISH | PSWIZB_BACK);

return CPropertyPage::OnSetActive();
}


GENPAGE.H

// genpage.h : header file
//

/////////////////////////////////////////////////////////////////////////////
// CGeneralPage dialog

class CGeneralPage : public CPropertyPage
{
DECLARE_DYNCREATE(CGeneralPage)

// Construction
public:
CGeneralPage();
~CGeneralPage();
BOOL Create(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL);
BOOL Create(UINT nIDTemplate, CWnd* pParentWnd = NULL);

// Dialog Data
//{{AFX_DATA(CGeneralPage)
enum { IDD = IDD_GENERAL };
::CEditm_EditCtrl;
CStringm_szName;
//}}AFX_DATA

// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CGeneralPage)
public:
virtual BOOL OnApply();
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CGeneralPage)
afx_msg void OnDestroy();
afx_msg void OnEditChange();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

public:
    long    m_hConsoleHandle; // Handle given to the snap-in by the console

private:
    BOOL    m_bUpdate;
};
/////////////////////////////////////////////////////////////////////////////
// CExtensionPage dialog

class CExtensionPage : public CPropertyPage
{
DECLARE_DYNCREATE(CExtensionPage)

// Construction
public:
CExtensionPage();
~CExtensionPage();

// Dialog Data
//{{AFX_DATA(CExtensionPage)
enum { IDD = IDD_EXTENSION_PAGE };
::CStaticm_hTextCtrl;
CStringm_szText;
//}}AFX_DATA

// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CExtensionPage)
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CExtensionPage)
virtual BOOL OnInitDialog();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

};
/////////////////////////////////////////////////////////////////////////////
// CStartUpWizard dialog

class CBaseWizard : public CPropertyPage
{
DECLARE_DYNCREATE(CBaseWizard)
public:
    CBaseWizard(UINT id);
    CBaseWizard() {};

// Implementation
public:
    PROPSHEETPAGE m_psp97;

protected:
// Generated message map functions
//{{AFX_MSG(CStartUpWizard)
afx_msg void OnDestroy();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

};

class CStartUpWizard : public CBaseWizard
{
DECLARE_DYNCREATE(CStartUpWizard)

// Construction
public:
CStartUpWizard();
~CStartUpWizard();

// Dialog Data
//{{AFX_DATA(CStartUpWizard)
enum { IDD = IDD_INSERT_WIZARD };
// NOTE - ClassWizard will add data members here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_DATA

// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CStartUpWizard)
public:
virtual BOOL OnSetActive();
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CStartUpWizard)
virtual BOOL OnInitDialog();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

};
/////////////////////////////////////////////////////////////////////////////
// CStartupWizard1 dialog

class CStartupWizard1 : public CBaseWizard
{
DECLARE_DYNCREATE(CStartupWizard1)

// Construction
public:
CStartupWizard1();
~CStartupWizard1();

// Dialog Data
//{{AFX_DATA(CStartupWizard1)
enum { IDD = IDD_INSERT_WIZARD };
// NOTE - ClassWizard will add data members here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_DATA

// Overrides
// ClassWizard generate virtual function overrides
//{{AFX_VIRTUAL(CStartupWizard1)
public:
virtual BOOL OnSetActive();
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CStartupWizard1)
virtual BOOL OnInitDialog();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

};


ABOUT.CPP

// This is a part of the Microsoft Management Console.
// Copyright (C) 1995-1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

#include <stdafx.h>

#include "Service.h"
#include "CSnapin.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CSnapinAboutImpl::CSnapinAboutImpl()
{
}

CSnapinAboutImpl::~CSnapinAboutImpl()
{
}

HRESULT CSnapinAboutImpl::AboutHelper(UINT nID, LPOLESTR* lpPtr)
{
    if (lpPtr == NULL)
        return E_POINTER;

    CString s;

    // Needed for Loadstring
    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    s.LoadString(nID);
    *lpPtr = reinterpret_cast<LPOLESTR>
            (CoTaskMemAlloc((s.GetLength() + 1)* sizeof(wchar_t)));

    if (*lpPtr == NULL)
        return E_OUTOFMEMORY;

USES_CONVERSION;

    wcscpy(*lpPtr, T2OLE((LPTSTR)(LPCTSTR)s));

    return S_OK;
}

STDMETHODIMP CSnapinAboutImpl::GetSnapinDescription(LPOLESTR* lpDescription)
{
    return AboutHelper(IDS_DESCRIPTION, lpDescription);
}

STDMETHODIMP CSnapinAboutImpl::GetProvider(LPOLESTR* lpName)
{
    return AboutHelper(IDS_COMPANY, lpName);
}

STDMETHODIMP CSnapinAboutImpl::GetSnapinVersion(LPOLESTR* lpVersion)
{
    return AboutHelper(IDS_VERSION, lpVersion);
}

STDMETHODIMP CSnapinAboutImpl::GetSnapinImage(HICON* hAppIcon)
{
    if (hAppIcon == NULL)
        return E_POINTER;

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    *hAppIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_APPICON));

    ASSERT(*hAppIcon != NULL);
    return (*hAppIcon != NULL) ? S_OK : E_FAIL;
}

STDMETHODIMP CSnapinAboutImpl::GetStaticFolderImage(HBITMAP* hSmallImage,
                                                    HBITMAP* hSmallImageOpen,
                                                    HBITMAP* hLargeImage,
                                                    COLORREF* cLargeMask)
{
    return S_OK;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by snapin.rc
//
#define IDS_SNAPIN_DESC                 1
#define IDS_NAME                        2
#define IDS_SIZE                        3
#define IDS_TYPE                        4
#define IDS_NODENAME                    5
#define IDS_STRING6                     6
#define IDS_DESCRIPTION                 6
#define IDS_VERSION                     7
#define IDS_COMPANY                     8
#define IDD_GENERAL                     107
#define IDD_EXTENSION_PAGE              108
#define IDD_INSERT_WIZARD               109
#define IDC_NEW_FOLDER                  201
#define IDB_16x16                       202
#define IDC_EXT_STATIC                  202
#define IDB_32x32                       203
#define IDC_EXT_TEXT                    203
#define IDI_FOLDER                      205
#define IDB_TOOLBAR1                    207
#define IDB_TOOLBAR2                    208
#define IDI_APPICON                     209
#define IDB_SMALLBMP                    210
#define IDB_LARGE                       211
#define IDR_MENU1                       212
#define FILEEX_MENU                     213
#define FOLDEREX_MENU                   214
#define IDB_WATERMARK                   216
#define IDB_BANNER                      218
#define ID_TEST2_ITEM1                  32770
#define ID_TEST2_ITEM2                  32771
#define ID_TEST2_ITEM3                  32773
#define ID_TEST2_ITEM4_ITEMA            32774
#define ID_TEST2_ITEM4_ITEMB            32775
#define ID_TEST2_ITEM4_ITEMC            32776
#define ID_TEST2_ITEM5                  32777
#define ID_TEST2_COLOR1                 32779
#define ID_TEST2_COLOR2                 32780
#define ID_TEST2_COLOR3                 32781
#define ID_MENUITEM32782                32782
#define ID_MENUITEM32783                32783
#define ID_MENUITEM32784                32784
#define ID_MENUITEM32785                32785
#define ID_MENUITEM32786                32786

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        219
#define _APS_NEXT_COMMAND_VALUE         32787
#define _APS_NEXT_CONTROL_VALUE         204
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SERVICE.CPP

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

#include "stdafx.h"
#include "Service.h"
#include "CSnapin.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

void CFolder::Create(LPWSTR szName, int nImage, int nOpenImage,
                                FOLDER_TYPES type, BOOL bHasChildren)
{
    ASSERT(m_pScopeItem == NULL); // Calling create twice on this item?

    // Two-stage construction
    m_pScopeItem = new SCOPEDATAITEM;
    memset(m_pScopeItem, 0, sizeof(SCOPEDATAITEM));

    // Set folder type
    m_type = type;

    // Add node name
    if (szName != NULL)
    {
        m_pScopeItem->mask = SDI_STR;
    m_pScopeItem->displayname = (unsigned short*)(-1);

        UINT uiByteLen = (wcslen(szName) + 1) * sizeof(OLECHAR);
        LPOLESTR psz = (LPOLESTR)::CoTaskMemAlloc(uiByteLen);

        if (psz != NULL)
        {
            wcscpy(psz, szName);
            m_pszName = psz;
        }
    }

    // Add close image
    if (nImage != 0)
    {
        m_pScopeItem->mask |= SDI_IMAGE;
        m_pScopeItem->nImage = nImage;
    }

    // Add open image
    if (nOpenImage != 0)
    {
        m_pScopeItem->mask |= SDI_OPENIMAGE;
        m_pScopeItem->nOpenImage = nOpenImage;
    }

    // Add button to node if the folder has children
    if (bHasChildren == TRUE)
    {
        m_pScopeItem->mask |= SDI_CHILDREN;
        m_pScopeItem->cChildren = 1;
    }
}


SERVICE.H

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

#ifndef _SERVICE_H
#define _SERVICE_H

// Forward declarations
class CSnapin;

#define SCOPE_ITEM      111
#define RESULT_ITEM     222

// Internal structure used for cookies
struct FOLDER_DATA
{
    wchar_t*    szName;
    wchar_t*    szSize;
    wchar_t*    szType;

    FOLDER_TYPES    type;
};

struct RESULT_DATA
{
    DWORD       itemType; // used for debug purpose only
    FOLDER_TYPES parentType;

    wchar_t*    szName;
    wchar_t*    szSize;
    wchar_t*    szType;
};

class CFolder
{
    DWORD       itemType;   // Used for debug purpose only. This should be the first
                            // member. The class should not have any virtual fuctions.

    friend class CSnapin;
    friend class CComponentDataImpl;

public:
    // UNINITIALIZED is an invalid memory address and is a good cookie initializer
    CFolder()
    {
        itemType = SCOPE_ITEM;  // used for debug purpose only

        m_cookie = UNINITIALIZED;
        m_enumed = FALSE;
        m_pScopeItem = NULL;
        m_type = NONE;
        m_pszName = NULL;
    };

    ~CFolder() { delete m_pScopeItem; CoTaskMemFree(m_pszName); };

// Interface
public:
    BOOL IsEnumerated() { return  m_enumed; };
    void Set(BOOL state) { m_enumed = state; };
    void SetCookie(long cookie) { m_cookie = cookie; }
    FOLDER_TYPES GetType() { ASSERT(m_type != NONE); return m_type; };
    BOOL operator == (const CFolder& rhs) const { return rhs.m_cookie == m_cookie; };
    BOOL operator == (long cookie) const { return cookie == m_cookie; };
    void SetName(LPWSTR pszIn)
    {
        UINT len = wcslen(pszIn) + 1;
        LPWSTR psz = (LPWSTR)CoTaskMemAlloc(len * sizeof(WCHAR));
        if (psz != NULL)
        {
            wcscpy(psz, pszIn);
            CoTaskMemFree(m_pszName);
            m_pszName = psz;
        }
    }

// Implementation
private:
    void Create(LPWSTR szName, int nImage, int nOpenImage,
        FOLDER_TYPES type, BOOL bHasChildren = FALSE);

// Attributes
private:
    LPSCOPEDATAITEM m_pScopeItem;
    long            m_cookie;
    BOOL            m_enumed;
    FOLDER_TYPES    m_type;
    LPOLESTR        m_pszName;
};

#endif


SNAPIN.CPP

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

// You will need the NT SUR Beta 2 SDK or VC 4.2 in order to build this
// project.  This is because you will need MIDL 3.00.15 or higher and new
// headers and libs.  If you have VC 4.2 installed, then everything should
// already be configured correctly.

// Note: Proxy/Stub Information
//To build a separate proxy/stub DLL,
//run nmake -f Snapinps.mak in the project directory.

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "Service.h"
#include "CSnapin.h"

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_Snapin, CComponentDataPrimaryImpl)
OBJECT_ENTRY(CLSID_Extension, CComponentDataExtensionImpl)
OBJECT_ENTRY(CLSID_About, CSnapinAboutImpl)
END_OBJECT_MAP()

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

class CSnapinApp : public CWinApp
{
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
};

CSnapinApp theApp;

BOOL CSnapinApp::InitInstance()
{
_Module.Init(ObjectMap, m_hInstance);
return CWinApp::InitInstance();
}

int CSnapinApp::ExitInstance()
{
_Module.Term();

    DEBUG_VERIFY_INSTANCE_COUNT(CSnapin);
    DEBUG_VERIFY_INSTANCE_COUNT(CComponentDataImpl);

return CWinApp::ExitInstance();
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return (AfxDllCanUnloadNow()==S_OK && _Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(FALSE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
_Module.UnregisterServer();
return S_OK;
}


SNAPIN.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 3.00.15 */
/* at Thu Oct 03 17:06:29 1996
 */
/* Compiler settings for Snapin.idl:
    Os, W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/

#ifndef __Snapin_h__
#define __Snapin_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __IComponent_FWD_DEFINED__
#define __IComponent_FWD_DEFINED__
typedef interface IComponent IComponent;
#endif /* __IComponent_FWD_DEFINED__ */

/* header files for imported files */
#include "oaidl.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __IComponent_INTERFACE_DEFINED__
#define __IComponent_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IComponent
 * at Thu Oct 03 17:06:29 1996
 * using MIDL 3.00.15
 ****************************************/
/* [unique][helpstring][uuid][object] */

EXTERN_C const IID IID_IComponent;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface IComponent : public IUnknown
    {
    public:
    };

#else /* C style interface */

    typedef struct IComponentVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IComponent __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IComponent __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IComponent __RPC_FAR * This);

        END_INTERFACE
    } IComponentVtbl;

    interface IComponent
    {
        CONST_VTBL struct IComponentVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IComponent_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IComponent_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IComponent_Release(This)\
    (This)->lpVtbl -> Release(This)

#endif /* COBJMACROS */

#endif /* C style interface */

#endif /* __IComponent_INTERFACE_DEFINED__ */

#ifndef __SNAPINLib_LIBRARY_DEFINED__
#define __SNAPINLib_LIBRARY_DEFINED__

/****************************************
 * Generated header for library: SNAPINLib
 * at Thu Oct 03 17:06:29 1996
 * using MIDL 3.00.15
 ****************************************/
/* [helpstring][version][uuid] */

EXTERN_C const IID LIBID_SNAPINLib;

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_Snapin;

class Snapin;
#endif
#endif /* __SNAPINLib_LIBRARY_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


STDAFX.CPP

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

#include "stdafx.h"

const CLSID CLSID_Snapin =    {0x18731372,0x1D79,0x11D0,{0xA2,0x9B,0x00,0xC0,0x4F,0xD9,0x09,0xDD}};
const CLSID CLSID_Extension = {0x27E5EC4E,0x9F2D,0x11D0,{0xA7,0xD6,0x00,0xC0,0x4F,0xD9,0x09,0xDD}};
const CLSID CLSID_About =     {0xF0A1B9BE,0xA172,0x11D0,{0xA7,0xD7,0x00,0xC0,0x4F,0xD9,0x09,0xDD}};

///////////////////////////////////////////////////////////////////////////////
//
//                  OBJECT TYPES
//

//
// OBJECT TYPE for Scope Nodes.
//

// Static NodeType GUID in numeric & string formats.
const GUID cNodeTypeStatic = {0x44092d22,0x1d7e,0x11D0,{0xA2,0x9B,0x00,0xC0,0x4F,0xD9,0x09,0xDD}};
const wchar_t*  cszNodeTypeStatic = L"{44092d22-1d7e-11d0-a29b-00c04fd909dd}";

// Company Data NodeType GUID in numeric & string formats.
const GUID cNodeTypeCompany = {0x55092d22,0x1d7e,0x11D0,{0xA2,0x9B,0x00,0xC0,0x4F,0xD9,0x09,0xDD}};
const wchar_t*  cszNodeTypeCompany = L"{55092d22-1d7e-11d0-a29b-00c04fd909dd}";

// User Data NodeType GUID in numeric & string formats.
const GUID cNodeTypeUser = {0x66092d22,0x1d7e,0x11D0,{0xA2,0x9B,0x00,0xC0,0x4F,0xD9,0x09,0xDD}};
const wchar_t*  cszNodeTypeUser = L"{66092d22-1d7e-11d0-a29b-00c04fd909dd}";

// Extension Company Data NodeType GUID in numeric & string formats.
extern const GUID cNodeTypeExtCompany = {0xe5092d22,0x1d7e,0x11D0,{0xA2,0x9B,0x00,0xC0,0x4F,0xD9,0x09,0xDD}};
extern const wchar_t*  cszNodeTypeExtCompany = L"{e5092d22-1d7e-11d0-a29b-00c04fd909dd}";

// Extension User Data NodeType GUID in numeric & string formats.
extern const GUID cNodeTypeExtUser = {0xe6092d22,0x1d7e,0x11D0,{0xA2,0x9B,0x00,0xC0,0x4F,0xD9,0x09,0xDD}};
extern const wchar_t*  cszNodeTypeExtUser = L"{e6092d22-1d7e-11d0-a29b-00c04fd909dd}";

// Virtual Data NodeType GUID in numeric & string formats.
extern const GUID cNodeTypeVirtual = {0xad003e5a,0x483,0x11d1,{0xae,0xf7,0x0,0xc0,0x4f,0xb6,0xdd,0x2c}};
extern const wchar_t*  cszNodeTypeVirtual = L"{AD003E5A-0483-11d1-AEF7-00C04FB6DD2C}";

// Dynamicaly created objects.
extern const GUID cNodeTypeDynamic = {0x0ac69b7a,0xafce,0x11d0,{0xa7,0x9b,0x00,0xc0,0x4f,0xd8,0xd5,0x65}};
extern const wchar_t*  cszNodeTypeDynamic = L"{0ac69b7a-afce-11d0-a79b-00c04fd8d565}";

//
// OBJECT TYPE for result items.
//

// Result items object type GUID in numeric & string formats.
extern const GUID cObjectTypeResultItem = {0x00c86e52,0xaf90,0x11d0,{0xa7,0x9b,0x00,0xc0,0x4f,0xd8,0xd5,0x65}};
extern const wchar_t*  cszObjectTypeResultItem = L"{00c86e52-af90-11d0-a79b-00c04fd8d565}";

//
//
//////////////////////////////////////////////////////////////////////////////

// Internal private format
const wchar_t* SNAPIN_INTERNAL = L"SNAPIN_INTERNAL"; // Step 3

// Published formats
const wchar_t* SNAPIN_WORKSTATION = L"SNAPIN_WORKSTATION"; // Extension


STDAFX.H

// This is a part of the Microsoft Management Console.
// Copyright 1995 - 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Management Console and related
// electronic documentation provided with the interfaces.

// stdafx.h : include file for standard system include files,
//      or project specific include files that are used frequently,
//      but are changed infrequently

#undef _MSC_EXTENSIONS

#include <afxwin.h>
#include <afxext.h>         // MFC extensions
#include <afxdisp.h>
#include "afxtempl.h"

//#include <shellapi.h>

#include <atlbase.h>
using namespace ATL;

//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>

//#include "afxtempl.h"

#pragma comment(lib, "mmc")
#include <mmc.h>

const long UNINITIALIZED = -1;

// Constants used in samples
const int NUM_FOLDERS = 4;
const int NUM_NAMES = 4;
const int NUM_COMPANY = 6;
const int NUM_VIRTUAL_ITEMS = 100000;
const int MAX_ITEM_NAME = 64;

// Sample folder types
enum FOLDER_TYPES
{
    STATIC = 0x8000,
    COMPANY = 0x8001,
    USER = 0x8002,
    VIRTUAL = 0x8003,
    EXT_COMPANY = 0x8004,
    EXT_USER = 0x8005,
    EXT_VIRTUAL = 0x8006,
    NONE = 0xFFFF
};

/////////////////////////////////////////////////////////////////////////////
// Helper functions

template<class TYPE>
inline void SAFE_RELEASE(TYPE*& pObj)
{
    if (pObj != NULL)
    {
        pObj->Release();
        pObj = NULL;
    }
    else
    {
        TRACE(_T("Release called on NULL interface ptr\n"));
    }
}

extern const CLSID CLSID_Snapin;    // In-Proc server GUID
extern const CLSID CLSID_Extension; // In-Proc server GUID
extern const CLSID CLSID_About;

///////////////////////////////////////////////////////////////////////////////
//
//                  OBJECT TYPES
//

//
// OBJECT TYPE for Scope Nodes.
//

// Static NodeType GUID in numeric & string formats.
extern const GUID cNodeTypeStatic;
extern const wchar_t*  cszNodeTypeStatic;

// Company Data NodeType GUID in numeric & string formats.
extern const GUID cNodeTypeCompany;
extern const wchar_t*  cszNodeTypeCompany;

// User Data NodeType GUID in numeric & string formats.
extern const GUID cNodeTypeUser;
extern const wchar_t*  cszNodeTypeUser;

// Extension Company Data NodeType GUID in numeric & string formats.
extern const GUID cNodeTypeExtCompany;
extern const wchar_t*  cszNodeTypeExtCompany;

// Extension User Data NodeType GUID in numeric & string formats.
extern const GUID cNodeTypeExtUser;
extern const wchar_t*  cszNodeTypeExtUser;

// Extension Virtual NodeType GUID in numeric & string formats.
extern const GUID cNodeTypeVirtual;
extern const wchar_t*  cszNodeTypeVirtual;

// Dynamicaly created objects.
extern const GUID cNodeTypeDynamic;
extern const wchar_t*  cszNodeTypeDynamic;

//
// OBJECT TYPE for result items.
//

// Result items object type GUID in numeric & string formats.
extern const GUID cObjectTypeResultItem;
extern const wchar_t*  cszObjectTypeResultItem;

//
//
//////////////////////////////////////////////////////////////////////////////

// New Clipboard format that has the Type and Cookie
extern const wchar_t* SNAPIN_INTERNAL;

// Published context information for extensions to extend
extern const wchar_t* SNAPIN_WORKSTATION;

struct INTERNAL
{
    INTERNAL()
    {
        m_type = CCT_UNINITIALIZED;
        m_cookie = -1;
        ZeroMemory(&m_clsid, sizeof(CLSID));
    };

    ~INTERNAL() {}

    DATA_OBJECT_TYPES   m_type;         // What context is the data object.
    long                m_cookie;       // What object the cookie represents
    CString             m_string;       //
    CLSID               m_clsid;       // Class ID of who created this data object

    INTERNAL & operator=(const INTERNAL& rhs)
    {
        if (&rhs == this)
            return *this;

        // Deep copy the information
        m_type = rhs.m_type;
        m_cookie = rhs.m_cookie;
        m_string = rhs.m_string;
        memcpy(&m_clsid, &rhs.m_clsid, sizeof(CLSID));

        return *this;
    }

    BOOL operator==(const INTERNAL& rhs)
    {
        return rhs.m_string == m_string;
    }
};

// Debug instance counter
#ifdef _DEBUG
inline void DbgInstanceRemaining(char * pszClassName, int cInstRem)
{
    char buf[100];
    wsprintfA(buf, "%s has %d instances left over.", pszClassName, cInstRem);
    ::MessageBoxA(NULL, buf, "SAMPLE: Memory Leak!!!", MB_OK);
}
    #define DEBUG_DECLARE_INSTANCE_COUNTER(cls)      extern int s_cInst_##cls = 0;
    #define DEBUG_INCREMENT_INSTANCE_COUNTER(cls)    ++(s_cInst_##cls);
    #define DEBUG_DECREMENT_INSTANCE_COUNTER(cls)    --(s_cInst_##cls);
    #define DEBUG_VERIFY_INSTANCE_COUNT(cls)    \
        extern int s_cInst_##cls; \
        if (s_cInst_##cls) DbgInstanceRemaining(#cls, s_cInst_##cls);
#else
    #define DEBUG_DECLARE_INSTANCE_COUNTER(cls)
    #define DEBUG_INCREMENT_INSTANCE_COUNTER(cls)
    #define DEBUG_DECREMENT_INSTANCE_COUNTER(cls)
    #define DEBUG_VERIFY_INSTANCE_COUNT(cls)
#endif


README.TXT

This is step one of a tutorial sample. This first step is your basic
"Hello World" application. It places a root node and one child node
in the MMC scope pane tree.

This sample will compile with VC++ 5.0.
It is set up to build a debug version of the DLL.
If you want to run this DLL from the debugger, then go into
the "Project Settings" dialog and on the Debug tab make sure that
the path is correct to the MMC.EXE.
The IDE build will register your DLL for you.

If you build from the command line you will want to
copy the DLL to the system32 directory and register the DLL
by typing from the command line "regsvr32 disk.dll".

This sample uses mmc.h and mmc.lib. In the VC++ 5.0 Project menu,
select Settings. Then add the appropriate directories to find mmc.h
and mmc.lib on the C++ and Link tabs.

To try out the snapin - in MMC, from the Console menu, select Add/Remove
Snap-In. Click the Add button. The name of the Snap-In is Disk.


COMP.CPP

/////////////////////////////////////////////////////////////////////////////
// comp.cpp : IComponent Interface to communicate with MMC for the
//                       results pane.  This implementation uses the
//                       default list view.
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#include "stdafx.h"
#include "disk.h"
#include "globals.h"
#include "comp.h"

/////////////////////////////////////////////////////////////////////////////
// CComponent - This class at this point is the basic interface to handle
//              anything to do with the results pane. At this point it is
//              mostly a stub place holder and serves no real purpose. It will
//              get filled in and used in later steps.

CComponent::CComponent()
{
  m_pConsole = NULL;
  m_cRefs    = 0;                      // Not used in Step1
}

CComponent::~CComponent()
{
}

/////////////////////////////////////////////////////////////////////////////
// IComponent implementation
//

//---------------------------------------------------------------------------
// IComponent::Initialize is called when a snap-in is being created and
// has items in the result pane to enumerate. The pointer to IConsole that
// is passed in is used to make QueryInterface calls to the console for
// interfaces such as IResultData.
//
STDMETHODIMP
CComponent::Initialize
(
  LPCONSOLE lpConsole        // [in] Pointer to IConsole's IUnknown interface
)
{
  HRESULT hr = S_OK;

  _ASSERT( NULL != lpConsole );

  //
  // Save away all the interfaces we'll need
  //

  // Not realy going to use it in this step!
  m_pConsole = lpConsole;
  m_pConsole->AddRef();

  return hr;

} // end Initialize()

//---------------------------------------------------------------------------
//  Notifies the snap-in of actions taken by the user
//
STDMETHODIMP
CComponent::Notify
(
  LPDATAOBJECT     pDataObject,  // [in] Points to data object
  MMC_NOTIFY_TYPE  event,        // [in] Identifies action taken by user
  long             arg,          // [in] Depends on the notification type
  long             param         // [in] Depends on the notification type
)
{
  // Don't need to handle any messages at this point
  return S_OK;

} // end Notify()

//---------------------------------------------------------------------------
// Releases all references to the console.
// Only the console should call this method.
//
STDMETHODIMP
CComponent::Destroy
(
  long cookie          // Reserved, not in use at this time
)
{
  // Release the interfaces that we QI'ed
  if (m_pConsole != NULL)
  {
    m_pConsole->Release();
    m_pConsole = NULL;
  }
  return S_OK;

} // end Destroy()

//---------------------------------------------------------------------------
// Returns a data object that can be used to retrieve context information
// for the specified cookie.
//
STDMETHODIMP
CComponent::QueryDataObject
(
  long               cookie,      // [in]  Specifies the unique identifier
  DATA_OBJECT_TYPES  context,     // [in]  Type of data object
  LPDATAOBJECT      *ppDataObject // [out] Points to address of returned data
)
{
  // Make sure we don't get here, we don't need it in this step
  _ASSERT(FALSE);

  return S_FALSE;

} // end QueryDataObject()

//---------------------------------------------------------------------------
// This is really the only function of any use in this class for Step1.
// It is used when the results pane is displaying tree data such as nodes
// that show up in the scope pane.
//
STDMETHODIMP
CComponent::GetDisplayInfo
(
  LPRESULTDATAITEM pResultItem    // [in,out] Type of info required
)
{
  static WCHAR* s_szSize = L"ABC";

  _ASSERT( NULL != pResultItem );

  if (pResultItem)
  {
    // Only responding for scope items for now.
    if ( TRUE == pResultItem->bScopeItem )
    {
      if (pResultItem->mask & RDI_STR)     // Looking for a string
      {
        if (0 == pResultItem->nCol )
          pResultItem->str = (LPOLESTR)L"Geometry";
        else if (1 == pResultItem->nCol )
          pResultItem->str = (LPOLESTR)L"Child Folder";
        else
          pResultItem->str = (LPOLESTR)L"Else";
      }

      if (pResultItem->mask & RDI_IMAGE)
      {
        pResultItem->nImage = 0;
      }
    }
    else
    {
      _ASSERT(FALSE);
    }
  }

  return S_OK;

} // end GetDisplayInfo()

//---------------------------------------------------------------------------
// Determines what the result pane view should be
//
STDMETHODIMP
CComponent::GetResultViewType
(
  long  cookie,        // [in]  Specifies the unique identifier
  BSTR  *ppViewType,   // [out] Points to address of the returned view type
  long  *pViewOptions  // [out] Pointer to the MMC_VIEW_OPTIONS enumeration
)
{
  //
  // Ask for default listview.
  //
  *pViewOptions = MMC_VIEW_OPTIONS_NONE;
  return S_FALSE;

} // end GetResultViewType()

//---------------------------------------------------------------------------
// Not used in Step1
//
HRESULT
CComponent::CompareObjects
(
  LPDATAOBJECT lpDataObjectA,  // [in] First data object to compare
  LPDATAOBJECT lpDataObjectB   // [in] Second data object to compare
)
{
return S_FALSE;

} // end CompareObjects()


COMP.H

/////////////////////////////////////////////////////////////////////////////
// comp.h : IComponent Interface to communicate with MMC for results pane use
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#ifndef __COMP_H_
#define __COMP_H_

#include <mmc.h>

/////////////////////////////////////////////////////////////////////////////
// CComponent - This class at this point is the basic interface to handle
//              anything to do with the results pane. At this point it is
//              mostly a stib place holder and serves no real purpose. It will
//              get filled in and used in later steps.

class CComponent:
public IComponent,
  public CComObjectRoot
{
public:

// ATL Maps
DECLARE_NOT_AGGREGATABLE(CComponent)

BEGIN_COM_MAP(CComponent)
COM_INTERFACE_ENTRY(IComponent)
END_COM_MAP()

  CComponent();
  ~CComponent();

//
// IComponent interface members
//
  STDMETHOD(Initialize)(LPCONSOLE lpConsole);
  STDMETHOD(Notify)(LPDATAOBJECT lpDataObject, MMC_NOTIFY_TYPE event, long arg, long param);
  STDMETHOD(Destroy)(long cookie);
  STDMETHOD(GetResultViewType)(long cookie,  LPOLESTR* ppViewType, long* pViewOptions);
  STDMETHOD(QueryDataObject)(long cookie, DATA_OBJECT_TYPES type, LPDATAOBJECT* ppDataObject);
  STDMETHOD(GetDisplayInfo)(RESULTDATAITEM*  pResultDataItem);
  STDMETHOD(CompareObjects)(LPDATAOBJECT lpDataObjectA, LPDATAOBJECT lpDataObjectB);

private:
    LPCONSOLE m_pConsole; // from MMC
    ULONG m_cRefs;   // object refcount
};

#endif // __COMP_H_


COMPDATA.CPP

/////////////////////////////////////////////////////////////////////////////
//  compdata.cpp : IComponentData Interface to communicate with MMC for
//                 scope pane use
//
//  This is a part of the MMC SDK.
//  Copyright (C) 1997 Microsoft Corporation
//  All rights reserved.
//
//  This source code is only intended as a supplement to the
//  MMC SDK Reference and related
//  electronic documentation provided with the library.
//  See these sources for detailed information regarding the
//  MMC Library product.
//

#include "stdafx.h"
#include <mmc.h>                       // Copy from MMC.SDK
#include "disk.h"
#include "globals.h"                   // Helper functions
#include "comp.h"
#include "compdata.h"

/////////////////////////////////////////////////////////////////////////////
// CComponentData - This class is the interface to handle anything to do
//                  with the scope pane. MMC calls the IComponent interfaces.
//                  This class keeps a few pointers to interfaces that MMC
//                  implements.

CComponentData::CComponentData()
{
  m_ipConsoleNameSpace = NULL;
  m_ipConsole          = NULL;
}

CComponentData::~CComponentData()
{
// We release the cached interface pointers in Destroy()
}

/////////////////////////////////////////////////////////////////////////////
// IComponentData methods
//

//---------------------------------------------------------------------------
// We get here only once, when the user clicks on the snapin.
//
// This method should not change as we progress through further steps.
// Here we get a chance to get pointer to some interfaces MMC provides.
// We QueryInterface for pointers to the name space and console, which
// we cache in local variables
// The other task to acomplish here is the adding of a bitmap that contains
// the icons to be used in the scope pane.
//
STDMETHODIMP
CComponentData::Initialize
(
  LPUNKNOWN pUnknown         // [in] Pointer to the IConsole s IUnknown interface
)
{
    HRESULT      hr;
    LPIMAGELIST  lpScopeImage;

    _ASSERT( NULL != pUnknown );

    // MMC should only call ::Initialize once!
    _ASSERT( NULL == m_ipConsoleNameSpace );

    //
    // Get pointer to name space interface
    //
    hr = pUnknown->QueryInterface(IID_IConsoleNameSpace, (VOID**)(&m_ipConsoleNameSpace));
    _ASSERT( S_OK == hr );

    //
    // Get pointer to console interface
    //
    hr = pUnknown->QueryInterface(IID_IConsole, (VOID**)(&m_ipConsole));
    _ASSERT( S_OK == hr );

    //
    // Add the images for the scope tree
    //
    hr = m_ipConsole->QueryScopeImageList(&lpScopeImage);
    _ASSERT( S_OK == hr );

    // Load the bitmaps from the dll
    m_hbmp16x16 = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_SCOPE_16x16));
    _ASSERT( NULL != m_hbmp16x16 );

    // Set the images
    hr = lpScopeImage->ImageListSetStrip( (long*)m_hbmp16x16,
                                          (long*)m_hbmp16x16,
                                          0,
                                          RGB(0,255,0)
                                        );
    _ASSERT( S_OK == hr );

    lpScopeImage->Release();

    return S_OK;

} // end Initialize()

//---------------------------------------------------------------------------
// Release interfaces and clean up objects which allocated memory
//
STDMETHODIMP
CComponentData::Destroy()
{
  // Free interfaces
  m_ipConsoleNameSpace->Release();
  m_ipConsoleNameSpace = NULL;

  m_ipConsole->Release();
  m_ipConsole = NULL;

  _ASSERT( DeleteObject(m_hbmp16x16) );

  return S_OK;

} // end Destroy()

//---------------------------------------------------------------------------
// Come in here once right after Initialize. MMC wants a pointer to the
// IComponent interface.
//
STDMETHODIMP
CComponentData::CreateComponent
(
  LPCOMPONENT* ppComponent   // [out] Pointer to the location that stores
)                            // the newly created pointer to IComponent
{
  CComObject<CComponent>* pObject;

  //
  // MMC asks us for a pointer to the IComponent interface
  //
  // For those getting up to speed with COM...
  // If we had implemented IUnknown with its methods QueryInterface, AddRef,
  // and Release in our CComponent class...
  // The following line would have worked
  //
  // pNewSnapin = new CComponent(this);
  //
  // In this code we will have ATL take care of IUnknown for us and create
  // an object in the following manner...

  _ASSERT( NULL != ppComponent );

  CComObject<CComponent>::CreateInstance( &pObject );
  _ASSERT( NULL != pObject );

  return  pObject->QueryInterface( IID_IComponent,
                                   reinterpret_cast<void**>(ppComponent)
                                 );
} // end CreateComponent()

//---------------------------------------------------------------------------
// In this first step, we only implement EXPAND.
// The expand message asks us to populate what is under our root node.
// We just put one item under there.
//
STDMETHODIMP
CComponentData::Notify
(
  LPDATAOBJECT     lpDataObject,  // [in] Points to the selected data object
  MMC_NOTIFY_TYPE  event,         // [in] Identifies action taken by user.
  long             arg,           // [in] Depends on the notification type
  long             param          // [in] Depends on the notification type
)
{
  HRESULT hr = S_OK;

  switch (event)
  {
    case MMCN_EXPAND:
      ATLTRACE( L"ComponentData::Notify: MMCN_EXPAND\n" );
      hr = OnExpand( lpDataObject, arg, param );
      break;

    default:
      ATLTRACE( L"CComponentData::Notify: unimplemented event %x\n", event );
      hr = S_FALSE;
      break;
  }
  return hr;

} // end Notify()

//---------------------------------------------------------------------------
// This is where MMC asks us to provide IDataObjects for every node in the
// scope pane
//
STDMETHODIMP
CComponentData::QueryDataObject
(
  long              cookie,       // [in]  Data object's unique identifier
  DATA_OBJECT_TYPES type,         // [in]  Data object's type
  LPDATAOBJECT*     ppDataObject  // [out] Points to the returned data object
)
{
  HRESULT     hr      = S_OK;
  CDataObject *pdoNew = NULL;

  do
  {
pdoNew = new CDataObject;
    *ppDataObject = pdoNew;

    if (!pdoNew)
    {
      hr = E_OUTOFMEMORY;
      // Use MessageBox() rather than IConsole::MessageBox() here because the
      // first time this gets called m_ipConsole is not fully initialized
      ::MessageBox( NULL,
                    L"Unable to allocate new data object",
                    L"CComponentData::QueryDataObject",
                    MB_OK
                  );
      break;
    }

    //
    // The cookie represents a snapin manager or scope pane item.
    //
    // If the passed-in cookie is NULL, it is our snapin's root node folder
    // We never needed to ask for this to be created. MMC did this for us.
    //
    // If the passed-in cookie is non-NULL, then it should be one we
    // created when we added a node to the scope pane. See OnExpand().
    //

    if (cookie)
    {
      // cookie is the lparam field that we passed in SCOPEDATAITEM
      // used for the m_ipConsoleNameSpace->InsertItem(&sdi);
      pdoNew->SetCookie( cookie, CCT_SCOPE, COOKIE_IS_STATUS );
    }
    else
    {
      // In this case the node is our top node, and was placed there for us.
      pdoNew->SetCookie( 0, type, COOKIE_IS_ROOT );
    }
  } while (0);

  return hr;

} // end QueryDataObject()

//---------------------------------------------------------------------------
// This is where we provide strings for nodes in the scope pane.
// We only have one node, so this is easy. MMC handles the root node string.
//
STDMETHODIMP
CComponentData::GetDisplayInfo
(
  LPSCOPEDATAITEM pItem      // [in, out] Points to a SCOPEDATAITEM struct
)
{
  _ASSERT( NULL != pItem );
  _ASSERT( pItem->mask & SDI_STR );

  pItem->displayname = (LPOLESTR)L"Geometry";  // Title to our child folder

return S_OK;

} // end GetDisplayInfo()

//---------------------------------------------------------------------------
//  This is not used in Step1 so we just return S_OK
//
STDMETHODIMP
CComponentData::CompareObjects
(
  LPDATAOBJECT lpDataObjectA,    // [in] First data object to compare
  LPDATAOBJECT lpDataObjectB     // [in] Second data object to compare
)
{
return S_OK;

} // end CompareObjects()

/////////////////////////////////////////////////////////////////////////////
//  Methods needed to support IComponentData
//

//---------------------------------------------------------------------------
// Here is our chance to place things under the root node.
// We will put one item under it.
//
HRESULT
CComponentData::OnExpand
(
  LPDATAOBJECT pDataObject,      // [in] Points to data object
  long         arg,              // [in] TRUE is we are expanding
  long         param             // [in] Points to the HSCOPEITEM
)
{
  CDataObject   *pdo;
  SCOPEDATAITEM sdi;
  INT           iResult;
  HRESULT       hr = S_OK;

  _ASSERT( NULL != m_ipConsoleNameSpace ); // Make sure we QI'ed for the interface
  _ASSERT( NULL != pDataObject );          // Must have valid data object

  if (TRUE == arg )
  {
    pdo = ExtractOwnDataObject( pDataObject );

    if (!pdo)
    {
      hr = m_ipConsole->MessageBox( L"Did not understand the data object.\nNot set up to be an extension",
                                    L"CComponentData::OnExpand",
                                    MB_OK,
                                    &iResult
                                  );
      return hr;
    }

    // Make sure that what we are placing ourselvs under is the root node!
    if (pdo->GetCookieType() != COOKIE_IS_ROOT)
      return S_FALSE;

    _ASSERT( CCT_SCOPE == pdo->GetContext() );   // Scope pane must be current context

    //
    // Place our folder into the scope pane
    //
    ZeroMemory(&sdi, sizeof(SCOPEDATAITEM) );
    sdi.mask       = SDI_STR       |   // Displayname is valid
                     SDI_PARAM     |   // lParam is valid
                     SDI_IMAGE     |   // nImage is valid
                     SDI_OPENIMAGE |   // nOpenImage is valid
                     SDI_PARENT;
    sdi.relativeID  = (HSCOPEITEM)param;
    sdi.nImage      = 0;
    sdi.nOpenImage  = 1;
    sdi.displayname = MMC_CALLBACK;
    sdi.lParam      = (LPARAM)1;       // The cookie

    hr = m_ipConsoleNameSpace->InsertItem( &sdi );
  }
  return hr;

} // end OnExpand()


COMPDATA.H

// compdata.h : IComponentData Interface to communicate with MMC for scope pane use
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#if !defined(AFX_COMPDATA_H__CF8CC7F0_0CFE_11D1_8F07_000000000000__INCLUDED_)
#define AFX_COMPDATA_H__CF8CC7F0_0CFE_11D1_8F07_000000000000__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include "resource.h"        // main symbols
#include <mmc.h>

/////////////////////////////////////////////////////////////////////////////
// CComponentData - This class is the interface to handle anything to do
//                  with the scope pane. MMC calls the IComponent interfaces.
//                  This class keeps a few pointers to interfaces that MMC
//                  implements.

class CComponentData :
public IComponentData,
public CComObjectRoot,
public CComCoClass<CComponentData,&CLSID_ComponentData>
{
public:
CComponentData();
~CComponentData();

BEGIN_COM_MAP(CComponentData)
COM_INTERFACE_ENTRY(IComponentData)
END_COM_MAP()

DECLARE_NOT_AGGREGATABLE(CComponentData)
DECLARE_REGISTRY_RESOURCEID(IDR_ComponentData)

//
// IComponentData methods
//
STDMETHOD(CompareObjects)(LPDATAOBJECT lpDataObjectA, LPDATAOBJECT lpDataObjectB);
STDMETHOD(GetDisplayInfo)(LPSCOPEDATAITEM pItem);
STDMETHOD(QueryDataObject)(long cookie, DATA_OBJECT_TYPES type, LPDATAOBJECT * ppDataObject);
STDMETHOD(Notify)(LPDATAOBJECT lpDataObject, MMC_NOTIFY_TYPE event, long arg, long param);
STDMETHOD(CreateComponent)(LPCOMPONENT * ppComponent);
STDMETHOD(Initialize)(LPUNKNOWN pUnknown);
  STDMETHOD(Destroy)();

  //
  //  Methods to support IComponentData
  //
private:
    // Notify event handling functions
    HRESULT OnExpand(LPDATAOBJECT lpDataObject, long arg, long param);

    LPCONSOLENAMESPACE  m_ipConsoleNameSpace;  // Pointer name space interface
    LPCONSOLE           m_ipConsole;           // Pointer to the console interface
    HBITMAP             m_hbmp16x16;           // The strip of tree icons
};

#endif // !defined(AFX_COMPDATA_H__CF8CC7F0_0CFE_11D1_8F07_000000000000__INCLUDED_)


DATAOBJ.CPP

/////////////////////////////////////////////////////////////////////////////
// dataobj.cpp : Implementation of IDataObject for data communication
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#include "stdafx.h"
#include "disk.h"
#include "dataobj.h"

//
// This is the minimum set of clipboard formats we must implement.
// MMC uses these to get necessary information from our snapin about
// our nodes.
//
UINT CDataObject::s_cfInternal    = 0;
UINT CDataObject::s_cfDisplayName = 0;
UINT CDataObject::s_cfNodeType    = 0;
UINT CDataObject::s_cfSnapinClsid = 0;

// Generated with uuidgen. Each node must have a GUID associated with it.
// This one is for the main root node.
const GUID GUID_RootNode = /* 570f713a-0f57-11d1-a194-00c04fc3092f */
{
    0x570f713a,
    0x0f57,
    0x11d1,
    {0xa1, 0x94, 0x00, 0xc0, 0x4f, 0xc3, 0x09, 0x2f}
};

// Generated with uuidgen. Each node must have a GUID associated with it.
// This one is for the child under the main root node.
const GUID GUID_MainNode = { /* 50c4fbdc-1f02-11d1-a1ae-00c04fc3092f */
    0x50c4fbdc,
    0x1f02,
    0x11d1,
    {0xa1, 0xae, 0x00, 0xc0, 0x4f, 0xc3, 0x09, 0x2f}
};

#define CF_SNAPIN_INTERNAL L"MMC_SNAPIN_MACHINE_NAME"

/////////////////////////////////////////////////////////////////////////////
// CDataObject - This class is used to pass data back and forth with MMC. It
//               uses a standard interface, IDataObject to acomplish this. Refer
//               to OLE documentation for a description of clipboard formats and
//               the IdataObject interface.

//============================================================================
//
// Constructor and Destructor
//

//----------------------------------------------------------------------------
//  CDataObject::CDataObject
//
CDataObject::CDataObject()
{

  m_ulCookie = 0;
  m_Context  = CCT_UNINITIALIZED;
  m_Type     = COOKIE_IS_ROOT;

  // These are the clipboard formats that we must supply at a minimum.
  // mmc.h actually defined these. We can make up our own to use for
  // other reasons. We don't need any others at this time.
  s_cfInternal    = RegisterClipboardFormat(CF_SNAPIN_INTERNAL);
  s_cfDisplayName = RegisterClipboardFormat(CCF_DISPLAY_NAME);
  s_cfNodeType    = RegisterClipboardFormat(CCF_NODETYPE);
  s_cfSnapinClsid = RegisterClipboardFormat(CCF_SNAPIN_CLASSID);

} // end Constructor()

//---------------------------------------------------------------------------
//  CDataObject::~CDataObject
//
CDataObject::~CDataObject()
{
  if ( COOKIE_IS_STATUS == m_Type )
  {
     _ASSERT(m_ulCookie);
  }
} // end Destructor()

/////////////////////////////////////////////////////////////////////////////
// IDataObject implementation
//

//---------------------------------------------------------------------------
//  Fill the hGlobal in pmedium with the requested data
//
STDMETHODIMP
CDataObject::GetDataHere
(
  FORMATETC *pFormatEtc,     // [in]  Pointer to the FORMATETC structure
  STGMEDIUM *pMedium         // [out] Pointer to the STGMEDIUM structure
)
{
  HRESULT hr = DV_E_FORMATETC;         // Unknown format
  const   CLIPFORMAT cf = pFormatEtc->cfFormat;
  IStream *pStream = NULL;

  pMedium->pUnkForRelease = NULL;      // by OLE spec

  do                                   // Write data to the stream based
  {                                    // of the clipformat
    hr = CreateStreamOnHGlobal( pMedium->hGlobal, FALSE, &pStream );
    if ( FAILED(hr) )
      return hr;                       // Minimal error checking

    if (cf == s_cfDisplayName)
    {
      hr = _WriteDisplayName( pStream );
    }
    else if (cf == s_cfInternal)
    {
      hr = _WriteInternal( pStream );
    }
    else if (cf == s_cfNodeType)
    {
      hr = _WriteNodeType( pStream );
    }
    else if (cf == s_cfSnapinClsid)
    {
      hr = _WriteClsid( pStream );
    }
  } while (0);

  pStream->Release();

  return hr;

} // end GetDataHere()

/////////////////////////////////////////////////////////////////////////////
//  Support methods
//

//---------------------------------------------------------------------------
//  Write the appropriate GUID to the stream
//
HRESULT
CDataObject::_WriteNodeType
(
  IStream* pStream           // [in] Stream we are writing to
)
{
  const GUID *pGuid = NULL;

  switch (m_Type)
  {
    case COOKIE_IS_ROOT:
      pGuid = &GUID_RootNode;
      break;

    case COOKIE_IS_STATUS:
      pGuid = &GUID_MainNode;
      break;

    default:
     _ASSERT( FALSE );
     return E_UNEXPECTED;
  }

  return pStream->Write( (PVOID)pGuid, sizeof(GUID), NULL );

} // end _WriteNodeType()

//---------------------------------------------------------------------------
// Writes the display name to the stream
//
HRESULT
CDataObject::_WriteDisplayName
(
  IStream* pStream           // [in] Stream we are writing to
)
{
  LPWSTR  pwszName;

  if (m_ulCookie)
  {
    pwszName = L"Change This";
  }
  else                                 // Null cookie is root
  {
    pwszName = L"Hello MMC";
  }

  ULONG ulSizeofName = wcslen(pwszName);
  ulSizeofName++;                      // Count null
  ulSizeofName *= sizeof(WCHAR);

  return pStream->Write(pwszName, ulSizeofName, NULL);

} // end _WriteDisplayName()

//---------------------------------------------------------------------------
//  Writes a pointer to this data object to the stream
//
HRESULT
CDataObject::_WriteInternal
(
  IStream* pStream           // [in] Stream we are writing to
)
{
  CDataObject *pThis = this;
  return pStream->Write( &pThis, sizeof(CDataObject*), NULL );

} // end _WriteInternal

//---------------------------------------------------------------------------
//  Writes the Class ID to the stream
//
HRESULT
CDataObject::_WriteClsid
(
  IStream* pStream           // [in] Stream we are writing to
)
{
  return pStream->Write( &CLSID_ComponentData,
                         sizeof(CLSID_ComponentData),
                         NULL
                       );
} // end _WriteClsid()

//---------------------------------------------------------------------------
//  The cookie is what ever we decide it is going to be.
//  This is being called from QuerDataObject. Refer to that code.
//
VOID
CDataObject::SetCookie
(
  ULONG              ulCookie, // [in] Unique indentifier
  DATA_OBJECT_TYPES  Context,  // [in] Context of the caller
  COOKIETYPE         Type      // [in] Type of cookie
)
{
  _ASSERT(!m_ulCookie);
  m_ulCookie = ulCookie;
  m_Type = Type;
  m_Context = Context;

  if (m_Type == COOKIE_IS_STATUS)
  {
    _ASSERT(m_ulCookie);
  }
} // end SetCookie()

/////////////////////////////////////////////////////////////////////////////
// IUnknown implementation
//

//---------------------------------------------------------------------------
//  Standard implementation
//
STDMETHODIMP
CDataObject::QueryInterface
(
  REFIID  riid,
  LPVOID *ppvObj
)
{
  // TRACE_METHOD(CDataObject, QueryInterface);
  HRESULT hr = S_OK;

  do
  {
    if (NULL == ppvObj)
    {
      hr = E_INVALIDARG;
      break;
    }

    if (IsEqualIID(riid, IID_IUnknown))
    {
      *ppvObj = (IUnknown *)(IDataObject *)this;
    }
    else if (IsEqualIID(riid, IID_IDataObject))
    {
      *ppvObj = (IUnknown *)(IDataObject *)this;
    }
    else
    {
      hr = E_NOINTERFACE;
      *ppvObj = NULL;
      break;
    }

    //
    // If we got this far we are handing out a new interface pointer on
    // this object, so addref it.
    //

    AddRef();
  } while (0);

  return hr;

} // end QueryInterface()

//---------------------------------------------------------------------------
//  Standard implementation
//
STDMETHODIMP_(ULONG)
CDataObject::AddRef()
{
  return InterlockedIncrement((LONG *) &m_cRefs);
}

//---------------------------------------------------------------------------
//  Standard implementation
//
STDMETHODIMP_(ULONG)
CDataObject::Release()
{
  ULONG cRefsTemp;

  cRefsTemp = InterlockedDecrement((LONG *)&m_cRefs);

  if (0 == cRefsTemp)
  {
    delete this;
  }

  return cRefsTemp;
}


DATAOBJ.H

/////////////////////////////////////////////////////////////////////////////
// dataobj.h : IDataObject Interface to communicate data
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#ifndef __DATAOBJ_H_
#define __DATAOBJ_H_

#include <mmc.h>

//
// Defines, Types etc...
//

typedef enum tagCOOKIETYPE
{
  COOKIE_IS_ROOT,
  COOKIE_IS_STATUS

} COOKIETYPE;

class CComponentData;        // Forward declaration

/////////////////////////////////////////////////////////////////////////////
// CDataObject - This class is used to pass data back and forth with MMC. It
//               uses a standard interface, IDataObject to acomplish this. Refer
//               to OLE documentation for a description of clipboard formats and
//               the IdataObject interface.

class CDataObject:
public IDataObject,
  public CComObjectRoot
{
public:

// ATL Maps
DECLARE_NOT_AGGREGATABLE(CDataObject)

BEGIN_COM_MAP(CDataObject)
COM_INTERFACE_ENTRY(IDataObject)
END_COM_MAP()

  CDataObject();
  ~CDataObject();

  VOID SetCookie(ULONG ulCookie, DATA_OBJECT_TYPES Type, COOKIETYPE ct);

  //
  // IUnknown overrides
  //

  STDMETHOD(QueryInterface) (REFIID riid, LPVOID FAR* ppvObj);
  STDMETHOD_(ULONG, AddRef) ();
  STDMETHOD_(ULONG, Release) ();

  //
  // IDataObject implementation methods
  //

  STDMETHOD(GetDataHere)(FORMATETC *pformatetc, STGMEDIUM *pmedium);

  // Not Implemented
private:
  STDMETHOD(GetData)(LPFORMATETC lpFormatetcIn, LPSTGMEDIUM lpMedium)
  { return E_NOTIMPL; };

  STDMETHOD(EnumFormatEtc)(DWORD dwDirection, LPENUMFORMATETC* ppEnumFormatEtc)
  { return E_NOTIMPL; };

  STDMETHOD(QueryGetData)(LPFORMATETC lpFormatetc)
  { return E_NOTIMPL; };

  STDMETHOD(GetCanonicalFormatEtc)(LPFORMATETC lpFormatetcIn, LPFORMATETC lpFormatetcOut)
  { return E_NOTIMPL; };

  STDMETHOD(SetData)(LPFORMATETC lpFormatetc, LPSTGMEDIUM lpMedium, BOOL bRelease)
  { return E_NOTIMPL; };

  STDMETHOD(DAdvise)(LPFORMATETC lpFormatetc, DWORD advf, LPADVISESINK pAdvSink, LPDWORD pdwConnection)
  { return E_NOTIMPL; };

  STDMETHOD(DUnadvise)(DWORD dwConnection)
  { return E_NOTIMPL; };

  STDMETHOD(EnumDAdvise)(LPENUMSTATDATA* ppEnumAdvise)
  { return E_NOTIMPL; };

  //
  // Non-interface member functions
  //
public:
  DATA_OBJECT_TYPES  GetContext()    { return m_Context; }
  COOKIETYPE         GetCookieType() { return m_Type; }
  ULONG CDataObject::GetCookie()     { return m_ulCookie; }

private:
  HRESULT  _WriteInternal(IStream *pstm);
  HRESULT  _WriteDisplayName(IStream *pstm);
  HRESULT  _WriteNodeType(IStream *pstm);
  HRESULT  _WriteClsid(IStream *pstm);

  ULONG               m_cRefs;     // object refcount
  ULONG               m_ulCookie;  // what this obj refers to
  DATA_OBJECT_TYPES   m_Context;   // context in which this was created
  COOKIETYPE          m_Type;      // how to interpret _ulCookie

public:
  // At a minimum we have to implement these clipboard formats
  // to keep MMC happy. We will ASSERT if we don't
  static UINT s_cfInternal;       // Our custom clipboard format
  static UINT s_cfDisplayName;
  static UINT s_cfNodeType;
  static UINT s_cfSnapinClsid;
};

#endif // __DATAOBJ_H_


DISK.CPP

/////////////////////////////////////////////////////////////////////////////
// disk.cpp : Implementation of DLL Exports.
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

// Note: Proxy/Stub Information
//To build a separate proxy/stub DLL,
//run nmake -f diskps.mk in the project directory.

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "disk.h"

#include "disk_i.c"
#include "globals.h"
#include "compdata.h"

CComModule _Module;
                                       // Default entry point
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_ComponentData, CComponentData)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
  if ( DLL_PROCESS_ATTACH == dwReason )
  {
    g_hinst = hInstance;               // Store global instance handle
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
  }
  else if ( DLL_PROCESS_DETACH == dwReason )
_Module.Term();

  return TRUE;                         // OK

} // end DllMain()

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
  return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
  return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry
// If we don't get these things into the registry, then MMC will not see us
// and we will not appear in the "Add Snapin" dialog.
// We error check every return value in both DllRegisterServer() and
// DllUnRegisterServer().  You may want to do this only in your debug
// version to reduce code size, at the expense of making troubleshooting
// more difficult
//
STDAPI DllRegisterServer(void)
{
  HKEY hMmcParentKey  = NULL;
  HKEY hDiskParentKey = NULL;
  HKEY hStandAloneKey = NULL;
  HKEY hNodeTypesKey  = NULL;
  HKEY hTempNodeKey   = NULL;
  LONG nErr           = 0;

  //DebugBreak();                  // Uncomment this to step through registration

  // Open the MMC Parent key
  nErr = RegOpenKey( HKEY_LOCAL_MACHINE,
                 L"Software\\Microsoft\\MMC\\SnapIns",
         &hMmcParentKey
       );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Open MMC Parent Key Failed" );

  // Create the ID for our ICompnentData Interface
  // The ID was generated for us, because we used a Wizard to create the app.
  // Take the ID for IComponentData in the IDL file.
  //
  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  // Make sure you change this if you use this code as a starting point!
  // Change other IDs as well below!
  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  nErr = RegCreateKey( hMmcParentKey,
                   L"{CF8CC7EF-0CFE-11D1-8F07-000000000000}",
       &hDiskParentKey
         );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Open Disk Parent Key Failed" );

  nErr = RegSetValueEx( hDiskParentKey,
                    L"NameString",
            0,
        REG_SZ,
        (LPBYTE)L"Disk Sample",
        sizeof(L"Disk Sample")
          );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Set NameString Failed" );

  nErr = RegSetValueEx( hDiskParentKey,
                    L"NodeType",
        0,
        REG_SZ,
        (LPBYTE)L"{570F713A-0F57-11D1-A194-00C04FC3092F}",
        sizeof(L"{570F713A-0F57-11D1-A194-00C04FC3092F}")
          );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Set NodeType Failed" );

  // We are a stand alone snapin, so set the key for this
  nErr = RegCreateKey( hDiskParentKey, L"StandAlone", &hStandAloneKey);
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Create StandAlone Key Failed" );

  // StandAlone has no children, so close it
  nErr = RegCloseKey( hStandAloneKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close StandAlone Failed" );

  // Set the node types that appear in our snapin
  nErr = RegCreateKey( hDiskParentKey, L"NodeTypes", &hNodeTypesKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Create NodeTypes Key Failed" );

  // Here is our root node.  Used uuidgen to get it
  nErr = RegCreateKey( hNodeTypesKey,
                   L"{570F713A-0F57-11D1-A194-00C04FC3092F}",
       &hTempNodeKey
         );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Create RootNode Key Failed" );

  nErr = RegSetValueEx( hTempNodeKey,
                    NULL,
            0,
        REG_SZ,
        (LPBYTE)L"Root Node",
        sizeof(L"Root Node")
          );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Set Root Node String Failed" );

  nErr = RegCloseKey( hTempNodeKey );    // Close it so we can reuse handle
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close RootNode Failed" );

  // Here is our child node under the root node. Used uuidgen to get it
  nErr = RegCreateKey( hNodeTypesKey,
                   L"{50C4FBDC-1F02-11D1-A1AE-00C04FC3092F}",
       &hTempNodeKey
         );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Create Child Node Key Failed" );

  nErr = RegSetValueEx( hTempNodeKey,
                    NULL,
        0,
        REG_SZ,
        (LPBYTE)L"Child Under Root Node",
        sizeof(L"Child Under Root Node")
          );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Set Child Node String Failed" );

  nErr = RegCloseKey( hTempNodeKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close Child Node Key Failed" );

  nErr = RegCloseKey( hNodeTypesKey  );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close Node Types Key Failed" );

  nErr = RegCloseKey( hDiskParentKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close Disk Parent Key Failed" );

  nErr = RegCloseKey( hMmcParentKey  );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close MMC Parent Key Failed" );

  // Registers object, typelib and all interfaces in typelib
  return _Module.RegisterServer(TRUE);

} // end DllRegisterServer()

//---------------------------------------------------------------------------
// DllUnregisterServer:  Since we are a good citizen, delete all the keys
//                       we added to the MMC keys in the registry as well as
//                       the normal unregistering of our com objects.
//
STDAPI DllUnregisterServer(void)
{
  HKEY hMmcParentKey  = NULL;          // MMC parent key
  HKEY hDiskParentKey = NULL;          // Our Snap-In key  - has children
  HKEY hNodeTypesKey  = NULL;          // Our NodeType key - has children
  LONG nErr           = 0;

  // DebugBreak();                       // Uncomment this to step through UnRegister

  // Open the MMC parent key
  nErr = RegOpenKey( HKEY_LOCAL_MACHINE,
                     L"Software\\Microsoft\\MMC\\SnapIns",
                     &hMmcParentKey
                   );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Open MMC Parent Key Failed"  );

  // Open our Parent key
  nErr = RegOpenKey( hMmcParentKey,
                     L"{CF8CC7EF-0CFE-11D1-8F07-000000000000}",
                     &hDiskParentKey
                   );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Open Disk Parent Key Failed" );

  // Now open the NodeTypes key
  nErr = RegOpenKey( hDiskParentKey,       // Handle of parent key
                     L"NodeTypes",         // Name of key to open
                     &hNodeTypesKey        // Handle to newly opened key
                   );
  if( ERROR_SUCCESS != nErr )
     DisplayError( GetLastError(), L"Open NodeTypes Key Failed"  );

  // Delete the root node key
  nErr = RegDeleteKey( hNodeTypesKey, L"{570F713A-0F57-11D1-A194-00C04FC3092F}" );
  if( ERROR_SUCCESS != nErr )
     DisplayError( GetLastError(), L"Delete Root Node Key Failed"  );

  // Delete the child node key
  nErr = RegDeleteKey( hNodeTypesKey, L"{50C4FBDC-1F02-11D1-A1AE-00C04FC3092F}" );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Delete Child Node Key Failed"  );

  // Close the node type key so we can delete it
  nErr = RegCloseKey( hNodeTypesKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close NodeTypes Key failed"  );

  // Delete the NodeTypes key
  nErr = RegDeleteKey( hDiskParentKey, L"NodeTypes" );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Delete NodeTypes Key failed"  );

  // StandAlone key has no children so we can delete it now
  nErr = RegDeleteKey( hDiskParentKey, L"StandAlone" );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Delete StandAlone Key Failed"  );

  // Close our Parent Key
  nErr = RegCloseKey( hDiskParentKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close Disk Parent Key Failed"  );

  // Now we can delete our Snap-In key since the children are gone
  nErr = RegDeleteKey( hMmcParentKey, L"{CF8CC7EF-0CFE-11D1-8F07-000000000000}" );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Delete Disk Parent Key Failed"  );

  nErr = RegCloseKey( hMmcParentKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close MMC Parent Key Failed"  );

  // UnRegisters object, typelib and all interfaces in typelib
  return _Module.UnregisterServer();

} // end DllUnregisterServer()


DISK.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 3.01.75 */
/* at Mon Oct 06 11:37:34 1997
 */
/* Compiler settings for disk.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"

#ifndef __disk_h__
#define __disk_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __ComponentData_FWD_DEFINED__
#define __ComponentData_FWD_DEFINED__

#ifdef __cplusplus
typedef class ComponentData ComponentData;
#else
typedef struct ComponentData ComponentData;
#endif /* __cplusplus */

#endif /* __ComponentData_FWD_DEFINED__ */

/* header files for imported files */
#include "oaidl.h"
#include "ocidl.h"
#include "mmc.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __DISKLib_LIBRARY_DEFINED__
#define __DISKLib_LIBRARY_DEFINED__

/****************************************
 * Generated header for library: DISKLib
 * at Mon Oct 06 11:37:34 1997
 * using MIDL 3.01.75
 ****************************************/
/* [helpstring][version][uuid] */

EXTERN_C const IID LIBID_DISKLib;

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_ComponentData;

class DECLSPEC_UUID("CF8CC7EF-0CFE-11D1-8F07-000000000000")
ComponentData;
#endif
#endif /* __DISKLib_LIBRARY_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


DISK.IDL

// disk.idl : IDL source for disk.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (disk.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";
// Make sure to put this in here
import "mmc.idl";

[
uuid(CF8CC7E0-0CFE-11D1-8F07-000000000000),
version(1.0),
helpstring("disk 1.0 Type Library")
]
library DISKLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(CF8CC7EF-0CFE-11D1-8F07-000000000000),
helpstring("ComponentData Class")
]
coclass ComponentData
{
[default] interface IComponentData;
};

};


DISK_I.C

/* this file contains the actual definitions of */
/* the IIDs and CLSIDs */

/* link this file in with the server and any clients */

/* File created by MIDL compiler version 3.01.75 */
/* at Mon Oct 06 11:37:34 1997
 */
/* Compiler settings for disk.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#ifdef __cplusplus
extern "C"{
#endif

#ifndef __IID_DEFINED__
#define __IID_DEFINED__

typedef struct _IID
{
    unsigned long x;
    unsigned short s1;
    unsigned short s2;
    unsigned char  c[8];
} IID;

#endif // __IID_DEFINED__

#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED

const IID LIBID_DISKLib = {0xCF8CC7E0,0x0CFE,0x11D1,{0x8F,0x07,0x00,0x00,0x00,0x00,0x00,0x00}};

const CLSID CLSID_ComponentData = {0xCF8CC7EF,0x0CFE,0x11D1,{0x8F,0x07,0x00,0x00,0x00,0x00,0x00,0x00}};

#ifdef __cplusplus
}
#endif


DISK_P.C

/* this ALWAYS GENERATED file contains the proxy stub code */

/* File created by MIDL compiler version 3.01.75 */
/* at Mon Aug 04 20:31:09 1997
 */
/* Compiler settings for disk.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )

#define USE_STUBLESS_PROXY

#include "rpcproxy.h"
#include "disk.h"

#define TYPE_FORMAT_STRING_SIZE   1
#define PROC_FORMAT_STRING_SIZE   1

typedef struct _MIDL_TYPE_FORMAT_STRING
    {
    short          Pad;
    unsigned char  Format[ TYPE_FORMAT_STRING_SIZE ];
    } MIDL_TYPE_FORMAT_STRING;

typedef struct _MIDL_PROC_FORMAT_STRING
    {
    short          Pad;
    unsigned char  Format[ PROC_FORMAT_STRING_SIZE ];
    } MIDL_PROC_FORMAT_STRING;

extern const MIDL_TYPE_FORMAT_STRING __MIDL_TypeFormatString;
extern const MIDL_PROC_FORMAT_STRING __MIDL_ProcFormatString;

/* Object interface: IUnknown, ver. 0.0,
   GUID={0x00000000,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}} */

/* Object interface: IComponentData, ver. 0.0,
   GUID={0xCF8CC7ED,0x0CFE,0x11D1,{0x8F,0x07,0x00,0x00,0x00,0x00,0x00,0x00}} */

extern const MIDL_STUB_DESC Object_StubDesc;

#pragma code_seg(".orpc")
CINTERFACE_PROXY_VTABLE(3) _IComponentDataProxyVtbl =
{
    0,
    &IID_IComponentData,
    IUnknown_QueryInterface_Proxy,
    IUnknown_AddRef_Proxy,
    IUnknown_Release_Proxy
};

const CInterfaceStubVtbl _IComponentDataStubVtbl =
{
    &IID_IComponentData,
    0,
    3,
    0, /* pure interpreted */
    CStdStubBuffer_METHODS
};

/* Object interface: IExtendPropertySheet, ver. 0.0,
   GUID={0xCF8CC7EE,0x0CFE,0x11D1,{0x8F,0x07,0x00,0x00,0x00,0x00,0x00,0x00}} */

extern const MIDL_STUB_DESC Object_StubDesc;

#pragma code_seg(".orpc")

static const MIDL_STUB_DESC Object_StubDesc =
    {
    0,
    NdrOleAllocate,
    NdrOleFree,
    0,
    0,
    0,
    0,
    0,
    __MIDL_TypeFormatString.Format,
    0, /* -error bounds_check flag */
    0x20000, /* Ndr library version */
    0,
    0x301004b, /* MIDL Version 3.1.75 */
    0,
    0,
    0,  /* Reserved1 */
    0,  /* Reserved2 */
    0,  /* Reserved3 */
    0,  /* Reserved4 */
    0   /* Reserved5 */
    };

CINTERFACE_PROXY_VTABLE(3) _IExtendPropertySheetProxyVtbl =
{
    0,
    &IID_IExtendPropertySheet,
    IUnknown_QueryInterface_Proxy,
    IUnknown_AddRef_Proxy,
    IUnknown_Release_Proxy
};

const CInterfaceStubVtbl _IExtendPropertySheetStubVtbl =
{
    &IID_IExtendPropertySheet,
    0,
    3,
    0, /* pure interpreted */
    CStdStubBuffer_METHODS
};

#pragma data_seg(".rdata")

#if !defined(__RPC_WIN32__)
#error  Invalid build platform for this stub.
#endif

#if !(TARGET_IS_NT40_OR_LATER)
#error You need a Windows NT 4.0 or later to run this stub because it uses these features:
#error   -Oif or -Oicf.
#error However, your C/C++ compilation flags indicate you intend to run this app on earlier systems.
#error This app will die there with the RPC_X_WRONG_STUB_VERSION error.
#endif

static const MIDL_PROC_FORMAT_STRING __MIDL_ProcFormatString =
    {
        0,
        {

0x0
        }
    };

static const MIDL_TYPE_FORMAT_STRING __MIDL_TypeFormatString =
    {
        0,
        {

0x0
        }
    };

const CInterfaceProxyVtbl * _disk_ProxyVtblList[] =
{
    ( CInterfaceProxyVtbl *) &_IComponentDataProxyVtbl,
    ( CInterfaceProxyVtbl *) &_IExtendPropertySheetProxyVtbl,
    0
};

const CInterfaceStubVtbl * _disk_StubVtblList[] =
{
    ( CInterfaceStubVtbl *) &_IComponentDataStubVtbl,
    ( CInterfaceStubVtbl *) &_IExtendPropertySheetStubVtbl,
    0
};

PCInterfaceName const _disk_InterfaceNamesList[] =
{
    "IComponentData",
    "IExtendPropertySheet",
    0
};

#define _disk_CHECK_IID(n)IID_GENERIC_CHECK_IID( _disk, pIID, n)

int __stdcall _disk_IID_Lookup( const IID * pIID, int * pIndex )
{
    IID_BS_LOOKUP_SETUP

    IID_BS_LOOKUP_INITIAL_TEST( _disk, 2, 1 )
    IID_BS_LOOKUP_RETURN_RESULT( _disk, 2, *pIndex )

}

const ExtendedProxyFileInfo disk_ProxyFileInfo =
{
    (PCInterfaceProxyVtblList *) & _disk_ProxyVtblList,
    (PCInterfaceStubVtblList *) & _disk_StubVtblList,
    (const PCInterfaceName * ) & _disk_InterfaceNamesList,
    0, // no delegation
    & _disk_IID_Lookup,
    2,
    2
};


DLLDATA.C

/*********************************************************
   DllData file -- generated by MIDL compiler

        DO NOT ALTER THIS FILE

   This file is regenerated by MIDL on every IDL file compile.

   To completely reconstruct this file, delete it and rerun MIDL
   on all the IDL files in this DLL, specifying this file for the
   /dlldata command line option

*********************************************************/

#include <rpcproxy.h>

#ifdef __cplusplus
extern "C"   {
#endif

EXTERN_PROXY_FILE( disk )

PROXYFILE_LIST_START
/* Start of list */
  REFERENCE_PROXY_FILE( disk ),
/* End of list */
PROXYFILE_LIST_END

DLLDATA_ROUTINES( aProxyFileList, GET_DLL_CLSID )

#ifdef __cplusplus
}  /*extern "C" */
#endif

/* end of generated dlldata file */


GLOBALS.H

/////////////////////////////////////////////////////////////////////////////
// globals.h : Utility functions and global variables
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#ifndef __GLOBALS_H_
#define __GLOBALS_H_

#include "dataobj.h"

extern HINSTANCE    g_hinst;

/////////////////////////////////////////////////////////////////////////////
// We need a few functions to help work with dataobjects and clipboard formats

HRESULT ExtractFromDataObject(LPDATAOBJECT lpDataObject,UINT cf,ULONG cb,HGLOBAL *phGlobal);
CDataObject *ExtractOwnDataObject(LPDATAOBJECT lpDataObject);
VOID DisplayError( LONG nErrorCode, LPWSTR wszDlgTitle );

#endif // __GLOBALS_H_


MMC.IDL

import "oaidl.idl";

///////////////////////////////////////////////////////////////////////////////
/// Interfaces implement by the Common Console's Node Manager

interface IConsole;
interface IHeaderCtrl;
interface IToolbar;
interface IImageList;
interface IResultData;
interface IConsoleNameSpace;
interface IPropertySheetProvider;
interface IPropertySheetCallback;
interface IContextMenuProvider;
interface IContextMenuCallback;
interface IControlbar;
interface IConsoleVerb;
interface IMenuButton;
interface IQuickFilter;

///////////////////////////////////////////////////////////////////////////////
/// Interfaces implement by the Snap-in server

interface IComponent;
interface IComponentData;
interface IExtendPropertySheet;
interface IExtendContextMenu;
interface IExtendControlbar;
interface IResultDataCompare;
interface ISnapinAbout;
interface IResultOwnerData;

///////////////////////////////////////////////////////////////////////////////
/// Type definitions

typedef IConsole*                   LPCONSOLE;
typedef IHeaderCtrl*                LPHEADERCTRL;
typedef IToolbar*                   LPTOOLBAR;
typedef IImageList*                 LPIMAGELIST;
typedef IResultData*                LPRESULTDATA;
typedef IConsoleNameSpace*          LPCONSOLENAMESPACE;
typedef IPropertySheetProvider*     LPPROPERTYSHEETPROVIDER;
typedef IPropertySheetCallback*     LPPROPERTYSHEETCALLBACK;
typedef IContextMenuProvider*       LPCONTEXTMENUPROVIDER;
typedef IContextMenuCallback*       LPCONTEXTMENUCALLBACK;
typedef IControlbar*                LPCONTROLBAR;
typedef IConsoleVerb*               LPCONSOLEVERB;
typedef IMenuButton*                LPMENUBUTTON;

typedef IComponent*                 LPCOMPONENT;
typedef IComponentData*             LPCOMPONENTDATA;
typedef IExtendPropertySheet*       LPEXTENDPROPERTYSHEET;
typedef IExtendContextMenu*         LPEXTENDCONTEXTMENU;
typedef IExtendControlbar*          LPEXTENDCONTROLBAR;
typedef IResultDataCompare*         LPRESULTDATACOMPARE;
typedef ISnapinAbout*               LPSNAPABOUT;
typedef IResultOwnerData*           LPRESULTOWNERDATA;

///////////////////////////////////////////////////////////////////////////////
// Published Common Console ListView constants

#define LVS_ICON                0x0000
#define LVS_REPORT              0x0001
#define LVS_SMALLICON           0x0002
#define LVS_LIST                0x0003
#define LVSICF_NOINVALIDATEALL  0x00000001
#define LVSICF_NOSCROLL         0x00000002

const long  MMCLV_AUTO = -1;
const long  MMCLV_NOPARAM = -2;
const long  MMCLV_NOICON = -1;
const long  MMCLV_VIEWSTYLE_ICON = LVS_ICON;
const long  MMCLV_VIEWSTYLE_SMALLICON = LVS_SMALLICON;
const long  MMCLV_VIEWSTYLE_LIST = LVS_LIST;
const long  MMCLV_VIEWSTYLE_REPORT = LVS_REPORT;
const void* MMCLV_NOPTR = 0x0;
const long  MMCLV_UPDATE_NOINVALIDATEALL = LVSICF_NOINVALIDATEALL;
const long  MMCLV_UPDATE_NOSCROLL = LVSICF_NOSCROLL;

static unsigned short* MMC_CALLBACK = ((unsigned short *)(-1));

///////////////////////////////////////////////////////////////////////////////
// ResultData and Scope Data item structures.

typedef long    HSCOPEITEM;
typedef long    COMPONENTID;
typedef long    HRESULTITEM;

const DWORD RDI_STR     = 0x0002;
const DWORD RDI_IMAGE   = 0x0004;
const DWORD RDI_STATE   = 0x0008;
const DWORD RDI_PARAM   = 0x0010;
const DWORD RDI_INDEX   = 0x0020;
const DWORD RDI_INDENT  = 0x0040;

typedef enum _MMC_RESULT_VIEW_STYLE
{
    MMC_SINGLESEL       = 0x0001,
    MMC_SHOWSELALWAYS   = 0x0002,
    MMC_NOSORTHEADER    = 0x0004,

} MMC_RESULT_VIEW_STYLE;

// List view with single select
const long MMC_VIEW_OPTIONS_NONE            = 0x0000;

// NO List view. Only custom views.
const long MMC_VIEW_OPTIONS_NOLISTVIEWS     = 0x0001;

// List view with multi select.
const long MMC_VIEW_OPTIONS_MULTISELECT     = 0x0002;

//List view with user owned data
const long MMC_VIEW_OPTIONS_OWNERDATALIST   = 0x0004;

//List view with filter header
const long MMC_VIEW_OPTIONS_FILTERED        = 0x0008;

// Create new control (don't use cached)
const long MMC_VIEW_OPTIONS_CREATENEW       = 0x0010;

// Property sheet options
const DWORD MMC_PSO_NOAPPLYNOW              = 0x00000001;
const DWORD MMC_PSO_HASHELP                 = 0x00000002;
const DWORD MMC_PSO_NEWWIZARDTYPE           = 0x00000004;

// Type of controls that can be attached to the IControlbar
typedef enum  _MMC_CONTROL_TYPE
{
    TOOLBAR,
    MENUBUTTON,
    COMBOBOXBAR
} MMC_CONTROL_TYPE;

///////////////////////////////////////////////////////////////////////////////
// Console commands - These should be reviewed
//
typedef enum _MMC_CONSOLE_VERB
{
    MMC_VERB_NONE            = 0x0000,
    MMC_VERB_OPEN            = 0x8000,
    MMC_VERB_COPY            = 0x8001,
    MMC_VERB_PASTE           = 0x8002,
    MMC_VERB_DELETE          = 0x8003,
    MMC_VERB_PROPERTIES      = 0x8004,
    MMC_VERB_RENAME          = 0x8005,
    MMC_VERB_REFRESH         = 0x8006,
    MMC_VERB_PRINT           = 0x8007,

} MMC_CONSOLE_VERB;

// Structure to create buttons on the toolbar
typedef struct _MMCButton
{
    int      nBitmap;       // Offset into the bitmap list
    int      idCommand;     // Command ID return when the button is clicked
    BYTE     fsState;       // TBSTYLE_ENABLED ..etc
    BYTE     fsType;        // TBSTYLE_BUTTON || TBSTYLE_SEP
    LPOLESTR     lpButtonText;  // Text value for the Button
    LPOLESTR     lpTooltipText; // Text value for the tooltip

} MMCBUTTON;

typedef MMCBUTTON* LPMMCBUTTON;

typedef enum _MMC_BUTTON_STATE
{
    ENABLED         = 0x01,
    CHECKED         = 0x02,
    HIDDEN          = 0x04,
    INDETERMINATE   = 0x08,
    BUTTONPRESSED   = 0x10,

} MMC_BUTTON_STATE;

typedef struct _RESULTDATAITEM
{
    DWORD       mask;
    BOOL        bScopeItem;
    HRESULTITEM itemID;
    int         nIndex;
    int         nCol;
    LPOLESTR    str;
    int         nImage;
    UINT        nState;
    LPARAM      lParam;
    int         iIndent;
} RESULTDATAITEM;

typedef RESULTDATAITEM* LPRESULTDATAITEM;

// Find result item options
const DWORD RFI_PARTIAL = 0x0001;
const DWORD RFI_WRAP    = 0x0002;

typedef struct _RESULTFINDINFO
{
    LPOLESTR    psz;
    int         nStart;
    DWORD       dwOptions;
}
 RESULTFINDINFO;
typedef RESULTFINDINFO* LPRESULTFINDINFO;

//
// Sort Result Items options
//
const DWORD RSI_DESCENDING = 0x0001;

const DWORD SDI_STR         = 0x00002;
const DWORD SDI_IMAGE       = 0x00004;
const DWORD SDI_OPENIMAGE   = 0x00008;
const DWORD SDI_STATE       = 0x00010;
const DWORD SDI_PARAM       = 0x00020;
const DWORD SDI_CHILDREN    = 0x00040;

// The top 4 bit of the mask determines the relative position of this item,
// relative to the SCOPEDATAITEM::relativeID. By default it is the parent.

// For SDI_PARENT, SCOPEDATAITEM::relativeID is the HSCOPEITEM of the parent.
// As you can see by the SDI_PARENT value it is a no-op. Since by default
// SCOPEDATAITEM::relativeID is treated as the parents ID.
const DWORD SDI_PARENT      = 0x00000000;

// For SDI_PREVIOUS, SCOPEDATAITEM::relativeID is the HSCOPEITEM of the previous sibling
const DWORD SDI_PREVIOUS    = 0x10000000;

// For SDI_NEXT, SCOPEDATAITEM::relativeID is the HSCOPEITEM of the next sibling.
const DWORD SDI_NEXT        = 0x20000000;

// For SDI_PARENT, bit 27 determines whether the item is to be inserted as the
// first child. By default this item will inserted as the last child.
const DWORD SDI_FIRST       = 0x08000000;

typedef struct _SCOPEDATAITEM
{
    DWORD       mask;
    LPOLESTR    displayname;
    int         nImage;
    int         nOpenImage;
    UINT        nState;
    int         cChildren;
    LPARAM      lParam;
    HSCOPEITEM  relativeID;
    HSCOPEITEM  ID;
} SCOPEDATAITEM;

typedef SCOPEDATAITEM* LPSCOPEDATAITEM;

typedef enum _MMC_SCOPE_ITEM_STATE
{
    MMC_SCOPE_ITEM_STATE_NORMAL = 0x0001,        // Not bold. To set or get.
    MMC_SCOPE_ITEM_STATE_BOLD = 0x0002,          // To set or get.
    MMC_SCOPE_ITEM_STATE_EXPANDEDONCE = 0x0003,  // Only to get.

} MMC_SCOPE_ITEM_STATE;

typedef struct _CONTEXTMENUITEM
{
    LPWSTR      strName;
    LPWSTR      strStatusBarText;
    LONG        lCommandID;
    LONG        lInsertionPointID;
    LONG        fFlags;
    LONG        fSpecialFlags;
} CONTEXTMENUITEM;

typedef CONTEXTMENUITEM* LPCONTEXTMENUITEM;

//
// Console defined menu command IDs
//   The console reserves the negative numbers for predefined menu command
//   IDs which it sends to a snapin's IExtendContextMenu::Command method
//
typedef enum _MMC_MENU_COMMAND_IDS
{
    MMCC_STANDARD_VIEW_SELECT = -1
} MMC_MENU_COMMAND_IDS;

//IMenuButton data structure

typedef struct _MENUBUTTONDATA
{
    int      idCommand;
    int      x;
    int      y;
} MENUBUTTONDATA;

typedef MENUBUTTONDATA* LPMENUBUTTONDATA;

//  For multi select the cookie passed to the snapins will be
//  MMC_MULTI_SELECT_COOKIE. The data object that will be returned
//  should provide the CCF_OBJECT_TYPES_IN_MULTI_SELECT clipboard
//  format.
//
const long MMC_MULTI_SELECT_COOKIE = -2;

/*
NOTIFICATIONS
=============

Notify(dataobject, event, arg, param);
    For all the MMC_NOTIFY_TYPE events,
    dataobject
        For single select:
            dataobject for cookie, can be NULL when dataobject is not required
        For multi select:
            Console supplied data object. Supports only one format (CCF_MULTI_SELECT_SNAPIN),
            which can be used to accquire an IUnknow to the primary snapin.
    event = one of the MMCN_NOTIFY_TYPEs
    arg and param depend on type, see below.

MMCN_ACTIVATE
    Is sent when a window is being activated or deactivated.
    arg = TRUE if window is activated, false otherwise.
    param = Not used.

MMCN_ADD_IMAGES
    Sent to IComponent to add images for the result pane. The primary snapin
        should add images for both folders and leaf items. Extension snapins
        should add only folder images.
    arg = ptr to result panes IImageList.
    param = HSCOPEITEM of selected/deselected item

MMCN_BTN_CLICK
    This message is sent when a user clicks on a button.
    arg   = When sent to ExtendControlbar it is the data object of currently selected,
            when sent to IComponent/IComponentData it is 0.
    param = CmdID of the button equal to a value of the MMC_COMMANDS enum type.

MMCN_EXPAND
    arg = TRUE => expand, FALSE => contract
    param = parents HSCOPEITEM.

MMCN_MINIMIZED
    Is sent when a window is being minimized or maximized.
    arg = TRUE if minimized, false otherwise.

MMCN_PROPERTY_CHANGE
    lpDataObject = NULL
    lParam = user object

MMCN_REMOVE_CHILDREN
    Informs the snapin to delete all the cookies it has added below.
    arg = HSCOPEITEM of the node whose children needs to be deleted.
    param = unused.
    return = unused.

MMCN_RENAME
    This gets called the first time to query for rename and a
    second time to do the rename.  For the query S_OK or S_FALSE for the
    return type.  After the rename, we will send the new name with a LPOLESTR.
    arg = 0 for query, 1 for rename action
    param = LPOLESTR for containing new name
    return = S_OK to allow rename and S_FALSE to disallow rename.

MMCN_SELECT
    If sent to IComponent::Notify:
        arg:
            BOOL bScope = (BOOL) LOWORD(arg);
            BOOL bSelect = (BOOL) HIWORD(arg);
                bScope:     TRUE if an item the scope pane is selected,
                            FALSE if an item in the result pane is selected.
                bSelect:    TRUE if the item is selected,
                            FALSE if it is de-selected.
        param:
            Ignored.

    If sent to IExtendControlbar::ControlbarNotify:
        arg:
            BOOL bScope = (BOOL) LOWORD(arg);
            BOOL bSelect = (BOOL) HIWORD(arg);
                bScope:     TRUE if an item the scope pane is selected,
                            FALSE if an item in the result pane is selected.
                bSelect:    TRUE if the item is selected,
                            FALSE if it is de-selected.
        param:
            LPDATAOBJECT pDataobject = (LPDATAOBJECT)param;
            pDataobject data object of item getting selected/de-selected.

MMCN_SHOW
    arg = <>0 if selecting, 0 if deselecting
    param = HSCOPEITEM of selected/deselected item

MMCN_VIEW_CHANGE
    This message is sent to update all views of a change.
    arg = TRUE if Scope Item, FALSE if Result Item
    param = ptr to DataObject selected

MMCN_MENU_BTNCLICK
    This message is sent when a user clicks on a button.
    arg   = Data object of currently selected.
    param = Structure (LPMENUBUTTONDATA).

MMCN_SNAPINHELP
    This message is sent when the user requests help about the snapin.
    dataObject = NULL
    arg = 0
    param = 0

MMCN_CONTEXTHELP
    This message is sent when the user requests help about a selected item
    arg = 0
    param = 0

MMCN_DESELECT_ALL
    This message is sent when all items of an owner-data result pane
    are deselected.
    dataObject = NULL
    arg = 0
    param = 0

MMCN_COLUMN_CLICK
    This message is sent when the user clicks on a result listview column header.
    dataObject = NULL
    arg = Column number
    param = Sort option flags (RSI_xxx)

MMCN_DELETE:
    Sent to inform the snapin that the item needs to be deleted. As a result of
    the user hitting the 'Delete' key or delete button.
    dataobject: dataobject of the selected item(s) provided by the snap-in.
    arg, param: unused.

MMCN_CUTORMOVE:
    dataobject: NULL.
    arg: pointer to a dataobject. See multi-selection below.
    param: unused.

MMCN_QUERY_PASTE:
    dataobject: dataobject of the selected item provided by the snap-in.
    arg: dataobject of the item(s) provided by the source snap-in that needs to be pasted.
    param: unused.

MMCN_PASTE:
    dataobject & arg: are same as for MMCN_QUERY_PASTE.
    param:
        NULL for move (as opposed to cut).
        For single item paste:
            BOOL* pPasted = (BOOL*)param;
            Set this to TRUE here if the item was successfully pasted.
        For multi-item paste:
            LPDATAOBJECT* ppDataObj = (LPDATAOBJECT*)param;
            Use this to return a pointer to a dataobject consisting of the
            items successfully pasted. (see MMCN_CUTORMOVE below).
*/

typedef enum _MMC_NOTIFY_TYPE
{
    MMCN_ACTIVATE           = 0x8001,
    MMCN_ADD_IMAGES         = 0x8002,
    MMCN_BTN_CLICK          = 0x8003,
    MMCN_CLICK              = 0x8004,
    MMCN_COLUMN_CLICK       = 0x8005,
    MMCN_CONTEXTMENU        = 0x8006,
    MMCN_CUTORMOVE          = 0x8007,
    MMCN_DBLCLICK           = 0x8008,
    MMCN_DELETE             = 0x8009,
    MMCN_DESELECT_ALL       = 0x800A,
    MMCN_EXPAND             = 0x800B,
    MMCN_HELP               = 0x800C,
    MMCN_MENU_BTNCLICK      = 0x800D,
    MMCN_MINIMIZED          = 0x800E,
    MMCN_PASTE              = 0x800F,
    MMCN_PROPERTY_CHANGE    = 0x8010,
    MMCN_QUERY_PASTE        = 0x8011,
    MMCN_REFRESH            = 0x8012,
    MMCN_REMOVE_CHILDREN    = 0x8013,
    MMCN_RENAME             = 0x8014,
    MMCN_SELECT             = 0x8015,
    MMCN_SHOW               = 0x8016,
    MMCN_VIEW_CHANGE        = 0x8017,
    MMCN_SNAPINHELP         = 0x8018,
    MMCN_CONTEXTHELP        = 0x8019,
    MMCN_INITOCX            = 0x801A,

} MMC_NOTIFY_TYPE;

typedef enum _DATA_OBJECT_TYPES
{
    CCT_SCOPE            = 0x8000, // Data object while for scope pane context
    CCT_RESULT           = 0x8001, // Data object while for result pane context
    CCT_SNAPIN_MANAGER   = 0x8002, // Data object while for snap-in manager context
    CCT_UNINITIALIZED    = 0xFFFF, // Data object has an invalid type

} DATA_OBJECT_TYPES;

//New window option(s)
const unsigned long MMC_NW_OPTION_LONGTITLE =  0x0000;
const unsigned long MMC_NW_OPTION_SHORTTITLE = 0x0001;

///////////////////////////////////////////////////////////////////////////////
// Common Console clipboard formats
//

// Clipboard format for node type guid
const wchar_t* CCF_NODETYPE = L"CCF_NODETYPE";

// Clipboard format for string version of node type guid
const wchar_t* CCF_SZNODETYPE = L"CCF_SZNODETYPE";

// Clipboard format for name displayed in scope pane
const wchar_t* CCF_DISPLAY_NAME = L"CCF_DISPLAY_NAME";

// Clipboard format for the snapin's class id.
const wchar_t* CCF_SNAPIN_CLASSID = L"CCF_SNAPIN_CLASSID";

// This is the MMC supplied Multi-select data object.
const wchar_t* CCF_MMC_MULTISELECT_DATAOBJECT = L"CCF_MMC_MULTISELECT_DATAOBJECT";

// Clipboard format for the snapins multi selected dataobjects.
// If there are N snapins whose objects are selected in the result
// pane, these N dataobjects will be passed in a GloballAlloced
// memory. The first DWORD contains the number of snapins, this will
// be followed by N ptrs to the DataObjects.
typedef struct _SMMCDataObjects
{
    DWORD           count;
    LPDATAOBJECT    lpDataObject[1];

} SMMCDataObjects;
const wchar_t* CCF_MULTI_SELECT_SNAPINS = L"CCF_MULTI_SELECT_SNAPINS";

// Clipboard format for the array of GUIDs which constitutes the object
// types of all the currently selected result items put by the snapin.
typedef struct _SMMCObjectTypes
{
    DWORD   count;
    GUID    guid[1];

} SMMCObjectTypes;

const wchar_t* CCF_OBJECT_TYPES_IN_MULTI_SELECT = L"CCF_OBJECT_TYPES_IN_MULTI_SELECT";

///////////////////////////////////////////////////////////////////////////////
// APIs exported in mmc.lib
//
cpp_quote("STDAPI MMCPropertyChangeNotify(long lNotifyHandle, long param);")
cpp_quote("STDAPI MMCFreeNotifyHandle(long lNotifyHandle);")
cpp_quote("STDAPI MMCPropPageCallback(void* vpsp);")
cpp_quote("EXTERN_C const CLSID CLSID_NodeManager;")

///////////////////////////////////////////////////////////////////////////////
// Special dataobjects
//
cpp_quote("#define DOBJ_NULL (LPDATAOBJECT)0 ")
cpp_quote("#define DOBJ_CUSTOMOCX (LPDATAOBJECT)-1 ")
cpp_quote("#define DOBJ_CUSTOMWEB (LPDATAOBJECT)-2 ")

///////////////////////////////////////////////////////////////////////////////
// Macros
//
cpp_quote("#define IS_SPECIAL_DATAOBJECT(d) ((int)(d) >= -10 && (int)(d) <= 0)")
cpp_quote("#define IS_SPECIAL_COOKIE(c) ((c) >= -10 && (c) <= -1)")

///////////////////////////////////////////////////////////////////////////////
//  Interfaces
//
    [
        object,
        uuid(955AB28A-5218-11D0-A985-00C04FD8D565),
        helpstring("IComponentData Interface"),
        pointer_default(unique)
    ]
    interface IComponentData : IUnknown
    {
        [helpstring("Snap-in entry point. Can QI for IConsole & IConsoleNameSpace")]
        HRESULT Initialize([in] LPUNKNOWN pUnknown);

        [helpstring("Create a Componet for this ComponetData")]
        HRESULT CreateComponent([out] LPCOMPONENT* ppComponent);

        [helpstring("User actions")]
        HRESULT Notify([in] LPDATAOBJECT lpDataObject, [in] MMC_NOTIFY_TYPE event,
                       [in] long arg, [in] long param);

        [helpstring("Release cookies associated with the children of a specific node")]
        HRESULT Destroy();

        [helpstring("Returns a data object which may be used to retrieve the context information for the specified cookie")]
        HRESULT QueryDataObject([in] long cookie, [in] DATA_OBJECT_TYPES type,
                                [out] LPDATAOBJECT* ppDataObject);

        [helpstring("Get display info for the name space item")]
        HRESULT GetDisplayInfo([in,out] SCOPEDATAITEM* pScopeDataItem);

        [helpstring("The snap-in's compare function for two data objects")]
        HRESULT CompareObjects([in] LPDATAOBJECT lpDataObjectA, [in] LPDATAOBJECT lpDataObjectB);
    };

    [
        object,
        uuid(43136EB2-D36C-11CF-ADBC-00AA00A80033),
        helpstring("IComponent Interface"),
        pointer_default(unique)
    ]
    interface IComponent : IUnknown
    {
        [helpstring("Snap-in entry point")]
        HRESULT Initialize([in] LPCONSOLE lpConsole);

        [helpstring("User actions")]
        HRESULT Notify([in] LPDATAOBJECT lpDataObject, [in] MMC_NOTIFY_TYPE event,
                       [in] long arg, [in] long param);

        [helpstring("Release cookies associated with the children of a specific node")]
        HRESULT Destroy([in] long cookie);

        [helpstring("Returns a data object which may be used to retrieve the context information for the specified cookie")]
        HRESULT QueryDataObject([in] long cookie, [in] DATA_OBJECT_TYPES type,
                                [out] LPDATAOBJECT* ppDataObject);

        [helpstring("Returns the result view type for the specified cookie")]
        HRESULT GetResultViewType([in] long cookie, [out] LPOLESTR* ppViewType,
                                  [out] long* pViewOptions);

        [helpstring("Get display info for the result item")]
        HRESULT GetDisplayInfo([in,out] RESULTDATAITEM*  pResultDataItem);

        [helpstring("The snap-in's compare function for two data objects")]
        HRESULT CompareObjects([in] LPDATAOBJECT lpDataObjectA, [in] LPDATAOBJECT lpDataObjectB);
    };

    [
        object,
        uuid(E8315A52-7A1A-11D0-A2D2-00C04FD909DD),
        helpstring("Compare interface for sorting result items"),
        pointer_default(unique)
    ]
    interface IResultDataCompare : IUnknown
    {
        [helpstring("Compare two cookies")]
        HRESULT Compare([in] long lUserParam,
            [in] long cookieA, [in] long cookieB, [in, out] int* pnResult);
    }

    [
        object,
        uuid(9CB396D8-EA83-11d0-AEF1-00C04FB6DD2C),
        helpstring("Interface for owner data result pane list"),
        pointer_default(unique)
    ]
    interface IResultOwnerData : IUnknown
    {
        [helpstring("Find result item that matches string")]
        HRESULT FindItem([in] LPRESULTFINDINFO pFindInfo, [out] int* pnFoundIndex);

        [helpstring("Hint to cache result item data")]
        HRESULT CacheHint ([in] int nStartIndex, [in] int nEndIndex);

        [helpstring("Sort result items")]
        HRESULT SortItems([in] int nColumn, [in] DWORD dwSortOptions, [in] long lUserParam);
    }

    [
        object,
        uuid(43136EB1-D36C-11CF-ADBC-00AA00A80033),
        helpstring("IConsole Interface"),
        pointer_default(unique)
    ]
    interface IConsole : IUnknown
    {
        [helpstring("Sets IConsoles header interface")]
        HRESULT SetHeader([in] LPHEADERCTRL pHeader);

        [helpstring("Sets IConsoles toolbar interface")]
        HRESULT SetToolbar([in] LPTOOLBAR pToolbar);

        [helpstring("Queries IConsoles user provided IUnknown")]
        HRESULT QueryResultView([out] LPUNKNOWN* pUnknown);

        [helpstring("Queries the IConsole provided image list for the scope pane.")]
        HRESULT QueryScopeImageList([out] LPIMAGELIST* ppImageList);

        [helpstring("Queries the IConsole provided image list for the result pane.")]
        HRESULT QueryResultImageList([out] LPIMAGELIST* ppImageList);

        [helpstring("Generates a notification to update view(s) because of content change")]
        HRESULT UpdateAllViews([in] LPDATAOBJECT lpDataObject,
                               [in] long data,
                               [in] long hint);

        [helpstring("Displays a message box")]
        HRESULT MessageBox([in] LPCWSTR lpszText, [in] LPCWSTR lpszTitle,
                           [in] UINT fuStyle, [out] int* piRetval);

        [helpstring("Query for the IConsoleVerb.")]
        HRESULT QueryConsoleVerb([out] LPCONSOLEVERB * ppConsoleVerb);

        [helpstring("Selects the given scope item.")]
        HRESULT SelectScopeItem([in] HSCOPEITEM hScopeItem);

        [helpstring("Returns handle to the main frame window.")]
        HRESULT GetMainWindow([out] HWND* phwnd);

        [helpstring("Returns handle to the main frame window.")]
        HRESULT NewWindow([in] HSCOPEITEM hScopeItem, [in] unsigned long lOptions);

    };

    [
        object,
        uuid(43136EB3-D36C-11CF-ADBC-00AA00A80033),
        helpstring("INodeInit Interface"),
        pointer_default(unique)
    ]
    interface IHeaderCtrl : IUnknown
    {
        const int AUTO_WIDTH = -1;  // Column width is determined by the string length + padding

        [helpstring("Add a column to a Default Result view")]
        HRESULT InsertColumn([in] int nCol, [in,] LPCWSTR title, [in] int nFormat, [in] int nWidth);

        [helpstring("Remove a column to a Default Result view")]
        HRESULT DeleteColumn([in] int nCol);

        [helpstring("Set a columns text")]
        HRESULT SetColumnText([in] int nCol, [in] LPCWSTR title);

        [helpstring("Gets a columns text")]
        HRESULT GetColumnText([in] int nCol, [out] LPOLESTR* pText);

        [helpstring("Set a columns width")]
        HRESULT SetColumnWidth([in] int nCol, [in] int nWidth);

        [helpstring("Gets a columns width")]
        HRESULT GetColumnWidth([in] int nCol, [out] int* pWidth);
    };

enum
{
///////////////////////////////////////////////////////////////////////////////
// The following bits in insertion point / submenu IDs are handled specially:
//
// CCM_INSERTIONPOINTID_MASK_SPECIAL:
//   Insertion points and submenus whose IDs contain any bit in
//   CCM_INSERTIONPOINTID_MASK_SPECIAL have special behavior.  Snap-Ins may use
//   the other bits as they see fit.
// CCM_INSERTIONPOINTID_MASK_SHARED:
//   Insertion points and submenus whose IDs contain CCM_INSERTIONPOINTID_MASK_SHARED
//   are shared between the creator of the context menu, the primary extension and
//   the third-party extension.  Any of these entities adding items to a shared
//   insertion point or submenu, add them to the same insertion point or submenu.
//   Only IContextMenuProvider may create insertion points or submenus with this bit
//   set, unless CCM_INSERTIONPOINTID_MASK_CREATE_PRIMARY is also set, in which case
//   only the primary extension may create them.
// CCM_INSERTIONPOINT_CREATE_PRIMARY:
//   Only the system may add insertion points or submenus for which CCM_INSERTIONPOINT_SHARED
//   is set and this bit is not set.  Only the primary extension may add insertion points
//   or submenus for which both bits are set.  This prevents insertion point ID conflicts
//   between insertion points and submenus created by IContextMenuProvider and those
//   created by the primary extension.
// CCM_INSERTIONPOINTID_MASK_ADD_PRIMARY:
//   If CCM_INSERTIONPOINT_SHARED is set and this bit is not set, then
//   the primary extension may not add to this insertion point or submenu.
// CCM_INSERTIONPOINTID_MASK_ADD_3RDPARTY:
//   If CCM_INSERTIONPOINT_SHARED is set and this bit is not set, then
//   the third-party extensions may not add to this insertion point or submenu.
// CCM_INSERTIONPOINTID_MASK_RESERVED:
//   Insertion points and submenus may not be added with any of these bits set.
// CCM_INSERTIONPOINTID_MASK_FLAGINDEX:
//   This mask extracts the the index field from system insertion point IDs.
//   The index gives the bit position of the associated insertion allowed flag.

    CCM_INSERTIONPOINTID_MASK_SPECIAL        = 0xFFFF0000,
    CCM_INSERTIONPOINTID_MASK_SHARED         = 0x80000000,
    CCM_INSERTIONPOINTID_MASK_CREATE_PRIMARY = 0x40000000,
    CCM_INSERTIONPOINTID_MASK_ADD_PRIMARY    = 0x20000000,
    CCM_INSERTIONPOINTID_MASK_ADD_3RDPARTY   = 0x10000000,
    CCM_INSERTIONPOINTID_MASK_RESERVED       = 0x0FFF0000,
    CCM_INSERTIONPOINTID_MASK_FLAGINDEX      = 0x0000001F,

///////////////////////////////////////////////////////////////////////////////
// Use these InsertionPointIDs to add items at the predefined insertion points:
//
// 0
//   This is interpreted the same as CCM_INSERTIONPOINTID_ROOT_MENU (see below).
// CCM_INSERTIONPOINTID_PRIMARY_TOP:
//   The primary extension may use this insertion point to add items to the top of
//   the main context menu.
// CCM_INSERTIONPOINTID_PRIMARY_NEW:
//   The primary extension may use this insertion point to add items to the top of
//   the Create New submenu.
// CCM_INSERTIONPOINTID_PRIMARY_TASK:
//   The primary extension may use this insertion point to add items to the top of
//   the Task submenu.
// CCM_INSERTIONPOINTID_3RDPARTY_NEW:
//   Third-party extensions may use this insertion point to add items to the bottom of
//   the Create New submenu.
// CCM_INSERTIONPOINTID_3RDPARTY_TASK:

//   Third-party extensions may use this insertion point to add items to the bottom of
//   the Task submenu.
// CCM_INSERTIONPOINTID_ROOT_MENU:
//   IContextMenuProvider may use this insertion point to add items to the root menu.
//   Neither primary extensions nor third-party extension may add items to the root
//   menu except through insertion points added by IContextMenuProvider.
//
    CCM_INSERTIONPOINTID_PRIMARY_TOP   = 0xA0000000, // SHARED and ADD_PRIMARY
    CCM_INSERTIONPOINTID_PRIMARY_NEW   = 0xA0000001, // SHARED and ADD_PRIMARY
    CCM_INSERTIONPOINTID_PRIMARY_TASK  = 0xA0000002, // SHARED and ADD_PRIMARY
    CCM_INSERTIONPOINTID_PRIMARY_VIEW  = 0xA0000003, // SHARED and ADD_PRIMARY
    CCM_INSERTIONPOINTID_3RDPARTY_NEW  = 0x90000001, // SHARED and ADD_3RDPARTY
    CCM_INSERTIONPOINTID_3RDPARTY_TASK = 0x90000002, // SHARED and ADD_3RDPARTY
    CCM_INSERTIONPOINTID_ROOT_MENU     = 0x80000000  // SHARED
};

//
// Macro to derive an insertion allowed flag from an system insertion point ID
//
#define INSERTALLOWED_FLAG(insertionID) \
         (1L << (insertionID & CCM_INSERTIONPOINTID_MASK_FLAGINDEX))
enum
{
/////////////////////////////////////////////////////////////////////////////////////
// These flags give permission to insert menu items at the CCM_INSERTIONPOINTID_xxx
// insertion points define above. The bit position of each flag is derived from the
// index portion of the corresponding insertion point ID. They are passed to the
// snap-in's AddMenuItems method.

   CCM_INSERTIONALLOWED_TOP  = INSERTALLOWED_FLAG(CCM_INSERTIONPOINTID_PRIMARY_TOP),
   CCM_INSERTIONALLOWED_NEW  = INSERTALLOWED_FLAG(CCM_INSERTIONPOINTID_PRIMARY_NEW),
   CCM_INSERTIONALLOWED_TASK = INSERTALLOWED_FLAG(CCM_INSERTIONPOINTID_PRIMARY_TASK),
   CCM_INSERTIONALLOWED_VIEW = INSERTALLOWED_FLAG(CCM_INSERTIONPOINTID_PRIMARY_VIEW)
};

enum
{
///////////////////////////////////////////////////////////////////////////////
// The following bits in menu command IDs are handled specially:
//
// CCM_COMMANDID_MASK_RESERVED:
//   Items other than insertion points and submenus may not be added with any of
//   these bits set.
//
    CCM_COMMANDID_MASK_RESERVED       = 0xFFFF0000
};

enum
{
///////////////////////////////////////////////////////////////////////////////
// The following flags may be passed via AddMenuItem parameter fSpecialFlags:
//
// CCM_SPECIAL_SEPARATOR
//   Ignore all other parameters except lInsertionPointID.  Add a separator to the
//   end of the menu or the specified insertion point, except that
//   CCM_SPECIAL_SEPARATOR will never add a separator as the first or last item
//   in a menu/submenu, and if two or more consecutive CCM_SPECIAL_SEPARATORs are added,
//   only one appears in the menu.  Only IContextMenuProvider is permitted to add
//   separators, either special or otherwise, to menus created by IContextMenuProvider.
//
    CCM_SPECIAL_SEPARATOR         = 0x0001,
//
// CCM_SPECIAL_SUBMENU
//   If this submenu is empty, then it will be grayed and disabled.
//   This is only valid for MF_POPUP items.
//
    CCM_SPECIAL_SUBMENU           = 0x0002,
//
// CCM_SPECIAL_DEFAULT_ITEM
//   This should be the default menu item.  If more than one menu item specifies this flag,
//   the last one in each submenu takes precedence.
//
    CCM_SPECIAL_DEFAULT_ITEM      = 0x0004,
//
// CCM_SPECIAL_INSERTION_POINT
//   Ignore all other parameters except lCommandID and lInsertionPointID.  This creates
//   a new "insertion point" at the end of the insertion point or submenu identified by
//   lInsertionPointID (0 is the end of the main menu).  Subsequent calls may use
//   the lCommandID from this call as their lInsertionPointID, and insert their own
//   menu items, submenus or insertion points at this point in this menu.
//
    CCM_SPECIAL_INSERTION_POINT   = 0x0008,
//
// CCM_SPECIAL_TESTONLY
//   Perform the normal validation of the menu item parameters and return the appropriate
//   result code, but do not actually add the menu item.
//
    CCM_SPECIAL_TESTONLY          = 0x0010
};

    [
        object,
        uuid(43136EB7-D36C-11CF-ADBC-00AA00A80033),
        helpstring("IContextMenuCallback Interface"),
        pointer_default(unique)
    ]
    interface IContextMenuCallback : IUnknown
    {
        // returns S_OK if the item was added
        // returns E_INVALIDARG if given a NULL pointer
        // returns E_INVALIDARG if an item already exists with this lCommandID
        // returns E_INVALIDARG if insertion point ID could not be found
        // returns E_INVALIDARG if command ID or insertion point ID is invalid
        // returns E_INVALIDARG if fFlags contains MF_OWNERDRAW or MF_BITMAP
        // returns E_INVALIDARG if an extension attempts to add an item where
        //   fFlags contains MF_SEPARATOR or fSpecialFlags contains CCM_SPECIAL_SEPARATOR
        //   except in submenus created by extensions
        // otherwise an unexpected error occurred
        [helpstring("Adds one item to context menu")]
        HRESULT AddItem([in] CONTEXTMENUITEM* pItem);
    };

    [
        object,
        uuid(43136EB6-D36C-11CF-ADBC-00AA00A80033),
        object,
        helpstring("IContextMenuProvider Interface"),
        pointer_default(unique)
    ]
    //
    // Note that this derives from IContextMenuCallback
    //
    interface IContextMenuProvider : IContextMenuCallback
    {
        // Methods
        // returns S_OK unless an unexpected error occurs
        [helpstring("Clear context menu")]
        HRESULT EmptyMenuList();

        // returns S_OK if successful
        // returns E_INVALIDARG on NULL parameter
        // passes through return code from IExtendContextMenu::AddMenuItems
        //   or from CoCreateInstance
        // otherwise an unexpected error occurred
        [helpstring("Allow the primary extension to add to bottom of context menu")]
        HRESULT AddPrimaryExtensionItems([in] LPUNKNOWN piExtension, [in] LPDATAOBJECT piDataObject);

        // returns S_OK if successful
        // returns E_INVALIDARG on NULL parameter
        // returns S_FALSE if context menu has already been extended
        // passes through return code from IExtendContextMenu::AddMenuItems
        //   or from CLSIDToString or CoCreateInstance
        // otherwise an unexpected error occurred
        [helpstring("Allow third-party extensions to add to bottom of context menu")]
        HRESULT AddThirdPartyExtensionItems([in] LPDATAOBJECT piDataObject);

        // returns S_OK unless an unexpected error occurs
        [helpstring("Display context menu")]
        HRESULT ShowContextMenu([in] HWND hwndParent, [in] long xPos, [in] long yPos, [out,retval] long* plSelected);
    };

    [
        object,
        uuid(4F3B7A4F-CFAC-11CF-B8E3-00C04FD8D5B0),
        helpstring("IExtendContextMenu Interface"),
        pointer_default(unique)
    ]
    interface IExtendContextMenu : IUnknown
    {
        [helpstring("Extension may add context menu items via callback interface")]
        HRESULT AddMenuItems([in] LPDATAOBJECT piDataObject,
                             [in] LPCONTEXTMENUCALLBACK piCallback,
                             [in,out] long *pInsertionAllowed);

        [helpstring("Extension context menu item was selected")]
        HRESULT Command([in] long lCommandID, [in] LPDATAOBJECT piDataObject);
    };

    [
        object,
        uuid(43136EB8-D36C-11CF-ADBC-00AA00A80033),
        helpstring("IImageList Interface"),
        pointer_default(unique)
    ]
    interface IImageList : IUnknown
    {
        [helpstring("Sets an Icon in the image list, creates it if it is not there.")]
        HRESULT ImageListSetIcon([in] long* pIcon, [in] long nLoc);

        [helpstring("Sets a strip in the image list, starting at nLoc using a pair of bitmaps.")]
        HRESULT ImageListSetStrip([in] long* pBMapSm,
            [in] long* pBMapLg,[in] long nStartLoc, [in] COLORREF cMask);
    };

    [
        object,
        uuid(31DA5FA0-E0EB-11cf-9F21-00AA003CA9F6),
        helpstring("IResultData Interface"),
        pointer_default(unique)
    ]
    interface IResultData : IUnknown
    {
        [helpstring("Allows the snap-in to insert a single item.")]
        HRESULT InsertItem([in,out] LPRESULTDATAITEM item);

        [helpstring("Allows the snap-in to delete a single item.")]
        HRESULT DeleteItem([in] HRESULTITEM itemID, [in] int nCol);

        [helpstring("Allows the snap-in to find an item/subitem based on its user inserted lParam.")]
        HRESULT FindItemByLParam([in] LPARAM lParam, [out] HRESULTITEM *pItemID);

        [helpstring("Allows the snap-in to delete all the items.")]
        HRESULT DeleteAllRsltItems();

        [helpstring("Allows the snap-in to set a single item.")]
        HRESULT SetItem([in] LPRESULTDATAITEM item);

        [helpstring("Allows the snap-in to get a single item.")]
        HRESULT GetItem([in,out] LPRESULTDATAITEM item);

        [helpstring("Returns the lParam of the first item, which matches the given state.")]
        HRESULT GetNextItem([in,out] LPRESULTDATAITEM item);

        [helpstring("Allows the snap-in to modify the state of an item.")]
        HRESULT ModifyItemState([in] int nIndex, [in] HRESULTITEM itemID,
                                [in] UINT uAdd, [in] UINT uRemove);

        [helpstring("Allows the snap-in to set the result view style.")]
        HRESULT ModifyViewStyle([in] MMC_RESULT_VIEW_STYLE add,
                             [in] MMC_RESULT_VIEW_STYLE remove);

        [helpstring("Allows the snap-in to set the result view mode.")]
        HRESULT SetViewMode([in] long lViewMode);

        [helpstring("Allows the snap-in to get the result view mode.")]
        HRESULT GetViewMode([out] long* lViewMode);

        [helpstring("Allows the snap-in to update a single item.")]
        HRESULT UpdateItem([in] HRESULTITEM itemID);

        [helpstring("Sort all items in result pane")]
        HRESULT Sort([in] int nColumn, [in] DWORD dwSortOptions, [in] long lUserParam);

        [helpstring("Set the description bar text for the result view")]
        HRESULT SetDescBarText([in] LPOLESTR DescText);

        [helpstring("Set number of items in result pane list")]
        HRESULT SetItemCount([in] int nItemCount, [in] DWORD dwOptions);

    };

    [
        object,
        uuid(9757abb8-1b32-11d1-a7ce-00c04fd8d565),
        helpstring("IQuickFilter Interface"),
        pointer_default(unique)
    ]
    interface IQuickFilter : IUnknown
    {
        [helpstring("Sets the time out for the quick filter")]
        HRESULT SetChangeTimeOut([in] unsigned long uTimeout);

        [helpstring("Sets the quick filter")]
        HRESULT SetQuickFilter([in] UINT nColumn, [in] long lFilter);

        [helpstring("Gets the quick filter")]
        HRESULT GetQuickFilter([in] UINT nColumn, [out] long* plFilter);
    };

    [
        object,
        uuid(BEDEB620-F24D-11cf-8AFC-00AA003CA9F6),
        helpstring("IConsoleNameSpace Interface"),
        pointer_default(unique)
    ]
    interface IConsoleNameSpace : IUnknown
    {
        import "oaidl.idl";
        [helpstring("Allows the snap-in to insert a single item into the scope view.")]
        HRESULT InsertItem([in,out] LPSCOPEDATAITEM  item);

        [helpstring("Allows the snap-in to delete a single item from the scope view.")]
        HRESULT DeleteItem([in] HSCOPEITEM hItem, [in] long fDeleteThis);

        [helpstring("Allows the snap-in to set a single scope view item.")]
        HRESULT SetItem([in] LPSCOPEDATAITEM item);

        [helpstring("Allows the snap-in to get a single scope view item.")]
        HRESULT GetItem([in,out] LPSCOPEDATAITEM item);

        [helpstring("The handle of the child item if successful, otherwise NULL.")]
        HRESULT GetChildItem([in] HSCOPEITEM item, [out] HSCOPEITEM* pItemChild,
                             [out] long* plCookie);

        [helpstring("The handle of the next item if successful, otherwise NULL.")]
        HRESULT GetNextItem([in] HSCOPEITEM item, [out] HSCOPEITEM* pItemNext,
                            [out] long* plCookie);

        [helpstring("The handle of the parent item if successful, otherwise NULL.")]
        HRESULT GetParentItem([in] HSCOPEITEM item, [out] HSCOPEITEM* pItemParent,
                              [out] long* plCookie);
    };

struct _PSP;
typedef struct _PSP * HPROPSHEETPAGE;

    [
        local,
        object,
        uuid(85DE64DD-EF21-11cf-A285-00C04FD8DBE6),
        helpstring("IPropertySheetCallback Interface"),
        pointer_default(unique)
    ]
    interface IPropertySheetCallback : IUnknown
    {

        [helpstring("Snap-in uses this to add a page to a property sheet")]
        HRESULT AddPage([in] HPROPSHEETPAGE hPage);

        [helpstring("Snap-in uses this to remove a page from a property sheet")]
        HRESULT RemovePage([in] HPROPSHEETPAGE  hPage);
    };

    [
        object,
        uuid(85DE64DE-EF21-11cf-A285-00C04FD8DBE6),
        helpstring("IPropertySheetProvider Interface"),
        pointer_default(unique)
    ]
    interface IPropertySheetProvider : IUnknown
    {
        [helpstring("Creates a property sheet frame")]
        HRESULT CreatePropertySheet([in] LPCWSTR title, [in] boolean type,
                                    [in] long cookie, [in] LPDATAOBJECT pIDataObjectm,
                                    [in] DWORD dwOptions);

        [helpstring("Determine if the property sheet exist")]
        HRESULT FindPropertySheet([in] long cookie, [in] LPCOMPONENT lpComponent, [in] LPDATAOBJECT lpDataObject);

        [helpstring("Collects the pages from the primary snap-in")]
        HRESULT AddPrimaryPages(LPUNKNOWN lpUnknown, BOOL bCreateHandle, HWND hNotifyWindow, BOOL bScopePane);

        [helpstring("Collects the pages from the extension snap-in(s)")]
        HRESULT AddExtensionPages();

        [helpstring("Shows a property sheet frame parented to the HWND passed in")]
        HRESULT Show([in] long window, [in] int page);
    };

    [
        object,
        uuid(85DE64DC-EF21-11cf-A285-00C04FD8DBE6),
        helpstring("IExtendPropertySheet Interface"),
        pointer_default(unique)
    ]
    interface IExtendPropertySheet : IUnknown
    {
        [helpstring("Interface implemented by the snap-in to add pages to a sheet")]
        // handle - This handle must be saved in the property page object
        //          to notify the parent of changes in property using API
        //          MMCPropertyChangeNotify. The API MMCFreeNotifyHandle
        //          should be called just before the property page is
        //          destroyed to delete the handle.
        HRESULT CreatePropertyPages([in] LPPROPERTYSHEETCALLBACK lpProvider,
                                    [in] long handle,
                                    [in] LPDATAOBJECT lpIDataObject);

        [helpstring("Interface implemented by the snap-in to determine if this object needs pages")]
        HRESULT QueryPagesFor([in] LPDATAOBJECT lpDataObject);
    };

    [
        object,
        uuid(69FB811E-6C1C-11D0-A2CB-00C04FD909DD),
        helpstring("Control bar to hold toolbar and other controls"),
        pointer_default(unique)
    ]
    interface IControlbar : IUnknown
    {
        [helpstring("Create and return the control requested")]
        HRESULT Create([in] MMC_CONTROL_TYPE nType,
                       [in] LPEXTENDCONTROLBAR pExtendControlbar,
                       [out] LPUNKNOWN* ppUnknown);

        [helpstring("Associated the control to the control bar")]
        HRESULT Attach([in] MMC_CONTROL_TYPE nType, [in] LPUNKNOWN  lpUnknown);

        [helpstring("Break the association between the control and the control bar")]
        HRESULT Detach([in] LPUNKNOWN lpUnknown);
    };

    [
        object,
        uuid(49506520-6F40-11D0-A98B-00C04FD8D565),
        helpstring("IExtendControlbar Interface"),
        pointer_default(unique)
    ]
    interface IExtendControlbar : IUnknown
    {
        [helpstring("Extension may add toolbars via callback interface")]
        HRESULT SetControlbar([in] LPCONTROLBAR pControlbar);

        [helpstring("User actions")]
        HRESULT ControlbarNotify([in] MMC_NOTIFY_TYPE event,
                                 [in] long arg, [in] long param);
    };

    [
        object,
        uuid(43136EB9-D36C-11CF-ADBC-00AA00A80033),
        pointer_default(unique)
    ]
    interface IToolbar : IUnknown
    {
        [helpstring("Add and image to the toolbar")]
        HRESULT AddBitmap([in] int nImages, [in] HBITMAP hbmp, [in] int cxSize, [in] int cySize, [in] COLORREF crMask );

        [helpstring("Add an array of buttons to the toolbar")]
        HRESULT AddButtons([in] int nButtons, [in] LPMMCBUTTON lpButtons);

        [helpstring("Add a single button to the toolbar at position nIndex")]
        HRESULT InsertButton([in] int nIndex, [in] LPMMCBUTTON lpButton);

        [helpstring("Remove a button at the index")]
        HRESULT DeleteButton([in] int nIndex);

        [helpstring("Get an attribute of a button")]
        HRESULT GetButtonState([in] int idCommand, [in] MMC_BUTTON_STATE nState, [out] BOOL* pState);

        [helpstring("Set an attribute of a button")]
        HRESULT SetButtonState([in] int idCommand, [in] MMC_BUTTON_STATE nState, [in] BOOL bState);
    };

    [
        object,
        uuid(E49F7A60-74AF-11D0-A286-00C04FD8FE93),
        pointer_default(unique)
    ]
    interface IConsoleVerb : IUnknown
    {
        [helpstring("Get verb state")]
        HRESULT GetVerbState([in] MMC_CONSOLE_VERB eCmdID, [in] MMC_BUTTON_STATE nState, [out] BOOL* pState);

        [helpstring("Set verb state")]
        HRESULT SetVerbState([in] MMC_CONSOLE_VERB eCmdID, [in] MMC_BUTTON_STATE nState, [in] BOOL bState);

        [helpstring("Set default verb")]
        HRESULT SetDefaultVerb([in] MMC_CONSOLE_VERB eCmdID);

        [helpstring("Get default verb")]
        HRESULT GetDefaultVerb([out] MMC_CONSOLE_VERB* peCmdID);
    };

    [
        object,
        uuid(1245208C-A151-11D0-A7D7-00C04FD909DD),
        pointer_default(unique)
    ]
    interface ISnapinAbout : IUnknown
    {
        [helpstring("Text for the snap-in description box")]
        HRESULT GetSnapinDescription([  out] LPOLESTR* lpDescription);

        [helpstring("Provider name")]
        HRESULT GetProvider([out] LPOLESTR* lpName);

        [helpstring("Version number for the snap-in")]
        HRESULT GetSnapinVersion([out] LPOLESTR* lpVersion);

        [helpstring("Main icon for about box")]
        HRESULT GetSnapinImage([out] HICON* hAppIcon);

        [helpstring("Static folder images for scope and result panes")]
        HRESULT GetStaticFolderImage([out] HBITMAP* hSmallImage,
                                     [out] HBITMAP* hSmallImageOpen,
                                     [out] HBITMAP* hLargeImage,
                                     [out] COLORREF* cMask);
    };

    [
        object,
        uuid(951ED750-D080-11d0-B197-000000000000),
        pointer_default(unique)
    ]
    interface IMenuButton : IUnknown
    {
        [helpstring("Adds a button")]
        HRESULT AddButton([in] int idCommand,[in] LPOLESTR lpButtonText,
                                             [in] LPOLESTR lpTooltipText);

        [helpstring("Set an attributes of a button")]
        HRESULT SetButton([in] int idCommand,[in] LPOLESTR lpButtonText,
                                             [in] LPOLESTR lpTooltipText);

        [helpstring("Set the state of a button")]
        HRESULT SetButtonState([in] int idCommand,
                               [in] MMC_BUTTON_STATE nState,
                               [in] BOOL bState);
    };

    [
        object,
        uuid(A6B15ACE-DF59-11D0-A7DD-00C04FD909DD),
        pointer_default(unique)
    ]
    interface ISnapinHelp : IUnknown
    {
        [helpstring("Get the compiled help file (.chm) from a snap-in to merge")]
        HRESULT GetHelpTopic([out] LPOLESTR* lpCompiledHelpFile);
    }


MMCOLD.IDL

import "oaidl.idl";

///////////////////////////////////////////////////////////////////////////////
/// Interfaces implement by the Common Console's Node Manager

interface IConsole;
interface IHeaderCtrl;
interface IToolbar;
interface IImageList;
interface IResultData;
interface IConsoleNameSpace;
interface IPropertySheetProvider;
interface IPropertySheetCallback;
interface IContextMenuProvider;
interface IContextMenuCallback;
interface IControlbar;
interface IConsoleVerb;
interface IMenuButton;

///////////////////////////////////////////////////////////////////////////////
/// Interfaces implement by the Snap-in server

interface IComponent;
interface IComponentData;
interface IExtendPropertySheet;
interface IExtendContextMenu;
interface IExtendControlbar;
interface IResultDataCompare;
interface ISnapinAbout;

///////////////////////////////////////////////////////////////////////////////
/// Type definitions

typedef IConsole*                   LPCONSOLE;
typedef IHeaderCtrl*                LPHEADERCTRL;
typedef IToolbar*                   LPTOOLBAR;
typedef IImageList*                 LPIMAGELIST;
typedef IResultData*                LPRESULTDATA;
typedef IConsoleNameSpace*          LPCONSOLENAMESPACE;
typedef IPropertySheetProvider*     LPPROPERTYSHEETPROVIDER;
typedef IPropertySheetCallback*     LPPROPERTYSHEETCALLBACK;
typedef IContextMenuProvider*       LPCONTEXTMENUPROVIDER;
typedef IContextMenuCallback*       LPCONTEXTMENUCALLBACK;
typedef IControlbar*                LPCONTROLBAR;
typedef IConsoleVerb*               LPCONSOLEVERB;
typedef IMenuButton*                LPMENUBUTTON;

typedef IComponent*                 LPCOMPONENT;
typedef IComponentData*             LPCOMPONENTDATA;
typedef IExtendPropertySheet*       LPEXTENDPROPERTYSHEET;
typedef IExtendContextMenu*         LPEXTENDCONTEXTMENU;
typedef IExtendControlbar*          LPEXTENDCONTROLBAR;
typedef IResultDataCompare*         LPRESULTDATACOMPARE;
typedef ISnapinAbout*               LPSNAPABOUT;

///////////////////////////////////////////////////////////////////////////////
// Published Common Console ListView constants

#define LVS_ICON                0x0000
#define LVS_REPORT              0x0001
#define LVS_SMALLICON           0x0002
#define LVS_LIST                0x0003

const long  MMCLV_AUTO = -1;
const long  MMCLV_NOPARAM = -2;
const long  MMCLV_NOICON = -1;
const long  MMCLV_VIEWSTYLE_ICON = LVS_ICON;
const long  MMCLV_VIEWSTYLE_SMALLICON = LVS_SMALLICON;
const long  MMCLV_VIEWSTYLE_LIST = LVS_LIST;
const long  MMCLV_VIEWSTYLE_REPORT = LVS_REPORT;
const void* MMCLV_NOPTR = 0x0;

static unsigned short* MMC_CALLBACK = ((unsigned short *)(-1));

///////////////////////////////////////////////////////////////////////////////
// ResultData and Scope Data item structures.

typedef long    HSCOPEITEM;
typedef long    COMPONENTID;
typedef long    HRESULTITEM;

const DWORD RDI_STR     = 0x0002;
const DWORD RDI_IMAGE   = 0x0004;
const DWORD RDI_STATE   = 0x0008;
const DWORD RDI_PARAM   = 0x0010;
const DWORD RDI_INDEX   = 0x0020;
const DWORD RDI_INDENT  = 0x0040;

typedef enum _MMC_RESULT_VIEW_STYLE
{
    MMC_SINGLESEL       = 0x0001,
    MMC_SHOWSELALWAYS   = 0x0002,
    MMC_NOSORTHEADER    = 0x0004,

} MMC_RESULT_VIEW_STYLE;

const long MMC_VIEW_OPTIONS_NONE        = 0x0000;
const long MMC_VIEW_OPTIONS_NOLISTVIEWS = 0x0001;

// Type of controls that can be attached to the IControlbar
typedef enum  _MMC_CONTROL_TYPE
{
    TOOLBAR,
    MENUBUTTON,
    COMBOBOXBAR
} MMC_CONTROL_TYPE;

///////////////////////////////////////////////////////////////////////////////
// Console commands - These should be reviewed
//
typedef enum _MMC_CONSOLE_VERB
{
    MMC_VERB_CUT             = 0x8000, // User Implemented
    MMC_VERB_COPY            = 0x8001, // User Implemented
    MMC_VERB_PASTE           = 0x8002, // User Implemented
    MMC_VERB_DELETE          = 0x8003, // Console Implemented
    MMC_VERB_PROPERTIES      = 0x8004, // Console Implemented
    MMC_VERB_RENAME          = 0x8005, // Console Implemented
    MMC_VERB_REFRESH         = 0x8006, // Console Implemented
    MMC_VERB_PRINT           = 0x8007, // User Implemented
} MMC_CONSOLE_VERB;

// Structure to create buttons on the toolbar
typedef struct _MMCButton
{
    int      nBitmap;       // Offset into the bitmap list
    int      idCommand;     // Command ID return when the button is clicked
    BYTE     fsState;       // TBSTYLE_ENABLED ..etc
    BYTE     fsType;        // TBSTYLE_BUTTON || TBSTYLE_SEP
    LPOLESTR     lpButtonText;  // Text value for the Button
    LPOLESTR     lpTooltipText; // Text value for the tooltip

} MMCBUTTON;

typedef MMCBUTTON* LPMMCBUTTON;

typedef enum _MMC_BUTTON_STATE
{
    ENABLED,
    CHECKED,
    HIDDEN,
    INDETERMINATE,
    BUTTONPRESSED

} MMC_BUTTON_STATE;

typedef struct _RESULTDATAITEM
{
    DWORD       mask;
    BOOL        bScopeItem;
    HRESULTITEM itemID;
    int         nIndex;
    int         nCol;
    LPOLESTR    str;
    int         nImage;
    UINT        nState;
    LPARAM      lParam;
    int         iIndent;
} RESULTDATAITEM;

typedef RESULTDATAITEM* LPRESULTDATAITEM;

const DWORD SDI_STR         = 0x00002;
const DWORD SDI_IMAGE       = 0x00004;
const DWORD SDI_OPENIMAGE   = 0x00008;
const DWORD SDI_STATE       = 0x00010;
const DWORD SDI_PARAM       = 0x00020;
const DWORD SDI_CHILDREN    = 0x00040;

// The top 4 bit of the mask determines the relative position of this item,
// relative to the SCOPEDATAITEM::relativeID. By default it is the parent.

// For SDI_PARENT, SCOPEDATAITEM::relativeID is the HSCOPEITEM of the parent.
// As you can see by the SDI_PARENT value it is a no-op. Since by default
// SCOPEDATAITEM::relativeID is treated as the parents ID.
const DWORD SDI_PARENT      = 0x00000000;

// For SDI_PREVIOUS, SCOPEDATAITEM::relativeID is the HSCOPEITEM of the previous sibling
const DWORD SDI_PREVIOUS    = 0x10000000;

// For SDI_NEXT, SCOPEDATAITEM::relativeID is the HSCOPEITEM of the next sibling.
const DWORD SDI_NEXT        = 0x20000000;

// For SDI_PARENT, bit 27 determines whether the item is to be inserted as the
// first child. By default this item will inserted as the last child.
const DWORD SDI_FIRST       = 0x08000000;

const COMPONENTID SDI_ROOT  = (COMPONENTID)0xFFFF0000;

typedef struct _SCOPEDATAITEM
{
    DWORD       mask;
    LPOLESTR    displayname;
    int         nImage;
    int         nOpenImage;
    UINT        nState;
    int         cChildren;
    LPARAM      lParam;
    HSCOPEITEM  relativeID;
    HSCOPEITEM  ID;
} SCOPEDATAITEM;

typedef SCOPEDATAITEM* LPSCOPEDATAITEM;

typedef struct _CONTEXTMENUITEM
{
    LPWSTR      strName;
    LPWSTR      strStatusBarText;
    LONG        lCommandID;
    LONG        lInsertionPointID;
    LONG        fFlags;
    LONG        fSpecialFlags;
} CONTEXTMENUITEM;

typedef CONTEXTMENUITEM* LPCONTEXTMENUITEM;

//
// Console defined menu command IDs
//   The console reserves the negative numbers for predefined menu command
//   IDs which it sends to a snapin's IExtendContextMenu::Command method
//
typedef enum _MMC_MENU_COMMAND_IDS
{
    MMCC_STANDARD_VIEW_SELECT = -1
} MMC_MENU_COMMAND_IDS;

//IMenuButton data structure

typedef struct _MENUBUTTONDATA
{
    int      idCommand;
    int      x;
    int      y;
} MENUBUTTONDATA;

typedef MENUBUTTONDATA* LPMENUBUTTONDATA;

/*
NOTIFICATIONS
=============

Notify(dataobject, event, arg, param);
    For all the MMC_NOTIFY_TYPE events,
    dataobject = dataobject for cookie, can be NULL when dataobject is not required
    event = one of the CD_NOTIFY_TYPEs
    arg and param depend on type, see below.

MMCN_ACTIVATE
    Is sent when a window is being activated or deactivated.
    arg = TRUE if window is activated, false otherwise.
    param = Not used.

MMCN_ADD_IMAGES
    Sent to IComponent to add images for the result pane. The primary snapin
        should add images for both folders and leaf items. Extension snapins
        should add only folder images.
    arg = ptr to result panes IImageList.
    param = HSCOPEITEM of selected/deselected item

MMCN_BTN_CLICK
    This message is sent when a user clicks on a button.
    arg   = When sent to ExtendControlbar it is the data object of currently selected,
            when sent to IComponent/IComponentData it is 0.
    param = CmdID of the button equal to a value of the MMC_COMMANDS enum type.

MMCN_DELETE
    Sent to inform the snapin that the item needs to be deleted. As a result of
    the user hitting the 'Delete' key or delete button.
    arg = unused
    param = unused.
    return = unused.

MMCN_EXPAND
    arg = TRUE => expand, FALSE => contract
    param = parents HSCOPEITEM.

MMCN_MINIMIZED
    Is sent when a window is being minimized or maximized.
    arg = TRUE if minimized, false otherwise.

MMCN_MULTI_SELECT
    same as MMCN_SELECT

MMCN_PROPERTY_CHANGE
    lpDataObject = NULL
    lParam = user object

MMCN_REMOVE_CHILDREN
    Informs the snapin to delete all the cookies it has added below.
    arg = HSCOPEITEM of the node whose children needs to be deleted.
    param = unused.
    return = unused.

MMCN_RENAME
    This gets called the first time to query for rename and a
    second time to do the rename.  For the query S_OK or S_FALSE for the
    return type.  After the rename, we will send the new name with a LPOLESTR.
    arg = 0 for query, 1 for rename action
    param = LPOLESTR for containing new name
    return = S_OK to allow rename and S_FALSE to disallow rename.

MMCN_SELECT
    If sent to IComponent::Notify:
        arg:
            BOOL bScope = (BOOL) LOWORD(arg);
            BOOL bSelect = (BOOL) HIWORD(arg);
                bScope:     TRUE if an item the scope pane is selected,
                            FALSE if an item in the result pane is selected.
                bSelect:    TRUE if the item is selected,
                            FALSE if it is de-selected.
        param:
            Ignored.

    If sent to IExtendControlbar::ControlbarNotify:
        arg:
            BOOL bScope = (BOOL) LOWORD(arg);
            BOOL bSelect = (BOOL) HIWORD(arg);
                bScope:     TRUE if an item the scope pane is selected,
                            FALSE if an item in the result pane is selected.
                bSelect:    If bScope is TRUE.
                                bSelect is ignored.
                            Else
                                TRUE if the result item is selected,
                                FALSE if it is de-selected.
        param:
            If bScope is TRUE.
                LPDATAOBJECT* ppDataobject = (LPDATAOBJECT)param;
                ppDataobject[0] data object of item getting de-slected.
                ppDataobject[1] data object of item getting slected.
                Note:
                    (ppDataobject[0] == NULL) imples:
                        There was no previously selected item, or
                        there was no toolbar put up by the snapin for the
                        item getting de-selected.
                    (ppDataobject[1] == NULL) imples:
                        The snapin should not put up any toolbars for the
                        item getting selected.
            Else // result item selected
                LPDATAOBJECT pDataobject = (LPDATAOBJECT)param;
                    pDataobject == NULL & bSelect == TRUE then:
                        The snapin should disable all the toolbar buttons
                        for the selected item.
                pDataobject data object of item getting slected/de-selected.

MMCN_SHOW
    arg = <>0 if selecting, 0 if deselecting
    param = HSCOPEITEM of selected/deselected item

MMCN_VIEW_CHANGE
    This message is sent to update all views of a change.
    arg = TRUE if Scope Item, FALSE if Result Item
    param = ptr to DataObject selected

MMCN_MENU_BTNCLICK
    This message is sent when a user clicks on a button.
    arg   = Data object of currently selected.
    param = Structure (LPMENUBUTTONDATA).

MMCN_HELP
    This message is sent when the user presses the F1 help key.
    param = pointer to a GUID**.  If NULL, the NodeType is used instead.

*/

typedef enum _MMC_NOTIFY_TYPE
{
    MMCN_ACTIVATE           = 0x8001,
    MMCN_ADD_IMAGES         = 0x8002,
    MMCN_BTN_CLICK          = 0x8003,
    MMCN_CLICK              = 0x8004,
    MMCN_DBLCLICK           = 0x8005,
    MMCN_DELETE             = 0x8006,
    MMCN_EXPAND             = 0x8007,
    MMCN_MINIMIZED          = 0x8008,
    MMCN_PROPERTY_CHANGE    = 0x8009,
    MMCN_REMOVE_CHILDREN    = 0x800A,
    MMCN_RENAME             = 0x800B,
    MMCN_SELECT             = 0x800C,
    MMCN_SHOW               = 0x800D,
    MMCN_VIEW_CHANGE        = 0x800E,
    MMCN_CONTEXTMENU        = 0x800F,
    MMCN_MENU_BTNCLICK      = 0x8010,
    MMCN_HELP               = 0x8011,
    MMCN_MULTI_SELECT       = 0x8012,
    MMCN_REFRESH            = 0x8013,

} MMC_NOTIFY_TYPE;

typedef enum _DATA_OBJECT_TYPES
{
    CCT_SCOPE            = 0x8000, // Data object while for scope pane context
    CCT_RESULT           = 0x8001, // Data object while for result pane context
    CCT_SNAPIN_MANAGER   = 0x8002, // Data object while for snap-in manager context
    CCT_UNINITIALIZED    = 0xFFFF, // Data object has an invalid type

} DATA_OBJECT_TYPES;

///////////////////////////////////////////////////////////////////////////////
// Common Console clipboard formats
//

// Clipboard format for node type guid
const wchar_t* CCF_NODETYPE = L"CCF_NODETYPE";

// Clipboard format for string version of node type guid
const wchar_t* CCF_SZNODETYPE = L"CCF_SZNODETYPE";

// Clipboard format for name displayed in scope pane
const wchar_t* CCF_DISPLAY_NAME = L"CCF_DISPLAY_NAME";

// Clipboard format for the snapin's class id.
const wchar_t* CCF_SNAPIN_CLASSID = L"CCF_SNAPIN_CLASSID";

///////////////////////////////////////////////////////////////////////////////
// APIs exported in mmc.lib
//
cpp_quote("STDAPI MMCPropertyChangeNotify(long lNotifyHandle, long param);")
cpp_quote("STDAPI MMCFreeNotifyHandle(long lNotifyHandle);")
cpp_quote("STDAPI MMCPropPageCallback(void* vpsp);")

///////////////////////////////////////////////////////////////////////////////
//  Interfaces
//
    [
        object,
        uuid(955AB28A-5218-11D0-A985-00C04FD8D565),
        helpstring("IComponentData Interface"),
        pointer_default(unique)
    ]
    interface IComponentData : IUnknown
    {
        [helpstring("Snap-in entry point. Can QI for IConsole & IConsoleNameSpace")]
        HRESULT Initialize([in] LPUNKNOWN pUnknown);

        [helpstring("Create a Componet for this ComponetData")]
        HRESULT CreateComponent([out] LPCOMPONENT* ppComponent);

        [helpstring("User actions")]
        HRESULT Notify([in] LPDATAOBJECT lpDataObject, [in] MMC_NOTIFY_TYPE event,
                       [in] long arg, [in] long param);

        [helpstring("Release cookies associated with the children of a specific node")]
        HRESULT Destroy();

        [helpstring("Returns a data object which may be used to retrieve the context information for the specified cookie")]
        HRESULT QueryDataObject([in] long cookie, [in] DATA_OBJECT_TYPES type,
                                [out] LPDATAOBJECT* ppDataObject);

        [helpstring("Get display info for the name space item")]
        HRESULT GetDisplayInfo([in,out] SCOPEDATAITEM* pScopeDataItem);

        [helpstring("The snap-in's compare function for two data objects")]
        HRESULT CompareObjects([in] LPDATAOBJECT lpDataObjectA, [in] LPDATAOBJECT lpDataObjectB);
    };

    [
        object,
        uuid(43136EB2-D36C-11CF-ADBC-00AA00A80033),
        helpstring("IComponent Interface"),
        pointer_default(unique)
    ]
    interface IComponent : IUnknown
    {
        [helpstring("Snap-in entry point")]
        HRESULT Initialize([in] LPCONSOLE lpConsole);

        [helpstring("User actions")]
        HRESULT Notify([in] LPDATAOBJECT lpDataObject, [in] MMC_NOTIFY_TYPE event,
                       [in] long arg, [in] long param);

        [helpstring("Release cookies associated with the children of a specific node")]
        HRESULT Destroy([in] long cookie);

        [helpstring("Returns a data object which may be used to retrieve the context information for the specified cookie")]
        HRESULT QueryDataObject([in] long cookie, [in] DATA_OBJECT_TYPES type,
                                [out] LPDATAOBJECT* ppDataObject);

        [helpstring("Returns the result view type for the specified cookie")]
        HRESULT GetResultViewType([in] long cookie, [out] LPOLESTR* ppViewType,
                                  [out] long* pViewOptions);

        [helpstring("Get display info for the result item")]
        HRESULT GetDisplayInfo([in,out] RESULTDATAITEM*  pResultDataItem);

        [helpstring("The snap-in's compare function for two data objects")]
        HRESULT CompareObjects([in] LPDATAOBJECT lpDataObjectA, [in] LPDATAOBJECT lpDataObjectB);
    };

    [
        object,
        uuid(E8315A52-7A1A-11D0-A2D2-00C04FD909DD),
        helpstring("Compare interface for sorting result items"),
        pointer_default(unique)
    ]
    interface IResultDataCompare : IUnknown
    {
        [helpstring("Compare two cookies")]
        HRESULT Compare([in] long lUserParam,
            [in] long cookieA, [in] long cookieB, [in, out] int* pnResult);
    }

    [
        object,
        uuid(43136EB1-D36C-11CF-ADBC-00AA00A80033),
        helpstring("IConsole Interface"),
        pointer_default(unique)
    ]
    interface IConsole : IUnknown
    {
        [helpstring("Sets IConsoles header interface")]
        HRESULT SetHeader([in] LPHEADERCTRL pHeader);

        [helpstring("Sets IConsoles toolbar interface")]
        HRESULT SetToolbar([in] LPTOOLBAR pToolbar);

        [helpstring("Queries IConsoles user provided IUnknown")]
        HRESULT QueryResultView([out] LPUNKNOWN* pUnknown);

        [helpstring("Queries the IConsole provided image list for the scope pane.")]
        HRESULT QueryScopeImageList([out] LPIMAGELIST* ppImageList);

        [helpstring("Queries the IConsole provided image list for the result pane.")]
        HRESULT QueryResultImageList([out] LPIMAGELIST* ppImageList);

        [helpstring("Generates a notification to update view(s) because of content change")]
        HRESULT UpdateAllViews([in] LPDATAOBJECT lpDataObject,
                               [in] long data,
                               [in] long hint);

        [helpstring("Displays a message box")]
        HRESULT MessageBox([in] LPCWSTR lpszText, [in] LPCWSTR lpszTitle,
                           [in] UINT fuStyle, [out] int* piRetval);

        [helpstring("Query for the IConsoleVerb.")]
        HRESULT QueryConsoleVerb([out] LPCONSOLEVERB * ppConsoleVerb);

        [helpstring("Selects the given scope item.")]
        HRESULT SelectScopeItem([in] HSCOPEITEM hScopeItem);

        [helpstring("Returns handle to the main frame window.")]
        HRESULT GetMainWindow([out] HWND* phwnd);
    };

    [
        object,
        uuid(43136EB3-D36C-11CF-ADBC-00AA00A80033),
        helpstring("INodeInit Interface"),
        pointer_default(unique)
    ]
    interface IHeaderCtrl : IUnknown
    {
        const int AUTO_WIDTH = -1;  // Column width is determined by the string length + padding

        [helpstring("Add a column to a Default Result view")]
        HRESULT InsertColumn([in] int nCol, [in,] LPCWSTR title, [in] int nFormat, [in] int nWidth);

        [helpstring("Remove a column to a Default Result view")]
        HRESULT DeleteColumn([in] int nCol);

        [helpstring("Set a columns text")]
        HRESULT SetColumnText([in] int nCol, [in] LPCWSTR title);

        [helpstring("Gets a columns text")]
        HRESULT GetColumnText([in] int nCol, [out] LPOLESTR* pText);

        [helpstring("Set a columns width")]
        HRESULT SetColumnWidth([in] int nCol, [in] int nWidth);

        [helpstring("Gets a columns width")]
        HRESULT GetColumnWidth([in] int nCol, [out] int* pWidth);
    };

enum
{
///////////////////////////////////////////////////////////////////////////////
// The following bits in insertion point / submenu IDs are handled specially:
//
// CCM_INSERTIONPOINTID_MASK_SPECIAL:
//   Insertion points and submenus whose IDs contain any bit in
//   CCM_INSERTIONPOINTID_MASK_SPECIAL have special behavior.  Snap-Ins may use
//   the other bits as they see fit.
// CCM_INSERTIONPOINTID_MASK_SHARED:
//   Insertion points and submenus whose IDs contain CCM_INSERTIONPOINTID_MASK_SHARED
//   are shared between the creator of the context menu, the primary extension and
//   the third-party extension.  Any of these entities adding items to a shared
//   insertion point or submenu, add them to the same insertion point or submenu.
//   Only IContextMenuProvider may create insertion points or submenus with this bit
//   set, unless CCM_INSERTIONPOINTID_MASK_CREATE_PRIMARY is also set, in which case
//   only the primary extension may create them.
// CCM_INSERTIONPOINT_CREATE_PRIMARY:
//   Only the system may add insertion points or submenus for which CCM_INSERTIONPOINT_SHARED
//   is set and this bit is not set.  Only the primary extension may add insertion points
//   or submenus for which both bits are set.  This prevents insertion point ID conflicts
//   between insertion points and submenus created by IContextMenuProvider and those
//   created by the primary extension.
// CCM_INSERTIONPOINTID_MASK_ADD_PRIMARY:
//   If CCM_INSERTIONPOINT_SHARED is set and this bit is not set, then
//   the primary extension may not add to this insertion point or submenu.
// CCM_INSERTIONPOINTID_MASK_ADD_3RDPARTY:
//   If CCM_INSERTIONPOINT_SHARED is set and this bit is not set, then
//   the third-party extensions may not add to this insertion point or submenu.
// CCM_INSERTIONPOINTID_MASK_RESERVED:
//   Insertion points and submenus may not be added with any of these bits set.
// CCM_INSERTIONPOINTID_MASK_FLAGINDEX:
//   This mask extracts the the index field from system insertion point IDs.
//   The index gives the bit position of the associated insertion allowed flag.

    CCM_INSERTIONPOINTID_MASK_SPECIAL        = 0xFFFF0000,
    CCM_INSERTIONPOINTID_MASK_SHARED         = 0x80000000,
    CCM_INSERTIONPOINTID_MASK_CREATE_PRIMARY = 0x40000000,
    CCM_INSERTIONPOINTID_MASK_ADD_PRIMARY    = 0x20000000,
    CCM_INSERTIONPOINTID_MASK_ADD_3RDPARTY   = 0x10000000,
    CCM_INSERTIONPOINTID_MASK_RESERVED       = 0x0FFF0000,
    CCM_INSERTIONPOINTID_MASK_FLAGINDEX      = 0x0000001F,

///////////////////////////////////////////////////////////////////////////////
// Use these InsertionPointIDs to add items at the predefined insertion points:
//
// 0
//   This is interpreted the same as CCM_INSERTIONPOINTID_ROOT_MENU (see below).
// CCM_INSERTIONPOINTID_PRIMARY_TOP:
//   The primary extension may use this insertion point to add items to the top of
//   the main context menu.
// CCM_INSERTIONPOINTID_PRIMARY_NEW:
//   The primary extension may use this insertion point to add items to the top of
//   the Create New submenu.
// CCM_INSERTIONPOINTID_PRIMARY_TASK:
//   The primary extension may use this insertion point to add items to the top of
//   the Task submenu.
// CCM_INSERTIONPOINTID_3RDPARTY_NEW:
//   Third-party extensions may use this insertion point to add items to the bottom of
//   the Create New submenu.
// CCM_INSERTIONPOINTID_3RDPARTY_TASK:
//   Third-party extensions may use this insertion point to add items to the bottom of
//   the Task submenu.
// CCM_INSERTIONPOINTID_ROOT_MENU:
//   IContextMenuProvider may use this insertion point to add items to the root menu.
//   Neither primary extensions nor third-party extension may add items to the root
//   menu except through insertion points added by IContextMenuProvider.
//
    CCM_INSERTIONPOINTID_PRIMARY_TOP   = 0xA0000000, // SHARED and ADD_PRIMARY
    CCM_INSERTIONPOINTID_PRIMARY_NEW   = 0xA0000001, // SHARED and ADD_PRIMARY
    CCM_INSERTIONPOINTID_PRIMARY_TASK  = 0xA0000002, // SHARED and ADD_PRIMARY
    CCM_INSERTIONPOINTID_PRIMARY_VIEW  = 0xA0000003, // SHARED and ADD_PRIMARY
    CCM_INSERTIONPOINTID_3RDPARTY_NEW  = 0x90000001, // SHARED and ADD_3RDPARTY
    CCM_INSERTIONPOINTID_3RDPARTY_TASK = 0x90000002, // SHARED and ADD_3RDPARTY
    CCM_INSERTIONPOINTID_ROOT_MENU     = 0x80000000  // SHARED
};

//
// Macro to derive an insertion allowed flag from an system insertion point ID
//
#define INSERTALLOWED_FLAG(insertionID) \
         (1L << (insertionID & CCM_INSERTIONPOINTID_MASK_FLAGINDEX))
enum
{
/////////////////////////////////////////////////////////////////////////////////////
// These flags give permission to insert menu items at the CCM_INSERTIONPOINTID_xxx
// insertion points define above. The bit position of each flag is derived from the
// index portion of the corresponding insertion point ID. They are passed to the
// snap-in's AddMenuItems method.

   CCM_INSERTIONALLOWED_TOP  = INSERTALLOWED_FLAG(CCM_INSERTIONPOINTID_PRIMARY_TOP),
   CCM_INSERTIONALLOWED_NEW  = INSERTALLOWED_FLAG(CCM_INSERTIONPOINTID_PRIMARY_NEW),
   CCM_INSERTIONALLOWED_TASK = INSERTALLOWED_FLAG(CCM_INSERTIONPOINTID_PRIMARY_TASK),
   CCM_INSERTIONALLOWED_VIEW = INSERTALLOWED_FLAG(CCM_INSERTIONPOINTID_PRIMARY_VIEW)
};

enum
{
///////////////////////////////////////////////////////////////////////////////
// The following bits in menu command IDs are handled specially:
//
// CCM_COMMANDID_MASK_RESERVED:
//   Items other than insertion points and submenus may not be added with any of
//   these bits set.
//
    CCM_COMMANDID_MASK_RESERVED       = 0xFFFF0000
};

enum
{
///////////////////////////////////////////////////////////////////////////////
// The following flags may be passed via AddMenuItem parameter fSpecialFlags:
//
// CCM_SPECIAL_SEPARATOR
//   Ignore all other parameters except lInsertionPointID.  Add a separator to the
//   end of the menu or the specified insertion point, except that
//   CCM_SPECIAL_SEPARATOR will never add a separator as the first or last item
//   in a menu/submenu, and if two or more consecutive CCM_SPECIAL_SEPARATORs are added,
//   only one appears in the menu.  Only IContextMenuProvider is permitted to add
//   separators, either special or otherwise, to menus created by IContextMenuProvider.
//
    CCM_SPECIAL_SEPARATOR         = 0x0001,
//
// CCM_SPECIAL_SUBMENU
//   If this submenu is empty, then it will be grayed and disabled.
//   This is only valid for MF_POPUP items.
//
    CCM_SPECIAL_SUBMENU           = 0x0002,
//
// CCM_SPECIAL_DEFAULT_ITEM
//   This should be the default menu item.  If more than one menu item specifies this flag,
//   the last one in each submenu takes precedence.
//
    CCM_SPECIAL_DEFAULT_ITEM      = 0x0004,
//
// CCM_SPECIAL_INSERTION_POINT
//   Ignore all other parameters except lCommandID and lInsertionPointID.  This creates
//   a new "insertion point" at the end of the insertion point or submenu identified by
//   lInsertionPointID (0 is the end of the main menu).  Subsequent calls may use
//   the lCommandID from this call as their lInsertionPointID, and insert their own
//   menu items, submenus or insertion points at this point in this menu.
//
    CCM_SPECIAL_INSERTION_POINT   = 0x0008,
//
// CCM_SPECIAL_TESTONLY
//   Perform the normal validation of the menu item parameters and return the appropriate
//   result code, but do not actually add the menu item.
//
    CCM_SPECIAL_TESTONLY          = 0x0010
};

    [
        object,
        uuid(43136EB7-D36C-11CF-ADBC-00AA00A80033),
        helpstring("IContextMenuCallback Interface"),
        pointer_default(unique)
    ]
    interface IContextMenuCallback : IUnknown
    {
        // returns S_OK if the item was added
        // returns E_INVALIDARG if given a NULL pointer
        // returns E_INVALIDARG if an item already exists with this lCommandID
        // returns E_INVALIDARG if insertion point ID could not be found
        // returns E_INVALIDARG if command ID or insertion point ID is invalid
        // returns E_INVALIDARG if fFlags contains MF_OWNERDRAW or MF_BITMAP
        // returns E_INVALIDARG if an extension attempts to add an item where
        //   fFlags contains MF_SEPARATOR or fSpecialFlags contains CCM_SPECIAL_SEPARATOR
        //   except in submenus created by extensions
        // otherwise an unexpected error occurred
        [helpstring("Adds one item to context menu")]
        HRESULT AddItem([in] CONTEXTMENUITEM* pItem);
    };

    [

object,
        uuid(43136EB6-D36C-11CF-ADBC-00AA00A80033),
        object,
        helpstring("IContextMenuProvider Interface"),
        pointer_default(unique)
    ]
    //
    // Note that this derives from IContextMenuCallback
    //
    interface IContextMenuProvider : IContextMenuCallback
    {
        // Methods
        // returns S_OK unless an unexpected error occurs
        [helpstring("Clear context menu")]
        HRESULT EmptyMenuList();

        // returns S_OK if successful
        // returns E_INVALIDARG on NULL parameter
        // passes through return code from IExtendContextMenu::AddMenuItems
        //   or from CoCreateInstance
        // otherwise an unexpected error occurred
        [helpstring("Allow the primary extension to add to bottom of context menu")]
        HRESULT AddPrimaryExtensionItems([in] LPUNKNOWN piExtension, [in] LPDATAOBJECT piDataObject);

        // returns S_OK if successful
        // returns E_INVALIDARG on NULL parameter
        // returns S_FALSE if context menu has already been extended
        // passes through return code from IExtendContextMenu::AddMenuItems
        //   or from CLSIDToString or CoCreateInstance
        // otherwise an unexpected error occurred
        [helpstring("Allow third-party extensions to add to bottom of context menu")]
        HRESULT AddThirdPartyExtensionItems([in] LPDATAOBJECT piDataObject);

        // returns S_OK unless an unexpected error occurs
        [helpstring("Display context menu")]
        HRESULT ShowContextMenu([in] HWND hwndParent, [in] long xPos, [in] long yPos, [out,retval] long* plSelected);
    };

    [
        object,
        uuid(4F3B7A4F-CFAC-11CF-B8E3-00C04FD8D5B0),
        helpstring("IExtendContextMenu Interface"),
        pointer_default(unique)
    ]
    interface IExtendContextMenu : IUnknown
    {
        [helpstring("Extension may add context menu items via callback interface")]
        HRESULT AddMenuItems([in] LPDATAOBJECT piDataObject,
                             [in] LPCONTEXTMENUCALLBACK piCallback,
                             [in,out] long *pInsertionAllowed);

        [helpstring("Extension context menu item was selected")]
        HRESULT Command([in] long lCommandID, [in] LPDATAOBJECT piDataObject);
    };

    [
        object,
        uuid(43136EB8-D36C-11CF-ADBC-00AA00A80033),
        helpstring("IImageList Interface"),
        pointer_default(unique)
    ]
    interface IImageList : IUnknown
    {
        [helpstring("Sets an Icon in the image list, creates it if it is not there.")]
        HRESULT ImageListSetIcon([in] long* pIcon, [in] long nLoc);

        [helpstring("Sets a strip in the image list, starting at nLoc using a pair of bitmaps.")]
        HRESULT ImageListSetStrip([in] long* pBMapSm,
            [in] long* pBMapLg,[in] long nStartLoc, [in] COLORREF cMask);
    };

    [
        object,
        uuid(31DA5FA0-E0EB-11cf-9F21-00AA003CA9F6),
        helpstring("IResultData Interface"),
        pointer_default(unique)
    ]
    interface IResultData : IUnknown
    {
        [helpstring("Allows the snap-in to insert a single item.")]
        HRESULT InsertItem([in,out] LPRESULTDATAITEM item);

        [helpstring("Allows the snap-in to delete a single item.")]
        HRESULT DeleteItem([in] HRESULTITEM itemID, [in] int nCol);

        [helpstring("Allows the snap-in to find an item/subitem based on its user inserted lParam.")]
        HRESULT FindItemByLParam([in] LPARAM lParam, [out] HRESULTITEM *pItemID);

        [helpstring("Allows the snap-in to delete all the items.")]
        HRESULT DeleteAllRsltItems();

        [helpstring("Allows the snap-in to set a single item.")]
        HRESULT SetItem([in] LPRESULTDATAITEM item);

        [helpstring("Allows the snap-in to get a single item.")]
        HRESULT GetItem([in,out] LPRESULTDATAITEM item);

        [helpstring("Returns the lParam of the first item, which matches the given state.")]
        HRESULT GetNextItem([in,out] LPRESULTDATAITEM item);

        [helpstring("Allows the snap-in to modify the state of an item.")]
        HRESULT ModifyItemState([in] int nIndex, [in] HRESULTITEM itemID,
                                [in] UINT uAdd, [in] UINT uRemove);

        [helpstring("Allows the snap-in to set the result view style.")]
        HRESULT ModifyViewStyle([in] MMC_RESULT_VIEW_STYLE add,
                             [in] MMC_RESULT_VIEW_STYLE remove);

        [helpstring("Allows the snap-in to set the result view mode.")]
        HRESULT SetViewMode([in] long lViewMode);

        [helpstring("Allows the snap-in to get the result view mode.")]
        HRESULT GetViewMode([out] long* lViewMode);

        [helpstring("Allows the snap-in to update a single item.")]
        HRESULT UpdateItem([in] HRESULTITEM itemID);

        [helpstring("Sort all items in result pane")]
        HRESULT Sort([in] long lUserParam);

        [helpstring("Set the description bar text for the result view")]
        HRESULT SetDescBarText([in] LPOLESTR DescText);

    };

    [
        object,
        uuid(BEDEB620-F24D-11cf-8AFC-00AA003CA9F6),
        helpstring("IConsoleNameSpace Interface"),
        pointer_default(unique)
    ]
    interface IConsoleNameSpace : IUnknown
    {
        import "oaidl.idl";
        [helpstring("Allows the snap-in to insert a single item into the scope view.")]
        HRESULT InsertItem([in,out] LPSCOPEDATAITEM  item);

        [helpstring("Allows the snap-in to delete a single item from the scope view.")]
        HRESULT DeleteItem([in] HSCOPEITEM hItem, [in] long fDeleteThis);

        [helpstring("Allows the snap-in to set a single scope view item.")]
        HRESULT SetItem([in] LPSCOPEDATAITEM item);

        [helpstring("Allows the snap-in to get a single scope view item.")]
        HRESULT GetItem([in,out] LPSCOPEDATAITEM item);

        [helpstring("The handle of the child item if successful, otherwise NULL.")]
        HRESULT GetChildItem([in] HSCOPEITEM item, [out] HSCOPEITEM* pItemChild,
                             [out] long* plCookie);

        [helpstring("The handle of the next item if successful, otherwise NULL.")]
        HRESULT GetNextItem([in] HSCOPEITEM item, [out] HSCOPEITEM* pItemNext,
                            [out] long* plCookie);

        [helpstring("The handle of the parent item if successful, otherwise NULL.")]
        HRESULT GetParentItem([in] HSCOPEITEM item, [out] HSCOPEITEM* pItemParent,
                              [out] long* plCookie);
    };

struct _PSP;
typedef struct _PSP * HPROPSHEETPAGE;

    [
        local,
        object,
        uuid(85DE64DD-EF21-11cf-A285-00C04FD8DBE6),
        helpstring("IPropertySheetCallback Interface"),
        pointer_default(unique)
    ]
    interface IPropertySheetCallback : IUnknown
    {

        [helpstring("Snap-in uses this to add a page to a property sheet")]
        HRESULT AddPage([in] HPROPSHEETPAGE hPage);

        [helpstring("Snap-in uses this to remove a page from a property sheet")]
        HRESULT RemovePage([in] HPROPSHEETPAGE  hPage);
    };

    [
        object,
        uuid(85DE64DE-EF21-11cf-A285-00C04FD8DBE6),
        helpstring("IPropertySheetProvider Interface"),
        pointer_default(unique)
    ]
    interface IPropertySheetProvider : IUnknown
    {
        [helpstring("Creates a property sheet frame")]
        HRESULT CreatePropertySheet([in] LPCWSTR title, [in] boolean type, [in] long cookie, [in] LPDATAOBJECT pIDataObject);

        [helpstring("Determine if the property sheet exist")]
        HRESULT FindPropertySheet([in] long cookie, [in] LPCOMPONENT lpComponent, [in] LPDATAOBJECT lpDataObject);

        [helpstring("Collects the pages from the primary snap-in")]
        HRESULT AddPrimaryPages(LPUNKNOWN lpUnknown, BOOL bCreateHandle, HWND hNotifyWindow, BOOL bScopePane);

        [helpstring("Collects the pages from the extension snap-in(s)")]
        HRESULT AddExtensionPages();

        [helpstring("Shows a property sheet frame parented to the HWND passed in")]
        HRESULT Show([in] long window, [in] int page);
    };

    [
        object,
        uuid(85DE64DC-EF21-11cf-A285-00C04FD8DBE6),
        helpstring("IExtendPropertySheet Interface"),
        pointer_default(unique)
    ]
    interface IExtendPropertySheet : IUnknown
    {
        [helpstring("Interface implemented by the snap-in to add pages to a sheet")]
        // handle - This handle must be saved in the property page object
        //          to notify the parent of changes in property using API
        //          MMCPropertyChangeNotify. The API MMCFreeNotifyHandle
        //          should be called just before the property page is
        //          destroyed to delete the handle.
        HRESULT CreatePropertyPages([in] LPPROPERTYSHEETCALLBACK lpProvider,
                                    [in] long handle,
                                    [in] LPDATAOBJECT lpIDataObject);

        [helpstring("Interface implemented by the snap-in to determine if this object needs pages")]
        HRESULT QueryPagesFor([in] LPDATAOBJECT lpDataObject);
    };

    [
        object,
        uuid(69FB811E-6C1C-11D0-A2CB-00C04FD909DD),
        helpstring("Control bar to hold toolbar and other controls"),
        pointer_default(unique)
    ]
    interface IControlbar : IUnknown
    {
        [helpstring("Create and return the control requested")]
        HRESULT Create([in] MMC_CONTROL_TYPE nType,
                       [in] LPEXTENDCONTROLBAR pExtendControlbar,
                       [out] LPUNKNOWN* ppUnknown);

        [helpstring("Associated the control to the control bar")]
        HRESULT Attach([in] MMC_CONTROL_TYPE nType, [in] LPUNKNOWN  lpUnknown);

        [helpstring("Break the association between the control and the control bar")]
        HRESULT Detach([in] LPUNKNOWN lpUnknown);
    };

    [
        object,
        uuid(49506520-6F40-11D0-A98B-00C04FD8D565),
        helpstring("IExtendControlbar Interface"),
        pointer_default(unique)
    ]
    interface IExtendControlbar : IUnknown
    {
        [helpstring("Extension may add toolbars via callback interface")]
        HRESULT SetControlbar([in] LPCONTROLBAR pControlbar);

        [helpstring("User actions")]
        HRESULT ControlbarNotify([in] MMC_NOTIFY_TYPE event,
                                 [in] long arg, [in] long param);
    };

    [
        object,
        uuid(43136EB9-D36C-11CF-ADBC-00AA00A80033),
        pointer_default(unique)
    ]
    interface IToolbar : IUnknown
    {
        [helpstring("Add and image to the toolbar")]
        HRESULT AddBitmap([in] int nImages, [in] HBITMAP hbmp, [in] int cxSize, [in] int cySize, [in] COLORREF crMask );

        [helpstring("Add an array of buttons to the toolbar")]
        HRESULT AddButtons([in] int nButtons, [in] LPMMCBUTTON lpButtons);

        [helpstring("Add a single button to the toolbar at position nIndex")]
        HRESULT InsertButton([in] int nIndex, [in] LPMMCBUTTON lpButton);

        [helpstring("Remove a button at the index")]
        HRESULT DeleteButton([in] int nIndex);

        [helpstring("Get an attribute of a button")]
        HRESULT GetButtonState([in] int idCommand, [in] MMC_BUTTON_STATE nState, [out] BOOL* pState);

        [helpstring("Set an attribute of a button")]
        HRESULT SetButtonState([in] int idCommand, [in] MMC_BUTTON_STATE nState, [in] BOOL bState);
    };

    [
        object,
        uuid(E49F7A60-74AF-11D0-A286-00C04FD8FE93),
        pointer_default(unique)
    ]
    interface IConsoleVerb : IUnknown
    {
        [helpstring("Get verb state")]
        HRESULT GetVerbState([in] MMC_CONSOLE_VERB m_eCmdID, [in] MMC_BUTTON_STATE nState, [out] BOOL* pState);

        [helpstring("Set verb state")]
        HRESULT SetVerbState([in] MMC_CONSOLE_VERB m_eCmdID, [in] MMC_BUTTON_STATE nState, [in] BOOL bState);

    };

    [
        object,
        uuid(1245208C-A151-11D0-A7D7-00C04FD909DD),
        pointer_default(unique)
    ]
    interface ISnapinAbout : IUnknown
    {
        [helpstring("Text for the snap-in description box")]
        HRESULT GetSnapinDescription([  out] LPOLESTR* lpDescription);

        [helpstring("Provider name")]
        HRESULT GetProvider([out] LPOLESTR* lpName);

        [helpstring("Version number for the snap-in")]
        HRESULT GetSnapinVersion([out] LPOLESTR* lpVersion);

        [helpstring("Main icon for about box")]
        HRESULT GetSnapinImage([out] HICON* hAppIcon);

        [helpstring("Static folder images for scope and result panes")]
        HRESULT GetStaticFolderImage([out] HBITMAP* hSmallImage,
                                     [out] HBITMAP* hSmallImageOpen,
                                     [out] HBITMAP* hLargeImage,
                                     [out] COLORREF* cMask);
    };

    [
        object,
        uuid(951ED750-D080-11d0-B197-000000000000),
        pointer_default(unique)
    ]
    interface IMenuButton : IUnknown
    {
        [helpstring("Adds a button")]
        HRESULT AddButton([in] int idCommand,[in] LPOLESTR lpButtonText,
                                             [in] LPOLESTR lpTooltipText);

        [helpstring("Set an attributes of a button")]
        HRESULT SetButton([in] int idCommand,[in] LPOLESTR lpButtonText,
                                             [in] LPOLESTR lpTooltipText);

        [helpstring("Set the state of a button")]
        HRESULT SetButtonState([in] int idCommand,
                               [in] MMC_BUTTON_STATE nState,
                               [in] BOOL bState);
    };

    [
        object,
        uuid(A6B15ACE-DF59-11D0-A7DD-00C04FD909DD),
        pointer_default(unique)
    ]
    interface ISnapinHelp : IUnknown
    {
        [helpstring("Get the compiled help file (.chm) from a snap-in to merge")]
        HRESULT GetHelpTopic([out] LPOLESTR* lpCompiledHelpFile);
    }


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by disk.rc
//
#define IDS_PROJNAME                    100
#define IDS_COMPONENTDATA_DESC          101
#define IDR_ComponentData               102
#define IDB_SCOPE_16x16                 201

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           103
#endif
#endif


STDAFX.CPP

// stdafx.cpp : source file that includes just the standard includes
//  stdafx.pch will be the pre-compiled header
//  stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>


STDAFX.H

// stdafx.h : include file for standard system include files,
//      or project specific include files that are used frequently,
//      but are changed infrequently

#if !defined(AFX_STDAFX_H__CF8CC7E4_0CFE_11D1_8F07_000000000000__INCLUDED_)
#define AFX_STDAFX_H__CF8CC7E4_0CFE_11D1_8F07_000000000000__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#define STRICT

#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>

#if __RPCNDR_H_VERSION__ < 440             // This may be needed when building
#define __RPCNDR_H_VERSION__ 440           // on NT5 (1671) to prevent MIDL errors
#define MIDL_INTERFACE(x) interface
#endif

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__CF8CC7E4_0CFE_11D1_8F07_000000000000__INCLUDED)


README.TXT

This is step two of a tutorial sample.
The second step adds population of the result pane.
It also adds the REFRESH verb to refresh the result pane.

This sample will compile with VC++ 5.0.
It is set up to build a debug version of the DLL.
If you want to run this DLL from the debugger, then go into
the "Project Settings" dialog and on the Debug tab make sure that
the path is correct to the MMC.EXE.
The IDE build will register your DLL for you.

If you build from the command line you will want to
copy the DLL to the system32 directory and register the DLL
by typing from the command line "regsvr32 disk.dll".

This sample uses mmc.h and mmc.lib. In the VC++ 5.0 Project menu,
select Settings. Then add the appropriate directories to find mmc.h
and mmc.lib on the C++ and Link tabs.

To try out the snapin - in MMC, from the Console menu, select Add/Remove
Snap-In. Click the Add button. The name of the Snap-In is Disk.


COMP.CPP

/////////////////////////////////////////////////////////////////////////////
// comp.cpp : IComponent Interface to communicate with MMC for the
//                       results pane.  This implementation uses the
//                       default list view.
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#include "stdafx.h"
#include "disk.h"
#include "globals.h"
#include "compdata.h"                  // Step2: Added for SetIComponetData
#include "comp.h"

/////////////////////////////////////////////////////////////////////////////
// CComponent - Several new interfaces are added in Step2:
//
//              mostly a stub place holder and serves no real purpose. It will
//              get filled in and used in later steps.

CComponent::CComponent()
{
  m_ipConsole     = NULL;              // Step2: Adding support for a bunch
  m_ipHeaderCtrl  = NULL;              // of new interfaces
  m_ipResultData  = NULL;
  m_ipConsoleVerb = NULL;
  m_ipImageResult = NULL;
  m_pCompData     = NULL;
  m_cRefs         = 0;

} // end Constructor()

CComponent::~CComponent()
{
}

/////////////////////////////////////////////////////////////////////////////
// IComponent implementation
//

//---------------------------------------------------------------------------
// IComponent::Initialize is called when a snap-in is being created and
// has items in the result pane to enumerate. The pointer to IConsole that
// is passed in is used to make QueryInterface calls to the console for
// interfaces such as IResultData.
// Step2:  We add the basic interfaces from the console.
//
STDMETHODIMP
CComponent::Initialize
(
  LPCONSOLE lpConsole        // [in] Pointer to IConsole's IUnknown interface
)
{
  HRESULT hr = S_OK;
  _ASSERT( NULL != lpConsole );

  // Save away all the interfaces we'll need.
  // Fail if we can't QI the required interfaces.

  m_ipConsole = lpConsole;
  m_ipConsole->AddRef();
  hr = m_ipConsole->QueryInterface( IID_IResultData,
                                (VOID**)&m_ipResultData
  );
  if( FAILED(hr) )
    return hr;

  hr = m_ipConsole->QueryInterface( IID_IHeaderCtrl,
                                (VOID**)&m_ipHeaderCtrl
  );
  if( FAILED(hr) )
    return hr;                         // Console needs the header
  else                                 // control pointer
    m_ipConsole->SetHeader( m_ipHeaderCtrl );

  hr = m_ipConsole->QueryResultImageList( &m_ipImageResult);
  if( FAILED(hr) )
    return hr;

  hr = m_ipConsole->QueryConsoleVerb( &m_ipConsoleVerb );
  if( FAILED(hr) )
    return hr;

  // Load the bitmaps from the dll for the results pane
  m_hbmp16x16 = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_RESULT_16x16));
  _ASSERT( m_hbmp16x16 );
  m_hbmp32x32 = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_RESULT_32x32));
  _ASSERT( m_hbmp32x32 );

  return hr;

} // end Initialize()

//---------------------------------------------------------------------------
//  Step2:  Add code to implment notifications.  At this point we do not
//          handle every notification, but we have handlers for the most
//          important ones.
//
STDMETHODIMP
CComponent::Notify
(
  LPDATAOBJECT     pDataObject,  // [in] Points to data object
  MMC_NOTIFY_TYPE  event,        // [in] Identifies action taken by user
  long             arg,          // [in] Depends on the notification type
  long             param         // [in] Depends on the notification type
)
{
  HRESULT hr = S_OK;

  switch( event )
  {
    case MMCN_ADD_IMAGES:
      ATLTRACE( L"CComponent::Notify: MMCN_ADD_IMAGES\n" );
      hr = OnAddImages( pDataObject, arg, param );
      break;

    case MMCN_SHOW:
      ATLTRACE( L"CComponent::Notify: MMCN_SHOW\n" );
      hr = OnShow( pDataObject, arg, param );
      break;

    case MMCN_SELECT:
      ATLTRACE( L"CComponent::Notify: MMCN_SELECT\n" );
      hr = OnSelect( pDataObject, arg, param );
      break;

    case MMCN_REFRESH:
      ATLTRACE( L"CComponent::Notify: MMCN_REFRESH\n" );
      hr = OnRefresh( pDataObject, arg, param );
      break;

    case MMCN_VIEW_CHANGE:
      ATLTRACE( L"CComponent::Notify: MMCN_VIEW_CHANGE unimplemented\n" );
      break;

    case MMCN_CLICK:
      ATLTRACE( L"CComponent::Notify: MMCN_CLICK unimplemented\n" );
      break;

    case MMCN_DBLCLICK:
      ATLTRACE( L"CComponent::Notify: MMCN_DBLCLICK unimplemented\n" );
      break;

    case MMCN_ACTIVATE:
      ATLTRACE( L"CComponent::Notify: MMCN_ACTIVATE unimplemented\n" );
      break;

    case MMCN_MINIMIZED:
      ATLTRACE( L"CComponent::Notify: MMCN_MINIMIZED unimplemented\n" );
      break;

    case MMCN_BTN_CLICK:
      ATLTRACE( L"CComponent::Notify: MMCN_BTN_CLICK unimplemented\n" );
      break;

    default:
      ATLTRACE( L"CComponent::Notify: unexpected event %x\n" , event );
      hr = E_UNEXPECTED;
      break;
  }
  return hr;

} // end Notify()

//---------------------------------------------------------------------------
// Releases all references to the console.
// Only the console should call this method.
// Step2:  Added a bunch of new interfaces to release
//
STDMETHODIMP
CComponent::Destroy
(
  long cookie          // Reserved, not in use at this time
)
{
  // Release the interfaces that we QI'ed
  m_ipConsole->SetHeader(NULL);
  if( NULL != m_ipHeaderCtrl )
  {
    m_ipHeaderCtrl->Release();
    m_ipHeaderCtrl = NULL;
  }

  if( NULL != m_ipResultData )
  {
    m_ipResultData->Release();
    m_ipResultData = NULL;
  }

  if( NULL != m_ipImageResult )
  {
    m_ipImageResult->Release();
    m_ipImageResult = NULL;
  }

  if( NULL != m_ipConsoleVerb )
  {
    m_ipConsoleVerb->Release();
    m_ipConsoleVerb= NULL;
  }

  if( NULL != m_ipConsole )
  {
    m_ipConsole->Release();
    m_ipConsole = NULL;
  }

  if( NULL != m_pCompData )
  {
    m_pCompData->Release();
    m_pCompData = NULL;
  }

  _ASSERT( DeleteObject(m_hbmp16x16) );
  _ASSERT( DeleteObject(m_hbmp32x32) );

  return S_OK;

} // end Destroy()

//---------------------------------------------------------------------------
// Returns a data object that can be used to retrieve context information
// for the specified cookie.
// Step2:  Now we need to implement this method
//
STDMETHODIMP
CComponent::QueryDataObject
(
  long               cookie,      // [in]  Specifies the unique identifier
  DATA_OBJECT_TYPES  context,     // [in]  Type of data object
  LPDATAOBJECT      *ppDataObject // [out] Points to address of returned data
)
{
  INT iResult;
  HRESULT hr = S_OK;
  CDataObject *pdoNew = NULL;

  _ASSERT( CCT_SCOPE          == context  ||     // Must have a context
       CCT_RESULT         == context  ||     // we understand
           CCT_SNAPIN_MANAGER == context
         );

  do
  {
    pdoNew = new CDataObject;
    *ppDataObject = pdoNew;

    if( !pdoNew )                      // Can't create object
    {
      hr = m_ipConsole->MessageBox( L"Out of memory",
                            L"CComponent::QueryDataObject",
MB_OK,
&iResult
  );
      break;
    }

    if( CCT_RESULT == context )
    {
      if( cookie )
      {
        // cookie is the lparam field that we passed in SCOPEDATAITEM
        // used for the m_ipConsoleNameSpace->InsertItem(&sdi);
        pdoNew->SetCookie( cookie, CCT_SCOPE, COOKIE_IS_STATUS );
      }
      else
      {
//        _ASSERT(FALSE);                // Should never have NULL cookie
        hr = E_UNEXPECTED;             // with CCT_RESULT as the context
      }
    }
    else
    {
      hr = E_UNEXPECTED;               // Only handling CCT_RESULT
    }
  } while( 0 );

  return S_FALSE;

} // end QueryDataObject()

//---------------------------------------------------------------------------
// This is where we provide strings for items we added to the the result
// pane.  We only have one item, so this is easy. We get asked for a string
// for each column.
// Note that we still need to provide strings for items that are actually
// scope pane items. Notice that when the scope pane item was asked for a
// string for the scope pane we gave it. Here we actually have two columns
// of strings - "Name" and "Type".
// We also get asked for the icons for items in both panes.
//
STDMETHODIMP
CComponent::GetDisplayInfo
(
  LPRESULTDATAITEM pResultItem    // [in,out] Type of info required
)
{
  static WCHAR* s_szSize = L"ABC";

  _ASSERT( NULL != pResultItem );

  if (pResultItem)
  {
    // Only responding for scope items for now.
    if ( TRUE == pResultItem->bScopeItem )
    {
      if (pResultItem->mask & RDI_STR)     // Looking for a string
      {
        if (0 == pResultItem->nCol )
          pResultItem->str = (LPOLESTR)L"Geometry";
        else if (1 == pResultItem->nCol )
          pResultItem->str = (LPOLESTR)L"Child Folder";
        else
          pResultItem->str = (LPOLESTR)L"Error: Invalid Column";
      }

      if (pResultItem->mask & RDI_IMAGE)
      {
        pResultItem->nImage = 0;
      }
    }
    else                     // Step2: Provide strings for Result Pane items
    {                        //        See the comments in OnShow()
      if( pResultItem->mask & RDI_STR )
      {
        if( pResultItem->nCol == 0 )
          pResultItem->str = (LPOLESTR)L"Column 0";
        else if( pResultItem->nCol == 1 )
          pResultItem->str = (LPOLESTR)L"Column 1";
        else
          pResultItem->str = (LPOLESTR)L"Error: Unknown Column.";
      }

      if( pResultItem->mask & RDI_IMAGE )  // Looking for image
      {
        pResultItem->nImage = 2;
      }
    }
  }

  return S_OK;

} // end GetDisplayInfo()

//---------------------------------------------------------------------------
// Determines what the result pane view should be
//
STDMETHODIMP
CComponent::GetResultViewType
(
  long  cookie,        // [in]  Specifies the unique identifier
  BSTR  *ppViewType,   // [out] Points to address of the returned view type
  long  *pViewOptions  // [out] Pointer to the MMC_VIEW_OPTIONS enumeration
)
{
  // Ask for default listview.
  //
  *pViewOptions = MMC_VIEW_OPTIONS_NONE;
  return S_FALSE;

} // end GetResultViewType()

//---------------------------------------------------------------------------
// Not used in Step2
//
HRESULT
CComponent::CompareObjects
(
  LPDATAOBJECT lpDataObjectA,  // [in] First data object to compare
  LPDATAOBJECT lpDataObjectB   // [in] Second data object to compare
)
{
return S_FALSE;

} // end CompareObjects()

/////////////////////////////////////////////////////////////////////////////
//  Support methods
//

//---------------------------------------------------------------------------
// Here is where we handle the MMCN_SHOW message.  Insert the column
// headers, and then the rows of data into the result pane.
//
HRESULT
CComponent::OnShow
(
  LPDATAOBJECT pDataObject,    // [in] Points to data object
  long         arg,            // [in]
  long         param           // [in]
)
{
  INT             iResult;
  CDataObject     *pdo;
  RESULTDATAITEM  ResultItem;
  HRESULT         hr = S_OK;

  _ASSERT( NULL != pDataObject );
  _ASSERT( m_ipResultData );           // Must be first time

  if( TRUE == arg )
  {
    pdo = ExtractOwnDataObject(pDataObject);

    // If this is the root node, Let use the default mmc column headings
    if( COOKIE_IS_ROOT == pdo->GetCookieType() )
    {
      return S_FALSE;
    }

    // Set the column headers in the results pane
    //
    hr = m_ipHeaderCtrl->InsertColumn( 0, L"One", 0, 100 );
    _ASSERT( S_OK == hr );
    hr = m_ipHeaderCtrl->InsertColumn( 1, L"Two", 0, 200 );
    _ASSERT( S_OK == hr );

    // Set the items in the results pane rows
    //
    if( !pdo )
    {
      hr = m_ipConsole->MessageBox( L"Unknown data object.\nNot set up to be an extension",
                                L"CComponent::OnShow",
                        MB_OK,
                                    &iResult
  );
      return hr;
    }

    _ASSERT( CCT_SCOPE == pdo->GetContext() );
    _ASSERT( COOKIE_IS_STATUS == pdo->GetCookieType() );

    // Here we add the one item to the result pane.
    // NOTE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // The lParam is what we see in QueryDataObject as the cookie.
    // In the upcomming steps we will actually create an object representing
    // row data, so that the cookie knows what to display in the results pane,
    // and when we get into GetDisplayInfo we cast the cookie to our object,
    // and can get the data to display.
    //
    memset( &ResultItem, 0, sizeof(RESULTDATAITEM) );
    ResultItem.mask =   RDI_STR     |    // Displayname is valid
                        RDI_IMAGE   |    // nImage is valid
                        RDI_PARAM;       // lParam is valid

    ResultItem.str    = MMC_CALLBACK;    // Will get this from GetDisplayInfo
    ResultItem.nImage = 2;
    ResultItem.lParam = NULL;
    m_ipResultData->InsertItem( &ResultItem );
  }
  return hr;

} // end OnShow()

//---------------------------------------------------------------------------
// Step2:  New handler for the MMCN_ADD_IMAGES message
//
HRESULT
CComponent::OnAddImages
(
  LPDATAOBJECT pDataObject,  // [in] Points to the data object
  long arg,                  // [in] Not used
  long param                 // [in] Not used
)
{
  HRESULT hr;
  _ASSERT( NULL != m_ipImageResult );
  hr = m_ipImageResult->ImageListSetStrip( (LONG *)m_hbmp16x16,
                                           (LONG *)m_hbmp32x32,
                                           0,
                                           RGB(0,255,0)
 );
  _ASSERT( S_OK == hr );
  return hr;

} // end OnAddImages()

//---------------------------------------------------------------------------
// Step2: This is a handler for the MMCN_SELECT notification. The user
//        selected the node that populated the result pane. We have a
//        chance to enable verbs. We will enable the REFRESH verb.
//
HRESULT
CComponent::OnSelect
(
  LPDATAOBJECT pDataObject,  // [in] Points to the data object
  long         arg,          // [in] Contains flags about the selected item
  long         param         // [in] Not used
)
{
  BOOL fScopePane;
  BOOL fSelected;
  CDataObject *pdo;
  HRESULT hr  = S_OK;

  fScopePane = LOWORD(arg);
  fSelected  = HIWORD(arg);

  // Don't care about de-select notifications so just return
  //
  if( !fSelected )
  {
    return S_OK;
  }

  //
  // Bail if we couldn't get the console verb interface, or if the
  // selected item is the root;
  //
  pdo = ExtractOwnDataObject( pDataObject );
  _ASSERT( NULL != pdo );              // Check object before using it

  if( NULL == m_ipConsoleVerb || COOKIE_IS_ROOT == pdo->GetCookieType() )
  {
    return S_OK;
  }

  //
  // Use selections and set which verbs are allowed
  //

  if( fScopePane )                     // Selection in the scope pane
  {
    if( COOKIE_IS_STATUS == pdo->GetCookieType() )
    {
      hr = m_ipConsoleVerb->SetVerbState( MMC_VERB_REFRESH, ENABLED, TRUE );
      _ASSERT( S_OK == hr );
    }
  }
  else                                 // Selection in the result pane
  {
    //
  }
  return S_OK;

} // end OnSelect()

//---------------------------------------------------------------------------
//  Step2: Respond to the MMCN_REFRESH notification and refresh the rows.
//
HRESULT
CComponent::OnRefresh
(
  LPDATAOBJECT pDataObject,  // [in] Points to the data object
  long         arg,          // [in]
  long         param         // [in]
)
{
  CDataObject     *pdo;
  RESULTDATAITEM  ResultItem;
  HRESULT         hr = S_OK;

  //
  // Clear out and re-populate
  //
  hr = m_ipResultData->DeleteAllRsltItems();
  _ASSERT( S_OK == hr );

  _ASSERT( NULL != m_ipResultData );

  pdo = ExtractOwnDataObject( pDataObject );
  _ASSERT( NULL != pdo );

  // If this is the root node, Let use the default mmc column headings
  if( COOKIE_IS_ROOT == pdo->GetCookieType() )
  {
    return S_FALSE;
  }

  //
  // Set the items in the results pane rows
  //

  _ASSERT( CCT_SCOPE == pdo->GetContext() );
  _ASSERT( COOKIE_IS_STATUS == pdo->GetCookieType() );

  memset( &ResultItem, 0, sizeof(RESULTDATAITEM) );
  ResultItem.mask =   RDI_STR     |    // displayname is valid
                      RDI_IMAGE   |    // nImage is valid
                      RDI_PARAM;       // lParam is valid

  ResultItem.str    = MMC_CALLBACK;
  ResultItem.nImage = 2;
  ResultItem.lParam = NULL;
  m_ipResultData->InsertItem( &ResultItem );

  return S_OK;

} // end OnRefresh()

//---------------------------------------------------------------------------
//  Store the parent IComponetData object.
//
VOID
CComponent::SetIComponentData
(
  CComponentData*  pData
)
{
  _ASSERT( pData );
  _ASSERT( NULL == m_pCompData );        // Can't do this twice
  LPUNKNOWN pUnk = pData->GetUnknown();  // Get the object IUnknown

  HRESULT hr;
  hr = pUnk->QueryInterface( IID_IComponentData,
                         reinterpret_cast<void**>(&m_pCompData)
   );
  _ASSERT( S_OK == hr );

} // end SetIComponentData()


COMP.H

/////////////////////////////////////////////////////////////////////////////
// comp.h : IComponent Interface to communicate with MMC for results pane use
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#ifndef __COMP_H_
#define __COMP_H_

#include <mmc.h>

/////////////////////////////////////////////////////////////////////////////
// CComponent - This class at this point is the basic interface to handle
//              anything to do with the results pane. At this point it is
//              mostly a stib place holder and serves no real purpose. It will
//              get filled in and used in later steps.

class CComponent:
public IComponent,
    public CComObjectRoot
{
public:

// ATL Maps
DECLARE_NOT_AGGREGATABLE(CComponent)

BEGIN_COM_MAP(CComponent)
COM_INTERFACE_ENTRY(IComponent)
END_COM_MAP()

  CComponent();
  ~CComponent();

  void SetIComponentData(CComponentData* pData);

  // IComponent interface members
  //
  STDMETHOD(Initialize)(LPCONSOLE lpConsole);
  STDMETHOD(Notify)(LPDATAOBJECT lpDataObject, MMC_NOTIFY_TYPE event, long arg, long param);
  STDMETHOD(Destroy)(long cookie);
  STDMETHOD(GetResultViewType)(long cookie,  LPOLESTR* ppViewType, long* pViewOptions);
  STDMETHOD(QueryDataObject)(long cookie, DATA_OBJECT_TYPES type, LPDATAOBJECT* ppDataObject);
  STDMETHOD(GetDisplayInfo)(RESULTDATAITEM*  pResultDataItem);
  STDMETHOD(CompareObjects)(LPDATAOBJECT lpDataObjectA, LPDATAOBJECT lpDataObjectB);

private:
  LPCONSOLE        m_ipConsole;      // MMC interface to console
  IHeaderCtrl*     m_ipHeaderCtrl;   // MMC interface to header control
  IResultData*     m_ipResultData;   // MMC interface to result data
  IConsoleVerb*    m_ipConsoleVerb;  // MMC interface to console verb
  LPIMAGELIST      m_ipImageResult;  // MMC interface to result pane images
  ULONG            m_cRefs;          // object refcount
  CComponentData*  m_pCompData;      // Parent scope pane object
  HBITMAP          m_hbmp16x16;
  HBITMAP          m_hbmp32x32;

private:
  HRESULT OnShow(LPDATAOBJECT pDataObject, long arg, long param);
  HRESULT OnSelect(LPDATAOBJECT pDataObject, long arg, long param);
  HRESULT OnAddImages(LPDATAOBJECT pDataObject, long arg, long param);
  HRESULT OnRefresh(LPDATAOBJECT pDataObject, long arg, long param);
};

#endif // __COMP_H_


COMPDATA.CPP

/////////////////////////////////////////////////////////////////////////////
//  compdata.cpp : IComponentData Interface to communicate with MMC for
//                 scope pane use
//
//  This is a part of the MMC SDK.
//  Copyright (C) 1997 Microsoft Corporation
//  All rights reserved.
//
//  This source code is only intended as a supplement to the
//  MMC SDK Reference and related
//  electronic documentation provided with the library.
//  See these sources for detailed information regarding the
//  MMC Library product.
//

#include "stdafx.h"
#include <mmc.h>                       // Copy from MMC.SDK
#include "disk.h"
#include "globals.h"                   // Helper functions
#include "comp.h"
#include "compdata.h"

/////////////////////////////////////////////////////////////////////////////
// CComponentData - This class is the interface to handle anything to do
//                  with the scope pane. MMC calls the IComponent interfaces.
//                  This class keeps a few pointers to interfaces that MMC
//                  implements.

CComponentData::CComponentData()
{
  m_ipConsoleNameSpace = NULL;
  m_ipConsole          = NULL;
}

CComponentData::~CComponentData()
{
// We release the cached interface pointers in Destroy()
}

/////////////////////////////////////////////////////////////////////////////
// IComponentData methods
//

//---------------------------------------------------------------------------
// We get here only once, when the user clicks on the snapin.
//
// This method should not change as we progress through further steps.
// Here we get a chance to get pointer to some interfaces MMC provides.
// We QueryInterface for pointers to the name space and console, which
// we cache in local variables
// The other task to acomplish here is the adding of a bitmap that contains
// the icons to be used in the scope pane.
//
STDMETHODIMP
CComponentData::Initialize
(
  LPUNKNOWN pUnknown         // [in] Pointer to the IConsole s IUnknown interface
)
{
    HRESULT      hr;
    LPIMAGELIST  lpScopeImage;

    _ASSERT( NULL != pUnknown );

    // MMC should only call ::Initialize once!
    _ASSERT( NULL == m_ipConsoleNameSpace );

    //
    // Get pointer to name space interface
    //
    hr = pUnknown->QueryInterface(IID_IConsoleNameSpace, (VOID**)(&m_ipConsoleNameSpace));
    _ASSERT( S_OK == hr );

    //
    // Get pointer to console interface
    //
    hr = pUnknown->QueryInterface(IID_IConsole, (VOID**)(&m_ipConsole));
    _ASSERT( S_OK == hr );

    //
    // Add the images for the scope tree
    //
    hr = m_ipConsole->QueryScopeImageList(&lpScopeImage);
    _ASSERT( S_OK == hr );

    // Load the bitmaps from the dll
    m_hbmp16x16 = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_SCOPE_16x16));
    _ASSERT( NULL != m_hbmp16x16 );

    // Set the images
    hr = lpScopeImage->ImageListSetStrip( (long*)m_hbmp16x16,
                                          (long*)m_hbmp16x16,
                                          0,
                                          RGB(0,255,0)
                                        );
    _ASSERT( S_OK == hr );

    lpScopeImage->Release();

    return S_OK;

} // end Initialize()

//---------------------------------------------------------------------------
// Release interfaces and clean up objects which allocated memory
//
STDMETHODIMP
CComponentData::Destroy()
{
  // Free interfaces
  m_ipConsoleNameSpace->Release();
  m_ipConsoleNameSpace = NULL;

  m_ipConsole->Release();
  m_ipConsole = NULL;

  _ASSERT( DeleteObject(m_hbmp16x16) );

  return S_OK;

} // end Destroy()

//---------------------------------------------------------------------------
// Come in here once right after Initialize. MMC wants a pointer to the
// IComponent interface.
//
STDMETHODIMP
CComponentData::CreateComponent
(
  LPCOMPONENT* ppComponent   // [out] Pointer to the location that stores
)                            // the newly created pointer to IComponent
{
  CComObject<CComponent>* pObject;

  //
  // MMC asks us for a pointer to the IComponent interface
  //
  // For those getting up to speed with COM...
  // If we had implemented IUnknown with its methods QueryInterface, AddRef,
  // and Release in our CComponent class...
  // The following line would have worked
  //
  // pNewSnapin = new CComponent(this);
  //
  // In this code we will have ATL take care of IUnknown for us and create
  // an object in the following manner...

  _ASSERT( NULL != ppComponent );

  CComObject<CComponent>::CreateInstance( &pObject );
  _ASSERT( NULL != pObject );

  // Step2:  Store IComponentData.
  // Can't have a constructor with parameters, so pass it in this way.
  pObject->SetIComponentData( this );

  return  pObject->QueryInterface( IID_IComponent,
                                   reinterpret_cast<void**>(ppComponent)
                                 );
} // end CreateComponent()

//---------------------------------------------------------------------------
// In this first step, we only implement EXPAND.
// The expand message asks us to populate what is under our root node.
// We just put one item under there.
//
STDMETHODIMP
CComponentData::Notify
(
  LPDATAOBJECT     lpDataObject,  // [in] Points to the selected data object
  MMC_NOTIFY_TYPE  event,         // [in] Identifies action taken by user.
  long             arg,           // [in] Depends on the notification type
  long             param          // [in] Depends on the notification type
)
{
  HRESULT hr = S_OK;

  switch (event)
  {
    case MMCN_EXPAND:
      ATLTRACE( L"ComponentData::Notify: MMCN_EXPAND\n" );
      hr = OnExpand( lpDataObject, arg, param );
      break;

    case MMCN_DELETE:                  // Step2:  Function not implemented
      ATLTRACE( L"CComponentData::Notify: MMCN_DELETE unimplemented\n" );
      hr = S_FALSE;
      break;

    case MMCN_RENAME:                  // Step2:  Function not implemented
      ATLTRACE( L"ComponentData::Notify: MMCN_RENAME unimplemented\n" );
      hr = S_FALSE;
      break;

    case MMCN_SELECT:                  // Step2:  Function not implemented
      ATLTRACE(_T("ComponentData::Notify: MMCN_SELECT unimplemented\n"));
      hr = S_FALSE;
      break;

    case MMCN_PROPERTY_CHANGE:         // Step2:  Function not implemented
      ATLTRACE(_T("ComponentData::Notify: MMCN_PROPERTY_CHANGE unimplemented\n"));
      hr = S_FALSE;
      break;

    case MMCN_REMOVE_CHILDREN:         // Step2:  Function not implemented
      ATLTRACE(_T("ComponentData::Notify: MMCN_REMOVE_CHILDREN unimplemented\n"));
      hr = S_FALSE;
      break;

    default:
      ATLTRACE( L"CComponentData::Notify: unimplemented event %x\n", event );
      hr = S_FALSE;
      break;
  }
  return hr;

} // end Notify()

//---------------------------------------------------------------------------
// This is where MMC asks us to provide IDataObjects for every node in the
// scope pane
//
STDMETHODIMP
CComponentData::QueryDataObject
(
  long              cookie,       // [in]  Data object's unique identifier
  DATA_OBJECT_TYPES type,         // [in]  Data object's type
  LPDATAOBJECT*     ppDataObject  // [out] Points to the returned data object
)
{
  HRESULT     hr      = S_OK;
  CDataObject *pdoNew = NULL;

  do
  {
pdoNew = new CDataObject;
    *ppDataObject = pdoNew;

    if (!pdoNew)
    {
      hr = E_OUTOFMEMORY;
      // Use MessageBox() rather than IConsole::MessageBox() here because the
      // first time this gets called m_ipConsole is not fully initialized
      ::MessageBox( NULL,
                    L"Unable to allocate new data object",
                    L"CComponentData::QueryDataObject",
                    MB_OK
                  );
      break;
    }

    //
    // The cookie represents a snapin manager or scope pane item.
    //
    // If the passed-in cookie is NULL, it is our snapin's root node folder
    // We never needed to ask for this to be created. MMC did this for us.
    //
    // If the passed-in cookie is non-NULL, then it should be one we
    // created when we added a node to the scope pane. See OnExpand().
    //

    if (cookie)
    {
      // cookie is the lparam field that we passed in SCOPEDATAITEM
      // used for the m_ipConsoleNameSpace->InsertItem(&sdi);
      pdoNew->SetCookie( cookie, CCT_SCOPE, COOKIE_IS_STATUS );
    }
    else
    {
      // In this case the node is our top node, and was placed there for us.
      pdoNew->SetCookie( 0, type, COOKIE_IS_ROOT );
    }
  } while (0);

  return hr;

} // end QueryDataObject()

//---------------------------------------------------------------------------
// This is where we provide strings for nodes in the scope pane.
// We only have one node, so this is easy. MMC handles the root node string.
//
STDMETHODIMP
CComponentData::GetDisplayInfo
(
  LPSCOPEDATAITEM pItem      // [in, out] Points to a SCOPEDATAITEM struct
)
{
  _ASSERT( NULL != pItem );
  _ASSERT( pItem->mask & SDI_STR );

  pItem->displayname = (LPOLESTR)L"Geometry";  // Title to our child folder

return S_OK;

} // end GetDisplayInfo()

//---------------------------------------------------------------------------
// Step2: Here we test two data objects.
//
STDMETHODIMP
CComponentData::CompareObjects
(
  LPDATAOBJECT lpDataObjectA,    // [in] First data object to compare
  LPDATAOBJECT lpDataObjectB     // [in] Second data object to compare
)
{
  CDataObject *pdoA;
  CDataObject *pdoB;

  // At least one of these data objects is supposed to be ours, so one
  // of the extracted pointers should be non-NULL.
  pdoA = ExtractOwnDataObject( lpDataObjectA );
  pdoB = ExtractOwnDataObject( lpDataObjectB );
  _ASSERT( pdoA || pdoB );             // Assert if we can't get any objects

  // If extraction failed for one of them, then that one is foreign and
  // can't be equal to the other one.  (Or else ExtractOwnDataObject
  // returned NULL because it ran out of memory, but the most conservative
  // thing to do in that case is say they're not equal.)
  if( !pdoA || !pdoB )
  {
    return S_FALSE;
  }

  // The cookie type could be COOKIE_IS_ROOT or COOKIE_IS_OBJECT.  If they
  // differ then the objects refer to different things.
  if( pdoA->GetCookieType() != pdoB->GetCookieType() )
  {
    return S_FALSE;
  }

  return S_OK;

} // end CompareObjects()

/////////////////////////////////////////////////////////////////////////////
//  Methods needed to support IComponentData
//

//---------------------------------------------------------------------------
// Here is our chance to place things under the root node.
// We will put one item under it.
//
HRESULT
CComponentData::OnExpand
(
  LPDATAOBJECT pDataObject,      // [in] Points to data object
  long         arg,              // [in] TRUE is we are expanding
  long         param             // [in] Points to the HSCOPEITEM
)
{
  CDataObject   *pdo;
  SCOPEDATAITEM sdi;
  INT           iResult;
  HRESULT       hr = S_OK;

  _ASSERT( NULL != m_ipConsoleNameSpace ); // Make sure we QI'ed for the interface
  _ASSERT( NULL != pDataObject );          // Must have valid data object

  if (TRUE == arg )
  {
    pdo = ExtractOwnDataObject( pDataObject );

    if( !pdo )
    {
      hr = m_ipConsole->MessageBox( L"Did not understand the data object.\nNot set up to be an extension",
                                    L"CComponentData::OnExpand",
                                    MB_OK,
                                    &iResult
                                  );
      return hr;
    }

    // Make sure that what we are placing ourselvs under is the root node!
    if (pdo->GetCookieType() != COOKIE_IS_ROOT)
      return S_FALSE;

    _ASSERT( CCT_SCOPE == pdo->GetContext() );   // Scope pane must be current context
    _ASSERT( COOKIE_IS_ROOT == pdo->GetCookieType() );

    //
    // Place our folder into the scope pane
    //
    ZeroMemory(&sdi, sizeof(SCOPEDATAITEM) );
    sdi.mask       = SDI_STR       |   // Displayname is valid
                     SDI_PARAM     |   // lParam is valid
                     SDI_IMAGE     |   // nImage is valid
                     SDI_OPENIMAGE |   // nOpenImage is valid
                     SDI_PARENT;
    sdi.relativeID  = (HSCOPEITEM)param;
    sdi.nImage      = 0;
    sdi.nOpenImage  = 1;
    sdi.displayname = MMC_CALLBACK;
    sdi.lParam      = (LPARAM)1;       // The cookie

    hr = m_ipConsoleNameSpace->InsertItem( &sdi );
  }
  return hr;

} // end OnExpand()


DATAOBJ.H

/////////////////////////////////////////////////////////////////////////////
// dataobj.h : IDataObject Interface to communicate data
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#ifndef __DATAOBJ_H_
#define __DATAOBJ_H_

#include <mmc.h>

//
// Defines, Types etc...
//

typedef enum tagCOOKIETYPE
{
    COOKIE_IS_ROOT,
    COOKIE_IS_STATUS
} COOKIETYPE;

class CComponentData; // Forward declaration

/////////////////////////////////////////////////////////////////////////////
// CDataObject - This class is used to pass data back and forth with MMC. It
//               uses a standard interface, IDataObject to acomplish this.
//                Refer to OLE documentation for a description of clipboard
//               formats and the IDataObject interface.

class CDataObject:
    public IDataObject,
    public CComObjectRoot
{
public:

// ATL Maps
DECLARE_NOT_AGGREGATABLE(CDataObject)

BEGIN_COM_MAP(CDataObject)
COM_INTERFACE_ENTRY(IDataObject)
END_COM_MAP()

    CDataObject();
   ~CDataObject();

    VOID SetCookie(ULONG ulCookie, DATA_OBJECT_TYPES Type, COOKIETYPE ct);

    //
    // IUnknown overrides
    //

    STDMETHOD(QueryInterface) (REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef) ();
    STDMETHOD_(ULONG, Release) ();

    //
    // IDataObject overrides
    //

    STDMETHOD(GetDataHere) (FORMATETC *pformatetc,
                          STGMEDIUM *pmedium);

// Not Implemented
private:
    STDMETHOD(GetData)(LPFORMATETC lpFormatetcIn, LPSTGMEDIUM lpMedium)
    { return E_NOTIMPL; };

    STDMETHOD(EnumFormatEtc)(DWORD dwDirection, LPENUMFORMATETC* ppEnumFormatEtc)
    { return E_NOTIMPL; };

    STDMETHOD(QueryGetData)(LPFORMATETC lpFormatetc)
    { return E_NOTIMPL; };

    STDMETHOD(GetCanonicalFormatEtc)(LPFORMATETC lpFormatetcIn, LPFORMATETC lpFormatetcOut)
    { return E_NOTIMPL; };

    STDMETHOD(SetData)(LPFORMATETC lpFormatetc, LPSTGMEDIUM lpMedium, BOOL bRelease)
    { return E_NOTIMPL; };

    STDMETHOD(DAdvise)(LPFORMATETC lpFormatetc, DWORD advf,
                LPADVISESINK pAdvSink, LPDWORD pdwConnection)
    { return E_NOTIMPL; };

    STDMETHOD(DUnadvise)(DWORD dwConnection)
    { return E_NOTIMPL; };

    STDMETHOD(EnumDAdvise)(LPENUMSTATDATA* ppEnumAdvise)
    { return E_NOTIMPL; };

    //
    // Non-interface member functions
    //
public:
    DATA_OBJECT_TYPES GetContext() { return m_Context;  }
    COOKIETYPE GetCookieType()     { return m_Type;     }
    ULONG CDataObject::GetCookie() { return m_ulCookie; }

private:
    HRESULT _WriteInternal(IStream *pstm);
    HRESULT _WriteDisplayName(IStream *pstm);
    HRESULT _WriteNodeType(IStream *pstm);
    HRESULT _WriteClsid(IStream *pstm);

    ULONG               m_cRefs;     // object refcount
    ULONG               m_ulCookie;  // what this obj refers to
    DATA_OBJECT_TYPES   m_Context;   // context in which this was created
    COOKIETYPE          m_Type;      // how to interpret _ulCookie
    CComponentData     *m_pcd;       // NULL if created by csnapin

public:
    static UINT s_cfInternal;       // Our custom clipboard format
    static UINT s_cfDisplayName;    // Our test for a node
    static UINT s_cfNodeType;
    static UINT s_cfSnapinClsid;
};

#endif // __DATAOBJ_H_


DISK.CPP

/////////////////////////////////////////////////////////////////////////////
// disk.cpp : Implementation of DLL Exports.
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

// Note: Proxy/Stub Information
//To build a separate proxy/stub DLL,
//run nmake -f diskps.mk in the project directory.

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "disk.h"

#include "disk_i.c"
#include "globals.h"
#include "compdata.h"

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_ComponentData, CComponentData)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
  if ( DLL_PROCESS_ATTACH == dwReason )
  {
    g_hinst = hInstance;               // Store global instance handle
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
  }
  else if ( DLL_PROCESS_DETACH == dwReason )
_Module.Term();

  return TRUE;                         // OK

} // end DllMain()

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
  return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
  return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry
// If we don't get these things into the registry, then MMC will not see us
// and we will not appear in the "Add Snapin" dialog.
// We error check every return value in both DllRegisterServer() and
// DllUnRegisterServer().  You may want to do this only in your debug
// version to reduce code size, at the expense of making troubleshooting
// more difficult
//
STDAPI DllRegisterServer(void)
{
  HKEY hMmcParentKey  = NULL;
  HKEY hDiskParentKey = NULL;
  HKEY hStandAloneKey = NULL;
  HKEY hNodeTypesKey  = NULL;
  HKEY hTempNodeKey   = NULL;
  LONG nErr           = 0;

  //DebugBreak();                  // Uncomment this to step through registration

  // Open the MMC Parent key
  nErr = RegOpenKey( HKEY_LOCAL_MACHINE,
                 L"Software\\Microsoft\\MMC\\SnapIns",
         &hMmcParentKey
       );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Open MMC Parent Key Failed" );

  // Create the ID for our ICompnentData Interface
  // The ID was generated for us, because we used a Wizard to create the app.
  // Take the ID for IComponentData in the IDL file.
  //
  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  // Make sure you change this if you use this code as a starting point!
  // Change other IDs as well below!
  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  nErr = RegCreateKey( hMmcParentKey,
                     L"{CF8CC7EF-0CFE-11D1-8F07-000000000000}",
               &hDiskParentKey
               );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Open Disk Parent Key Failed" );

  nErr = RegSetValueEx( hDiskParentKey,
                    L"NameString",
            0,
        REG_SZ,
        (LPBYTE)L"Disk Sample",
        sizeof(L"Disk Sample")
          );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Set NameString Failed" );

  nErr = RegSetValueEx( hDiskParentKey,
                    L"NodeType",
        0,
        REG_SZ,
        (LPBYTE)L"{570F713A-0F57-11D1-A194-00C04FC3092F}",
        sizeof(L"{570F713A-0F57-11D1-A194-00C04FC3092F}")
          );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Set NodeType Failed" );

  // We are a stand alone snapin, so set the key for this
  nErr = RegCreateKey( hDiskParentKey, L"StandAlone", &hStandAloneKey);
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Create StandAlone Key Failed" );

  // StandAlone has no children, so close it
  nErr = RegCloseKey( hStandAloneKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close StandAlone Failed" );

  // Set the node types that appear in our snapin
  nErr = RegCreateKey( hDiskParentKey, L"NodeTypes", &hNodeTypesKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Create NodeTypes Key Failed" );

  // Here is our root node.  Used uuidgen to get it
  nErr = RegCreateKey( hNodeTypesKey,
                   L"{570F713A-0F57-11D1-A194-00C04FC3092F}",
       &hTempNodeKey
         );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Create RootNode Key Failed" );

  nErr = RegSetValueEx( hTempNodeKey,
                    NULL,
            0,
        REG_SZ,
        (LPBYTE)L"Root Node",
        sizeof(L"Root Node")
          );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Set Root Node String Failed" );

  nErr = RegCloseKey( hTempNodeKey );    // Close it so we can reuse handle
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close RootNode Failed" );

  // Here is our child node under the root node. Used uuidgen to get it
  nErr = RegCreateKey( hNodeTypesKey,
                   L"{50C4FBDC-1F02-11D1-A1AE-00C04FC3092F}",
       &hTempNodeKey
         );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Create Child Node Key Failed" );

  nErr = RegSetValueEx( hTempNodeKey,
                    NULL,
        0,
        REG_SZ,
        (LPBYTE)L"Child Under Root Node",
        sizeof(L"Child Under Root Node")
          );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Set Child Node String Failed" );

  nErr = RegCloseKey( hTempNodeKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close Child Node Key Failed" );

  nErr = RegCloseKey( hNodeTypesKey  );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close Node Types Key Failed" );

  nErr = RegCloseKey( hDiskParentKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close Disk Parent Key Failed" );

  nErr = RegCloseKey( hMmcParentKey  );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close MMC Parent Key Failed" );

  // Registers object, typelib and all interfaces in typelib
  return _Module.RegisterServer(TRUE);

} // end DllRegisterServer()

//---------------------------------------------------------------------------
// DllUnregisterServer:  Since we are a good citizen, delete all the keys
//                       we added to the MMC keys in the registry as well as
//                       the normal unregistering of our com objects.
//
STDAPI DllUnregisterServer(void)
{
  HKEY hMmcParentKey  = NULL;          // MMC parent key
  HKEY hDiskParentKey = NULL;          // Our Snap-In key  - has children
  HKEY hNodeTypesKey  = NULL;          // Our NodeType key - has children
  LONG nErr           = 0;

  // DebugBreak();                       // Uncomment this to step through UnRegister

  // Open the MMC parent key
  nErr = RegOpenKey( HKEY_LOCAL_MACHINE,
                     L"Software\\Microsoft\\MMC\\SnapIns",
                     &hMmcParentKey
                   );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Open MMC Parent Key Failed"  );

  // Open our Parent key
  nErr = RegOpenKey( hMmcParentKey,
                     L"{CF8CC7EF-0CFE-11D1-8F07-000000000000}",
                     &hDiskParentKey
                   );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Open Disk Parent Key Failed" );

  // Now open the NodeTypes key
  nErr = RegOpenKey( hDiskParentKey,       // Handle of parent key
                     L"NodeTypes",         // Name of key to open
                     &hNodeTypesKey        // Handle to newly opened key
                   );
  if( ERROR_SUCCESS != nErr )
     DisplayError( GetLastError(), L"Open NodeTypes Key Failed"  );

  // Delete the root node key
  nErr = RegDeleteKey( hNodeTypesKey, L"{570F713A-0F57-11D1-A194-00C04FC3092F}" );
  if( ERROR_SUCCESS != nErr )
     DisplayError( GetLastError(), L"Delete Root Node Key Failed"  );

  // Delete the child node key
  nErr = RegDeleteKey( hNodeTypesKey, L"{50C4FBDC-1F02-11D1-A1AE-00C04FC3092F}" );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Delete Child Node Key Failed"  );

  // Close the node type key so we can delete it
  nErr = RegCloseKey( hNodeTypesKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close NodeTypes Key failed"  );

  // Delete the NodeTypes key
  nErr = RegDeleteKey( hDiskParentKey, L"NodeTypes" );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Delete NodeTypes Key failed"  );

  // StandAlone key has no children so we can delete it now
  nErr = RegDeleteKey( hDiskParentKey, L"StandAlone" );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Delete StandAlone Key Failed"  );

  // Close our Parent Key
  nErr = RegCloseKey( hDiskParentKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close Disk Parent Key Failed"  );

  // Now we can delete our Snap-In key since the children are gone
  nErr = RegDeleteKey( hMmcParentKey, L"{CF8CC7EF-0CFE-11D1-8F07-000000000000}" );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Delete Disk Parent Key Failed"  );

  nErr = RegCloseKey( hMmcParentKey );
  if( ERROR_SUCCESS != nErr )
    DisplayError( GetLastError(), L"Close MMC Parent Key Failed"  );

  // UnRegisters object, typelib and all interfaces in typelib
  return _Module.UnregisterServer();

} // end DllUnregisterServer()


DISK.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 3.01.75 */
/* at Thu Oct 23 17:30:02 1997
 */
/* Compiler settings for disk.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"

#ifndef __disk_h__
#define __disk_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __ComponentData_FWD_DEFINED__
#define __ComponentData_FWD_DEFINED__

#ifdef __cplusplus
typedef class ComponentData ComponentData;
#else
typedef struct ComponentData ComponentData;
#endif /* __cplusplus */

#endif /* __ComponentData_FWD_DEFINED__ */

/* header files for imported files */
#include "oaidl.h"
#include "ocidl.h"
#include "mmc.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __DISKLib_LIBRARY_DEFINED__
#define __DISKLib_LIBRARY_DEFINED__

/****************************************
 * Generated header for library: DISKLib
 * at Thu Oct 23 17:30:02 1997
 * using MIDL 3.01.75
 ****************************************/
/* [helpstring][version][uuid] */

EXTERN_C const IID LIBID_DISKLib;

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_ComponentData;

class DECLSPEC_UUID("CF8CC7EF-0CFE-11D1-8F07-000000000000")
ComponentData;
#endif
#endif /* __DISKLib_LIBRARY_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


DISK_I.C

/* this file contains the actual definitions of */
/* the IIDs and CLSIDs */

/* link this file in with the server and any clients */

/* File created by MIDL compiler version 3.01.75 */
/* at Thu Oct 23 17:30:02 1997
 */
/* Compiler settings for disk.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#ifdef __cplusplus
extern "C"{
#endif

#ifndef __IID_DEFINED__
#define __IID_DEFINED__

typedef struct _IID
{
    unsigned long x;
    unsigned short s1;
    unsigned short s2;
    unsigned char  c[8];
} IID;

#endif // __IID_DEFINED__

#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED

const IID LIBID_DISKLib = {0xCF8CC7E0,0x0CFE,0x11D1,{0x8F,0x07,0x00,0x00,0x00,0x00,0x00,0x00}};

const CLSID CLSID_ComponentData = {0xCF8CC7EF,0x0CFE,0x11D1,{0x8F,0x07,0x00,0x00,0x00,0x00,0x00,0x00}};

#ifdef __cplusplus
}
#endif


GLOBALS.CPP

/////////////////////////////////////////////////////////////////////////////
// globals.cpp : IComponent Interface helpers
//
// This is a part of the MMC SDK.
// Copyright (C) 1997 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// MMC SDK Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// MMC Library product.
//

#include "stdafx.h"
#include "globals.h"

HINSTANCE g_hinst;           // Global instance handle

/////////////////////////////////////////////////////////////////////////////
// Global helper functions to help work with dataobjects and
// clipboard formats

//---------------------------------------------------------------------------
//  Returns the current object based on the s_cfInternal clipboard format
//
CDataObject*
ExtractOwnDataObject
(
  LPDATAOBJECT lpDataObject      // [in] IComponent pointer
)
{
  HGLOBAL     hGlobal;
  HRESULT     hr   = S_OK;
  CDataObject *pdo = NULL;

  hr = ExtractFromDataObject( lpDataObject,
                              CDataObject::s_cfInternal,
                              sizeof(CDataObject **),
                              &hGlobal
                            );

  if (SUCCEEDED(hr))
  {
    pdo = *(CDataObject **)(hGlobal);
    _ASSERT(pdo);
    _ASSERT(!GlobalFree(hGlobal));
  }

  return pdo;

} // end ExtractOwnDataObject()

//---------------------------------------------------------------------------
//  Extracts data based on the passed-in clipboard format
//
HRESULT
ExtractFromDataObject
(
  LPDATAOBJECT lpDataObject,   // [in]  Points to data object
  UINT         cfClipFormat,   // [in]  Clipboard format to use
  ULONG        nByteCount,     // [in]  Number of bytes to allocate
  HGLOBAL      *phGlobal       // [out] Points to the data we want
)
{
  HRESULT hr = S_OK;
  STGMEDIUM stgmedium = { TYMED_HGLOBAL,  NULL  };
  FORMATETC formatetc = { cfClipFormat, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };

  _ASSERT( NULL != lpDataObject );

  *phGlobal = NULL;

  do
{
    // Allocate memory for the stream
    stgmedium.hGlobal = GlobalAlloc( GMEM_SHARE, nByteCount );

if (!stgmedium.hGlobal)
    {
      hr = E_OUTOFMEMORY;
      ATLTRACE( L"Out of memory\n" );
      break;
    }

    // Attempt to get data from the object
    hr = lpDataObject->GetDataHere( &formatetc, &stgmedium );
    if (FAILED(hr))
    {
      break;
    }

    // stgmedium now has the data we need
    *phGlobal = stgmedium.hGlobal;
    stgmedium.hGlobal = NULL;

  } while (0);

  if (FAILED(hr) && stgmedium.hGlobal)
  {
    _ASSERT( !GlobalFree(stgmedium.hGlobal) );
  }
  return hr;

} // end ExtractFromDataObject()

VOID DisplayError( LONG nErrorCode, LPWSTR wszDlgTitle )
{
  LPVOID lpMsgBuf;
  ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
               NULL,
                   nErrorCode,
                   MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                   (LPWSTR)&lpMsgBuf,
           0,
           NULL
         );
  ::MessageBox( NULL, (LPWSTR)lpMsgBuf, wszDlgTitle, MB_OK|MB_ICONINFORMATION );
  LocalFree( lpMsgBuf );

}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by disk.rc
//
#define IDS_PROJNAME                    100
#define IDS_COMPONENTDATA_DESC          101
#define IDR_ComponentData               102
#define IDB_SCOPE_16x16                 201
#define IDB_RESULT_16x16                202
#define IDB_RESULT_32x32                203

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           103
#endif
#endif


STDAFX.H

// stdafx.h : include file for standard system include files,
//      or project specific include files that are used frequently,
//      but are changed infrequently

#if !defined(AFX_STDAFX_H__CF8CC7E4_0CFE_11D1_8F07_000000000000__INCLUDED_)
#define AFX_STDAFX_H__CF8CC7E4_0CFE_11D1_8F07_000000000000__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#define STRICT

#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>
//#include <atlctl.h>

#if __RPCNDR_H_VERSION__ < 440             // This may be needed when building
#define __RPCNDR_H_VERSION__ 440           // on NT5 (1671) to prevent MIDL errors
#define MIDL_INTERFACE(x) interface
#endif

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__CF8CC7E4_0CFE_11D1_8F07_000000000000__INCLUDED)


APITSTDB.CPP

#pragma message("Test of Msi External Database Access - Resequences InstallSequence table.  Copyright 1997 - 1998 Microsoft Corp.")
#if 0  // makefile definitions, to build: %vcbin%\nmake -fApiTstDb.cpp
DESCRIPTION = Test of Msi external database access
MODULENAME = ApiTstDb
SUBSYSTEM = console
FILEVERSION = 0.20
!include <MsiTool.Mak>
!if 0  #nmake skips the rest of this file
#endif // end of makefile definitions

// test of external database access
#define WINDOWS_LEAN_AND_MEAN  // faster compile
#include <windows.h>
#ifndef RC_INVOKED    // start of source code
#include <stdio.h>   // printf/wprintf
#include <tchar.h>   // define UNICODE=1 on nmake command line to build UNICODE
#include "MsiQuery.h"

// routine to check return status for ERROR_SUCCESS, and throw exception if not
TCHAR*  g_szErrorContext = 0;
void CheckMsi(UINT iStat, TCHAR* szContext)
{
if (iStat != ERROR_SUCCESS)
{
g_szErrorContext = szContext;
throw iStat;
}
}

void SmoothSequence(MSIHANDLE hDatabase, int iSpacing)
{
PMSIHANDLE hView;
CheckMsi(MsiDatabaseOpenView(hDatabase, TEXT("SELECT Action, Sequence FROM InstallSequence ORDER BY Sequence"),&hView),TEXT("OpenView"));
CheckMsi(MsiViewExecute(hView, 0), TEXT("Execute View"));
int iLast = 0;
for (;;)
{
PMSIHANDLE hRecord;
UINT iStat = MsiViewFetch(hView, &hRecord);
if (iStat == ERROR_NO_MORE_ITEMS)
break;
CheckMsi(iStat, TEXT("Fetch"));
TCHAR szAction[60];
int iSequence, iNew;
DWORD cchAction = sizeof(szAction)/sizeof(TCHAR);
MsiRecordGetString(hRecord, 1, szAction, &cchAction);
iSequence = MsiRecordGetInteger(hRecord, 2);
if (iSequence > 0)
{
iLast += iSpacing;
iNew = iLast;
CheckMsi(MsiRecordSetInteger(hRecord, 2, iNew), TEXT("SetInteger"));
CheckMsi(MsiViewModify(hView, MSIMODIFY_UPDATE, hRecord), TEXT("UpdateView"));
}
else
iNew = iSequence;
_tprintf(TEXT("%3i %3i  %s\n"), iSequence, iNew, szAction);
}
}

extern "C" int __cdecl _tmain(int argc, TCHAR* argv[])
{
if (argc < 2)
{
_tprintf(TEXT("Usage: ApiTstDb {database path} [{sequence spacing}]"));
return 1;
}
int iSpacing = 10;
if (argc >= 3)
{
int i = _ttoi(argv[2]);
if (i > 0)
iSpacing = i;
}
try
{
PMSIHANDLE hDatabase;
CheckMsi(MsiOpenDatabase(argv[1], MSIDBOPEN_TRANSACT, &hDatabase),TEXT("OpenDatabase"));
SmoothSequence(hDatabase, iSpacing);
MsiDatabaseCommit(hDatabase);
TCHAR rgchBuf[MAX_PATH];
GetTempPath(MAX_PATH, rgchBuf);
CheckMsi(MsiDatabaseExport(hDatabase, TEXT("InstallSequence"), rgchBuf, TEXT("InstallSequence.idt")),TEXT("ExportTable"));
}
catch (UINT iError)
{
_tprintf(TEXT("%s error %i"), g_szErrorContext, iError);
return 1;
}
int iOpenHandles = MsiCloseAllHandles();  // diagnostic check only
if (iOpenHandles != 0)
_tprintf(TEXT("%i Handle(s) not closed"), iOpenHandles);
return 0;
}

#else // RC_INVOKED, end of source code, start of resources
// resource definition go here
#endif // RC_INVOKED
#if 0
!endif // makefile terminator
#endif


CUSTACT1.CPP

#pragma message("Simple Custom Action DLL.  Copyright 1997 - 1998 Microsoft Corp.")
#if 0  // makefile definitions, build with "nmake -f CustAct1.cpp"
DESCRIPTION = Custom Action Test DLL
MODULENAME = CustAct1
FILEVERSION = 0.20
ENTRY = Action1
!include <MsiTool.Mak>
!if 0  #nmake skips the rest of this file
#endif // end of makefile definitions

// test of external database access
#define WINDOWS_LEAN_AND_MEAN  // faster compile
#include <windows.h>  // included for both CPP and RC passes
#ifndef RC_INVOKED    // start of CPP source code
#include <stdio.h>    // printf/wprintf
#include <tchar.h>    // define UNICODE=1 on nmake command line to build UNICODE
#include "MsiQuery.h" // must be in this directory or on INCLUDE path

UINT __stdcall Action1(MSIHANDLE hInstall)
{
TCHAR szValue[200];
DWORD cchValue = sizeof(szValue)/sizeof(TCHAR);
if (MsiGetProperty(hInstall, "TESTACTION", szValue, &cchValue)
== ERROR_SUCCESS)
::MessageBox(0, szValue, TEXT("Greetings from Action1"), MB_OK);
return ERROR_SUCCESS;
}

#else // RC_INVOKED, end of CPP source code, start of resource definitions
// resource definition go here
#endif // RC_INVOKED
#if 0  // required at end of source file, to hide makefile terminator
!endif // makefile terminator
#endif


CUSTEXE1.CPP

#pragma message("Custom Action Test EXE.  Copyright 1997 - 1998 Microsoft Corp.")
#if 0  // makefile definitions, build with "nmake -f CustExe1.cpp"
DESCRIPTION = Custom Action Test EXE
MODULENAME  = CustExe1
FILEVERSION = 1.0,0,0
SUBSYSTEM = windows
!include <MsiTool.Mak>
!if 0  #nmake skips the rest of this file
#endif // end of makefile definitions

#define WINDOWS_LEAN_AND_MEAN  // faster compile
#include <windows.h>
#include <tchar.h>   // define UNICODE=1 on nmake command line to build UNICODE
#define IDD_TEST 1
#ifndef RC_INVOKED   // start of CPP source code

BOOL CALLBACK DialogProc(HWND  hwndDlg, UINT uMsg, WPARAM wParam, LPARAM  lParam)
{
switch (uMsg)
{
case WM_INITDIALOG: return TRUE; // indicate we did not set focus to the control
case WM_COMMAND:
switch (wParam)
{
case IDOK:     PostQuitMessage(0); return TRUE;
case IDCANCEL: PostQuitMessage(1); return TRUE;
}
return FALSE;
default: return FALSE;
};
}

extern "C" int __stdcall _tWinMain(HINSTANCE hInst, HINSTANCE/*hPrev*/, TCHAR* szCmdLine, int/*show*/)
{
if ((szCmdLine[0]=='-' || szCmdLine[0]=='/') && (szCmdLine[1]=='Q' || szCmdLine[1]=='q'))
return 0;
HWND hWnd = ::CreateDialog(hInst, MAKEINTRESOURCE(1), 0, (DLGPROC)DialogProc);
if (hWnd == 0)
return 2;
::SetDlgItemText(hWnd, 8, szCmdLine);
//::ShowWindow(hWnd, SW_SHOW); //!! why do we need this?
MSG msg;
while (::GetMessage(&msg, 0, 0, 0) == TRUE)
::IsDialogMessage(hWnd, &msg);
::DestroyWindow(hWnd);
return msg.wParam;
}

#else // RC_INVOKED, end of source code, start of resources
IDD_TEST DIALOG 150, 150, 160, 75
STYLE DS_MODALFRAME | DS_NOIDLEMSG | WS_POPUP | WS_CAPTION | WS_VISIBLE
CAPTION "Custom Action Test EXE"
FONT 12, "Arial"
{
 LTEXT      "",         8,         20,  7, 120, 40
 PUSHBUTTON "&Succeed", IDOK,      20, 50,  40, 16
 PUSHBUTTON "&Fail",    IDCANCEL, 100, 50,  40, 16
}
#endif // RC_INVOKED
#if 0
!endif // makefile terminator
#endif


INITMSI.CPP

// initmsi.cpp: MSI initialisation functions for msispy
#include "initmsi.h"
#include "resource.h"
#include "propshts.h"
#include "stdio.h"

// hard-coded component GUIDs and feature Names
TCHARg_szEXEComponentCode[MAX_GUID+1]= TEXT("{5CB2D5F1-19DD-11d1-9A9D-006097C4E489}");
TCHARg_szDLLComponentCode[MAX_GUID+1]= TEXT("{5CB2D5F0-19DD-11d1-9A9D-006097C4E489}");
TCHARg_szIntlDLLComponentCode[MAX_GUID+1]= TEXT("{B62B2CE0-1A98-11d1-9A9E-006097C4E489}");
TCHARg_szHelpComponentCode[MAX_GUID+1]= TEXT("{1F7586D0-20B1-11d1-9AB3-006097C4E489}");
TCHARg_szMyProductCode[MAX_GUID+1]= TEXT("");// will be obtained at runtime
TCHARg_szDLLFeatureName[]= TEXT("SystemInterface");
TCHARg_szEXEFeatureName[]= TEXT("UserInterface");
TCHARg_szDefaultQualifier[]= TEXT("0");
LCIDg_lcidCurrentLocale;
TCHARg_szHelpFilePath[MAX_PATH+1]= TEXT("");

externMSISPYSTRUCTvmInfo;

// This message is displayed if the (localised) resource DLL could not be loaded.
// This string will always appear in English regardless of the User/System LCID.

#define ERRORMESSAGE_UNABLETOLOADDLLTEXT("Msispy was unable to load the resource file.\nComponent Code: %s\nError Code: %d")
#defineERRORCAPTION_UNABLETOLOADDLLTEXT("Msispy: Fatal Error")

// -------------------------------------------------------------------------------------------
// FindComponent()
//Locates and provides the required component calling MsiProvideQualifiedComponent,
//using the global LCID (g_lcidCurrentLocale) as the qualifier. If that fails, it
//tries just the primary lang-ID of g_lcidCurrentLocale. If that fails as well,
//it tries the default qualifier (g_szDefaultQualifier). Finally if this fails as
//well it returns the error code of this attempt, else it returns ERROR_SUCCESS
// The path to the component is returned in szPath

UINT FindComponent(
  LPCTSTRszComponentCode,// GUID of the component to Provide
  LPTSTRszPath,//Buffer for returned path
  DWORD*pcchPath//size of buffer
  )
{
DWORDcchPathInitial = *pcchPath;
lstrcpy(szPath, TEXT(""));// set [out] variable to known value

TCHARszQualifier[MAX_PATH+1];
UINTiResult;
for (UINT iCount = 0; 3 > iCount ; iCount++)
{
switch (iCount)
{
case 0:
//Try the full Language ID
wsprintf(szQualifier, TEXT("%4.4x"), LANGIDFROMLCID(g_lcidCurrentLocale));
break;

case 1:
// Full language ID failed, try primary Language ID
wsprintf(szQualifier, TEXT("%2.2x"), PRIMARYLANGID(LANGIDFROMLCID(g_lcidCurrentLocale)));
break;

case 2:
// language IDs failed, try the default qualifier
lstrcpy(szQualifier, g_szDefaultQualifier);
break;
}

*pcchPath = cchPathInitial;
iResult = MsiProvideQualifiedComponent(szComponentCode, szQualifier, 0, szPath, pcchPath);

switch (iResult)
{
case ERROR_SUCCESS:
case ERROR_INSTALL_USEREXIT:
case ERROR_INSTALL_FAILURE:
return iResult;
}
}

// ProvideQualifiedComponent failed
return iResult;
}

// -------------------------------------------------------------------------------------------
// fInitMSI()
//Function to fault in resource DLL and initialise MSI related items
//Returns TRUE if the MSI initialisation succeeded, FALSE if there
//was a fatal error.
//When the function returns, hResourceInstance points to the hInstance
//of the resource DLL if the DLL was loaded successfully

BOOL fInitMSI(HINSTANCE *hResourceInstance)
{

// set [out] variable to known value
*hResourceInstance = 0;

g_lcidCurrentLocale = GetUserDefaultLCID();
// Get the product-code of the product using this component.
// The product-code is not hard-coded in because a component may be
// shared by multiple products.
MsiGetProductCode(g_szEXEComponentCode, g_szMyProductCode);

TCHARszIntlDLLPath[MAX_PATH+1];
DWORDcchIntlDLLPath = MAX_PATH+1;
UINTiResult;

// Try finding the resource DLL- if unsuccessful, inform user and exit
if (ERROR_SUCCESS != (iResult = FindComponent(g_szIntlDLLComponentCode, szIntlDLLPath, &cchIntlDLLPath)))
{
TCHARszErrorMessage[MAX_MESSAGE+1];
wsprintf(szErrorMessage, ERRORMESSAGE_UNABLETOLOADDLL, g_szIntlDLLComponentCode, iResult);

TCHARszErrorCaption[MAX_HEADER+1];
wsprintf(szErrorCaption, ERRORCAPTION_UNABLETOLOADDLL);

MessageBox(NULL, szErrorMessage, szErrorCaption, MB_ICONSTOP|MB_OK);
return FALSE;
}

// Load the international DLL
*hResourceInstance = W32::LoadLibrary(szIntlDLLPath);

// Check if the SystemInterface feature is available for use. If not,
// call SwitchMode to gray out features that depend on it and inform
// user.
if (MsiQueryFeatureState(g_szMyProductCode, g_szDLLFeatureName) == INSTALLSTATE_UNKNOWN)
{
// Switch to Degraded Mode [grays out features that depend on SystemInterface feature]
SwitchMode(MODE_DEGRADED);

TCHARszRestrMsg[MAX_MESSAGE+1];
LoadString(*hResourceInstance, IDS_STARTUPRESTRICTEDMSG, szRestrMsg, MAX_MESSAGE+1);

TCHARszRestrCaption[MAX_HEADER+1];
LoadString(*hResourceInstance, IDS_STARTUPRESTRICTEDCPN, szRestrCaption, MAX_HEADER+1);

// Inform user that Msispy is now in "Restricted Mode"
MessageBox(NULL, szRestrMsg, szRestrCaption, MB_OK|MB_ICONEXCLAMATION);
}
else
SwitchMode(MODE_NORMAL);

// Prepare to use the UserInterface feature: check its current state and increase usage count.
INSTALLSTATE iEXEFeatureState = MsiUseFeature(g_szMyProductCode, g_szEXEFeatureName);

// If feature is not currently usable, try fixing it
switch (iEXEFeatureState)
{
case INSTALLSTATE_DEFAULT:
case INSTALLSTATE_LOCAL:
case INSTALLSTATE_SOURCE:

// feature is installed and usable
return TRUE;

case INSTALLSTATE_ABSENT:

// feature isn't installed, try installing it
if (MsiConfigureFeature(g_szMyProductCode, g_szEXEFeatureName, INSTALLSTATE_LOCAL) != ERROR_SUCCESS)
return FALSE;// installation failed
break;

default:

// feature is busted- try fixing it
if (MsiReinstallFeature(g_szMyProductCode, g_szEXEFeatureName,
REINSTALLMODE_REPAIR
+ REINSTALLMODE_FILEEQUALVERSION
+ REINSTALLMODE_MACHINEDATA
+ REINSTALLMODE_USERDATA
+ REINSTALLMODE_SHORTCUT) != ERROR_SUCCESS)
return FALSE;// we couldn't fix it
break;
}

return TRUE;
}

// -------------------------------------------------------------------------------------------
// fHandleHelp()
//  Creates a new process to bring up the help
//Uses FindComponent to load the appropriate help file (based on g_lcidCurrentLocale)

BOOL fHandleHelp(HINSTANCE hResourceInstance)
{
DWORDcchHelpFilePath= MAX_PATH+1;
UINTiResult= 0;

if (ERROR_SUCCESS != (iResult = FindComponent(g_szHelpComponentCode, g_szHelpFilePath, &cchHelpFilePath)))
{
// we couldn't find the help file
TCHAR szErrorMsg[MAX_MESSAGE+1];
TCHAR szErrorCpn[MAX_HEADER+1];
LoadString(hResourceInstance, IDS_NOHELPMSG, szErrorMsg, MAX_MESSAGE+1);
LoadString(hResourceInstance, IDS_NOHELPCPN, szErrorCpn, MAX_HEADER+1);

MessageBox(NULL, szErrorMsg, szErrorCpn, MB_OK|MB_ICONEXCLAMATION);
return FALSE;
}

//MsiUseFeature(g_szMyProductCode, g_szHelpFeatureName);

// Launch WinHelp to handle help
return WinHelp(vmInfo.hwndParent, g_szHelpFilePath, HELP_FINDER, 0);

}


MSIMERG.CPP

#pragma message("Merge Database Utility.  Copyright 1997 - 1998 Microsoft Corp.")
#if 0  // makefile definitions, to build: %vcbin%\nmake -fMsiMerg.cpp
DESCRIPTION = Merge Database Utility
MODULENAME = MsiMerg
SUBSYSTEM = console
FILEVERSION = Msi
LINKLIBS = OLE32.lib
!include <MsiTool.Mak>
!if 0  #nmake skips the rest of this file
#endif // end of makefile definitions

#define W32DOWS_LEAN_AND_MEAN  // faster compile
#define W32
#define MSI

#include <windows.h>
#ifndef RC_INVOKED    // start of source code
#include <tchar.h>    // define UNICODE=1 on nmake command line to build UNICODE
#include "MsiQuery.h" // MSI API

//________________________________________________________________________________
//
// Constants and globals
//________________________________________________________________________________

const TCHAR szHelp[] = TEXT("Msi Merge Tool --- Merge Two Databases\n\nMsiMerg(d).exe {base db} {ref db}\n");
const TCHAR szTable[] = TEXT("_MergeErrors");

const int cchDisplayBuf = 4096;
HANDLE g_hStdOut;
TCHAR g_rgchBuffer[4096];

//________________________________________________________________________________
//
// Function prototypes
//________________________________________________________________________________

void Display(LPCTSTR szMessage);
void ErrorExit(UINT iError, LPCTSTR szMessage);
void CheckError(UINT iError, LPCTSTR szMessage);
void Merge(TCHAR* szBaseDb, TCHAR* szRefDb);

//_____________________________________________________________________________________________________
//
// main
//_____________________________________________________________________________________________________

extern "C" int __cdecl _tmain(int argc, TCHAR* argv[])
{
// Determine handle
g_hStdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
if (g_hStdOut == INVALID_HANDLE_VALUE)
g_hStdOut = 0;  // non-zero if stdout redirected or piped

if (argc == 2 && ((_tcscmp(argv[1], TEXT("-?")) == 0) || (_tcscmp(argv[1], TEXT("/?")) == 0)))
ErrorExit( 0, szHelp);

// Check for enough arguments and valid options
CheckError(argc != 3, TEXT("msimerg(d).exe {base db} {ref db}"));
Merge(argv[1], argv[2]);
ErrorExit(0, TEXT("Done"));
return 0;
}

//________________________________________________________________________________
//
// Merge function
//    Merge(...);
//________________________________________________________________________________

void Merge(TCHAR* szBaseDb, TCHAR* szRefDb)
{
PMSIHANDLE hBaseDb = 0;
PMSIHANDLE hRefDb = 0;

CheckError(MSI::MsiOpenDatabase(szBaseDb, MSIDBOPEN_TRANSACT, &hBaseDb), TEXT("Error Opening Base Database"));
CheckError(MSI::MsiOpenDatabase(szRefDb, MSIDBOPEN_READONLY, &hRefDb), TEXT("Error Opening Reference Databaes"));
UINT uiError = MSI::MsiDatabaseMerge(hBaseDb, hRefDb, szTable);
CheckError(MSI::MsiDatabaseCommit(hBaseDb), TEXT("Error Saving Database"));
CheckError(uiError, TEXT("Error Merging Database, Check _MergeErrors Table for Merge conflicts"));
}

//________________________________________________________________________________
//
// Error handling and Display functions:
//    Display(...);
//   ErrorExit(...);
//    CheckError(...);
//
//________________________________________________________________________________

void Display(LPCTSTR szMessage)
{
if (szMessage)
{
int cbOut = _tcsclen(szMessage);;
if (g_hStdOut)
{
#ifdef UNICODE
char rgchTemp[cchDisplayBuf];
if (W32::GetFileType(g_hStdOut) == FILE_TYPE_CHAR)
{
W32::WideCharToMultiByte(CP_ACP, 0, szMessage, cbOut, rgchTemp, sizeof(rgchTemp), 0, 0);
szMessage = (LPCWSTR)rgchTemp;
}
else
cbOut *= 2;   // write Unicode if not console device
#endif
DWORD cbWritten;
W32::WriteFile(g_hStdOut, szMessage, cbOut, &cbWritten, 0);
}
else
W32::MessageBox(0, szMessage, W32::GetCommandLine(), MB_OK);
}
}

void ErrorExit(UINT iError, LPCTSTR szMessage)
{
if (szMessage)
{
int cbOut;
TCHAR szBuffer[256];  // errors only, not used for display output
if (iError == 0)
cbOut = lstrlen(szMessage);
else
{
LPCTSTR szTemplate = (iError & 0x80000000L)
? TEXT("Error 0x%X. %s\n")
: TEXT("Error %i. %s\n");
cbOut = wsprintf(szBuffer, szTemplate, iError, szMessage);
szMessage = szBuffer;
}
if (g_hStdOut)
{
#ifdef UNICODE
char rgchTemp[cchDisplayBuf];
if (W32::GetFileType(g_hStdOut) == FILE_TYPE_CHAR)
{
W32::WideCharToMultiByte(CP_ACP, 0, szMessage, cbOut, rgchTemp, sizeof(rgchTemp), 0, 0);
szMessage = (LPCWSTR)rgchTemp;
}
else
cbOut *= 2;   // write Unicode if not console device
#endif // UNICODE
DWORD cbWritten;
W32::WriteFile(g_hStdOut, szMessage, cbOut, &cbWritten, 0);
}
else
W32::MessageBox(0, szMessage, W32::GetCommandLine(), MB_OK);
}
MSI::MsiCloseAllHandles();
W32::ExitProcess(szMessage != 0);
}

void CheckError(UINT iError, LPCTSTR szMessage)
{
if (iError != ERROR_SUCCESS)
ErrorExit(iError, szMessage);
}

#else // RC_INVOKED, end of source code, start of resources
#endif // RC_INVOKED
#if 0
!endif // makefile terminator
#endif


MSIVAL.CPP

#if 0  // makefile definitions, to build: %vcbin%\nmake -fMsiVal.cpp

# Copyright 1997 - 1998 Microsoft Corporation

DESCRIPTION = Msi Database Validator Using External API
MODULENAME = MsiVal
SUBSYSTEM = console
FILEVERSION = Msi
!include <MsiTool.Mak>
!if 0  #nmake skips the rest of this file
#endif // end of makefile definitions

// Required headers
#define WINDOWS_LEAN_AND_MEAN  // faster compile
#include <windows.h>

#define IDS_NoError            0
#define IDS_DuplicateKey       1
#define IDS_Required           2
#define IDS_BadLink            3
#define IDS_Overflow           4
#define IDS_Underflow          5
#define IDS_NotInSet           6
#define IDS_BadVersion         7
#define IDS_BadCase            8
#define IDS_BadGuid            9
#define IDS_BadWildCard        10
#define IDS_BadIdentifier      11
#define IDS_BadLanguage        12
#define IDS_BadFileName        13
#define IDS_BadPath            14
#define IDS_BadCondition       15
#define IDS_BadFormatted       16
#define IDS_BadTemplate        17
#define IDS_BadDefaultDir      18
#define IDS_BadRegPath         19
#define IDS_BadCustomSource    20
#define IDS_BadProperty        21
#define IDS_MissingData        22
#define IDS_BadCabinet         23
#define IDS_BadCategory        24
#define IDS_BadKeyTable        25
#define IDS_BadMaxMinValues    26
#define IDS_BadShortcut        27
#define IDS_StringOverflow     28
#define IDS_UndefinedError     29
#define IDS_MissingEntry       30
#define IDS_BadLocalizeAttrib  31

#ifndef RC_INVOKED    // start of source code
#include <stdio.h>   // printf/wprintf
#include <tchar.h>   // define UNICODE=1 on nmake command line to build UNICODE
#include "MsiQuery.h"

TCHAR*  g_szErrorContext = 0; // Global error string
HANDLE g_hStdOut = 0; // Global handle

// Function prototypes
void Display(LPCTSTR szMessage);
void CheckMsi(UINT iStat, TCHAR* szContext);
BOOL CheckMissingColumns(MSIHANDLE hDatabase);
BOOL Validate(MSIHANDLE hDatabase);
BOOL ValidateOrganizationInstallSequence(MSIHANDLE hDatabase);
BOOL ValidateRequired(MSIHANDLE hDatabase);
BOOL InDialogTable(MSIHANDLE hDatabase, LPCTSTR szAction);
BOOL InCustomActionTable(MSIHANDLE hDatabase, LPCTSTR szAction);
BOOL ValidateInstallSequence(MSIHANDLE hDatabase);

// SQL queries
const TCHAR szSQLTableCatalog[]         = TEXT("SELECT `Name` FROM `_Tables`");
const TCHAR szSQLTable[]                = TEXT("SELECT * FROM ");
const TCHAR szSQLColMissing[]           = TEXT("SELECT `Table`, `Number`, `Name`, `Type` FROM `_Columns` WHERE `Table`=? AND `Name`=?");
const TCHAR szSQLValidationTable[]      = TEXT("SELECT `Table`, `Column` FROM `_Validation`");
const TCHAR szSQLInstallSeqTable[]      = TEXT("SELECT `Action`, `Sequence` FROM `InstallSequence` ORDER BY `Sequence`");
const TCHAR szSQLInstallValidate[]      = TEXT("SELECT `Action`, `SectionFlag` FROM `_InstallValidate` WHERE `Action`=?");
const TCHAR szSQLRequiredTable[]        = TEXT("SELECT `Table`, `Value`, `KeyCount` FROM `_Required` ORDER BY `Table`");
const TCHAR szSQLDialogTable[]          = TEXT("SELECT `Dialog` FROM `Dialog` WHERE `Dialog`=?");
const TCHAR szSQLCustomActionTable[]    = TEXT("SELECT `Action` FROM `CustomAction` WHERE `Action`=?");
const TCHAR szSQLSeqTableQueryNotNull[] = TEXT("SELECT `Dependent` FROM `_Sequence` WHERE `Action`=? AND `Marker`=210  AND `After`=0");
const TCHAR szSQLSeqTableQueryNull[]    = TEXT("SELECT `Dependent` FROM `_Sequence` WHERE `Action`=? AND `Marker`=0 AND `After`=1 AND `Optional`=0");
const TCHAR szSQLSeqTableAddCol[]       = TEXT("ALTER TABLE `_Sequence` ADD `Marker` SHORT TEMPORARY");
const TCHAR szSQLSeqMarkerInit[]        = TEXT("UPDATE `_Sequence` SET `Marker`=0");

const int iMaxNumColumns = 32;
const int cchBuffer = 4096;
const int cbName = 64;

const int cchDisplayBuf = 4096;

// InstallSequence sectional constants
const int isdSearch         = 0x00000001L;
const int isdCosting        = 0x00000002L;
const int isdSelection      = 0x00000004L;
const int isdAdvertise      = 0x00000008L; // before execute because must be called before InstallValidate
const int isdExecution      = 0x00000010L;

// InstallSequence sectional devisors
const TCHAR szEndSearch[]      = TEXT("CostInitialize");  // end Search, begin Costing
const TCHAR szEndCosting[]     = TEXT("CostFinalize");    // end Costing, begin Selection
const TCHAR szEndSelection[]   = TEXT("RegisterProduct"); // end Selection, begin Advertise
const TCHAR szEndAdvertise[]   = TEXT("InstallValidate"); // end Advertise, begin Execution
const TCHAR szReset[]          = TEXT("ExecuteFinalize"); // reset to Search

// InstallSequence divisions
const TCHAR szSearch[]         = TEXT("Search");
const TCHAR szCosting[]        = TEXT("Costing");
const TCHAR szSelection[]      = TEXT("Selection");
const TCHAR szAdvertise[]      = TEXT("Advertise");
const TCHAR szExecution[]      = TEXT("Execution");

//_______________________________________________________________________________________________________________
//
// _tmain -- UNICODE/ANSI main function
//
// Driver routine
//_______________________________________________________________________________________________________________

extern "C" int __cdecl _tmain(int argc, TCHAR* argv[])
{
// Determine handle
g_hStdOut = ::GetStdHandle(STD_OUTPUT_HANDLE);
if (g_hStdOut == INVALID_HANDLE_VALUE)
g_hStdOut = 0;  // non-zero if stdout redirected or piped

// Bool to allow user to specify option to turn OFF InstallSequence and Required Validation
//!! So that databases won't fail if don't have the _InstallValidate and/or _Required tables
BOOL fOff = FALSE;

if (argc != 2 && argc != 3)
{
_tprintf(TEXT("USAGE:\n msival.exe {database}\n msival.exe {database} -OFF"));
return 1;
}

if (argc == 2 && (lstrcmp(argv[1],TEXT("-?")) == 0 || lstrcmp(argv[1],TEXT("/?")) == 0))
{
                _tprintf(TEXT("USAGE:\n msival.exe {database}\n")
                         TEXT("msival.exe {database} -OFF\n")
                         TEXT("NOTE:\n")
                         TEXT(" For validation to proceed. . .\n")
                                 TEXT("\tTables required:\n")
                                 TEXT("\t _Validation (always)\n")
                                 TEXT("\t _InstallValidate (unless -OFF)\n")
                                 TEXT("\t _Required (unless -OFF)\n")
                                 TEXT("\t _Sequence (unless -OFF)\n"));
return 0;
}

if (argc == 3)
{
if (lstrcmp(argv[2],TEXT("-OFF")) == 0 || lstrcmp(argv[2],TEXT("/OFF")) == 0
|| lstrcmp(argv[2],TEXT("-off")) == 0 || lstrcmp(argv[2],TEXT("/off")) == 0)
fOff = TRUE;
else
{
_tprintf(TEXT("USAGE:\n msival.exe {database} -OFF\n"));
return 0;
}
}

BOOL fDataValid = TRUE;
BOOL fColValid  = TRUE;
BOOL fSeqOrgValid  = TRUE;
BOOL fSeqOrderValid = TRUE;
BOOL fReqValid  = TRUE;
try
{
PMSIHANDLE hDatabase;
CheckMsi(MsiOpenDatabase(argv[1],MSIDBOPEN_READONLY,&hDatabase),TEXT("OpenDatabase"));
_tprintf(TEXT("\nINFO: Validating for missing columns. . .\n\n"));
fColValid = CheckMissingColumns(hDatabase);
_tprintf(TEXT("\nINFO: Validating data and foreign keys. . .\n\n"));
fDataValid = Validate(hDatabase);
if (fOff)
{
// Print out warning of database not exactly valid since skipping these validations
_tprintf(TEXT("WARNING!\n Skipping InstallSequence and Required Validation.\n  Database may not be completely valid\n"));
}
else
{
_tprintf(TEXT("\nINFO: Validating Install Sequence Table Organization. . .\n\n"));
fSeqOrgValid = ValidateOrganizationInstallSequence(hDatabase);
_tprintf(TEXT("\nINFO: Validating Sequence of Actions In Install Sequence Table. . .\n\n"));
fSeqOrderValid = ValidateInstallSequence(hDatabase);
_tprintf(TEXT("\nINFO: Validating Required Values. . .\n\n"));
fReqValid = ValidateRequired(hDatabase);
}
if (fDataValid && fColValid && fSeqOrgValid && fReqValid && fSeqOrderValid)
_tprintf(TEXT("Database is valid: %s"), argv[1]);
}
catch (UINT iError)
{
_tprintf(TEXT("\n%s error %i"), g_szErrorContext, iError);
MsiCloseAllHandles();
return 1;
}
catch (...)
{
_tprintf(TEXT("\n%s"), TEXT("Unhandled exception"));
MsiCloseAllHandles();
return 99;
}
int iOpenHandles = MsiCloseAllHandles();  // diagnostic check only
if (iOpenHandles != 0)
_tprintf(TEXT("\n%i Handle(s) not closed"), iOpenHandles);
return (fDataValid && fColValid && fSeqOrgValid && fReqValid && fSeqOrderValid) ? 0 : 1;
}

void CheckMsi(UINT iStat, TCHAR* szContext)
/*----------------------------------------------------------------------------------
CheckMsi -- Routine to check return status for error and throw exception if error.
  Arguments:
iStat -- error status
szContext -- error string
  Returns:
none, but throws error if one
-------------------------------------------------------------------------------------*/
{
if (iStat != ERROR_SUCCESS)
{
g_szErrorContext = szContext;
throw iStat;
}
}

BOOL CheckMissingColumns(MSIHANDLE hDatabase)
/*---------------------------------------------------------------------
CheckMissingColumns -- used _Validation table and _Columns catalog to
 determine if any columns/tables are not listed.  All columns in
 _Validation table must be listed in the _Columns catalog.  If a column
 is optional and not used in the database, then it should not be found
 in the _Validation table or the _Columns catalog.  Normal validation
 catches the instance where a column is defined in the _Columns catalog
 but not in the _Validation table.
---------------------------------------------------------------------*/
{
PMSIHANDLE hValidationView   = 0;
PMSIHANDLE hColCatalogView   = 0;
PMSIHANDLE hValidationRecord = 0;
PMSIHANDLE hColCatalogRecord = 0;
PMSIHANDLE hExecRecord       = 0;

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLColMissing, &hColCatalogView), TEXT("OpenColumnCatalogView"));
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLValidationTable, &hValidationView), TEXT("OpenValidationTableView"));

UINT iRet = 0;
TCHAR szTable[cbName] = {0};
TCHAR szColumn[cbName] = {0};
unsigned long cchTableBuf = sizeof(szTable)/sizeof(TCHAR);
unsigned long cchColumnBuf = sizeof(szColumn)/sizeof(TCHAR);
BOOL fStat = TRUE;

hExecRecord = MsiCreateRecord(2);
CheckMsi(MsiViewExecute(hValidationView, 0), TEXT("ExecuteValidationView"));
for (;;)
{
iRet = MsiViewFetch(hValidationView, &hValidationRecord);
if (iRet == ERROR_NO_MORE_ITEMS || !hValidationRecord)
break;
CheckMsi(iRet, TEXT("ColumnCatalogFetch"));
CheckMsi(MsiRecordGetString(hValidationRecord, 1, szTable, &cchTableBuf), TEXT("GetTableName"));
cchTableBuf = sizeof(szTable)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hValidationRecord, 2, szColumn, &cchColumnBuf), TEXT("GetColumnName"));
cchColumnBuf = sizeof(szColumn)/sizeof(TCHAR);
CheckMsi(MsiRecordSetString(hExecRecord, 1, szTable), TEXT("SetTableName"));
CheckMsi(MsiRecordSetString(hExecRecord, 2, szColumn), TEXT("SetColumnName"));
CheckMsi(MsiViewExecute(hColCatalogView, hExecRecord), TEXT("ExecuteColumnCatalogView"));
iRet = MsiViewFetch(hColCatalogView, &hColCatalogRecord);
if (iRet == ERROR_NO_MORE_ITEMS || !hColCatalogRecord)
{
// Error --> Missing from database
TCHAR szMsgBuf[150];
const TCHAR* szMessage = (TCHAR*)IDS_MissingEntry;
const TCHAR** pszMsg;
pszMsg = &szMessage;
::LoadString(0, *(unsigned*)pszMsg, szMsgBuf, sizeof(szMsgBuf)/sizeof(TCHAR));
*pszMsg = szMsgBuf;
_tprintf(TEXT("Table.Column: %s.%s Message: %s\n"), szTable, szColumn, szMsgBuf);
fStat = FALSE;
}
CheckMsi(MsiViewClose(hColCatalogView), TEXT("CloseView"));
}
MsiViewClose(hValidationView);

return fStat;
}

BOOL InDialogTable(MSIHANDLE hDatabase, LPCTSTR szAction)
{
PMSIHANDLE hviewDialogTable = 0;
PMSIHANDLE hrecExecute      = 0;
PMSIHANDLE hrecFetch        = 0;

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLDialogTable, &hviewDialogTable), TEXT("DialogTableOpenView"));
hrecExecute = MsiCreateRecord(1);
CheckMsi(hrecExecute == NULL, TEXT("CreateRecord"));
CheckMsi(MsiRecordSetString(hrecExecute, 1, szAction), TEXT("RecordSetString"));
CheckMsi(MsiViewExecute(hviewDialogTable, hrecExecute), TEXT("DialogTableViewExecute"));
UINT iStat = MsiViewFetch(hviewDialogTable, &hrecFetch);
CheckMsi(MsiViewClose(hviewDialogTable), TEXT("CloseDialogTableView"));
if (iStat == ERROR_SUCCESS && hrecFetch != 0)
return TRUE;
return FALSE;
}

BOOL InCustomActionTable(MSIHANDLE hDatabase, LPCTSTR szAction)
{
PMSIHANDLE hviewCustomActionTable = 0;
PMSIHANDLE hrecExecute            = 0;
PMSIHANDLE hrecFetch              = 0;

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLCustomActionTable, &hviewCustomActionTable), TEXT("CustomActionTableOpenView"));
hrecExecute = MsiCreateRecord(1);
CheckMsi(hrecExecute == NULL, TEXT("CreateRecord"));
CheckMsi(MsiRecordSetString(hrecExecute, 1, szAction), TEXT("RecordSetString"));
CheckMsi(MsiViewExecute(hviewCustomActionTable, hrecExecute), TEXT("CustomActionTableViewExecute"));
UINT iStat = MsiViewFetch(hviewCustomActionTable, &hrecFetch);
CheckMsi(MsiViewClose(hviewCustomActionTable), TEXT("CloseCustomActionTableView"));
if (iStat == ERROR_SUCCESS && hrecFetch != 0)
return TRUE;
return FALSE;
}

BOOL ValidateOrganizationInstallSequence(MSIHANDLE hDatabase)
/*---------------------------------------------------------------------------------------
ValidateOrganizationInstallSequence -- Routine to validate InstallSequence table of database.  Uses
the _InstallValidate table as the basis for the validation.
-----------------------------------------------------------------------------------------*/
{
PMSIHANDLE hviewInstallTable    = 0;
PMSIHANDLE hviewValInstallTable = 0;
PMSIHANDLE hrecValExecute       = 0;
PMSIHANDLE hrecInstallFetch     = 0;
PMSIHANDLE hrecValFetch         = 0;

int isd = isdSearch; // initialize section definition to Search

BOOL fValid = TRUE; // validation status
BOOL fRequireExecute = FALSE; // if script operations, must call ExecuteFinalize

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLInstallSeqTable, &hviewInstallTable), TEXT("OpenView on InstallSequence table"));
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLInstallValidate, &hviewValInstallTable), TEXT("OpenView on _InstallValidate table"));
CheckMsi(MsiViewExecute(hviewInstallTable, 0), TEXT("ExecuteView InstallSequence view"));

hrecValExecute = MsiCreateRecord(1);
CheckMsi(hrecValExecute == NULL, TEXT("CreateRecord"));
UINT iRet = ERROR_SUCCESS;
while ((iRet = MsiViewFetch(hviewInstallTable, &hrecInstallFetch)) != ERROR_NO_MORE_ITEMS)
{
CheckMsi(iRet, TEXT("InstallSequenceTableFetch"));
if (!hrecInstallFetch)
break;
TCHAR szAction[255] = {0};
DWORD cbAction = sizeof(szAction)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hrecInstallFetch, 1, szAction, &cbAction), TEXT("InstallFetchRecGetString Error"));
CheckMsi(MsiRecordSetString(hrecValExecute, 1, szAction), TEXT("ValExecuteRecSetString Error"));
CheckMsi(MsiViewExecute(hviewValInstallTable, hrecValExecute), TEXT("Execute _InstallValidate view"));

// Determine if have to switch the current section state
// Depends on a certain action defined as the boundary
// This action is required in the InstallSequence table -- CostInitialize, CostFinalize, InstallValidate
// ExecuteFinalize can cause a reset so you execute one script and then continue again
// ExecuteFinalize must be called if any execution operations exist
if (lstrcmp(szAction, szEndSearch) == 0)
isd = isdCosting;
else if (lstrcmp(szAction, szEndCosting) == 0)
isd = isdSelection;
else if (lstrcmp(szAction, szEndSelection) == 0)
isd = isdAdvertise;
else if (lstrcmp(szAction, szEndAdvertise) == 0)
isd = isdExecution;
else if (lstrcmp(szAction, szReset) == 0)
{
if (fRequireExecute)
fRequireExecute = FALSE;
else
{
TCHAR szError[cchBuffer] = {0};
wsprintf(szError, TEXT("ERROR: Action: '%s' Can Only Be Called When Script Operations Exist To Be Executed\n"), szAction);
Display(szError);
}
isd = isdSearch; // reset to search
}

// Validate action, using the _InstallValidate table to obtain the section mask to which
// the action belongs and then comparing with the current section state
if ((iRet = MsiViewFetch(hviewValInstallTable, &hrecValFetch)) == ERROR_NO_MORE_ITEMS)
{
// Action not found in _InstallValidate table
// Check to see if CustomAction (located in CustomAction table) or Dialog (located in Dialog table)
if (!InDialogTable(hDatabase, szAction) && !InCustomActionTable(hDatabase, szAction))
{
TCHAR szError[cchBuffer] = {0};
wsprintf(szError, TEXT("ERROR: Action: '%s' -- Not in _InstallValidate, CustomAction or Dialog tables\n"), szAction);
Display(szError);
fValid = FALSE;
}
}
else
{
// Action found in _InstallValidate table
// Obtain the SectionFlag
// Compare the SectionFlag to the current flag and determine if match
// If not match, then ERROR --> incorrect section (print action, current section, correct section)
CheckMsi(iRet, TEXT("_InstallValidateFetch Error"));
int iSection = MsiRecordGetInteger(hrecValFetch, 2);
CheckMsi(iSection == MSI_NULL_INTEGER, TEXT("ValFetchRecGetInteger"));
if (iSection & isd)
{
// Action located in correct section, found
if (iSection == isdExecution || iSection == isdAdvertise)
fRequireExecute = TRUE;
}
else
{
// Action not in correct section
// Print out error --> action, current section, correct section
TCHAR szError[cchBuffer] = {0};
TCHAR szCurrentSect[cchBuffer] = {0};
TCHAR szCorrectSect[cchBuffer] = {0};
switch (isd)
{
case isdSearch:    lstrcpy(szCurrentSect, szSearch);    break;
case isdCosting:   lstrcpy(szCurrentSect, szCosting);   break;
case isdSelection: lstrcpy(szCurrentSect, szSelection); break;
case isdAdvertise: lstrcpy(szCurrentSect, szAdvertise); break;
case isdExecution: lstrcpy(szCurrentSect, szExecution); break;
}
switch (iSection)
{
case isdSearch:    lstrcpy(szCorrectSect, szSearch);    break;
case isdCosting:   lstrcpy(szCorrectSect, szCosting);   break;
case isdSelection: lstrcpy(szCorrectSect, szSelection); break;
case isdAdvertise: lstrcpy(szCorrectSect, szAdvertise); break;
case isdExecution: lstrcpy(szCorrectSect, szExecution); break;
}
wsprintf(szError, TEXT("ERROR: Action: '%s' CurrentSection: '%s' CorrectSection: '%s'\n"), szAction, szCurrentSect, szCorrectSect);
Display(szError);
fValid = FALSE;
}
}
CheckMsi(MsiViewClose(hviewValInstallTable), TEXT("ValInstallTableViewClose"));
}
if (fRequireExecute)
{
TCHAR szError[cchBuffer] = {0};
wsprintf(szError, TEXT("ERROR: ExecuteFinalize must be called as there are script operations\n"));
Display(szError);
fValid = FALSE;
}
return fValid;
}

BOOL ValidateRequired(MSIHANDLE hDatabase)
/*-----------------------------------------------------------------------------------
ValidateRequired -- Uses the _Required table and checks the tables listed for the
'required' values that are listed in the table.

-------------------------------------------------------------------------------------*/
{
PMSIHANDLE hviewRequiredTable = 0;
PMSIHANDLE hviewTable         = 0;
PMSIHANDLE hrecTableExecute   = 0;
PMSIHANDLE hrecRequiredFetch  = 0;
PMSIHANDLE hrecTableFetch     = 0;
PMSIHANDLE hrecColInfo        = 0;

BOOL fValid = TRUE;
BOOL fFirstRun = TRUE;
UINT iStat = ERROR_SUCCESS;

TCHAR szPrevTable[100] = {0};
TCHAR szTable[100] = {0};
TCHAR szValue[256] = {0};
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLRequiredTable, &hviewRequiredTable), TEXT("OpenViewRequiredTable"));
CheckMsi(MsiViewExecute(hviewRequiredTable, 0), TEXT("RequiredTableViewExecute"));
while ((iStat = MsiViewFetch(hviewRequiredTable, &hrecRequiredFetch)) != ERROR_NO_MORE_ITEMS)
{
CheckMsi(iStat, TEXT("RequiredTableViewFetch"));
if (!hrecRequiredFetch)
break;
int cPrimaryKeys = MsiRecordGetInteger(hrecRequiredFetch, 3);
DWORD cbTable = sizeof(szTable)/sizeof(TCHAR);
DWORD cbValue = sizeof(szValue)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hrecRequiredFetch, 1, szTable, &cbTable), TEXT("RequiredTableRecordGetString"));
CheckMsi(MsiRecordGetString(hrecRequiredFetch, 2, szValue, &cbValue), TEXT("RequiredTableRecordGetString"));
if (fFirstRun)
fFirstRun = FALSE;
else
CheckMsi(MsiViewClose(hviewTable), TEXT("TableViewClose"));
hrecTableExecute = MsiCreateRecord(cPrimaryKeys);
if (hrecTableExecute == 0)
return FALSE;

if (lstrcmp(szPrevTable, szTable) != 0)
{
// New table, need to open a new view.
TCHAR szSQL[1024] = {0};
PMSIHANDLE hrecPrimaryKeys = 0;
TCHAR szKeyColName[50] = {0};
DWORD cbKey = sizeof(szKeyColName)/sizeof(TCHAR);
CheckMsi(MsiDatabaseGetPrimaryKeys(hDatabase, szTable, &hrecPrimaryKeys), TEXT("DatabaseGetPrimaryKeys"));
CheckMsi(MsiRecordGetString(hrecPrimaryKeys, 1, szKeyColName, &cbKey), TEXT("PrimaryKeysRecordGetString"));
CheckMsi(MsiRecordGetFieldCount(hrecPrimaryKeys) != cPrimaryKeys, TEXT("PrimaryKeyCountWrong"));
CheckMsi(cPrimaryKeys == ERROR_INVALID_HANDLE, TEXT("PrimaryKeysRecordGetFieldCount"));

// Develop query of table to be checked
int cchWritten = wsprintf(szSQL, TEXT("SELECT * FROM `%s` WHERE `%s`=?"), szTable, szKeyColName);
int cchAddition = cchWritten;
for (int i = 2; i <= cPrimaryKeys; i++)
{
cbKey = sizeof(szKeyColName)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hrecPrimaryKeys, i, szKeyColName, &cbKey), TEXT("PrimaryKeysRecordGetString"));
cchWritten = wsprintf(szSQL + cchAddition, TEXT(" AND `%s`=?"), szKeyColName);
cchAddition = cchWritten;
}
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQL, &hviewTable), TEXT("DatabaseOpenView"));
CheckMsi(MsiViewGetColumnInfo(hviewTable, MSICOLINFO_TYPES, &hrecColInfo), TEXT("GetColumnInfo"));
lstrcpy(szPrevTable, szTable);
}

// Fill in execute record with the key data values
TCHAR* pch = szValue;
TCHAR szKeyValue[256] = {0};
TCHAR szType[32] = {0};
DWORD cbType = sizeof(szType)/sizeof(TCHAR);
int nDex = 0;
for (int j = 1; j <= cPrimaryKeys; j++)
{
while (pch != 0 && *pch != TEXT(';') &&  *pch != 0)
szKeyValue[nDex++] = *pch++;
szKeyValue[nDex] = 0;
pch++; // for ; or 0
cbType = sizeof(szType)/sizeof(TCHAR);
CheckMsi(MsiRecordGetString(hrecColInfo, j, szType, &cbType), TEXT("ColInfoGetString"));
if (szType != 0 && *szType == TEXT('s'))
CheckMsi(MsiRecordSetString(hrecTableExecute, j, szKeyValue), TEXT("TableExecuteRecordSetString"));
else // integer primary key
CheckMsi(MsiRecordSetInteger(hrecTableExecute, j, _ttoi(szKeyValue)), TEXT("TableExecuteRecordSetInteger"));
nDex = 0;
}

// Execute view and attempt to fetch listed item from table
CheckMsi(MsiViewExecute(hviewTable, hrecTableExecute), TEXT("TableViewExecute"));
iStat = MsiViewFetch(hviewTable, &hrecTableFetch);
if (iStat == ERROR_NO_MORE_ITEMS)
{
// Value not found
TCHAR szError[cchBuffer] = {0};
wsprintf(szError, TEXT("ERROR: Value: '%s' Is Required In Table: '%s'\n"), szValue, szTable);
Display(szError);
fValid = FALSE;
}
else if (iStat != ERROR_SUCCESS)
CheckMsi(iStat, TEXT("TableViewFetch"));
}

return fValid;

}

BOOL ValidateInstallSequence(MSIHANDLE hDatabase)
/*----------------------------------------------------------------------------
ValidateInstallSequence -- validates the order of the actions in the
InstallSequence table to ensure that they are allowed by the _Sequence table.
The _Sequence table is required for this validation.
------------------------------------------------------------------------------*/
{
BOOL fValid = TRUE;

PMSIHANDLE hviewInstallTable    = 0;
PMSIHANDLE hviewSeqQueryNull    = 0;
PMSIHANDLE hviewSeqQueryNotNull = 0;
PMSIHANDLE hviewSeqUpdate       = 0;
PMSIHANDLE hviewSeqAddColumn    = 0;
PMSIHANDLE hviewSeqMarkerInit   = 0;
PMSIHANDLE hrecSeqUpdateExecute = 0;
PMSIHANDLE hrecQueryExecute     = 0;
PMSIHANDLE hrecInstallFetch     = 0;
PMSIHANDLE hrecQueryNullFetch   = 0;
PMSIHANDLE hrecQueryNotNullFetch= 0;

// Create the temporary marking column for the _Sequence table (this will store the sequence #s of the Dependent Actions)
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLSeqTableAddCol, &hviewSeqAddColumn), TEXT("_SequenceTableAddColOpenView"));
CheckMsi(MsiViewExecute(hviewSeqAddColumn, 0), TEXT("_SequenceTableAddColExecute"));
CheckMsi(MsiViewClose(hviewSeqAddColumn), TEXT("_SequenceTableAddColClose"));

// Initialize the temporary marking column to zero
//!! NO INSTALL SEQUENCE ACTIONS CAN HAVE A ZERO SEQUENCE # AS ZERO IS CONSIDERED "NULL"
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLSeqMarkerInit, &hviewSeqMarkerInit), TEXT("_SequenceTableMarkerInitOpenView"));
CheckMsi(MsiViewExecute(hviewSeqMarkerInit, 0), TEXT("_SequenceTableMarkerInitExecute"));
CheckMsi(MsiViewClose(hviewSeqMarkerInit), TEXT("_SequenceTableMarkerInitClose"));

// Open view on InstallSequence table and order by the Sequence #
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLInstallSeqTable, &hviewInstallTable), TEXT("InstallSequenceTableOpenView"));
CheckMsi(MsiViewExecute(hviewInstallTable, 0), TEXT("InstallSequenceTableExecute"));

// Open the two query views on _Sequence table for determining the validity of the actions
// Create execution record
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLSeqTableQueryNull, &hviewSeqQueryNull), TEXT("SequenceTableQueryNullOpenView"));
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLSeqTableQueryNotNull, &hviewSeqQueryNotNull), TEXT("_SequenceTableQueryNotNullOpenView"));
hrecQueryExecute = MsiCreateRecord(1); // for action
CheckMsi(hrecQueryExecute == 0, TEXT("QueryExecuteCreateRecord"));
hrecSeqUpdateExecute = MsiCreateRecord(1); // for action
CheckMsi(hrecSeqUpdateExecute == 0, TEXT("UpdateExecuteCreateRecord"));

// Start fetching actions from the InstallSequence table
UINT iStat1 = ERROR_SUCCESS;
UINT iStat2 = ERROR_SUCCESS;
TCHAR szSQLUpdateQuery[4096] = {0};
TCHAR szAction[100] = {0};
int iSequence = 0;
for (;;)
{
iStat1 = MsiViewFetch(hviewInstallTable, &hrecInstallFetch);
if (iStat1 == ERROR_NO_MORE_ITEMS || !hrecInstallFetch)
break;
CheckMsi(iStat1, TEXT("InstallTableFetch"));
DWORD cbSize = sizeof(szAction)/sizeof(TCHAR);

// Obtain name of action and Sequence # of action in InstallSequence table
CheckMsi(MsiRecordGetString(hrecInstallFetch, 1, szAction, &cbSize), TEXT("InstallFetchRecordGetString"));
iSequence = MsiRecordGetInteger(hrecInstallFetch, 2);
CheckMsi(iSequence == MSI_NULL_INTEGER, TEXT("InstallFetchRecordGetInteger"));

// Prepare execution records
CheckMsi(MsiRecordSetString(hrecQueryExecute, 1, szAction), TEXT("_SequenceQueryExecuteRecordSetString"));
CheckMsi(MsiRecordSetString(hrecSeqUpdateExecute, 1, szAction), TEXT("_SequenceUpdateExecuteRecordSetString"));

// Execute _Sequence query table views
CheckMsi(MsiViewExecute(hviewSeqQueryNull, hrecQueryExecute), TEXT("_SequenceQueryNullExecute"));
CheckMsi(MsiViewExecute(hviewSeqQueryNotNull, hrecQueryExecute), TEXT("_SequenceQueryNotNullExecute"));

// Fetch from _Sequence table.  If resultant set, then ERROR
// Following are the possibilities and whether permitted:
//   Action After Dependent Where Dependent Is Required And Temp Sequence Column Is Zero --> ERROR
//   Action After Dependent Where Dependent Is Required And Temp Sequence Column Is Greater Than Zero --> CORRECT
//   Action After Dependent Where Dependent Is Optional And Temp Sequence Column Is Zero --> CORRECT
//   Action After Dependent Where Dependent Is Optional And Temp Sequence Column Is Greater Than Zero --> CORRECT
//   Action Before Dependent Where Dependent Is Optional Or Required And Temp Sequence Column Is Zero --> CORRECT
//   Action Before Dependent Where Dependent Is Optional Or Requred And Temp Sequence Column Is Greater Than Zero --> ERROR

// ** Only issue is when Action Is After Optional Dependent And Temp Sequence Column Is Zero because we
// ** have no way of knowing whether the action will be later (in which case it would be invalid.  This is
// ** ensured to be successful though by proper authoring of the _Sequence table.  If an Action comes after
// ** the Optional Dependent Action, then the _Sequence table must also be authored with the Dependent Action
// ** listed as coming before that Action (so if we come later, and find a result set, we flag this case).

// If return is not equal to ERROR_NO_MORE_ITEMS, then ERROR and Output Action
//!! Any more info to output ??
iStat1 = MsiViewFetch(hviewSeqQueryNull, &hrecQueryNullFetch);
iStat2 = MsiViewFetch(hviewSeqQueryNotNull, &hrecQueryNotNullFetch);

if (iStat1 != ERROR_NO_MORE_ITEMS || iStat2 != ERROR_NO_MORE_ITEMS)
{
TCHAR szError[1024] = {0};
TCHAR szDependent[100] = {0};
DWORD cb = sizeof(szDependent)/sizeof(TCHAR);
if (iStat1 != ERROR_NO_MORE_ITEMS)
CheckMsi(MsiRecordGetString(hrecQueryNullFetch, 1, szDependent, &cb), TEXT("MsiRecordGetString"));

else
CheckMsi(MsiRecordGetString(hrecQueryNotNullFetch, 1, szDependent, &cb), TEXT("MsiRecordGetString"));
wsprintf(szError, TEXT("ERROR: %s Action Is Sequenced Incorrectly (Dependent=%s)\n"), szAction, szDependent);
Display(szError);
fValid = FALSE;
}

// Update _Sequence table temporary Sequence column (that we created) with the install sequence number
// The Sequence column stores the sequence number of the Dependent Actions, so we are updating every
// row where the action in the Dependent column equals the current action.  In the query view, we only
// check to insure that this column is zero or greater than zero (so we don't care too much about the value)
// Build the query: UPDATE `_Sequence` SET `Marker`=iSequence WHERE `Dependent`=szAction
wsprintf(szSQLUpdateQuery, TEXT("UPDATE `_Sequence` SET `Marker`=%d WHERE `Dependent`=?"), iSequence);
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLUpdateQuery, &hviewSeqUpdate), TEXT("_SequenceTableUpdateOpenView"));
CheckMsi(MsiViewExecute(hviewSeqUpdate, hrecSeqUpdateExecute), TEXT("_SequenceUpdateExectue"));

// Close the _Sequence table views so we can re-execute
CheckMsi(MsiViewClose(hviewSeqUpdate), TEXT("_SequenceUpdateViewClose"));
CheckMsi(MsiViewClose(hviewSeqQueryNull), TEXT("_SequenceQueryNullViewClose"));
CheckMsi(MsiViewClose(hviewSeqQueryNotNull), TEXT("_SequenceQueryNotNullViewClose"));
}

// Close the InstallSequence table view
CheckMsi(MsiViewClose(hviewInstallTable), TEXT("InstallSequenceTableViewClose"));

return fValid;
}

BOOL Validate(MSIHANDLE hDatabase)
/*-----------------------------------------------------------------------------------
Validate -- Routine to validate database.  Prints out invalid data if any.
  Arguments:
hDatabase -- handle to database
iValid -- integer for storing whether database is valid
  Returns:
BOOL status -- TRUE (all valid), FALSE (invalid data found)
-------------------------------------------------------------------------------------*/
{
// _Tables (Table Catalog)
PMSIHANDLE hTableCatalogView;
PMSIHANDLE hTableCatalogRecord;
// Table To Validate
PMSIHANDLE hValidationView;
PMSIHANDLE hValidationRecord;
// Record for Primary Keys
PMSIHANDLE hKeyRecord;

CheckMsi(MsiDatabaseOpenView(hDatabase, szSQLTableCatalog, &hTableCatalogView),TEXT("OpenTableCatalogView"));
CheckMsi(MsiViewExecute(hTableCatalogView, 0), TEXT("Execute Table Catalog View"));
TCHAR szSQL[256];
TCHAR szTableName[32];
TCHAR szColumnData[255];
TCHAR szColumnName[32];
DWORD cchTableName = sizeof(szTableName)/sizeof(TCHAR);
DWORD cchColumnName = sizeof(szColumnName)/sizeof(TCHAR);
DWORD cchColumnData = sizeof(szColumnData)/sizeof(TCHAR);

BOOL fDataValid = TRUE; // initially valid
DWORD cchTableBuf = cchTableName;
DWORD cchDataBuf = cchColumnData;
DWORD cchBuf = cchColumnName;
UINT uiRet = 0;
for (;;)
{

uiRet = MsiViewFetch(hTableCatalogView, &hTableCatalogRecord);
if (uiRet == ERROR_NO_MORE_ITEMS)
break;
CheckMsi(uiRet, TEXT("Fetch Table Catalog Record"));
if (!hTableCatalogRecord)
break;
cchTableBuf = cchTableName; // on return size of string written
CheckMsi(MsiRecordGetString(hTableCatalogRecord, 1, szTableName, &cchTableBuf), TEXT("Get Table Name From Fetched Record"));
MSICONDITION ice = MsiDatabaseIsTablePersistent(hDatabase, szTableName);
if (ice == MSICONDITION_FALSE)
continue;
CheckMsi(ice != MSICONDITION_TRUE, TEXT("IsTablePersistent"));
wsprintf(szSQL, TEXT("%s`%s`"), szSQLTable, szTableName);
CheckMsi(MsiDatabaseOpenView(hDatabase, szSQL, &hValidationView),TEXT("OpenView"));
CheckMsi(MsiViewExecute(hValidationView, 0), TEXT("Execute View"));
for (;;)
{
uiRet = MsiViewFetch(hValidationView, &hValidationRecord);
if (uiRet == ERROR_NO_MORE_ITEMS)
break;
CheckMsi(uiRet, TEXT("Fetch record"));
if (!hValidationRecord)
break;
if (MsiViewModify(hValidationView, MSIMODIFY_VALIDATE, hValidationRecord) != ERROR_SUCCESS)
{
fDataValid = FALSE;
cchTableBuf = cchTableName;
cchDataBuf = cchColumnData;
cchBuf = cchColumnName;

MSIDBERROR eReturn;
while ((eReturn = MsiViewGetError(hValidationView, szColumnName, &cchBuf)) != MSIDBERROR_NOERROR)
{
if (eReturn == MSIDBERROR_FUNCTIONERROR || eReturn == MSIDBERROR_MOREDATA || eReturn == MSIDBERROR_INVALIDARG)
{
_tprintf(TEXT("\nFunction Error"));
break;
}

int iResId;
int iValue;
switch (eReturn)
{
case MSIDBERROR_NOERROR:           iResId = IDS_NoError;          break;
case MSIDBERROR_DUPLICATEKEY:      iResId = IDS_DuplicateKey;     break;
case MSIDBERROR_REQUIRED:          iResId = IDS_Required;         break;
case MSIDBERROR_BADLINK:           iResId = IDS_BadLink;          break;
case MSIDBERROR_OVERFLOW:          iResId = IDS_Overflow;         break;
case MSIDBERROR_UNDERFLOW:         iResId = IDS_Underflow;        break;
case MSIDBERROR_NOTINSET:          iResId = IDS_NotInSet;         break;
case MSIDBERROR_BADVERSION:        iResId = IDS_BadVersion;       break;
case MSIDBERROR_BADCASE:           iResId = IDS_BadCase;          break;
case MSIDBERROR_BADGUID:           iResId = IDS_BadGuid;          break;
case MSIDBERROR_BADWILDCARD:       iResId = IDS_BadWildCard;      break;
case MSIDBERROR_BADIDENTIFIER:     iResId = IDS_BadIdentifier;    break;
case MSIDBERROR_BADLANGUAGE:       iResId = IDS_BadLanguage;      break;
case MSIDBERROR_BADFILENAME:       iResId = IDS_BadFileName;      break;
case MSIDBERROR_BADPATH:           iResId = IDS_BadPath;          break;
case MSIDBERROR_BADCONDITION:      iResId = IDS_BadCondition;     break;
case MSIDBERROR_BADFORMATTED:      iResId = IDS_BadFormatted;     break;
case MSIDBERROR_BADTEMPLATE:       iResId = IDS_BadTemplate;      break;
case MSIDBERROR_BADDEFAULTDIR:     iResId = IDS_BadDefaultDir;    break;
case MSIDBERROR_BADREGPATH:        iResId = IDS_BadRegPath;       break;
case MSIDBERROR_BADCUSTOMSOURCE:   iResId = IDS_BadCustomSource;  break;
case MSIDBERROR_BADPROPERTY:       iResId = IDS_BadProperty;      break;
case MSIDBERROR_MISSINGDATA:       iResId = IDS_MissingData;      break;
case MSIDBERROR_BADCATEGORY:       iResId = IDS_BadCategory;      break;
case MSIDBERROR_BADKEYTABLE:       iResId = IDS_BadKeyTable;      break;
case MSIDBERROR_BADMAXMINVALUES:   iResId = IDS_BadMaxMinValues;  break;
case MSIDBERROR_BADCABINET:        iResId = IDS_BadCabinet;       break;
case MSIDBERROR_BADSHORTCUT:       iResId = IDS_BadShortcut;      break;
case MSIDBERROR_STRINGOVERFLOW:    iResId = IDS_StringOverflow;   break;
case MSIDBERROR_BADLOCALIZEATTRIB: iResId = IDS_BadLocalizeAttrib;break;
default:                           iResId = IDS_UndefinedError;   break;
};

// Print table
_tprintf(TEXT("\n Error: %s\t"), szTableName);

// Get Row
CheckMsi(MsiDatabaseGetPrimaryKeys(hDatabase, szTableName, &hKeyRecord), TEXT("Get Primary Keys"));
unsigned int iNumFields = MsiRecordGetFieldCount(hKeyRecord);
if (MsiRecordGetString(hValidationRecord, 1, szColumnData, &cchDataBuf) != ERROR_SUCCESS)
{
iValue = MsiRecordGetInteger(hValidationRecord, 1);
_tprintf(TEXT("%d"), iValue);
}
else
_tprintf(TEXT("%s"), szColumnData);
cchDataBuf = cchColumnData;
for (int i = 2; i <= iNumFields; i++)
{
_tprintf(TEXT("."));
cchDataBuf = cchColumnData;
if (MsiRecordGetString(hValidationRecord, i, szColumnData, &cchDataBuf) != ERROR_SUCCESS)
{
iValue = MsiRecordGetInteger(hValidationRecord, 1);
_tprintf(TEXT("%d"), iValue);
}
else
_tprintf(TEXT("%s"), szColumnData);
}
// Print name of column and enum value
TCHAR szMsgBuf[80];
const TCHAR* szMessage = (TCHAR*)iResId;
const TCHAR** pszMsg;
pszMsg = &szMessage;
::LoadString(0, *(unsigned*)pszMsg, szMsgBuf, sizeof(szMsgBuf)/sizeof(TCHAR));
*pszMsg = szMsgBuf;
_tprintf(TEXT("\t%s\t%s"), szColumnName, szMsgBuf);
cchBuf = cchColumnName; // on return size of string written
cchDataBuf = cchColumnData;
}
cchBuf = cchColumnName; // on return size of string written
}
}
CheckMsi(MsiViewClose(hValidationView), TEXT("Close view"));
}
CheckMsi(MsiViewClose(hTableCatalogView), TEXT("Close Table Catalog View"));
return fDataValid;
}

void Display(LPCTSTR szMessage)
{
if (szMessage)
{
int cbOut = _tcsclen(szMessage);;
if (g_hStdOut)
{
#ifdef UNICODE
char rgchTemp[cchDisplayBuf];
if (GetFileType(g_hStdOut) == FILE_TYPE_CHAR)
{
WideCharToMultiByte(CP_ACP, 0, szMessage, cbOut, rgchTemp, sizeof(rgchTemp), 0, 0);
szMessage = (LPCWSTR)rgchTemp;
}
else
cbOut *= 2;   // write Unicode if not console device
#endif
DWORD cbWritten;
WriteFile(g_hStdOut, szMessage, cbOut, &cbWritten, 0);
}
else
MessageBox(0, szMessage, GetCommandLine(), MB_OK);
}
}

#else // RC_INVOKED, end of source code, start of resources
// resource definition go here

STRINGTABLE DISCARDABLE
{
IDS_NoError,          "No Error"
IDS_DuplicateKey,     "Duplicate primary key"
IDS_Required,         "Not a nullable column"
IDS_BadLink,          "Not a valid foreign key"
IDS_Overflow,         "Value exceeds MaxValue"
IDS_Underflow,        "Value below MinValue"
IDS_NotInSet,         "Value not a member of the set"
IDS_BadVersion,       "Invalid version string"
IDS_BadCase,          "Must be all upper or all lower case"
IDS_BadGuid,          "Invalid GUID string"
IDS_BadWildCard,      "Invalid wildcard filename or usage of wildcards"
IDS_BadIdentifier,    "Invalid identifier"
IDS_BadLanguage,      "Invalid Language Id"
IDS_BadFileName,      "Invalid Filename"
IDS_BadPath,          "Invalid full path"
IDS_BadCondition,     "Bad conditional string"
IDS_BadFormatted,     "Invalid format string"
IDS_BadTemplate,      "Invalid template string"
IDS_BadDefaultDir,    "Invalid DefaultDir string"
IDS_BadRegPath,       "Invalid registry path"
IDS_BadCustomSource,  "Bad CustomSource data"
IDS_BadProperty,      "Bad property"
IDS_MissingData,      "Missing data in _Validation table or old Database"
IDS_BadCabinet,       "Bad cabinet syntax/name"
IDS_BadCategory,      "_Validation table: Invalid category string"
IDS_BadKeyTable,      "_Validation table: Data in KeyTable col is bad"
IDS_BadMaxMinValues,  "_Validation table: value in MaxValue col < that in MinValue col"
IDS_BadShortcut,      "Bad shortcut target"
IDS_StringOverflow,   "String overflow:  Length greater than that allowed by column definition"
IDS_MissingEntry,     "Column is required by _Validation table"
IDS_UndefinedError,   "Undefined Error"
IDS_BadLocalizeAttrib,"Primary Key columns cannot be set to be localized"
}

#endif // RC_INVOKED
#if 0
!endif // makefile terminator
#endif


README.TXT

Readme.txt for Msispy

Overview
--------
Msispy is a tool that provides a component level view of the products
and features installed on a system, using either an MSI Package, or
the registry information. Msispy also displays the status of each,
and allows you to partially or completely re-install missing or
damaged items. You can also re-configure any product or feature,
selecting the install level and specifying whether to install it
locally or run it from a CD or network server.

Msispy allows you to save the system configuration, and restore it
at a later date.

Installing Msispy
-----------------
Msispy can be installed from any of the three Msispy installation
packages available:
I386\Msispy.msi:   Intel (SHIP),
DEBUG\MsispyD.msi: Intel (DEBUG) and
ALPHA\Msispy.msi:  ALPHA (SHIP)

Msispy can be installed
(a) on your local machine,
(b) to run from CD or a network server,
(c) as a combination of both
(d) advertised (install-on-demand), on NT 5 only

The typical install installs the most frequently used components
of Msispy on your local machine, and leaves the rest to be run
from the CD when needed. This takes up less disk space, but you
will need the source CD everytime you use any of the features that
have been configured to run from the CD.

The complete install copies all the files over to your local
hard-drive. This will take up more disk space, but you will not
need the CD again (unless Msispy files are deleted).

The custom install option allows you to configure which features
of Msispy you would like to install locally, and where on the
local drive these files should reside. It also gives you the
diskspace that you will need for your selection.

Resiliency
----------
If any of the Msispy files are deleted from the local drive,
they are copied back on from the CD when they are needed. Feel
free to delete all of Msispy's files and launch Msispy- It will
automatically copy over ("fault in") the required components as
and when they are needed. (Keep the source CD handy!)

If you are using Windows NT 4.0, or Windows 95 (or earlier
versions), the file Msispy.EXE will not be faulted in when you
click on the shortcut. (You will need to run the installer again
to fix Msispy if you delete Msispy.exe).

On MSI descriptor enabled systems (NT 5.0, Windows 98, with
IE 4.0) even if you delete Msispy.exe, clicking on the shortcut
will automatically fault in the required components, (including
Msispy.exe)

Help
----
Once you have installed Msispy, help is available under the Help
Menu (select "Overview"). You can run the hlp file directly
before installation from the CD by clicking on spyENU.hlp (for the
English (US) version) or spyDEU.hlp (for the German (Std) version)

Source
------
Many of the MSI initialisation functions performed by Msispy are
in the source file InitMSI.cpp, available under the "samples"
directory (..\samples\InitMSI.cpp). This file serves as an example
of an app using the functions.

Language
--------
For the Japanese DLLs, the font MS UI Gothic is used, which will only
show up correctly on NT5 J or Windows98, unless the font is installed.

Compatibility
-------------
Msispy is compatible with Windows NT 4.0. At this time, Msispy is NOT
fully compatible with the Windows-95 environment.

THIS TOOL AND INFORMATION ARE 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.


SEQUENCE.BAS

' Sample script for sequencing an installation from outside MSI - test code only, hardwired paths
' Requires AutoApi.DLL to be self-registered (use REGSVR32 AutoApi.DLL)
Sub SingleStep()
   Dim Engine As Object, ActionView As Object
   Dim Sequence As Integer, Action As String, Condition As String
   Dim Test As Integer, Result As Integer
   Dim ExitAction As String, ErrorAction As String, CancelAction As String
   Set MsiAuto = CreateObject("Msi.ApiAutomation")
   MsiAuto.UILevel = 5 'Full UI (no enums yet)
   MsiAuto.EnableLog "warped", "C:\temp\step.log"
   Set Engine = MsiAuto.OpenPackage("C:\msi\testdb\testdb.msi")
'  Engine.Property("SOURCEDIR") = "C:\msi\testdb"  ' needed if source location different than database
   Set Database = Engine.Database
   Set ActionView = Database.OpenView("SELECT Action,Condition,Sequence FROM InstallSequence ORDER BY Sequence")
   ActionView.Execute
   Do
      Set Record = ActionView.Fetch
      If Record Is Nothing Then Exit Do
      Sequence = Record.IntegerData(3)
      Action = Record.StringData(1)
      Condition = Record.StringData(2)
      If Sequence <= 0 Then
         Debug.Print Sequence; " Action = "; Action; "  (exit dialog)"
         If (Sequence = -1) Then ExitAction = Action
         If (Sequence = -2) Then CancelActon = Action
         If (Sequence = -3) Then ErrorAction = Action
      Else
         Test = Engine.EvaluateCondition(Condition)
         Debug.Print Sequence; " Action = "; Action; "  Condition:"; Test;
         If Test = iecError Then Debug.Print "Invalid Expression"; Condition
         If Test <> iecFalse Then
            Result = Engine.DoAction(Action)
            Debug.Print "  Result: "; Result
            If Result <> iesSuccess And Result <> iesNoAction Then Exit Do
         Else
            Debug.Print "(not called)"
         End If
      End If
   Loop
   If Result = iesSuccess Then Engine.DoAction (ExitAction)
   If Result = iesFailure Then Engine.DoAction (ErrorAction)
   If Result = iesUserExit Then Engine.DoAction (CancelAction)
   Debug.Print "Sequence completed"
End Sub


README.TXT

The InfInst sample demonstrates the basic use of the Win32 Setup APIs.

Win32 Setup APIs are a series of functions for installing files,
modifying the registry, and modifying INI files.  The primary way
to use the Win32 Setup APIs is to combine the INF handling functions
with one or more INF files that describe the actions to perform
during your install or uninstall.  One benefit of the Win32 Setup APIs
is that it provides dialog boxes that are consistent, familiar to user
from the NT and Windows 95 setup, and localized if the OS is
localized.  Moreover, the copy operations can handle things like
version checking and replacing in use files.  See the thorough
documentation in the Win32 SDK help.

Below are the primary APIs used in this sample. These are implemented
in the doinst.c file.  The rest of the sample is a basic wizard
that collects simple information from the user. The wizard is based
on the win32\wizard sample and is not complete in its error handling
and in helping the user determine things like disks space.

The install process overview is:
  TASK                       SETUPAPI
  open a specific inf        SetupOpenInfFile
  call the wizard for input  (CreateWizard implement in the sample)
  set the directory ids      SetupSetDirectoryId
  create a file queue        SetupOpenFileQueue
  create a queue context     SetupInitDefaultQueueCallback
  add files to queue         SetupInstallFilesFromInfSection
  do the copy                SetupCommitFileQueue
  do the registry stuff      SetupInstallFromInfSection
  close the queue            SetupTermDefaultQueueCallback
  close the inf              SetupCloseFileQueue

Many of the Win32 Setup APIs are specific to device driver
installations which are not addressed in this sample.


DOINST.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

#include <windows.h>    // includes basic windows functionality
#include <string.h>     // includes the string functions
#include <shlobj.h>     // To update the desktop
#include <regstr.h>

#include "setupapi.h"   // includes the inf setup api
#include "instwiz.h"    // includes the application-specific information
#include "infinst.h"    // includes the application-specific information
#include "infdesc.h"    // includes the specifics of how this
                        // inf is layed out, like the inf names
                        // the HKeys and the DirIds

DWORD InstallFinish(BOOL DoRunOnce);

//
//    FUNCTION: BOOL DoInstallation( HWND hWnd, INSTALLINFO * si )
//
//    PURPOSE: Install components via setupapi.dll.
//
//   COMMENTS:
//
//      The function does inf install operations based
//      on the content of the INSTALLINFO data.
//      These steps could be done during the wizard but
//      this technique allows for installations with user input.
//
// This routine will take a INSTALLINFO
// and do an installation based on those settings
// using the setupapi.dll

BOOL DoInstallation( HWND hWnd, INSTALLINFO * si )
{

    HINF hInf;
    char szSourcePath[MAX_PATH];
    char szInfFileName[MAX_PATH];

    DWORD dwResult;
    BOOL bResult = FALSE;

    // Context for my call back routine
    MY_INSTALL_DATA MyInstallData;

    HSPFILEQ FileQueue;

    //
    // First we setup the inf to what the wizard collected
    // note the hInf will maintain the information and let
    // you build copy list as you go
    // This sample seperates the wizard from the setapi stuff
    // just to keep the setupapi stuff in one place
    //

    //
    // The install process overview is:
    //   TASK                       SETUPAPI
    //   open a specific inf        SetupOpenInfFile
    //   call the wizard for input  CreateWizard (implemented in instwiz.c)
    //   set the directory ids      SetupSetDirectoryId
    //   create a file queue        SetupOpenFileQueue
    //   create a queue context     SetupInitDefaultQueueCallback
    //   add files to queue         SetupInstallFilesFromInfSection
    //   do the copy                SetupCommitFileQueue
    //???   do the registry stuff      SetupInstallFromInfSection
    //   close the queue            SetupTermDefaultQueueCallback
    //   close the inf              SetupCloseFileQueue

    //
    // In this sample we assume the inf is in the base of the
    // base installation source path--it usually is for most installs
    //

    GetModuleFileName(NULL, szSourcePath, _MAX_PATH);
    *(strrchr(szSourcePath, '\\') + 1) = '\0';        // Strip setup.exe off path

    strcpy(szInfFileName, szSourcePath);
    strcat(szInfFileName, "product.inf");

    //
    // Get inf handle
    // must know where the inf is located
    // SetupOpenInfFile will only look in windows\inf by default
    //

    hInf = SetupOpenInfFile (
        szInfFileName,       // If path,needs full path, else looks in %windir%\inf
        NULL,                // Inf Type, matches Class in [Version] section SetupClass=SAMPLE
        INF_STYLE_WIN4,      // or INF_STYLE_OLDNT
        NULL                 // Line where error occurs if inf is has a problem
        );

    if (hInf == INVALID_HANDLE_VALUE)
    {
        dwResult = GetLastError();

        //
        // TODO: handle case where inf cannot be opened
        //       by asking the user to locate the inf

        return FALSE;
    }

    // Run the wizard
    if (CreateWizard(hWnd, si->hInst))
    {
        RuntimeRegistration(si);
    }
    else
    {
        SetupCloseInfFile(hInf);
        return FALSE;
    }

    //
    // Special case the uninstall destitation path
    // this is because we need to get the registry value
    // for where to delete the files from.
    //

    if (IDC_INSTALL_TYPE_UNINSTALL == si->iInstall_Type)
    {
        // get path installed to here!
        // query registry for si->pszDestPath

        GetRegString (MYPRODUCT_KEY, TEXT("DestinationPath"),
                si->pszDestPath);
    }

    //
    // Directory ids are set per HINF
    //
    // TODO: verify dest path as a valid file name

    bResult = SetupSetDirectoryId(hInf,          // the HINF that will hold these ids
                        (DWORD) DESTINATION_DIR, // the id that matchs one in the inf
                        si->pszDestPath);        // the user's string

    if(!bResult)
    {
        dwResult = GetLastError();

        // TODO: handle case where we cannot set dirids, should we default or bail?
        //       perhaps write this out to an error log
        //
        // Close the inf file and return
        //

        SetupCloseInfFile(hInf);
        return FALSE;
    }

    //
    // Create a Setup file queue and initialize the default Setup
    // queue callback routine.
    //

    FileQueue = SetupOpenFileQueue();

    if(!FileQueue || (FileQueue == INVALID_HANDLE_VALUE))
    {
        dwResult = ERROR_NOT_ENOUGH_MEMORY;

        //
        // Close the queue and return
        //

        SetupCloseInfFile(hInf);
        return FALSE;
    }

    //
    // This initialize memory for the default call back
    // This context MUST be used if our private callback routine
    //     wants to callback into the default routine, we do it here with
    //     a global value for the default context
    //
    // We want to use the default progress dialog.
    // We could override this by specifying a window that
    // will handle the ui in param 2.
    // By not passing a progress HWND this is the same as
    // using SetupInitDefaultQueueCallback.
    //

    MyInstallData.pDefaultContext = SetupInitDefaultQueueCallbackEx(
                    hWnd,  // HWND of owner window
                    NULL,  // HWND of alternate progress dialog which receives -- for example
                           //      if you wanted to display your progress bar right in the wizard
                    0,     // Message sent to above window indicating a progress message
                    0,     // DWORD Reserved
                    NULL   // PVOID Reserved
                    );

    if(!(MyInstallData.pDefaultContext))
    {
        dwResult = ERROR_NOT_ENOUGH_MEMORY;

        //
        // Close the queue and the inf file and return
        //

        SetupCloseFileQueue(FileQueue);
        SetupCloseInfFile(hInf);
        return FALSE;
    }

    //
    // Now that we have a FileQueue, a Queue Context, and an HINF
    // we want to map the DirectoryIds in the INF to the ones
    // obtained by the user

    //
    // Queue file operations and commit the queue.
    //

    //
    // Install each option they have selected
    //

    if (IDC_INSTALL_TYPE_UNINSTALL == si->iInstall_Type)
    {
        //TODO: allow the user to remove specific components
        //      note you would want to update the wizard to reflect this

        bResult = SetupInstallFilesFromInfSection(
                  hInf,     // HINF that has the directory ids set above
                  NULL,          // layout.inf if you have one, this a convient
                                 //     place to do all of your file to media id mapping
                  FileQueue,     // Queue to add files to
                  TEXT("MyProgramUninstall"),   // SectionName,
                  szSourcePath,    // Path where the source files are located
                  SP_COPY_NEWER);// The controls how to version check
                                 // and how to prompt
    }
    else
    {

      if (si->iCustom_Options1){
        bResult = SetupInstallFilesFromInfSection(
                      hInf,     // HINF that has the directory ids set above
                      NULL,          // layout.inf if you have one, this a convient
                                     //     place to do all of your file to media id mapping
                      FileQueue,     // Queue to add files to
                      INF_OPTION1,   // SectionName,
                      szSourcePath,    // Path where the source files are located
                      SP_COPY_NEWER);// The controls how to version check
                                     // and how to prompt
        }

        if (si->iCustom_Options2) {
            bResult = SetupInstallFilesFromInfSection(hInf, NULL,
                FileQueue, INF_OPTION2, szSourcePath, SP_COPY_NEWER );
        }

        if (si->iCustom_Options3) {
            bResult = SetupInstallFilesFromInfSection(hInf, NULL,
                FileQueue, INF_OPTION3, szSourcePath, SP_COPY_NEWER );
        }

        if (si->iCustom_Options4) {
            bResult = SetupInstallFilesFromInfSection(hInf, NULL,
                FileQueue, INF_OPTION4, szSourcePath, SP_COPY_NEWER );
        }
    }

    //
    // All the files for each component are now in one queue
    // now we commit it to start the copy ui, this way the
    // user has one long copy progress dialog--and for a big install
    // can go get the cup of coffee
    //
    if(bResult) {

        bResult = SetupCommitFileQueue(
                hWnd,                      // Owner
                FileQueue,                 // Queue with the file list
                (PSP_FILE_CALLBACK) MyQueueCallback,
                                           // This is our handler, it calls the default for us
                                           // NOTE:
                                           // (PSP_FILE_CALLBACK) SetupDefaultQueueCallback
                                           // would use the default message handler automatically
                &MyInstallData             // Pointer to resources allocated with SetupInitDefaultQueueCallback/Ex
                );

        dwResult = bResult ? NO_ERROR : GetLastError();

    } else {

        dwResult = GetLastError();

    }

    //
    // Do registry munging, etc.
    // NOTE: you can do the entire install
    // for a section with this api but in this case
    // we build the file list conditionally and
    // do only out ProductInstall section for registy stuff
    // Also using SPINST_FILES will do the files
    // as above but only one section at a time
    // so the progress bar would keep completing and starting over
    // SPINST_ALL does files, registry and inis
    //
    if (si->iCustom_Options1)
    {
      bResult = SetupInstallFromInfSection(
            hWnd,
            hInf,
            INF_OPTION1,
            SPINST_REGISTRY | SPINST_INIFILES,
            NULL,
            NULL,//szSourcePath,    // Path where the source files are located
            0,//SP_COPY_NEWER,
            NULL,//(PSP_FILE_CALLBACK) MyQueueCallback,
            NULL,//&MyInstallData,
            NULL,
            NULL
            );
    }

    //
    // Perserve first non-success error code.
    //
    if(!bResult && (dwResult == NO_ERROR)) {

        dwResult = GetLastError();

    }

    //
    // We're done so free the context, close the queue,
    // and release the inf handle
    //

    SetupTermDefaultQueueCallback(MyInstallData.pDefaultContext);
    SetupCloseFileQueue(FileQueue);
    SetupCloseInfFile(hInf);

    if(dwResult != NO_ERROR) {

        // TODO: Log an error here

    }

    if (bResult) {

        // do whatever you need to complete a successful install

        //
        // Refresh the desktop for folders and icons.
        //
        InstallFinish(TRUE);

    } else {

        MessageBox( hWnd,
            TEXT("Installation script did not complete successfully!"),
            TEXT("Product Sample Install Error"),
            MB_OK);
    }

    return TRUE;
}

LRESULT
WINAPI
MyQueueCallback (
    IN MY_INSTALL_DATA* pMyInstallData,
    IN UINT Notification,
    IN UINT Param1,
    IN UINT Param2
    )
{
    if (SPFILENOTIFY_DELETEERROR == Notification)
    {
        // Skip any file delete errors
        // this sample only deletes files on an unintall
        // so if the delete encounters an error simply skip the operation
        // and continue processing the queue
        return FILEOP_SKIP;
    }
    else
    {
        // Pass all other notifications through without modification
        return SetupDefaultQueueCallback(pMyInstallData->pDefaultContext,
                                         Notification, Param1, Param2);
    }
}

DWORD
InstallFinish(
    IN BOOL DoRunOnce
    )
/*++

Routine Description:

    This routine sets up runonce/grpconv to run after a successful INF installation.

Arguments:

    DoRunOnce - If TRUE, then invoke (via WinExec) the runonce utility to perform the
        runonce actions.  If this flag is FALSE, then this routine simply sets the
        runonce registry values and returns.

        NOTE:  The return code from WinExec is not currently being checked, so the return
        value of InstallStop only reflects whether the registry values were set up
        successfully--_not_ whether 'runonce -r' was successfully run.

Return Value:

    If successful, the return value is NO_ERROR, otherwise it is the Win32 error code
    indicating the error that was encountered.

--*/
{
    HKEY  hKey, hSetupKey;
    DWORD Error;
    LONG l;

    //
    // First, open the key "HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce"
    //
    if((l = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                         REGSTR_PATH_RUNONCE,
                         0, KEY_ALL_ACCESS, &hKey)) != ERROR_SUCCESS) {
        return (DWORD)l;
    }

    //
    // If we need to run the runonce exe for the setup key...
    //
    if(RegOpenKeyEx(hKey,
                    TEXT("Setup"),
                    0,
                    KEY_READ,
                    &hSetupKey) == ERROR_SUCCESS) {
        //
        // We don't need the key--we just needed to check its existence.
        //
        RegCloseKey(hSetupKey);

        //
        // Add the runonce value.
        //
        Error = (DWORD)RegSetValueEx(hKey,
                                     TEXT("Wrapper"),
                                     0,
                                     REG_SZ,
                                     TEXT("runonce"),
                                     sizeof(TEXT("runonce"))
                                    );
    } else {
        //
        // We're OK so far.
        //
        Error = NO_ERROR;
    }

    //
    // GroupConv is always run.
    //
    if(RegSetValueEx(hKey,
                     TEXT("GrpConv"),
                     0,
                     REG_SZ,
                     TEXT("grpconv -o"),
                     sizeof(TEXT("grpconv -o"))) != ERROR_SUCCESS) {
        //
        // Since GrpConv is always run, consider it a more serious error than any error
        // encountered when setting 'runonce'.  (This decision is rather arbitrary, but
        // in practice, it should never make any difference.  Once we get the registry key
        // opened, there's no reason either of these calls to RegSetValueEx should fail.)
        //
        Error = (DWORD)l;
    }

    RegCloseKey(hKey);

    if(DoRunOnce) {
        WinExec("runonce -r", SW_SHOWNORMAL);
    }

    return Error;
}


INFDESC.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

//Header file for inf specific data

//
// SetupApi helpers for the main exe
// implemented in doinst.c
//
BOOL DoInstallation(HWND, INSTALLINFO*);

//
// These are the custom DirIds for the inf
// Doinst.c maps real install time strings
// to these numbers.
//
// The ids are mapped exactly to the numbers in the inf
// custom ids must be > 65536 (decimal)
//
// I have chosen 65600 as a starting number at random
//
#define DESTINATION_DIR     65600

//
// optional inf section names, in this sample these
// are not platform speific -- like MS Word templates for examples
//
#define INF_OPTION2         TEXT("Option2Section")
#define INF_OPTION3         TEXT("Option3Section")
#define INF_OPTION4         TEXT("Option4Section")

//
// Main inf section name, in this sample this
// is platform speific -- say the executables for your
// application
//
#ifdef _ALPHA_
#define INF_OPTION1         TEXT("ProductInstall.Alpha")
#else

#ifdef  _MIPS_
#define INF_OPTION1         TEXT("ProductInstall.Mips")
#else

#ifdef  _PPC_
#define INF_OPTION1         TEXT("ProductInstall.PPC")

#else // default to X86
#define INF_OPTION1         TEXT("ProductInstall")
#endif
#endif
#endif


INFINST.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

//
//    PROGRAM:InfInst.c
//
//    PURPOSE:Demonstrates how create a wizard based install
//              using win32 functions in setupapi.dll
//
//    PLATFORMS:Windows 95 and NT 4.0
//
//    FUNCTIONS:
//    WinMain() - calls initialization function, processes message loop
//    InitApplication() - Initializes window data nd registers window
//    InitInstance() - saves instance handle and creates main window
//    MainWindProc() - Processes messages
//    RuntimeRegistration() - Add runtime data to registry
//      RegisterString() - adds a string to registry, simple win32 registry api wrapper
//      GetRegString() - gets a string from registry, simple win32 registry api wrapper
//      GetPlatformInfo () - not used but this is an exampleof what you can do in
//             a win32 or win16 setup stub program to get your setup started
//             NOTE: In this sample we are assuming the autorun.inf takes care of this
//
//    CALLS:
//    DoInstallation() - Does the resulting installation options in doinst.c
//
//    SPECIAL INSTRUCTIONS: N/A
//

#include <windows.h>    // includes basic windows functionality
#include <string.h>     // includes the string functions
#include <prsht.h>      // includes the property sheet functionality
#include "setupapi.h"   // includes the inf setup api
#include "resource.h"   // includes the definitions for the resources
#include "instwiz.h"     // includes the application-specific information
#include "infinst.h"     // includes the application-specific information
#include "infdesc.h"

INSTALLINFO setupInfo;      // a structure containing the review information
HWND hwndEdit;          // handle to the main MLE
TCHAR lpReview[MAX_BUF]; // Buffer for the review

BOOL bCreated = FALSE;  // Keep us minimized once we are created

//
//
//   FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
//
//   PURPOSE: Main entry point for the application.
//
//   COMMENTS:
//
//    This function calls the initialization functions and processes
//    the main message loop.
//
int APIENTRY WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
    )
{
        MSG msg;

        // save off the current instance
        setupInfo.hInst = hInstance;

        // if the initialization fails, return.
        if (!InitApplication(hInstance))
            return (FALSE);

        // Perform initializations that apply to a specific instance
        if (!InitInstance(hInstance, nCmdShow))
            return (FALSE);

       // Acquire and dispatch messages until a WM_QUIT message is received.
       while (GetMessage(&msg, NULL, 0,0))
       {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
       }

       return (msg.wParam);
}

//
//
//   FUNCTION: InitApplication(HANDLE)
//
//   PURPOSE: Initializes window data and registers window class
//
//   COMMENTS:
//
//    This function registers the window class for the main window.
//
BOOL InitApplication(HANDLE hInstance)
{
        WNDCLASS  wcSample;

        // Fill in window class structure with parameters that describe the
        // main window.

        wcSample.style = 0;
        wcSample.lpfnWndProc = (WNDPROC)MainWndProc;
        wcSample.cbClsExtra = 0;
        wcSample.cbWndExtra = 0;
        wcSample.hInstance = hInstance;
        wcSample.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(EXE_ICON));
        wcSample.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcSample.hbrBackground = GetStockObject(WHITE_BRUSH);
        wcSample.lpszMenuName =  NULL;
        wcSample.lpszClassName = TEXT("SampleWClass");

        return (RegisterClass(&wcSample));

}

//
//
//   FUNCTION: InitInstance(HANDLE, int)
//
//   PURPOSE: Creates the main window.
//
//   COMMENTS: N/A
//
//
BOOL InitInstance(
    HANDLE          hInstance,
    int             nCmdShow)
{
    HWND hWndMain;

    hWndMain = CreateWindow(
        TEXT("SampleWClass"),
        TEXT("Install Sample"),
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    NULL,
    NULL,
    hInstance,
    NULL);

    /* If window could not be created, return "failure" */
    if (!hWndMain)
    return (FALSE);

    /* Make the window visible; update its client area; and return "success" */
    ShowWindow(hWndMain, SW_MINIMIZE);
    UpdateWindow(hWndMain);
    return (TRUE);

}

//
//   FUNCTION: MainWndProc(HWND, UINT, UINT, LONG)
//
//  PURPOSE:  Processes messages for the main window procedure
//
//    MESSAGES:
//
//    WM_CREATE - creates the main MLE for the window
//    WM_COMMAND - processes the menu commands for the application
//    WM_SIZE - sizes the MLE to fill the client area of the window
//    WM_DESTROY - posts a quit message and returns
//
LONG APIENTRY MainWndProc(
    HWND hWnd,                // window handle
    UINT message,             // type of message
    UINT wParam,              // additional information
    LONG lParam)              // additional information
{

    switch (message)
    {
        case WM_CREATE:
            // TODO: put in a Bitmap for a splash window );

            GetRegString(MYPRODUCT_KEY, USER_NAME_KEY,  setupInfo.pszUserName);
            GetRegString(MYPRODUCT_KEY, COMPANY_KEY,    setupInfo.pszCompany);
            GetRegString(MYPRODUCT_KEY, PRODUCT_ID_KEY, setupInfo.pszProductIdString);
            GetRegString(MYPRODUCT_KEY, EMAIL_KEY,      setupInfo.pszEmailAddress);
            GetRegString(MYPRODUCT_KEY, TEXT("DestinationPath"),  setupInfo.pszDestPath);

            // Start up the install
            PostMessage(hWnd, WM_COMMAND, ID_INSTALL, 0 );
            return 0;

         case WM_WINDOWPOSCHANGING:
             if (bCreated)
             {
                 LPWINDOWPOS lpwp;

                 lpwp = (LPWINDOWPOS) lParam; // points to size and position data
                 lpwp->flags = SWP_NOMOVE    | SWP_NOOWNERZORDER |
                               SWP_NOSIZE | SWP_NOREDRAW |
                               SWP_NOREPOSITION;

              }
              else
              {
                 bCreated = TRUE;
              }
              break;

        case WM_COMMAND:
            switch( LOWORD( wParam ))
    {
                /*******************************************************\
                *
                *  Here is where the real work takes place
                *     Do the wizard to collect the user information
                *     Do the installation with the setupapis
                *     Update the registry with runtime user data
                *
                \*******************************************************/
                case ID_INSTALL:
                    // Do installation
                    DoInstallation(hWnd, &setupInfo);

                    //installs done so go away
                    PostMessage(hWnd, WM_DESTROY, 0, 0 );
                    break;

                 default:
                    return (DefWindowProc(hWnd, message, wParam, lParam));

    }
    break;

    case WM_DESTROY:                  /* message: window being destroyed */
    PostQuitMessage(0);
    break;

    default:
    return (DefWindowProc(hWnd, message, wParam, lParam));
    }
    return (0);
}

void RuntimeRegistration(INSTALLINFO *si)
{
    //
    // The setupapi calls have completed
    // now we will finish up by updating the user information
    // and uninstall in the registry
    //

    char buf[MAX_PATH];

    //
    // Stuff for out product
    // fixed application values are done by the inf
    // We are only doing runtime values here
    //

    RegisterString(MYPRODUCT_KEY,
             USER_NAME_KEY,
             si->pszUserName);

    RegisterString(MYPRODUCT_KEY,
             COMPANY_KEY,
             si->pszCompany);

    RegisterString(MYPRODUCT_KEY,
             PRODUCT_ID_KEY,
             si->pszProductIdString);

    RegisterString(MYPRODUCT_KEY,
             EMAIL_KEY,
             si->pszEmailAddress);

    wsprintf(buf, "%s", si->pszDestPath);

    RegisterString(MYPRODUCT_KEY,
                TEXT("DestinationPath"),
                buf);

/*
    //
    // Setup up Add/Remove Programs Control Panel Applet
    // We need to set Display name and how to uninstall
    //
    // UNINSTALL Info
    //

    RegisterString(UNINSTALL_KEY,
                TEXT("DisplayName"),
                TEXT("MyProduct Version 1.0"));

    wsprintf(buf, "%s\\setup.exe Uninstall", si->pszDestPath);

    RegisterString(UNINSTALL_KEY,
                TEXT("UninstallString"),
                buf);

*/
    return;
}

BOOL
RegisterString (
   LPSTR pszKey,
   LPSTR pszValue,
   LPSTR pszData
   )
{

    HKEY hKey;
    DWORD dwDisposition;

    //
    // Create the key, if it exists it will be opened
    //

    if (ERROR_SUCCESS !=
        RegCreateKeyEx(
          HKEY_LOCAL_MACHINE,       // handle of an open key
          pszKey,                  // address of subkey name
          0,                       // reserved
          NULL,                    // address of class string
          REG_OPTION_NON_VOLATILE, // special options flag
          KEY_ALL_ACCESS,           // desired security access
          NULL,                       // address of key security structure
          &hKey,                   // address of buffer for opened handle
          &dwDisposition))            // address of disposition value buffer
    {
        return FALSE;
    }

    //
    // Write the value and it's data to the key
    //

    if (ERROR_SUCCESS !=
        RegSetValueEx(
            hKey,                 // handle of key to set value for
            pszValue,             // address of value to set
            0,                     // reserved
            REG_SZ,                 // flag for value type
            pszData,             // address of value data
            strlen(pszData) ))      // size of value data
    {

        RegCloseKey(hKey);
        return FALSE;
    }

    //
    // Close the key
    //

    RegCloseKey(hKey);

    return TRUE;
}

BOOL
GetRegString (
  LPSTR pszKey,
  LPSTR pszValue,
  LPSTR pszData
  )
{

    HKEY hKey;
    DWORD dwDataSize = MAX_PATH - 1;
    DWORD dwValueType = REG_SZ;

    RegOpenKeyEx(
       HKEY_LOCAL_MACHINE,    // handle of open key
       pszKey,                // address of name of subkey to open
       0,                    // reserved
       KEY_QUERY_VALUE,        // security access mask
       &hKey                 // address of handle of open key
       );

    RegQueryValueEx(
        hKey,         // handle of key to query
        pszValue,     // address of name of value to query
        0,             // reserved
        &dwValueType,// address of buffer for value type
        pszData,     // address of data buffer
        &dwDataSize  // address of data buffer size
        );

    if (pszData[dwDataSize] != '\0')
        pszData[dwDataSize] = '\0';

    return TRUE;
}

/*

//      GetPlatformInfo () - not used but this is an example of what you can do in
//             a win32 or win16 setup stub program to get your setup started
//             NOTE: In this sample we are assuming the autorun.inf takes care of this
//                   for us. See the autorun.inf which shows this
//
//      The core of this could be your entire WinMain in a win16 stub
//
int GetPlatformInfo(HINSTANCE hInstance)
{
    char szCpu[64], szPath[_MAX_PATH], szExeStr[128];
    char szSystemDir[_MAX_PATH];

    GetModuleFileName(hInstance, szPath, _MAX_PATH);
    *(strrchr(szPath, '\\') + 1) = '\0';// Strip SETUPPROG off path
    GetSystemDirectory(szSystemDir, _MAX_PATH);

    if (getenv("PROCESSOR_ARCHITECTURE") != NULL)
    {
    lstrcpyn(szCpu, getenv("PROCESSOR_ARCHITECTURE"), 63);
    }
    else
    {   // Not defined, guess x86
    lstrcpy(szCpu, "x86");
    }

        if (HIBYTE(LOWORD(GetVersion())) > 11) // Check if Win95
{
    //Win95
            sprintf(szExeStr, "%s\\Bin\\I386\\infinst.exe CmdLineOptions", szPath);
}
        // Else pick the NT platform
        else if (!lstrcmp(szCpu, "MIPS"))
{
            sprintf(szExeStr, "%s\\Bin\\MIPS\\infinst.exe CmdLineOptions", szPath);
}
else if (!lstrcmp(szCpu, "ALPHA"))
{
            sprintf(szExeStr, "%s\\Bin\\ALPHA\\infinst.exe CmdLineOptions", szPath);
}
else if (!lstrcmp(szCpu, "PPC"))
{
            sprintf(szExeStr, "%s\\Bin\\PPC\\infinst.exe CmdLineOptions", szPath);
}
else   // x86 NT box
{
            sprintf(szExeStr, "%s\\Bin\\I386\\infinst.exe CmdLineOptions", szPath);
        }

    //    MessageBox ( NULL, szExeStr,szCpu,MB_OK);

    //    WinExec(szExeStr, SW_SHOW);

    if (wReturn < 32) {
             //Failed
    }
    else {
              //Worked
    }

    return(0);
}
*/


INFINST.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

//
// commands
//
#define ID_INSTALL     100

//
// icons
//
#define EXE_ICON        300

//
// typedefs
//
typedef struct tagINSTALLINFO
{
    HINSTANCE hInst;        // current instance
    int iWelcome;
    int iLicense;
    int iInstall_Type;
    int iCustom_Options1;
    int iCustom_Options2;
    int iCustom_Options3;
    int iCustom_Options4;
    int iInstall;
    char pszUserName[MAX_PATH];
    char pszCompany[MAX_PATH];
    char pszProductIdString[MAX_PATH];
    char pszEmailAddress[MAX_PATH];
    char pszDestPath[MAX_PATH];
} INSTALLINFO;

//
// globals
//
extern INSTALLINFO setupInfo;   // a structure containing the review information
extern HWND hwndEdit;           // handle to the main MLE
extern TCHAR lpReview[MAX_BUF]; // Buffer for the review
extern BOOL bCreated;           // Keep us minimized once we are created
extern BOOL bUninstallCommand;  // Is it an uninstall

//
// Function prototypes
//

long APIENTRY MainWndProc(HWND, UINT, UINT, LONG);
BOOL InitApplication(HANDLE);
BOOL InitInstance(HANDLE, int);
void RuntimeRegistration(INSTALLINFO*);

//
// simple win32 registry api wrappers
//
BOOL RegisterString(LPSTR pszKey, LPSTR pszValue, LPSTR pszData);
BOOL GetRegString(LPSTR pszKey, LPSTR pszValue, LPSTR pszData);

//
// Context for data needed in my callback routine
// This needs to include the context for the default callback
// since my callback calls the default
//

typedef struct tagMY_INSTALL_DATA{

   // TODO: Add other data here that you want to have
   //       available to your callback function.
   //       For example, you may keep a counter that indicates
   //       your bill board if you are doing your own progress dialog

   PVOID pDefaultContext;

} MY_INSTALL_DATA;

//
// call back routine for setup api uses for
// progress and error messages
//
LRESULT WINAPI MyQueueCallback (
    IN MY_INSTALL_DATA* pMyInstallData,
    IN UINT Notification,
    IN UINT Param1,
    IN UINT Param2
    );

//
// Product Registry keys
// set by exe and not inf since these are runtime
// values.  All registry information that is not determined at
// runtime is in the inf file
//
#define USER_NAME_KEY       TEXT("UserName")
#define COMPANY_KEY         TEXT("CompanyName")
#define PRODUCT_ID_KEY      TEXT("ProductId")
#define EMAIL_KEY           TEXT("EmailAddress")

#define UNINSTALL_KEY       TEXT("SoftWare\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MyProduct")
#define MYPRODUCT_KEY       TEXT("Software\\MyCompany\\MyProduct\\UserInfo\\")


INSTWIZ.C

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1994 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

//
//
//  This wizard code is adapted from the mstools\win32\wizard
//  sample, refer to the wizard sample and the online help for
//  more information about wizards and property pages
//
//
//  Functions:
//      CreateWizard(HWND, HINSTANCE) - starts the wizard
//    FillInPropertyPage() - Fills in a PROPSHEETPAGE structure
//
//    Welcome(),License(),YourInfo(),Install_Type(),Install_Destination(),
//      Custom_Options(),Install()
//           - Process the respective Install pages
//
//

#include <windows.h>
#include "instwiz.h"
#include "infinst.h"

// global license flag
BOOL gLicenseAccepted = FALSE;

//
//
//    FUNCTION: CreateWizard(HWND)
//
//    PURPOSE: Create the Install control.
//
//   COMMENTS:
//
//      This function creates the install property sheet.
//
int CreateWizard(HWND hwndOwner, HINSTANCE hInst)
{
    PROPSHEETPAGE psp[NUM_PAGES];
    PROPSHEETHEADER psh;

    FillInPropertyPage( &psp[0], IDD_WELCOME, TEXT("Welcome"), Welcome);
    //FillInPropertyPage( &psp[1], IDD_LICENSE, TEXT("Software License Agreement"), License);
    FillInPropertyPage( &psp[1], IDD_INFO, TEXT("Your Information"), YourInfo);
    FillInPropertyPage( &psp[2], IDD_INSTALL_TYPE, TEXT("Installation Type"), Install_Type);
    FillInPropertyPage( &psp[3], IDD_INSTALL_DESTINATION, TEXT("Installation Location"), Install_Destination);
    FillInPropertyPage( &psp[4], IDD_CUSTOM_OPTIONS, TEXT("Custom Installation Options"), Custom_Options);
    FillInPropertyPage( &psp[5], IDD_INSTALL, TEXT("Finish Installation"), Install);

    psh.dwSize = sizeof(PROPSHEETHEADER);
    psh.dwFlags = PSH_PROPSHEETPAGE | PSH_WIZARD | PSH_NOAPPLYNOW;
    psh.hwndParent = hwndOwner;
    psh.pszCaption = (LPSTR) TEXT("Product Install");
    psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE);
    psh.nStartPage = 0;
    psh.ppsp = (LPCPROPSHEETPAGE) &psp;

    return (PropertySheet(&psh));
}

//
//
//  FUNCTION: FillInPropertyPage(PROPSHEETPAGE *, int, LPSTR, LPFN)
//
//  PURPOSE: Fills in the given PROPSHEETPAGE structure
//
//  COMMENTS:
//
//      This function fills in a PROPSHEETPAGE structure with the
//      information the system needs to create the page.
//
void FillInPropertyPage( PROPSHEETPAGE* psp, int idDlg, LPSTR pszProc, DLGPROC pfnDlgProc)
{
    psp->dwSize = sizeof(PROPSHEETPAGE);
    psp->dwFlags = 0;
    psp->hInstance = setupInfo.hInst;
    psp->pszTemplate = MAKEINTRESOURCE(idDlg);
    psp->pszIcon = NULL;
    psp->pfnDlgProc = pfnDlgProc;
    psp->pszTitle = pszProc;
    psp->lParam = 0;

}

//////////////////////////////////////////
//
// Wizard procs
//
//////////////////////////////////////////

//
//  FUNCTION: Welcome (HWND, UINT, UINT, LONG)
//
//  PURPOSE:  Processes messages for "Welcome" page
//
//  MESSAGES:
//
//    WM_INITDIALOG - intializes the page
//    WM_NOTIFY - processes the notifications sent to the page
//    WM_COMMAND - saves the id of the choice selected
//
BOOL APIENTRY Welcome(
    HWND hDlg,
    UINT message,
    UINT wParam,
    LONG lParam)
{

    switch (message)
    {
    case WM_INITDIALOG:
    setupInfo.iWelcome = 0;
           SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    break;

    case WM_NOTIFY:
        switch (((NMHDR FAR *) lParam)->code)
        {

      case PSN_KILLACTIVE:
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    return 1;
    break;

    case PSN_RESET:
    // rest to the original values
    setupInfo.iWelcome = 0;
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    break;

     case PSN_SETACTIVE:
     PropSheet_SetWizButtons(GetParent(hDlg),  PSWIZB_NEXT);
    SendMessage(GetDlgItem(hDlg,0x3024 ), BM_SETSTYLE,
                                                   (WPARAM)BS_PUSHBUTTON, MAKELONG(FALSE, 0));
    break;

                case PSN_WIZNEXT:
                    //
                    // If they haven't accepted the license
                    // take them there, otherwise proceed with the wizard
                    //
                    if (TRUE != gLicenseAccepted)
                    {
                        int nUserResponse;

                        // Ask them to accept the license
                        nUserResponse = DialogBox
                            (setupInfo.hInst,
                             MAKEINTRESOURCE(IDD_LICENSE),
                             hDlg,
                             (DLGPROC) License);

                        if (IDC_LICENSE_ACCEPT == nUserResponse)
                        {
                            // They agreed, now we won't show them the
                            // license again
                            gLicenseAccepted = TRUE;
                        }
                        else
                        {
                            // the user just cancelled the install
                            // by declining the license agreement
                            // you could put dialog box here
                            // making sure they realize that this will
                            // cancel the installation
                            PostQuitMessage(0);
                            return 1;
                        }
                    }

                    break;

                    default:
                        return FALSE;

        }
    break;

    default:
    return FALSE;
    }
    return TRUE;
}

//
//  FUNCTION: License (HWND, UINT, UINT, LONG)
//
//  PURPOSE:  Processes messages for "License Agreement" page
//
//  MESSAGES:
//
//    WM_INITDIALOG - intializes the page
//    WM_NOTIFY - processes the notifications sent to the page
//    WM_COMMAND - saves the id of the choice selected
//
BOOL APIENTRY License(
    HWND hDlg,
    UINT message,
    UINT wParam,
    LONG lParam)
{

    switch (message)
    {
    case WM_INITDIALOG:
            {
                //Read in License fiel
                char szLicenseFile[MAX_PATH], ReturnTextBuffer[MAX_PATH];

                LPSTR lpszLicenseFile = (char *) &szLicenseFile ;
                LPSTR lpszLicenseText;

                HWND hEditCtl;
                HANDLE hFile;

                DWORD NumberOfBytesRead, dwFileSize;

                //
                // Determine where we are installing from
                // and specific the license file there
                //
                GetModuleFileName(NULL, szLicenseFile, _MAX_PATH);
                *(strrchr(szLicenseFile, '\\') + 1) = '\0';        // Strip setup.exe off path

                strcat(szLicenseFile,TEXT("license.txt"));

                // Open License file
                hFile = CreateFile(
                                   lpszLicenseFile,       // pointer to name of the file
                                   GENERIC_READ,          // access (read-write) mode
                                   FILE_SHARE_READ,       // share mode
                                   NULL,                  // pointer to security descriptor
                                   OPEN_EXISTING,         // how to create
                                   FILE_ATTRIBUTE_NORMAL, // file attributes
                                   NULL);                 // handle to file with attributes to copy

                    if(INVALID_HANDLE_VALUE == hFile)
                    {
                        wsprintf(ReturnTextBuffer, "Error accessing file: %s", szLicenseFile);

                        MessageBox(hDlg, ReturnTextBuffer,
                                   "Sample Installation Critical Error", 0);

                        //install cannot proceed so go away

                        PostQuitMessage(0);
                        return FALSE;
                    }

                    // Read License file into string
                    // setup memory
                    dwFileSize = GetFileSize (hFile, NULL) ;

                    lpszLicenseText = malloc ((dwFileSize + 1));

                    if(NULL == lpszLicenseText)
                    {
                        wsprintf(ReturnTextBuffer, "Error allocating memory for license text.");
                CloseHandle(hFile);

                        MessageBox(hDlg, ReturnTextBuffer,
                            "Sample Installation Critical Error", 0);

                        //install cannot proceed so go away

                        free(lpszLicenseText);
                        PostQuitMessage(0);
                        return FALSE;
                    }

                    //read file
                    if (!ReadFile(hFile,    // handle of file to read
                                  lpszLicenseText,    // address of buffer that receives data
                                  dwFileSize,    // number of bytes to read
                                  &NumberOfBytesRead,    // address of number of bytes read
                                  NULL))                // address of structure for data
                    {
                        wsprintf(ReturnTextBuffer, "Error reading license file: %s", szLicenseFile);

                        // clean up
                        free(lpszLicenseText);
                        CloseHandle(hFile);

                        MessageBox(hDlg, "Sample Installation Critical Error",
                                   ReturnTextBuffer, 0);

                        //install cannot proceed so go away

                        PostMessage(hDlg, WM_DESTROY, 0, 0 );
                        return FALSE;
                    }

                //Done with file
                CloseHandle(hFile);
                hFile = NULL;

                // Be sure the file string is null terminated
                lpszLicenseText[dwFileSize] = '\0';

                SetDlgItemText(hDlg, LICENSE_TEXT, lpszLicenseText);

                //set focus to license text
                hEditCtl = GetDlgItem(hDlg, LICENSE_TEXT);
                SetFocus(hEditCtl);

                // the control now has the text
                // so free the memory
                free(lpszLicenseText);
                return(FALSE);
            }

            break;

        case WM_COMMAND:
            //
            // LOWORD added for portability
            //
            switch(LOWORD(wParam)) {
            case IDC_LICENSE_ACCEPT:
                EndDialog(hDlg, IDC_LICENSE_ACCEPT);
                return 0;
            case IDC_LICENSE_DECLINE:
                EndDialog(hDlg, IDC_LICENSE_DECLINE);
                return 0;
            }
            break;
        }
    return(FALSE);

    UNREFERENCED_PARAMETER(lParam);
}

//
//  FUNCTION: YourInfo(HWND, UINT, UINT, LONG)
//
//  PURPOSE:  Processes messages for "Your Information" page
//
//  MESSAGES:
//
//    WM_INITDIALOG - intializes the page
//    WM_NOTIFY - processes the notifications sent to the page
//
BOOL APIENTRY YourInfo(
    HWND hDlg,
    UINT message,
    UINT wParam,
    LONG lParam)
{

    switch (message)
    {
    case WM_INITDIALOG:
    break;

    case WM_NOTIFY:
        switch (((NMHDR FAR *) lParam)->code)
        {

      case PSN_KILLACTIVE:
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    return 1;
    break;

    case PSN_RESET:
    // reset to the blank values
    lstrcpy(setupInfo.pszUserName, TEXT(""));
    lstrcpy(setupInfo.pszCompany,TEXT(""));
    lstrcpy(setupInfo.pszProductIdString, TEXT(""));
    lstrcpy(setupInfo.pszEmailAddress, TEXT(""));
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    break;

     case PSN_SETACTIVE:
        PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK |PSWIZB_NEXT);
    SendMessage(GetDlgItem(hDlg,0x3024 ), BM_SETSTYLE, (WPARAM)BS_PUSHBUTTON, MAKELONG(FALSE, 0));
    //SendMessage(GetParent(hDlg), DM_SETDEFID, (WPARAM)IDC_BUTTON1, 0);
    SendMessage(GetDlgItem(hDlg, IDE_NAME),
                                     WM_SETTEXT, 0, (LPARAM)setupInfo.pszUserName);
    SendMessage(GetDlgItem(hDlg, IDE_COMPANY),
                                     WM_SETTEXT, 0, (LPARAM)setupInfo.pszCompany);
    SendMessage(GetDlgItem(hDlg, IDE_PRODUCT_ID),
                                     WM_SETTEXT, 0, (LPARAM)setupInfo.pszProductIdString);
    SendMessage(GetDlgItem(hDlg, IDE_EMAIL),
                                     WM_SETTEXT, 0, (LPARAM)setupInfo.pszEmailAddress);
    break;

                case PSN_WIZBACK:
                    break;

                case PSN_WIZNEXT:
    // the Next button was pressed
     SendDlgItemMessage(hDlg, IDE_NAME, WM_GETTEXT,
                                    (WPARAM)MAX_PATH, (LPARAM) setupInfo.pszUserName);
    SendDlgItemMessage(hDlg, IDE_COMPANY, WM_GETTEXT,
                                    (WPARAM)MAX_PATH, (LPARAM)setupInfo.pszCompany);
    SendDlgItemMessage(hDlg, IDE_PRODUCT_ID, WM_GETTEXT,
                                    (WPARAM)MAX_PATH, (LPARAM)setupInfo.pszProductIdString);
    SendDlgItemMessage(hDlg, IDE_EMAIL, WM_GETTEXT,
                                    (WPARAM)MAX_PATH, (LPARAM)setupInfo.pszEmailAddress);
                    break;

    default:
    return FALSE;

        }
    break;

    default:
    return FALSE;
    }
    return TRUE;
}

//
//  FUNCTION: Install_Type (HWND, UINT, UINT, LONG)
//
//  PURPOSE:  Processes messages for "Install_Type" page
//
//  MESSAGES:
//
//    WM_INITDIALOG - intializes the page
//    WM_NOTIFY - processes the notifications sent to the page
//    WM_COMMAND - saves the id of the choice selected
//
BOOL APIENTRY Install_Type(
    HWND hDlg,
    UINT message,
    UINT wParam,
    LONG lParam)
{

    switch (message)
    {
    case WM_INITDIALOG:
            // pick normal as the default
            setupInfo.iInstall_Type = IDC_INSTALL_TYPE_NORMAL;
            CheckRadioButton( hDlg, IDC_INSTALL_TYPE_NORMAL, IDC_INSTALL_TYPE_UNINSTALL,
            IDC_INSTALL_TYPE_NORMAL);
            setupInfo.iCustom_Options1 = 1;
            setupInfo.iCustom_Options2 = 1;
            setupInfo.iCustom_Options3 = 0;
            setupInfo.iCustom_Options4 = 1;
            break;

    case WM_COMMAND:
            if (HIWORD(wParam) == BN_CLICKED)
            {
                setupInfo.iInstall_Type = LOWORD(wParam);
                CheckRadioButton( hDlg, IDC_INSTALL_TYPE_NORMAL, IDC_INSTALL_TYPE_UNINSTALL, LOWORD(wParam));

                //TODO: you could change the wizard at this
                // point with add and remove page.
                // We will just set the options of custom options
                // for simplicity

                // change the NEXT to FINISH if they want to uninstall
                if (IDC_INSTALL_TYPE_UNINSTALL == LOWORD(wParam))
                {
                    //TODO: could check that the product is indeed
                    // installed and if not, don't let them select
                    // it--you could grey out the selection or
                    // you could just uninstall even though
                    // it won't do anything
                    PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK | PSWIZB_FINISH);
                }
                else
                {
                    PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK | PSWIZB_NEXT);
                }

                // add in options according to what was seleced
                switch LOWORD(wParam)
                {
                case IDC_INSTALL_TYPE_CUSTOM:
                    // first reset options to off
                    setupInfo.iCustom_Options1 = 0;
                    setupInfo.iCustom_Options2 = 0;
                    setupInfo.iCustom_Options3 = 0;
                    setupInfo.iCustom_Options4 = 0;
                    break;
                case IDC_INSTALL_TYPE_NORMAL:
                    setupInfo.iCustom_Options1 = 1;
                    setupInfo.iCustom_Options2 = 1;
                    setupInfo.iCustom_Options3 = 0;
                    setupInfo.iCustom_Options4 = 1;
                    break;
                case IDC_INSTALL_TYPE_MIN:
                    setupInfo.iCustom_Options1 = 1;
                    setupInfo.iCustom_Options2 = 1;
                    setupInfo.iCustom_Options3 = 0;
                    setupInfo.iCustom_Options4 = 0;
                    break;
                default:
                    break;
                }
            }

    break;

    case WM_NOTIFY:
        switch (((NMHDR FAR *) lParam)->code)
        {

      case PSN_KILLACTIVE:
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    return 1;
    break;

    case PSN_RESET:
    // rest to the original values
    setupInfo.iInstall_Type = 0;
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    break;

     case PSN_SETACTIVE:
                               //if they have selected an install type, be sure
                               //it is checked
                               if (setupInfo.iInstall_Type)
    SendMessage(GetDlgItem(hDlg, setupInfo.iInstall_Type),
                                            BM_SETCHECK, 1, 0L);

                               // Set the correct button NEXT or FINISH
                               if (IDC_INSTALL_TYPE_UNINSTALL == setupInfo.iInstall_Type)
                               {
                                   //TODO: could check that the product is indeed
                                   // installed and if not, don't let them select
                                   // it--you could grey out the selection or
                                   // you could just uninstall even though
                                   // it won't do anything
                                   PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK | PSWIZB_FINISH);
                               }
                               else
                               {
                                   PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK | PSWIZB_NEXT);
                               }
break;

                case PSN_WIZBACK:
                    break;

                case PSN_WIZNEXT:
                    break;

                case PSN_WIZFINISH:
                    // They finished the wizard, now do
                    // what they said
                    break;

                default:
                    return FALSE;

        }
    break;

    default:
    return FALSE;
    }
    return TRUE;
}
//
//  FUNCTION: Install_Destination(HWND, UINT, UINT, LONG)
//
//  PURPOSE:  Processes messages for "Install Destination" page
//
//  MESSAGES:
//
//    WM_INITDIALOG - intializes the page
//    WM_NOTIFY - processes the notifications sent to the page
//
BOOL APIENTRY Install_Destination(
    HWND hDlg,
    UINT message,
    UINT wParam,
    LONG lParam)
{

    switch (message)
    {
    case WM_INITDIALOG:
    //lstrcpy(setupInfo.pszDestPath, TEXT(""));
    break;

    case WM_NOTIFY:
        switch (((NMHDR FAR *) lParam)->code)
        {
                //TODO: Add code here to check that the user entered
                //      path is valid and show the user disk space available
                //      You can also have more on disk space on the
                //      customer options page.
                //      So this sample does NOT verify the path and disk space
                //      requirements.  Note the setupapi functions will gracefully
                //      let the user know there is no disk space avail--at which
                //      time the user can go clean up some space or cancel the install

      case PSN_KILLACTIVE:
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    return 1;
    break;

    case PSN_RESET:
    // reset to the original values
    lstrcpy(setupInfo.pszDestPath, TEXT(""));
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    break;

     case PSN_SETACTIVE:
        PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK |PSWIZB_NEXT);
    SendMessage(GetDlgItem(hDlg,0x3024 ), BM_SETSTYLE, (WPARAM)BS_PUSHBUTTON, MAKELONG(FALSE, 0));
    SendMessage(GetDlgItem(hDlg, IDE_PATH), WM_SETTEXT, 0, (LPARAM)setupInfo.pszDestPath);
    break;

                case PSN_WIZBACK:
                    break;

                case PSN_WIZNEXT:
    // the Next button was pressed
     SendDlgItemMessage(hDlg, IDE_PATH, WM_GETTEXT, (WPARAM)MAX_PATH, (LPARAM) setupInfo.pszDestPath);
         break;

    default:
    return FALSE;

        }
    break;

    default:
    return FALSE;
    }
    return TRUE;
}
//
//  FUNCTION: Custom_Options (HWND, UINT, UINT, LONG)
//
//  PURPOSE:  Processes messages for "Custom options" page
//
//  MESSAGES:
//
//    WM_INITDIALOG - intializes the page
//    WM_NOTIFY - processes the notifications sent to the page
//    WM_COMMAND - saves the id of the choice selected
//
BOOL APIENTRY Custom_Options(
    HWND hDlg,
    UINT message,
    UINT wParam,
    LONG lParam)
{

    switch (message)
    {
    case WM_INITDIALOG:
            // these are initialized via the install type page
            // so we don't need to initialize anything
    break;

    case WM_COMMAND:
    if (HIWORD(wParam) == BN_CLICKED)
    {
                    if (LOWORD(wParam) == IDC_CUSTOM_OPTION1) {
                        if (setupInfo.iCustom_Options1) {
                             setupInfo.iCustom_Options1 = 0;
                         } else {
                             setupInfo.iCustom_Options1 = 1;
                         }
                     }

                     if (LOWORD(wParam) == IDC_CUSTOM_OPTION2) {
                         if (setupInfo.iCustom_Options2) {
                              setupInfo.iCustom_Options2 = 0;
                          } else {
                              setupInfo.iCustom_Options2 = 1;
                          }
                      }

           if (LOWORD(wParam) == IDC_CUSTOM_OPTION3) {
                         if (setupInfo.iCustom_Options3) {
                              setupInfo.iCustom_Options3 = 0;
                         } else {
                              setupInfo.iCustom_Options3 = 1;
                         }
                      }

          if (LOWORD(wParam) == IDC_CUSTOM_OPTION4) {
                          if (setupInfo.iCustom_Options4) {
                               setupInfo.iCustom_Options4 = 0;
                          } else {
                               setupInfo.iCustom_Options4 = 1;
                      }
                   }
    }
    break;

    case WM_NOTIFY:
        switch (((NMHDR FAR *) lParam)->code)
        {

      case PSN_KILLACTIVE:
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    return 1;
    break;

    case PSN_RESET:
    // rest to the original values
    setupInfo.iCustom_Options1 = 0;
    setupInfo.iCustom_Options2 = 0;
    setupInfo.iCustom_Options3 = 0;
    setupInfo.iCustom_Options4 = 0;
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    break;

     case PSN_SETACTIVE:
    CheckDlgButton (hDlg, IDC_CUSTOM_OPTION1,
                        setupInfo.iCustom_Options1);
    CheckDlgButton (hDlg, IDC_CUSTOM_OPTION2,
                        setupInfo.iCustom_Options2);
    CheckDlgButton (hDlg, IDC_CUSTOM_OPTION3,
                        setupInfo.iCustom_Options3);
    CheckDlgButton (hDlg, IDC_CUSTOM_OPTION4,
                        setupInfo.iCustom_Options4);
     PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK | PSWIZB_NEXT);
    break;

                case PSN_WIZBACK:
                    break;

                case PSN_WIZNEXT:
                    break;

                default:
                    return FALSE;

        }
    break;

    default:
    return FALSE;
    }
    return TRUE;
}
//
//  FUNCTION: Install(HWND, UINT, UINT, LONG)
//
//  PURPOSE:  Processes messages for "Installation" page
//
//  MESSAGES:
//
//    WM_INITDIALOG - intializes the page
//    WM_NOTIFY - processes the notifications sent to the page
//    WM_COMMAND - saves the id of the choice selected
//
//
BOOL APIENTRY Install(
    HWND hDlg,
    UINT message,
    UINT wParam,
    LONG lParam)
{

    switch (message)
    {
    case WM_INITDIALOG:
    setupInfo.iInstall = 0;
    break;

    case WM_NOTIFY:
        switch (((NMHDR FAR *) lParam)->code)
        {
      case PSN_KILLACTIVE:
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    return 1;
    break;

    case PSN_RESET:
    // rest to the original values
    setupInfo.iInstall = 0;
               SetWindowLong(hDlg,DWL_MSGRESULT, FALSE);
    break;

     case PSN_SETACTIVE:
    PropSheet_SetWizButtons(GetParent(hDlg), PSWIZB_BACK | PSWIZB_FINISH);
    break;

                case PSN_WIZBACK:
                    break;

                case PSN_WIZFINISH:
                    // They finished the wizard, now do
                    // what they said
                    break;

                default:
                    return FALSE;
        }
    break;

    default:
    return FALSE;
    }
    return TRUE;
}


INSTWIZ.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

//////////////////////////////
// resource definitions

#include "resource.h"

//
// constants
//
#define NUM_PAGES    6
#define MAX_BUF    5000
#define MAX_LINE    512

//
// Pages for INSTALL
//
BOOL APIENTRY Welcome(HWND, UINT, UINT, LONG);
BOOL APIENTRY License(HWND, UINT, UINT, LONG);
BOOL APIENTRY YourInfo(HWND, UINT, UINT, LONG);
BOOL APIENTRY Install_Type(HWND, UINT, UINT, LONG);
BOOL APIENTRY Install_Destination(HWND, UINT, UINT, LONG);
BOOL APIENTRY Specify_Location(HWND, UINT, UINT, LONG);
BOOL APIENTRY Custom_Options(HWND, UINT, UINT, LONG);
BOOL APIENTRY Install(HWND, UINT, UINT, LONG);

//
//functions
//
int CreateWizard(HWND, HINSTANCE);
void FillInPropertyPage( PROPSHEETPAGE* , int, LPSTR, DLGPROC);


RESOURCE.H

/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1998 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

// Used by infinst.RC
//
#define IDS_LICENSE_ACCEPT              1
#define IDC_LICENSE_ACCEPT              101
#define IDS_LICENSE_DECLINE             2
#define IDC_LICENSE_DECLINE             102
#define IDS_CUSTOM_OPTION1              5
#define IDS_CUSTOM_OPTION2              6
#define IDS_CUSTOM_OPTION3              7
#define IDS_CUSTOM_OPTION4              8
#define IDC_CUSTOM_OPTION1              9
#define IDC_CUSTOM_OPTION2              10
#define IDC_CUSTOM_OPTION3              11
#define IDC_CUSTOM_OPTION4              12
#define IDS_INSTALL_TYPE                17
#define IDS_INSTALL_TYPE2               18
#define IDS_INSTALL_TYPE3               19
#define IDS_INSTALL_TYPE4               20
#define IDD_INSTALL                     101
#define IDD_WELCOME                     102
#define IDD_CUSTOM_OPTIONS              103
#define IDD_LICENSE                     104
#define IDD_INSTALL_TYPE                105
#define IDD_INFO                        106
#define IDD_SPECITY_LOCATION            107
#define IDD_INSTALL_DESTINATION         107
#define IDC_MUST_AGREE                  999
#define LICENSE_TEXT                    1010
#define LICENSE_BOTTOM1                 1011
#define LICENSE_BOTTOM2                 1012
#define LICENSE_TOP1                    1013
#define LICENSE_TOP2                    1014
#define IDC_INSTALL_TYPE1               1016
#define IDC_INSTALL_TYPE2               1017
#define IDC_INSTALL_TYPE3               1018
#define IDC_INSTALL_TYPE4               1019
#define IDE_NAME                        1020
#define IDE_TITLE                       1021
#define IDE_COMPANY                     1021
#define IDE_PROJECT                     1022
#define IDE_PRODUCT_ID                  1022
#define IDE_DEPARTMENT                  1023
#define IDE_EMAIL                       1023
#define IDE_PATH                        1024
#define IDC_INSTALL_TYPE_UNINSTALL      1024
#define IDC_INSTALL_TYPE_MIN            1025
#define IDC_INSTALL_TYPE_CUSTOM         1026
#define IDC_INSTALL_TYPE_NORMAL         1027
#define IDC_STATIC                      -1


README.TXT

SMS Demonstration database
--------------------------

The files smsdmp.dat and sms.sql are used to create a sample database
in Systems Management Server (SMS). These files are compatible with SMS 1.2.

Documentation on the import and use of this database is contained
in the SMS part of the BackOffice documentation.


BROWSER.C

//=============================================================================
//  MODULE: browser.c
//
//  Description:
//  Bloodhound parser DLL for MS Browser
//
//  Modification History
//  Written by Scott Jiles with help from Stevehi 6/94
//  7/5/94   Fix Periodicity time to zero values when needed.
//  7/10/94  Fix Server Type bits.
//
//=============================================================================

#include "browser.h"

HPROTOCOL hBrowser = NULL;

typedef VOID (WINAPIV *FORMATPROC)(LPPROPERTYINST);
VOID WINAPI HandleHostAnnounce ( HFRAME hFrame, LPBrowserFRAME BrowserFrame );
VOID WINAPI HandleAnncReq ( HFRAME hFrame, LPBrowserFRAME BrowserFrame );
VOID WINAPI HandleBackReq ( HFRAME hFrame, LPBrowserFRAME BrowserFrame );
VOID WINAPI HandleLocMstrAnnc ( HFRAME hFrame, LPBrowserFRAME BrowserFrame );
VOID WINAPI HandleBecomeBackup ( HFRAME hFrame, LPBrowserFRAME BrowserFrame );
VOID WINAPI HandleBackResp ( HFRAME hFrame, LPBrowserFRAME BrowserFrame );
VOID WINAPI HandleMasterAnnc ( HFRAME hFrame, LPBrowserFRAME BrowserFrame );
VOID WINAPIV FormatPeriodicity(LPPROPERTYINST lpPropertyInst );
VOID WINAPI HandleElection ( HFRAME hFrame, LPBrowserFRAME BrowserFrame, DWORD BytesLeft );
VOID WINAPIV FormatUpTime(LPPROPERTYINST lpPropertyInst );
VOID WINAPI HandleReset ( HFRAME hFrame, LPBrowserFRAME BrowserFrame );
VOID WINAPI HandleWkGrpAnnc ( HFRAME hFrame, LPBrowserFRAME BrowserFrame );

//=============================================================================
//  Labeled Browser command set.
//=============================================================================
LABELED_BYTE Commands[] =
{
    // first name is defined in .h file.  string will be used to describe
    // command.
    { HostAnnouncement,        "Host Announcement [0x01]"},
    { AnnouncementRequest,     "Announcement Request [0x02]"},
    { Election,                "Election [0x08]"},
    { GetBackupListReq,        "Get Backup List Request [0x09]" },
    { GetBackupListResp,       "Get Backup List Response [0x0a]" },
    { BecomeBackup,            "Become Backup [0x0b]" },
    { WkGroupAnnouncement,     "Workgroup Announcement [0x0c]" },
    { MasterAnnouncement,      "Master Announcement [0x0d]" },
    { ResetBrowserState,       "Reset Browser [0x0e]" },
    { LocalMasterAnnouncement, "Local Master Announcement [0x0f]" }
};

#define COMMAND_SET_SIZE    (sizeof Commands / sizeof(LABELED_BYTE))

SET CommandSet = { COMMAND_SET_SIZE, Commands };

//=============================================================================
//  Labeled Server Type bits used in Announces.
//=============================================================================
LABELED_BIT TypeBits[] =
{
  {0, "Not Workstation", "Workstation",},
  {1, "Not Server", "Server",},
  {2, "Not SQL Server", "SQL Server",},
{3, "Not Domain Controller", "Domain Controller",},
{4, "Not Backup Controller", "Backup Controller",},
{5, "Not Time Source", "Time Source Server",},
{6, "Not Apple Server", "Apple Protocol Server",},
{7, "Not Novell", "Novell Server",},
{8, "Not Domain Member Server", "Domain Member Server",},
{9, "Not Print Queue Server", "Print Queue Server",},
{10, "Not Dialin Server", "Dialin Server",},
{11, "Not Xenix Server", "Xenix Server",},
{12, "Not NT System", "Windows NT System",},
{13, "Not WFW System", "Windows for Workgroups",},
{16, "Not Potential Browser", "Potential Browser Server",},
{17, "Not Backup Browser", "Backup Browser Server",},
{18, "Not Master Browser", "Master Browser Server",},
{19, "Not Domain Master Browser", "Domain Master Browser",},
{20, "Not OSF", "OSF",},
{21, "Not VMS", "VMS",},
{30, "Not Local List Only", "Local List Only",},
{31, "Not Domain Enum", "Domain Enum",},
};

SET TypeSet = {sizeof(TypeBits)/sizeof(LABELED_BIT), TypeBits };

//=============================================================================
//  Labeled bits for Native OS type in Election frames.
//=============================================================================
LABELED_BIT OSBits[] =
{
  {0, "Not WFW", "Windows for Workgroups"},
  {1, "Not used", "Not used"},
  {2, "Not used", "Not used"},
  {3, "Not used", "Not used"},
  {4, "Not Windows NT Workstation", "Windows NT Workstation",},
  {5, "Not Advanced Server", "Advanced Server Windows NT",},
{6, "Not used", "Not used"},
{7, "Not used", "Not used"},
};

SET OSSet = {sizeof(OSBits)/sizeof(LABELED_BIT), OSBits };

//=============================================================================
//  Labeled bit set for Election Desire.
//=============================================================================
LABELED_BIT DesireBits[] =
{
  {0, "Not Backup Browse Server", "Currently Backup Browse Server"},
  {1, "Not Standby Browse Server", "Standby Browser Server"},
  {2, "Not Master Browser", "Currently Master Browser Server"},
  {3, "Not Domain Master Browser", "Domain Master Browse Server"},
{4, "Not used", "Not used"},
{5, "Not WINS Client", "Transport Running WINS Client"},
{6, "Not used", "Not used"},
{7, "Not Advanced Server", "Windows NT Advanced Server"},
};

SET DesireSet = {sizeof(DesireBits)/sizeof(LABELED_BIT), DesireBits };

//=============================================================================
//  Labeled bits for Reset option bits.
//=============================================================================
LABELED_BIT ResetBits[] =
{
  {0, "Zero", "Stop Being Browse Master"},
  {1, "Zero", "Reset Entire Browser State"},
  {2, "Zero", "Shutdown Browser"},
};

SET ResetSet = {sizeof(ResetBits)/sizeof(LABELED_BIT), ResetBits };

//=============================================================================
//  Property Table.
//
//  Browser database.
//  The order of the database entry must match the
//property defines in browser.h
//
//=============================================================================
//#define Browser_COMMAND         0
//#define Browser_SUMMARY         1
//#define HA_UpdateCount2
//#define HA_Periodicity3
//#define HA_ServerName4
//#define HA_VersionMajor5
//#define HA_VersionMinor6
//#define HA_Type_Flag_Summary7
//#define HA_Type8
//#define HA_ElectionVersion9
//#define HA_Constant10
//#define AR_Flags11
//#define AR_Reply12
//#define BREQ_RequestedCount13
//#define BREQ_Token14
//#define BRSP_ServerCnt15
//#define BRSP_Token16
//#define BRSP_ServerList17
//#define MA_MasterName18
//#define ELEC_Version19
//#define ELEC_Criteria20
//#define ELEC_TimeUp21
//#define ELEC_MustBeZero22
//#define ELEC_ServerName23
//#define ELEC_OS_Summary24
//#define ELEC_OS25
//#define ELEC_Revision26
//#define ELEC_Desire_Summary27
//#define ELEC_Desire28
//#define RST_Options29
//#define WKGRP_CommentPtr30
//#define WKGRP_Comment31
//#define RST_Options_Summary32
//=============================================================================
PROPERTYINFO BrowserDatabase[] =
{
{   // Browser_COMMAND
0,0,
"Command",
"Browser Command",
PROP_TYPE_BYTE,
PROP_QUAL_LABELED_SET,
&CommandSet,// use this if you have labeled set
64,// avg line length
FormatPropertyInstance},        // generic format routine

    {   // Browser_Summary
0,0,                    // handle, label
"Summary",              // prop name
"Browser Summary",      // status bar message
PROP_TYPE_SUMMARY,      // must have special prop type of summary
PROP_QUAL_LABELED_SET,         // qualifiier
&CommandSet,                      // address of set struct
64,                     // max string size of display set
FormatBrowserSummary},  // instance data - format routine

        {   // HA_UpdateCount
0,0,
"Update Count",
"Incremented when Announce Data Changes.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},        // generic format routine

        {   // HA_Periodicity
0,0,
"Periodicity",
"Announcement cycle in milliseconds.",
PROP_TYPE_DWORD,
PROP_QUAL_NONE,
0,
64,
FormatPeriodicity},

{   // HA_ServerName
0,0,
"Name",
"Name.",
PROP_TYPE_STRING,               // use this prop for char data
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // HA_VersionMajor
0,0,
"Major Version",
"Major Version Number.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // HA_VersionMinor
0,0,
"Minor Version",
"Minor Version Number.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // HA_Type_Flag_Summary
0,0,
"Server Type Summary",
"Server Type Summary.",
PROP_TYPE_DWORD,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // HA_Type
0,0,
"Server Type",
"Server Type.",
PROP_TYPE_DWORD,
PROP_QUAL_FLAGS,
&TypeSet,
64 * 22,           // Must have space for all label bits.
FormatPropertyInstance},

{   // HA_ElectionVersion
0,0,
"Browser Election Version",
"Browser Election Version.",
PROP_TYPE_WORD,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // HA_Constant
0,0,
"Browser Constant",
"Browser Constant.",
PROP_TYPE_WORD,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // AR_Flags
0,0,
"Unused Flags",
"Unused Flags.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // AR_Reply
0,0,
"Reply System Name",
"Send Reply to this name [Not used for Windows NT].",
PROP_TYPE_STRING,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // BREQ_RequestedCount
0,0,
"Get Backup List Requested Count",
"Get Backup List Requested Count.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // BREQ_Token
0,0,
"Backup Request Token",
"Correlates Request with Response.",
PROP_TYPE_DWORD,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // BRSP_ServerCnt
0,0,
"Backup Server Count",
"Number of Backup Servers returned.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // BRSP_Token
0,0,
"Backup Response Token",
"Correlates Request with Response.",
PROP_TYPE_DWORD,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // BRSP_ServerList
0,0,
"Backup Servers",
"List of Backup Servers.",
PROP_TYPE_STRING,
PROP_QUAL_NONE,
0,
100,
FormatPropertyInstance},

{   // MA_MasterName
0,0,
"Master Name",
"Master Annoucement Server Name.",
PROP_TYPE_STRING,
PROP_QUAL_NONE,
0,
100,
FormatPropertyInstance},

{   // ELEC_Version
0,0,
"Election Version",
"Election Version.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // ELEC_Criteria
0,0,
"Election Criteria",
"Election Criteria.",
PROP_TYPE_DWORD,
PROP_QUAL_NONE,
0,
100,
FormatPropertyInstance},

{   // ELEC_TimeUp
0,0,
"Server Up Time [DD:hh:mm:ss] ",
"Amount of Time Server has been Running.",
PROP_TYPE_DWORD,
PROP_QUAL_NONE,
0,
80,
FormatUpTime},

{   // ELEC_MustBeZero
0,0,
"Must be zero",
"Must be zero.",
PROP_TYPE_DWORD,
PROP_QUAL_NONE,
0,
80,
FormatPropertyInstance},

{   // ELEC_ServerName
0,0,
"Election Server Name",
"Election Server Name.",
PROP_TYPE_STRING,
PROP_QUAL_NONE,
0,
80,
FormatPropertyInstance},

{   // ELEC_OS_Summary
0,0,
"Election OS Summary",
"Election OS Summary.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // ELEC_OS
0,0,
"Native OS",
"Native Operating System.",
PROP_TYPE_BYTE,
PROP_QUAL_FLAGS,
&OSSet,
64 * 8,           // Must have space for all label bits.
FormatPropertyInstance},

{   // ELEC_Revision
0,0,
"Election Revision",
"Election Revision.",
PROP_TYPE_WORD,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // ELEC_Desire_Summary
0,0,
"Election Desire Summary",
"Election Desire Summary.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // ELEC_Desire
0,0,
"Election Desire",
"Election Desire.",
PROP_TYPE_BYTE,
PROP_QUAL_FLAGS,
&DesireSet,
64 * 8,           // Must have space for all label bits.
FormatPropertyInstance},

{   // RST_Options
0,0,
"Reset Options",
"Reset Options.",
PROP_TYPE_BYTE,
PROP_QUAL_FLAGS,
&ResetSet,
64 * 4,           // Must have space for all label bits.
FormatPropertyInstance},

{   // WKGRP_CommentPtr
0,0,
"Comment Pointer",
"Comment Pointer.",
PROP_TYPE_DWORD,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // WKGRP_Comment
0,0,
"Server Name",
"Server Source Name.",
PROP_TYPE_STRING,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},

{   // RST_Options_Summary
0,0,
"Options Summary",
"Reset Frame Options Summary.",
PROP_TYPE_BYTE,
PROP_QUAL_NONE,
0,
64,
FormatPropertyInstance},
};

DWORD nBrowserProperties = ((sizeof BrowserDatabase) / PROPERTYINFO_SIZE);

//=============================================================================
//  Protocol entry points.
//=============================================================================
extern VOID   WINAPI BrowserRegister(HPROTOCOL);
extern VOID   WINAPI BrowserDeregister(HPROTOCOL);
extern LPBYTE WINAPI BrowserRecognizeFrame(HFRAME, LPVOID, LPVOID, DWORD, DWORD, HPROTOCOL, DWORD, LPDWORD, LPHPROTOCOL, LPDWORD);
extern LPBYTE WINAPI BrowserAttachProperties(HFRAME, LPVOID, LPVOID, DWORD, DWORD, HPROTOCOL, DWORD, DWORD);
extern DWORD  WINAPI BrowserFormatProperties(HFRAME, LPVOID, LPVOID, DWORD, LPPROPERTYINST);

ENTRYPOINTS BrowserEntryPoints =
{
    BrowserRegister,
    BrowserDeregister,
    BrowserRecognizeFrame,
    BrowserAttachProperties,
    BrowserFormatProperties
};

//=============================================================================
//  FUNCTION: BrowserRegister()
//
//  Modification History
//  Written by Scottji 6/94
//
//=============================================================================
VOID WINAPI BrowserRegister(HPROTOCOL hBrowserProtocol)
{
    register DWORD i;

    //=========================================================================
    //  Create the property database.
    //=========================================================================

    CreatePropertyDatabase(hBrowserProtocol, nBrowserProperties);

    for(i = 0; i < nBrowserProperties; ++i)
    {
AddProperty(hBrowserProtocol, &BrowserDatabase[i]);
    }
}

//=============================================================================
//  FUNCTION: Deregister()
//
//  Modification History
//  Written by Scottji 6/94
//
//=============================================================================
VOID WINAPI BrowserDeregister(HPROTOCOL hBrowserProtocol)
{
    DestroyPropertyDatabase(hBrowserProtocol);
}

//=============================================================================
//  FUNCTION: BrowserRecognizeFrame()
//
//  Modification History
//  Written by Scottji 6/94
//
//=============================================================================
LPBYTE WINAPI BrowserRecognizeFrame(HFRAME          hFrame,                     //... frame handle.
    LPBYTE          MacFrame,                   //... Frame pointer.
    LPBrowserFRAME  BrowserFrame,
    DWORD           MacType,                    //... MAC type.
    DWORD           BytesLeft,                  //... Bytes left.
    HPROTOCOL       hPreviousProtocol,          //... Previous protocol or NULL if none.
    DWORD           nPreviousProtocolOffset,    //... Offset of previous protocol.
    LPDWORD         ProtocolStatusCode,         //... Pointer to return status code in.
    LPHPROTOCOL     hNextProtocol,              //... Next protocol to call (optional).
    LPDWORD         InstData)                   //... Next protocol instance data.
{
    // Will only be called if SMB parser finds Transact
    // with path = \mailslot\browse.

    // Make sure there is some data left.
    if (BytesLeft == 0) {
*ProtocolStatusCode = PROTOCOL_STATUS_NOT_RECOGNIZED;
return (LPBYTE) BrowserFrame;
    }

    // should probably check bytes left for max and min length.
    // need to find max and min length.
    if (BytesLeft < 2 ) { // Reset frame has 2 bytes.
*ProtocolStatusCode = PROTOCOL_STATUS_NOT_RECOGNIZED;
return (LPBYTE) BrowserFrame;
    }

    // Make sure command is between max and min command code.
    if ((BrowserFrame->command >= HostAnnouncement) &
       (BrowserFrame->command <= LocalMasterAnnouncement) ) {
    *ProtocolStatusCode = PROTOCOL_STATUS_CLAIMED;
    return NULL;  //  null means I claim the rest
    }

    #ifdef DEBUG
    DebugBreak();
    #endif

    // should never hit this point.
    *ProtocolStatusCode = PROTOCOL_STATUS_NOT_RECOGNIZED;
    return (LPBYTE) BrowserFrame;

}

//=============================================================================
//  FUNCTION: BrowserAttachProperties()
//
//  Modification History
//  Written by Scottji 6/94
//
//=============================================================================
LPBYTE WINAPI BrowserAttachProperties(HFRAME          hFrame,
      LPBYTE          Frame,
      LPBrowserFRAME  BrowserFrame,
      DWORD           MacType,
      DWORD           BytesLeft,
      HPROTOCOL       hPreviousProtocol,
      DWORD           nPreviousProtocolOffset,
      DWORD           InstData)
{

    //=========================================================================
    //  Attach Summary.
    //=========================================================================

    AttachPropertyInstance(hFrame,
   BrowserDatabase[Browser_SUMMARY].hProperty,
   BytesLeft,   // claim the remaining bytes
   BrowserFrame,
   0, 0, 0);    // Summary should be 0.

    //=========================================================================
    //  Attach Command.
    //=========================================================================

    AttachPropertyInstance(hFrame,
   BrowserDatabase[Browser_COMMAND].hProperty,
   sizeof(BYTE),
   BrowserFrame,
   0, 1, 0);  // Detail other than 0 for drill down.

    switch( BrowserFrame->command )
    {
case HostAnnouncement:
    HandleHostAnnounce ( hFrame, BrowserFrame );
    break;

case AnnouncementRequest:
    HandleAnncReq ( hFrame, BrowserFrame );
    break;

case Election:
    HandleElection ( hFrame, BrowserFrame, BytesLeft);
    break;

case GetBackupListReq:
    HandleBackReq ( hFrame, BrowserFrame );
    break;

case GetBackupListResp:
    HandleBackResp ( hFrame, BrowserFrame );
    break;

case BecomeBackup:
    HandleBecomeBackup ( hFrame, BrowserFrame );
    break;

case WkGroupAnnouncement:
    HandleWkGrpAnnc ( hFrame, BrowserFrame );
    break;

case MasterAnnouncement:
    HandleMasterAnnc ( hFrame, BrowserFrame );
    break;

case ResetBrowserState:
    HandleReset ( hFrame, BrowserFrame );
    break;

case LocalMasterAnnouncement:
    HandleLocMstrAnnc ( hFrame, BrowserFrame );
    break;

default:
    // CommandString = "";
    break;
    }
    return NULL;  //must return null from attachproperies.
}

///////////////////////////////////////////////////////////////////////////
//
// FormatProperties (Bloodhound parser DLL entrypoint)
//
///////////////////////////////////////////////////////////////////////////
DWORD WINAPI BrowserFormatProperties(HFRAME hFrame,
     LPBYTE MacFrame,
     LPBYTE BrowserFrame,
     DWORD  nPropertyInsts,
     LPPROPERTYINST p)
{
    while(nPropertyInsts--)
    {
if (p->lpPropertyInfo->InstanceData != NULL )
    ((FORMATPROC)p->lpPropertyInfo->InstanceData)(p);
p++;
    }
    return BHERR_SUCCESS;
}

//==============================================================================
//  FUNCTION: FormatBrowserSummary()
//
//  Modification History
//  Scottji             6/94        Created
//  Tom Laird-McConnell 07/20/94    Modified to have more info on summary line
//  Scottji             07/25/94    Modified Election. Pass BytesLeft.  Add Force.
//  Tom Laird-McConnell 08/31/94    Converted strings to ANSI
//==============================================================================
VOID WINAPIV FormatBrowserSummary(LPPROPERTYINST lpPropertyInst )
{
    LPBrowserFRAME BrowserFrame;
//    CHAR * CommandString;
    char szAnsiName[255];

    BrowserFrame = (LPBrowserFRAME) lpPropertyInst->lpData;

//    CommandString = (char *) malloc (sizeof(char) * 80);

switch( BrowserFrame->command )
{
    case HostAnnouncement:
        {
            LP_HA_FRAME HA_Frame = (LP_HA_FRAME) BrowserFrame;

            // convert string to ANSI
            OemToChar( (LPCSTR)&HA_Frame->ServerName, szAnsiName);

            wsprintf(lpPropertyInst->szPropertyText, "%s %s", Commands[0].Label, szAnsiName);
    break;
        }

    case AnnouncementRequest:
        {
            LP_ANNC_REQ AR_Frame = (LP_ANNC_REQ) BrowserFrame;

            wsprintf(lpPropertyInst->szPropertyText, Commands[1].Label);
    break;
        }

    case Election:
        {
            LP_ELECTION ELEC_Frame = (LP_ELECTION) BrowserFrame;

            if (ELEC_Frame->Criteria == (ULONG) NULL ) {
                wsprintf(lpPropertyInst->szPropertyText, "%s [Force]", Commands[2].Label);
            }
            else
            {
                // convert string to ANSI
                OemToChar( (LPCSTR)&ELEC_Frame->ServerName, szAnsiName);

                wsprintf(lpPropertyInst->szPropertyText, "%s %s", Commands[2].Label, szAnsiName);
            }
    break;
        }

    case GetBackupListReq:
            wsprintf(lpPropertyInst->szPropertyText, Commands[3].Label);
    break;

    case GetBackupListResp:
        {
            LP_BACK_RESP BRSP_Frame = (LP_BACK_RESP) BrowserFrame;

            wsprintf(lpPropertyInst->szPropertyText, "%s %d Servers", Commands[4].Label, BRSP_Frame->BackupServerCount);
    break;
        }

    case BecomeBackup:
        {
            LP_BECOME_BACKUP BB_Frame = (LP_BECOME_BACKUP) BrowserFrame;

            // convert string to ANSI
            OemToChar( (LPCSTR)&BB_Frame->BrowserToPromote, szAnsiName);

            wsprintf(lpPropertyInst->szPropertyText, "%s Name = %s",  Commands[5].Label, szAnsiName);
    break;
        }

    case WkGroupAnnouncement:
        {
            LP_WKGRP_FRAME WKGRP_Frame = (LP_WKGRP_FRAME) BrowserFrame;

            // convert string to ANSI
            OemToChar( (LPCSTR)&WKGRP_Frame->ServerName, szAnsiName);

            wsprintf(lpPropertyInst->szPropertyText, "%s %s", Commands[6].Label, szAnsiName);
    break;
        }

    case MasterAnnouncement:
        {
            LP_MASTER_ANNC MA_Frame = (LP_MASTER_ANNC) BrowserFrame;

            // convert string to ANSI
            OemToChar( (LPCSTR)&MA_Frame->MasterName, szAnsiName);

            wsprintf(lpPropertyInst->szPropertyText, "%s %s", Commands[7].Label, szAnsiName);
    break;
        }

    case ResetBrowserState:
            wsprintf(lpPropertyInst->szPropertyText, Commands[8].Label);
    break;

    case LocalMasterAnnouncement:
        {
            LP_HA_FRAME HA_Frame = (LP_HA_FRAME) BrowserFrame;

            // convert string to ANSI
            OemToChar( (LPCSTR)&HA_Frame->ServerName, szAnsiName);

            wsprintf(lpPropertyInst->szPropertyText, "%s %s", Commands[9].Label, szAnsiName);
    break;
        }

    default:
            wsprintf(lpPropertyInst->szPropertyText, "Unknown Command?");
    break;
}
}

//==============================================================================
//  FUNCTION: HandleHostAnnouce()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPI HandleHostAnnounce ( HFRAME hFrame, LPBrowserFRAME BrowserFrame )
{
    LP_HA_FRAME HA_Frame;

    // HA Update Count
    HA_Frame = (LP_HA_FRAME) BrowserFrame;
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_UpdateCount].hProperty,
   sizeof(BYTE),
   &HA_Frame->UpdateCount,
   0, 1, 0);
    // Periodicity;     announcement cycle in milliseconds
    HA_Frame = (LP_HA_FRAME) BrowserFrame;
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Periodicity].hProperty,
   sizeof(ULONG),
   &HA_Frame->Periodicity,
   0, 1, 0);
    // HA_ServerName
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_ServerName].hProperty,
   LM20_CNLEN+1,
   &HA_Frame->ServerName,
   0, 1, 0);
    // HA_VersionMajor
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_VersionMajor].hProperty,
   sizeof(BYTE),
   &HA_Frame->VersionMajor,
   0, 1, 0);
    // HA_VersionMinor
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_VersionMinor].hProperty,
   sizeof(BYTE),
   &HA_Frame->VersionMinor,
   0, 1, 0);
    // HA_Type_Flag_Summary
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Type_Flag_Summary].hProperty,
   sizeof(ULONG),
   &HA_Frame->Type,
   0, 1, 0);
    // HA_Type
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Type].hProperty,
   sizeof(ULONG),
   &HA_Frame->Type,
   0, 2, 0);
    // HA_ElectionVersion
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_ElectionVersion].hProperty,
   sizeof(WORD),
   &HA_Frame->HAElectionVersion,
   0, 1, 0);
    // HA_Constant
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Constant].hProperty,
   sizeof(WORD),
   &HA_Frame->HAConstant,
   0, 1, 0);
}

//==============================================================================
//  FUNCTION: HandleAnncReq()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================

VOID WINAPI HandleAnncReq ( HFRAME hFrame, LPBrowserFRAME BrowserFrame )
{
    LP_ANNC_REQ AR_Frame;
    int len;

    AR_Frame = (LP_ANNC_REQ) BrowserFrame;

    // AR_Flags
    AttachPropertyInstance(hFrame,
   BrowserDatabase[AR_Flags].hProperty,
   sizeof(UCHAR),
   &AR_Frame->Flags,
   0, 1, 0);
    len = strlen ( &AR_Frame->Reply[0] );
    // AR_Reply
    AttachPropertyInstance(hFrame,
   BrowserDatabase[AR_Reply].hProperty,
   len,
   &AR_Frame->Reply,
   0, 1, 0);
}

//==============================================================================
//  FUNCTION: HandleBackReq()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPI HandleBackReq ( HFRAME hFrame, LPBrowserFRAME BrowserFrame )
{
    LP_BACK_REQ_FRAME BREQ_Frame;

    BREQ_Frame = (LP_BACK_REQ_FRAME) BrowserFrame;

    // BREQ_RequestedCount
    AttachPropertyInstance(hFrame,
   BrowserDatabase[BREQ_RequestedCount].hProperty,
   sizeof(UCHAR),
   &BREQ_Frame->RequestedCount,
   0, 1, 0);
    // BREQ_Token
    AttachPropertyInstance(hFrame,
   BrowserDatabase[BREQ_Token].hProperty,
   sizeof(LONG),
   &BREQ_Frame->Token,
   0, 1, 0);
}

//==============================================================================
//  FUNCTION: HandleLocMstrAnnc()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPI HandleLocMstrAnnc ( HFRAME hFrame, LPBrowserFRAME BrowserFrame )
{
    LP_HA_FRAME HA_Frame;

    //=========================================================================
    //  Same as Host Announcement frame.
    //=========================================================================

    // HA Update Count
    HA_Frame = (LP_HA_FRAME) BrowserFrame;
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_UpdateCount].hProperty,
   sizeof(BYTE),

&HA_Frame->UpdateCount,
   0, 1, 0);
    // Periodicity;     announcement cycle in milliseconds

    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Periodicity].hProperty,
   sizeof(ULONG),
   &HA_Frame->Periodicity,
   0, 1, 0);
    // HA_ServerName
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_ServerName].hProperty,
   LM20_CNLEN+1,
   &HA_Frame->ServerName,
   0, 1, 0);
    // HA_VersionMajor
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_VersionMajor].hProperty,
   sizeof(BYTE),
   &HA_Frame->VersionMajor,
   0, 1, 0);
    // HA_VersionMinor
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_VersionMinor].hProperty,
   sizeof(BYTE),
   &HA_Frame->VersionMinor,
   0, 1, 0);
    // HA_Type_Flag_Summary
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Type_Flag_Summary].hProperty,
   sizeof(ULONG),
   &HA_Frame->Type,
   0, 1, 0);
    // HA_Type
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Type].hProperty,
   sizeof(ULONG),
   &HA_Frame->Type,
   0, 2, 0);
    // HA_ElectionVersion
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_ElectionVersion].hProperty,
   sizeof(WORD),
   &HA_Frame->HAElectionVersion,
   0, 1, 0);
    // HA_Constant
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Constant].hProperty,
   sizeof(WORD),
   &HA_Frame->HAConstant,
   0, 1, 0);
}

//==============================================================================
//  FUNCTION: HandleBecomeBackup()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPI HandleBecomeBackup ( HFRAME hFrame, LPBrowserFRAME BrowserFrame )
{
    LP_BECOME_BACKUP BB_Frame;
    int len;

    BB_Frame = (LP_BECOME_BACKUP) BrowserFrame;

    // BrowserToPromote = HA_ServerName
    len = strlen ( &BB_Frame->BrowserToPromote[0] );
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_ServerName].hProperty,
   len,
   &BB_Frame->BrowserToPromote,
   0, 1, 0);
}

//==============================================================================
//  FUNCTION: HandleBackResp()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPI HandleBackResp ( HFRAME hFrame, LPBrowserFRAME BrowserFrame )
{
    LP_BACK_RESP BRSP_Frame;
    int i, k, len, offset;

    BRSP_Frame = (LP_BACK_RESP) BrowserFrame;
    i = (int)BRSP_Frame->BackupServerCount;

    // BRSP_ServerCnt
    AttachPropertyInstance(hFrame,
   BrowserDatabase[BRSP_ServerCnt].hProperty,
   sizeof(UCHAR),
   &BRSP_Frame->BackupServerCount,
   0, 1, 0);
    // BRSP_Token
    AttachPropertyInstance(hFrame,
   BrowserDatabase[BRSP_Token].hProperty,
   sizeof(ULONG),
   &BRSP_Frame->Token,
   0, 1, 0);

    for (k = 1, offset = 0 ; k <= i; k++) {
        len = strlen ( &BRSP_Frame->BackupServerList[offset] );
        // BRSP_ServerList
        AttachPropertyInstance(hFrame,
   BrowserDatabase[BRSP_ServerList].hProperty,
   len,
   &BRSP_Frame->BackupServerList[offset],
   0, 1, 0);
        offset = offset + 1 + strlen( (UCHAR *) &BRSP_Frame->BackupServerList[offset] );
    }
}

//==============================================================================
//  FUNCTION: HandleMasterAnnc()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPI HandleMasterAnnc ( HFRAME hFrame, LPBrowserFRAME BrowserFrame )
{
    LP_MASTER_ANNC MA_Frame;
    int len;

    MA_Frame = (LP_MASTER_ANNC) BrowserFrame;
    len = strlen ( &MA_Frame->MasterName[0] );

    // MA_MasterName
    AttachPropertyInstance(hFrame,
   BrowserDatabase[MA_MasterName].hProperty,
   len,
   &MA_Frame->MasterName,
   0, 1, 0);
}

//==============================================================================
//  FUNCTION: FormatPeriodicity()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPIV FormatPeriodicity(LPPROPERTYINST lpPropertyInst )
{
    ULPDWORD period;
    ULONG value;
    char CommandString[80];

    period =  (ULPDWORD)lpPropertyInst->lpData;
    value = (((ULONG)* period) / 1000) /60 ;
    _ultoa (value, CommandString, 10);
    strcpy (lpPropertyInst->szPropertyText, "Annoucement Interval [minutes] = ");
    strcat ( lpPropertyInst->szPropertyText, CommandString );
}

//==============================================================================
//  FUNCTION: HandleElection()
//
//  Modification History
//  Written by Scottji 6/94
//  Scottji             07/25/94    Modified Election. Add Force.
//                                  Pass BytesLeft.  Check length before attach.
//
//==============================================================================
VOID WINAPI HandleElection ( HFRAME hFrame, LPBrowserFRAME BrowserFrame, DWORD BytesLeft )
{
    LP_ELECTION ELEC_Frame;
    int len;
    char *p;

    ELEC_Frame = (LP_ELECTION) BrowserFrame;
    len = strlen ( &ELEC_Frame->ServerName[0] );

    if (BytesLeft >= 2 ) {
        // ELEC_Version
        AttachPropertyInstance(hFrame,
   BrowserDatabase[ELEC_Version].hProperty,
   sizeof(BYTE),
   &ELEC_Frame->Version,
   0, 1, 0);
    }

    if (BytesLeft >= 6  ) {
        // ELEC_Criteria
        AttachPropertyInstance(hFrame,
   BrowserDatabase[ELEC_Criteria].hProperty,
   sizeof(DWORD),
   &ELEC_Frame->Criteria,
   0, 1, 0);

        p = (char *) &ELEC_Frame->Criteria;
        // ELEC_OS_Summary
        AttachPropertyInstance(hFrame,
   BrowserDatabase[ELEC_OS_Summary].hProperty,
   sizeof(BYTE),
   p+3,
   0, 2, 0);
        // ELEC_OS
        AttachPropertyInstance(hFrame,
   BrowserDatabase[ELEC_OS].hProperty,
   sizeof(BYTE),
   p+3,
   0, 3, 0);
        // ELEC_Revision
        AttachPropertyInstance(hFrame,
   BrowserDatabase[ELEC_Revision].hProperty,
   sizeof(WORD),
   p+1,
   0, 2, 0);
        // ELEC_Desire_Summary
        AttachPropertyInstance(hFrame,
   BrowserDatabase[ELEC_Desire_Summary].hProperty,
   sizeof(BYTE),
   p,
   0, 2, 0);

        // ELEC_Desire
        AttachPropertyInstance(hFrame,
   BrowserDatabase[ELEC_Desire].hProperty,
   sizeof(BYTE),
   p,
   0, 3, 0);
    }

    if (BytesLeft >= 10 ) {
        // ELEC_TimeUp
        AttachPropertyInstance(hFrame,
   BrowserDatabase[ELEC_TimeUp].hProperty,
   sizeof(DWORD),
   &ELEC_Frame->TimeUp,
   0, 1, 0);
    }

    if ((BytesLeft > 14 ) & (BytesLeft <50 ) ) {
    // ELEC_ServerName
    AttachPropertyInstance(hFrame,
   BrowserDatabase[ELEC_ServerName].hProperty,
   len,
   &ELEC_Frame->ServerName,
   0, 1, 0);
    }
}

//==============================================================================
//  FUNCTION: FormatUpTime()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPIV FormatUpTime(LPPROPERTYINST lpPropertyInst )
{
    ULPDWORD value;
    ULONG time, days, hours, minutes, seconds;
    char CommandString[80];
    ULONG ONEDAY, ONEHOUR, ONEMINUTE;

    ONEDAY =   86400000;
    ONEHOUR =  3600000;
    ONEMINUTE =  60000;
    value =  (ULPDWORD)lpPropertyInst->lpData;
    time = (ULONG) *value;

    if (time >= ONEDAY) {
        days = time / ONEDAY;
        time = time - (days * ONEDAY);
    }
    else {
        days = 0;
    }
    if (time >= ONEHOUR){
        hours = time / ONEHOUR;
        time = time - (hours * ONEHOUR);
    }
    else {
        hours = 0;
    }
    if (time >= ONEMINUTE) {
        minutes = time / ONEMINUTE;
        time = time - (minutes * ONEMINUTE);
    }
    else {
        minutes = 0;
    }
    if(seconds != 0) {
        seconds = time / 1000;
    }
    else {
        seconds = 0;
    }

    strcpy (lpPropertyInst->szPropertyText, "Server Up Time [DD:hh:mm:ss] ");
    if( (ULONG)*value != 0 ) {
    _ultoa (days, CommandString, 10);
    strcat (lpPropertyInst->szPropertyText, CommandString );
    strcat (lpPropertyInst->szPropertyText, ":");
    _ultoa (hours, CommandString, 10);
    strcat (lpPropertyInst->szPropertyText, CommandString );
    strcat (lpPropertyInst->szPropertyText, ":");
    _ultoa (minutes, CommandString, 10);
    strcat (lpPropertyInst->szPropertyText, CommandString );
    strcat (lpPropertyInst->szPropertyText, ":");
    _ultoa (seconds, CommandString, 10);
    strcat (lpPropertyInst->szPropertyText, CommandString );
    }
    else {
    strcat (lpPropertyInst->szPropertyText, "Null");
 }
}

//==============================================================================
//  FUNCTION: HandleReset()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPI HandleReset ( HFRAME hFrame, LPBrowserFRAME BrowserFrame )
{
    LP_RESET RST_Frame;

    RST_Frame = (LP_RESET) BrowserFrame;

    // RST_Options_Summary
    AttachPropertyInstance(hFrame,
   BrowserDatabase[RST_Options_Summary].hProperty,
   sizeof(BYTE),
   &RST_Frame->Options,
   0, 1, 0);
    // RST_Options
    AttachPropertyInstance(hFrame,
   BrowserDatabase[RST_Options].hProperty,
   sizeof(BYTE),
   &RST_Frame->Options,
   0, 2, 0);
}

//==============================================================================
//  FUNCTION: HandleWkGrpAnnc()
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==============================================================================
VOID WINAPI HandleWkGrpAnnc ( HFRAME hFrame, LPBrowserFRAME BrowserFrame )
{
    LP_WKGRP_FRAME WKGRP_Frame;
    int len;

    //=========================================================================
    //  Same as Host Announcement frame.
    //=========================================================================

    WKGRP_Frame = (LP_WKGRP_FRAME) BrowserFrame;
    //  Update Count

    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_UpdateCount].hProperty,
   sizeof(BYTE),
   &WKGRP_Frame->UpdateCount,
   0, 1, 0);
    // Periodicity;     announcement cycle in milliseconds
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Periodicity].hProperty,
   sizeof(ULONG),
   &WKGRP_Frame->Periodicity,
   0, 1, 0);
    // ServerName
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_ServerName].hProperty,
   LM20_CNLEN+1,
   &WKGRP_Frame->ServerName,
   0, 1, 0);
    // VersionMajor
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_VersionMajor].hProperty,
   sizeof(BYTE),
   &WKGRP_Frame->VersionMajor,
   0, 1, 0);
    // VersionMinor
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_VersionMinor].hProperty,
   sizeof(BYTE),
   &WKGRP_Frame->VersionMinor,
   0, 1, 0);
    // Type_Flag_Summary
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Type_Flag_Summary].hProperty,
   sizeof(ULONG),
   &WKGRP_Frame->Type,
   0, 1, 0);
    // Type
    AttachPropertyInstance(hFrame,
   BrowserDatabase[HA_Type].hProperty,
   sizeof(ULONG),
   &WKGRP_Frame->Type,
   0, 2, 0);
    // WKGRP_CommentPtr
    AttachPropertyInstance(hFrame,
   BrowserDatabase[WKGRP_CommentPtr].hProperty,
   sizeof(DWORD),
   &WKGRP_Frame->CommentPtr,
   0, 1, 0);
    len = strlen ( &WKGRP_Frame->Comment[0] );
    // WKGRP_Comment
    AttachPropertyInstance(hFrame,
   BrowserDatabase[WKGRP_Comment].hProperty,
   len,
   &WKGRP_Frame->Comment,
   0, 1, 0);
}


BROWSER.H

//==========================================================================================================================
//  MODULE: browser.h
//
//  Description:
//
//  Bloodhound parser DLL for MS Browser
//
//  Modification History
//  Written by Scottji 6/94
//
//
//==========================================================================================================================

// question:  need to look at bh.h and inilib.h
#include <windows.h>
#include <string.h>
#include <bh.h>
#include <parser.h>
#include <inilib.h>
#include <malloc.h>
#include <stdlib.h>

//question:
#pragma pack(1)

//
// Browser Commands
//
#define HostAnnouncement1   //  done
#define AnnouncementRequest2   //  done
#define Election8   //
#define GetBackupListReq9   //  done
#define GetBackupListResp10  //a done
#define BecomeBackup11  //b done
#define WkGroupAnnouncement12  //c change
#define MasterAnnouncement13  //d done
#define ResetBrowserState14  //e
#define LocalMasterAnnouncement15  //f done

//
// Struct for Recognize Function
//
typedef struct _BrowserFRAME
{
    BYTE        command;
    CHAR        data;
} BrowserFRAME;

typedef BrowserFRAME UNALIGNED *LPBrowserFRAME;

//
// Lanman names from lmcons.h
//
#define CNLEN       15                  // Computer name length
#define LM20_CNLEN  15                  // LM 2.0 Computer name length
#define DNLEN       CNLEN               // Maximum domain name length
#define LM20_DNLEN  LM20_CNLEN          // LM 2.0 Maximum domain name length

//
// Mailslot name strings.
//
#define SMB_MAILSLOT_PREFIX "\\MAILSLOT"
#define UNICODE_SMB_MAILSLOT_PREFIX L"\\MAILSLOT"
#define SMB_MAILSLOT_PREFIX_LENGTH (sizeof(SMB_MAILSLOT_PREFIX) - 1)
#define UNICODE_SMB_MAILSLOT_PREFIX_LENGTH \
                    (sizeof(UNICODE_SMB_MAILSLOT_PREFIX) - sizeof(WCHAR))

#define MAILSLOT_LANMAN_NAME SMB_MAILSLOT_PREFIX "\\LANMAN"
#define MAILSLOT_BROWSER_NAME SMB_MAILSLOT_PREFIX "\\BROWSE"
//#define ANNOUNCEMENT_MAILSLOT_NAME     "\\\\*" ITRANS_MS_NAME
#define MAXCOMMENTSZ 256                // Multipurpose comment length
#define LM20_MAXCOMMENTSZ 48            // LM 2.0 Multipurpose comment length

//
//Browser netbios name extensions.
//
#define WORKSTATION_SIGNATURE       '\0'
#define SERVER_SIGNATURE            ' '
#define PRIMARY_DOMAIN_SIGNATURE    '\0'
#define PRIMARY_CONTROLLER_SIGNATURE '\x1B'
#define DOMAIN_CONTROLLER_SIGNATURE '\x1C'
#define MASTER_BROWSER_SIGNATURE    '\x1D'
#define BROWSER_ELECTION_SIGNATURE  '\x1E'
#define DOMAIN_ANNOUNCEMENT_NAME    "\x01\x02__MSBROWSE__\x02\x01"

//
//  AnnouncementRequest
//
//  The request announcement packet is sent by clients to request that
//  remote servers announce themselves.
//

typedef struct _REQUEST_ANNOUNCE_PACKET_1 {      // Contents of request announcement
    UCHAR    Type;
    UCHAR    Flags;                 // Unused Flags
    CHAR     Reply[LM20_CNLEN+1];
}  REQUEST_ANNOUNCE_PACKET_1, UNALIGNED *LP_ANNC_REQ;

//typedef REQUEST_ANNOUNCE_PACKET_1 UNALIGNED *LP_ANNC_REQ;

//  HostAnnouncement and LocalMasterAnnouncement
//
// General announcement message.  This is used for opcodes:
//
//

typedef struct _BROWSE_ANNOUNCE_PACKET_1 {
    UCHAR       BrowseType;
    UCHAR       UpdateCount;    // Inc'ed when announce data changed.
    ULONG       Periodicity;    // announcement cycle in milliseconds

    UCHAR       ServerName[LM20_CNLEN+1];
    UCHAR       VersionMajor;
    UCHAR       VersionMinor;   /*  "  "   "   "    "    "    "   */
    ULONG       Type;           // Server type.
    WORD        HAElectionVersion;
    WORDHAConstant;
    CHAR        Comment[LM20_MAXCOMMENTSZ+1];
} BROWSE_ANNOUNCE_PACKET, UNALIGNED *LP_HA_FRAME;

// WkGroupAnnouncement
//
//
typedef struct _WKGRP_ANNOUNCE_PACKET_1 {
    UCHAR       BrowseType;
    UCHAR       UpdateCount;    // Inc'ed when announce data changed.
    ULONG       Periodicity;    // announcement cycle in milliseconds

    UCHAR       ServerName[LM20_CNLEN+1];
    UCHAR       VersionMajor;
    UCHAR       VersionMinor;   /*  "  "   "   "    "    "    "   */
    ULONG       Type;           // Server type.
    ULONG       CommentPtr;
    CHAR        Comment[LM20_MAXCOMMENTSZ+1];
} WKGRP_ANNOUNCE_PACKET, UNALIGNED *LP_WKGRP_FRAME;

// GetBackupListReq
//
//
typedef struct _BACKUP_LIST_REQUEST_1 {
    UCHARType;
    UCHAR       RequestedCount;
    ULONG       Token;
} BACKUP_LIST_REQUEST_1, UNALIGNED *LP_BACK_REQ_FRAME;

//  BackupListResp
//  Response containing a backup server list.
//

typedef struct _BACKUP_LIST_RESPONSE_1 {
    UCHAR Type;
    UCHAR       BackupServerCount;
    ULONG       Token;
    UCHAR       BackupServerList[51];
} BACKUP_LIST_RESPONSE_1, UNALIGNED *LP_BACK_RESP;

//  BecomeBackup
//  Message indicating that a potential browser server should become a backup
//  server.
//
typedef struct _BECOME_BACKUP_1 {
    UCHAR Type;
    UCHAR       BrowserToPromote[1];
} BECOME_BACKUP_1;

typedef BECOME_BACKUP_1 UNALIGNED *LP_BECOME_BACKUP;

//  MasterAnnouncement
//  Send from master to domain master.
//

typedef struct _MASTER_ANNOUNCEMENT_1 {
    UCHAR Type;
    UCHAR       MasterName[16];
} MASTER_ANNOUNCEMENT_1, UNALIGNED *LP_MASTER_ANNC;

//  Election
//  Sent during the election process.
//

typedef struct _REQUEST_ELECTION_1 {
    UCHAR Type;
    UCHAR       Version;
    ULONG       Criteria;
    ULONG       TimeUp;
    ULONG       MustBeZero;
    UCHAR       ServerName[1];
} REQUEST_ELECTION_1, UNALIGNED *LP_ELECTION;

#define ELECTION_CR_OSTYPE      0xFF000000L // Native OS running on server
#define ELECTION_CR_OSWFW       0x01000000L //  Windows for workgroups server
#define ELECTION_CR_WIN_NT      0x10000000L //  Windows/NT Server
#define ELECTION_CR_LM_NT       0x20000000L //  Lan Manager for Windows/NT

#define ELECTION_CR_REVISION    0x00FFFF00L // Browser software revision
#define ELECTION_MAKE_REV(major, minor) (((major)&0xffL)<<16|((minor)&0xFFL)<<8)

#define ELECTION_CR_DESIRE      0x000000FFL // Desirability of becoming master.

//
//  Election desirability within criteria.
//
//  Most important is a running PDC, next is a configured domain master.
//
//  After that come running masters, then configured backups, then existing
//  running backups.
//

#define ELECTION_DESIRE_AM_BACKUP  0x00000001L // Currently is backup
#define ELECTION_DESIRE_AM_CFG_BKP 0x00000002L // Always want to be
                                               //  master - set if backup &&
                                               //  MaintainServerList==YES
#define ELECTION_DESIRE_AM_MASTER  0x00000004L // Currently is master
#define ELECTION_DESIRE_AM_DOMMSTR 0x00000008L // Configured as domain master

#define ELECTION_DESIRE_AM_PDC     0x00000080L // Machine is a lanman NT server.

// Machines running WINS client are important because they are more capable
// of connecting to a PDC who's address was configured via DHCP.

#define ELECTION_DESIRE_WINS_CLIENT 0x00000020L // Transport running WINS client

// ResetBrowserState
//  "Tickle" packet - sent to change state of browser.
//

typedef struct _RESET_STATE_1 {
        UCHAR Type;
        UCHAR   Options;
} RESET_STATE_1, *LP_RESET;

#define RESET_STATE_STOP_MASTER 0x01    // Stop being master
#define RESET_STATE_CLEAR_ALL   0x02    // Clear all browser state.
#define RESET_STATE_STOP        0x04    // Stop the browser service.

//
// Type Server
//
#defineSV_TYPE_WORKSTATION0x00000001
#defineSV_TYPE_SERVER0x00000002
#defineSV_TYPE_SQLSERVER0x00000004
#defineSV_TYPE_DOMAIN_CTRL0x00000008
#defineSV_TYPE_DOMAIN_BAKCTRL0x00000010
#defineSV_TYPE_TIME_SOURCE0x00000020
#defineSV_TYPE_AFP0x00000040
#defineSV_TYPE_NOVELL0x00000080
#defineSV_TYPE_DOMAIN_MEMBER0x00000100
#defineSV_TYPE_PRINTQ_SERVER0x00000200
#defineSV_TYPE_DIALIN_SERVER0x00000400
#defineSV_TYPE_XENIX_SERVER0x00000800
#defineSV_TYPE_NT0x00001000
#defineSV_TYPE_POTENTIAL_BROWSER0x00010000
#defineSV_TYPE_BACKUP_BROWSER0x00020000
#defineSV_TYPE_MASTER_BROWSER0x00040000
#defineSV_TYPE_DOMAIN_MASTER0x00080000
#defineSV_TYPE_LOCAL_LIST_ONLY0x40000000
#defineSV_TYPE_DOMAIN_ENUM0x80000000

//===========================================================================
//  Function prototypes.
//===========================================================================

// question:  what other functions?

extern VOID  WINAPIV BrowserFormatSummary(LPPROPERTYINST lpPropertyInst);
extern VOID  WINAPIV BrowserFormatCommand(LPPROPERTYINST lpPropertyInst);
extern VOID  WINAPIV FormatBrowserSummary(LPPROPERTYINST lpPropertyInst);

//=============================================================================
//  Helper functions.
//=============================================================================

//question: what helper functions?

// extern WORD   WINAPI GetBrowserFrameSize(LPBrowser Frame);

//=============================================================================
//  Format Table.
//=============================================================================
// question:  what is this?

//=============================================================================
//  Manifests for Creating Handoff Set
//=============================================================================

//question: what is handoff set?

//#define MAX_SAPS            50
//#define MAX_ETYPES          50

#define ELECTION_DESIRE_AM_BACKUP  0x00000001L // Currently is backup
#define ELECTION_DESIRE_AM_CFG_BKP 0x00000002L // Always want to be

#pragma pack()

//=============================================================================
//  Property table index constants
//=============================================================================

#define Browser_COMMAND         0
#define Browser_SUMMARY         1
#define HA_UpdateCount2
#define HA_Periodicity3
#define HA_ServerName4
#define HA_VersionMajor5
#define HA_VersionMinor6
#define HA_Type_Flag_Summary7
#define HA_Type8
#define HA_ElectionVersion9
#define HA_Constant10
#define AR_Flags11
#define AR_Reply12
#define BREQ_RequestedCount13
#define BREQ_Token14
#define BRSP_ServerCnt15
#define BRSP_Token16
#define BRSP_ServerList17
#define MA_MasterName18
#define ELEC_Version19
#define ELEC_Criteria20
#define ELEC_TimeUp21
#define ELEC_MustBeZero22
#define ELEC_ServerName23
#define ELEC_OS_Summary24
#define ELEC_OS25
#define ELEC_Revision26
#define ELEC_Desire_Summary27
#define ELEC_Desire28
#define RST_Options29
#define WKGRP_CommentPtr30
#define WKGRP_Comment31
#define RST_Options_Summary32
//#define WKGRP_VersionMajor5
//#define WKGRP_VersionMinor6


HOSTANNC.H

/*++

Copyright (c) 1990 Microsoft Corporation

Module Name:

    hostannc.h

Abstract:

    This module defines the data structures related to host announcements.

Author:

    Larry Osterman (LarryO) 22-Oct-1990

Revision History:

    22-Oct-1991  LarryO

        Created

--*/

#ifndef _HOSTANNC_
#define _HOSTANNC_

typedef enum _MailslotType {
    MailslotTransaction = -2,
    OtherTransaction = -1,
    Illegal = 0,
    HostAnnouncement = 1,
    AnnouncementRequest = 2,
    InterrogateInfoRequest = 3,
    RelogonRequest = 4,
    Election = 8,
    GetBackupListReq = 9,
    GetBackupListResp = 10,
    BecomeBackupServer = 11,
    WkGroupAnnouncement = 12,
    MasterAnnouncement = 13,
    ResetBrowserState = 14,
    LocalMasterAnnouncement = 15,
    MaximumMailslotType
} MAILSLOTTYPE, *PMAILSLOTTYPE;

#define WORKSTATION_SIGNATURE       '\0'
#define SERVER_SIGNATURE            ' '
#define PRIMARY_DOMAIN_SIGNATURE    '\0'
#define PRIMARY_CONTROLLER_SIGNATURE '\x1B'
#define DOMAIN_CONTROLLER_SIGNATURE '\x1C'
#define MASTER_BROWSER_SIGNATURE    '\x1D'
#define BROWSER_ELECTION_SIGNATURE  '\x1E'
#define DOMAIN_ANNOUNCEMENT_NAME    "\x01\x02__MSBROWSE__\x02\x01"
//
//  The following values should be the minimum and maximum of the
//  mailslot transaction opcodes defined above.
//

#define MIN_TRANSACT_MS_OPCODE          MailslotTransaction
#define MAX_TRANSACT_MS_OPCODE          RelogonRequest

//
//  Common name for reserved, `internal' transactions
//

#define MAILSLOT_LANMAN_NAME SMB_MAILSLOT_PREFIX "\\LANMAN"
#define MAILSLOT_BROWSER_NAME SMB_MAILSLOT_PREFIX "\\BROWSE"
#define ANNOUNCEMENT_MAILSLOT_NAME     "\\\\*" ITRANS_MS_NAME

#include <packon.h>
 //
// Each visible server on the net periodically emits a host announcement.
// This is a SMB TRANSACTION REQUEST on a reserved, "internal" name.
//

//
//  There are two versions of each of these structures defined.  The first,
//  is the actual "meat" of the structure, the second includes the announcement
//  type.
//

//
// Lan Manager announcement message.  This is used for opcodes:
//
//  HostAnnouncement to \MAILSLOT\LANMAN on the LANMAN domain name.
//

typedef struct _HOST_ANNOUNCE_PACKET_1 {
    UCHAR       CompatibilityPad;
    ULONG       Type;
    UCHAR       VersionMajor;   /* version of LM running on host */
    UCHAR       VersionMinor;   /*  "  "   "   "    "    "    "   */
    USHORT      Periodicity;   /* announcement cycle in secs   */
    CHAR        NameComment[LM20_CNLEN+1+LM20_MAXCOMMENTSZ+1];
} HOST_ANNOUNCE_PACKET_1, *PHOST_ANNOUNCE_PACKET_1;

typedef struct _HOST_ANNOUNCE_PACKET {
    UCHAR       AnnounceType;
    HOST_ANNOUNCE_PACKET_1 HostAnnouncement;
} HOST_ANNOUNCE_PACKET, *PHOST_ANNOUNCE_PACKET;

//
// General announcement message.  This is used for opcodes:
//
//  HostAnnouncement, WkGroupAnnouncement, and LocalMasterAnnouncement
//

typedef struct _BROWSE_ANNOUNCE_PACKET_1 {
    UCHAR       UpdateCount;    // Inc'ed when announce data changed.
    ULONG       Periodicity;    // announcement cycle in milliseconds

    UCHAR       ServerName[LM20_CNLEN+1];
    UCHAR       VersionMajor;
    UCHAR       VersionMinor;   /*  "  "   "   "    "    "    "   */
    ULONG       Type;           // Server type.
    CHAR        *CommentPointer;
    CHAR        Comment[LM20_MAXCOMMENTSZ+1];
} BROWSE_ANNOUNCE_PACKET_1, *PBROWSE_ANNOUNCE_PACKET_1;

typedef struct _BROWSE_ANNOUNCE_PACKET {
    UCHAR       BrowseType;
    BROWSE_ANNOUNCE_PACKET_1 BrowseAnnouncement;
} BROWSE_ANNOUNCE_PACKET, *PBROWSE_ANNOUNCE_PACKET;
//
//  The request announcement packet is sent by clients to request that
//  remote servers announce themselves.
//

typedef struct _REQUEST_ANNOUNCE_PACKET_1 {      // Contents of request announcement
    UCHAR    Flags;                 // Unused Flags
    CHAR     Reply[LM20_CNLEN+1];
}  REQUEST_ANNOUNCE_PACKET_1, *PREQUEST_ANNOUNCE_PACKET_1;

typedef struct _REQUEST_ANNOUNCE_PACKET {        /* Request announcement struct */
    UCHAR   Type;
    REQUEST_ANNOUNCE_PACKET_1    RequestAnnouncement;
} REQUEST_ANNOUNCE_PACKET, *PREQUEST_ANNOUNCE_PACKET;

#define HOST_ANNC_NAME(xx)     ((xx)->NameComment)
#define HOST_ANNC_COMMENT(xx)  ((xx)->NameComment + (strlen(HOST_ANNC_NAME(xx))+1))

#define BROWSE_ANNC_NAME(xx)     ((xx)->ServerName)
#define BROWSE_ANNC_COMMENT(xx)  ((xx)->Comment)

 //
//  Definitions for Windows Browser
//

//
//  Request to retrieve a backup server list.
//

typedef struct _BACKUP_LIST_REQUEST_1 {
    UCHAR       RequestedCount;
    ULONG       Token;
} BACKUP_LIST_REQUEST_1, *PBACKUP_LIST_REQUEST_1;

typedef struct _BACKUP_LIST_REQUEST {
    UCHAR  Type;
    BACKUP_LIST_REQUEST_1 BackupListRequest;
} BACKUP_LIST_REQUEST, *PBACKUP_LIST_REQUEST;

//
//  Response containing a backup server list.
//

typedef struct _BACKUP_LIST_RESPONSE_1 {
    UCHAR       BackupServerCount;
    ULONG       Token;
    UCHAR       BackupServerList[1];
} BACKUP_LIST_RESPONSE_1, *PBACKUP_LIST_RESPONSE_1;

typedef struct _BACKUP_LIST_RESPONSE {
    UCHAR Type;
    BACKUP_LIST_RESPONSE_1 BackupListResponse;
} BACKUP_LIST_RESPONSE, *PBACKUP_LIST_RESPONSE;

//
//  Message indicating that a potential browser server should become a backup
//  server.
//

typedef struct _BECOME_BACKUP_1 {
    UCHAR       BrowserToPromote[1];
} BECOME_BACKUP_1, *PBECOME_BACKUP_1;

typedef struct _BECOME_BACKUP {
    UCHAR Type;
    BECOME_BACKUP_1 BecomeBackup;
} BECOME_BACKUP, *PBECOME_BACKUP;

//
//  Sent during the election process.
//

typedef struct _REQUEST_ELECTION_1 {
    UCHAR       Version;
    ULONG       Criteria;
    ULONG       TimeUp;
    ULONG       MustBeZero;
    UCHAR       ServerName[1];
} REQUEST_ELECTION_1, *PREQUEST_ELECTION_1;

typedef struct _REQUEST_ELECTION {
    UCHAR Type;
    REQUEST_ELECTION_1 ElectionRequest;
} REQUEST_ELECTION, *PREQUEST_ELECTION;

#define ELECTION_CR_OSTYPE      0xFF000000L // Native OS running on server
#define ELECTION_CR_OSWFW       0x01000000L //  Windows for workgroups server
#define ELECTION_CR_WIN_NT      0x10000000L //  Windows/NT Server
#define ELECTION_CR_LM_NT       0x20000000L //  Lan Manager for Windows/NT

#define ELECTION_CR_REVISION    0x00FFFF00L // Browser software revision
#define ELECTION_MAKE_REV(major, minor) (((major)&0xffL)<<16|((minor)&0xFFL)<<8)

#define ELECTION_CR_DESIRE      0x000000FFL // Desirability of becoming master.

//
//  Election desirability within criteria.
//
//  Most important is a running PDC, next is a configured domain master.
//
//  After that come running masters, then configured backups, then existing
//  running backups.
//

#define ELECTION_DESIRE_AM_BACKUP  0x00000001L // Currently is backup
//                                     00000001
#define ELECTION_DESIRE_AM_CFG_BKP 0x00000002L // Always want to be
//                                     00000010
                                               //  master - set if backup &&
                                               //  MaintainServerList==YES
#define ELECTION_DESIRE_AM_MASTER  0x00000004L // Currently is master
//            00000100
#define ELECTION_DESIRE_AM_DOMMSTR 0x00000008L // Configured as domain master
//                                     00001000

#define ELECTION_DESIRE_AM_PDC     0x00000080L // Machine is a lanman NT server.
//                                     10000000
//
//  "Tickle" packet - sent to change state of browser.
//

typedef struct _RESET_STATE_1 {
    UCHAR       Options;
} RESET_STATE_1, *PRESET_STATE_1;

typedef struct _RESET_STATE {
    UCHAR Type;
    RESET_STATE_1 ResetStateRequest;
} RESET_STATE, *PRESET_STATE;

#define RESET_STATE_STOP_MASTER 0x01    // Stop being master
#define RESET_STATE_CLEAR_ALL   0x02    // Clear all browser state.
#define RESET_STATE_STOP        0x04    // Stop the browser service.

//
//  Master Announcement - Send from master to domain master.
//

typedef struct _MASTER_ANNOUNCEMENT_1 {
    UCHAR       MasterName[1];
} MASTER_ANNOUNCEMENT_1, *PMASTER_ANNOUNCEMENT_1;

typedef struct _MASTER_ANNOUNCEMENT {
    UCHAR Type;
    MASTER_ANNOUNCEMENT_1 MasterAnnouncement;
} MASTER_ANNOUNCEMENT, *PMASTER_ANNOUNCEMENT;

 //
//  Definitions for Workstation interrogation and revalidation transactions
//

typedef struct _WKSTA_INFO_INTERROGATE_PACKET {
    UCHAR   CompatibilityPad;
    ULONG   Delay ;             // Number of milliseconds to wait before replying
    CHAR    ReturnMailslot[1] ; // Mailslot to reply to.
} WKSTA_INFO_INTERROGATE_PACKET, *PWKSTA_INFO_INTERROGATE_PACKET;

typedef struct _WKSTA_INFO_RESPONSE_PACKET {
    UCHAR   CompatibilityPad;
    UCHAR   VersionMajor;
    UCHAR   VersionMinor;
    USHORT  OsVersion ;
    CHAR    ComputerName[1] ;       // var-length ASCIIZ string */
#if 0
//
//  The following two ASCIIZ strings are not defined in the structure
//  but are concatenated to the end of the structure.
//
    CHAR        UserName[] ;
    CHAR        LogonDomain[] ;
#endif
} WKSTA_INFO_RESPONSE_PACKET, *PWKSTA_INFO_RESPONSE_PACKET;

typedef struct _WKSTA_RELOGON_REQUEST_PACKET {
    UCHAR   CompatibilityPad;
    ULONG   Delay ;
    ULONG   Flags ;
    CHAR    ReturnMailslot[1] ; // var-length ASCIIZ string
#if 0
//
//  The following ASCIIZ string is not defined in the structure
//  but is concatenated to the end of the structure.
//

    CHAR    DC_Name[] ;
#endif
} WKSTA_RELOGON_REQUEST_PACKET, *PWKSTA_RELOGON_REQUEST_PACKET;

//
//  Values for <wkrrq_flags> field */
//

#define WKRRQ_FLAG_LOGON_SERVER      0x1    // I'm your official logon server;
                                            // do a relogon to me.
                                            //

typedef struct _WKSTA_RELOGON_RESPONSE_PACKET {
    UCHAR   CompatibilityPad;
    USHORT  Status ;
    CHAR    ComputerName[1] ;   // var-length ASCIIZ string
} WKSTA_RELOGON_RESPONSE_PACKET, *PWKSTA_RELOGON_RESPONSE_PACKET;

//
//  Values for <wkrrs_status> field
//

#define WKRRS_STATUS_SUCCEEDED      0       // Operation succeeded
#define WKRRS_STATUS_DENIED         1       // Operation denied to caller
#define WKRRS_STATUS_FAILED         2       // Operation tried but failed

#define EXCESS_NAME_LEN (sizeof(ITRANS_MS_NAME) - \
                            FIELD_OFFSET(SMB_TRANSACTION_MAILSLOT, Buffer) )

//
//  This structure defines all of the types of requests that appear in messages
//  to the internal mailslot.
//

typedef struct _INTERNAL_TRANSACTION {
    UCHAR   Type;                               // Type of request.
    union {
        HOST_ANNOUNCE_PACKET_1           Announcement ;
        BROWSE_ANNOUNCE_PACKET_1         BrowseAnnouncement ;
        REQUEST_ANNOUNCE_PACKET_1        RequestAnnounce ;
        BACKUP_LIST_RESPONSE_1           GetBackupListResp ;
        BACKUP_LIST_REQUEST_1            GetBackupListRequest ;
        BECOME_BACKUP_1                  BecomeBackup ;
        REQUEST_ELECTION_1               RequestElection ;
        MASTER_ANNOUNCEMENT_1            MasterAnnouncement ;
        RESET_STATE_1                    ResetState ;

        WKSTA_INFO_INTERROGATE_PACKET    InterrogateRequest ;
        WKSTA_INFO_RESPONSE_PACKET       InterrogateResponse ;
        WKSTA_RELOGON_REQUEST_PACKET     RelogonRequest ;
        WKSTA_RELOGON_RESPONSE_PACKET    RelogonResponse ;
    } Union;
} INTERNAL_TRANSACTION, *PINTERNAL_TRANSACTION ;

#include <packoff.h>

#endif // _HOSTANNC_


MAIN.C

//=============================================================================
//  MODULE: main.c
//
//  Description:
//
//  Bloodhound parer DLL Browser
//
//  Modification History
//
//
//=============================================================================

#include "browser.h"

extern ENTRYPOINTS BrowserEntryPoints;

extern HPROTOCOL hBrowser;

// question
DWORD Attached = 0;

//=============================================================================
//  FUNCTION: DLLEntry()
//
//  Modification History
//
//
//=============================================================================

BOOL WINAPI DLLEntry(HANDLE hInstance, ULONG Command, LPVOID Reserved)
{
    //=========================================================================
    //  If we are loading!
    //=========================================================================

    if ( Command == DLL_PROCESS_ATTACH )
    {
        if ( Attached++ == 0 )
        {
            hBrowser = CreateProtocol("Browser", &BrowserEntryPoints, ENTRYPOINTS_SIZE);

        }
    }

    //=========================================================================
    //  If we are unloading!
    //=========================================================================

    if ( Command == DLL_PROCESS_DETACH )
    {
        if ( --Attached == 0 )
        {
            DestroyProtocol(hBrowser);

        }
    }

    return TRUE;                    //... Bloodhound parsers ALWAYS return TRUE.
}


REMAPI.C

//=============================================================================
//  MODULE: RemAPI.c
//
//  Description:
//
//  Bloodhound Parser DLL for SMB Remote APIs
//
//  Modification History
//
//  Steve Hiskey        07/07/94        Created
//=============================================================================

#include "RemAPI.h"

//=============================================================================
//  Forward references.
//=============================================================================

VOID WINAPIV RemAPIFormatSummary(LPPROPERTYINST lpPropertyInst);

//=============================================================================
//  Labeled RemAPI command set.
//=============================================================================

LABELED_WORD Commands[] =
{
    {     0,    "NetShareEnum"},
    {     1,    "NetShareGetInfo"},
    {     2,    "NetShareSetInfo"},
    {     3,    "NetShareAdd"},
    {     4,    "NetShareDel"},
    {     5,    "NetShareCheck"},
    {     6,    "NetSessionEnum"},
    {     7,    "NetSessionGetInfo"},
    {     8,    "NetSessionDel"},
    {     9,    "NetConnectionEnum"},
    {    10,    "NetFileEnum"},
    {    11,    "NetFileGetInfo"},
    {    12,    "NetFileClose"},
    {    13,    "NetServerGetInfo"},
    {    14,    "NetServerSetInfo"},
    {    15,    "NetServerDiskEnum"},
    {    16,    "NetServerAdminCommand"},
    {    17,    "NetAuditOpen"},
    {    18,    "NetAuditClear"},
    {    19,    "NetErrorLogOpen"},
    {    20,    "NetErrorLogClear"},
    {    21,    "NetCharDevEnum"},
    {    22,    "NetCharDevGetInfo"},
    {    23,    "NetCharDevControl"},
    {    24,    "NetCharDevQEnum"},
    {    25,    "NetCharDevQGetInfo"},
    {    26,    "NetCharDevQSetInfo"},
    {    27,    "NetCharDevQPurge"},
    {    28,    "NetCharDevQPurgeSelf"},
    {    29,    "NetMessageNameEnum"},
    {    30,    "NetMessageNameGetInfo"},
    {    31,    "NetMessageNameAdd"},
    {    32,    "NetMessageNameDel"},
    {    33,    "NetMessageNameFwd"},
    {    34,    "NetMessageNameUnFwd"},
    {    35,    "NetMessageBufferSend"},
    {    36,    "NetMessageFileSend"},
    {    37,    "NetMessageLogFileSet"},
    {    38,    "NetMessageLogFileGet"},
    {    39,    "NetServiceEnum"},
    {    40,    "NetServiceInstall"},
    {    41,    "NetServiceControl"},
    {    42,    "NetAccessEnum"},
    {    43,    "NetAccessGetInfo"},
    {    44,    "NetAccessSetInfo"},
    {    45,    "NetAccessAdd"},
    {    46,    "NetAccessDel"},
    {    47,    "NetGroupEnum"},
    {    48,    "NetGroupAdd"},
    {    49,    "NetGroupDel"},
    {    50,    "NetGroupAddUser"},
    {    51,    "NetGroupDelUser"},
    {    52,    "NetGroupGetUsers"},
    {    53,    "NetUserEnum"},
    {    54,    "NetUserAdd"},
    {    55,    "NetUserDel"},
    {    56,    "NetUserGetInfo"},
    {    57,    "NetUserSetInfo"},
    {    58,    "NetUserPasswordSet"},
    {    59,    "NetUserGetGroups"},
    {    60,    "DeadTableEntry"},
    {    61,    "DeadTableEntry"},
    {    62,    "NetWkstaSetUID"},
    {    63,    "NetWkstaGetInfo"},
    {    64,    "NetWkstaSetInfo"},
    {    65,    "NetUseEnum"},
    {    66,    "NetUseAdd"},
    {    67,    "NetUseDel"},
    {    68,    "NetUseGetInfo"},
    {    69,    "NetPrintQEnum"},
    {    70,    "NetPrintQGetInfo"},
    {    71,    "NetPrintQSetInfo"},
    {    72,    "NetPrintQAdd"},
    {    73,    "NetPrintQDel"},
    {    74,    "NetPrintQPause"},
    {    75,    "NetPrintQContinue"},
    {    76,    "NetPrintJobEnum"},
    {    77,    "NetPrintJobGetInfo"},
    {    78,    "NetPrintJobSetInfo"},
    {    79,    "DeadTableEntry"},
    {    80,    "DeadTableEntry"},
    {    81,    "NetPrintJobDel"},
    {    82,    "NetPrintJobPause"},
    {    83,    "NetPrintJobContinue"},
    {    84,    "NetPrintDestEnum"},
    {    85,    "NetPrintDestGetInfo"},
    {    86,    "NetPrintDestControl"},
    {    87,    "NetProfileSave"},
    {    88,    "NetProfileLoad"},
    {    89,    "NetStatisticsGet"},
    {    90,    "NetStatisticsClear"},
    {    91,    "NetRemoteTOD"},
    {    92,    "NetBiosEnum"},
    {    93,    "NetBiosGetInfo"},
    {    94,    "NetServerEnum"},
    {    95,    "I_NetServerEnum"},
    {    96,    "NetServiceGetInfo"},
    {    97,    "DeadTableEntry"},
    {    98,    "DeadTableEntry"},
    {    99,    "DeadTableEntry"},
    {   100,    "DeadTableEntry"},
    {   101,    "DeadTableEntry"},
    {   102,    "DeadTableEntry"},
    {   103,    "NetPrintQPurge"},
    {   104,    "NetServerEnum2"},
    {   105,    "NetAccessGetUserPerms"},
    {   106,    "NetGroupGetInfo"},
    {   107,    "NetGroupSetInfo"},
    {   108,    "NetGroupSetUsers"},
    {   109,    "NetUserSetGroups"},
    {   110,    "NetUserModalsGet"},
    {   111,    "NetUserModalsSet"},
    {   112,    "NetFileEnum2"},
    {   113,    "NetUserAdd2"},
    {   114,    "NetUserSetInfo2"},
    {   115,    "NetUserPasswordSet2"},
    {   116,    "I_NetServerEnum2"},
    {   117,    "NetConfigGet2"},
    {   118,    "NetConfigGetAll2"},
    {   119,    "NetGetDCName"},
    {   120,    "NetHandleGetInfo"},
    {   121,    "NetHandleSetInfo"},
    {   122,    "NetStatisticsGet2"},
    {   123,    "NetBuildGetInfo"},
    {   124,    "NetFileGetInfo2"},
    {   125,    "NetFileClose2"},
    {   126,    "NetServerReqChallenge"},
    {   127,    "NetServerAuthenticate"},
    {   128,    "NetServerPasswordSet"},
    {   129,    "NetAccountDeltas"},
    {   130,    "NetAccountSync"},
    {   131,    "NetUserEnum2"},
    {   132,    "NetWkstaUserLogon"},
    {   133,    "NetWkstaUserLogoff"},
    {   134,    "NetLogonEnum"},
    {   135,    "NetErrorLogRead"},
    {   136,    "I_NetPathType"},
    {   137,    "I_NetPathCanonicalize"},
    {   138,    "I_NetPathCompare"},
    {   139,    "I_NetNameValidate"},
    {   140,    "I_NetNameCanonicalize"},
    {   141,    "I_NetNameCompare"},
    {   142,    "NetAuditRead"},
    {   143,    "NetPrintDestAdd"},
    {   144,    "NetPrintDestSetInfo"},
    {   145,    "NetPrintDestDel"},
    {   146,    "NetUserValidate2"},
    {   147,    "NetPrintJobSetInfo"},
    {   148,    "TI_NetServerDiskEnum"},
    {   149,    "TI_NetServerDiskGetInfo"},
    {   150,    "TI_FTVerifyMirror"},
    {   151,    "TI_FTAbortVerify"},
    {   152,    "TI_FTGetInfo"},
    {   153,    "TI_FTSetInfo"},
    {   154,    "TI_FTLockDisk"},
    {   155,    "TI_FTFixError"},
    {   156,    "TI_FTAbortFix"},
    {   157,    "TI_FTDiagnoseError"},
    {   158,    "TI_FTGetDriveStats"},
    {   159,    "DeadTableEntry"},
    {   160,    "TI_FTErrorGetInfo"},
    {   161,    "DeadTableEntry"},
    {   162,    "DeadTableEntry"},
    {   163,    "NetAccessCheck"},
    {   164,    "NetAlertRaise"},
    {   165,    "NetAlertStart"},
    {   166,    "NetAlertStop"},
    {   167,    "NetAuditWrite"},
    {   168,    "NetIRemoteAPI"},
    {   169,    "NetServiceStatus"},
    {   170,    "I_NetServerRegister"},
    {   171,    "I_NetServerDeregister"},
    {   172,    "I_NetSessionEntryMake"},
    {   173,    "I_NetSessionEntryClear"},
    {   174,    "I_NetSessionEntryGetInfo"},
    {   175,    "I_NetSessionEntrySetInfo"},
    {   176,    "I_NetConnectionEntryMake"},
    {   177,    "I_NetConnectionEntryClear"},
    {   178,    "I_NetConnectionEntrySetInfo"},
    {   179,    "I_NetConnectionEntryGetInfo"},
    {   180,    "I_NetFileEntryMake"},
    {   181,    "I_NetFileEntryClear"},
    {   182,    "I_NetFileEntrySetInfo"},
    {   183,    "I_NetFileEntryGetInfo"},
    {   184,    "AltSrvMessageBufferSend"},
    {   185,    "AltSrvMessageFileSend"},
    {   186,    "I_NetRplWkstaEnum"},
    {   187,    "I_NetRplWkstaGetInfo"},
    {   188,    "I_NetRplWkstaSetInfo"},
    {   189,    "I_NetRplWkstaAdd"},
    {   190,    "I_NetRplWkstaDel"},
    {   191,    "I_NetRplProfileEnum"},
    {   192,    "I_NetRplProfileGetInfo"},
    {   193,    "I_NetRplProfileSetInfo"},
    {   194,    "I_NetRplProfileAdd"},
    {   195,    "I_NetRplProfileDel"},
    {   196,    "I_NetRplProfileClone"},
    {   197,    "I_NetRplBaseProfileEnum"},
    {   198,    "DeadTableEntry"},
    {   199,    "DeadTableEntry"},
    {   200,    "DeadTableEntry"},
    {   201,    "NetServerSetInfo"},
    {   202,    "DeadTableEntry"},
    {   203,    "DeadTableEntry"},
    {   204,    "DeadTableEntry"},
    {   205,    "NetPrintDriverEnum"},
    {   206,    "NetPrintQProcessorEnum"},
    {   207,    "NetPrintPortEnum"},
    {   208,    "NetWriteUpdateLog"},
    {   209,    "NetAccountUpdate"},
    {   210,    "NetAccountConfirmUpdate"},
    {   211,    "NetConfigSet"},
    {   212,    "NetAccountsReplicate"},
};

SET CommandSET = { (sizeof Commands / sizeof(LABELED_BYTE)), Commands };

//=============================================================================
//  RemAPI database.
//=============================================================================

#define REMAPI_SUMMARY      0
#define REMAPI_COMMAND      1

PROPERTYINFO RemAPIDatabase[] =
{
    {   //  REMAPI_SUMMARY
        0,0,
        "Summary",
        "Remote API packet",
        PROP_TYPE_SUMMARY,
        PROP_QUAL_NONE,
        0,
        FORMAT_BUFFER_SIZE,
        RemAPIFormatSummary},

    {   // REMAPI_COMMAND
        0,0,
        "Command",
        "Remote API Command field.",
        PROP_TYPE_WORD,
        PROP_QUAL_LABELED_SET,
        &CommandSET,
        FORMAT_BUFFER_SIZE,
        FormatPropertyInstance},
};

DWORD nRemAPIProperties = ((sizeof RemAPIDatabase) / PROPERTYINFO_SIZE);

//=============================================================================
//  Protocol entry points.
//=============================================================================

VOID   WINAPI RemAPIRegister(HPROTOCOL);
VOID   WINAPI RemAPIDeregister(HPROTOCOL);
LPBYTE WINAPI RemAPIRecognizeFrame(HFRAME, LPVOID, LPVOID, DWORD, DWORD, HPROTOCOL, DWORD, LPDWORD, LPHPROTOCOL, LPDWORD);
LPBYTE WINAPI RemAPIAttachProperties(HFRAME, LPVOID, LPVOID, DWORD, DWORD, HPROTOCOL, DWORD, DWORD);
DWORD  WINAPI RemAPIFormatProperties(HFRAME, LPVOID, LPVOID, DWORD, LPPROPERTYINST);

ENTRYPOINTS RemAPIEntryPoints =
{
    RemAPIRegister,
    RemAPIDeregister,
    RemAPIRecognizeFrame,
    RemAPIAttachProperties,
    RemAPIFormatProperties
};

HPROTOCOL hRemAPI = NULL;

DWORD Attached = 0;

//=============================================================================
//  FUNCTION: DLLEntry()
//
//  Modification History
//
//  Steve Hiskey        07/07/94        Created
//=============================================================================

BOOL WINAPI DLLEntry(HANDLE hInstance, ULONG Command, LPVOID Reserved)
{
    //=========================================================================
    //  If we are loading!
    //=========================================================================

    if ( Command == DLL_PROCESS_ATTACH )
    {
        if ( Attached++ == 0 )
        {
            hRemAPI = CreateProtocol("RemAPI", &RemAPIEntryPoints, ENTRYPOINTS_SIZE);
        }
    }

    //=========================================================================
    //  If we are unloading!
    //=========================================================================

    if ( Command == DLL_PROCESS_DETACH )
    {
        if ( --Attached == 0 )
        {
            DestroyProtocol(hRemAPI);
        }
    }

    return TRUE;                    //... Bloodhound parsers ALWAYS return TRUE.
}

//=============================================================================
//  FUNCTION: RemAPIRegister()
//
//  Modification History
//
//  Steve Hiskey        07/07/94        Created
//=============================================================================

VOID WINAPI RemAPIRegister(HPROTOCOL hRemAPIProtocol)
{
    register DWORD i;

    //=========================================================================
    //  Create the property database.
    //=========================================================================

    CreatePropertyDatabase(hRemAPIProtocol, nRemAPIProperties);

    for(i = 0; i < nRemAPIProperties; ++i)
    {
        AddProperty(hRemAPIProtocol, &RemAPIDatabase[i]);
    }

}

//=============================================================================
//  FUNCTION: Deregister()
//
//  Modification History
//
//  Steve Hiskey        07/07/94        Created
//=============================================================================

VOID WINAPI RemAPIDeregister(HPROTOCOL hRemAPIProtocol)
{
    DestroyPropertyDatabase(hRemAPIProtocol);
}

//=============================================================================
//  FUNCTION: RemAPIRecognizeFrame()
//
//  Modification History
//
//  Steve Hiskey        07/07/94        Created
//=============================================================================

LPBYTE WINAPI RemAPIRecognizeFrame(HFRAME          hFrame,                     //... frame handle.
                                LPBYTE          MacFrame,                   //... Frame pointer.
                                LPBYTE          RemAPIFrame,                   //... Relative pointer.
                                DWORD           MacType,                    //... MAC type.
                                DWORD           BytesLeft,                  //... Bytes left.
                                HPROTOCOL       hPreviousProtocol,          //... Previous protocol or NULL if none.
                                DWORD           nPreviousProtocolOffset,    //... Offset of previous protocol.
                                LPDWORD         ProtocolStatusCode,         //... Pointer to return status code in.
                                LPHPROTOCOL     hNextProtocol,              //... Next protocol to call (optional).
                                LPDWORD         InstData)                   //... Next protocol instance data.
{
    *ProtocolStatusCode = PROTOCOL_STATUS_CLAIMED;
    return NULL;
}

//=============================================================================
//  FUNCTION: RemAPIAttachProperties()
//
//  Modification History
//
//  Steve Hiskey        07/07/94        Created
//=============================================================================

LPBYTE WINAPI RemAPIAttachProperties(HFRAME    hFrame,
                                  LPBYTE    Frame,
                                  LPBYTE    RemAPIFrame,
                                  DWORD     MacType,
                                  DWORD     BytesLeft,
                                  HPROTOCOL hPreviousProtocol,
                                  DWORD     nPreviousProtocolOffset,
                                  DWORD     InstData)
{
    if ( InstData == 0 )    // this is a request... SMB said so...
    {
        AttachPropertyInstance(hFrame,
                           RemAPIDatabase[REMAPI_SUMMARY].hProperty,
                           BytesLeft,
                           RemAPIFrame,
                           0, 0, 0);

        AttachPropertyInstance(hFrame,
                           RemAPIDatabase[REMAPI_COMMAND].hProperty,
                           sizeof(WORD),
                           RemAPIFrame,
                           0,
                           1,   // level
                           0);

    }
    else    // this is a response SMB transact... we don't get told WHAT command the
            // request was... so we have to look back to find the previous frame that
            // contains our request.
            // This parser assumes that the SMB parser is kind in that it will tell
            // us the frame number of the request frame.  Without this, we would have
            // to get the SMB mid/pid/tid/uid from our frame, find the previous SMB
            // that matches our description, and see what our Remote API command was.
    {
        // The InstData is actually the frame number of the frame that holds the request..
        // If the frame number was 0, then the frame number will be (DWORD)-1.
        DWORD   ReqFrameNum = (InstData==(DWORD)-1)?0:InstData;
        WORD    RemAPICommand;
        HFRAME  hFrameReq;
        LPBYTE  lpReqRemAPI;
        LPBYTE  lpReqFrame;

        // Go get that frame and see what type of remote API we are...
        hFrameReq = GetFrame ( GetFrameCaptureHandle(hFrame), ReqFrameNum );

        if ( hFrameReq == NULL )    // we have a frame number but no frame??
        {   // This should never happen because the SMB parser SAID that we were
            // a remote api AND it gave us a frame number... therefore, hitting
            // this code path implies a bug in either SMB or the kernel.
            #ifdef DEBUG
            DebugBreak ();
            #endif
            return NULL;
        }

        lpReqFrame = LockFrame ( hFrameReq );
        lpReqRemAPI = lpReqFrame + GetProtocolStartOffset ( hFrameReq, "REMAPI" );
        RemAPICommand = *(UNALIGNED WORD *)lpReqRemAPI;  // command is the first word.

        // NOW we can attach properties for the response

        UnlockFrame ( hFrameReq );
    }
    return NULL;
}

//==============================================================================
//  FUNCTION: RemAPIFormatSummary()
//
//  Modification History
//
//  Steve Hiskey        07/07/94        Created
//==============================================================================

VOID WINAPIV RemAPIFormatSummary(LPPROPERTYINST lpPropertyInst)
{
    WORD Command = *(UNALIGNED WORD *)lpPropertyInst->lpData;
    LPSTR CommandStr = LookupWordSetString ( &CommandSET, Command );

    DWORD Length = wsprintf(lpPropertyInst->szPropertyText,
                            "Remote API %s",
                            CommandStr?CommandStr:"Unknown" );
}

//==============================================================================
//  FUNCTION: RemAPIFormatProperties()
//
//  Modification History
//
//  Steve Hiskey        07/07/94        Created
//==============================================================================

DWORD WINAPI RemAPIFormatProperties(HFRAME         hFrame,
                                 LPBYTE         MacFrame,
                                 LPBYTE         FrameData,
                                 DWORD          nPropertyInsts,
                                 LPPROPERTYINST p)
{
    //=========================================================================
    //  Format each property in the property instance table.
    //
    //  The property-specific instance data was used to store the address of a
    //  property-specific formatting function so all we do here is call each
    //  function via the instance data pointer.
    //=========================================================================

    while (nPropertyInsts--)
    {
        ((FORMAT) p->lpPropertyInfo->InstanceData)(p);

        p++;
    }

    return BHERR_SUCCESS;
}


REMAPI.H

//==========================================================================================================================
//  MODULE: RemAPI.h
//
//  Description:
//
//  Bloodhound parser SMB Remote APIs
//
//  Modification History
//
//  Steve Hiskey        07/07/94        Created
//==========================================================================================================================

#include <windows.h>
#include <string.h>
#include <bh.h>
#include <inilib.h>

#pragma pack(1)

#define FORMAT_BUFFER_SIZE 80

#pragma pack()


ISVREG.C

/* */
/*ISVReg.c - C routine that creates Registry entries */
/*for ISV Application Launching in SMS 1.2 */
/* */
/* */

#include <windows.h>
#include "isvreg.h"

#if defined(__cplusplus)
extern "C"
{
#endif

int MakeMultiSz(LPTSTR lpBuf, LPCTSTR lpData)
{
/* fills lpBuf from lpData converting carriage returns to*/
/* nulls and eliminating other control characters.*/
/* Adds an additional null at the end*/
/* to make a valid REG_MULTI_SZ string*/
/* returns size in bytes of result including nulls*/
LPTSTR lpB;
LPCTSTR lpD;

lpB = lpBuf;
lpD = lpData;
while (*lpD  != _T('\0'))
{
if (*lpD == _T('\r'))
{
*lpB++ = _T('\0');
}
else
if (!_istcntrl(*lpD))
{
*lpB++ = *lpD;
}
lpD++;
}
*lpB++ = _T('\0');/* add additional null */
*lpB++ = _T('\0');/* add additional null */
/* return size as the differential in bytes */
return ((LPBYTE)lpB - (LPBYTE)lpBuf);
}

/*
    UnregisterApplication(HKEY hkParentKey, LPCTSTR lpAppName)

    Deletes the key with the given name after deleting any context
    keys that may be underneath it. This routine is necessary because
    Windows NT 3.51 RegDeleteKey doesn't automatically delete subkeys.

    This routine will break if subkeys are added to the context keys.
    It can be removed if RegDeleteKey is eventually brought to up speed
    and so can delete all subkeys like it does in Win95.
*/
long UnregisterApplication(HKEY hkParentKey, LPCTSTR lptAppName)
{
    long    lRet;
    HKEY    hkAppKey;
TCHARtSubKey[MAX_PATH+1];

    /* open the appName key */
lRet = RegOpenKeyEx( hkParentKey, lptAppName, 0, KEY_ALL_ACCESS, &hkAppKey);
if (lRet != ERROR_SUCCESS)
    {
        return ERROR_SUCCESS;    /* if not there it's OK */
    }

    /* enumerate the subkeys, deleting each one, assumes they don't have */
    /* subkeys themselves                                                */
    while(TRUE)
    {
        lRet = RegEnumKey(hkAppKey, 0, tSubKey, MAX_PATH+1);
        if (lRet == ERROR_NO_MORE_ITEMS || lRet != ERROR_SUCCESS)
        {
            break;
        }
   lRet = RegDeleteKey(hkAppKey, tSubKey);
        if (lRet != ERROR_SUCCESS)
        {
            break;
        }
}

    if (lRet == ERROR_SUCCESS || lRet == ERROR_NO_MORE_ITEMS)
    {
        RegCloseKey(hkAppKey);
        lRet = RegDeleteKey(hkParentKey, lptAppName);
    }

    return lRet;
}

/*
RegisterSMSTool(BOOL bRemove, LPISVAppReg lpisv)

This routine makes multiple calls to Registry APIs to create or update
information about a single application that wants to be visible in the
SMS menu and launchable from the SMS Admin UI.

bRemove == TRUE means get rid of named application in the Registry.
bRemove == FALSE means add or update the application into the Registry.

The input struct contains all the strings and values to be registered.
See the ISV Application Launching spec for details about what these
strings should be. In general the name of the key or value is the
about the same as the member variable. Because an application can support
multiple contexts, the structure takes a pointer to multiple ISVContext
structs in an array, plus a count.

Return ERROR_SUCCESS if successful, otherwise the error code from the
Registry function that failed. Does some preliminary error checking to
    catch some bad input situations.
*/

LONG WINAPI RegisterSMSTool(BOOL bRemove, LPISVAppReg lpisv)
{
/* This code creates or updates the registry settings for an*/
/* SMS ISV application that wants to be launched from the SMS*/
/* Administrator UI.*/

TCHARtCurKey[MAX_PATH+1];
TCHARtValueName[MAX_PATH+1];
TCHARtValueData[SmsRegMaxLength];
LONGlRet;
HKEYhkParentKey;
HKEYhkCurKey;
DWORDdwKeyDisp;
intnSize;
inti;

    /* do some preliminary error checking */
    if (_tcslen(lpisv->m_pAppNameKey) <= 0 ||
        _tcslen(lpisv->m_pVendor) <= 0 ||
        (bRemove == FALSE && _tcslen(lpisv->m_pCommand) <= 0) ||
        (bRemove == FALSE && lpisv->m_nNumContexts <= 0) ||
        (lpisv->m_nNumContexts > SmsTotalNumContexts-1) ||
        (bRemove == FALSE && _tcslen(lpisv->m_pAppName) <= 0))
    {
        return ERROR_INVALID_PARAMETER;
    }

    /* get the starting key. This routine should NOT create it */
_tcscpy(tCurKey, SmsRegRootKey);
lRet = RegOpenKeyEx
(
lpisv->m_bHkeyLocal ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
tCurKey, 0, KEY_ALL_ACCESS, &hkParentKey
);
if (lRet != ERROR_SUCCESS)
    {
        return lRet;
    }

/* version checking will happen here if and when */

/* Make subkeys and values */
/* Vendor key is the passed in name of the vendor */
_tcscpy(tCurKey, lpisv->m_pVendor);
    lRet = RegCreateKeyEx(hkParentKey, tCurKey, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkCurKey, &dwKeyDisp);
if (lRet != ERROR_SUCCESS)
    {
        RegCloseKey(hkParentKey);
        return lRet;
    }

if (bRemove)
{
/* remove the named application */
        lRet = UnregisterApplication(hkCurKey, lpisv->m_pAppNameKey);
        if (lRet == ERROR_SUCCESS)
        {
            // if there are no more subkeys then delete the vendor key
            DWORD dwNumSubkeys = 1;
            RegQueryInfoKey(hkCurKey, NULL, NULL, NULL, &dwNumSubkeys,
                NULL, NULL, NULL, NULL, NULL, NULL, NULL);
            RegCloseKey(hkCurKey);
            if (dwNumSubkeys == 0)
            {
                lRet = RegDeleteKey(hkParentKey, tCurKey);
            }
        }
        else
        {
            RegCloseKey(hkCurKey);
        }
        RegCloseKey(hkParentKey);
        return lRet;
    }

/* Make the key for the application itself */
    RegCloseKey(hkParentKey);
hkParentKey = hkCurKey;
_tcscpy(tCurKey, lpisv->m_pAppNameKey);
lRet = RegCreateKeyEx(hkParentKey, tCurKey, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkCurKey, &dwKeyDisp);
if (lRet != ERROR_SUCCESS)
    {
        RegCloseKey(hkCurKey);
        RegCloseKey(hkParentKey);
        return lRet;
    }

/* values for the named application key */
/* ApplicationName */
_tcscpy(tValueName, SmsRegNameValue);
lRet = RegSetValueEx(hkCurKey, tValueName, 0, REG_SZ,
lpisv->m_pAppName, _tcslen(lpisv->m_pAppName)+1);
if (lRet != ERROR_SUCCESS)
    {
        RegCloseKey(hkCurKey);
        RegCloseKey(hkParentKey);
        return lRet;
    }

/* Order */
_tcscpy(tValueName, SmsRegOrder);
lRet = RegSetValueEx(hkCurKey, tValueName, 0, REG_DWORD,
(const BYTE *)&lpisv->m_dwOrder, sizeof(DWORD));
if (lRet != ERROR_SUCCESS)
    {
        RegCloseKey(hkCurKey);
        RegCloseKey(hkParentKey);
        return lRet;
    }

/* Command */
_tcscpy(tValueName, SmsRegCommand);
lRet = RegSetValueEx(hkCurKey, tValueName, 0, REG_SZ,
lpisv->m_pCommand, _tcslen(lpisv->m_pCommand)+1);
if (lRet != ERROR_SUCCESS)
    {
        RegCloseKey(hkCurKey);
        RegCloseKey(hkParentKey);
        return lRet;
    }

/* Description */
_tcscpy(tValueName, SmsRegDescription);
lRet = RegSetValueEx(hkCurKey, tValueName, 0, REG_SZ,
lpisv->m_pDescription, _tcslen(lpisv->m_pDescription)+1);
if (lRet != ERROR_SUCCESS)
    {
        RegCloseKey(hkCurKey);
        RegCloseKey(hkParentKey);
        return lRet;
    }

/* Working Directory */
_tcscpy(tValueName, SmsRegWorkingDir);
lRet = RegSetValueEx(hkCurKey, tValueName, 0, REG_EXPAND_SZ,
lpisv->m_pWorkingDir, _tcslen(lpisv->m_pWorkingDir)+1);
if (lRet != ERROR_SUCCESS)
    {
        RegCloseKey(hkCurKey);
        RegCloseKey(hkParentKey);
        return lRet;
    }

/* Run Window */
_tcscpy(tValueName, SmsRegRunWindow);
lRet = RegSetValueEx(hkCurKey, tValueName, 0, REG_SZ,
lpisv->m_pRunWindow, _tcslen(lpisv->m_pRunWindow)+1);
if (lRet != ERROR_SUCCESS)
    {
        RegCloseKey(hkCurKey);
        RegCloseKey(hkParentKey);
        return lRet;
    }

    /* create a subkey for each context */
    RegCloseKey(hkParentKey);
hkParentKey = hkCurKey;

for (i = 0; i < lpisv->m_nNumContexts; i++)
{
ISVContext isvContext;

isvContext = lpisv->m_pContexts[i];

/* create/open context key */
_tcscpy(tCurKey, isvContext.m_pContextKey);
lRet = RegCreateKeyEx(hkParentKey, tCurKey, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
&hkCurKey, &dwKeyDisp);
    if (lRet != ERROR_SUCCESS)
        {
            RegCloseKey(hkCurKey);
            RegCloseKey(hkParentKey);
            return lRet;
        }

/* values for context, construct REG_MULTI_SZ strings from input */
/* Enable Rule */
_tcscpy(tValueName, SmsRegEnableRule);
nSize = MakeMultiSz(tValueData, isvContext.m_pEnableRule);
lRet = RegSetValueEx(hkCurKey, tValueName, 0, REG_MULTI_SZ,
tValueData, nSize);
    if (lRet != ERROR_SUCCESS)
        {
            RegCloseKey(hkCurKey);
            RegCloseKey(hkParentKey);
            return lRet;
        }

/* Present Rule */
_tcscpy(tValueName, SmsRegPresentRule);
nSize = MakeMultiSz(tValueData, isvContext.m_pPresentRule);
lRet = RegSetValueEx(hkCurKey, tValueName, 0, REG_MULTI_SZ,
tValueData, nSize);
    if (lRet != ERROR_SUCCESS)
        {
            RegCloseKey(hkCurKey);
            RegCloseKey(hkParentKey);
            return lRet;
        }

/* Arguments */
_tcscpy(tValueName, SmsRegArguments);
nSize = MakeMultiSz(tValueData, isvContext.m_pArguments);
lRet = RegSetValueEx(hkCurKey, tValueName, 0, REG_MULTI_SZ,
tValueData, nSize);
    if (lRet != ERROR_SUCCESS)
        {
            RegCloseKey(hkCurKey);
            RegCloseKey(hkParentKey);
            return lRet;
        }

        RegCloseKey(hkCurKey);      /* close this subkey */
}

    RegCloseKey(hkParentKey);
return ERROR_SUCCESS;
}

#if defined(__cplusplus)
}
#endif


ISVREG.H

/* */
/*ISVReg.h - include file for C routine that creates Registry entries      */
/*for ISV Application Launching in SMS 1.2     */
/* */
/*Created: 8 April 1996 */
/* */

#ifndef ISVREG
#define ISVREG

/* we use the generic text-mapping macros to handle multiple platforms */
#include <tchar.h>

#define SmsRegMaxLength2048/* the maximum size of any value: bytes  */

/* useful defines */
#define SmsRegRootKey_T("Software\\Microsoft\\SMS\\Applications")
#define SmsLocalMachine     _T("HKEY_LOCAL_MACHINE")
#define SmsCurrentUser      _T("HKEY_CURRENT_USER")
#define SmsRegNameValue_T("ApplicationName")
#define SmsRegOrder_T("Order")
#define SmsRegIconFile_T("IconFile")
#define SmsRegIconIndex_T("IconFile")
#define SmsRegCommand_T("Command")
#define SmsRegDescription_T("Description")
#define SmsRegWorkingDir_T("WorkingDir")
#define SmsRegRunWindow_T("RunWindow")
#define SmsRegEnableRule_T("EnableRule")
#define SmsRegPresentRule_T("PresentRule")
#define SmsRegArguments_T("Arguments")
#define SmsRegPrompt_T("Prompt")

#define SmsTotalNumContexts     13    /* Total number of defined contexts      */
#define SmsGeneralContext       _T("General")
#define SmsAlertsContext        _T("SMSAlerts")
#define SmsArchWndContext       _T("SMSArchWnd")
#define SmsJobsContext          _T("SMSJobs")
#define SmsMachineContext       _T("SMSMachine")
#define SmsMachineGroupsContext _T("SMSMachineGroups")
#define SmsPackagesContext      _T("SMSPackages")
#define SmsPackageGroupsContext _T("SMSPackageGroups")
#define SmsQueriesContext       _T("SMSQueries")
#define SmsQueryResultsContext  _T("SMSQueryResults")
#define SmsSiteGroupsContext    _T("SMSSiteGroups")
#define SmsSitesContext         _T("SMSSites")
#define SmsTrapWndContext       _T("SMSTrapWnd")

/* ISVContext holds the information stored in the a Context subkey */

typedef struct
{
LPTSTRm_pContextKey;/* the context the app launches from */
/*
m_pEnableRule, m_pPresentRule, and m_pArguments should point to
null-terminated strings with the character \r\n (CR\LF) in
them representing line breaks. RegisterSMSTool will
convert those strings into REG_MULTI_SZ data by replacing the \r\n
characters with nulls and adding an additional null at the end
*/
LPTSTRm_pEnableRule;
LPTSTRm_pPresentRule;
LPTSTRm_pArguments;
}
ISVContext,
*LPISVContext;

/* ISVAppReg holds the information stored in the Vendor Key and subkeys*/
/* including an array of ISVContext structs that hold the Context subkeys   */
/* to be registered.*/

typedef struct
{
LPTSTRm_pVendor;          /* name of vendor */
LPTSTRm_pAppNameKey;      /* name of application key */
LPTSTRm_pAppName;         /* value-name that appears in menu */
LPTSTRm_pCommand;         /* The command to execute */
LPTSTRm_pWorkingDir;      /* working directory */
LPTSTRm_pDescription;     /* status bar description*/
LPTSTRm_pRunWindow;       /* Normal, Minimized, or Maximized */

DWORDm_dwOrder;
BOOLm_bHkeyLocal;       /* TRUE if using HKEY_LOCAL_MACHINE */
intm_nNumContexts;/* number of ISVContexts in m_pContexts */
LPISVContextm_pContexts;/* an array of ISVContexts */
}
ISVAppReg,
*LPISVAppReg;

#if defined(__cplusplus)
extern "C"
{
#endif
/*
RegisterSMSTool(BOOL bRemove, LPISVAppReg lpisv)

This routine makes multiple calls to Registry APIs to create or update
information about a single application that wants to be visible in the
SMS Tools menu and therefore launchable from the SMS Admin UI.

bRemove == TRUE means take out the information
bRemove == FALSE means add or update the information

The input struct contains all the strings and values to be registered.
See the ISV Application Launching spec for details about what these
strings should be. In general the name of the key or value is the
about the same as the member variable. Because an application can support
multiple contexts, the input structure has a pointer to multiple ISVContext
structs in an array, plus a count.

Return ERROR_SUCCESS if successful, otherwise the error code from the
Registry function that failed.
*/

LONG WINAPI RegisterSMSTool(BOOL bRemove, LPISVAppReg lpisv);
#if defined(__cplusplus)
}
#endif

#endif  /* ISVREG */


README.TXT

This directory contains sample programs for the
SMS C api.

The makefiles for each sample program includes
"..\paths.mk", thus you need to make sure that
this file is present in the parent directory
when you build each sample program. Before
building the samples, edit "paths.mk"
and change the paths specified there to match
your system's configuration.  Also note that the
DEST directory, specified in "paths.mk", must
exist before you can build the sample applications.


README.TXT

SMS API Example: attribute filters. (attribut.exe)
    ==================================================

This sample illustrates the use of the Attribute filter that is
applied to a site container in order to enumerate specified
properties of a machine (architecture: Personal Computer).

Applying an attribute filter in order to enumerate machines is
faster than setting group filters and then retrieving specified
machine group scalars (machine properties). This is due to the
nature of the SQL operations that have to be performed.
This filter is also useful in the respect that only the information
that is requested will be returned.

This program opens a site container, constructs and applies an
attribute filter composed of three tokens:
    a) Name scalar        (from MICROSOFT|IDENTIFICATION|1.0 group).
    b) SMSLocation scalar (from MICROSOFT|IDENTIFICATION|1.0 group).
    c) NetCardID scalar   (from MICROSOFT|IDENTIFICATION|1.0 group).

Two other filters are applied to the container:
1. Site filter: to instruct the container to retrieve the root site.
2. Architecture filter: to specify that we want machines of the
   Personal Computer architecture.

After populating the container, which will cause the container to
contain only a site folder for the root site, we open the folder
and then close the container.
Closing the container is an important step since the container
increments a use count that is held for all descendant folders. If
the container is not closed as soon as possible we will never be
able to free the resources for any descendant folder. This would
cause us to exceed virtual memory very quickly for a database of
any size.

We then enter a loop to deal with any folders contained in the
site container. Although, by definition, there can only be one
folder in the site container, this loop is an example of the
general procedure to be followed in folder enumeration involving
the SMS API set.

The function DisplayFolder does most of the work.
It is passed a handle to the folder currently under examination,
at this stage all he is aware of is that this is a handle to a
folder. No mention is made yet as to what type of folder this is.

For display purposes we retrieve the ID of the folder. We then
discovers the folder's properties; these are:
1. the folder's type, and
2. the number of scalars and sub-folders contained in this folder.

The scalars are then displayed.

For each folder we will open each of its child sub-folders, storing
the handles away in a temporary list, and then close the folder.
This is identical to what we did above for the container.

Turning our attention now to the DisplayScalars function we see
that this consists of a simple loop that repeatedly tries to
retrieve a scalar from the folder. The loop terminates when there
are no more scalars in the folder.
Careful note should be taken of the use of the SCALAR structure.
This is composed (currently - expect minor modifications here) of
six fields, two of which are pointers to memory that must be
allocated by the application.
When retrieving a scalar we set the pszName and pszValue pointers
to buffers. The pszName buffer will receive the name of the scalar,
no indication is given by the API as to what size that buffer
should be. However, a size of 256 (see SMS_DATA_BUFF_SIZE define)
is guaranteed to be sufficient.
The pszValue field is used when retrieving string data. For binary
data the pvalue field is used.
Associated with this is a length field. Prior to retrieving a
scalar, you must set the length field to the size of this buffer.
If the scalar is a string or binary, then the actual size of the
data will be returned here. If the buffer was too small then the
return code from the API will be SMS_MORE_DATA.

Once this API has been called, we execute a switch statement to
find out what the type of the scalar is. For integer and time
scalars the value will be in the dwValue member of the SCALAR
structure. The time value can be converted to a string by using
one of the standard C library routines, or a CTime object from
MFC.


ATTRIBUT.CPP

// ====================================================================
//
//  File: attribut.cpp
//
//  Copyright (C) 1994, 1995 Microsoft Corp.
//
//  Author:
//      Jonathan Shuval     Microsoft Corp.
//
//  This program illustrates possible uses of the following SMS APIs:
//
//      SmsAPIVer
//
//      SmsDataSourceConnect
//      SmsDataSourceDisconnect
//
//      SmsOpenContainer
//      SmsSetFilter
//      SmsPopulate
//      SmsGetFolderCount
//      SmsCloseContainer
//      SmsGetNextFolder
//
//      SmsCreateFilter
//      SmsAddToken
//      SmsCloseFilter
//
//      SmsGetFolderType
//      SmsGetFolderID
//      SmsCloseFolder
//      SmsGetScalarCount
//      SmsGetNextScalar
//
//
//  See the readme.txt file in this directory for a decription of this
//  sample.
//
// ====================================================================
//

// ====================================================================
//
//  Includes.
//
// ====================================================================
#include <afx.h>
#include <smsapi.h>             // Header for the APIs.
#include <time.h>               // For converting time scalars into strings.

// Include the GetStatusName function.
// -------------------------------------
#include "..\common\status.inc"

// ====================================================================
//
//  Defines.
//
// ====================================================================
#define CCH_MAXINPUT 256        // Length for some string buffers.

// ====================================================================
//
//  Globals.
//
// ====================================================================
HANDLE ghConnect;               // Connection handle.

// ====================================================================
//
//  Local prototypes.
//
// ====================================================================

// Set filters.
// -------------------------------------------------------------
BOOL setFilters( HANDLE hContainer );

// Displays folders (recursively).
// -------------------------------------------------------------
void DisplayFolder( DWORD ctFolder, HANDLE hFolder );

// Retrieves, decodes, and displays a folder's scalars.
// -------------------------------------------------------------
void DisplayScalars( HANDLE hFolder );

// Prompt the user for input and return the reply.
// -------------------------------------------------------------
void InputString( const char *pszMessage, char *pszResult );

// Connect to the SMS datasource.
// -------------------------------------------------------------
HANDLE ConnectToDatasource();

// Display the help message.
// -------------------------------------------------------------
void DisplayHelp();

// Display the greeting.
// -------------------------------------------------------------
void DisplayGreeting();

// Check if there was a request for help on the command line.
// -------------------------------------------------------------
BOOL DidRequestHelp( int argc, char **argv );

// Display an error message with its SMS status value.
// -------------------------------------------------------------
void DisplaySmsError( const char *pszMessage, SMS_STATUS stat );

// Get the string equivalence for an SMS status code.
// -------------------------------------------------------------
const char *GetStatusName( SMS_STATUS stat );

// ====================================================================
//  The work starts here.
// ====================================================================
void main( int argc, char **argv )
{

    // Check to see if this is a request to display the help
    // screen. If so, display it. Otherwise, display a greeting
    // banner.
    // =========================================================
    if (DidRequestHelp(argc, argv)) {
        DisplayHelp();
        return;
    }
    else {
        DisplayGreeting();
    }

    SMS_STATUS stat;                    // Return from the APIs.
    HANDLE hContainer;                  // Handle to a container.

    // Get and display the SMS API version.
    // ------------------------------------
    char *pszVersion;                   // Receives pointer to the version string.
    SmsAPIVer( &pszVersion );           // Get version
    printf("Using %s\n", pszVersion);   // and print it.

    //===========================================
    // Connect to the SMS datasource.
    //===========================================
    ghConnect = ConnectToDatasource();
    if (ghConnect == NULL) {
        return;
    }

    // Open the site container.
    // ========================
    stat = SmsOpenContainer( C_SITE, ghConnect, &hContainer );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsOpenContainer failed", stat);
        SmsDataSourceDisconnect( ghConnect );
        return;
    }

    // Construct and apply filters.
    // ============================
    BOOL bRet = setFilters( hContainer );
    if (!bRet) {
        // Error message already displayed, but give a general one as well.
        printf("Problem with setting filters.\n");
        SmsDataSourceDisconnect( ghConnect );
        return;
    }

    // Select all folders matching our filters.
    // ========================================
    stat = SmsPopulate( hContainer, POP_SYNC, NULL );
    if (stat != SMS_OK) {
        DisplaySmsError("Bad return from SmsPopulate", stat);
        SmsCloseContainer( hContainer );
        SmsDataSourceDisconnect( ghConnect );
        return;
    }

    printf("========   Enumerating site hierarchy   =============\n");

    // For each folder in the list enumerate its properties.
    // What we do is open all folders in a loop and  then
    // close the container. The open container causes ALL
    // folders to be left open until the container closes.
    // -----------------------------------------------------
    CObArray topFolders;
    HANDLE hFolder;

    // Open all top-level folders.
    // ===========================
    while ((stat = SmsGetNextFolder( hContainer, F_ANY, &hFolder )) == SMS_OK) {

        // Store the folder's handle for later processing.
        // -----------------------------------------------
        topFolders.Add( (CObject *)hFolder );
    }

    printf("*** Retrieved %d folders ***\n", topFolders.GetSize());

    // Check why we exited the loop.
    // -----------------------------
    if (stat != SMS_NO_MORE_DATA) {
        DisplaySmsError("Failed to retrieve folder", stat);
    }

    // Close the container.
    // --------------------
    SmsCloseContainer( hContainer );

    // Now (recursively) process each top-level folder.
    // ================================================
    for (int iLoop = 0; iLoop < topFolders.GetSize(); iLoop++) {

        // Retrieve the handle.
        // --------------------
        hFolder = (HANDLE)topFolders[iLoop];

        printf("*** Top folder #%d\n", iLoop);

        // Display the folder.
        // -------------------
        DisplayFolder( iLoop, hFolder );

        // Note: the folder is closed inside DisplayFolder.
    }

    printf("********* All done ***************\n");

    stat = SmsDataSourceDisconnect( ghConnect );

}  /* main */

// ====================================================================
//
//  DisplayFolder
//
//  Display the folder's details and scalars, then recursively process
//  any sub-folders.
//
//  Parameters:
//      DWORD ctFolder
//          The number of sub-folders at this level of the container/folder
//          hierarchy.
//
//      HANDLE hFolder
//          The handle of the folder to display.
//
//  Returns: Nothing.
//
// ====================================================================

// Simple macro for checking status.
#define CHKSTAT(str) if (stat != SMS_OK && stat != SMS_MORE_DATA) { \
        printf(str); printf(": %d\n", stat); \
        return; }

void DisplayFolder( DWORD ctFolder, HANDLE hFolder )
{

    SMS_STATUS stat;
    HANDLE hSubFolder;
    DWORD fType;                        // type of folder we're dealing with.
    char szfType[30];                   // type as a string.
    DWORD totFolders;                   // Total number of sub-folders of
                                        // all types.
    char szFolderID[100];               // This folder's ID.
    DWORD ctScalars;                    // How many scalars in this folder.

    // Get object type and id.
    // -----------------------
    stat = SmsGetFolderID( hFolder, szFolderID );
    CHKSTAT("SmsGetFolderID");

    stat = SmsGetFolderType( hFolder, &fType, szfType );
    CHKSTAT("SmsGetFolderType");

    printf("===================================================\n");
    printf("[%d] Folder %s is a %s\n", ctFolder, szFolderID, szfType);

    // How many scalars and folders in this folder.
    // --------------------------------------------------------
    stat = SmsGetScalarCount( hFolder, &ctScalars );
    stat = SmsGetFolderCount( hFolder, F_ANY, &totFolders );
    printf("\tContains %d scalars and %d folders\n", ctScalars, totFolders);
    DisplayScalars( hFolder );

    // Allocate space for sub-folder handles.
    // --------------------------------------
    CObArray subFolders;
    subFolders.SetSize( totFolders );

    // This loop gets the sub-folder IDs and displays them.
    // ====================================================
    char szSubFolderID[100];
    DWORD ctFolders;

    for (ctFolders = 0; ctFolders < totFolders; ctFolders++) {

        // Get a handle to a sub-folder.
        // -----------------------------
        stat = SmsGetNextFolder( hFolder, F_ANY, &hSubFolder );
        CHKSTAT("SmsGetNextFolder");
        subFolders.SetAt( ctFolders, (CObject *)hSubFolder );

        // Get and display the sub-folder's ID.
        // ------------------------------------
        stat = SmsGetFolderID( hSubFolder, (char *)szSubFolderID );
        CHKSTAT("SmsGetFolderID");
        printf("Sub-folder #%d: %s\n", ctFolders, szSubFolderID);
    }

    // =======================================================
    // We can now release the handle that was passed in to us.
    // If we wait until the function returns then, because we
    // recurse, we don't release it till the very end.
    // =======================================================
    SmsCloseFolder( hFolder );

    // This loop gets the sub-folders and displays them.
    // =================================================
    for (ctFolders = 0; ctFolders < totFolders; ctFolders++) {
        hSubFolder = (HANDLE)subFolders[ctFolders];
        DisplayFolder( ctFolders, hSubFolder );
    }

    // Free the folder handle array.
    // -----------------------------
    subFolders.RemoveAll();

}  /* DisplayFolder */

// ====================================================================
//
//  DisplayScalars
//
//  Display all the scalars for the folder.
//
//  Parameters:
//      HANDLE hFolder
//          This is the folder handle. Display this folder's scalars.
//
//  Returns:  Nothing.
//
// ====================================================================

#define MY_BUFF_SIZE 100            // Buff size for strings and binaries.
// NOTE: this is deliberately set lower than the maximum (SMS_DATA_BUFF_SIZE).
// By doing this we can exercise the code for checking if the buffer is too
// small for the data (see 'Check for buffer being too small' below).

void DisplayScalars( HANDLE hFolder )
{
    SMS_STATUS stat = SMS_OK;
    SCALAR scalar;
    char szName[50];                // buffer for name
    char szValue[MY_BUFF_SIZE];     // buffer for value
    BYTE byValue[MY_BUFF_SIZE];     // Buffer for binary scalars.
    scalar.pszName  = szName;
    scalar.pszValue = szValue;
    scalar.pValue   = byValue;

    char *pszTime;                  // For time scalars.
    CTime tTime;                    //  ditto
    CString sTime;                  //  ditto

    while (1) {

        scalar.dwLen = MY_BUFF_SIZE-1;       // must tell him the size

        stat = SmsGetNextScalar( hFolder, &scalar);
        if (stat != SMS_OK && stat != SMS_MORE_DATA) {
            break;
        }

        // Check for buffer being too small.
        // ---------------------------------
        if (stat == SMS_MORE_DATA) {
            printf("Receive buffer too small, should be %d. Data truncated\n",
                            scalar.dwLen);
            // NOTE: at this point we could retrieve the scalar by name
            // after allocating a larger buffer.
        }

        // Check scalar type, display accordingly.
        // ---------------------------------------
        switch (scalar.scType) {
        case SCALAR_STRING:
            printf("\t%25s (string): %s\n", scalar.pszName, scalar.pszValue);
            break;

        case SCALAR_INT:
            printf("\t%25s (Integer): %ld\n", scalar.pszName, scalar.dwValue);
            break;

        case SCALAR_TIME:
            // Check if we have a string equivalence. If so use it.
            if (scalar.bStringEquivalence) {
                pszTime = scalar.pszValue;

            } else {
                // Use time functions to make a string out of it.
                // NOTE: we really need to get rid of trailing newline.
                tTime = scalar.tValue;
                sTime = tTime.Format( "%m/%d/%y %I:%M %p" );
                pszTime = (char *)(const char *)sTime;
            }
            printf("\t%25s (time): %s", scalar.pszName, pszTime);
            break;

        case SCALAR_BINARY:
            // In this sample we won't display the binary data.
            // Just tell the user its size.
            printf("\t%25s (Binary - this is its size): %ld\n", scalar.pszName, scalar.dwLen);
            break;
        }
    }

    // Why did we exit (other than no more scalars)?
    // ---------------------------------------------
    if (stat != SMS_NO_MORE_DATA) {
        DisplaySmsError("Bad return from Scalar access: %d\n", stat);
    }

    // Terminate with newline.
    // -----------------------
    printf("\n");

}

// ********************************************************************
//      Helper functions.
// ********************************************************************

// ====================================================================
//
// setFilters
//
// Create a new filter and apply it to the specified container.
//
// Parameters:
//      HANDLE hContainer
//          This is the handle of the container to which the filter
//          will be applied to.
//
// Returns:
//      BOOL
//          TRUE if no errors occcured, otherwise FALSE.
//
// ====================================================================

#define CHECK(str) if (stat != SMS_OK) {       \
        DisplaySmsError(str, stat);            \
        return(FALSE);                         \
    }

BOOL setFilters( HANDLE hContainer )
{

    printf("This test will set the following filters:\n");
    printf("        Site filter: RootSite\n");
    printf("Architecture filter: Personal Computer\n");
    printf("   Attribute filter: (MS|ID|1.0) Name\n");
    printf("                   : (MS|ID|1.0) SMSLocation\n");
    printf("                   : (MS|ID|1.0) NetCardID\n");

    SMS_STATUS stat;
    HANDLE hFilter;
    TOKEN Token;

    // Define a filter: root site.
    // ----------------------------------
    memset( &Token, 0, sizeof(TOKEN) );             // Clear it.
    stat = SmsCreateFilter( SITE_FILTER, ghConnect, &hFilter );
    strcpy( Token.szName, "RootSite" );
    stat = SmsAddToken( hFilter, OP_OR, &Token, -2 );
    CHECK("SmsAddToken (site)");

    stat = SmsSetFilter( hContainer, hFilter );
    SmsCloseFilter( hFilter );
    CHECK("SmsSetFilter (site)");

    // =========================================================
    // Must set an architecture filter else we get no machines.
    // =========================================================
    memset( &Token, 0, sizeof(TOKEN) );             // Clear it.
    stat = SmsCreateFilter( ARCHITECTURE_FILTER, ghConnect, &hFilter );
    strcpy( Token.szName, "Architecture" );
    strcpy( Token.szValue, "Personal Computer" );
    Token.dwOp = QOP_STR_EQ;
    stat = SmsAddToken( hFilter, OP_OR, &Token, -2 );
    CHECK("SmsAddToken (architecture)");
    stat = SmsSetFilter( hContainer, hFilter );
    SmsCloseFilter( hFilter );
    CHECK("SmsSetFilter (architecture)");

    // =========================================================
    // Attribute filter (MICROSOFT|IDENTIFICATION|1.0)
    // Three tokens:
    //  Name
    //  SMSLocation
    //  NetCardID
    // =========================================================
    memset( &Token, 0, sizeof(TOKEN) );             // Clear it.
    stat = SmsCreateFilter( ATTRIBUTE_FILTER, ghConnect, &hFilter );

    strcpy( Token.szArchitecture, "Personal Computer" );
    strcpy( Token.szGroupClass, "MICROSOFT|IDENTIFICATION|1.0" );
    strcpy( Token.szAttributeName, "Name" );
    stat = SmsAddToken( hFilter, OP_OR, &Token, -2 );
    CHECK("SmsAddToken (attribute:name)");

    strcpy( Token.szAttributeName, "SMSLocation" );
    stat = SmsAddToken( hFilter, OP_OR, &Token, -2 );
    CHECK("SmsAddToken (attribute:Location)");

    strcpy( Token.szAttributeName, "NetCardID" );
    stat = SmsAddToken( hFilter, OP_OR, &Token, -2 );
    CHECK("SmsAddToken (attribute:NetCardID)");

    stat = SmsSetFilter( hContainer, hFilter );
    SmsCloseFilter( hFilter );
    CHECK("SmsSetFilter (attribute)");

    return( TRUE );

}  /* setFilters */

// ====================================================================
//
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char *pszMessage
//          The user prompt to display.
//
//      char *pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//
// ====================================================================
void InputString( const char *pszMessage, char *pszResult )
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

// ====================================================================
//
// ConnectToDatasource
//
// Get the datasource connection information from the user and use it
// to connect to the datasource.
//
// Parameters:  None.
//
// Returns:
//      The connection handle or NULL if the connection failed.
//
// ====================================================================
HANDLE ConnectToDatasource()
{
    // Get the information we need to connect to the
    // data source from the user.
    //==============================================
    char szServer[CCH_MAXINPUT];
    char szUser[CCH_MAXINPUT];
    char szPasswd[CCH_MAXINPUT];
    char szDatabase[CCH_MAXINPUT];

    printf("\n");
    printf("**************************\n");
    printf("* Connect to data source *\n");
    printf("**************************\n");
    InputString("SQL server name", szServer);
    InputString("SQL database name", szDatabase);
    InputString("User name for SQL server", szUser);
    InputString("Password for SQL server", szPasswd);
    printf("\n");

    // Connect to a data source. SQL in this case.
    // ===========================================
    DATASOURCE dsParams;

    dsParams.sqlParams.ds          = DB_SQL;
    dsParams.sqlParams.pszServer   = szServer;
    dsParams.sqlParams.pszUserName = szUser;
    dsParams.sqlParams.pszPasswd   = szPasswd;
    dsParams.sqlParams.pszDbName   = szDatabase;
    dsParams.sqlParams.pFunc       = NULL;         // No encryption.
    dsParams.sqlParams.pszKey      = "";

    HANDLE hConnect;
    SMS_STATUS stat;
    stat = SmsDataSourceConnect( &dsParams, &hConnect);

    if (stat != SMS_OK) {
        hConnect = NULL;
        DisplaySmsError("Connect to data source failed", stat);
    }

    return( hConnect );
}

// ====================================================================
//
// DidRequestHelp
//
// Check the program's arguments to see if the user asked for
// the help screen to be displayed.
//
// Parameters:
//      int argc
//          The argc value from main(argc, argv)
//
//      char **argv
//          The argv value from main(argc, argv)
//
// Returns:
//      TRUE if command line parameters included a request
//      for help to be displayed.
//
// ====================================================================
BOOL DidRequestHelp( int argc, char **argv )
{
    if (argc == 2  && (strcmp(argv[1], "-help") == 0)) {
        return( TRUE );
    }
    else {
        return( FALSE );
    }

}

// ====================================================================
//
// DisplayHelp
//
// This function displays the samples help screen.
//
// Parameters:
//      None
//
// Returns:
//      Nothing.
//
// ====================================================================
void DisplayHelp()
{
    printf("\n\n");
    printf("***********************************************************\n");
    printf("* attribut.exe:                                           *\n");
    printf("*                                                         *\n");
    printf("* SMS API Example: Test attribute filters.                *\n");
    printf("*                                                         *\n");
    printf("* This sample illustrates the use of the Attribute filter *\n");
    printf("* that is applied to a site container in order to         *\n");
    printf("* enumerate specified properties of a machine             *\n");
    printf("* (architecture Personal Computer).                       *\n");
    printf("*                                                         *\n");
    printf("* Applying an attribute filter in order to enumerate      *\n");
    printf("* machines is considerably faster than setting group      *\n");
    printf("* filters and then retrieving specified machine group     *\n");
    printf("* scalars (machine properties).                           *\n");
    printf("*                                                         *\n");
    printf("* Syntax:                                                 *\n");
    printf("*     attribut.exe [-help]                                *\n");
    printf("*                                                         *\n");
    printf("* Switches:                                               *\n");
    printf("*     -help       Display this help message.              *\n");
    printf("*                                                         *\n");
    printf("***********************************************************\n");
    printf("\n");
}

// ====================================================================
//
// DisplayGreeting
//
// Display the initial greeting banner.
//
// Parameters:
//     None.
//
// Returns:
//     Nothing.
//
// ====================================================================
void DisplayGreeting()
{
    // For this sample, the greeting is identical to the help screen.
    //===============================================================
    DisplayHelp();
}

/* EOF: attribut.cpp */


README.TXT

Sample: container browser
=========================

This program implements a browser for SMS objects, it illustrates
possible uses of the following SMS APIs:
    SmsEnumContainers
    SmsEnumFilters
    SmsDataSourceConnect
    SmsDataSourceDisconnect
    SmsOpenContainer
    SmsPopulate
    SmsCloseContainer
    SmsGetNextFolder
    SmsCloseFolder
    SmsRewind
    SmsGetFolderID
    SmsGetFolderType
    SmsGetScalarCount
    SmsGetFolderCount
    SmsEnumFolderTypes
    SmsGetNextScalar
    SmsCreateFilter
    SmsAddToken
    SmsGetAllFilters.
    SmsGetFilterType
    SmsGetToken
    SmsGetTokenCount
    SmsCloseFilter

Using the EnumContainers and EnumFilters APIs the program discovers
what containers and filters have been registered with the API engine.
The resulting strings are used to populate the containers and
filters listboxes.

After establishing a connection to the appropriate datasource, the user
selects a container to examine. When the "Done" button, in the select
container section of the dialogue, is pressed the "Select" and "Done"
buttons in the Filters section are enabled. At this point the user
should select and configure filters (explanations further down).
Multiple filters may be selected for the container. Pressing the "Done"
button in this section enables the "View container" button at the
lower part of the dialogue.

The "View container" button brings up the "The container" dialogue.
This dialogue displays the container type (eg "Package container"), the
number of folders contained in the container, and a list of these
folders. The user then selects a folder and views it ("View folder"
button). The "Done" button dismisses this dialogue and returns to the
main doalogue. In this case all the user can do is terminate the
browser, or restart by pressing the "Connect" button.

Viewing folders:
View a folder from the container display brings up a dialogue titled
"View of folder: <the folder's name>". This dialogue has two sections,
folder details, and a sub-folders listbox. The folder details section
displays the folder's ID, type, count of scalars in the folder, count
of sub-folders in the folder, and the number of sub-folder types that
the folder can contain. Refer to the smsapi.h header file, or supplied
help file, for the precise meaning and use of sub-folder type count.
If there are any scalars in the folder the "View scalars" button will
be enabled. Pressing this brings up a dialogue displaying the scalars and
and their values.
The sub-folder list displays the names of any sub-folders. Again, if
there are any sub-folders the "View sub-folder" button will be enabled.
Pressing this will cause the dialogue contents to be replaced with the
details of the selected folder.

Configuring filters:
There are currently 8 filters available:
    site, machine, group, job, architecure, package, attribute, and
        sitelimit.
After selecting a filter the user must configure it in order for it to
be applied to the container, this is done in the "Configure filter"
dialogue.
Filters are composed of one or more tokens, the dialogue allows the
setting of tokens into the specified filter.
There are, currently, two forms of APIs for inserting tokens into
filters, Add{And|Or}Token3 and Add{And|Or}Token4. The former version is
used by all filters with the exception of Machine filters.
If the dialogue detects that it is processing a Machine filter, then an
additional edit control is enabled. For all other filters it is disabled.
Machine filters need to be able to specify a group class, an attribute
name, an operator, and a value. Other filters do not need the group
class field.
The usage of the three common fields differs considerably among filters,
indeed, some filters do not even need all three parameters to be filled
in.

    Site filter
    -----------
Possible values for the name field are: RootSite, and SiteByCode.
They inform the container what site folders it should contain.
If no site filter is applied then the site container will contain
one folder for each site in the datasource.
If RootSite is specified, then no other parameters are needed, and the
container will only contain a folder for the root site.
If SiteByCode is specified, then the Value field must contain the three
letter site code for the requested site. Mixing of a RootSite with
SiteByCode tokens makes no sense. (You have asked for only the root site
and then additional sites.) In this case the RootSite token will take
precedence and the other tokens will be discarded.
It is possible, and perfectly sensible, to set multiple SiteByCode
tokens. In this case the container will be populated with one folder for
each of the requested sites. However, in this case the tokens must be
ORed into the filter. To say that you want the boolean product of site
ABC and site DEF makes no sense since no site can possibly have to
site codes.

    Group filter
    -----------
This filter allows you to select the groupclasses that will be included
for any machines that you wish to browse. As such it is only relevant
for a container that includes machines within its hierarchy, eg site
container, machine container.
Configure this filter as follows:

Name field: must say "GroupClass"
GroupClass field: a valid groupclass name such as
        "MICROSOFT|IDENTIFICATION|1.0".
Operator combobox: select the "is" operator. It's the only one allowed.

It is possible to OR multiple tokens together. For instance, selecting
(OR) a token for MICROSOFT|IDENTIFICATION|1.0 group, select (OR) a
token for MICROSOFT|NETCARD|1.0 group. When viewing the parent machine
folder there will be two sub-folders, one for each of these groups.
Note: some groupclasses have multiple instances, eg MICROSOFT|ENVIRONMENT|1.0.
In this case there will be as many machine group sub-folders as there
are instances of this group.
As in site filters, ANDing tokens together makes no sense.

    Job filter
    -----------
Three options exist for filtering jobs: by job ID, by job type, and
by job status. In all three cases the name and value fields are used.
The operation field, currently, is not.
To set tokens, enter one of the strings "JobType", "JobID", or "JobStatus"
in the name field.
For JobType tokens the value should be one of the following (again as
a literal string):
    "Install"           - for workstation install jobs.
    "Server"            - for server share jobs.
    "Remove package"    - for remove package jobs.
    "System"            - for NAD transfer, site, and minijobs.

For retrieving jobs by their ID, you should type "JobID" in the name
field, and the (eight character) job name in the value field.

For retrieving jobs by their status, type "JobStatus" in the name field.
The value field should contain one of the following string values:
    "Pending"
    "Active"
    "Cancelled"
    "Complete"
    "Failed"
    "Active failed"

Job tokens can be combined together inside a filter. For instance, the
tokens: JobType = Install OR JobID = "ABC00123" will result in the
retrieval of all workstation install jobs, and the job with the name
"ABC00123".

    Architecture filter
    -------------------
This uses one parameter, the value field. Additional documentation
will describe what values are allowed. For instance, "Personal Computer"
will result in machines of the Personal Computer architectures being
selected. "SMSEvent" will result in SMS event MIFs being selected.

    Package filter
    --------------
Currently, there is only one type of token for package filters, this
is the Type token. The type field should say "PackageType". The value field
is one of the following literal strings:
    "Workstation"
    "Server"
    "Inventory"
These can be combined by ORing them into a filter, in which case
you could retrieve, say, Workstation and Sharing packages.
If no package filter is applied, then the system defaults to returning
all packages that are in the datasource.

    Attribute filter
    ----------------
An attribute filter acts in the same way as an SMS query format. It
is used when enumerating/selecting machines and defines what machine
attributes should be accepted by the container. This has a significant
performance gain over using group filters.
For instance, if the user only wants to see the Name, SMSLocation,
and NetCardID from a machine's Identification group, then an
attribute filter containing tokens for each of these attributes will
result in a much faster folder selection than specifying a group
filter for then Identification group. Additionally, only the information
requested will be returned.

The name field should contain the groupclass (eg MICROSOFT|IDENTIFICATION|1.0).
The value field should contain the name of the attribute (eg "Name").
The operator is not currently used.

NOTE: attribute filters specifying attributes that exist in multiple
instance groups (eg the Environment group) will produce indeterminate
results. They should not be used in this case.

        Machine filter
        --------------
A machine filter is the API equivalent of an SMS query. To configure this
type of filter you must provide the following:

Architecture field: this must contain an architecture that is known to
    SMS, exam,ples are: "Personal Computer", "SMSEvent", "UserGroups".
    Consult the SMS documentation for full details about architectures.
    GroupClass field: a valid groupclass name such as
        "MICROSOFT|IDENTIFICATION|1.0".
Attribute name field: the name of an attribute that exists within the
    specified groupclass. For example "SystemRole".
Value: the value for the attribute.
Operator combobox: this is a dropdown listbox that contains operators that
    define the relationship between the attribute and the value.
    For example, "SystemRole" "is" "Server". In this case "is" denotes the
    string equality operator.

This filter can be applied to site, machine, and machine group containers.

For more details refer to your SMS documentation on SMS queries.

        Sitelimit filter
        ----------------
This filter can only be applied to a machine container. Using it you
can specify that you only want machines from a specific site and,
optionally, a specified domain to be included within the container.
Configure this filter as follows:

Site code field: the three letter site code
[optional] Domain: the name of the domain


BROWSER.CPP

//***************************************************************************
//
//  File: browser.cpp
//
//  Copyright (c) 1994, 1995 Microsoft Corp.
//
//  Sample Windows application for the SMS API.
//
//  Author:
//      Jonathan Shuval
//
//  This program implements a browser for SMS objects.
//
//  See the readme.txt file in this directory for full details.
//
//***************************************************************************

// ====================================================================
//
//  Includes
//
// ====================================================================
#include "resource.h"

#include <afx.h>
#include <afxcoll.h>
#include <time.h>               // For converting time scalars into strings.

#include <smsapi.h>             // SMS API

// 3d controls.
// -----------------------
#include <ctl3d.h>

// My header.
// -----------------------
#include "browser.h"

// ====================================================================
//
//  Manifests.
//
// ====================================================================
#define WM_LOGIN_MSG    WM_USER+101     // So we can send ourself an
                                        // instruction to connect.

// -------------------------------------------------------------
//  Debug utility
// -------------------------------------------------------------
#ifdef DEBUG
#include <io.h>
#include <stdio.h>
#include <time.h>
int  Log( char *pFmt, ... );
#endif  // DEBUG

// ====================================================================
//
//  The program starts here.
//
// ====================================================================

int PASCAL WinMain(
                HINSTANCE hInstance,
                HINSTANCE hPrevInstance,
                LPSTR lpszCmdLine,
                int nCmdShow)
{
    int rc;

    _hInstance = hInstance;

    // Register the 3d controls.
    // =========================
    Ctl3dRegister(_hInstance);
    Ctl3dAutoSubclass(_hInstance);

    // Load the saved db login information into the globals.
    // Change to suite your preference.
    // =========================================================
    strcat( gszServer,   "jonshu2" );
    strcat( gszDbName,   "SMS" );
    strcat( gszUserName, "sa" );

    // Dialogue does the work.
    // =======================
    rc = DialogBox( _hInstance,
                    MAKEINTRESOURCE(IDD_MAIN_DIALOG),
                    0,
                    (DLGPROC)MainDlg );

    Ctl3dUnregister(_hInstance);

    return(rc);
}

// ====================================================================
//
//  Dialogue to solicit login information for the datasource.
//
// ====================================================================

extern "C" BOOL CALLBACK LoginDlg(  HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam)
{

    HWND hEdit;

    switch (message) {

    case WM_INITDIALOG:
        //KLUDGE: set it with the params I most often use.
        SetDlgItemText( hDlg, IDC_SQLSERVER, gszServer );
        SetDlgItemText( hDlg, IDC_DATABASE,  gszDbName );
        SetDlgItemText( hDlg, IDC_LOGINID,   gszUserName);

        // Set focus on this control.
        hEdit = GetDlgItem( hDlg, IDC_SQLSERVER );
        SetFocus(hEdit);
        return(TRUE);

    case WM_CLOSE:
        EndDialog( hDlg, IDCANCEL );
        return(TRUE);

    case WM_SYSCOLORCHANGE:
        Ctl3dColorChange();
        return(TRUE);

    case WM_COMMAND:

        switch (wParam) {

        case IDOK:
            // Retrieve the values from the edit controls.
            //LATER: validate.
            gdsParams.sqlParams.ds          = DB_SQL;
            gdsParams.sqlParams.pFunc       = NULL;     // No encryption.
            gdsParams.sqlParams.pszKey      = "";
            gdsParams.sqlParams.pszServer   = gszServer;
            gdsParams.sqlParams.pszDbName   = gszDbName;
            gdsParams.sqlParams.pszUserName = gszUserName;
            gdsParams.sqlParams.pszPasswd   = gszPasswd;

            hEdit = GetDlgItem( hDlg, IDC_SQLSERVER );
            SendMessage( hEdit, WM_GETTEXT, sizeof(gszServer), (LPARAM)gszServer );

            hEdit = GetDlgItem( hDlg, IDC_DATABASE );
            SendMessage( hEdit, WM_GETTEXT, sizeof(gszDbName), (LPARAM)gszDbName );

            hEdit = GetDlgItem( hDlg, IDC_LOGINID );
            SendMessage( hEdit, WM_GETTEXT, sizeof(gszUserName), (LPARAM)gszUserName );

            hEdit = GetDlgItem( hDlg, IDC_PASSWORD );
            SendMessage( hEdit, WM_GETTEXT, sizeof(gszPasswd), (LPARAM)gszPasswd );

            // Disable the Login button. This will be enabled after a
            // container enumeration has completed.
            hEdit = GetDlgItem( hDlg, IDC_CONNECT );
            EnableWindow( hEdit, FALSE );

            // And now terminate.
            EndDialog( hDlg, IDOK );
            return(TRUE);
        }
    }

    return(FALSE);

}  /* LoginDlg */

// ====================================================================
//
//  The real work starts here.
//
//  This dialogue allows the user to select a container from the list
//  of available containers. Once selected the user can select filters
//  to be applied to the container.
//  When these steps are completed the container can be viewed by
//  pressing the "View container" button.
// ====================================================================

extern "C" BOOL CALLBACK MainDlg(   HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam)
{

    // At init we disable all controls except the "Select container" button.
    // Once a container has been selected this button is disabled and the
    // "Select filter" button and associated listbox are enabled.
    // This button enables us to select multiple filters.
    // Pressing the "View container" button causes us to dismiss this
    // dialogue and display the container contents.
    // =====================================================================
    HWND hEdit;
    SMS_STATUS stat;
    DWORD dwI;
    FOLDER_INFO *pCont = NULL;
    FILTER_INFO    *pF = NULL;
    MY_FILTER_INFO *pMyFilter = NULL;
    DWORD ctContainers = 0;
    int index;
    static CObArray aFilters;       // Array of FILTER_INFOs representing
                                    // selected filters.
    // Get the container types and insert them into the menu
    static FOLDER_INFO **pContainerList = NULL;
    int reply;
    HANDLE hFilter;

    switch (message) {
    case WM_INITDIALOG:
        // Dialogue initialisation.

        // Post a message to ourselves. This will be processed after the init
        // completes.
        // This message causes us to connect to the data source.
        PostMessage( hDlg, WM_LOGIN_MSG, 0, 0 );

        // ==============================================================
        // We need to enumerate the containers and filters
        // and populate the appropriate listboxes.
        // Enumeration is done by calling into the engine.
        // The resulting lists are held in globals.
        // ==============================================================

        // Enumerate the container types.
        // ---------------------------------------------------------

            // Get the number of containers.
        SmsEnumContainers(NULL, &ctContainers);

            // Allocate memory and call the enum again.
        pContainerList = new FOLDER_INFO *[ctContainers];
        SmsEnumContainers(pContainerList, &ctContainers);

            // Populate the listbox.
        hEdit = GetDlgItem(hDlg, IDC_AVAIL_CONTAINER_LIST);

        for (dwI = 0; dwI < ctContainers; dwI++) {

            pCont = pContainerList[dwI];
            SendMessage(hEdit, LB_ADDSTRING, 0, (LPARAM)pCont->pszTag);
        }

            // Select the first one in the list.
        SendMessage(hEdit, LB_SETCURSEL, 0, 0);

        // Enumerate the filter types.
        // ---------------------------------------------------------
            // Get the number of filters.
        SmsEnumFilters(0, &ctFilters);

            // Allocate memory and call the enum again.
        gpAvailFilterList = new FILTER_INFO[ctFilters];
        SmsEnumFilters(gpAvailFilterList, &ctFilters);

            // Populate the listbox.
        hEdit = GetDlgItem(hDlg, IDC_AVAIL_FILTER_LIST);

        for (dwI = 0; dwI < ctFilters; dwI++) {
            pF = &gpAvailFilterList[dwI];
            SendMessage(hEdit, LB_ADDSTRING, 0, (LPARAM)pF->szTag);
            pF++;
        }

            // Select the first one in the list.
        SendMessage(hEdit, LB_SETCURSEL, 0, 0);

        // Disable all except the select container button.
        // ===============================================
            // Container group
            // ---------------
        hEdit = GetDlgItem(hDlg, IDC_SELECT_CONTAINER_BTN);
        EnableWindow( hEdit, TRUE );

        hEdit = GetDlgItem(hDlg, IDC_SELECT_CONTAINER_DONE);
        EnableWindow( hEdit, FALSE );

            // Filter group.
            // -------------
        hEdit = GetDlgItem(hDlg, IDC_SELECT_FILTER_BTN);
        EnableWindow( hEdit, FALSE );

        hEdit = GetDlgItem(hDlg, IDC_DESELECT_FILTER_BTN);
        EnableWindow( hEdit, FALSE );

        hEdit = GetDlgItem(hDlg, IDC_SELECT_FILTERS_DONE);
        EnableWindow( hEdit, FALSE );

        // Disable the "view container" button.
        hEdit = GetDlgItem(hDlg, IDOK);
        EnableWindow( hEdit, FALSE );

        // Disable the "configure filter" button.
        hEdit = GetDlgItem(hDlg, IDC_CONFIG_FILTER_BTN);
        EnableWindow( hEdit, FALSE );

        // Disable the "View selected filters" button.
        hEdit = GetDlgItem(hDlg, IDC_VIEW_SEL_FILTERS_BTN);
        EnableWindow( hEdit, FALSE );

        return(TRUE);

    case WM_CLOSE:
        EndDialog( hDlg, IDCANCEL );
        return(TRUE);

    case WM_SYSCOLORCHANGE:
        Ctl3dColorChange();
        return(TRUE);

    case WM_LOGIN_MSG:
        // Establish connection to datasource.
        // Done via the Login dialogue.
        // ====================================
        DialogBox( _hInstance,
                   MAKEINTRESOURCE(IDD_LOGIN),
                   hDlg,
                   (DLGPROC)LoginDlg );

        stat = SmsDataSourceConnect( &gdsParams, &ghConnect );
        if (stat != SMS_OK) {
            wsprintf(szMsg, "DataSourceConnect fails: %d", stat);
            MessageBox(hDlg, szMsg, "Problem here", MB_OK);

        } else {
            // Enable "select container" button.
            hEdit = GetDlgItem(hDlg, IDC_SELECT_CONTAINER_BTN);
            EnableWindow( hEdit, TRUE );
        }
        return(TRUE);

    case WM_COMMAND:

        switch (wParam) {

        case IDC_SELECT_CONTAINER_BTN:
            // This takes the current selection from the avail containers
            // list and puts it in the selected container control.
            hEdit = GetDlgItem(hDlg, IDC_AVAIL_CONTAINER_LIST);
            index = SendMessage(hEdit, LB_GETCURSEL, 0, 0);
            gpCInfo = pContainerList[index];

            hEdit = GetDlgItem(hDlg, IDC_SELECTED_CONTAINER);
            SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)gpCInfo->pszTag);

            // Enable the container done button.
            hEdit = GetDlgItem(hDlg, IDC_SELECT_CONTAINER_DONE);
            EnableWindow( hEdit, TRUE );

            // Disable the "Connect" button.
            hEdit = GetDlgItem(hDlg, IDC_CONNECT);
            EnableWindow( hEdit, FALSE );

            return(TRUE);

        case IDC_SELECT_CONTAINER_DONE:

            // Disable the "Select container" button and enable the
            // "Select filters" button.
            hEdit = GetDlgItem(hDlg, IDC_SELECT_CONTAINER_BTN);
            EnableWindow( hEdit, FALSE );

            // Disable container done btn.
            hEdit = GetDlgItem(hDlg, IDC_SELECT_CONTAINER_DONE);
            EnableWindow( hEdit, FALSE );

            // Enable the filters buttons.
            // ---------------------------
            hEdit = GetDlgItem(hDlg, IDC_SELECT_FILTER_BTN);
            EnableWindow( hEdit, TRUE );

            hEdit = GetDlgItem(hDlg, IDC_SELECT_FILTERS_DONE);
            EnableWindow( hEdit, TRUE );

            // Open the container.
            // -------------------
            stat = SmsOpenContainer( gpCInfo->dwTag, ghConnect, &ghContainer );
            if (stat != SMS_OK) {
                wsprintf(szMsg, "bad return from OpenContainer: %d", stat);
                MessageBox(hDlg, szMsg, "Badness", MB_OK );
            }

            return(TRUE);

        case IDC_SELECT_FILTER_BTN:
            // "Select filter" button.
            // This button is grayed-out until a container has
            // been selected.

            // Take the currently selected filter from the
            // available filter listbox and add it to the selected
            // filters listbox.
            hEdit = GetDlgItem(hDlg, IDC_AVAIL_FILTER_LIST);
            index = SendMessage(hEdit, LB_GETCURSEL, 0, 0);
            pF = &gpAvailFilterList[index];

            hEdit = GetDlgItem(hDlg, IDC_SELECTED_FILTERS);
            index = SendMessage(hEdit, LB_ADDSTRING, 0, (LPARAM)pF->szTag);

            // Highlight this selection in the selected box.
            SendMessage(hEdit, LB_SETCURSEL, (WPARAM)index, 0 );

            // Add the filter entry to a list of filters that we
            // keep handy. Maintain the index for easier access.
            //NOTE: remember to free it all.
            pMyFilter = new MY_FILTER_INFO;
            pMyFilter->pF = pF;
            pMyFilter->hFilter = NULL;
            aFilters.Add( (CObject *)pMyFilter );

            // Enable the deselect button.
            // ---------------------------
            hEdit = GetDlgItem(hDlg, IDC_DESELECT_FILTER_BTN);
            EnableWindow( hEdit, TRUE );

            // And the "configure filter" button.
            // ----------------------------------
            hEdit = GetDlgItem(hDlg, IDC_CONFIG_FILTER_BTN);
            EnableWindow( hEdit, TRUE );

            // Enable the "selected filters" label and combo.
            hEdit = GetDlgItem(hDlg, IDC_SELECTED_FILTERS);
            EnableWindow( hEdit, TRUE );

            return(TRUE);

        case IDC_DESELECT_FILTER_BTN:
            // Remove the current item from the selected filters listbox.
            hEdit = GetDlgItem(hDlg, IDC_SELECTED_FILTERS);
            index = SendMessage(hEdit, LB_GETCURSEL, 0, 0);
            SendMessage(hEdit, LB_DELETESTRING, (WPARAM)index, 0);

            // Remove this filter from the selected filters list.
            pMyFilter = (MY_FILTER_INFO *)aFilters.GetAt( index );
            delete pMyFilter;
            pMyFilter = NULL;
            aFilters.RemoveAt( index );

            // Select the previous one in the list.
            // If this was the first (index = 0) then leave index alone
            // else decrement it.
            if (index > 0) {
                index--;
            }
            SendMessage(hEdit, LB_SETCURSEL, (WPARAM)index, 0);

            // Check if we need to disable any buttons.
            // If the selected filters listbox is now empty disable
            // the "deselect" and "configure" filter buttons.
            // -----------------------------------------------------
            index = SendMessage(hEdit, LB_GETCOUNT, 0, 0);
            if (index == 0) {
                // deselect button.
                // ---------------------------
                hEdit = GetDlgItem(hDlg, IDC_DESELECT_FILTER_BTN);
                EnableWindow( hEdit, FALSE );

                // configure filter button.
                // ----------------------------------
                hEdit = GetDlgItem(hDlg, IDC_CONFIG_FILTER_BTN);
                EnableWindow( hEdit, FALSE );
            }

            return(TRUE);

        case IDC_CONFIG_FILTER_BTN:
            // Call a dialogue to handle this.

            // Retrieve the filter and pass it in a global so that
            // the dlgproc can use it.
            hEdit = GetDlgItem(hDlg, IDC_SELECTED_FILTERS);
            index = SendMessage(hEdit, LB_GETCURSEL, 0, 0);
            gpMyFilter = (MY_FILTER_INFO *)aFilters[index];

            reply = DialogBox(  _hInstance,
                                MAKEINTRESOURCE(IDD_CONFIG_FILTER),
                                hDlg,
                                (DLGPROC)ConfigFilterDlg );

            //LATER: put in cancel btn
            return(TRUE);

        case IDC_VIEW_SEL_FILTERS_BTN:
            // User has requested to view filters that have been
            // applied to the container.
            // =================================================
            DialogBox(  _hInstance,
                        MAKEINTRESOURCE(IDD_VIEW_SEL_FILTERS_DLG),
                        hDlg,
                        (DLGPROC)ViewSelFiltersDlg );
            return(TRUE);

        case IDC_SELECT_FILTERS_DONE:
            // Check that all of the filters that we've selected have been
            // configured, warning if not.
            // (NOTE: we should put in a check mark next to selected
            // filters to show if they've been configured.)
            // It is only at this point that we can actually apply the
            // filter to the container.

            // Loop through the filters.
            for (dwI = 0; dwI < (DWORD)aFilters.GetSize(); dwI++) {
                pMyFilter = (MY_FILTER_INFO *)aFilters[dwI];
                if (pMyFilter->hFilter == NULL) {
                    wsprintf(szMsg, "A %s has not been configured,\ndo you want to configure it now?",
                                    pMyFilter->pF->szTag);
                    reply = MessageBox( hDlg,
                                        szMsg,
                                        "Warning",
                                        MB_ICONEXCLAMATION| MB_YESNO );
                    if (reply == IDYES) {
                        // We want to configure this filter now.
                        // Stubbed: needs implementing.
                        MessageBox(hDlg, "Not implemented", "Go away", MB_OK);
                    }

                } else {
                    // We can apply the filter now.
                    hFilter = pMyFilter->hFilter;
                    stat = SmsSetFilter( ghContainer, hFilter );
                    SmsCloseFilter( hFilter);
                    if (stat != SMS_OK) {
                        wsprintf(szMsg, "SetFilter for %s failed: %d",
                                            pMyFilter->pF->szTag, stat);
                        MessageBox(hDlg, szMsg, "Warning", MB_OK);
                    }
                }
            }

            // Must grey-out all other filter controls.
            hEdit = GetDlgItem(hDlg, IDC_SELECT_FILTERS_DONE);
            EnableWindow( hEdit, FALSE );

            hEdit = GetDlgItem(hDlg, IDC_SELECT_FILTER_BTN);
            EnableWindow( hEdit, FALSE );

            hEdit = GetDlgItem(hDlg, IDC_DESELECT_FILTER_BTN);
            EnableWindow( hEdit, FALSE );

            hEdit = GetDlgItem(hDlg, IDC_CONFIG_FILTER_BTN);
            EnableWindow( hEdit, FALSE );

            // The view container btn enabled only when filter done btn
            // clicked.
            hEdit = GetDlgItem(hDlg, IDOK);
            EnableWindow( hEdit, TRUE );

            // Enable the "Connect" button.
            hEdit = GetDlgItem(hDlg, IDC_CONNECT);
            EnableWindow( hEdit, TRUE );

            // Enable the "View selected filters" button.
            // We only want to do this if we have any filters.
            if (aFilters.GetSize() > 0) {
                hEdit = GetDlgItem(hDlg, IDC_VIEW_SEL_FILTERS_BTN);
                EnableWindow( hEdit, TRUE );
            }

            return(TRUE);

        case IDOK:
            // This is the "View Container" button.

            // Tell the container to populate himself.
            stat = SmsPopulate( ghContainer, POP_SYNC, NULL );
            if (stat != SMS_OK) {
                wsprintf(szMsg, "bad return from Populate: %d", stat);
                MessageBox(hDlg, szMsg, "Badness", MB_OK );
            }

            // Enter the container view dialogue.
            DialogBox(  _hInstance,
                        MAKEINTRESOURCE(IDD_THE_CONTAINER),
                        hDlg,
                        (DLGPROC)ContainerViewDlg );

            SmsCloseContainer( ghContainer );

            // Log off from data source.
            SmsDataSourceDisconnect( ghConnect );

            ////////////////////////////////////////////////////
            //  Ready for restart.
            ////////////////////////////////////////////////////

            // Free all global and static data.
            // -------------------------------------------------
            for (index = 0; index < aFilters.GetSize(); index ++) {
                pMyFilter = (MY_FILTER_INFO *)aFilters.GetAt( index );
                delete pMyFilter;
                pMyFilter = NULL;
            }
            aFilters.RemoveAll();

            pCont = NULL;
            pF = NULL;
            gpCInfo = NULL;
            ghConnect = NULL;

            // Delete any strings that were in the selected
            // container and filter edit controls.
            // -------------------------------------------------
            SetDlgItemText( hDlg, IDC_SELECTED_CONTAINER, "" );
            hEdit = GetDlgItem(hDlg, IDC_SELECTED_FILTERS);
            SendMessage( hEdit, LB_RESETCONTENT, 0, 0 );

            // Disable all controls except Login.
            // -------------------------------------------------
                // Container group
                // ---------------
            hEdit = GetDlgItem(hDlg, IDC_SELECT_CONTAINER_BTN);
            EnableWindow( hEdit, FALSE );

            hEdit = GetDlgItem(hDlg, IDC_SELECT_CONTAINER_DONE);
            EnableWindow( hEdit, FALSE );

                // Filter group.
                // -------------
            hEdit = GetDlgItem(hDlg, IDC_SELECT_FILTER_BTN);
            EnableWindow( hEdit, FALSE );

            hEdit = GetDlgItem(hDlg, IDC_DESELECT_FILTER_BTN);
            EnableWindow( hEdit, FALSE );

            hEdit = GetDlgItem(hDlg, IDC_SELECT_FILTERS_DONE);
            EnableWindow( hEdit, FALSE );

            // Disable the "view container" button.
            hEdit = GetDlgItem(hDlg, IDOK);
            EnableWindow( hEdit, FALSE );

            // Disable the "configure filter" button.
            hEdit = GetDlgItem(hDlg, IDC_CONFIG_FILTER_BTN);
            EnableWindow( hEdit, FALSE );

            // Disable the "View selected filters" button.
            hEdit = GetDlgItem(hDlg, IDC_VIEW_SEL_FILTERS_BTN);
            EnableWindow( hEdit, FALSE );

            // Reset listbox selections.
            // -------------------------------------------------
            // Reset listbox selections to first in list.
            hEdit = GetDlgItem(hDlg, IDC_AVAIL_CONTAINER_LIST);
            SendMessage(hEdit, LB_SETCURSEL, 0, 0);

            hEdit = GetDlgItem(hDlg, IDC_AVAIL_FILTER_LIST);
            SendMessage(hEdit, LB_SETCURSEL, 0, 0);

            ////////////////////////////////////////////////////

            // Enable the Login button so use can try again.
            hEdit = GetDlgItem( hDlg, IDC_CONNECT );
            EnableWindow( hEdit, TRUE );

            return(TRUE);

        case IDC_CONNECT:
            // This is the "Connect" button.
            SendMessage( hDlg, WM_LOGIN_MSG, 0, 0 );
            return(TRUE);

        case IDC_ALL_DONE:
            // This is the "done" button.
            EndDialog( hDlg, IDOK );

            // And now terminate.
            return(TRUE);
        }
    }

    return(FALSE);

}

// ====================================================================
//
//  This is the view container dialogue that is brought up in response
//  to the "view container" button in the main dialogue.
//
//  We have a listbox for the sub-folders, which we need to populate.
//  We also have a group box containing details about the container.
//
//  The container handle is held in a global "ghContainer".
//
// ====================================================================

extern "C" BOOL CALLBACK ContainerViewDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam)
{

    // We need to fill in the container tag and the number of folders.
    // The first we'll put into a global for now (from MainDlg), the
    // latter is currently unavailable so we'll put in some gunk.

    HWND hEdit;
    static DWORD ctFolders = 0;
    SMS_STATUS stat;
    static CStringArray asID;           // Array of CStrings for IDs.
    CString sID;
    char szID[SMS_DATA_BUFF_SIZE+1];    // buffer to read in folder ID.
    DWORD dwI;
    HANDLE handle;                      // Temp holds handle of folder.
    int index;
    static CDWordArray folderList;      // List of opened folder handles.

    switch (message) {

    case WM_INITDIALOG:
        // Dialogue initialisation.

        // We have to open all the sub-folders, holding their
        // handles in some array. For each folder we want to extract
        // and display in the listbox it's ID.
        // NOTE: GetFolderCount not yet imnplemented for containers.
        // Instead I'll do GetNextFolder till I run out of folders.
        ctFolders = 0;

        for (ctFolders = 0; 1; ctFolders++) {

            stat = SmsGetNextFolder( ghContainer, F_ANY, &handle );
            if (stat != SMS_OK) {
                // assume no more folders for now
                break;
            }
            // Save the handle.
            folderList.Add( (DWORD)handle );
        }

        // Get the ID of each folder and insert into listbox.
        // --------------------------------------------------
        hEdit = GetDlgItem(hDlg, IDC_ID_LB);

        for (dwI = 0; dwI < ctFolders; dwI++) {

            // Retrieve the ID and store in the listbox.
            handle = (HANDLE)folderList.GetAt( dwI );
            stat = SmsGetFolderID( handle, szID );
            if (stat != SMS_OK) {
                wsprintf(szMsg, "GetID [%d] fails: %d", dwI, stat);
                MessageBox(hDlg, szMsg, "API problem", MB_OK);
            }
            asID.SetAtGrow( dwI, (CString)szID );

            SendMessage(hEdit, LB_ADDSTRING, 0, (LPARAM)szID);
        }

        // Select the first one in the list.
        SendMessage(hEdit, LB_SETCURSEL, 0, 0);

        // Set container name and number of folders.
        // -----------------------------------------
        SetDlgItemText( hDlg, IDC_WHAT_CONTAINER, (LPCSTR)gpCInfo->pszTag );
        SetDlgItemInt( hDlg, IDC_CTR_NUM_FOLDERS, ctFolders, FALSE );

        // If no folders disable the "View folder" button.
        // -----------------------------------------------
        if (ctFolders == 0) {
            hEdit = GetDlgItem(hDlg, IDOK);
            EnableWindow( hEdit, FALSE );
        }

        return(TRUE);

    case WM_CLOSE:
        // Close all folders.
        for (dwI = 0; dwI < ctFolders; dwI++) {
            handle = (HANDLE)folderList.GetAt( dwI );
            SmsCloseFolder( handle );
        }
        folderList.RemoveAll();

        // Clear the ID string array.
        asID.RemoveAll();

        EndDialog( hDlg, IDCANCEL );
        return(TRUE);

    case WM_SYSCOLORCHANGE:
        Ctl3dColorChange();
        return(TRUE);

    case WM_COMMAND:

        switch (wParam) {

        case IDOK:
            // Get the current selection and store it in a global
            // so that it can be picked up by FolderViewDlg.
            // --------------------------------------------------
            hEdit = GetDlgItem(hDlg, IDC_ID_LB);
            index = SendMessage(hEdit, LB_GETCURSEL, 0, 0);
            ghFolder = (HANDLE)folderList.GetAt( index );

            // Now we rewind the folder. This means that we can re-enumerate
            // the folder's properties (ID, scalars etc).
            SmsRewind( ghFolder, RW_ALL );

            // Enter the folder view dialogue.
            DialogBox(  _hInstance,
                        MAKEINTRESOURCE(IDD_FOLDER_VIEW),
                        hDlg,
                        (DLGPROC)FolderViewDlg );

            return(TRUE);

        case IDC_DONE:
            // We're done with this container. Close all the folders
            // and the container.
            for (dwI = 0; dwI < ctFolders; dwI++) {
                handle = (HANDLE)folderList.GetAt( dwI );
                // Close the folder
                SmsCloseFolder( handle );
            }
            folderList.RemoveAll();

            // Clear the ID string array.
            asID.RemoveAll();

            // Container closed in calling dlg.

            // This returns us to the previous dialogue.
            EndDialog(hDlg, IDOK);
            return(TRUE);
        }
    }

    return(FALSE);

}  /* ContainerViewDlg */

// ====================================================================
//
//  This is the view folder dialogue that is brought up in response to
//  the "view folder" button in the container dialogue.
//
//  We have a listbox for the sub-folders, which we need to populate.
//  We also have a group box containing details about the folder.
//
//  The handle to the current folder has been placed in the ghFolder
//  global.
//
//  When this dlgproc is entered via INITDIALOG we create a folder
//  frame that contains information that we have gleaned from the
//  folder, including the folder's handle. This frame is stacked.
//  When the 'view sub-folder" button is pressed we will simulate this
//  same action. A 'Done" button press causes us to free the current
//  frame and reload from the top of stack (if any).
//  The WM_CLOSE causes us to walk the stack frame freeing all frames.
//
// ====================================================================

// Macro for easier checking of returns from the APIs.
// Not sure about the NO_MORE_DATA. Check it.
#define CHKIT(h, str) if (stat != SMS_OK && stat != SMS_MORE_DATA && stat != SMS_NO_MORE_DATA) { \
    wsprintf(szMsg, "Handle: %08X %s fails: %d", h, str, stat); \
    MessageBox(hDlg, szMsg, "API Error", MB_OK); return((CFrame *)NULL); }

extern "C" BOOL CALLBACK FolderViewDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam)
{
    HWND hEdit;
    DWORD dwI;                          // Loop index.

    int index;
    CString sID;                        // Temp sub-folder's ID.
    static CFrame *pf = NULL;           // Current stack frame.
    char *pszTmp;
    char szBuff[SMS_DATA_BUFF_SIZE+1];  // Just for setting window title.

    static CFrameStack frameStack(hDlg);

    switch (message) {

    case WM_INITDIALOG:
        // Dialogue initialisation.

        // Set the window title to say "View of folder: <name>".
        SmsGetFolderID( ghFolder, szBuff );
        wsprintf(szMsg, "View of folder: %s", szBuff);
        SetWindowText( hDlg, szMsg );

        // Extract requisite information about the folder:
        // ID, type (use tag), scalar count, folder count, folder type count.
        pf = CreateFolderFrame( hDlg, ghFolder );
        if (pf) {
            // And display it.
            DisplayFolderFrame( hDlg, pf );

        } else {
            // This is an error.
            wsprintf(szMsg, "CreateFolderFrame failed for handle %08X", ghFolder);
            MessageBox(hDlg, szMsg, "An error", MB_OK);
            EndDialog( hDlg, IDCANCEL );
        }

        return(TRUE);

    case WM_CLOSE:
        // Delete current frame.
        // =====================
        // remove the ID strings from the string array
        for (dwI = 0; dwI < (DWORD)pf->aszID.GetSize(); dwI++) {
            pszTmp = (char *)pf->aszID[dwI];
            delete pszTmp;
        }
        // remove the handles
        delete pf->phFolders;

        delete pf;
        pf = NULL;

        // Free all previous frames.
        // =========================
        while (pf = frameStack.pop()) {
            // remove the ID strings from the string array
            for (dwI = 0; dwI < (DWORD)pf->aszID.GetSize(); dwI++) {
                pszTmp = (char *)pf->aszID[dwI];
                delete pszTmp;
            }
            // Remove the handles
            delete pf->phFolders;
            ghFolder = pf->hFolder;
            delete pf;

        }
        EndDialog( hDlg, IDCANCEL );
        return(TRUE);

    case WM_SYSCOLORCHANGE:
        Ctl3dColorChange();
        return(TRUE);

    case WM_COMMAND:

        switch (wParam) {

        case IDC_VIEW_FOLDER:
            // We're going down a level so stack our frame.
            frameStack.push( pf );

            // Get the current selection index. Use this to locate
            // the folder's handle.
            // We must save the existing folder handle in the frame
            // and re-instate it when we return.

            hEdit = GetDlgItem( hDlg, IDC_SUBFOLDER_LIST );
            index = SendMessage(hEdit, LB_GETCURSEL, 0, 0);
            ghFolder = pf->phFolders[index];

            // First clear the listbox entries (should clear other entries
            // as well, but I think they'll just get overwritten.)
            SendMessage(hEdit, LB_RESETCONTENT, 0, 0);

            // Create a new stack frame with all the details
            // that we did in the INITDLG.
            pf = CreateFolderFrame( hDlg, ghFolder );
            if (pf) {
                // And display it.
                DisplayFolderFrame( hDlg, pf );

            } else {
                // This is an error.
                wsprintf(szMsg, "CreateFolderFrame failed for handle %08X", ghFolder);
                MessageBox(hDlg, szMsg, "An error", MB_OK);
                EndDialog( hDlg, IDCANCEL );
            }

            // Select the first one in the list and set focus here.
            SendMessage(hEdit, LB_SETCURSEL, 0, 0);
            SetFocus(hEdit);

            return(TRUE);

        case IDC_VIEW_SCALARS:
            // So we can enumerate scalars from the beginning.
            SmsRewind( ghFolder, RW_ALL );

            DialogBox( _hInstance,
                       MAKEINTRESOURCE(IDD_SCALARS),
                       hDlg,
                       (DLGPROC)ScalarViewDlg );
            return(TRUE);

        case IDC_VIEW_EXPRESSION:
            // So we can enumerate tokens in expression from the beginning.
            SmsRewind( ghFolder, RW_ALL );

            DialogBox( _hInstance,
                       MAKEINTRESOURCE(IDD_EXPRESSION),
                       hDlg,
                       (DLGPROC)ExpressionViewDlg );
            return(TRUE);

        case IDC_BACK:
            // This will take us back one level.
            // We have to free all data from the current stack frame
            // and then restore the previous one.
            // When the stack frame is empty we return to the previous
            // dialogue.

            // Delete current frame.
            // =====================
            // remove the ID strings from the string array
            for (dwI = 0; dwI < (DWORD)pf->aszID.GetSize(); dwI++) {
                pszTmp = (char *)pf->aszID[dwI];
                delete pszTmp;
            }
            // remove the handles
            delete pf->phFolders;

            delete pf;
            pf = NULL;

            // Restore the previous frame.
            // ===========================
            pf = frameStack.pop();
            if (pf == NULL) {
                // Nothing there, return to previous dialogue.
                EndDialog(hDlg, IDOK);
                return(TRUE);
            }

            DisplayFolderFrame( hDlg, pf );
            // restore folder handle
            ghFolder = pf->hFolder;

            return(TRUE);
        }
    }

    return(FALSE);

}   /* FolderViewDlg */

// ====================================================================
//
//  Creates a frame for this folder.
//  Note: We could check if we have already stacked this folder.
//
// ====================================================================

CFrame *CreateFolderFrame( HWND hDlg, HANDLE hFolder )
{
    HANDLE hSubFolder = NULL;           // Temp reference to sub-folder.
    CFrame *pFrame = new CFrame;
    SMS_STATUS stat;

    SmsRewind( hFolder, RW_ALL );

    // Save the folder's handle.
    pFrame->hFolder = ghFolder;

    stat = SmsGetFolderID( hFolder, pFrame->szFolderID );
    CHKIT(hFolder, "GetFolderID");

    stat = SmsGetFolderType( hFolder, &pFrame->fType, pFrame->szfType );
    CHKIT(hFolder, "GetFolderType");

    stat = SmsGetScalarCount( hFolder, &pFrame->ctScalars );
    CHKIT(hFolder, "GetScalarCount");

    stat = SmsGetFolderCount( hFolder, F_ANY, &pFrame->ctFolders );
    CHKIT(hFolder, "GetFolderCount");

    stat = SmsEnumFolderTypes( hFolder, NULL, &pFrame->ctFolderTypes );
    CHKIT(hFolder, "EnumFolderTypes");

    // 02-May-96. Add expression display (inventory folder).
    stat = SmsGetTokenCount( hFolder, &pFrame->ctTokens );
//    CHKIT(hFolder, "GetTokenCount");

    // Enumerate the sub-folders.
    // -----------------------------------------
    char szBuff[SMS_DATA_BUFF_SIZE+1];

    // Allocate a handle for each folder.
    pFrame->phFolders = (HANDLE *)new HANDLE[pFrame->ctFolders];
    char *pszTmp;

    DWORD dwI;
    for (dwI = 0; dwI < pFrame->ctFolders; dwI++) {
        // Rewind the folder - just in case.

        stat = SmsGetNextFolder( hFolder, F_ANY, &hSubFolder );
        CHKIT(hFolder, "GetNextFolder");
        pFrame->phFolders[dwI] = hSubFolder;

        // Get folder's ID and add it to our cstring array and to the listbox.
        // We want it saved since we will have to redraw the listbox when
        // drilling down and then up in the folder list.
        stat = SmsGetFolderID( hSubFolder, szBuff );
        CHKIT(hFolder, "GetFolderID");
        pszTmp = new char[strlen(szBuff)+1];        // deleted when we do a BACK or CLOSE
        strcpy(pszTmp, szBuff);
        pFrame->aszID.Add( (CObject *)pszTmp );

    }

    return(pFrame);

}  /* CreateFolderFrame */

// ====================================================================
//
//  Display the given folder frame. This is part of the Folder view
//  dialogue.
//
// ====================================================================

void DisplayFolderFrame( HWND hDlg, CFrame *pFrame )
{
    HWND hEdit;

    // First of all lets clear the listbox (whether needs it or not).
    hEdit = GetDlgItem( hDlg, IDC_SUBFOLDER_LIST );
    SendMessage(hEdit, LB_RESETCONTENT, 0, 0);

    SetDlgItemText( hDlg, IDC_FOLDERID, (LPCSTR)pFrame->szFolderID);
    SetDlgItemText( hDlg, IDC_FOLDERTYPE, (LPCSTR)pFrame->szfType);
    SetDlgItemInt( hDlg,  IDC_FOLDERSC_CT, pFrame->ctScalars, FALSE);
    SetDlgItemInt( hDlg,  IDC_FOLDERFOLDER_CT, pFrame->ctFolders, FALSE);
    SetDlgItemInt( hDlg,  IDC_FOLDERFT_CT, pFrame->ctFolderTypes, FALSE);

    // Display the sub-folder IDs.
    // -----------------------------------------
    hEdit = GetDlgItem( hDlg, IDC_SUBFOLDER_LIST );
    char *pszTmp;

    for (DWORD dwI = 0; dwI < pFrame->ctFolders; dwI++) {

        pszTmp = (char *)pFrame->aszID.GetAt( dwI );
        SendMessage(hEdit, LB_ADDSTRING, 0, (LPARAM)pszTmp);
    }

    // If there are no sub-folders or no scalars then
    // disable the appropriate buttons.
    // -----------------------------------------------------
    hEdit = GetDlgItem( hDlg, IDC_VIEW_SCALARS );
    if (pFrame->ctScalars == 0) {
        EnableWindow( hEdit, FALSE );
    } else {
        EnableWindow( hEdit, TRUE );
    }

    hEdit = GetDlgItem( hDlg, IDC_VIEW_FOLDER );
    if (pFrame->ctFolders == 0) {
        EnableWindow( hEdit, FALSE );
    } else {
        EnableWindow( hEdit, TRUE );
    }

    // Select the first one in the list and set focus here.
    hEdit = GetDlgItem( hDlg, IDC_SUBFOLDER_LIST );
    SendMessage(hEdit, LB_SETCURSEL, 0, 0);

    // [02-May-96] If there are no tokens in this folder (ie it doesn't
    // have an expression) then disable the 'View expression' button.
    // ----------------------------------------------------------------
    hEdit = GetDlgItem( hDlg, IDC_VIEW_EXPRESSION );
    if (pFrame->ctTokens == 0) {
        EnableWindow( hEdit, FALSE );
    } else {
        EnableWindow( hEdit, TRUE );
    }
}

// ==========================================================================
//
//  Dialogue proc for the scalar view.
//
//  We need the hFolder, and preferably it's ID as well. We can put that
//  in the dlg title.
//  The hFolder is needed since we're going to enumerate its scalars.
//  We'll construct 2 string arrays, one for the scalar name, the other
//  for the value (maybe put in type as well later).
//
// ==========================================================================

extern "C" BOOL CALLBACK ScalarViewDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam)
{

    HWND hEditName;                 // Explicit handle to name listbox.
    HWND hEditVal;                  // Explicit handle to value listbox.
    HWND hEditType;                 // Explicit handle to type listbox.
    int index;
    SCALAR scalar;
    char szName[SMS_DATA_BUFF_SIZE+1];      // Scalar name.
    char szValue[SMS_DATA_BUFF_SIZE+1];     // String value.
    BYTE byValue[SMS_DATA_BUFF_SIZE];       // Binary value.
    scalar.pszName  = szName;
    scalar.pszValue = szValue;
    scalar.pValue   = byValue;
    SMS_STATUS stat;
    char szBuff[SMS_DATA_BUFF_SIZE+1];

    char *pszVal;                   // Point to scalar's value.
    char *pszType;                  // Point to scalar's type.

    CTime tTime;                    // For time scalars
    CString sTime;                  //  ditto

    switch (message) {

    case WM_INITDIALOG:
        // Dialogue initialisation.

        // 1. Set the window title to say "Scalars for folder <name>".
        SmsGetFolderID( ghFolder, szBuff );
        wsprintf(szMsg, "Scalars for folder %s", szBuff);
        SetWindowText( hDlg, szMsg );

        // 2. Enumerate the scalars storing the strings (strings only at
        // the moment) in 2 string lists: name and value.

        hEditName = GetDlgItem(hDlg, IDC_SCNAME_LB);
        hEditVal  = GetDlgItem(hDlg, IDC_SCVAL_LB);
        hEditType = GetDlgItem(hDlg, IDC_SCTYPE_LB);

        while (1) {
            scalar.dwLen = sizeof(szValue)-1;

            stat = SmsGetNextScalar( ghFolder, &scalar );
            if (stat != SMS_OK) {
                break;
            }

            // Copy the name and value into the listboxes - don't think I'll
            // bother with saving them though.

            switch (scalar.scType) {
            case SCALAR_STRING:
                pszVal = scalar.pszValue;
                pszType = "String";
                break;

            case SCALAR_INT:
                // Convert to string.
                wsprintf(szBuff, "%d", scalar.dwValue);
                pszVal = szBuff;
                pszType = "Integer";
                break;

            case SCALAR_TIME:
                // Check if we have a string equivalence. If so use it.
                if (scalar.bStringEquivalence) {
                    pszVal = scalar.pszValue;

                } else {
                    // Use time functions to make a string out of it.
                    // NOTE: we really need to get rid of trailing newline.
                    tTime = scalar.tValue;
                    sTime = tTime.Format( "%m/%d/%y %I:%M %p" );
                    pszVal = (char *)(const char *)sTime;
                }
                pszType = "Time";
                break;

            case SCALAR_BINARY:
                // Just print out its length.
                pszType = "Binary";
                wsprintf( szBuff, "Length of binary data: %ld", scalar.dwLen);
                pszVal = szBuff;
                break;

            default:
                wsprintf(szBuff, "UNKNOWN TYPE");
                pszVal = szBuff;
                pszType = "Unknown type";
                break;
            }

            // Scalar name.
            SendMessage(hEditName, LB_ADDSTRING, 0, (LPARAM)scalar.pszName);

            // Scalar type.
            SendMessage(hEditType, LB_ADDSTRING, 0, (LPARAM)pszType);

            // Scalar value.
            SendMessage(hEditVal,  LB_ADDSTRING, 0, (LPARAM)pszVal);

        }

        // Set current selections to the first in each list.
        SendMessage(hEditName, LB_SETCURSEL, (WPARAM)0, 0);
        SendMessage(hEditType, LB_SETCURSEL, (WPARAM)0, 0);
        SendMessage(hEditVal,  LB_SETCURSEL, (WPARAM)0, 0);

        // Set focus on this control.
        SetFocus(hEditName);

        return(TRUE);

    case WM_CLOSE:
        // Do I need to delete the strings from the listboxes??
        EndDialog( hDlg, IDCANCEL );
        return(TRUE);

    case WM_SYSCOLORCHANGE:
        Ctl3dColorChange();
        return(TRUE);

    case WM_COMMAND:

        // Whatever listbox is selected we want to select the
        // corresponding entry in the other.
        if (HIWORD(wParam) == LBN_SELCHANGE) {

            index = SendMessage((HWND)lParam, LB_GETCURSEL, 0, 0);

            hEditName = GetDlgItem(hDlg, IDC_SCNAME_LB);
            hEditType = GetDlgItem(hDlg, IDC_SCTYPE_LB);
            hEditVal  = GetDlgItem(hDlg, IDC_SCVAL_LB);

            // Select the corresponding entry all listboxes.
            SendMessage(hEditName, LB_SETCURSEL, (WPARAM)index, 0);
            SendMessage(hEditType, LB_SETCURSEL, (WPARAM)index, 0);
            SendMessage(hEditVal,  LB_SETCURSEL, (WPARAM)index, 0);

            return(TRUE);
        }

        switch (wParam) {

        case IDOK:
            // Do I need to delete the strings from the listboxes??
            EndDialog(hDlg, IDOK);
            return(TRUE);

        }
    }

    return(FALSE);

}  /* ScalarViewDlg */

// ==========================================================================
//
//  Dialogue proc for the expression view.
//##//
//  We need the hFolder, and preferably it's ID as well. We can put that
//  in the dlg title.
//  The hFolder is needed since we're going to enumerate its scalars.
//  We'll construct 2 string arrays, one for the scalar name, the other
//  for the value (maybe put in type as well later).
//
// ==========================================================================

extern "C" BOOL CALLBACK ExpressionViewDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam)
{

    HWND hEditToken;                // Explicit handle to token listbox.
    char szBuff[SMS_DATA_BUFF_SIZE+1];  // Just for setting window title.
    DWORD dwLoop = 0;

    switch (message) {

    case WM_INITDIALOG:
        // Dialogue initialisation.

        // 1. Set the window title to say "Expression for folder <name>".
        SmsGetFolderID( ghFolder, szBuff );
        wsprintf(szMsg, "Expression for folder %s", szBuff);
        SetWindowText( hDlg, szMsg );

        // 2. Enumerate the tokens storing them (ie their display strings)
        // into a string list.

        hEditToken = GetDlgItem(hDlg, IDC_TOKENS_LB);

        // Clear the token.
        // ================
        TOKEN Token;
        DWORD ctTokens;

        // Loop through its tokens, display the token string in the
        // edit control.
        // ========================================================
        SmsGetTokenCount( ghFolder, &ctTokens );
        for (dwLoop = 0; dwLoop < ctTokens; dwLoop++) {

            memset(&Token, 0, sizeof(TOKEN));
            Token.bIndent = 1;                  // Request indentation.
            // Get token.
            SmsGetToken( ghFolder, dwLoop, &Token );

            SendMessage(hEditToken, LB_ADDSTRING, 0, (LPARAM)Token.szTokenString);
        }

        // Set focus on this control.
        SetFocus(hEditToken);

        return(TRUE);

    case WM_CLOSE:
        // Do I need to delete the strings from the listboxes??
        EndDialog( hDlg, IDCANCEL );
        return(TRUE);

    case WM_SYSCOLORCHANGE:
        Ctl3dColorChange();
        return(TRUE);

    case WM_COMMAND:

        switch (wParam) {

        case IDOK:
            // Do I need to delete the strings from the listboxes??
            EndDialog(hDlg, IDOK);
            return(TRUE);

        }
#ifdef XXX
        // Whatever listbox is selected we want to select the
        // corresponding entry in the other.
        if (HIWORD(wParam) == LBN_SELCHANGE) {

            index = SendMessage((HWND)lParam, LB_GETCURSEL, 0, 0);

            hEditName = GetDlgItem(hDlg, IDC_SCNAME_LB);
            hEditType = GetDlgItem(hDlg, IDC_SCTYPE_LB);
            hEditVal  = GetDlgItem(hDlg, IDC_SCVAL_LB);

            // Select the corresponding entry all listboxes.
            SendMessage(hEditName, LB_SETCURSEL, (WPARAM)index, 0);
            SendMessage(hEditType, LB_SETCURSEL, (WPARAM)index, 0);
            SendMessage(hEditVal,  LB_SETCURSEL, (WPARAM)index, 0);

            return(TRUE);
        }
#else
        return(TRUE);
#endif  // XXX

        switch (wParam) {

        case IDOK:
            // Do I need to delete the strings from the listboxes??
            EndDialog(hDlg, IDOK);
            return(TRUE);

        }
    }

    return(FALSE);

}  /* ExpressionViewDlg */

// ====================================================================
//
// This is the dialogue that is brought up in response to the
// config filter button in the main dialogue.
//
//  A MY_FILTER_INFO struct is passed via a global. This
//  contains a description of the filter template (ie a FILTER_INFO
//  structure) and the handle of a particular filter. By examining
//  the filter info we determine how the filter's tokens are composed.

//  The dialogue itself has the group name controls set to not-
//  visible. If this is a Token2 then we make them visible.
//
//  For each token there is a name, a value and an operator.
//  I'll hard-code the operators for the moment.
//  Not all tokens need the value and operator, this should be
//  determined by the token processing.
//  The nature of the value is also dependent on the token, it's
//  (currently) either a DWORD or a string.
//
//  We create the filter and do the add tokens here. The filter
//  handle is stored in the MY_FILTER_INFO struct. When we
//  return the invoker of this dialogue will do a SetFilter.
//
// ====================================================================

extern "C" BOOL CALLBACK ConfigFilterDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam)
{

    HWND hEdit;
    FILTER_INFO *pFltr = NULL;
    static MY_FILTER_INFO *pMyF;
    int index;
    SMS_STATUS stat;
    static HANDLE hFilter = NULL;
    ANDOR opAndOr;
    TOKEN Token;                        // Token to add into filter.

    // We only know what fields the filter supports when we check the
    // TOKEN_INFO structure. The fields there that contain strings
    // tell us a) that the field is used by the filter, and b) what
    // the label should be for that field.
    // For simplicity we have two dialogues, we use one dialogue proc
    // to process both. This is only because of the difficulties
    // involved in moving edit controls around.
    // Different filter types will use different combinations of the
    // edit controls.
    // ===============================================================
    static BOOL bHasName;
    static BOOL bHasValue;
    static BOOL bHasGroupClass;
    static BOOL bHasArch;
    static BOOL bHasOp;
    static BOOL bHasAttribute;

    int iLoop;                          // Loop index.

    switch (message) {

    case WM_INITDIALOG:
        // Dialogue initialisation.
        pMyF = gpMyFilter;
        pFltr = pMyF->pF;

        // Look at the filter to determine what fields it will use.
        // make the appropriate fields visible, and set their labels.
        // ==========================================================
        bHasName       = FALSE;
        bHasValue      = FALSE;
        bHasGroupClass = FALSE;
        bHasArch       = FALSE;
        bHasOp         = FALSE;
        bHasAttribute  = FALSE;

        // Clear the token.
        // ================
        memset(&Token, 0, sizeof(TOKEN));

        // Name.
        if (strlen(pFltr->szName)) {
            hEdit = GetDlgItem(hDlg, IDC_NAME_STATIC);
            SetWindowText( hEdit, pFltr->szName );
            ShowWindow( hEdit, SW_SHOWNORMAL );

            hEdit = GetDlgItem(hDlg, IDC_NAME);
            ShowWindow( hEdit, SW_SHOWNORMAL );
            bHasName = TRUE;
        }

        // Value.
        if (strlen(pFltr->szValue)) {
            hEdit = GetDlgItem(hDlg, IDC_VALUE_STATIC);
            SetWindowText( hEdit, pFltr->szValue );
            ShowWindow( hEdit, SW_SHOWNORMAL );

            hEdit = GetDlgItem(hDlg, IDC_VALUE);
            ShowWindow( hEdit, SW_SHOWNORMAL );
            bHasValue = TRUE;
        }

        // GroupClass.
        if (strlen(pFltr->szGroupClass)) {
            hEdit = GetDlgItem(hDlg, IDC_GROUPCLASS_STATIC);
            SetWindowText( hEdit, pFltr->szGroupClass );
            ShowWindow( hEdit, SW_SHOWNORMAL );

            hEdit = GetDlgItem(hDlg, IDC_GROUPCLASS);
            ShowWindow( hEdit, SW_SHOWNORMAL );
            bHasGroupClass = TRUE;
        }

        // AttributeName.
        if (strlen(pFltr->szAttributeName)) {
            hEdit = GetDlgItem(hDlg, IDC_ATTRIBUTE_STATIC);
            SetWindowText( hEdit, pFltr->szAttributeName );
            ShowWindow( hEdit, SW_SHOWNORMAL );

            hEdit = GetDlgItem(hDlg, IDC_ATTRIBUTE);
            ShowWindow( hEdit, SW_SHOWNORMAL );
            bHasAttribute = TRUE;
        }

        // Architecture.
        if (strlen(pFltr->szArchitecture)) {
            hEdit = GetDlgItem(hDlg, IDC_ARCHITECTURE_STATIC);
            SetWindowText( hEdit, pFltr->szArchitecture );
            ShowWindow( hEdit, SW_SHOWNORMAL );

            hEdit = GetDlgItem(hDlg, IDC_ARCHITECTURE);
            ShowWindow( hEdit, SW_SHOWNORMAL );
            bHasArch = TRUE;
        }

        // Operator.
        if (strlen(pFltr->szOperator)) {
            hEdit = GetDlgItem(hDlg, IDC_OPERATOR_STATIC);
            SetWindowText( hEdit, pFltr->szOperator );
            ShowWindow( hEdit, SW_SHOWNORMAL );

            hEdit = GetDlgItem(hDlg, IDC_OP_CB);
            ShowWindow( hEdit, SW_SHOWNORMAL );
            bHasOp = TRUE;
        }

        // Create the filter.
        // If all goes ok then this will be stored in
        // gpMyFilter, and used in the main dlg.
        // -----------------------------------------------------
        stat = SmsCreateFilter( pFltr->filterType, ghConnect, &hFilter );
        if (stat != SMS_OK) {
            wsprintf(szMsg, "OpenFilter fails: %d", stat);
            MessageBox(hDlg, szMsg, "Whoops", MB_OK);
        }

        // Insert the operators that we support into the combobox.
        hEdit = GetDlgItem(hDlg, IDC_OP_CB);
        for (iLoop = 0; iLoop < QOP_LAST; iLoop++) {
            SendMessage(hEdit, CB_ADDSTRING, 0, (LPARAM)OpName[iLoop]);
        }

        // Default to selecting string equals.
        SendMessage(hEdit, CB_SETCURSEL, QOP_STR_EQ, 0);

        // Modify the window text to "Configure <type> filter"
        // We access the global MyFilter pointer, this points to
        // the filter type that we're working with.
        wsprintf(szMsg, "Configuring %s", pFltr->szTag);
        SetWindowText( hDlg, szMsg );

        // Disable the "Done" button.
        hEdit = GetDlgItem(hDlg, IDOK);
        EnableWindow( hEdit, FALSE );

        return(TRUE);

    case WM_CLOSE:

EndDialog( hDlg, IDCANCEL );
        return(TRUE);

    case WM_SYSCOLORCHANGE:
        Ctl3dColorChange();
        return(TRUE);

    case WM_COMMAND:

        switch (wParam) {

        case IDC_ADDAND_BTN:
        case IDC_ADDOR_BTN:
            if (wParam == IDC_ADDAND_BTN) {
                opAndOr = OP_AND;
            } else {
                opAndOr = OP_OR;
            }

            // Extract the fields. We recorded in INITDIALOG which fields
            // we have.
                // Name
            if (bHasName) {
                GetDlgItemText(hDlg, IDC_NAME, (LPTSTR)Token.szName, sizeof(Token.szName));
            }

                // Value (string first)
            if (bHasValue) {
                GetDlgItemText(hDlg, IDC_VALUE, (LPTSTR)Token.szValue, sizeof(Token.szValue));
            }

                // Operator. Get the index of the selection in the listbox
                // and convert to operator code.
            if (bHasOp) {
                hEdit = GetDlgItem(hDlg, IDC_OP_CB);
                index = SendMessage(hEdit, CB_GETCURSEL, 0, 0);
                Token.dwOp = index;
            }

                // GroupClass
            if (bHasGroupClass) {
                GetDlgItemText(hDlg, IDC_GROUPCLASS, (LPTSTR)Token.szGroupClass, sizeof(Token.szGroupClass));
            }

                // Attribute name.
            if (bHasAttribute) {
                GetDlgItemText(hDlg, IDC_ATTRIBUTE, (LPTSTR)Token.szAttributeName, sizeof(Token.szAttributeName));
            }

                // Architecture
            if (bHasArch) {
                GetDlgItemText(hDlg, IDC_ARCHITECTURE, (LPTSTR)Token.szArchitecture, sizeof(Token.szArchitecture));
            }
            // Add the token into the filter.
            // -2 for the index parameter means add the token at the end
            // of the filter's expression.
            // ---------------------------------------------------------
            pMyF = gpMyFilter;
            pFltr = pMyF->pF;

            stat = SmsAddToken( hFilter, opAndOr, &Token, -2 ); // -2 = at end.
            if (stat != SMS_OK) {
               wsprintf(szMsg, "AddToken fails: %d", stat);
               MessageBox(hDlg, szMsg, "Whoops", MB_OK);
            }

            // Clear the edit controls.
            // ------------------------
            SetDlgItemText( hDlg, IDC_NAME        , "" );
            SetDlgItemText( hDlg, IDC_VALUE       , "" );
            SetDlgItemText( hDlg, IDC_GROUPCLASS  , "" );
            SetDlgItemText( hDlg, IDC_ATTRIBUTE   , "" );
            SetDlgItemText( hDlg, IDC_ARCHITECTURE, "" );

            // Enable the "Done" button.
            // -------------------------
            hEdit = GetDlgItem(hDlg, IDOK);
            EnableWindow( hEdit, TRUE );

            return(TRUE);

        case IDOK:
            // We need to set the filter handle in the MYFILTER.
            pMyF = gpMyFilter;
            pMyF->hFilter = hFilter;
            hFilter = NULL;

            EndDialog(hDlg, IDOK);
            return(TRUE);

        }
    }

    return(FALSE);

}  /* SelectFilterDlg */

// ====================================================================
//
// This is the dialogue that is brought up in response to the
// view selected filters button in the main dialogue.
// It enables the user to view the filters that they have
// created and applied to the container. It shows the use of the
// SmsRetrieveFilters and SmsGetToken APIs.
//
// ====================================================================

extern "C" BOOL CALLBACK ViewSelFiltersDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam)
{

    HWND hEdit, hEdit1;
    SMS_STATUS stat;
    static DWORD ctFilters = 0;     // How many filters there are in the
                                    // container.
    DWORD ctTokens;                 // How many tokens in filter.
    static HANDLE *ahFilters;       // Retrieve an array of handles.
    DWORD dwLoop;                   // Loop index.
    TOKEN Token;
    int index;                      // Get index of selected filter.
    char szBuff[256];               // Build up filter tokens here.

    switch (message) {

    case WM_INITDIALOG:
        // Dialogue initialisation.

        // Get the number of filters, then allocate spae and call again.
        // =============================================================
        stat = SmsGetAllFilters( ghContainer, NULL, &ctFilters );

        ahFilters = new HANDLE[ctFilters];
        stat = SmsGetAllFilters( ghContainer, ahFilters, &ctFilters );

        // Build up the list of filters. We want to populate the listbox
        // with the filter tags. When one of these is selected (via the
        // view button) we'll display its contents in the edit control box.

            // Populate the listbox.
        hEdit = GetDlgItem(hDlg, IDC_SEL_FILTERS_LB);

        HANDLE hFilter;
        DWORD filterType;               // Current filter's type and tag
        char szFilterTag[50];           // ...

        for (dwLoop = 0; dwLoop < ctFilters; dwLoop++) {
            hFilter = ahFilters[dwLoop];
            SmsGetFilterType( hFilter, &filterType, szFilterTag );

            SendMessage(hEdit, LB_ADDSTRING, 0, (LPARAM)szFilterTag);
        }

            // Select the first one in the list.
        SendMessage(hEdit, LB_SETCURSEL, 0, 0);

            // Send the listbox a focus message. This should cause him
            // to display the contents of the specified filter.
        if (ctFilters > 0) {
            WPARAM wp = MAKEWPARAM( IDC_SEL_FILTERS_LB, LBN_SELCHANGE );
            SendMessage(hDlg, WM_COMMAND, wp, (LPARAM)hEdit);
        }
        return(TRUE);

    case WM_CLOSE:
        // Close all the filters that we retrieved in SmsGetAllFilters.
        for (dwLoop = 0; dwLoop < ctFilters; dwLoop++) {
            hFilter = ahFilters[dwLoop];
            SmsCloseFilter( hFilter );
        }
        delete ahFilters;

        EndDialog( hDlg, IDCANCEL );
        return(TRUE);

    case WM_SYSCOLORCHANGE:
        Ctl3dColorChange();
        return(TRUE);

    case WM_COMMAND:

        if ((HIWORD(wParam) == LBN_SELCHANGE) &&
            (LOWORD(wParam) == IDC_SEL_FILTERS_LB)) {

            hEdit1 = (HWND)lParam;

            // Clear the contents of the view listbox.
            // =======================================
            szBuff[0] = '\0';
            hEdit = GetDlgItem(hDlg, IDC_VIEWFILTER);
            SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)"");

            // Find out which filter we've selected and get it's handle.
            // =========================================================
            index = SendMessage(hEdit1, LB_GETCURSEL, 0, 0);
            hFilter = ahFilters[index];

            // Clear the token.
            // ================
            memset(&Token, 0, sizeof(TOKEN));

            // Loop through its tokens, display the token string in the
            // edit control.
            // ========================================================
            SmsGetTokenCount( hFilter, &ctTokens );
            for (dwLoop = 0; dwLoop < ctTokens; dwLoop++) {
                // Get token.
                SmsGetToken( hFilter, dwLoop, &Token );

                strcat( szBuff, Token.szTokenString );
                strcat( szBuff, " ");
            }
            SendMessage(hEdit, WM_SETTEXT, 0, (LPARAM)szBuff);

            return(TRUE);
        }

        switch (wParam) {

        case IDOK:
            EndDialog(hDlg, IDOK);
            return(TRUE);

        }
    }

    return(FALSE);

}  /* ViewSelFiltersDlg */

// ====================================================================
//
//  CFrameStack class.
//
//  This is only used in the folder view dialogue. It enables us to
//  descend into sub-folders but keep this current folder's data
//  around.
//  When we want to look at a sub-folder we just pop the current
//  folder's data onto the stack. when we return from a sub-folder we
//  just pop the stack.
//
// ====================================================================
CFrameStack::CFrameStack(HWND hDlg)
{
    sp = 0;
    _hDlg = hDlg;
}

CFrameStack::~CFrameStack()
{
    // Clear all objects from stack and delete them.
    CFrame *pFrame;
    for (int i = 0; i < stack.GetSize(); i++) {
        pFrame = (CFrame *)stack[i];
        delete pFrame;
    }
    stack.RemoveAll();
}

void CFrameStack::push( CFrame *pFrame )
{
    stack.Add( (CObject *)pFrame );
    sp++;

}

CFrame *CFrameStack::pop()
{
    if (sp == 0) {
        return(NULL);
    }
    --sp;
    CFrame *pFrame = (CFrame *)stack.GetAt( sp );
    stack.RemoveAt( sp );

    return(pFrame);
}

//Debug: dump the stack
void CFrameStack::Dump( const char *psz )
{
    CFrame *pf;
    char *p = szMsg;

    wsprintf(p, "sp=%d GetUpperBound=%d\n", sp-1, stack.GetUpperBound());
    p += strlen(p);

    for (int i = 0; i <= sp-1; i++) {
        pf = (CFrame *)stack[i];
        wsprintf(p, "%d ID: %s\n", i, pf->szFolderID);
        p += strlen(p);
    }
    MessageBox( _hDlg, szMsg, psz, MB_OK );
}

#ifdef DEBUG

// ====================================================================
//
//  Debug log utility.
//  Here so people can see an example of such a thing.
//
// eg: Log("WM_TIMER GetWindowRect fails: %d", GetLastError());
//
// ====================================================================
int Log(char *fmt, ...)
{
    char *buffer = new char[2048];

    va_list argptr;
    int cnt;
    va_start(argptr, fmt);
    cnt = vsprintf(buffer, fmt, argptr);
    va_end(argptr);

    FILE *fp;

    fp = fopen("browser.log", "at");

    if (!fp) {
        delete buffer;
        return( 0 );
    }

    time_t now;
    time(&now);
    struct tm *local = localtime(&now);

    fprintf(fp, "%s\n", buffer);

    BOOL killfile = FALSE;

    if (ftell(fp) > 0x10000) {
        killfile = TRUE;   // 64K Limit on log
    }

    fclose(fp);

    if (killfile) {
        remove("log.log");
    }

    delete buffer;

    return( cnt );
}
#endif  // DEBUG

/* EOF: browser.cpp */


BROWSER.H

//***************************************************************************
//
//  browser.h
//
//  Sample Windows app for SMS API.
//
//  This program illustrates the use of most of the SMS API set in order to
//  implement a generic Objectity browser.
//  Since this program is statically linked with smsapi.lib/dll it acts as
//  an SMS objectity browser only.
//
//  Using this program it is possible to view all containers and folders
//  that are implemented in the SMS API.
//
//  Jonathan Shuval     September 1994.
//
//***************************************************************************

// ==========================================================================
//
//      Function prototypes.
//
// ==========================================================================

extern "C" BOOL CALLBACK LoginDlg(  HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam);

extern "C" BOOL CALLBACK MainDlg(   HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam);

extern "C" BOOL CALLBACK ContainerViewDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam);

extern "C" BOOL CALLBACK ConfigFilterDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam);

extern "C" BOOL CALLBACK FolderViewDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam);

extern "C" BOOL CALLBACK ScalarViewDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam);

extern "C" BOOL CALLBACK ExpressionViewDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam);

extern "C" BOOL CALLBACK ViewSelFiltersDlg(
                                    HWND hDlg,
                                    UINT message,
                                    WPARAM wParam,
                                    LPARAM lParam);

// ==========================================================================
//
//      Globals.
//
// ==========================================================================

char szMsg[100];            // Message buffer. Used in formatting MessageBox
                            // strings.

static HINSTANCE _hInstance;

// Get the available filter types and insert them into the menu
FILTER_INFO    *gpAvailFilterList = NULL;
DWORD ctFilters = 0;

// =============================================================
// Define a structure that we use in the main dlg for selected
// filters.
// This is the same as FILTER_INFO but we have an additional
// field for the filter handle. A handle of NULL means that the
// filter has not been opened.
typedef struct _MY_FILTER_INFO {
    FILTER_INFO *pF;
    HANDLE hFilter;
} MY_FILTER_INFO;

// =============================================================

// Connection handle, make global.
HANDLE ghConnect;

// Handle of current top-level folder/container.
HANDLE ghFolder;

// Used to connect to datasource. Global so that LoginDlg can fill it in.
DATASOURCE gdsParams;
char gszServer[33];
char gszDbName[33];
char gszUserName[33];
char gszPasswd[33];

FOLDER_INFO *gpCInfo;
MY_FILTER_INFO *gpMyFilter;

HANDLE ghFilter;        // filter handle. Set in the filter stuff.
                        // Use in main dlg.

HANDLE ghContainer;                 // Container handle.

// [02-May-96] Changed from struct into class.
// Used in folder view.
class CFrame {
public:
    CFrame() {
        // Clear everything.
        hFolder = NULL;
        memset(szFolderID, 0, 256);
        memset(szfType, 0, 256);
        fType = 0;
        ctScalars = 0;
        ctFolders = 0;
        ctFolderTypes = 0;
        phFolders = NULL;
        ctTokens = 0;
    };
    ~CFrame() {};

    HANDLE   hFolder;           // Make local copy of current folder.
    char     szFolderID[256];   // This folder's ID.
    char     szfType[256];      // Folder tag.
    DWORD    fType;             // Folder type.
    DWORD    ctScalars;         // Number of scalars.
    DWORD    ctFolders;         // Number of sub-folders.
    DWORD    ctFolderTypes;     // Number of sub-folder types.
    HANDLE   *phFolders;        // Array of handles to the folders.
    CObArray aszID;             // Array of sub-folder's IDs.
    // 02-May-96
    DWORD    ctTokens;          // Num of tokens in expression (if supported).
};
CFrame *CreateFolderFrame( HWND hDlg, HANDLE hFolder );

// Display the given folder frame. This is part of the Folder view dialogue.
void DisplayFolderFrame( HWND hDlg, CFrame *pFrame );

class CFrameStack
{
public:
    CFrameStack(HWND hDlg);

    ~CFrameStack();

    void push( CFrame *pFrame );

    CFrame *pop();

    void Dump( const char *psz );   // debug

    int sp;
    CObArray stack;
    HWND _hDlg;

};  /* CFrameStack */

/* EOF: browser.h */


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by BROWSER.RC
//
#define IDD_AVAILABLE_FILTERS           103
#define IDD_CONFIG_FILTER               103
#define IDD_CONFIG_FILTER5              103
#define IDD_FOLDER_VIEW                 105
#define IDD_LOGIN                       106
#define IDD_MAIN_DIALOG                 107
#define IDD_THE_CONTAINER               109
#define IDD_SCALARS                     110
#define IDD_VIEW_SEL_FILTERS_DLG        113
#define IDD_EXPRESSION                  114
#define IDC_SELECTED_FILTERS            1006
#define IDC_AVAIL_CONTAINER_LIST        1008
#define IDC_AVAIL_FILTER_LIST           1009
#define IDC_NAME                        1010
#define IDC_VALUE                       1011
#define IDC_GROUP_NAME                  1012
#define IDC_GROUPCLASS                  1012
#define IDC_OP_CB                       1013
#define IDC_SELECTED_CONTAINER          1014
#define IDC_ATTRIBUTE                   1014
#define IDC_FOLDERFOLDER_CT             1019
#define IDC_FOLDERID                    1020
#define IDC_FOLDERTYPE                  1021
#define IDC_FOLDERSC_CT                 1022
#define IDC_FOLDERFT_CT                 1023
#define IDC_SUBFOLDER_LIST              1024
#define IDC_SQLSERVER                   1025
#define IDC_DATABASE                    1026
#define IDC_SELECT_CONTAINER_BTN        1026
#define IDC_LOGINID                     1027
#define IDC_SELECT_FILTER_BTN           1027
#define IDC_PASSWORD                    1028
#define IDC_DESELECT_FILTER_BTN         1028
#define IDC_ADDAND_BTN                  1035
#define IDC_ADDOR_BTN                   1036
#define IDC_WHAT_CONTAINER              1037
#define IDC_CTR_NUM_FOLDERS             1038
#define IDC_ID_LB                       1039
#define IDC_DONE                        1040
#define IDC_VIEW_FOLDER                 1041
#define IDC_VIEW_SCALARS                1042
#define IDC_SCVAL_LB                    1043
#define IDC_SCNAME_LB                   1044
#define IDC_SELECT_CONTAINER_DONE       1045
#define IDC_SELECT_FILTERS_DONE         1046
#define IDC_CONFIG_FILTER_BTN           1047
#define IDC_GROUP_NAME_STATIC           1048
#define IDC_GROUPCLASS_STATIC           1048
#define IDC_CONNECT                     1049
#define IDC_ALL_DONE                    1050
#define IDC_BACK                        1051
#define IDC_ARCHITECTURE_STATIC         1052
#define IDC_OPERATOR_STATIC             1053
#define IDC_VALUE_STATIC                1054
#define IDC_ATTRIBUTE_STATIC            1055
#define IDC_NAME_STATIC                 1056
#define IDC_ARCHITECTURE                1058
#define IDC_VIEW_SEL_FILTERS_BTN        1060
#define IDC_SEL_FILTERS_LB              1061
#define IDC_VIEWFILTER                  1067
#define IDC_SCTYPE_LB                   1068
#define IDC_VIEW_EXPRESSION             1069
#define IDC_TOKENS_xx                   1070
#define IDC_TOKENS_LB                   1071

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_3D_CONTROLS                     1
#define _APS_NEXT_RESOURCE_VALUE        115
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1072
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

DESCRIPTION:

    The deljob sample program illustrates how to use the SMS SDK to delete
    an SMS job.

USAGE:

    When the deljob program is run, it displays help text that
    describes how to use the program.

FILES PROVIDED:

    readme.txt
    makefile
    deljob.cpp
    status.cpp

BUILDING THE SAMPLE:

    To build this sample program type 'nmake'.

SMS API CALLS USED:

    SmsDataSourceConnect
    SmsDataSourceDisconnect
    SmsOpenContainer
    SmsPopulate
    SmsCloseContainer
    SmsGetNextFolder
    SmsGetFolderID
    SmsUnlinkFolder
    SmsCommitFolder
    SmsCloseFolder


DELJOB.CPP

// ===========================================================================
// Copyright (C) 1995 by Microsoft Corporation.
//
//  File: deljob.cpp
//
//  This program illustrates how to use the SMS SDK to delete an SMS
//  job.
//
//  Author: Larry A. French
//
// ===========================================================================

// ====================================================================
//  Includes.
// ====================================================================
#include <afx.h>
#include <stdlib.h>                 // for itoa
#include <smsapi.h>                 // Header for the APIs.
#include <time.h>                   // for time functions.

// Include the GetStatusName function.
// -------------------------------------
#include "..\common\status.inc"

// ====================================================================
//      Defines.
// ====================================================================

#define CCH_MAXSTRING   256
#define MAX_CREATE      100

// ====================================================================
//  Local prototypes.
// ====================================================================
const char *GetStatusName( SMS_STATUS stat );

void DisplaySmsError(const char* pszMessage, SMS_STATUS stat);

BOOL DidRequestHelp(int argc, char** argv);

HANDLE ConnectToDatasource();

void DisplayHelp();

void InputString(const char* pszMessage, char* pszResult);

void DisplayGreeting();

BOOL UserSaysYes(const char* pszPrompt);

// ====================================================================
//  Structs.
// ====================================================================
// This struct holds a folder handle and its ID.
typedef struct _FOLDERREC {
    HANDLE hFolder;                     // Handle to a folder.
    char szID[SMS_DATA_BUFF_SIZE+1];    // Its ID.
    BOOL bDeleted;                      // marked as deleted in this program.
} FOLDERREC;

// ====================================================================
//  The work starts here.
// ====================================================================
void main(int argc, char** argv)
{
    // Check to see if this is a request to display the help
    // screen.  If so, display it. Otherwise, display a greeting
    // banner.
    //=========================================================
    if (DidRequestHelp(argc, argv)) {
        DisplayHelp();
        return;
    }
    else {
        DisplayGreeting();
    }

    SMS_STATUS stat;
    HANDLE hConnect;
    HANDLE hContainerJob;
    char szJobID[SMS_DATA_BUFF_SIZE + 1];
    char szPrompt[CCH_MAXSTRING];

    // Connect to the data source. If the connect fails, give up.
    // Note a message will have already been displayed by
    // ConnectToDatasource.
    //===========================================================
    hConnect = ConnectToDatasource();
    if (hConnect == NULL) {
        return;
    }

    // Open package container.
    // =======================
    stat = SmsOpenContainer( C_JOB, hConnect, &hContainerJob );
    if (stat != SMS_OK) {
        printf("SmsOpenContainer failed: %d\n", stat);
        SmsDataSourceDisconnect( hConnect );
        return;
    }

    // Select all folders (no filters.)
    // ================================
    stat = SmsPopulate( hContainerJob, POP_SYNC, NULL);
    if (!(stat == SMS_OK || stat==SMS_EMPTY)) {
        printf("Bad return from SmsPopulate: %d\n", stat);
        SmsCloseContainer( hContainerJob );
        SmsDataSourceDisconnect( hConnect );
        return;
    }

    FOLDERREC *paFolderRec = NULL;

    // Get count of folders in container, allocate memory.
    // ===================================================
    DWORD ctFolders;
    SmsGetFolderCount( hContainerJob, F_ANY, &ctFolders );
    paFolderRec = (FOLDERREC *)malloc( ctFolders * sizeof(FOLDERREC) );

    // Retrieve all the jobs and store their handles and IDs.
    // ======================================================
    for (DWORD dwI = 0; dwI < ctFolders; dwI++) {

        SmsGetNextFolder( hContainerJob, F_ANY, &(paFolderRec[dwI].hFolder) );
        SmsGetFolderID( paFolderRec[dwI].hFolder, paFolderRec[dwI].szID );
        paFolderRec[dwI].bDeleted = FALSE;
    }

    BOOL bDidDeleteFolder;

AGAIN:

    // The bDidDelete flag is used to indicate whether or not
    // the folder deletion was successful.  We assume that something
    // will go wrong until we have proven otherwise and know that
    // the folder was in fact deleted.
    //=============================================================
    bDidDeleteFolder = FALSE;

    printf("**************************************************\n");
    printf("* Please enter the ID of the job that you wish   *\n");
    printf("* to delete.                                     *\n");
    printf("* Available jobs are:                            *\n");
    printf("**************************************************\n");

    // Display the job IDs.
    // ====================
    for (dwI = 0; dwI < ctFolders; dwI++) {
        if (!paFolderRec[dwI].bDeleted) {
            printf("[%d] %s\n", dwI, paFolderRec[dwI].szID);
        }
    }
    printf("\n");

    InputString("Job ID", szJobID);
    sprintf(szPrompt, "Delete job \"%s\"", szJobID);

    if (UserSaysYes(szPrompt)) {
        // Control comes here if the user has given the OK to delete
        // the specified job. To delete the job, we first get its
        // its folder handle, unlink the folder, and then commit it.
        // Note that SmsGetFolderByID doesn't work on containers.
        //
        // Search the array of FOLDERRECs looking for this ID.
        // =========================================================
        BOOL bFound = FALSE;
        for (dwI = 0; dwI < ctFolders; dwI++) {
            if (strcmp( szJobID, paFolderRec[dwI].szID ) == 0) {

                // Check that not already deleted
                if (!paFolderRec[dwI].bDeleted) {
                    bFound = TRUE;
                }
                break;
            }
        }

        // If we found the ID, unlink and commit the folder.
        // =================================================
        if (bFound) {
            // Unlink the folder to remove it from the in-memory hierarchy.
            //============================================================
            stat = SmsUnlinkFolder(paFolderRec[dwI].hFolder);
            if (stat != SMS_OK) {
                DisplaySmsError("Failed to unlink the job folder", stat);
            }

            // Commit the unlinked folder to delete the folder in the
            // datasource.
            //=======================================================
            stat = SmsCommitFolder(paFolderRec[dwI].hFolder);
            if (stat != SMS_OK) {
                DisplaySmsError("Failed to commit the job folder", stat);
            }

            // If all OK then mark that job as deleted in our array.
            // =====================================================
            if (stat == SMS_OK) {
                paFolderRec[dwI].bDeleted = TRUE;
                bDidDeleteFolder = TRUE;
                printf("Job: %s was deleted succesfully\n\n", szJobID);
            }

        } else {
            printf("Job %s not found\n", szJobID );
        }

    }

    // Allow user to try again.
    // (This is one of the few cases where a goto is permitted.)
    // =========================================================

    if (UserSaysYes(bDidDeleteFolder ? "Delete another job?" : "Try again?")) {
        goto AGAIN;
    }

    // Close all job folders.
    // ======================
    for (dwI = 0; dwI < ctFolders; dwI++) {
        SmsCloseFolder( paFolderRec[dwI].hFolder );
    }

    // Close the container.
    //=====================
    stat = SmsCloseContainer( hContainerJob );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to close the package container", stat);
    }

    // Disconnect from the datasource.
    //================================
    stat = SmsDataSourceDisconnect( hConnect );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to disconnect from the datasource", stat);
    }

}  /* main */

// ====================================================================
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char* pszMessage
//          The user prompt to display.
//
//      char* pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//
// ====================================================================
void InputString(const char* pszMessage, char* pszResult)
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

// ====================================================================
// UserSaysYes
//
// Prompt the user to reply yes or no.  If the user enters "Y" then
// return TRUE, otherwise FALSE.
//
// Parameters:
//      const char* pszPrompt
//          The string used to prompt the user
//
// Returns:
//      TRUE = User replied "Y" or "y"
//      FALSE = User gave any other reply.
//
// ====================================================================
BOOL UserSaysYes(const char* pszPrompt)
{
    printf("%s? [y/n]", pszPrompt);
    char szReply[256];
    gets(szReply);

    return(szReply[0] == 'y' || szReply[0] == 'Y');
}

// ====================================================================
// ConnectToDatasource
//
// Get the datasource connection information from the user and use it
// to connect to the datasource.
//
// Parameters:  None.
//
// Returns:
//      The connection handle or NULL if the connection failed.
//
// ====================================================================
HANDLE ConnectToDatasource()
{
    // Get the information we need to connect to the
    // data source from the user.
    //==============================================
    char szServer[CCH_MAXSTRING];
    char szUser[CCH_MAXSTRING];
    char szPasswd[CCH_MAXSTRING];
    char szDatabase[CCH_MAXSTRING];

    printf("\n");
    printf("**************************\n");
    printf("* Connect to data source *\n");
    printf("**************************\n");
    InputString("SQL server name", szServer);
    InputString("SQL database name", szDatabase);
    InputString("User name for SQL server", szUser);
    InputString("Password for SQL server", szPasswd);
    printf("\n");

    // Connect to a data source. SQL in this case.
    // ===========================================
    DATASOURCE dsParams;

    dsParams.sqlParams.ds          = DB_SQL;
    dsParams.sqlParams.pszServer   = szServer;
    dsParams.sqlParams.pszUserName = szUser;
    dsParams.sqlParams.pszPasswd   = szPasswd;
    dsParams.sqlParams.pszDbName   = szDatabase;
    dsParams.sqlParams.pFunc       = NULL;         // No encryption.
    dsParams.sqlParams.pszKey      = "";

    HANDLE hConnect;
    SMS_STATUS stat;
    stat = SmsDataSourceConnect( &dsParams, &hConnect);

    if (stat != SMS_OK) {
        hConnect = NULL;
        DisplaySmsError("Connect to data source failed", stat);
    }

    return( hConnect );
}

// ====================================================================
// DidRequestHelp
//
// Check the program's arguments to see if the user asked for
// the help screen to be displayed.
//
// Parameters:
//      int argc
//          The argc value from main(argc, argv)
//
//      char** argv
//          The argv value from main(argc, argv)
//
// Returns:
//      TRUE if command line parameters included a request
//      for help to be displayed.
//
// ====================================================================
BOOL DidRequestHelp(int argc, char** argv)
{
    const char* pszCommand = argv[1];
    if (argc==2  && (strcmp((const char*) argv[1], "-help")==0)) {
        return(TRUE);
    }
    else {
        return(FALSE);
    }
}

// ====================================================================
// DisplayHelp
//
// This function displays the samples help screen.
//
// Parameters:
//      None
//
// Returns:
//      Nothing.
//
// ====================================================================
void DisplayHelp()
{
    printf("\n\n");
    printf("********************************************************\n");
    printf("* deljob.exe:                                          *\n");
    printf("*                                                      *\n");
    printf("* This program illustrates how to use the SMS SDK to   *\n");
    printf("* delete a job in the SMS database.                    *\n");
    printf("*                                                      *\n");
    printf("* Syntax:                                              *\n");
    printf("*     deljob.exe [-help]                               *\n");
    printf("*                                                      *\n");
    printf("* Switches:                                            *\n");
    printf("*     -help       Display this help screen.            *\n");
    printf("*                                                      *\n");
    printf("********************************************************\n");
    printf("\n");
}

// ====================================================================
// DisplayGreeting
//
// Display the initial greeting banner.
//
// Parameters:
//     None.
//
// Returns:
//     Nothing.
//
// ====================================================================
void DisplayGreeting()
{
    // For this sample, the greeting is identical to the help screen.
    //===============================================================
    DisplayHelp();
}

/* EOF: deljob.cpp */


README.TXT

SMS API Example: object enumeration. (enumobj.exe)
    ==================================================

This sample illustrates the use of the SMS object enumeration APIs.
The objects involved here are currently Architectures and Platforms,
and their descendent objects.

The API draws a distinction between 'first-class' and 'secondary objects.
A 'first-class' object is one that can be described by SMS without any
prior knowledge. The first-class objects that are known to the system
is returned by calling SmsEnumObjectTypes. Calling this will currently
result in two object-types being returned: Architectures and Platforms.
In order to determine what Architectures exist the user then calls
SmsEnumObjects, passing "Architectures" as the types of object that we
are interested in. What is returned from this call depends on the
individual database. The set of architectures will certainly include:
        Personal Computer
        SMSEvents
        SNMP Traps      (SMS 1.2 and above)
        PackageLocation
        UserGroups
        etc.
These are all secondary objects whose type is "Architecture".

It is now possible to determine what objects each of these contain.
For instance, every object of type 'Architecture' will contain one or
more object of type 'Group', such as "MICROSOFT|IDENTIFICATION|1.0".
Note that it is impossible to determine what groups exist without first
knowing what architecture the user is interested in. Similarly, while a
Group object contains Attribute objects, it is impossible to know what
Attributes exist without knowing what group we are talking about.
This is why the SmsEnumObjects API has the notion of a predecessor list.

The data type used by the API to describe an object that it returns
is known as OBJDESCRIPTOR. Looking at the definition for this data type
(see smsapi.h) we see that there are the following fields:

    DWORD objType                Type of this object.

    SMSBUFF szName               Object name (eg 'Personal Computer')

    SMSBUFF szFriendlyName       Friendly name. Only used in groups
                                 where szName would be, eg,
                                 'MICROSOFT|IDENTIFICATION|1.0', the
                                 friendly name would be 'Identification'.

    BOOL bGotFriendlyName        TRUE if we have a friendly name.

    DWORD dwRelopMin             For attributes, indicates range of
    DWORD dwRelopMax             relational operators that can be used
                                 for this attribute.

    BOOL bGotRelops              TRUE if we have the relops fields set.

The 'objType' parameter is one of the set of OT_ defines (also in smsapi.h).
This tells the user what type of object is described by this OBJDESCRIPTOR.

'szName' provides the name for this object (eg 'Personal Computer', 'SMSID').

'szFriendlyName' if this is not null then it gives a user-friendly name for
the object (see above). This is only used in the case of Group objects, but
the 'bGotFriendlyName' datum will indicate whether this field is present
or not.

'dwRelopMin' and 'dwRelopMax' are used by attributes. They inform the caller
what the range of operators are for the attribute in question. 'bGotRelops'
is TRUE if these fields are present.
The values of these relational operators is from the file qrycodes.h.

Note that this example operates in a recursive manner in order to enumerate
all objects that are known to the API set.
A different use would of the APIs would be something like: given a specific
architecture and a specific group, what attributes exist. Code for this would
be something like:
        (where Architecture is Personal Computer, and Group is MICROSOFT|
         VIDEO|1.0)

    SMSBUFF aPreds[10];
    strcpy( aPreds[0], "Architectures" );
    strcpy( aPreds[1], "MICROSOFT|VIDEO|1.0 );
    strcpy( aPreds[2], "MICROSOFT|VIDEO|1.0 );

    SmsEnumObjects( hConnect,   // Handle to datasource connection.
                    pszObj,     // Attribute.
                    pPreds,     // see above.
                    ctPreds,    // 3: Architectures, PC, MS|VIDEO|1.0
                    Objects,    // filled in by API.
                    &ctObjs );  // filled in by API.

This would return, in 'Objects', all attributes for the video group.


ENUMOBJ.CPP

// ====================================================================
//
//  File: enumobj.cpp
//
//  Copyright (c) 1996 Microsoft Corp.
//
//  Author:
//      Jonathan Shuval     Microsoft Corp.
//
//  Sample program for illustrating use of the SMS object enumeration
//  APIs.
//
//
// ====================================================================

// ====================================================================
//
//      Includes
//
// ====================================================================
#include <afx.h>
#include <smsapi.h>

// Include the GetStatusName function.
// -------------------------------------
#include "..\common\status.inc"

// ====================================================================
//
//      Defines.
//
// ====================================================================
#define CCH_MAXINPUT 256

// ====================================================================
//
//      Prototypes.
//
// ====================================================================

// Display an error message with its SMS status value.
// -------------------------------------------------------------
void DisplaySmsError( const char *pszMessage, SMS_STATUS stat );

// Connect to the SMS datasource.
// -------------------------------------------------------------
HANDLE ConnectToDatasource();

// Do the real enumerate, recursive.
// -------------------------------------------------------------
void enumObj( HANDLE hConnect,
              SMSBUFF *pPreds,
              DWORD ctPreds,
              char *pszObj,
              int iIndent );

// Prompt the user for input and return the reply.
// -------------------------------------------------------------
void InputString( const char *pszMessage, char *pszResult );

// ====================================================================
//
//      Globals.
//
// ====================================================================
// None.

// ====================================================================
//
//      Start here.
//
// ====================================================================

void main()
{

    // Get and display the SMS API version.
    // ------------------------------------
    char *pszVersion;                   // Receives pointer to the version string.
    SmsAPIVer( &pszVersion );           // Get version
    printf("Using %s\n", pszVersion);   // and print it.

    SMS_STATUS stat;
    DWORD dwLoop;

    //===========================================
    // Connect to the SMS datasource.
    //===========================================
    HANDLE hConnect;
    hConnect = ConnectToDatasource();

    if (hConnect == NULL) {
        return;
    }

    // Get the first-class objects.
    // ============================
    SMSBUFF ObjectTypes[10];
    DWORD ctObjectTypes = 10;

    stat = SmsEnumObjectTypes( hConnect, ObjectTypes, &ctObjectTypes );

    if (stat != SMS_OK) {
        printf("EnumObjectTypes fails: %d\n", stat);
        return;
    }

    printf("There are %d object types...\n", ctObjectTypes);
    for (dwLoop = 0; dwLoop < ctObjectTypes; dwLoop++) {
        printf("\t%s\n", ObjectTypes[dwLoop]);
    }
    printf("\n\n");

    // Now loop for each object type
    // =============================

    for (dwLoop = 0; dwLoop < ctObjectTypes; dwLoop++) {
        printf("\n[%d] Enumerating %s...\n", dwLoop, ObjectTypes[dwLoop]);

        SMSBUFF Predecessors[50];
        DWORD ctPreds = 0;

        enumObj( hConnect, Predecessors, ctPreds, ObjectTypes[dwLoop], 1 );

    }

    printf("**** All done ****\n\n");

    SmsDataSourceDisconnect( hConnect );

}

// ====================================================================
// enumObj
//
//
//
//
// Parameters:
//      HANDLE hConnect         Handle to the connection.
//      SMSBUFF *pPreds         Pointer to the predecessor list.
//      DWORD ctPreds           Number of entries in the predecessor list.
//                              Note: this is not the size of the list,
//                              it is the number of entries that are
//                              filled in in the list.
//      char *pszObj            Name of object to enumerate.
//      int iIndent             Indent level. Just so we can display things
//                              nicely.
//
// Returns;
//      Nothing.
//
// NOTE: assumes that predecessor buffer is big enough.
// ====================================================================
void enumObj( HANDLE hConnect,
              SMSBUFF *pPreds,
              DWORD ctPreds,
              char *pszObj,
              int iIndent )
{
    OBJDESCRIPTOR Objects[50];      // Filled in by the API. Contains
                                    // descriptions of each object.
    DWORD ctObjs = 50;
    SMS_STATUS stat;
    char *pszObjType;               // We display the object type.

    // Leading spaces for indentation. Just so that the display looks ok.
    // ------------------------------------------------------------------
    char szIndent[50];
    szIndent[0] = '\0';
    for (int i = 0; i < iIndent; i++) {
        strcat(szIndent, "    ");
    }

    stat = SmsEnumObjects( hConnect, pszObj, pPreds, ctPreds, Objects, &ctObjs );
    if (stat == SMS_NO_MORE_DATA) {
        return;             // Just means we've finished enumerating this one.
    }
    if (stat != SMS_OK) {
        DisplaySmsError("Bad return from EnumObjects", stat);
        return;
    }

    // Add each object from our returned buffer into the
    // predecessor list and recurse.
    // =================================================
    for (DWORD dwLoop = 0; dwLoop < ctObjs; dwLoop++) {

        // Print out the current object.
        // -----------------------------
        printf("%s%-25s", szIndent, Objects[dwLoop].szName);

        // If we've got a friendly name print it.
        // --------------------------------------
        if (Objects[dwLoop].bGotFriendlyName) {
            printf(" <%s>", Objects[dwLoop].szFriendlyName);
        }

        // If this is an attribute we'll have its range of
        // relational operators.
        // -----------------------------------------------
        if (Objects[dwLoop].bGotRelops) {
            printf(" ['%s' to '%s']",
                OpName[Objects[dwLoop].dwRelopMin],
                OpName[Objects[dwLoop].dwRelopMax]);
        }
        printf("\n");

        // Print object type.
        // ------------------
        switch (Objects[dwLoop].objType) {
        case OT_ARCHLIST:
            pszObjType = "Architecture list";
            break;

        case OT_ARCH:
            pszObjType = "Architecture";
            break;

        case OT_GROUP:
            pszObjType = "Group";
            break;

        case OT_ATTRIBUTE:
            pszObjType = "Attribute";
            break;

        case OT_PLATFORMS:
            pszObjType = "Platform list";
            break;

        case OT_PLATFORM_PDF:
            pszObjType = "PDF Platform";
            break;

        case OT_PLATFORM_WINST:
            pszObjType = "Winst platform";
            break;

        case OT_PLATFORM_NAD:
            pszObjType = "NAD platform";
            break;

        case OT_UNKNOWN:
            pszObjType = "Unknown";
            break;

        }
        printf("%sThis is a: %s\n", szIndent, pszObjType );

        // Recurse to get descendents.
        // ---------------------------
        strcpy( pPreds[ctPreds], pszObj );      // Add current object to pred list
        enumObj( hConnect, pPreds, ctPreds+1, Objects[dwLoop].szName, iIndent+1 );

    }

}

// ====================================================================
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char* pszMessage
//          The user prompt to display.
//
//      char* pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//
// ====================================================================
void InputString( const char *pszMessage, char *pszResult)
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

// ====================================================================
//
// ConnectToDatasource
//
// Get the datasource connection information from the user and use it
// to connect to the datasource.
//
// Parameters:  None.
//
// Returns:
//      The connection handle or NULL if the connection failed.
//
// ====================================================================
HANDLE ConnectToDatasource()
{
    // Get the information we need to connect to the
    // data source from the user.
    //==============================================
    char szServer[CCH_MAXINPUT];
    char szUser[CCH_MAXINPUT];
    char szPasswd[CCH_MAXINPUT];
    char szDatabase[CCH_MAXINPUT];

    printf("\n");
    printf("**************************\n");
    printf("* Connect to data source *\n");
    printf("**************************\n");
    InputString("SQL server name", szServer);
    InputString("SQL database name", szDatabase);
    InputString("User name for SQL server", szUser);
    InputString("Password for SQL server", szPasswd);
    printf("\n");

    // Connect to a data source. SQL in this case.
    // ===========================================
    DATASOURCE dsParams;

    dsParams.sqlParams.ds          = DB_SQL;
    dsParams.sqlParams.pszServer   = szServer;
    dsParams.sqlParams.pszUserName = szUser;
    dsParams.sqlParams.pszPasswd   = szPasswd;
    dsParams.sqlParams.pszDbName   = szDatabase;
    dsParams.sqlParams.pFunc       = NULL;         // No encryption.
    dsParams.sqlParams.pszKey      = "";

    HANDLE hConnect;
    SMS_STATUS stat;
    stat = SmsDataSourceConnect( &dsParams, &hConnect);

    if (stat != SMS_OK) {
        hConnect = NULL;
        DisplaySmsError("Connect to data source failed", stat);
    }

    return( hConnect );
}

/* EOF: enumobj.cpp */


README.TXT

SMS API Example: creation of machine group (mgrp.exe)
    =====================================================

This sample shows how to create a machine group using the SMS API.

Currently the API only allows top-level machine groups to be created, in
other words you cannot a) modify existing machine groups, and b) you
cannot create nested machine groups.

There has been one addition to the API set in order to enable this; this is
the SmsDupFolder API.

The sample opens a machine container, and a machine group container.
From the machine container we select the machines that we want inserted into
the machine group that we are about to create. In this example we just select
all the Personal Computer architecture machines, a real-world usage would be
more judicious in its selection.

From the machine group container we create a new machine group folder.
We then enter a loop duplicating each machine folder (SmsDupFolder) and
inserting it into the machine group folder (SmsLinkFolder) and committing
the insertion.

When the loop terminates we set the scalars for the new machine group folder.
There are two scalars: Name and Comment. The Comment is optional and can be
any text string. The name however, must be unique. This is solicited from the
user.

The new folder is then linked and committed.

The contents of this machine group folder are then displayed. Note that this
can be very lengthy.


MGRP.CPP

// ====================================================================
//
//  File: mgrp.cpp
//
//  Copyright (C) 1996 by Microsoft Corporation.
//
//  Author:
//      Jonathan Shuval     Microsoft Corp.
//
//
//  This sample shows how to create a machine group and insert machines
//  into it.
//
// ====================================================================

// ====================================================================
//  Includes.
// ====================================================================
#include <afx.h>
#include <smsapi.h>             // Header for the APIs.
#include <time.h>               // For converting time scalars into string.

// Include the GetStatusName function.
// -------------------------------------
#include "..\common\status.inc"

// ====================================================================
//  Defines.
// ====================================================================
#define CCH_MAXINPUT 256

// ====================================================================
//  Local prototypes.
// ====================================================================

// Does the real work.
// -------------------------------------------------------------
void doTheWork( HANDLE hConnect );

// Set filters according to what container we're looking at.
// -------------------------------------------------------------
BOOL setMachFilters( HANDLE hConnect, HANDLE hContainer );

// Functions to display folders, scalars, and expressions.
// -------------------------------------------------------------
void DisplayFolder( HANDLE hFolder );
void DisplayScalars( HANDLE hFolder );

// Prompt the user for input and return the reply.
// -------------------------------------------------------------
void InputString(const char* pszMessage, char* pszResult);

// Connect to the SMS datasource.
// -------------------------------------------------------------
HANDLE ConnectToDatasource();

// Display an error message with its SMS status value.
// -------------------------------------------------------------
void DisplaySmsError(const char* pszMessage, SMS_STATUS stat);

// Get the string equivallent for an SMS status code.
// -------------------------------------------------------------
const char *GetStatusName(SMS_STATUS stat);

// Set a string scalar.
// -------------------------------------------------------------
SMS_STATUS SetScalarString( HANDLE hFolder, char *pszName, char *pszValue);

// ====================================================================
//
//  Macros for checking status
//
// ====================================================================
        // Void return
#define CHKSTAT(str) if (stat != SMS_OK) { \
        DisplaySmsError(str, stat);         \
        return; }

        // Bool return
#define CHKSTAT_RTN(str) if (stat != SMS_OK) { \
        DisplaySmsError(str, stat);             \
        return(FALSE); }

// ====================================================================
//
//  The work starts here.
//
// ====================================================================
void main()
{

    HANDLE hConnect;

    // Get and display API version.
    // ----------------------------
    char *pszVersion;
    SmsAPIVer( &pszVersion );
    printf("%s\n", pszVersion );

    //===========================================
    // Connect to the SMS datasource.
    //===========================================
    hConnect = ConnectToDatasource();
    if (hConnect == NULL) {
        return;
    }

    // This loop allows us to do the real work multiple times.
    // =======================================================
    BOOL bDone = FALSE;
    char reply[10];

    while (!bDone) {

        doTheWork( hConnect );

        printf("Restart [y/n]? "); gets(reply);
        bDone = (reply[0] != 'y' && reply[0] != 'Y');
    }

    // Disconnect from the datasource.
    // ===============================
    SmsDataSourceDisconnect( hConnect );

}  /* main */

// ====================================================================
//
//  This function does whatever work is required.
//  In this case, it will:
//      - open a machine group container
//      - create a machine group folder
//      - open a machine container
//      - select machine folders from mach container that I want
//          to copy to the mgrp folder
//      - loop copying these machine folders to the mgrp folder
//      - link and commit the mgrp folder
//      - close everything

//
// ====================================================================
void doTheWork( HANDLE hConnect )
{
    HANDLE hMGrpContainer;
    HANDLE hMachContainer;
    SMS_STATUS stat;

    // Open the containers
    // ===================
    stat = SmsOpenContainer( C_MACHINEGROUP, hConnect, &hMGrpContainer );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsOpenContainer (mach grp) failed", stat);
        return;
    }

    stat = SmsOpenContainer( C_MACHINE, hConnect, &hMachContainer );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsOpenContainer (mach) failed", stat);
        SmsCloseContainer( hMGrpContainer );
        return;
    }

    // Create machine group folder
    // ===========================
    HANDLE hMGrpFolder;
    stat = SmsCreateFolder( hMGrpContainer, F_MACHINEGROUP, "", &hMGrpFolder );
    if (stat != SMS_OK) {
        char szError[256];
        sprintf(szError, "SmsCreateFolder failed to create MachineGroup folder");
        DisplaySmsError(szError, stat);
        SmsCloseContainer( hMGrpContainer );
        SmsCloseContainer( hMachContainer );
        return;
    }

    // Select machine folders that we want tp put in to the mgrp folder.
    // Just get all the PC machines from the mach container for now.
    // Set filter for arch PC only machines.
    // =================================================================
    setMachFilters( hConnect, hMachContainer );

    stat = SmsPopulate( hMachContainer, POP_SYNC, NULL );
    if (stat != SMS_OK) {
        DisplaySmsError("Bad return from SmsPopulate", stat);
        // Close everything and return.
        SmsCloseContainer( hMGrpContainer );
        SmsCloseContainer( hMachContainer );
        SmsCloseFolder( hMGrpFolder );
        return;
    }

    // Loop getting a handle to each machine folder and
    // inserting it into the new mgroup folder.
    // ================================================
    DWORD ctFolders;
    SmsGetFolderCount( hMachContainer, F_ANY, &ctFolders );
    printf("\n======== Mach Container has %d folders\n", ctFolders);

    HANDLE hFolder;
    char szFolderID[100];               // This folder's ID.
    BOOL bValid = TRUE;

    for (DWORD dwLoop = 0; dwLoop < ctFolders; dwLoop++) {

        SmsGetNextFolder( hMachContainer, F_ANY, &hFolder );

        // Display message saying what we've got
        // -------------------------------------
        stat = SmsGetFolderID( hFolder, szFolderID );
        printf("[%d] Got folder: %s, about to insert in new folder\n",
                dwLoop+1, szFolderID);

        // Insert the machine folder into the machine group folder.
        // This can't do a straight swap of parents since we want to keep
        // the machine folder's relationship to his current parent.
        // What this API does therefore is to make a *copy* of the folder
        // and link this copy into the mach group folder (ie the mach group
        // folder becomes the new folder's parent).
        // This means that this API returns a handle to the newly-created
        // folder.
        // ================================================================
        HANDLE hNewFolder;
        stat = SmsDupFolder( hMGrpFolder, hFolder, &hNewFolder );
        if (stat != SMS_OK) {
            printf("Failure duplicating folder %s: %d\n", szFolderID, stat);
            bValid = FALSE;
            break;
        }

        // Now we need to link and commit the new machine folder to
        // the machine group folder.
        // ========================================================
        stat = SmsLinkFolder( hNewFolder );
        if (stat != SMS_OK) {
            printf("Failure linking folder %s: %d\n", szFolderID, stat);
            goto CLOSE;
        }

        stat = SmsCommitFolder( hNewFolder );
        if (stat != SMS_OK && stat != SMS_PARENT_NEEDS_COMMIT) {
            printf("Failure comitting folder %s: %d\n", szFolderID, stat);
            goto CLOSE;
        }

        SmsCloseFolder( hFolder );
        SmsCloseFolder( hNewFolder );
    }

    // Set scalars for the mach group folder.
    // ======================================
    char szBuff[99];
    InputString("Give me a unique name for the mach gp", szBuff);
    SetScalarString( hMGrpFolder, "Name", szBuff );
    SetScalarString( hMGrpFolder, "Comment", "some comment" );

    // Link mach grp folder into parent and commit it.
    // ===============================================
    stat = SmsLinkFolder( hMGrpFolder );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsLinkFolder failed on the mach Group folder", stat);
        goto CLOSE;
    }

    // Write it back to data source.
    // =============================
    stat = SmsCommitFolder( hMGrpFolder );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsCommitFolder failed on the mach Group folder", stat);
        goto CLOSE;
    }

    // If we got here then all seems to be ok.
    // Display the mach group folder
    // =======================================
    DisplayFolder( hMGrpFolder );

    // Close everything - we're done.
    // ==============================
CLOSE:
    SmsCloseContainer( hMGrpContainer );
    SmsCloseContainer( hMachContainer );
    SmsCloseFolder( hMGrpFolder );

    printf("\n********* All done ***************\n");

}

// ====================================================================
//  We are passed in a handle to a folder. We retrieve the folder's
//  information, displaying scalars if any.
//  We then recursively examine sub-folders (after closing the
//  current folder).
// ====================================================================
void DisplayFolder( HANDLE hFolder )
{

    SMS_STATUS stat;
    HANDLE hSubFolder;
    DWORD fType;                   // type of folder we're dealing with.
    char szfType[30];                   // type as a string.
    DWORD totFolders;                   // Total number of sub-folders of
                                        // all types.
    char szFolderID[100];               // This folder's ID.
    DWORD ctScalars;                    // How many scalars in this folder.

    printf("\n============================================================\n");

    // Get folder type and id.
    // -----------------------
    stat = SmsGetFolderID( hFolder, szFolderID );
    if (stat != SMS_OK) {
        if (stat == SMS_NEW_FOLDER) {
            strcpy(szFolderID, "**New folder**");
        } else {
            CHKSTAT("SmsGetFolderID");
        }
    }

    stat = SmsGetFolderType( hFolder, &fType, szfType );
    CHKSTAT("SmsGetFolderType");
    printf("Folder ID \"%s\" is a %s\n\n", szFolderID, szfType);

    // How many scalars in this folder.
    // --------------------------------------------------------
    stat = SmsGetScalarCount( hFolder, &ctScalars );
    CHKSTAT("SmsGetScalarCount");
    printf("Contains %d scalars\n\n", ctScalars);
    DisplayScalars( hFolder );

    // Get count of all sub-folders (ie of all types).
    // and allocate space for their handles.
    // -----------------------------------------------
    stat = SmsGetFolderCount( hFolder, F_ANY, &totFolders );
    printf("Contains %d folders\n\n", totFolders);

    char szSubFolderID[100];
    DWORD ctFolders;
    HANDLE *phFolders;
    phFolders = (HANDLE *)malloc(totFolders * sizeof(HANDLE));

    // This loop gets the sub-folder IDs and displays them.
    // ====================================================

    for (ctFolders = 0; ctFolders < totFolders; ctFolders++) {

        // Get a handle to a sub-folder.
        // -----------------------------
        stat = SmsGetNextFolder( hFolder, F_ANY, &hSubFolder );
        CHKSTAT("SmsGetNextFolder");
        phFolders[ctFolders] = hSubFolder;

        // Get and display the sub-folder's ID.
        // ------------------------------------
        stat = SmsGetFolderID( hSubFolder, szSubFolderID );
        CHKSTAT("SmsGetFolderID");
        printf("\tSub-folder: \"%s\"\n", szSubFolderID);
    }

    // =======================================================
    // We can now release the handle that was passed in to us.
    // If we wait until the function returns then, because we
    // recurse, we don't release it till the very end.
    // =======================================================
    SmsCloseFolder( hFolder );

    // This loop gets the sub-folders and displays them.
    // =================================================
    for (ctFolders = 0; ctFolders < totFolders; ctFolders++) {
        DisplayFolder( phFolders[ctFolders] );
    }

    // Free the folder handle array.
    // -----------------------------
    free( phFolders );

}  /* DisplayFolder */

// ====================================================================
//
//  Display all the scalars for the folder.
//
// Note: the buffer for string scalars has been deliberately set to a
// value lower than the maximum. If we encounter a string scalar whose
// value exceeds this amount then we will print the 'data truncated'
// message.
//
// ====================================================================
void DisplayScalars( HANDLE hFolder )
{
    SMS_STATUS stat = SMS_OK;
    SCALAR scalar;
    char szName[50];                        // Buffer for name.
    char szValue[SMS_DATA_BUFF_SIZE+1];     // Buffer for string value.
    BYTE byValue[SMS_DATA_BUFF_SIZE+1];     // Buffer for binary scalars.
    scalar.pszName  = szName;
    scalar.pszValue = szValue;
    scalar.pValue   = byValue;

    char *pszTime;          // For time scalars.

    while (1) {

        scalar.dwLen = sizeof(szValue)-1;       // must tell him the size

        stat = SmsGetNextScalar( hFolder, &scalar);
        if (stat != SMS_OK && stat != SMS_MORE_DATA) {
            break;
        }

        if (stat == SMS_MORE_DATA) {
            printf("Receive buffer too small, should be %d. Data truncated\n",
                            scalar.dwLen);
        }
        // Check scalar type, display accordingly.
        // ---------------------------------------
        switch (scalar.scType) {
        case SCALAR_STRING:
            printf("\t%30s: %s\n", scalar.pszName, scalar.pszValue);
            break;

        case SCALAR_INT:
            printf("\t%30s: %ld\n", scalar.pszName, scalar.dwValue);
            break;

        case SCALAR_TIME:
            // If there is a string equivalence use it.
            if (scalar.bStringEquivalence) {
                printf("\t%30s: %s\n", scalar.pszName, scalar.pszValue);
            } else {
                pszTime = ctime( &scalar.tValue );
                printf("\t%30s: %s", scalar.pszName, pszTime);
            }
            break;

        case SCALAR_BINARY:
            // Got binary data.
            // Just tell the user how much data there is.
            printf("\t%30s: Binary data - %d bytes of data\n",
                        scalar.pszName, scalar.dwLen);
            break;
        }
    }

    // Why did we exit (other than no more scalars)?
    // ---------------------------------------------
    if (stat != SMS_NO_MORE_DATA) {
        DisplaySmsError("Bad return from Scalar access", stat);
    }

    // Terminate with newline.
    // -----------------------
    printf("\n");

}

// ********************************************************************
//      Helper functions.
// ********************************************************************

// ====================================================================
// This function sets the filters when we are looking at a Machine
// container.
//
// Want only PC archs, so we set an architecture filter.
//
// ====================================================================
BOOL setMachFilters( HANDLE hConnect, HANDLE hContainer )
{
    HANDLE hFilter;

    // =========================================================
    // Architecture filter.
    // Architecture = Personal Computer.
    // =========================================================
    SmsCreateFilter( ARCHITECTURE_FILTER, hConnect, &hFilter );

    TOKEN ArchToken;
    memset( &ArchToken, 0, sizeof(TOKEN) );

    strcpy( ArchToken.szName, "Architecture" );
    strcpy( ArchToken.szValue, "Personal Computer" );
    ArchToken.dwOp = QOP_STR_EQ;

    SmsAddToken( hFilter, OP_OR, &ArchToken, 0 );

    SmsSetFilter( hContainer, hFilter );
    SmsCloseFilter( hFilter );

    SmsCloseFilter( hFilter );

    return(TRUE);

}  /* setMachFilters */

// ====================================================================
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char* pszMessage
//          The user prompt to display.
//
//      char* pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//
// ====================================================================
void InputString(const char* pszMessage, char* pszResult)
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

// ====================================================================
// ConnectToDatasource
//
// Get the datasource connection information from the user and use it
// to connect to the datasource.
//
// Parameters:  None.
//
// Returns:
//      The connection handle or NULL if the connection failed.
//
// ====================================================================
HANDLE ConnectToDatasource()
{
    // Get the information we need to connect to the
    // data source from the user.
    //==============================================
    char szServer[CCH_MAXINPUT];
    char szUser[CCH_MAXINPUT];
    char szPasswd[CCH_MAXINPUT];
    char szDatabase[CCH_MAXINPUT];

    printf("\n");
    printf("**************************\n");
    printf("* Connect to data source *\n");
    printf("**************************\n");
    InputString("SQL server name", szServer);
    InputString("SQL database name", szDatabase);
    InputString("User name for SQL server", szUser);
    InputString("Password for SQL server", szPasswd);
    printf("\n");

    // Connect to a data source. SQL in this case.
    // ===========================================
    DATASOURCE dsParams;

    dsParams.sqlParams.ds          = DB_SQL;
    dsParams.sqlParams.pszServer   = szServer;
    dsParams.sqlParams.pszUserName = szUser;
    dsParams.sqlParams.pszPasswd   = szPasswd;
    dsParams.sqlParams.pszDbName   = szDatabase;
    dsParams.sqlParams.pFunc       = NULL;         // No encryption.
    dsParams.sqlParams.pszKey      = "";

    HANDLE hConnect;
    SMS_STATUS stat;
    stat = SmsDataSourceConnect( &dsParams, &hConnect);

    if (stat != SMS_OK) {
        hConnect = NULL;
        DisplaySmsError("Connect to data source failed", stat);
    }

    return( hConnect );
}

SMS_STATUS SetScalarString(HANDLE hFolder, char* pszName, char* pszValue)
{
    SCALAR sc;
    sc.pszName = pszName;
    sc.scType = SCALAR_STRING;
    sc.pszValue = pszValue;

    SMS_STATUS stat;
    stat = SmsSetScalar(hFolder, &sc);
    return( stat );
}

/* EOF: mgrp.cpp */


README.TXT

Sample: newjob
==============

This sample illustrates the use of the SMS APIs in order to view,
create, and delete job folders.
There are four types of job folders:
        Run command on Workstation job
        Share package on Server job
        Remove package from Server job
        System job
All of these except system jobs can be created and deleted.

After logging on to a datasource, a job container is opened and populated.
No filtering is done since we want to see all the jobs that exist in the
system.
The program then enters a loop asking whether the user wants to view the
next job folder, delete the currently viewed folder, or create a new folder.

Both deletion and creation are two-stage processes. Only when the
SmsCommitFolder API is called will the folder be either deleted from or
inserted into the datasource.

Folder creation is handled in the CreateNewFolder function.
This creates an empty folder in the context of the parent which in this
case is the job container. The function then retrieves a description of
the folder via the SmsDescribeFolder API. This returns a FOLDER_INFO
structure which describes the folder in terms of its capabilities, see
the objectty.h header file (or the help file) for a full description of
this structure.

Included in FOLDER_INFO is a pointer to an array of SCALAR_INFO structures.
Each SCALAR_INFO describes one of the folder's scalars. The information
contained here is:
        the scalar's name,
        its type,
        its access mode.
CreateNewFolder loops through this array, for each scalar that has modify or
create access, it displays the name and asks the user to fill in the value.
Only nominal checking is done on the values, a real application would have
to go into much greater detail since job creation is a non-trivial task.
If there are any errors in the date/time fields they are set to the current
date and time.

This is a sample of a created workstation install job:

JS200009 (Workstation install job) contains 33 scalars
                           Job ID: JS200009
                         Job type: microsoft\SMS\workstation_script
                      Job comment: a descriptive comment
                    Activate time: Thu Jun 22 15:12:00 1995
                        Left icon: Package
                      Center icon: Machinegroup
                       Right icon: Site
                         Priority: 1
                      Repeat mode: 1
                      Cancel mode: 0
                       Job status: 1
                   Sending status: 1
                   Working status: 1
                    Cancel status: 1
                       Package ID: JS200049
                       Job target: 2
                 Distribute phase: 1
                       Send phase: 1
                   Limit to sites: 2
                 Include subsites: 1
                  Site limit name: jonshu2site
             Distribution servers: me
                         WCL name: Audit software
                         Query ID:
               Machine group name: My machine group
                     Machine path: *|*|*
                       Offer time: Thu Jun 22 15:15:00 1995
                   Mandatory time: Thu Jun 22 15:15:00 1995
               Use mandatory time: 0
             Force over slow link: 0
                      Expire time: Thu Jun 22 15:15:00 1995
                  Use expire time: 0
          Run workstation command: 1
============== JS200009 ===========


NEWJOB.CPP

// ====================================================================
//
//  File: newjob.cpp
//
//  Copyright (C) 1994, 1995 by Microsoft Corporation.
//
//  Author:
//      Jonathan Shuval     Microsoft Corp.
//
//
//  This program illustrates the use of the SMS APIs to view, create
//  and delete jobs.
//
//  See the readme.txt file in this directory for more information.
//
//
// ====================================================================

// ====================================================================
//  Includes.
// ====================================================================
#include <afx.h>
#include <smsapi.h>                 // Header for the APIs.
#include <time.h>                   // for time functions.

// Include the GetStatusName function.
// -------------------------------------
#include "..\common\status.inc"

// ====================================================================
//
//  Defines.
//
// ====================================================================
#define CCH_MAXINPUT 256

// ====================================================================
//  Local prototypes.
// ====================================================================

// Display the folder's scalars.
// -------------------------------------------------------------
void DisplayScalars( HANDLE hFolder );

// Display a folder.
// -------------------------------------------------------------
void ViewFolder( HANDLE hFolder );

// Create a new folder.
// -------------------------------------------------------------
void CreateNewFolder( HANDLE hParent );

// Convert date string from "dd\mm\yy hh:mm" to time_t.
// -------------------------------------------------------------
time_t ReadTime( char szTime[] );

// Prompt the user for input and return the reply.
//------------------------------------------------
void InputString(const char* pszMessage, char* pszResult);

// Connect to the SMS datasource.
//------------------------------
HANDLE ConnectToDatasource();

// Display the help message.
//=========================
void DisplayHelp();

// Display the greeting.
//======================
void DisplayGreeting();

// Check to see if there was a request for help
// on the command line.
//==============================================
BOOL DidRequestHelp(int argc, char** argv);

// Display an error message with its SMS status value.
//====================================================
void DisplaySmsError(const char* pszMessage, SMS_STATUS stat);

// Get the string equivallent for an SMS status code.
//===================================================
const char* GetStatusName(SMS_STATUS stat);

// ====================================================================
//  The work starts here.
// ====================================================================
void main(int argc, char** argv)
{
    // Check to see if this is a request to display the help
    // screen.  If so, display it. Otherwise, display a greeting
    // banner.
    //=========================================================
    if (DidRequestHelp(argc, argv)) {
        DisplayHelp();
        return;
    }
    else {
        DisplayGreeting();
    }

    SMS_STATUS stat;
    HANDLE hConnect;
    HANDLE hContainer;

    //===========================================
    // Connect to the SMS datasource.
    //===========================================
    hConnect = ConnectToDatasource();
    if (hConnect == NULL) {
        return;
    }

    // Open job container.
    // ===================
    stat = SmsOpenContainer( C_JOB, hConnect, &hContainer );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsOpenContainer failed", stat);
        SmsDataSourceDisconnect( hConnect );
        return;
    }

    // Set filters.
    // ============
    // None.

    // Select all folders matching our filters.
    // ========================================
    stat = SmsPopulate( hContainer, POP_SYNC, NULL );
    if (stat != SMS_OK && stat != SMS_EMPTY) {
        DisplaySmsError("Bad return from SmsPopulate", stat);
        SmsCloseContainer( hContainer );
        SmsDataSourceDisconnect( hConnect );
        return;
    }

    // Retrieve all the folders from the container and save them
    // away in a list. SmsGetFolderCount tells us how many folders
    // are in the container, use this value to allocate space for
    // the handles.
    // Don't close the container because it will be needed if we
    // want to create a job.
    // ===========================================================
    DWORD  numFolders;
    DWORD  dwI;
    HANDLE *phFolders;
    HANDLE hFolder;

    SmsGetFolderCount( hContainer, F_ANY, &numFolders);
    printf("==========  Container has %d folders  =========\n\n", numFolders );

    // Allocate memory for the top-level folders.
    // ------------------------------------------
    phFolders = (HANDLE *)malloc(numFolders * sizeof(HANDLE));

    // Open all top-level folders.
    // ---------------------------
    for (dwI = 0; dwI < numFolders; dwI++) {

        stat = SmsGetNextFolder( hContainer, F_ANY, &hFolder );
        if (stat != SMS_OK) {
            DisplaySmsError("Error in retrieving a folder", stat);
            break;
        }

        phFolders[dwI] = hFolder;
    }

    // Now we enter a loop asking the user whether they want to
    // view the next folder, delete the folder just displayed,
    // or create a new folder.
    // =========================================================
    BOOL  done = FALSE;
    char  reply[10];
    DWORD dwIndex = 0;
    char  szFolderID[SMS_DATA_BUFF_SIZE+1];

    // Display the first one (if it exists). That way we always
    // have a current folder.
    // If we don't have any then that's ok because the Delete
    // will fail like it's supposed to.
    // ========================================================
    if (numFolders > 0) {
        ViewFolder( phFolders[dwIndex] );
    }

    printf("You may now do one of the following:\n");
    printf("View the Next folder, Delete the current folder, Create a new folder\n");
    printf("Type X to exit\n");

    while (!done) {

        printf("[NDCX]: ");
        gets(reply);

        switch (reply[0]) {
        case 0:
        case 'N': case 'n':
            // =========== View next folder ====================
            dwIndex++;
            if (dwIndex < numFolders) {
                ViewFolder( phFolders[dwIndex] );
            } else {
                printf("No more folders to view\n");
            }
            break;

        case 'D': case 'd':
            // =========== Delete current folder ===============
            // Check we have a folder to delete.
            if (dwIndex >= numFolders) {
                printf("No folder to delete\n");
                break;
            }

            SmsGetFolderID( phFolders[dwIndex], szFolderID );
            printf("Confirm deletion of folder \"%s\"? [y/n] ", szFolderID);
            gets(reply);
            if (reply[0] != 'y') {
                printf("Delete cancelled\n");
                break;
            }

            stat = SmsUnlinkFolder( phFolders[dwIndex] );
            if (stat != SMS_OK) {
                DisplaySmsError( "SmsUnlinkFolder failed", stat);
                break;
            }
            stat = SmsCommitFolder( phFolders[dwIndex] );
            if (stat != SMS_OK) {
                DisplaySmsError("SmsCommitFolder error", stat);
                break;
            }

            printf("Folder deleted\n");
            break;

        case 'C': case 'c':
            // =========== Create new folder ===================
            CreateNewFolder( hContainer );
            break;

        case 'X': case 'x':
            // Terminate.
            done = TRUE;
            break;

        default:
            // print the message again.
            printf("Actions are: N(ext folder), D(elete folder), C(reate folder)\n");
            printf("Type X to exit\n");
            break;
        }
    }

    // Close the container, and disconnect from datasource.
    // ----------------------------------------------------
    SmsCloseContainer( hContainer );
    stat = SmsDataSourceDisconnect( hConnect );

    // Deallocate memory used for folder handles.
    // ------------------------------------------
    free( phFolders );

}  /* main */

// ====================================================================
// Display the folder contents.
//
//  This version only displays the folder name and type, number of
//  scalars, and the scalars. Other information not relevant.
//
//  NOTE: no error checking.
//
// ====================================================================
void ViewFolder( HANDLE hFolder )
{
    char szFolderID[SMS_DATA_BUFF_SIZE+1];              // This folder's ID.
    char szfType[SMS_DATA_BUFF_SIZE+1];                 // Folder's tag.
    DWORD fType;                                        // Folder's type.
    DWORD ctScalars;                                    // How many scalars in this folder.
    SMS_STATUS stat;

    // Get folder ID and type.
    // -----------------------
    stat = SmsGetFolderID( hFolder, szFolderID );
    // Test return. If this is a newly created folder that hasn't been
    // inserted into the datasource it will not yet have a valid ID.
    if (stat != SMS_OK) {
        strcpy(szFolderID, "<New Job>");
    }
    stat = SmsGetFolderType( hFolder, &fType, szfType );

    // Rewind the folder, just in case.
    // This allows us to re-examine a folder (ie call this multiple times).
    // ====================================================================
    SmsRewind( hFolder, RW_ALL );

    // Get count of scalars.
    // ---------------------
    stat = SmsGetScalarCount( hFolder, &ctScalars );
    printf("%s (%s) contains %d scalars\n", szFolderID, szfType, ctScalars);

    // Display the scalars.
    // --------------------
    DisplayScalars( hFolder );

    printf("============== %s ===========\n", szFolderID);

}  /* ViewFolder */

// ====================================================================
//
//  Create a new folder.
//
//  This is an example of how to create a folder.
//
//  There are several types of job folder. This function can create:
//      a run command on workstation job
//      a share package on server job
//      a remove package from server job.
//
//
// ====================================================================

void CreateNewFolder( HANDLE hParent )
{
    printf("This function allows the creation of one of:\n");
    printf("\t\"Run command on Workstation\" job (w)\n");
    printf("\t\"Share package on Server\" job    (s)\n");
    printf("\t\"Remove package from Server\" job (r)\n");

    DWORD dwJobType;
    char szType[10];
    BOOL done = FALSE;

    while (!done) {
        done = TRUE;

        printf("Enter one of [wsr]: ");
        gets(szType);

        switch (szType[0]) {
        case 'w': case 'W':
            dwJobType = F_INSTALLJOB;
            break;

        case 's': case 'S':
            dwJobType = F_SRVINSTALLJOB;
            break;

        case 'r': case 'R':
            dwJobType = F_REMPKGJOB;
            break;

        default:
            done = FALSE;
            printf("Invalid reply\n");
        }
    }

    HANDLE hNewFolder;
    SMS_STATUS stat;

    // Note the id of "<New Job>", this is because for jobs, SMS assigns
    // the job id itself.

    stat = SmsCreateFolder( hParent, dwJobType, "<New Job>", &hNewFolder );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsCreateFolder fails", stat);
        return;
    }

    // Use an objectity engine API to get information about the
    // folder and its scalars.
    // ---------------------------------------------------------
    FOLDER_INFO *pFInfo;
    stat = SmsDescribeFolder( T_FOLDER, dwJobType, &pFInfo );

    SCALAR_INFO *pSInfo;        // The info structure contains a pointer
                                // to an array of SCALAR_INFOs. Each of these
                                // describes a scalar.
    SCALAR sc;                  // We create scalars using a SCALAR.

    // Data for retrieving information from user for scalars.
    char szValue[SMS_DATA_BUFF_SIZE+1];     // User enters value here.
    time_t time;                            // Times are converted to t_time.

    // Loop through all the scalars, ask the user to supply a
    // value, but only if the scalar is modifiable.
    // =========================================================

    for (DWORD dwI = 0; dwI < pFInfo->ctScalars; dwI++) {

        pSInfo = &pFInfo->pScalars[dwI];

        // Is scalar modifiable?
        if (pSInfo->fAccess == ACCESS_CREATE || pSInfo->fAccess == ACCESS_MODIFY) {

            // Set up common fields in the scalar.
            // -----------------------------------
            sc.pszName = pSInfo->szName;
            sc.scType  = pSInfo->scType;

            char *pszType;

            switch (sc.scType) {
            case SCALAR_STRING:
                pszType = "string";     break;

            case SCALAR_INT:
                pszType = "integer";    break;

            case SCALAR_TIME:
                pszType = "time";       break;

            case SCALAR_BINARY:
                pszType = "binary";     break;
            }
            printf("Enter (%s) value for '%s': ", pszType, pSInfo->szName);

            // Now we need to know what type this is (string, int etc)
            if (pSInfo->scType == SCALAR_STRING) {
                gets( szValue );
                // store it directly
                sc.dwLen = sizeof(szValue)-1;   // -1 for terminating NULL.
                sc.pszValue = szValue;

                stat = SmsSetScalar( hNewFolder, &sc );

            } else if (pSInfo->scType == SCALAR_INT) {
                gets( szValue );
                // convert to int
                sc.dwValue = atol( szValue );

                stat = SmsSetScalar( hNewFolder, &sc );

            } else if (pSInfo->scType == SCALAR_TIME) {
                printf("Enter time in the form mm/dd/yy hh:mm ");
                gets( szValue );
                // Only primitive checking
                time = ReadTime( szValue );
                sc.tValue = time;

                stat = SmsSetScalar( hNewFolder, &sc );

            } else if (pSInfo->scType == SCALAR_BINARY) {
                // Be simplistic about this.
                gets( szValue );
                memcpy( sc.pValue, szValue, strlen(szValue) );
                stat = SmsSetScalar( hNewFolder, &sc );
            }
        }

    }

    // Now display it and ask user for confirmation of creation.
    // =========================================================
    printf("Newly created folder:\n");
    ViewFolder( hNewFolder );
    printf("OK to link folder into parent [y/n]? ");
    char reply[10];
    gets(reply);
    if (reply[0] == 'y') {
        // link it in to parent.
        stat = SmsLinkFolder( hNewFolder );
        if (stat != SMS_OK) {
            DisplaySmsError("SmsLinkFolder error", stat);
            return;
        }

        // write it back to data source.
        stat = SmsCommitFolder( hNewFolder );
        if (stat != SMS_OK) {
            DisplaySmsError("SmsCommitFolder error", stat);
            return;
        }
        SmsGetFolderID( hNewFolder, szValue );

        char *pszType;
        switch (dwJobType) {
        case F_INSTALLJOB:
            pszType = "Workstation install job";
            break;

        case F_SRVINSTALLJOB:
            pszType = "Server share job";
            break;

        case F_REMPKGJOB:
            pszType = "Remove package job";
            break;

        }

        printf("*****  %s %s created *****\n\n", pszType, szValue);

        printf("View of new folder from the datasource...\n");
        ViewFolder( hNewFolder );
        printf("\n\n");

    } else {
        printf("Folder creation cancelled\n");
    }

}

// ====================================================================
//
//  Display all the scalars for the folder.
//
//  This example also shows how the scalar can detect that a buffer is
//  too small.
// ====================================================================
void DisplayScalars( HANDLE hFolder )
{
    SMS_STATUS stat = SMS_OK;
    SCALAR scalar;
    char szName[SMS_DATA_BUFF_SIZE+1];       // buffer for name
    char szValue[SMS_DATA_BUFF_SIZE+1];      // buffer for value
    char *pszTime;          // For conversion of time scalars.

    scalar.pszName  = szName;
    scalar.pszValue = szValue;

    while (1) {

        scalar.dwLen = sizeof(szValue)-1;       // must tell him the size

        stat = SmsGetNextScalar( hFolder, &scalar);
        if (stat != SMS_OK && stat != SMS_MORE_DATA) {
            break;
        }

        if (stat == SMS_MORE_DATA) {
            printf("Receive buffer too small, should be %d. Data truncated\n",
                            scalar.dwLen);
        }

        // Check scalar type, display accordingly.
        // ---------------------------------------
        switch (scalar.scType) {
        case SCALAR_STRING:
            printf("\t%25s: %s\n", scalar.pszName, scalar.pszValue);
            break;

        case SCALAR_INT:
            printf("\t%25s: %ld\n", scalar.pszName, scalar.dwValue);
            break;

        case SCALAR_TIME:
            // If there is a string equivalence use it.
            if (scalar.bStringEquivalence) {
                printf("\t%25s: %s\n", scalar.pszName, scalar.pszValue);
            } else {
                pszTime = ctime( &scalar.tValue );
                printf("\t%25s: %s", scalar.pszName, pszTime);
            }
            break;

        case SCALAR_BINARY:
            // Got binary data.
            // Just tell the user how much data there is.
            printf("\t%25s: Binary data %d bytes of data\n",
                    scalar.pszName, scalar.dwLen);
            break;
        }
    }

    // Why did we exit (other than no more scalars)?
    // ---------------------------------------------
    if (stat != SMS_NO_MORE_DATA) {
        DisplaySmsError("Bad return from Scalar access", stat);
    }

}

// ********************************************************************
//      Helper functions.
// ********************************************************************

// ====================================================================
//  Convert time string to time_t. Primitive checking only.
//  If there are any errors we print a wanring and set the date/time to
//  today.
// ====================================================================
time_t ReadTime( char szTime[] )
{
    int mon, day, year, hour, min;
    time_t today;
    time( &today );

    // Get the month (1..12)
    mon = (szTime[0] - '0') * 10 + (szTime[1]) - '0';
    mon--;
    if (mon < 0 || mon > 11) {
        printf("Month wrong (valid 01-12)\n");
        return(today);
    }

    // Day (1..31)
    day = (szTime[3] - '0') * 10 + (szTime[4] - '0');
    if (day < 1 || day > 31) {
        printf("Day wrong (Valid 01-07)\n");
        return(today);
    }

    // Year
    year = (szTime[6] - '0') * 10 + (szTime[7] - '0');
    if (year < 90 || year > 99) {
        printf("Year wrong (Valid 90-99)\n");
        return(today);
    }

    // Hour (0..23)
    hour = (szTime[9] - '0') * 10 + (szTime[10] - '0');
    if (hour < 0 || hour > 23) {
        printf("Hour wrong (Valid 00-23)\n");
        return(today);
    }

    // Minute (0..59)
    min = (szTime[12] - '0') * 10 + (szTime[13] - '0');
    if (min < 0 || min > 59) {
        printf("Minute wrong (Valid 00-59)\n");
        return(today);
    }

    // Format the time, we want a time_t.
    struct tm theTime;
    theTime.tm_mon   = mon;
    theTime.tm_mday  = day;
    theTime.tm_year  = year;
    theTime.tm_hour  = hour;
    theTime.tm_min   = min;
    theTime.tm_sec   = 0;
    theTime.tm_isdst = -1;

    time_t time = mktime( &theTime );
    return(time);

}

//**************************************************************************
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char* pszMessage
//          The user prompt to display.
//
//      char* pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//***************************************************************************
void InputString(const char* pszMessage, char* pszResult)
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

//**************************************************************************
// ConnectToDatasource
//
// Get the datasource connection information from the user and use it
// to connect to the datasource.
//
// Parameters:  None.
//
// Returns:
//      The connection handle or NULL if the connection failed.
//***************************************************************************
HANDLE ConnectToDatasource()
{
    // Get the information we need to connect to the
    // data source from the user.
    //==============================================
    char szServer[CCH_MAXINPUT];
    char szUser[CCH_MAXINPUT];
    char szPasswd[CCH_MAXINPUT];
    char szDatabase[CCH_MAXINPUT];

    printf("\n");
    printf("**************************\n");
    printf("* Connect to data source *\n");
    printf("**************************\n");
    InputString("SQL server name", szServer);
    InputString("SQL database name", szDatabase);
    InputString("User name for SQL server", szUser);
    InputString("Password for SQL server", szPasswd);
    printf("\n");

    // Connect to a data source. SQL in this case.
    // ===========================================
    DATASOURCE dsParams;

    dsParams.sqlParams.ds          = DB_SQL;
    dsParams.sqlParams.pszServer   = szServer;
    dsParams.sqlParams.pszUserName = szUser;
    dsParams.sqlParams.pszPasswd   = szPasswd;
    dsParams.sqlParams.pszDbName   = szDatabase;
    dsParams.sqlParams.pFunc       = NULL;         // No encryption.
    dsParams.sqlParams.pszKey      = "";

    HANDLE hConnect;
    SMS_STATUS stat;
    stat = SmsDataSourceConnect( &dsParams, &hConnect);

    if (stat != SMS_OK) {
        hConnect = NULL;
        DisplaySmsError("Connect to data source failed", stat);
    }

    return( hConnect );
}

//**********************************************************
// DidRequestHelp
//
// Check the program's arguments to see if the user asked for
// the help screen to be displayed.
//
// Parameters:
//      int argc
//          The argc value from main(argc, argv)
//
//      char** argv
//          The argv value from main(argc, argv)
//
// Returns:
//      TRUE if command line parameters included a request
//      for help to be displayed.
//
//***********************************************************
BOOL DidRequestHelp(int argc, char** argv)
{
    const char* pszCommand = argv[1];
    if (argc==2  && (strcmp((const char*) argv[1], "-help")==0)) {
        return(TRUE);
    }
    else {
        return(FALSE);
    }
}

//**********************************************************************
// DisplayHelp
//
// This function displays the samples help screen.
//
// Parameters:
//      None
//
// Returns:
//      Nothing.
//
//*********************************************************************
void DisplayHelp()
{
    printf("\n\n");
    printf("***********************************************************\n");
    printf("* newjob.exe:                                             *\n");
    printf("*                                                         *\n");
    printf("* SMS API Example: view, create, and delete job folders.  *\n");
    printf("*                                                         *\n");
    printf("* This sample illustrates the use of the SMS APIs in      *\n");
    printf("* order to view create, and delete job folders.           *\n");
    printf("* There are four types of job folders:                    *\n");
    printf("*         Run command on Workstation job                  *\n");
    printf("*         Share package on Server job                     *\n");
    printf("*         Remove package from Server job                  *\n");
    printf("*         System job                                      *\n");
    printf("* All of these except system jobs can be created and      *\n");
    printf("* deleted.                                                *\n");
    printf("*                                                         *\n");
    printf("* Syntax:                                                 *\n");
    printf("*     newjob.exe [-help]                                  *\n");
    printf("*                                                         *\n");
    printf("* Switches:                                               *\n");
    printf("*     -help       Display this help message.              *\n");
    printf("*                                                         *\n");
    printf("***********************************************************\n");
    printf("\n");
}

//*******************************************************************
// DisplayGreeting
//
// Display the initial greeting banner.
//
// Parameters:
//     None.
//
// Returns:
//     Nothing.
//******************************************************************
void DisplayGreeting()
{
    // For this sample, the greeting is identical to the help screen.
    //===============================================================
    DisplayHelp();
}

/* EOF: newjob.cpp */


README.TXT

DESCRIPTION:

    The "newpkg" sample is a command line program that illustrates
    how to use the SMS SDK to view, create and delete packages.

USAGE:

    When the newpkg program is run, it displays help text that
    describes how to use the program.

FILES PROVIDED:

    makefile
    readme.txt
    newpkg.cpp

BUILDING THE SAMPLE:

    To build this sample program just type nmake.

SMS API CALLS USED:

      SmsDataSourceConnect
      SmsDataSourceDisconnect
      SmsOpenContainer
      SmsCloseContainer
      SmsCreateFolder
      SmsPopulate
      SmsGetNextFolder
      SmsDescribeFolder
      SmsGetFolderID
      SmsGetFolderType
      SmsGetFolderCount
      SmsLinkFolder
      SmsUnlinkFolder
      SmsCommitFolder
      SmsRewind
      SmsCloseFolder
      SmsGetScalarCount
      SmsSetScalar
      SmsGetNextScalar


NEWPKG.CPP

//**************************************************************************
// Copyright (C) 1995 by Microsoft Corporation.
//
//  File: newpkg.cpp
//
//  DESCRIPTION:
//
//  This program illustrates how to use the SMS SDK to view, create, and
//  delete packages.  It can be used to create sharing and workstation
//  packages but not inventory packages.  Also the packages created with
//  only a single sub-folder.  Thus the packages it creates can not be for
//  both sharing and workstations.
//
//
//  PROGRAM FLOW:
//
//  After logging on to a datasource, a package container is created and
//  populated. The program then enters a loop asking whether the user
//  wants to view the next package folder, delete the currently viewed
//  folder, or create a new folder.
//
//  Author: Larry A. French
//
//==========================================================================
//  The following API calls are used in this program.
//
//      SmsDataSourceConnect
//      SmsDataSourceDisconnect
//      SmsOpenContainer
//      SmsCloseContainer
//      SmsCreateFolder
//      SmsPopulate
//      SmsGetNextFolder
//      SmsDescribeFolder
//      SmsGetFolderID
//      SmsGetFolderType
//      SmsGetFolderCount
//      SmsLinkFolder
//      SmsUnlinkFolder
//      SmsCommitFolder
//      SmsRewind
//      SmsCloseFolder
//      SmsGetScalarCount
//      SmsSetScalar
//      SmsGetNextScalar
//
//**************************************************************************

// ====================================================================
//  Includes.
// ====================================================================
#include <afx.h>
#include <stdlib.h>                 // for itoa
#include <smsapi.h>                 // Header for the APIs.
#include <time.h>                   // for time functions.

// Include the GetStatusName function.
// -------------------------------------
#include "..\common\status.inc"

// ====================================================================
//
//      Defines.
//
// ====================================================================

#define CCH_MAXSTRING   256
#define MAX_CREATE      100

#define PACKAGE_PERMISSIONS (PACKAGE_USER_READ | PACKAGE_USER_WRITE | PACKAGE_GUEST_READ)

// ====================================================================
//
//      Prototypes.
//
// ====================================================================

void DisplayScalars( HANDLE hFolder );
void ViewFolder( HANDLE hFolder );
HANDLE CreatePackageFolder( HANDLE hFolderParent );
time_t ReadTime();
BOOL DidRequestHelp(int argc, char** argv);
void DisplayHelp();
void DisplayGreeting();
BOOL UserSaysYes(const char* pszPrompt);
HANDLE ConnectToDatasource();
void DisplaySmsError(const char* pszMessage, SMS_STATUS stat);
const char* GetStatusName(SMS_STATUS stat);
void InputString(const char* pszMessage, char* pszResult);

// ====================================================================
//
//  The work starts here.
//
// ====================================================================
void main(int argc, char** argv)
{
    // Check to see if this is a request to display the help
    // screen.  If so, display it. Otherwise, display a greeting
    // banner.
    //=========================================================
    if (DidRequestHelp(argc, argv)) {
        DisplayHelp();
        return;
    }
    else {
        DisplayGreeting();
    }

    SMS_STATUS stat;
    HANDLE hConnect;
    HANDLE hContainerPkg;
    DWORD numFolders;
    DWORD numFoldersMax;
    DWORD dwFolder;
    DWORD dwIndex;

    // Connect to the data source.  If the connect fails, give up.
    // Note that a status message will be displayed by ConnectToDatasource.
    //=====================================================================
    hConnect = ConnectToDatasource();
    if (hConnect == NULL) {
        return;
    }

    // Open package container.
    // ==============================================================
    stat = SmsOpenContainer( C_PACKAGE, hConnect, &hContainerPkg );
    if (stat != SMS_OK) {
        printf("SmsOpenContainer failed: %d\n", stat);
        SmsDataSourceDisconnect( hConnect );
        return;
    }

    // Any filters would be set here.

    // Select all folders matching our filters.
    // ========================================
    stat = SmsPopulate( hContainerPkg, POP_SYNC, NULL);
    if (!(stat == SMS_OK || stat==SMS_EMPTY)) {
        printf("Bad return from SmsPopulate: %d\n", stat);
        SmsCloseContainer( hContainerPkg );
        SmsDataSourceDisconnect( hConnect );
        return;
    }

    stat = SmsGetFolderCount( hContainerPkg, F_ANY, &numFolders);
    if (stat != SMS_OK) {
        DisplaySmsError("SmsGetFolderCount failed on package container", stat);
        return;
    }

    printf("==========  Container has %d folders  =========\n\n", numFolders );

    // Allocate enough space to hold handles for all of the folders currently
    // in the container, plus more than enough space to hold all of the folders
    // that we will create.
    //=========================================================================
    numFoldersMax = numFolders + MAX_CREATE;

    HANDLE *phFolders = (HANDLE *)malloc(numFoldersMax * sizeof(HANDLE));
    HANDLE hFolder;

    // Open all top-level folders.
    // ---------------------------
    for (dwFolder = 0; dwFolder < numFolders; dwFolder++) {
        stat = SmsGetNextFolder( hContainerPkg, F_ANY, &hFolder );
        if (stat != SMS_OK) {
            printf("Error in retrieving a folder: %d\n", stat);
            break;
        }

        phFolders[dwFolder] = hFolder;
    }

    // Now we enter a loop asking the user whether they want to
    // view the next folder, Delete the folder just displayed,
    // or create a new folder.
    // =========================================================
    BOOL bDoQuit = FALSE;
    char szReply[CCH_MAXSTRING];
    dwFolder = 0;

    while (!bDoQuit) {

        printf("\n\n");
        printf("*******************************************\n");
        printf("* Enter a command:                        *\n");
        printf("*    N      View next folder.             *\n");
        printf("*    P      View previous folder.         *\n");
        printf("*    V      View current folder.          *\n");
        printf("*    D      Delete current folder.        *\n");
        printf("*    C      Create new folder.            *\n");
        printf("*    X      Exit.                         *\n");
        printf("*******************************************\n");
        printf("\n");

        InputString("[NPVDCX]", szReply);

        switch (szReply[0]) {
        case 0:
        case 'N': case 'n':

            // Fall through to the "View" command
            // =========== View next folder ====================
            if ((dwFolder + 1) < numFolders) {
                dwFolder++;
                ViewFolder( phFolders[dwFolder] );
            } else
                printf("At end of folder list\n");
           break;

        case 'P': case 'p':
            if (dwFolder > 0) {
                --dwFolder;
                ViewFolder(phFolders[dwFolder]);
            } else {
                printf("At first folder\n");
            }
            break;

        case 'V': case 'v':
            if (dwFolder >= 0 && dwFolder < numFolders) {
                ViewFolder( phFolders[dwFolder]);
            } else {
                printf("View: No such folder: %d\n", dwFolder);
            }
            break;

        case 'D': case 'd':
            // =========== Delete current folder ===============

            // Check we have a folder to delete.
            if (dwFolder >= numFolders || dwFolder < 0) {
                printf("Delete: No such folder: %d\n", dwFolder);
                break;
            }

            hFolder = phFolders[dwFolder];

            if (!UserSaysYes("Are you sure you want to delete this folder")) {
                printf("Delete cancelled\n");
                break;
            }

            stat = SmsUnlinkFolder( hFolder );
            if (stat != SMS_OK) {
                DisplaySmsError("Delete: UnlinkFolder failed", stat);
                break;
            }
            stat = SmsCommitFolder( hFolder );
            if (stat != SMS_OK) {
                DisplaySmsError("Delete: commit folder failed", stat);
                break;
            }

            // Close the folder so that its memory will be deallocated
            // when all other references to it go away.  Note that the
            // container retains a reference to its folder, so the memory
            // will not actually be deallocated until the container is
            // also closed.
            //==========================================================
            stat = SmsCloseFolder(hFolder);
            if (stat != SMS_OK) {
                DisplaySmsError("Delete: close folder failed", stat);
                break;
            }

            // Remove the folder's handle from the handle table.
            //==================================================
            for(dwIndex = dwFolder; dwIndex < (numFolders - 1); ++dwIndex) {
                phFolders[dwIndex] = phFolders[dwIndex+1];
            }

            // Adjust the folder index and folder count.
            // since there is now one less folder in the table.
            //=================================================
            --numFolders;
            if ((dwFolder > 0) && (dwFolder >= numFolders)) {
                --dwFolder;
            }
            printf("Folder deleted\n");
            break;

        case 'C': case 'c':
            // =========== Create new folder ===================

            // Check to see if the folder handle array is already
            // full.  If so, then display an error message and
            // begin a new command.
            //===================================================
            if (numFolders >= numFoldersMax) {
                printf("Can't create a new folder because the folder\n");
                printf("handle array is full.\n");
                printf("\n");
                break;
            }

            HANDLE hFolderPkg;
            hFolderPkg = CreatePackageFolder(hContainerPkg);
            if (hFolderPkg != NULL) {
                dwFolder = numFolders;
                phFolders[numFolders++] = hFolderPkg;
            }
            break;

        case 'X': case 'x':
            // Terminate.
            bDoQuit = TRUE;
            break;

        default:
            // print the message again.
            printf("Actions are: N(ext folder), D(elete folder), C(reate folder)\n");
            printf("Type x to exit\n");
            break;
        }
    }

    // Close all of the package folders so that their memory will
    // be deallocated (when the container is closed).
    //===========================================================
    for (dwFolder=0; dwFolder < numFolders; ++dwFolder) {
        stat = SmsCloseFolder(phFolders[dwFolder]);
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to close a package folder", stat);
        }
    }

    // Close the container.
    //=====================
    stat = SmsCloseContainer( hContainerPkg );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to close the package container", stat);
    }

    // Disconnect from the datasource.
    //================================
    stat = SmsDataSourceDisconnect( hConnect );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to disconnecct from the datasource", stat);
    }

    // Deallocate memory used for folder handles.
    //===========================================
    free( phFolders );

}  /* main */

//***************************************************************************
// ReadTime
//
// Ask the user to enter a time string and convert it to a time_t value.
//
// Input: None.
//
// Returns:
//      time_t     The time value the user entered.
//
//
//***************************************************************************
time_t ReadTime()
{
    char szTime[CCH_MAXSTRING];
    InputString("Enter time in the form mm/dd/yy hh:mm ", szTime);

    int mon, day, year, hour, min;
    time_t today;
    time( &today );

    // Get the month (1..12)
    mon = (szTime[0] - '0') * 10 + (szTime[1]) - '0';
    mon--;
    if (mon < 0 || mon > 11) {
        printf("Month wrong (valid 01-12)\n");
        return(today);
    }

    // Day (1..31)
    day = (szTime[3] - '0') * 10 + (szTime[4] - '0');
    if (day < 1 || day > 31) {
        printf("Day wrong (Valid 01-07)\n");
        return(today);
    }

    // Year
    year = (szTime[6] - '0') * 10 + (szTime[7] - '0');
    if (year < 90 || year > 99) {
        printf("Year wrong (Valid 90-99)\n");
        return(today);
    }

    // Hour (0..23)
    hour = (szTime[9] - '0') * 10 + (szTime[10] - '0');
    if (hour < 0 || hour > 23) {
        printf("Hour wrong (Valid 00-23)\n");
        return(today);
    }

    // Minute (0..59)
    min = (szTime[12] - '0') * 10 + (szTime[13] - '0');
    if (min < 0 || min > 59) {
        printf("Minute wrong (Valid 00-59)\n");
        return(today);
    }

    // Format the time, we want a time_t.
    struct tm theTime;
    theTime.tm_mon   = mon;
    theTime.tm_mday  = day;
    theTime.tm_year  = year;
    theTime.tm_hour  = hour;
    theTime.tm_min   = min;
    theTime.tm_sec   = 0;
    theTime.tm_isdst = -1;

    time_t time = mktime( &theTime );
    return(time);
}

//**************************************************************************
// SetScalarString
//
// This function sets the value specified scalar to the given string
// value.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//      char* pszName
//          The name of the scalar.
//      char* pszValue
//          The string that will be assigned to the scalar's value.
//
// Returns:
//      SMS_STATUS
//          The SMS status code.
//
// Note that the scalar specified must be of type SCALAR_STRING.
//***************************************************************************
SMS_STATUS SetScalarString(HANDLE hFolder, char* pszName, char* pszValue)
{
    SCALAR sc;
    sc.pszName = pszName;
    sc.scType = SCALAR_STRING;
    sc.pszValue = pszValue;

    SMS_STATUS stat;
    stat = SmsSetScalar(hFolder, &sc);
    return( stat );
}

//**************************************************************************
// SetScalarDword
//
// This function sets the value of the specified scalar to
// the given DWORD value.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//      char* pszName
//          The name of the scalar.
//      DWORD dwValue
//          The DWORD value to set the scalar to.
//
// Returns:
//      SMS_STATUS
//          The SMS status code.
//
// Note that the scalar specified must be of type SCALAR_INT.
//***************************************************************************
SMS_STATUS SetScalarDword(HANDLE hFolder, char* pszName, DWORD dwValue)
{
    SCALAR sc;
    sc.pszName = pszName;
    sc.scType = SCALAR_INT;
    sc.dwValue = dwValue;

    SMS_STATUS stat;
    stat = SmsSetScalar(hFolder, &sc);
    return( stat );
}

//**************************************************************************
// GetPlatformSelection
//
// Prompt the user to select a one or more platforms.  The platforms are
// selected by entering a comma separated list of platform indexes.  These
// platform indexes are converted to the platforms flags value for the SDK.
//
// Parameters:  None.
//
// Returns:
//      The SMS SDK platform flags value.
//***************************************************************************
DWORD GetPlatformSelection()
{
    printf("\n\n");
    printf("**********************************************\n");
    printf("* Select a platform from the following list. *\n");
    printf("**********************************************\n");
    printf("   1) MS-DOS \n");
    printf("   2) Windows 3.1\n");
    printf("   3) Windows 95\n");
    printf("   4) Windows NT (X86)\n");
    printf("   5) Windows NT (Mips)\n");
    printf("   6) Windows NT (Alpha)\n");
    printf("   7) Macintosh\n");
    printf("\n");

    DWORD dwPlatforms = 0;
    char szReply[CCH_MAXSTRING];
    InputString("Enter a comma separated list of platform number(s)", szReply);
    printf("\n");

    // Scan through the reply string and set the bits
    // dwPlatforms to correspond to the platform(s) specified
    // by the user.  Note that the list is a string of comma
    // separated digits.
    //======================================================
    const char* pszReply = szReply;
    while (*pszReply) {
        if (*pszReply < '1' || *pszReply > '7') {
            // An invalid character is present in the
            // reply.  Flag this error by returning zero.
            //==========================================
            dwPlatforms = 0;
            break;
        }

        // Interpret the platform selector character.
        //===========================================
        switch(*pszReply++) {
        case '1':
            dwPlatforms |= PLTFRM_MSDOS;
            break;
        case '2':
            dwPlatforms |= PLTFRM_WIN16;
            break;
        case '3':
            dwPlatforms |= PLTFRM_WIN95;
            break;
        case '4':
            dwPlatforms |= PLTFRM_WIN32_X86;
            break;
        case '5':
            dwPlatforms |= PLTFRM_WIN32_MIPS;
            break;
        case '6':
            dwPlatforms |= PLTFRM_WIN32_ALPHA;
            break;
        case '7':
            dwPlatforms |= PLTFRM_MACINTOSH;
            break;
        }

        // Skip any trailing whitespace after the digit.
        //==============================================
        while (*pszReply == ' ' || *pszReply=='\t') {
            ++pszReply;
        }

        // Skip the comma.
        //================
        if (*pszReply == ',') {
            ++pszReply;
        }

        // Skip white space preceding the next digit.
        //=========================
        while (*pszReply == ' ' || *pszReply=='\t') {
            ++pszReply;
        }

    }

    return( dwPlatforms );
}

//**************************************************************************
// ConnectToDatasource
//
// Get the datasource connection information from the user and use it
// to connect to the datasource.
//
// Parameters:  None.
//
// Returns:
//      The connection handle or NULL if the connection failed.
//***************************************************************************
HANDLE ConnectToDatasource()
{
    // Get the information we need to connect to the
    // data source from the user.
    //==============================================
    char szServer[CCH_MAXSTRING];
    char szUser[CCH_MAXSTRING];
    char szPasswd[CCH_MAXSTRING];
    char szDatabase[CCH_MAXSTRING];

    printf("\n");
    printf("**************************\n");
    printf("* Connect to data source *\n");
    printf("**************************\n");
    InputString("SQL server name", szServer);
    InputString("SQL database name", szDatabase);
    InputString("User name for SQL server", szUser);
    InputString("Password for SQL server", szPasswd);
    printf("\n");

    // Connect to a data source. SQL in this case.
    // ===========================================
    DATASOURCE dsParams;

    dsParams.sqlParams.ds          = DB_SQL;
    dsParams.sqlParams.pszServer   = szServer;
    dsParams.sqlParams.pszUserName = szUser;
    dsParams.sqlParams.pszPasswd   = szPasswd;
    dsParams.sqlParams.pszDbName   = szDatabase;
    dsParams.sqlParams.pFunc       = NULL;         // No encryption.
    dsParams.sqlParams.pszKey      = "";

    HANDLE hConnect;
    SMS_STATUS stat;
    stat = SmsDataSourceConnect( &dsParams, &hConnect);

    if (stat != SMS_OK) {
        hConnect = NULL;
        DisplaySmsError("Connect to data source failed", stat);
    }

    return( hConnect );
}

//**************************************************************************
// ViewFolder
//
//  Display the folder contents.  This version only displays the folder name
//  and type, number of scalars, and the scalars. Other information not
//  relevant.
//
//  NOTE: no error checking.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder to view.
//
// Returns: Nothing.
//
//**************************************************************************
void ViewFolder( HANDLE hFolder )
{
    char szFolderID[SMS_DATA_BUFF_SIZE + 1];        // This folder's ID.
    char szfType[SMS_DATA_BUFF_SIZE + 1];           // Folder's tag.
    DWORD fType;                        // Folder's type.
    DWORD ctScalars;                    // How many scalars in this folder.
    SMS_STATUS stat;

    // Get folder ID and type.
    // -----------------------
    stat = SmsGetFolderID( hFolder, szFolderID );
    if (stat == SMS_NEW_FOLDER) {
        strcpy( szFolderID, "New folder without an ID yet" );

    }
    else if (stat != SMS_OK) {
        DisplaySmsError("ViewFolder: can't get folder ID", stat);
        strcpy( szFolderID, "Couldn't get the folder ID" );
    }

    printf("============== %s ===========\n", szFolderID);

    stat = SmsGetFolderType( hFolder, &fType, szfType );
    if (stat != SMS_OK) {
        DisplaySmsError("ViewFolder: can't get folder type", stat);
    }

    printf("SCALARS FOR FOLDER %s\n", szFolderID);

    // Get count of scalars.
    // ---------------------
    stat = SmsGetScalarCount( hFolder, &ctScalars );
    if (stat != SMS_OK) {
        DisplaySmsError("ViewFolder: can't get scalar count", stat);
    }
    printf("%s (%s) contains %d scalars\n", szFolderID, szfType, ctScalars);

    // Display the scalars.
    // --------------------
    DisplayScalars( hFolder );

    // Get the folder count
    DWORD ctFolders;
    stat = SmsGetFolderCount(hFolder, F_ANY, &ctFolders);
    printf("%s (%s) contains %d sub-folders\n", szFolderID, szfType, ctFolders);
    for (DWORD dwFolder = 0; dwFolder < ctFolders; ++dwFolder) {
        printf("Getting subfolder\n");
        HANDLE hSubFolder;
        stat = SmsGetNextFolder(hFolder, F_ANY, &hSubFolder);
        if (stat != SMS_OK) {
            DisplaySmsError("SmsGetNextFolder failed", stat);
            break;
        }

        ViewFolder(hSubFolder);

        // Close the sub-folder to avoid a memory leak.
        //============================================
        stat = SmsCloseFolder(hSubFolder);
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to close an open folder", stat);
            break;
        }
    }

    SmsRewind(hFolder, RW_FOLDER | RW_SCALAR);

}  /* ViewFolder */

//*************************************************************************
// CreateWcl
//
// This method is called to create a WCL subfolder in
// a package folder.
//
// Parameters:
//      HANDLE hFolderPkg
//          This is the handle of the parent folder that the newly
//          created subfolder will be inserted into.  For WCL and
//          program item folders, the parent folder is a package
//          folder.
//
// Returns:
//      HANDLE
//          This is the handle of the newly created subfolder.
//
//*************************************************************************
HANDLE CreateWcl(HANDLE hFolderPkg)
{
    printf("\n\n");
    printf("*****************************************************\n");
    printf("* Creating the workstation command line sub-folder. *\n");
    printf("*****************************************************\n");
    printf("\n");

    HANDLE hFolderNew;
    SMS_STATUS stat;
    char szError[CCH_MAXSTRING];
    char szReply[CCH_MAXSTRING];
    DWORD dwPlatforms;

    // Create the WCL folder
    //======================
    stat = SmsCreateFolder( hFolderPkg, F_WCL, "", &hFolderNew );
    if (stat != SMS_OK) {
        sprintf(szError, "SmsCreateFolder failed to create the WCL folder");
        DisplaySmsError(szError, stat);
        return( NULL );
    }

    dwPlatforms = GetPlatformSelection();

    printf("\n\n");
    printf("*********************************************\n");
    printf("* Enter values for the WCL folder scalars.  *\n");
    printf("*********************************************\n");
    printf("\n");

    InputString("Name", szReply);
    SetScalarString(hFolderNew, "Name", szReply);

    InputString("Command", szReply);
    SetScalarString(hFolderNew, "Command", szReply);

    SetScalarDword(hFolderNew, "Flags", 0);
    SetScalarDword(hFolderNew, "Platforms", dwPlatforms);

    // Link the new WCL folder into the folder hierarchy.  Note that
    // this does not insert the folder into the datasource.  Only
    // SmsCommitFolder modifies the datasource.
    //==============================================================
    stat = SmsLinkFolder(hFolderNew);
    if ((stat != SMS_OK) && (stat != SMS_PARENT_NEEDS_COMMIT)) {
        sprintf(szError, "SmsLinkFolder failed to link the WCL folder");
        DisplaySmsError(szError, stat);
        return( NULL );
    }

    return( hFolderNew ) ;
}

//*************************************************************************
// CreateProgItem
//
// This method is called to create a program item subfolder in
// a package folder.
//
// Parameters:
//      HANDLE hFolderPkg
//          This is the handle of the parent folder that the newly
//          created subfolder will be inserted into.  For WCL and
//          program item folders, the parent folder is a package
//          folder.
//
// Returns:
//      HANDLE
//          This is the handle of the newly created subfolder.
//
//*************************************************************************
HANDLE CreateProgItem(HANDLE hFolderPkg)
{
    printf("\n\n");
    printf("*****************************************\n");
    printf("* Creating the program item sub-folder. *\n");
    printf("*****************************************\n");
    printf("\n");

    HANDLE hFolderNew;
    SMS_STATUS stat;
    char szReply[CCH_MAXSTRING];
    char szError[CCH_MAXSTRING];
    DWORD dwPlatforms;

    // Create the programe item folder
    //================================
    stat = SmsCreateFolder( hFolderPkg, F_PROGITEM, "", &hFolderNew );
    if (stat != SMS_OK) {
        sprintf(szError, "SmsCreateFolder failed to create the program item folder");
        DisplaySmsError(szError, stat);
        return( NULL );
    }

    // Set the value of the scalars for the program item.
    //===================================================
    dwPlatforms = GetPlatformSelection();

    printf("\n\n");
    printf("******************************************************\n");
    printf("* Enter values for the program item folder scalars.  *\n");
    printf("******************************************************\n");
    printf("\n");

    InputString("Description", szReply);
    SetScalarString(hFolderNew, "Description", szReply);

    InputString("Command line", szReply);
    SetScalarString(hFolderNew, "Command line", szReply);

    SetScalarDword(hFolderNew,  "Run minimized", FALSE);
    SetScalarDword(hFolderNew,  "Platforms", dwPlatforms);
    SetScalarString(hFolderNew, "Configure script file", "");
    SetScalarDword(hFolderNew,  "Search local flag", 1);
    SetScalarDword(hFolderNew,  "Drive mode", 1);

    InputString("Registry name", szReply);
    SetScalarString(hFolderNew, "Registry name", szReply);
    SetScalarDword(hFolderNew,  "Executable flag", TRUE);

    // Link the new WCL folder into the folder hierarchy.  Note that
    // this does not insert the folder into the datasource.  Only
    // SmsCommitFolder modifies the datasource.
    //==============================================================
    stat = SmsLinkFolder(hFolderNew);

if ((stat != SMS_OK) && (stat != SMS_PARENT_NEEDS_COMMIT)) {
        sprintf(szError, "SmsLinkFolder failed to link the programe item folder");
        DisplaySmsError(szError, stat);
        return( NULL );
    }

    return( hFolderNew ) ;
}

//*************************************************************************
// CreatePackageFolder
//
// Create a new package folder
//
// Parameters:
//      HANDLE hPackageContainer
//          This is the handle to the package folder's parent, which
//          is a package container.
//
// Returns:
//      The package folder's handle.
//
// ====================================================================
HANDLE CreatePackageFolder(HANDLE hPackageContainer)
{
    SMS_STATUS stat;
    char szReply[CCH_MAXSTRING];
    HANDLE hFolderPkg = NULL;
    HANDLE hFolderWCL = NULL;
    HANDLE hFolderProgItem = NULL;

    stat = SmsCreateFolder( hPackageContainer, F_PACKAGE, "", &hFolderPkg );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsCreateFolder failed to create the package folder.", stat);
        return( NULL );
    }

    printf("\n\n");
    printf("***************************************************\n");
    printf("* Creating package folder.  Select a package      *\n");
    printf("* type.                                           *\n");
    printf("*                                                 *\n");
    printf("*    S         Sharing.                           *\n");
    printf("*    W         Workstation.                       *\n");
    printf("***************************************************\n");
    printf("\n");

    InputString("SW: ", szReply);
    switch(szReply[0]) {
    case 'S': case 's':
        hFolderProgItem = CreateProgItem(hFolderPkg);
        break;
    case 'W': case 'w':
        hFolderWCL = CreateWcl(hFolderPkg);
        break;
    }

    printf("\n\n");
    printf("*************************************\n");
    printf("* Enter the package folder scalars. *\n");
    printf("*************************************\n");
    printf("\n");

    InputString("Package name", szReply);
    SetScalarString(hFolderPkg, "Name", szReply);

    InputString("Package comment", szReply);
    SetScalarString(hFolderPkg, "Comment", szReply);

    // If this package contains a workstation command line, then set the
    // scalars that are relevant to workstation command lines.
    //==================================================================
    if (hFolderWCL) {
        SetScalarDword(hFolderPkg, "Share permissions", (DWORD) PACKAGE_PERMISSIONS);

        InputString("Workstation root", szReply);
        SetScalarString(hFolderPkg, "Workstation root", szReply);

    }

    // If this package contains a program item, then set the scalars
    // that are relevant to program items.
    //==============================================================
    if (hFolderProgItem) {
        SetScalarDword(hFolderPkg, "Share permissions", (DWORD) PACKAGE_PERMISSIONS);

        InputString("Server root", szReply);
        SetScalarString(hFolderPkg, "Server root", szReply);

        InputString("Share name", szReply);
        SetScalarString(hFolderPkg, "Share name", szReply);
    }

    // Display the folder and ask the user if he or she really wants to
    // write the folder to the datasource.  If the user says yes, then
    // commit the subfolder's first and then the package folder.  Note that
    // it does not make any sense to commit the package folder before its
    // subfolders because, once a folder is committed, modification is not
    // allowed and it would not be possible to commit the subfolders.
    //===================================================================

    ViewFolder(hFolderPkg);

    if (UserSaysYes("OK to write the folder to the datasource")) {

        if (hFolderWCL) {
            stat = SmsCommitFolder(hFolderWCL);
            if (stat != SMS_OK && stat!=SMS_PARENT_NEEDS_COMMIT) {
                DisplaySmsError("Failed to commit the WCL folder", stat);
                goto CLOSE_FOLDERS_AND_EXIT;
            }
        }

        if (hFolderProgItem) {
            stat = SmsCommitFolder(hFolderProgItem);
            if (stat != SMS_OK && stat!=SMS_PARENT_NEEDS_COMMIT) {
                DisplaySmsError("Failed to commit the program item folder", stat);
                goto CLOSE_FOLDERS_AND_EXIT;
            }
        }

        // Place the package folder in the in-memory hierarchy.
        stat = SmsLinkFolder(hFolderPkg);
        if (stat != SMS_OK) {
            DisplaySmsError("SmsLinkFolder failed on the package folder", stat);
            goto CLOSE_FOLDERS_AND_EXIT;
        }

        // write it back to data source.
        stat = SmsCommitFolder( hFolderPkg );
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to commit the package folder", stat);
        }
    }

    // Fall through to close the folders.

CLOSE_FOLDERS_AND_EXIT:

    // Close the folder's so that memory will be freed when there are no
    // references to them.  A folder's memory is freed when its reference
    // count goes to zero.  A folder has one reference for each handle to it
    // that is returned plus an additional refernce from its parent folder or
    // container.  Thus we close the folder handles here so that the memory
    // allocated to the folders will be freed, but the memory won't actually
    // be freed until the package container is closed.  If you fail to close
    // any handle returned to you by the SMS API, the memory for that folder
    // will never be deallocated (even when the folder's container is closed).
    //========================================================================

    if (hFolderWCL != NULL) {
        stat = SmsCloseFolder(hFolderWCL);
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to close the WCL folder", stat);
        }
    }

    if (hFolderProgItem != NULL) {
        stat = SmsCloseFolder(hFolderProgItem);
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to close the program item folder", stat);
        }
    }

    return( hFolderPkg );
}

//*************************************************************************
// DisplayScalars
//
// Display a folder's scalars.
//
// Parameters:
//      HANDLE hfolder
//          This is the folder handle.
//
// Returns: Nothing.
//
// ====================================================================
void DisplayScalars( HANDLE hFolder )
{
    SMS_STATUS stat = SMS_OK;
    SCALAR scalar;
    char *pszTime;          // For conversion of time scalars.

    // The scalars do not have their own string value buffer, so
    // we allocate the buffers here and set pointers to them in the
    // scalar structure.
    //=============================================================
    char szName[SMS_DATA_BUFF_SIZE+1];
    char szValue[SMS_DATA_BUFF_SIZE+1];
    scalar.pszName  = szName;
    scalar.pszValue = szValue;
    scalar.pValue = NULL;

    while (1) {

        scalar.dwLen = sizeof(szValue)-1;       // must tell him the size

        stat = SmsGetNextScalar( hFolder, &scalar);
        if (stat == SMS_NO_MORE_DATA) {
            break;
        }

        if (stat != SMS_OK && stat != SMS_MORE_DATA) {
            DisplaySmsError("SmsGetNextScalar failed in method DisplayScalars", stat);
            break;
        }

        if (stat == SMS_MORE_DATA) {

            printf("Receive buffer too small, should be %d. Data truncated\n",
                            scalar.dwLen);
        }

        // Check scalar type, display accordingly.
        // ---------------------------------------
        switch (scalar.scType) {
        case SCALAR_STRING:
            printf("\t%25s: %s\n", scalar.pszName, scalar.pszValue);
            break;

        case SCALAR_INT:
            printf("\t%25s: %ld\n", scalar.pszName, scalar.dwValue);
            break;

        case SCALAR_TIME:
            // Check if we have a string equivalence. If so use it.
            if (scalar.bStringEquivalence) {
                printf("\t%25s: %s\n", scalar.pszName, scalar.pszValue);

            } else {
                pszTime = ctime( &scalar.tValue );
                printf("\t%25s: %s", scalar.pszName, pszTime);
            }
            break;

        case SCALAR_BINARY:
            // In this sample we won't display the binary data.
            // Just tell the user its size.
            printf("\t%25s (Binary - this is its size): %ld\n", scalar.pszName, scalar.dwLen);
            break;
        }
    }

    // Why did we exit (other than no more scalars)?
    //==============================================
    if (stat != SMS_NO_MORE_DATA) {
        printf("Bad return from Scalar access: %d\n", stat);
    }

    printf("\n");
}

// ********************************************************************
//      Helper functions.
// ********************************************************************

//**********************************************************
// DidRequestHelp
//
// Check the program's arguments to see if the user asked for
// the help screen to be displayed.
//
// Parameters:
//      int argc
//          The argc value from main(argc, argv)
//
//      char** argv
//          The argv value from main(argc, argv)
//
// Returns:
//      TRUE if command line parameters included a request
//      for help to be displayed.
//
//***********************************************************
BOOL DidRequestHelp(int argc, char** argv)
{
    const char* pszCommand = argv[1];
    if (argc==2  && (strcmp((const char*) argv[1], "-help")==0)) {
        return(TRUE);
    }
    else {
        return(FALSE);
    }
}

//**********************************************************************
// DisplayHelp
//
// This function displays the samples help screen.
//
// Parameters:
//      None
//
// Returns:
//      Nothing.
//
//*********************************************************************
void DisplayHelp()
{
    printf("\n\n");
    printf("********************************************************\n");
    printf("* newpkg.exe:                                          *\n");
    printf("*                                                      *\n");
    printf("* This is a sample program for the SMS SDK. It can be  *\n");
    printf("* used to view, create, or delete packages in the SMS  *\n");
    printf("* database.                                            *\n");
    printf("*                                                      *\n");
    printf("* Syntax:                                              *\n");
    printf("*     newpkg.exe [-help]                               *\n");
    printf("*                                                      *\n");
    printf("* Switches:                                            *\n");
    printf("*     -help       Display this help screen.            *\n");
    printf("*                                                      *\n");
    printf("********************************************************\n");
    printf("\n");
}

//*******************************************************************
// DisplayGreeting
//
// Display the initial greeting banner.
//
// Parameters:
//     None.
//
// Returns:
//     Nothing.
//******************************************************************
void DisplayGreeting()
{
    // For this sample, the greeting is identical to the help screen.
    //===============================================================
    DisplayHelp();
}

//**************************************************************************
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char* pszMessage
//          The user prompt to display.
//
//      char* pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//***************************************************************************
void InputString(const char* pszMessage, char* pszResult)
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

//**************************************************************************
// UserSaysYes
//
// Prompt the user to reply yes or no.  If the user enters "Y" then
// return TRUE, otherwise FALSE.
//
// Parameters:
//      const char* pszPrompt
//          The string used to prompt the user
//
// Returns:
//      TRUE = User replied "Y" or "y"
//      FALSE = User gave any other reply.
//**************************************************************************
BOOL UserSaysYes(const char* pszPrompt)
{
    printf("%s? [y/n]", pszPrompt);
    char szReply[CCH_MAXSTRING];
    gets(szReply);

    return(szReply[0] == 'y' || szReply[0] == 'Y');

}

/* EOF: newjob.cpp */


PDF.CPP

// ====================================================================
//
//  File: pdf.cpp
//
//  Copyright (C) 1996 Microsoft Corp.
//
//  Author:
//      Jonathan Shuval     Microsoft Corp.
//
//  This program illustrates how to import a pdf file into SMS using
//  the SMS API set.
//
//  This sample is referenced in the Back Office documentation for SMS
//
// ====================================================================

#define PDF_NO_ERROR                0
#define PDF_ERROR                   1
#define PDF_ERROR_FILENOTFOUND      2
#define PDF_ERROR_NOTPDF            3
#define PDF_ERROR_VERSION           4
#define PDF_ERROR_MISSINGINFO       5

#define FILE_VERSION_PDF            "1.0"

// ====================================================================
//
//  Includes.
//
// ====================================================================
#include <afx.h>
#include <smsapi.h>             // Header for the APIs.

// Include the GetStatusName and DisplaySmsEror functions.
// -------------------------------------------------------
#include "..\common\status.inc"

// ====================================================================
//  Defines.
// ====================================================================
#define CCH_MAXINPUT    256
#define MAX_BUF         2048    // Max local buf size.
                                // No key info can exceed this.

// ====================================================================
//  Globals
// ====================================================================
CString gsFilename;             // PDF filename.
CString gsWorkingPath;          // Directory containing PDF file.

// ====================================================================
//
//  Local prototypes.
//
// ====================================================================

// Connect to the SMS datasource.
// -------------------------------------------------------------
HANDLE ConnectToDatasource();

// Display the help message.
// -------------------------------------------------------------
void DisplayHelp();

// Display the greeting.
// -------------------------------------------------------------
void DisplayGreeting();

// Check if there was a request for help on the command line.
// -------------------------------------------------------------
BOOL DidRequestHelp( int argc, char **argv );

// Display an error message with its SMS status value.
// -------------------------------------------------------------
void DisplaySmsError( const char *pszMessage, SMS_STATUS stat );

// Get the string equivalence for an SMS status code.
// -------------------------------------------------------------
const char *GetStatusName( SMS_STATUS stat );

// Prompt the user for input and return the reply.
// -------------------------------------------------------------
void InputString( const char *pszMessage, char *pszResult );

// Set the value of string and integer scalars.
// -------------------------------------------------------------
void SetScalarString(HANDLE hFolder, char *pszName, const char *pszValue);
void SetScalarInt(HANDLE hFolder, char *pszName, DWORD dwValue);

// Display specified folder (and sub-folders).
// -------------------------------------------------------------
void ViewFolder( HANDLE hFolder );

// Display a folder's scalars.
// -------------------------------------------------------------
void DisplayScalars( HANDLE hFolder );

// Decomposes string of comma-separated platforms into bit field.
// -------------------------------------------------------------
DWORD GetPlatforms( char *buf );

// Controlling function. Calls workers to parse the pdf and
// create the folder(s).
// -------------------------------------------------------------
DWORD Import( HANDLE hConnect, HANDLE hContainer, const char *pPathToPDF);

// Parse basic package identification info.
// -------------------------------------------------------------
DWORD ParsePackageDef( HANDLE hPkgFolder );

// Parse properties for program items.
// -------------------------------------------------------------
DWORD ParseProgramItemProperties( HANDLE hPkgFolder );

// Parse properties for setup variations.
// -------------------------------------------------------------
DWORD ParseSetupVariations( HANDLE hPkgFolder );

// Parse properties for setup for sharing.
// -------------------------------------------------------------
DWORD ParseSetupForSharing( HANDLE hPkgFolder );

// Functions for parsing inventory rules.
// -------------------------------------------------------------
DWORD ParseInventoryRules( HANDLE hPkgFolder );
void FillFileTokens(TOKEN *pToken, char *pszBuf );

// Test if specified file exists.
// -------------------------------------------------------------
BOOL FileExists(const char *pszPath);

// Initialise the (global) platform aray. This contains
// CPlatform objects each of which contains a string form of the
// platform name and a binary value to be used when setting the
// platforms scalar.
// -------------------------------------------------------------
void InitPlatforms();

// ====================================================================
//  The work starts here.
// ====================================================================

void main( int argc, char **argv )
{
    // Check to see if this is a request to display the help
    // screen. If so, display it. Otherwise, display a greeting
    // banner.
    // =========================================================
    if (DidRequestHelp(argc, argv)) {
        DisplayHelp();
        return;
    }
    else {
        DisplayGreeting();
    }

    SMS_STATUS stat;                    // Return from the APIs.
    HANDLE hConnect;                    // Connection handle.
    HANDLE hContainer;                  // Handle to a container.

    // Get and display the SMS API version.
    // ------------------------------------
    char *pszVersion;                   // Receives pointer to version string.
    SmsAPIVer( &pszVersion );           // Get version
    printf("Using %s\n", pszVersion);   // and print it.

    //===========================================
    // Connect to the SMS datasource.
    //===========================================
    hConnect = ConnectToDatasource();
    if (hConnect == NULL) {
        return;
    }

    //===========================================
    // Get the pdf file name and validate it.
    //===========================================
char szPDF[128];

if(argc == 2)
{
// Assume pdf file on command line
strcpy(szPDF, argv[1]);
printf("Using pdf file : %s\n",szPDF);
}
else
{
InputString("PDF file name", szPDF);
}

    if (!FileExists( szPDF )) {
        printf("Can't find pdf file: %s\n", szPDF);
printf("Make sure it's in the current directory\n");
        SmsDataSourceDisconnect( hConnect );
        return;
    }

    // Open package container.
    // ========================
    stat = SmsOpenContainer( C_PACKAGE, hConnect, &hContainer );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsOpenContainer failed", stat);
        SmsDataSourceDisconnect( hConnect );
        return;
    }

    // Does the work.
    // ==============
    DWORD dwRet = Import( hConnect, hContainer, szPDF );
    if (dwRet != 0) {
        printf("Error %d, file not processed\n", dwRet);
    }

    // Close everything.
    // =================
    SmsCloseContainer( hContainer );
    SmsDataSourceDisconnect( hConnect );

}

//*************************************************************************
//
//  Import()
//
//  Imports a PDF and initializes the member object pointed to by
//  <pPackage>.  This function calls other lower level parsing functions.
//
//  Parameters:
//      HANDLE hConnect                     Connection handle.
//      HANDLE hContainer                   Container handle.
//      const char *pPathToPDF              Path to the PDF file.
//
//
//  Returns:
//
//      DWORD                               0 on success, or a specific
//                                          error code.
//
//*************************************************************************
DWORD Import( HANDLE hConnect, HANDLE hContainer, const char *pPathToPDF )
{
    gsFilename = pPathToPDF;        // Hold globally as CString.

    // Compute the working directory/path based on analysis
    // of the filename.
    // ====================================================

    int iLastSlash = gsFilename.ReverseFind('\\');

    if (iLastSlash != -1) {
        gsWorkingPath = gsFilename.Left(iLastSlash) + "\\";
    }

    // Create a package folder.
    // ========================
    SMS_STATUS stat;
    HANDLE hPkgFolder;
    stat = SmsCreateFolder( hContainer, F_PACKAGE, "", &hPkgFolder );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsCreateFolder (package) failed", stat);
        return(PDF_ERROR);
    }

    // Initialise the platforms array.
    // ===============================
    InitPlatforms();

    // Start working.
    // ==============

    DWORD dwRet = PDF_NO_ERROR;

    dwRet = ParsePackageDef( hPkgFolder );
    if (dwRet != 0) {
        printf("ParsePackageDef failed: %d\n", dwRet);
        return(dwRet);
    }

    dwRet = ParseSetupVariations( hPkgFolder );
    if (dwRet != 0) {
        printf("ParseSetupVariations failed: %d\n", dwRet);
        return(dwRet);
    }

    dwRet = ParseSetupForSharing( hPkgFolder );
    if (dwRet != 0) {
        printf("ParseSetupForSharing failed: %d\n", dwRet);
        return(dwRet);
    }

    dwRet = ParseProgramItemProperties( hPkgFolder );
    if (dwRet != 0) {
        printf("ParseProgramItemProperties failed: %d\n", dwRet);
        return(dwRet);
    }

    dwRet = ParseInventoryRules( hPkgFolder );
    if (dwRet != 0) {
        printf("ParseInventoryRules failed: %d\n", dwRet);
        return(dwRet);
    }

    // If we got this far then everything must have been ok.
    // We can link and commit the folder.
    // =====================================================
    stat = SmsLinkFolder( hPkgFolder );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsLinkFolder for package failed", stat);
        return(PDF_ERROR);
    }
    stat = SmsCommitFolder( hPkgFolder );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsCommitFolder for package failed", stat);
        return(PDF_ERROR);
    }
    // Display the folder.
    // ===================
    printf("This is what the folder looks like:\n");
    ViewFolder( hPkgFolder );

    return(dwRet);
}

//***************************************************************************
//
//  ParsePackageDef
//
//  Parses out basic package identification info.
//
//  Parameters:
//      HANDLE hPkgFolder               Folder to newly created package
//                                      folder.
//  Returns:
//      DWORD                           0 on success or a PDF_ error code.
//
//***************************************************************************
DWORD ParsePackageDef( HANDLE hPkgFolder )
{
    CString sWork;
    char *pszTmp = new char[MAX_BUF];
    char szBuff[MAX_BUF];
    DWORD dwCheck = 0;
    DWORD dwRet = PDF_ERROR;
CString sDirectory;
char sDirBuff[MAX_BUF];

if(!GetCurrentDirectory(MAX_BUF,
sDirBuff))
{
dwRet = PDF_ERROR_FILENOTFOUND;
goto Exit;
}

sDirectory = sDirBuff;
sDirectory += '\\';
gsFilename = sDirectory + gsFilename;

    // Verify file exists.
    // ===================

    if (!FileExists((const char *)gsFilename)) {
        dwRet = PDF_ERROR_FILENOTFOUND;
        goto Exit;
    }

    // Verify we have a PDF of the correct version.
    // ============================================

    dwCheck = GetPrivateProfileString(  "PDF",
                                        "Version",
                                        "",
                                        pszTmp,
                                        MAX_BUF,
                                        gsFilename);

    if (!dwCheck) {
        printf("This file is not a valid PDF file.\n");
        dwRet = PDF_ERROR_NOTPDF;
        goto Exit;
    }

    if (_strnicmp(pszTmp, FILE_VERSION_PDF, strlen(FILE_VERSION_PDF)) != 0) {
        printf("\n");
        printf("This file does not have the correct PDF version.\n");
        printf("Acceptable PDF version is %s\n", FILE_VERSION_PDF);
        dwRet = PDF_ERROR_VERSION;
        goto Exit;
    }

    // [Package Definition]
    // ====================

    // Product
    // =======

    dwCheck = GetPrivateProfileString(  "Package Definition",
                                        "Product",
                                        "",
                                        pszTmp,
                                        MAX_BUF,
                                        gsFilename);

    if (!dwCheck) {
        dwRet = PDF_ERROR_MISSINGINFO;
        goto Exit;
    }

    sWork = pszTmp;
    sWork += " ";

    // Version
    // =======

    GetPrivateProfileString("Package Definition",
                            "Version",
                            "",
                            pszTmp,
                            MAX_BUF,
                            gsFilename);

    sWork += pszTmp;  // product + version

    SetScalarString( hPkgFolder, "Name", (const char *)sWork);

    // Comment
    // =======

    GetPrivateProfileString("Package Definition",
                            "Comment",
                            "",
                            pszTmp,
                            MAX_BUF,
                            gsFilename);

    SetScalarString( hPkgFolder, "Comment", pszTmp );

    // Source directory for Workstations.
    // If it's not in the pdf ask the user for it. It's mandatory.
    // ===========================================================
    GetPrivateProfileString("Package Definition",
                            "ImportDirectory",
                            "",
                            pszTmp,
                            MAX_BUF,
                            gsFilename);
    if (strlen(pszTmp) == 0) {
        printf("\n");
        printf("If this package is destined to be set up for workstations\n");
        printf("you must enter a value for the ImportDirectory.\n");
        printf("If you do not do so then the package will not be completely\n");
        printf("set up and you must alter it via the SMS Admin utility\n");
        printf( "Please enter a value (relative to path %s) or just hit ENTER: ",
                            (const char *)gsWorkingPath);
        gets( pszTmp );
    }
    if (strlen(pszTmp) != 0) {
        strcpy( szBuff, (const char *)gsWorkingPath );
        strcat( szBuff, pszTmp );
        SetScalarString( hPkgFolder, "Workstation root", szBuff );
    }

    // Source directory for Shared Server Apps.
    // If it's not in the pdf ask the user for it. It's mandatory.
    // ===========================================================
    GetPrivateProfileString("Package Definition",
                            "SetupDirectory",
                            "",
                            pszTmp,
                            MAX_BUF,
                            gsFilename);
    if (strlen(pszTmp) == 0) {
        printf("If this package is destined to be set up for sharing\n");
        printf("you must enter a value for the SetupDirectory.\n");
        printf("If you do not do so then the package will not be completely\n");
        printf("set up and you must alter it via the SMS Admin utility\n");
        printf( "Please enter a value (relative to path %s) or just hit ENTER: ",
                            (const char *)gsWorkingPath);
        gets( pszTmp );
    }
    if (strlen(pszTmp) != 0) {
        strcpy( szBuff, (const char *)gsWorkingPath );
        strcat( szBuff, pszTmp );
        SetScalarString( hPkgFolder, "Server root", szBuff );
    }

    // Get package access specifications from PDF. If there are specifications
    // set them in the package. If there are no specifications leave it alone.
    // =======================================================================

    GetPrivateProfileString("Package Definition",
                            "WorkstationAccess",
                            "",
                            pszTmp,
                            MAX_BUF,
                            gsFilename);

    if (strlen(pszTmp) > 0) {
        DWORD dwPerms = 0;

        if (strstr( pszTmp, "UserRead" )) {
            dwPerms |= PACKAGE_USER_READ;
        }
        if (strstr( pszTmp, "UserWrite" )) {
            dwPerms |= PACKAGE_USER_WRITE;
        }
        if (strstr( pszTmp, "GuestRead" )) {
            dwPerms |= PACKAGE_GUEST_READ;
        }
        if (strstr( pszTmp, "GuestWrite" )) {
            dwPerms |= PACKAGE_GUEST_WRITE;
        }

        SetScalarInt( hPkgFolder, "Workstation permissions", dwPerms );
    }

    dwRet = 0;

Exit:
    delete pszTmp;
    return(dwRet);
}

//***************************************************************************
//
//  ParseProgramItemProperties
//
//  Parses out properties for program items.
//
//  Returns:
//
//      DWORD                           0 on success or a PDF_ error code.
//
//***************************************************************************
DWORD ParseProgramItemProperties( HANDLE hPkgFolder )
{
    DWORD dwRet = 0;
    char *buf = new char[MAX_BUF];      // Temporary working buffer

    SMS_STATUS stat;

    for (int i = 1; ; i++) {

        // Create a new progitem folder.
        // Do this once per iteration.
        // If all goes well it will be linked and committed at the end
        // of each iteration.
        // ===========================================================
        HANDLE hPIFolder;
        stat = SmsCreateFolder( hPkgFolder, F_PROGITEM, "", &hPIFolder );
        if (stat != SMS_OK) {
            DisplaySmsError("SmsCreateFolder (program item) failed", stat);
            return( PDF_ERROR );
        }

        char Section[128];
        sprintf(Section, "Program Item Properties %d", i);

        dwRet = GetPrivateProfileString(Section,
                                        "CommandLine",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        if (dwRet == 0) {           // When no more program item sections
            break;                  // are available.
        }

        // If here, we have started reading a program item.
        // ================================================

        SetScalarString( hPIFolder, "Command line", buf );

        //-----------------------------------------------------

        dwRet = GetPrivateProfileString(Section,
                                        "Description",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        SetScalarString( hPIFolder, "Description", buf );

        //-----------------------------------------------------

        dwRet = GetPrivateProfileString(Section,
                                        "ConfigurationScript",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        if (strlen(buf)) {
            SetScalarString( hPIFolder, "Configure script file", buf );
        }

        //-----------------------------------------------------

        dwRet = GetPrivateProfileString(Section,
                                        "RegistryName",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        SetScalarString( hPIFolder, "Registry name", buf );

        //-----------------------------------------------------

        dwRet = GetPrivateProfileString(Section,
                                        "RunMinimized",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        if (_stricmp(buf, "TRUE") == 0) {
            SetScalarInt( hPIFolder,  "Run minimized", TRUE );
        } else {
            SetScalarInt( hPIFolder,  "Run minimized", FALSE );
        }

        //-----------------------------------------------------

        dwRet = GetPrivateProfileString(Section,
                                        "RunLocalCopyIfPresent",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        if (_stricmp(buf, "TRUE") == 0) {
            SetScalarInt( hPIFolder,  "Search local flag", TRUE );
        } else {
            SetScalarInt( hPIFolder,  "Search local flag", FALSE );
        }

        //-----------------------------------------------------

        dwRet = GetPrivateProfileString(Section,
                                        "DriveMode",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        if (strstr(buf, "UNC")) {
            SetScalarInt( hPIFolder, "Drive mode", PI_DRIVEMODE_UNC );
        }

        if (strstr(buf, "ANY_DRIVE_LETTER")) {
            SetScalarInt( hPIFolder, "Drive mode", PI_DRIVEMODE_ANY_LETTER );
        }

        if (strstr(buf, "SPECIFIC_DRIVE_LETTER")) {
            // Specific drive letter.
            char *p = strstr(buf, ":");
            SetScalarInt( hPIFolder, "Drive mode", PI_DRIVEMODE_SPECIFIC_LETTER );
            SetScalarString( hPIFolder, "Drive letter", &p[-1] );
        }

        //-----------------------------------------------------

        dwRet = GetPrivateProfileString(Section,
                                        "SupportedPlatforms",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        // 'buf' contains an array of strings comma-separated.
        // Each of these strings corresponds to a platform.
        // An example would be:
        //  "Windows 3.1, Windows NT 3.1 (MIPS), ..."
        // We'll call a function to analyse these strings and set
        // the appropriate bits in a DWORD. This is the value that
        // we will store in the scalar.
        DWORD dwPlatforms = GetPlatforms( buf );
        SetScalarInt( hPIFolder, "Platforms", dwPlatforms );

        //-----------------------------------------------------

        // Read in the icon.

        dwRet = GetPrivateProfileString(Section,
                                        "SetupIcon",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        CString sFile = gsWorkingPath + buf;

        FILE *fIcon = fopen((const char *)sFile, "rb");

        if (fIcon) {
            BYTE *pIconFileImage;
            DWORD dwImageSize;

            fseek(fIcon, 0, SEEK_END);
            dwImageSize = (DWORD)ftell(fIcon);
            fseek(fIcon, 0, SEEK_SET);
            pIconFileImage = new BYTE[dwImageSize];
            DWORD dwResult = fread(pIconFileImage, sizeof(BYTE), dwImageSize, fIcon);
            if (dwResult == dwImageSize) {
                SCALAR sc;
                sc.pszName = "Icon";
                sc.scType = SCALAR_BINARY;
                sc.pValue = pIconFileImage;
                sc.dwLen = dwImageSize;
                SmsSetScalar( hPIFolder, &sc );
            } else {
                printf("Can't read icon file %s\n", (const char *)sFile);
                delete pIconFileImage;
            }
            fclose(fIcon);
        } else {
            printf("Can't access icon file %s\n", (const char *)sFile);
        }

        //-----------------------------------------------------

        dwRet = GetPrivateProfileString(Section,
                                        "DisplayIconInProgGroup",
                                        "TRUE",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        if (_stricmp(buf, "TRUE") == 0) {
            SetScalarInt( hPIFolder,  "Display Icon in Program Group", TRUE );
        } else {
            SetScalarInt( hPIFolder,  "Display Icon in Program Group", FALSE );
        }

        // Add the program item folder to the package folder.
        // ==================================================
        stat = SmsLinkFolder( hPIFolder );
        stat = SmsCommitFolder( hPIFolder );
        stat = SmsCloseFolder( hPIFolder );
    }

    delete buf;
    return 0;
}

//***************************************************************************
//
//  ParseSetupVariations
//
//
//  Returns:
//
//      DWORD                           0 on success or a PDF_ error code.
//
//***************************************************************************
DWORD ParseSetupVariations( HANDLE hPkgFolder )
{
    CStringArray asVariations;
    DWORD dwRet;
    char *buf = new char[MAX_BUF];

    dwRet = GetPrivateProfileString("Package Definition",
                                    "SetupVariations",
                                    "",
                                    buf,
                                    MAX_BUF,
                                    gsFilename);

    char *p = strtok(buf, ",");

    while (p) {
        while (*p == ' ')
            p++;  // Move past leading blanks

        asVariations.Add(p);
        p = strtok(NULL, ",");
    }

    // Now parse out the individual variations.
    // ========================================

    for (int i = 0; i < asVariations.GetSize(); i++) {

        // Create a new WCL folder.
        // Do this once per iteration.
        // If all goes well it will be linked and committed at the end
        // of each iteration.
        // ===========================================================
        HANDLE hWclFolder;
        SMS_STATUS stat;
        stat = SmsCreateFolder( hPkgFolder, F_WCL, "", &hWclFolder );
        if (stat != SMS_OK) {
            DisplaySmsError("SmsCreateFolder (wcl) failed", stat);
            return( PDF_ERROR );
        }

        CString sVariation = asVariations[i] + CString(" Setup");

        //------------------------------------------------------

        dwRet = GetPrivateProfileString(sVariation,
                                        "CommandLine",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        SetScalarString( hWclFolder, "Command", buf );

        //------------------------------------------------------

        dwRet = GetPrivateProfileString(sVariation,
                                        "CommandName",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        SetScalarString( hWclFolder, "Name", buf );

        //------------------------------------------------------
        // since wclFlags is zero initialized we only need to OR
        // it together with any bitfields needing set
        DWORD wclFlags = 0;

        dwRet = GetPrivateProfileString(sVariation,
                                        "UserInputRequired",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        if (_stricmp(buf, "TRUE") != 0) {

wclFlags |= WCL_AUTOMATED_COMMAND_LINE;
        }

        SetScalarInt( hWclFolder, "Flags", wclFlags );

        //------------------------------------------------------

        dwRet = GetPrivateProfileString(sVariation,
                                        "SupportedPlatforms",
                                        "",
                                        buf,
                                        MAX_BUF,
                                        gsFilename);

        DWORD dwPlatforms = GetPlatforms( buf );
        SetScalarInt( hWclFolder, "Platforms", dwPlatforms );

        //------------------------------------------------------

        // Add WCL to package. -- link and commit wcl folder.
        // ===================

        stat = SmsLinkFolder( hWclFolder );
        stat = SmsCommitFolder( hWclFolder );
        stat = SmsCloseFolder( hWclFolder );

    }

    delete buf;
    return 0;
}

//***************************************************************************
//
//  ParseSetupForSharing
//
//
//  Returns:
//
//      DWORD                           0 on success or a PDF_ error code.
//
//***************************************************************************
DWORD ParseSetupForSharing( HANDLE hPkgFolder )
{
    BOOL bSharePkg = FALSE;
    DWORD dwRet;

    char *buf = new char[MAX_BUF];

    //-------------------------------------------------

    dwRet = GetPrivateProfileString("Setup Package for Sharing",
                                    "ShareName",
                                    "",
                                    buf,
                                    MAX_BUF,
                                    gsFilename);

      SetScalarString( hPkgFolder, "Share name", buf );

    //-------------------------------------------------

    dwRet = GetPrivateProfileString("Setup Package for Sharing",
                                    "ShareAccess",
                                    "",
                                    buf,
                                    MAX_BUF,
                                    gsFilename);

    DWORD dwFlags = 0;

    if (strstr(buf, "UserRead")) {
        dwFlags |= PACKAGE_USER_READ;
    }
    if (strstr(buf, "UserWrite")) {
        dwFlags |= PACKAGE_USER_WRITE;
    }
    if (strstr(buf, "GuestRead")) {
        dwFlags |= PACKAGE_GUEST_READ;
    }
    if (strstr(buf, "GuestWrite")) {
        dwFlags |= PACKAGE_GUEST_WRITE;
    }

    SetScalarInt( hPkgFolder, "Share permissions", dwFlags );

    if (dwFlags) {
        bSharePkg = TRUE;
    }

    //-------------------------------------------------

    delete buf;
    return 0;
}

//***************************************************************************
//
//  FillFileTokens
//
//
//
//
//***************************************************************************
void FillFileTokens(TOKEN *pToken, char *pszBuf )
{
    char *pszBuff2 = new char[MAX_BUF];
    char *pszBuff3 = new char[MAX_BUF];

    DWORD dwRet;
    LONG offset, length, value;
    int hr, min, mm, dd, yy;
    CString sToken;
    const CString sQuote("\"");

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf, "FILE", "", pszBuff2, MAX_BUF, gsFilename);

    sToken = "FILE " + sQuote + pszBuff2 + sQuote;

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf, "SIZE", "", pszBuff2, MAX_BUF, gsFilename);

    if (dwRet) {
        sToken += CString(" SIZE ") + pszBuff2;
    }

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf,
                                    "COLLECT",
                                    "",
                                    pszBuff2,
                                    MAX_BUF,
                                    gsFilename);

    if (dwRet && (_stricmp(pszBuff2, "TRUE") == 0)) {
        sToken += CString(" COLLECT ");
    }

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf,
                                    "CHECKSUM",
                                    "",
                                    pszBuff2,
                                    MAX_BUF,
                                    gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d,%d,%d", &offset, &length, &value);
        sToken += CString(" CHECKSUM ") + pszBuff2;
    }

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf, "CRC", "", pszBuff2, MAX_BUF, gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d,%d,%d", &offset, &length, &value);
        sToken += CString(" CRC ") + pszBuff2;
    }

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf, "DATE", "", pszBuff2, MAX_BUF, gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d,%d,%d", &mm, &dd, &yy);
        sprintf( pszBuff3, "%d/%d/%d", mm, dd, yy );
        sToken += CString(" DATE ") + pszBuff3;
    }

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf, "TIME", "", pszBuff2, MAX_BUF, gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d,%d", &hr, &min);
        sprintf( pszBuff3, "%d:%d", hr, min );
        sToken += CString(" TIME ") + pszBuff3;
    }

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf, "BYTE", "", pszBuff2, MAX_BUF, gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d,%d", &offset, &value);
        sToken += CString(" BYTE ") + pszBuff2;
    }

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf, "WORD", "", pszBuff2, MAX_BUF, gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d,%d", &offset, &value);
        sToken += CString(" WORD ") + pszBuff2;
    }

    //--------------------------------------------------------------

    dwRet = GetPrivateProfileString(pszBuf, "LONG", "", pszBuff2, MAX_BUF, gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d,%d", &offset, &value);
        sToken += CString(" LONG ") + pszBuff2;
    }

    //--------------------------------------------------------------
    // String values

    dwRet = GetPrivateProfileString(pszBuf,
                                    "TOKEN 1",
                                    "",
                                    pszBuff2,
                                    MAX_BUF,
                                    gsFilename);

    if (dwRet) {
        // All this to remove the comma after the offset.
        sscanf(pszBuff2, "%d, \"%[^\"]", &offset, pszBuff3);
        sprintf( pszBuff2, "%d \"%s\"", offset, pszBuff3 );
        sToken += CString(" STRING ") + pszBuff2;
    }

    dwRet = GetPrivateProfileString(pszBuf,
                                    "TOKEN 2",
                                    "",
                                    pszBuff2,
                                    MAX_BUF,
                                    gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d, \"%[^\"]", &offset, pszBuff3);
        sprintf( pszBuff2, "%d \"%s\"", offset, pszBuff3 );
        sToken += CString(" STRING ") + pszBuff2;
    }

    dwRet = GetPrivateProfileString(pszBuf,
                                    "TOKEN 3",
                                    "",
                                    pszBuff2,
                                    MAX_BUF,
                                    gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d, \"%[^\"]", &offset, pszBuff3);
        sprintf( pszBuff2, "%d \"%s\"", offset, pszBuff3 );
        sToken += CString(" STRING ") + pszBuff2;
    }

    dwRet = GetPrivateProfileString(pszBuf,
                                    "TOKEN 4",
                                    "",
                                    pszBuff2,
                                    MAX_BUF,
                                    gsFilename);

    if (dwRet) {
        sscanf(pszBuff2, "%d, \"%[^\"]", &offset, pszBuff3);
        sprintf( pszBuff2, "%d \"%s\"", offset, pszBuff3 );
        sToken += CString(" STRING ") + pszBuff2;
    }

    //--------------------------------------------------------------

    // Store the string in the token.
    // ==============================
    strcpy( pToken->szTokenString, (const char *)sToken );

    delete pszBuff2;
    delete pszBuff3;
}

//***************************************************************************
//
//  ParseInventoryRules
//
//  Parameters:
//      HANDLE hPkgFolder               Handle to package folder.
//
//  Returns:
//
//      DWORD                           0 on success or a PDF_ error code.
//
//  NOTE: This doesn't handle nested grouping.
//
//***************************************************************************
DWORD ParseInventoryRules( HANDLE hPkgFolder )
{
    char *pszBuff1 = new char[MAX_BUF];
    char *pszBuff2 = new char[MAX_BUF];

    char rulenum[64];
    DWORD dwRet;
    INT iStart, iEnd, iNextToken;
    iNextToken = 0;

    ANDOR opAndOr = OP_AND;

    HANDLE hInvFolder;
    SMS_STATUS stat;
    stat = SmsCreateFolder( hPkgFolder, F_INVENTORY, "", &hInvFolder );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsCreateFolder (inventory) failed", stat);
        return( PDF_ERROR );
    }

    // First, we build up the expression by traversin the
    // DetectionRule parts of the [Setup Package for Inventory]
    // section.
    // ========================================================

    for (int i = 1; 1; i++) {

        sprintf(rulenum, "Detection Rule Part %d", i);

        dwRet = GetPrivateProfileString("Setup Package for Inventory",
                                        rulenum,
                                        "",
                                        pszBuff1,
                                        MAX_BUF,
                                        gsFilename);

        if (!dwRet) {
            break;
        }

        TOKEN Token;
        memset( &Token, 0, sizeof(TOKEN) );

        // If here, we have a rule token.
        // ==============================

        if (_stricmp(pszBuff1, "AND") == 0) {
            opAndOr = OP_AND;
            iNextToken++;
        }

        else if (_stricmp(pszBuff1, "OR") == 0) {
            opAndOr = OP_OR;
            iNextToken++;
        }

        else if (_stricmp(pszBuff1, ")") == 0) {
            // Save the end index and then call SmsManipulateTokens.
            // Note that we subtract 1 from NextToken. This is because
            // we want the index of the *last* token in the range, and
            // not what the next one will be.
            iEnd = iNextToken-1;
            stat = SmsManipulateTokens( hInvFolder, TOK_GROUP, iStart, iEnd );
            if (stat != SMS_OK) {
                DisplaySmsError("SmsManipulateToken fails", stat);
            }
        }

        else if (_stricmp(pszBuff1, "(") == 0) {
            // Start of a group.
            // We will need to know and save the start and end indecies.
            // These we then pass through to SmsManipulateTokens.
            // This means that we need to keep track of the token indecies
            // all the way through.
            iStart = iNextToken;
        }

        else {
            // This is the real part of the token.
            FillFileTokens(&Token, pszBuff1);
            stat = SmsAddToken( hInvFolder, opAndOr, &Token, AT_END );
            if (stat != SMS_OK) {
                DisplaySmsError("SmsAddToken fails", stat);
            }
            iNextToken++;
        }

#ifdef DEBUG
    // Retrieve and print out tokens - with their indecies.
    DWORD ctTknsX=0;
    stat = SmsGetTokenCount( hInvFolder, &ctTknsX );
    printf("Tokens so far: %d Next token: %d\n", ctTknsX, iNextToken);
    for (int i = 0; i < (int)ctTknsX; i++) {
        stat = SmsGetToken( hInvFolder, i, &Token );
        printf(" [%d] <%s>\n", i, Token.szTokenString);
    }
    printf("=======\n");
#endif  // DEBUG

    }

    // Set 'Inventory this package' scalar if appropriate.
    // We only want to do this if there were indeed any inventory
    // rules specified. We check this by looking to see if the
    // filter contains tokens.
    // ==========================================================
    DWORD ctTokens=0;
    stat = SmsGetTokenCount( hInvFolder, &ctTokens );

    if (ctTokens > 0) {
        // Grab this stuff from the section dealing with package folder.
        // We deal with it here in the inventory folder.
        // -------------------------------------------------------------
        dwRet = GetPrivateProfileString("Setup Package for Inventory",
                                        "InventoryThisPackage",
                                        "",
                                        pszBuff1,
                                        MAX_BUF,
                                        gsFilename);

        DWORD dwFlags;
        if (_stricmp(pszBuff1, "TRUE") == 0) {
            dwFlags = 1;
        } else {
            dwFlags = 0;
        }
        SetScalarInt( hInvFolder, "Inventory this package", dwFlags );
    }

    // Cleanup.
    // ========

    delete pszBuff1;
    delete pszBuff2;

    // Only link and commit if there were any inventory rules.
    // =======================================================
    if (ctTokens > 0) {
        stat = SmsLinkFolder( hInvFolder );
        stat = SmsCommitFolder( hInvFolder );
    }
    stat = SmsCloseFolder( hInvFolder );

    return(0);
}

// Ancilliary routines

//***************************************************************************
//
//
//
//***************************************************************************

BOOL FileExists(const char *pszPath)
{
    BOOL bRet;
    CFileStatus status;

    if (CFile::GetStatus( pszPath, status )) {
        bRet = TRUE;
    } else {
        bRet = FALSE;
    }

    return(bRet);
}

//*************************************************************************
//
//  We are given a string buffer which contains a series of comma-delimited
//  platform names.
//  We analyse this string breaking it down into the individual platforms.
//  Each (valid) platform will then cause a corresponding bit-pattern to be
//  ORed into the resulting DWORD that we return.
//
//*************************************************************************

// Build up an array with platform names, for each name also have
// its corresponding bit value.
class CPlatform : public CObject
{
public:
    CPlatform() {};
    CPlatform( CString sPlatform, DWORD bPlatform ) {
        this->sPlatform = sPlatform;
        this->bPlatform = bPlatform;
    }
    ~CPlatform() {};
    CString sPlatform;          // String name of platform (eg "Macintosh").
    DWORD bPlatform;            // Bit value used in platform scalar.
};

CObArray aPlatforms;

// Setup the array.
void InitPlatforms()
{
    CPlatform *pPlat;

    pPlat = new CPlatform( "Macintosh",   PLTFRM_MACINTOSH );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "MS-DOS 5.0",  PLTFRM_DOS5 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "MS-DOS 6.0",  PLTFRM_DOS6 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "MS-DOS 6.2",  PLTFRM_DOS6 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "MS-DOS 6.21", PLTFRM_DOS6 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "MS-DOS 6.22", PLTFRM_DOS6 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows 3.1", PLTFRM_WIN16 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 3.1 (Alpha)", PLTFRM_WIN32_ALPHA );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 3.1 (MIPS)",  PLTFRM_WIN32_MIPS );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 3.1 (x86)",   PLTFRM_WIN32_X86 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 3.5 (Alpha)", PLTFRM_WIN32_ALPHA );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 3.5 (MIPS)",  PLTFRM_WIN32_MIPS );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 3.5 (x86)",   PLTFRM_WIN32_X86 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 3.51 (Alpha)",PLTFRM_WIN32_ALPHA );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 3.51 (MIPS)", PLTFRM_WIN32_MIPS );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 3.51 (x86)",  PLTFRM_WIN32_X86 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 4.0 (Alpha)", PLTFRM_WIN32_ALPHA );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 4.0 (MIPS)",  PLTFRM_WIN32_MIPS );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows NT 4.0 (x86)",   PLTFRM_WIN32_X86 );
    aPlatforms.Add( pPlat );

    pPlat = new CPlatform( "Windows95", PLTFRM_WIN95 );
    aPlatforms.Add( pPlat );
}

DWORD GetPlatforms( char *buf )
{
    DWORD dwPlatforms = 0;
    CPlatform *pPlatform;
    CString sBuff = buf;            // Easier to work with.

    // We have an array of platform names. Check each one of
    // these against our string setting the appropriate value
    // for each platform that we find.
    // ======================================================
    for (int iLoop = 0; iLoop < aPlatforms.GetSize(); iLoop++) {
        pPlatform = (CPlatform *)aPlatforms[iLoop];
        if (sBuff.Find( pPlatform->sPlatform ) != -1) {
            // Found a match. Get the bit value.
            dwPlatforms |= pPlatform->bPlatform;
        }
    }

    return(dwPlatforms);
}

// ====================================================================
//
// ConnectToDatasource
//
// Get the datasource connection information from the user and use it
// to connect to the datasource.
//
// Parameters:  None.
//
// Returns:
//      The connection handle or NULL if the connection failed.
//
// ====================================================================
HANDLE ConnectToDatasource()
{
    // Get the information we need to connect to the
    // data source from the user.
    //==============================================
    char szServer[CCH_MAXINPUT];
    char szUser[CCH_MAXINPUT];
    char szPasswd[CCH_MAXINPUT];
    char szDatabase[CCH_MAXINPUT];

    printf("\n");
    printf("**************************\n");
    printf("* Connect to data source *\n");
    printf("**************************\n");
    InputString("SQL server name", szServer);
    InputString("SQL database name", szDatabase);
    InputString("User name for SQL server", szUser);
    InputString("Password for SQL server", szPasswd);
    printf("\n");

    // Connect to a data source. SQL in this case.
    // ===========================================
    DATASOURCE dsParams;

    dsParams.sqlParams.ds          = DB_SQL;
    dsParams.sqlParams.pszServer   = szServer;
    dsParams.sqlParams.pszUserName = szUser;
    dsParams.sqlParams.pszPasswd   = szPasswd;
    dsParams.sqlParams.pszDbName   = szDatabase;
    dsParams.sqlParams.pFunc       = NULL;         // No encryption.
    dsParams.sqlParams.pszKey      = "";

    HANDLE hConnect;
    SMS_STATUS stat;
    stat = SmsDataSourceConnect( &dsParams, &hConnect);

    if (stat != SMS_OK) {
        hConnect = NULL;
        printf("Connect to data source failed: %d\n", stat);
    }

    return( hConnect );
}

// ====================================================================
//
// DidRequestHelp
//
// Check the program's arguments to see if the user asked for
// the help screen to be displayed.
//
// Parameters:
//      int argc
//          The argc value from main(argc, argv)
//
//      char **argv
//          The argv value from main(argc, argv)
//
// Returns:
//      TRUE if command line parameters included a request
//      for help to be displayed.
//
// ====================================================================
BOOL DidRequestHelp( int argc, char **argv )
{
    if (argc == 2  && (strcmp(argv[1], "-help") == 0)) {
        return(TRUE);
    }
    else {
        return(FALSE);
    }

}

// ====================================================================
//
// DisplayHelp
//
// This function displays the samples help screen.
//
// Parameters:
//      None
//
// Returns:
//      Nothing.
//
// ====================================================================
void DisplayHelp()
{
    printf("\n\n");
    printf("***********************************************************\n");
    printf("* pdf.exe:                                                *\n");
    printf("*                                                         *\n");
    printf("* SMS API Example: Import a PDF file into an SMS database.*\n");
    printf("*                                                         *\n");
    printf("* This sample shows hows the SMS APIs can be used to      *\n");
    printf("* import a pdf file.                                      *\n");
    printf("*                                                         *\n");
    printf("* The user will be asked to provide the SMS database      *\n");
    printf("* details, and then the name of a pdf file.               *\n");
    printf("*                                                         *\n");
    printf("*                                                         *\n");
    printf("*                                                         *\n");
    printf("* Syntax:                                                 *\n");
    printf("*     pdf.exe [-help] file.pdf                            *\n");
    printf("*                                                         *\n");
    printf("* Switches:                                               *\n");
    printf("*     -help       Display this help message.              *\n");
    printf("*                                                         *\n");
    printf("***********************************************************\n");
    printf("\n");
}

// ====================================================================
//
// DisplayGreeting
//
// Display the initial greeting banner.
//
// Parameters:
//     None.
//
// Returns:
//     Nothing.
//
// ====================================================================
void DisplayGreeting()
{
    // For this sample, the greeting is identical to the help screen.
    //===============================================================
    DisplayHelp();
}

// ====================================================================
//
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char *pszMessage
//          The user prompt to display.
//
//      char *pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//
// ====================================================================
void InputString( const char *pszMessage, char *pszResult )
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

#ifdef XXX
//**************************************************************************
// ViewFolder
//
//  Display the folder contents.  This version only displays the folder name
//  and type, number of scalars, and the scalars. Other information not
//  relevant.
//
//  NOTE: no error checking.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder to view.
//
// Returns: Nothing.
//
//**************************************************************************
void ViewFolder( HANDLE hFolder )
{
    char szFolderID[SMS_DATA_BUFF_SIZE + 1];        // This folder's ID.
    char szfType[SMS_DATA_BUFF_SIZE + 1];           // Folder's tag.
    DWORD fType;                        // Folder's type.
    DWORD ctScalars;                    // How many scalars in this folder.
    SMS_STATUS stat;

    // Get folder ID and type.
    // -----------------------
    stat = SmsGetFolderID( hFolder, szFolderID );
    if (stat == SMS_NEW_FOLDER) {
        strcpy( szFolderID, "New folder without an ID yet" );

    }
    else if (stat != SMS_OK) {
        DisplaySmsError("ViewFolder: can't get folder ID", stat);
        strcpy( szFolderID, "Couldn't get the folder ID" );
    }

    printf("============== '%s' ===========\n", szFolderID);

    stat = SmsGetFolderType( hFolder, &fType, szfType );
    if (stat != SMS_OK) {
        DisplaySmsError("ViewFolder: can't get folder type", stat);
    }

    printf("SCALARS FOR FOLDER '%s'\n", szFolderID);

    // Get count of scalars.
    // ---------------------
    stat = SmsGetScalarCount( hFolder, &ctScalars );
    if (stat != SMS_OK) {
        DisplaySmsError("ViewFolder: can't get scalar count", stat);
    }
    printf("'%s' (%s) contains %d scalars\n", szFolderID, szfType, ctScalars);

    // Display the scalars.
    // --------------------
    DisplayScalars( hFolder );

    // Get the folder count
    DWORD ctFolders;
    stat = SmsGetFolderCount(hFolder, F_ANY, &ctFolders);
    printf("'%s' (%s) contains %d sub-folders\n", szFolderID, szfType, ctFolders);
    for (DWORD dwFolder = 0; dwFolder < ctFolders; ++dwFolder) {
        printf("Getting subfolder\n");
        HANDLE hSubFolder;
        stat = SmsGetNextFolder(hFolder, F_ANY, &hSubFolder);
        if (stat != SMS_OK) {
            DisplaySmsError("SmsGetNextFolder failed", stat);
            break;
        }

        ViewFolder(hSubFolder);

        // Close the sub-folder to avoid a memory leak.
        //============================================
        stat = SmsCloseFolder(hSubFolder);
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to close an open folder", stat);
            break;
        }
    }

    SmsRewind(hFolder, RW_FOLDER | RW_SCALAR);

}  /* ViewFolder */

//*************************************************************************
// DisplayScalars
//
// Display a folder's scalars.
//
// Parameters:
//      HANDLE hfolder
//          This is the folder handle.
//
// Returns: Nothing.
//
// ====================================================================
void DisplayScalars( HANDLE hFolder )
{
    SMS_STATUS stat = SMS_OK;
    SCALAR scalar;
    char *pszTime;          // For conversion of time scalars.

    // The scalars do not have their own string value buffer, so
    // we allocate the buffers here and set pointers to them in the
    // scalar structure.
    //=============================================================
    char szName[SMS_DATA_BUFF_SIZE+1];
    char szValue[SMS_DATA_BUFF_SIZE+1];
    scalar.pszName  = szName;
    scalar.pszValue = szValue;
    scalar.pValue = NULL;

    while (1) {

        scalar.dwLen = sizeof(szValue)-1;       // must tell him the size

        stat = SmsGetNextScalar( hFolder, &scalar);
        if (stat == SMS_NO_MORE_DATA) {
            break;
        }

        if (stat != SMS_OK && stat != SMS_MORE_DATA) {
            DisplaySmsError("SmsGetNextScalar failed in method DisplayScalars", stat);
            break;
        }

        if (stat == SMS_MORE_DATA) {

            printf("Receive buffer too small, should be %d. Data truncated\n",
                            scalar.dwLen);
        }

        // Check scalar type, display accordingly.
        // ---------------------------------------
        switch (scalar.scType) {
        case SCALAR_STRING:
            printf("\t%30s: %s\n", scalar.pszName, scalar.pszValue);
            break;

        case SCALAR_INT:
            printf("\t%30s: %ld\n", scalar.pszName, scalar.dwValue);
            break;

        case SCALAR_TIME:
            // Check if we have a string equivalence. If so use it.
            if (scalar.bStringEquivalence) {
                printf("\t%30s: %s\n", scalar.pszName, scalar.pszValue);

            } else {
                pszTime = ctime( &scalar.tValue );
                printf("\t%30s: %s", scalar.pszName, pszTime);
            }
            break;

        case SCALAR_BINARY:
            // In this sample we won't display the binary data.
            // Just tell the user its size.
            printf("\t%30s: (Binary - this is its size) %ld\n", scalar.pszName, scalar.dwLen);
            break;
        }
    }

    // Why did we exit (other than no more scalars)?
    //==============================================
    if (stat != SMS_NO_MORE_DATA) {
        printf("Bad return from Scalar access: %d\n", stat);
    }

    printf("\n");
}
#endif  // XXX

//**************************************************************************
// SetScalarString
//
// This function sets the value specified scalar to the given string
// value.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//      char *pszName
//          The name of the scalar.
//      const char *pszValue
//          The string that will be assigned to the scalar's value.
//
// Returns:
//      Nothing.
//
// Note that the scalar specified must be of type SCALAR_STRING.
//
//***************************************************************************
void SetScalarString(HANDLE hFolder, char* pszName, const char *pszValue)
{
    SCALAR sc;
    sc.pszName  = pszName;
    sc.scType   = SCALAR_STRING;
    sc.pszValue = (char *)pszValue;

    SMS_STATUS stat = SmsSetScalar(hFolder, &sc);

    if (stat != SMS_OK) {
        CString sErr = "SmsSetScalar ('";
        sErr += pszName;
        sErr += "') failed";
        DisplaySmsError( (const char *)sErr, stat);
    }

}

//**************************************************************************
// SetScalarInt
//
// This function sets the value of the specified scalar to
// the given DWORD value.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//      char* pszName
//          The name of the scalar.
//      DWORD dwValue
//          The DWORD value to set the scalar to.
//
// Returns:
//      Nothing.
//
// Note that the scalar specified must be of type SCALAR_INT.
//
//***************************************************************************
void SetScalarInt(HANDLE hFolder, char *pszName, DWORD dwValue)
{
    SCALAR sc;
    sc.pszName = pszName;
    sc.scType = SCALAR_INT;
    sc.dwValue = dwValue;

    SMS_STATUS stat = SmsSetScalar(hFolder, &sc);

    if (stat != SMS_OK) {
        CString sErr = "SmsSetScalar ('";
        sErr += pszName;
        sErr += "') failed";
        DisplaySmsError( (const char *)sErr, stat);
    }
}

//**************************************************************************
// ViewFolder
//
//  Display the folder contents.  This version only displays the folder name
//  and type, number of scalars, and the scalars. Other information not
//  relevant.
//
//  NOTE: no error checking.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder to view.
//
// Returns: Nothing.
//
//**************************************************************************
void ViewFolder( HANDLE hFolder )
{
    char szFolderID[SMS_DATA_BUFF_SIZE + 1];        // This folder's ID.
    char szfType[SMS_DATA_BUFF_SIZE + 1];           // Folder's tag.
    DWORD fType;                        // Folder's type.
    DWORD ctScalars;                    // How many scalars in this folder.
    SMS_STATUS stat;

    // Get folder ID and type.
    // -----------------------
    stat = SmsGetFolderID( hFolder, szFolderID );
    if (stat == SMS_NEW_FOLDER) {
        strcpy( szFolderID, "New folder without an ID yet" );

    }
    else if (stat != SMS_OK) {
        DisplaySmsError("ViewFolder: can't get folder ID", stat);
        strcpy( szFolderID, "Couldn't get the folder ID" );
    }

    printf("============== '%s' ===========\n", szFolderID);

    stat = SmsGetFolderType( hFolder, &fType, szfType );
    if (stat != SMS_OK) {
        DisplaySmsError("ViewFolder: can't get folder type", stat);
    }

    printf("SCALARS FOR FOLDER '%s'\n", szFolderID);

    // Get count of scalars.
    // ---------------------
    stat = SmsGetScalarCount( hFolder, &ctScalars );
    if (stat != SMS_OK) {
        DisplaySmsError("ViewFolder: can't get scalar count", stat);
    }
    printf("'%s' (%s) contains %d scalars\n", szFolderID, szfType, ctScalars);

    // Display the scalars.
    // --------------------
    DisplayScalars( hFolder );

    // Get the folder count
    DWORD ctFolders;
    stat = SmsGetFolderCount(hFolder, F_ANY, &ctFolders);
    printf("'%s' (%s) contains %d sub-folders\n", szFolderID, szfType, ctFolders);
    for (DWORD dwFolder = 0; dwFolder < ctFolders; ++dwFolder) {
        printf("Getting subfolder\n");
        HANDLE hSubFolder;
        stat = SmsGetNextFolder(hFolder, F_ANY, &hSubFolder);
        if (stat != SMS_OK) {
            DisplaySmsError("SmsGetNextFolder failed", stat);
            break;
        }

        ViewFolder(hSubFolder);

        // Close the sub-folder to avoid a memory leak.
        //============================================
        stat = SmsCloseFolder(hSubFolder);
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to close an open folder", stat);
            break;
        }
    }

    SmsRewind(hFolder, RW_FOLDER | RW_SCALAR);

}  /* ViewFolder */

//*************************************************************************
// DisplayScalars
//
// Display a folder's scalars.
//
// Parameters:
//      HANDLE hfolder
//          This is the folder handle.
//
// Returns: Nothing.
//
// ====================================================================
void DisplayScalars( HANDLE hFolder )
{
    SMS_STATUS stat = SMS_OK;
    SCALAR scalar;
    char *pszTime;          // For conversion of time scalars.

    // The scalars do not have their own string value buffer, so
    // we allocate the buffers here and set pointers to them in the
    // scalar structure.
    //=============================================================
    char szName[SMS_DATA_BUFF_SIZE+1];
    char szValue[SMS_DATA_BUFF_SIZE+1];
    scalar.pszName  = szName;
    scalar.pszValue = szValue;
    scalar.pValue = NULL;

    while (1) {

        scalar.dwLen = sizeof(szValue)-1;       // must tell him the size

        stat = SmsGetNextScalar( hFolder, &scalar);
        if (stat == SMS_NO_MORE_DATA) {
            break;
        }

        if (stat != SMS_OK && stat != SMS_MORE_DATA) {
            DisplaySmsError("SmsGetNextScalar failed in method DisplayScalars", stat);
            break;
        }

        if (stat == SMS_MORE_DATA) {

            printf("Receive buffer too small, should be %d. Data truncated\n",
                            scalar.dwLen);
        }

        // Check scalar type, display accordingly.
        // ---------------------------------------
        switch (scalar.scType) {
        case SCALAR_STRING:
            printf("\t%30s: %s\n", scalar.pszName, scalar.pszValue);
            break;

        case SCALAR_INT:
            printf("\t%30s: %ld\n", scalar.pszName, scalar.dwValue);
            break;

        case SCALAR_TIME:
            // Check if we have a string equivalence. If so use it.
            if (scalar.bStringEquivalence) {
                printf("\t%30s: %s\n", scalar.pszName, scalar.pszValue);

            } else {
                pszTime = ctime( &scalar.tValue );
                printf("\t%30s: %s", scalar.pszName, pszTime);
            }
            break;

        case SCALAR_BINARY:
            // In this sample we won't display the binary data.
            // Just tell the user its size.
            printf("\t%30s: (Binary - this is its size) %ld\n", scalar.pszName, scalar.dwLen);
            break;
        }
    }

    // Why did we exit (other than no more scalars)?
    //==============================================
    if (stat != SMS_NO_MORE_DATA) {
        printf("Bad return from Scalar access: %d\n", stat);
    }

    printf("\n");
}

/* EOF: pdf.cpp */


RESDLL.C

#include <windows.h>

// **************************************************************************-
//
// Function: DllMain(HANDLE, DWORD, LPVOID)
//
// **************************************************************************-
//
// Description:
//
// This is the main initialization routine called by the NT kernel when
// a new thread requests use of the DLL.  It's also called when a thread
// stops using the DLL.  A parameter passed to the function identifies the
// reason for calling.
//
// **************************************************************************-

BOOL APIENTRY DllMain(HANDLE hDLL, DWORD dwReason, LPVOID lpReserved)
{
    // Nothing to initialize.
    return TRUE;
}


README.TXT

DESCRIPTION:

    The srvinst sample program illustrates how to create a server install
    job and monitor its status until the job completes.  This sample also
    illustrates how to create a run command on workstation job.

USAGE:

    When the srvinst program is run, it displays help text that
    describes how to use the program.

FILES PROVIDED:

    readme.txt
    makefile
    smsdefs.h
    srvinst.h
    status.cpp

BUILDING THE SAMPLE:

    To build this sample program type 'nmake'.

SMS API CALLS USED:

    SmsDataSourceConnect
    SmsDataSourceDisconnect
    SmsOpenContainer
    SmsCloseContainer
    SmsCreateFolder
    SmsLinkFolder
    SmsCommitFolder
    SmsCloseFolder
    SmsGetNextFolder
    SmsSetScalar
    SmsGetScalarByName


SRVINST.CPP

//**************************************************************************
// Copyright (C) 1995 by Microsoft Corporation.
//
// This is a sample program for the SMS SDK.  It illustrates how to create
// a server install job and monitor its status until the job completes.
//
// This sample program also illustrates how to create a run command on
// workstation job.
//
// The program will request the following input:
//
// Share package on workstation job:
//    Package name    = User defined name of the package
//    Package comment = User defined text
//    Package location (UNC path) = The pathname to the directory where the
//                                  package files are located.
//    Package share name = The name that the package will be shared as on the
//                         server.
//    Program item description = User defined text
//    Program item command line = The command to execute on the server.
//    Program item registry name = User defined registry key for the package
//
// Run command on workstation job.
//    Package name = User defined name of the package
//    Package comment = User defined text
//    Package location (share name) = The pathname to the directory where the
//                                    client package is located.
//    WCL Name = User defined name for the workstation command line
//    WCL Command = The command line to execute.
//
// NOTE: If you choose to create a share package on server job, then you must
//       first share the package directory.  If you choose to create a run
//       command on workstation job, then you must first share the directory
//       where the package files are located.
//
//*****************************************************************************

#include <afx.h>
#include <smsapi.h>
#include <smsinfo.h>

// Include the GetStatusName function.
// -------------------------------------
#include "..\common\status.inc"

// ====================================================================
//      Defines.
// ====================================================================

#define CCH_MAXSTRING   256
#define CCH_MAXINPUT    256
#define C_MAXSITES      256

#define SHARE_PERMISSIONS (PACKAGE_USER_READ | PACKAGE_USER_WRITE | PACKAGE_GUEST_READ)

extern const char *GetStatusName( SMS_STATUS stat );

enum {CMD_RUN_COMMAND, CMD_SHARE_PACKAGE, CMD_QUIT};

// ====================================================================
// TClientParams
//
// This typedef defines the parameters for the client setup
// job that are collected from the user.
// ====================================================================
typedef struct
{
    char szPackageName[CCH_MAXSTRING];
    char szPackageComment[CCH_MAXSTRING];
    char szPackagePath[CCH_MAXSTRING];
    char szWclName[CCH_MAXSTRING];
    char szWclCommand[CCH_MAXSTRING];
    char szSiteName[CCH_MAXSTRING];
    DWORD dwPlatforms;
} TClientParams;

// ====================================================================
// TServerParams
//
// This typedef defines the parameters for the share package on
// workstation job that are collected from the user.
// ====================================================================
typedef struct
{
    char szPackageName[CCH_MAXSTRING];
    char szPackageComment[CCH_MAXSTRING];
    char szPackagePath[CCH_MAXSTRING];
    char szShareName[CCH_MAXSTRING];
    char szDescription[CCH_MAXSTRING];
    char szCmdLine[CCH_MAXSTRING];
    char szRegistryName[CCH_MAXSTRING];
    char szSiteName[CCH_MAXSTRING];
    DWORD dwPlatforms;

} TServerParams;

// ====================================================================
//
//  Local prototypes.
//
// ====================================================================

void DisplayHelp();

void DisplayGreeting();

BOOL DidRequestHelp( int argc, char** argv );

void DisplaySmsError( const char *pszMessage, SMS_STATUS stat );

const char *GetStatusName( SMS_STATUS stat );

BOOL DidRequestHelp( int argc, char** argv );

HANDLE ConnectToDatasource();

void InputString( const char *pszMessage, char *pszResult );

void GetSiteName( HANDLE hConnect, char *pszSiteName );

void ClientDoRunCommand( HANDLE hConnect, TClientParams *pcp );

DWORD GetPlatformSelection();

void ServerDoShare( HANDLE hConnect, TServerParams *psp );

int GetJobType();

SMS_STATUS ClientJobCreate( HANDLE hConnect,
                            TClientParams *pcp,
                            char *pszPackageID,
                            char *pszWCLID );

// ====================================================================
//
//  The work starts here.
//
// ====================================================================
void main( int argc, char **argv )
{

    // Check to see if this is a request to display the help
    // screen.  If so, display it. Otherwise, display a greeting
    // banner.
    //=========================================================
    if (DidRequestHelp(argc, argv)) {
        DisplayHelp();
        return;
    }
    else {
        DisplayGreeting();
    }

    HANDLE hConnect = ConnectToDatasource();
    if (hConnect == NULL) {
        return;
    }

    char szSiteName[CCH_MAXSTRING];
    GetSiteName(hConnect, szSiteName);

    DWORD dwPlatforms = 0;
    while (dwPlatforms == 0) {
        dwPlatforms = GetPlatformSelection();
        if (dwPlatforms == 0) {
            printf("Error: Invalid platform selection\n");
        }
    }

    switch(GetJobType()) {
    case CMD_SHARE_PACKAGE:

        printf("***************************************\n");
        printf("* Share package on server job.        *\n");
        printf("***************************************\n");
        TServerParams sp;
        strcpy(sp.szSiteName, szSiteName);
        sp.dwPlatforms = dwPlatforms;
        InputString("Package name", sp.szPackageName);
        InputString("Package comment", sp.szPackageComment);
        InputString("Package directory location (UNC path)", sp.szPackagePath);
        InputString("Package share name", sp.szShareName);
        InputString("Program item description", sp.szDescription);
        InputString("Program item command line", sp.szCmdLine);
        InputString("Program item registry name", sp.szRegistryName);
        printf("\n");

        // First share the package on the server
        //=======================================
        ServerDoShare(hConnect, &sp);

        printf("**************************************************\n");
        printf("* The server install job has completed.  To      *\n");
        printf("* use the shared package please create a program *\n");
        printf("* group in the SMS administration program and add*\n");
        printf("* the package that was just shared to the group. *\n");
        printf("*                                                *\n");
        printf("* For more information, please refer to chapter  *\n");
        printf("* nine of the SMS Administrator's guide.         *\n");
        printf("**************************************************\n");
        printf("\n");
        break;

    case CMD_RUN_COMMAND:

        printf("************************************************\n");
        printf("* Run command on workstation job.              *\n");
        printf("************************************************\n");
        TClientParams cp;
        strcpy(cp.szSiteName, szSiteName);
        cp.dwPlatforms = dwPlatforms;
        InputString("Package name", cp.szPackageName);
        InputString("Package directory location (UNC path)", cp.szPackagePath);
        InputString("Package comment", cp.szPackageComment);
        InputString("WCL Name", cp.szWclName);
        InputString("WCL Command", cp.szWclCommand);
        printf("\n");

        // Now that we've shared the package on the server,
        // submit a job to setup the client program.
        //=================================================
        ClientDoRunCommand(hConnect, &cp);
        break;
    default:
        printf("Invalid job type. No job submitted.\n");
        break;
    }

    if (hConnect != NULL) {
        SmsDataSourceDisconnect( hConnect );
    }
}

// ====================================================================
//
// SetScalarString
//
// This function sets the value specified scalar to the given string
// value.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//      char *pszName
//          The name of the scalar.
//      char *pszValue
//          The string that will be assigned to the scalar's value.
//
// Returns:
//      SMS_STATUS
//          The SMS status code.
//
// Note that the scalar specified must be of type SCALAR_STRING.
//
// ====================================================================
SMS_STATUS SetScalarString( HANDLE hFolder, char *pszName, char *pszValue )
{
    SCALAR sc;
    sc.pszName = pszName;
    sc.scType = SCALAR_STRING;
    sc.pszValue = pszValue;

    SMS_STATUS stat;
    stat = SmsSetScalar(hFolder, &sc);
    return stat;
}

// ====================================================================
//
// SetScalarBool
//
// This function sets the value specified scalar to the given boolean value
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//      char *pszName
//          The name of the scalar.
//      BOOL bIsTrue
//          The boolean value to set the scalar to.
//
// Returns:
//      SMS_STATUS
//          The SMS status code.
//
// Note that the scalar specified must be of type SCALAR_INT.
//
// ====================================================================
SMS_STATUS SetScalarBool( HANDLE hFolder, char *pszName, BOOL bIsTrue )
{
    SCALAR sc;
    sc.pszName = pszName;
    sc.scType = SCALAR_INT;
    sc.dwValue = bIsTrue ? 1 : 0;

    SMS_STATUS stat;
    stat = SmsSetScalar(hFolder, &sc);
    return stat;
}

// ====================================================================
//
// SetScalarDword
//
// This function sets the value specified scalar to the given DWORD value.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//      char *pszName
//          The name of the scalar.
//      DWORD dwValue
//          The DWORD value to set the scalar to.
//
// Returns:
//      SMS_STATUS
//          The SMS status code.
//
// Note that the scalar specified must be of type SCALAR_INT.
//
// ====================================================================
SMS_STATUS SetScalarDword( HANDLE hFolder, char *pszName, DWORD dwValue )
{
    SCALAR sc;
    sc.pszName = pszName;
    sc.scType = SCALAR_INT;
    sc.dwValue = dwValue;

    SMS_STATUS stat;
    stat = SmsSetScalar(hFolder, &sc);
    return stat;
}

// ====================================================================
//
// SetScalarTime
//
// This function sets the value specified scalar to the given time
// value.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//      char *pszName
//          The name of the scalar.
//      time_t tValue
//          The time value to assign to the scalar.
//
// Returns:
//      SMS_STATUS
//          The SMS status code.
//
// Note that the scalar specified must be of type SCALAR_TIME.
//
// ====================================================================
SMS_STATUS SetScalarTime( HANDLE hFolder, char *pszName, time_t tValue )
{
    SCALAR sc;
    sc.pszName = pszName;
    sc.scType = SCALAR_TIME;
    sc.tValue = tValue;

    SMS_STATUS stat;
    stat = SmsSetScalar(hFolder, &sc);
    return stat;
}

// ====================================================================
//
// GetScalarString
//
// This function gets the string value of the specified scalar.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//      char *pszName
//          The name of the scalar.
//      char *pchBuffer
//          Pointer to the buffer to store the scalar's string value.
//      DWORD cchBuffer
//          The size of the buffer.
//
// Returns:
//      SMS_STATUS
//          The SMS status code.
//
// Note that the scalar specified must be of type SCALAR_STRING.
//
// ====================================================================
SMS_STATUS GetScalarString( HANDLE hFolder,
                            char *pszName,
                            char *pchBuffer,
                            DWORD cchBuffer )
{
    SCALAR sc;

    SMS_STATUS stat;
    *pchBuffer = '\0';

    char szNameBuffer[CCH_MAXSTRING];
    sc.pszName = szNameBuffer;
    sc.pszValue = pchBuffer;
    sc.dwLen = cchBuffer;
    stat = SmsGetScalarByName(hFolder, pszName, &sc);

    if (stat == SMS_OK) {
        if (sc.scType == SCALAR_STRING) {
            strcpy(pchBuffer, sc.pszValue);
        }
        else {
            DisplaySmsError("GetScalarString: scalar is not SCALAR_STRING", SMS_OK);
            stat = SMS_ERROR;
        }
    }
    else {
        DisplaySmsError("GetScalarString: SmsGetScalarByName failed", stat);
    }
    return(stat);
}

// ====================================================================
//
// GetScalarDword
//
// This function gets the DWORD value of the specified scalar.
//
// Parameters:
//      HANDLE hFolder
//          The handle of the folder containing the scalar to modify.
//
//      char *pszName
//          The name of the scalar.
//
//      DWORD *pdwValue
//          Pointer to the place to store the scalar's DWORD value.
//
// Returns:
//      SMS_STATUS
//          The SMS status code.
//
// Note that the scalar specified must be of type SCALAR_INT.
//
// ====================================================================
SMS_STATUS GetScalarDword( HANDLE hFolder, char *pszName, DWORD *pdwValue )
{
    SCALAR sc;

    SMS_STATUS stat;
    char szNameBuffer[CCH_MAXSTRING];
    sc.pszName = szNameBuffer;
    stat = SmsGetScalarByName(hFolder, pszName, &sc);

    if (stat == SMS_OK) {
        if (sc.scType == SCALAR_INT) {
            *pdwValue = sc.dwValue;
        }
        else {
            DisplaySmsError("GetScalarDword: Scalar is not SCALAR_INT", SMS_OK);
            stat = SMS_ERROR;
        }
    }
    else {
        DisplaySmsError("GetScalarDword: SmsGetScalarByName failed", stat);
    }
    return(stat);
}

// ====================================================================
//
// FindFolder
//
// This function searches a container for a folder with a scalar that
// matches the given scalar name and string scalar value.
//
// Parameters:
//      HANDLE hContainer
//          The handle of the containter to search.
//
//      char *pszName
//          The name of the scalar.
//
//      char *pszScalarValue
//          The "match" value to test for.
//
//      HFOLDER* phFolder
//          Pointer to the place to store the folder handle if a folder
//          containing the specified scalar value is found.
//
//
// Returns;
//      SMS_OK if the folder is found, SMS_NOT_FOUND otherwise.
//      The folder handle is returned via a pointer.
//
// ====================================================================
SMS_STATUS FindFolder( HANDLE hContainer,
                       DWORD fType,
                       char *pszScalarName,
                       char *pszScalarValue,
                       HANDLE *phFolder )
{
    *phFolder = NULL;

    while(TRUE) {
        SMS_STATUS stat;
        HANDLE hFolder;
        char szValue[CCH_MAXSTRING];

        stat = SmsGetNextFolder(hContainer, fType, &hFolder);
        if (stat == SMS_NO_MORE_DATA) {
            return(SMS_NOT_FOUND);
        }

        if (stat != SMS_OK) {
            DisplaySmsError("SmsGetNextFolder error", stat);
            return(SMS_NOT_FOUND);
        }

        GetScalarString(hFolder, pszScalarName, szValue, sizeof(szValue));
        if (!strcmp(szValue, pszScalarValue)) {
            *phFolder = hFolder;
            return(SMS_OK);
        }

        SmsCloseFolder(hFolder);
    }
}

// ====================================================================
//
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char *pszMessage
//          The user prompt to display.
//
//      char *pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//
// ====================================================================
void InputString( const char *pszMessage, char *pszResult )
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

// ====================================================================
//
// ConnectToDatasource
//
// Get the datasource connection information from the user and use it
// to connect to the datasource.
//
// Parameters:  None.
//
// Returns:
//      The connection handle or NULL if the connection failed.
//
// ====================================================================
HANDLE ConnectToDatasource()
{
    // Get the information we need to connect to the
    // data source from the user.
    //==============================================
    char szServer[CCH_MAXINPUT];
    char szUser[CCH_MAXINPUT];
    char szPasswd[CCH_MAXINPUT];
    char szDatabase[CCH_MAXINPUT];

    printf("\n");
    printf("**************************\n");
    printf("* Connect to data source *\n");
    printf("**************************\n");
    InputString("SQL server name", szServer);
    InputString("SQL database name", szDatabase);
    InputString("User name for SQL server", szUser);
    InputString("Password for SQL server", szPasswd);
    printf("\n");

    // Connect to a data source. SQL in this case.
    // ===========================================
    DATASOURCE dsParams;

    dsParams.sqlParams.ds          = DB_SQL;
    dsParams.sqlParams.pszServer   = szServer;
    dsParams.sqlParams.pszUserName = szUser;
    dsParams.sqlParams.pszPasswd   = szPasswd;
    dsParams.sqlParams.pszDbName   = szDatabase;
    dsParams.sqlParams.pFunc       = NULL;         // No encryption.
    dsParams.sqlParams.pszKey      = "";

    HANDLE hConnect;
    SMS_STATUS stat;
    stat = SmsDataSourceConnect( &dsParams, &hConnect);

    if (stat != SMS_OK) {
        hConnect = NULL;
        DisplaySmsError("Connect to data source failed", stat);
    }

    return( hConnect );
}

// ====================================================================
//
// GetPlatformSelection
//
// Prompt the user to select a one or more platforms.  The platforms
// are selected by entering a comma separated list of platform indexes.
// These platform indexes are converted to the platforms flags value
// for the SDK.
//
// Parameters:  None.
//
// Returns:
//      The SMS SDK platform flags value.
//
// ====================================================================
DWORD GetPlatformSelection()
{
    printf("**********************************************\n");
    printf("* Select a platform from the following list. *\n");
    printf("**********************************************\n");
    printf("   1) MS-DOS \n");
    printf("   2) Windows 3.1\n");
    printf("   3) Windows95\n");
    printf("   4) Windows NT (X86)\n");
    printf("   5) Windows NT (Mips)\n");
    printf("   6) Windows NT (Alpha)\n");
    printf("   7) Macintosh\n");
    printf("\n");

    DWORD dwPlatforms = 0;
    char szReply[CCH_MAXINPUT];
    InputString("Enter a comma separated list of platform number(s)", szReply);
    printf("\n");

    const char *pszReply = szReply;
    while (*pszReply) {

        if (*pszReply < '1' || *pszReply > '6') {
            // An invalid character is present in the
            // reply.  Flag this error by returning zero.
            //==========================================
            dwPlatforms = 0;
            break;
        }

        // Interpret the platform selector character.
        //===========================================
        switch(*pszReply++) {
        case '1':
            dwPlatforms |= PLTFRM_MSDOS;
            break;

        case '2':
            dwPlatforms |= PLTFRM_WIN16;
            break;

        case '3':
            dwPlatforms |= PLTFRM_WIN95;
            break;

        case '4':
            dwPlatforms |= PLTFRM_WIN32_X86;
            break;

        case '5':
            dwPlatforms |= PLTFRM_WIN32_MIPS;
            break;

        case '6':
            dwPlatforms |= PLTFRM_WIN32_ALPHA;
            break;

        case '7':
            dwPlatforms |= PLTFRM_MACINTOSH;
            break;
        }

        // Skip any trailing whitespace after the digit.
        //==============================================
        while(*pszReply == ' ' || *pszReply=='\t') {
            ++pszReply;
        }

        // Skip the comma.
        //================
        if (*pszReply == ',') {
            ++pszReply;
        }

        // Skip white space preceding the next digit.
        //=========================
        while (*pszReply == ' ' || *pszReply=='\t') {
            ++pszReply;
        }

    }

    return( dwPlatforms );
}

// ====================================================================
//
// ServerWriteProgItem
//
// Set the value of the program item folder's scalars, link it into the
// folder hierarchy and finally insert it into the data source.
//
// Parameters:
//      HANDLE hFolderProgItem
//          The program item folder's handle.
//
//      TServerParams *psp
//          Pointer to the "share package on server" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
// Returns:
//      SMS_OK if no error occurred, otherwise the SDK status code.
//
// ====================================================================
SMS_STATUS ServerWriteProgItem( HANDLE hFolderProgItem, TServerParams *psp )
{

    SetScalarString(hFolderProgItem, "Description", psp->szDescription);
    SetScalarString(hFolderProgItem, "Command line", psp->szCmdLine);
    SetScalarDword(hFolderProgItem,  "Run minimized", 0);
    SetScalarDword(hFolderProgItem,  "Platforms", psp->dwPlatforms);
    SetScalarString(hFolderProgItem, "Configure script file", "");
    SetScalarDword(hFolderProgItem,  "Search local flag", 1);
    SetScalarDword(hFolderProgItem,  "Drive mode", 1);
    SetScalarString(hFolderProgItem, "Registry name", psp->szRegistryName);
    SetScalarDword(hFolderProgItem,  "Executable flag", 1);

    SMS_STATUS stat;
    stat = SmsLinkFolder(hFolderProgItem);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to link the server share program item folder into the hierarchy", stat);
        return( stat );
    }

    stat = SmsCommitFolder(hFolderProgItem);
    if ((stat != SMS_OK) && (stat != SMS_PARENT_NEEDS_COMMIT)) {
        DisplaySmsError("Failed to insert the server share program item folder into the package", stat);
    }
    else {
        // If the status code was SMS_PARENT_NEEDS_COMMIT, we convert the status to
        // SMS_OK, since the commit was successful.
        //=========================================================================
        stat = SMS_OK;
    }

    // Note program item folders are not actually inserted into the
    // datasource until a commit is also done on the parent
    // package folder.
    //=====================================================
    return( stat );
}

// ====================================================================
//
// ServerWritePkg
//
// Set the value of the package folder's scalars, link it into the
// folder hierarchy and finally insert it into the data source.
//
// Parameters:
//      HANDLE hFolderPkg
//          The package folder's handle.
//
//      TServerParams *psp
//          Pointer to the "share package on server" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
// Returns:
//      SMS_OK if no error occurred, otherwise the SDK status code.
//
// ====================================================================
SMS_STATUS ServerWritePkg( HANDLE hFolderPkg, TServerParams *psp )
{
    SetScalarString(hFolderPkg, "Name", psp->szPackageName);
    SetScalarString(hFolderPkg, "Comment", psp->szPackageComment);
    SetScalarString(hFolderPkg, "Server root", psp->szPackagePath);
    SetScalarString(hFolderPkg, "Share name", psp->szShareName);
    SetScalarDword(hFolderPkg,  "Share permissions", (DWORD)SHARE_PERMISSIONS);

    SMS_STATUS stat;
    stat = SmsLinkFolder(hFolderPkg);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to link the server share package folder into the hierarchy", stat);
        return( stat );
    }

    stat = SmsCommitFolder(hFolderPkg);
    if ((stat != SMS_OK) && (stat != SMS_PARENT_NEEDS_COMMIT)) {
        DisplaySmsError("Failed to insert the server share package folder into the datasource", stat);
    }

    return( stat );
}

// ====================================================================
//
// ServerCreatePkg
//
// This function create the server install job package.
//
// Parameters:
//      HANDLE hConnect
//          The datasource connection handle.
//
//      TServerParams *psp
//          Pointer to the "share package on server" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
//      char *pszPackageID
//          Pointer to the buffer where the package ID will be
//          returned.
//
//      DWORD cchPackageID
//          Size of the package ID buffer.
//
//      char *pszProgItemID
//          Pointer to the buffer where the program item ID will be
//          returned.
//
//      DWORD cchProgItemID
//          Size of the program item ID buffer.
//
// Returns:
//      SMS_OK if no error occurred, otherwise the SDK status code.
//
// ====================================================================
SMS_STATUS ServerCreatePkg( HANDLE hConnect,
                            TServerParams *psp,
                            char *pszPackageID,
                            DWORD cchPackageID,
                            char *pszProgItemID,
                            DWORD cchProgItemID)
{
    SMS_STATUS stat;
    HANDLE hContainer;
    HANDLE hFolderPkg = NULL;
    HANDLE hFolderProgItem = NULL;

    // First open the package container.  If we fail to open it,
    // display an error message and give up.
    //=========================================================
    stat = SmsOpenContainer( C_PACKAGE, hConnect, &hContainer );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to open the package container", stat);
        return( stat );
    }

    // Now create a new package folder.
    //=================================
    stat = SmsCreateFolder( hContainer, F_PACKAGE, "Dummy", &hFolderPkg );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to create server share package", stat);
        goto CLEANUP_AND_EXIT;
    }

    stat = SmsCreateFolder(hFolderPkg, F_PROGITEM, "Dummy", &hFolderProgItem);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to create server share program item", stat);
        goto CLEANUP_AND_EXIT;
    }

    stat = ServerWriteProgItem(hFolderProgItem, psp);
    if (stat != SMS_OK) {
        goto CLEANUP_AND_EXIT;
    }

    stat = ServerWritePkg(hFolderPkg, psp);
    if (stat != SMS_OK) {
        goto CLEANUP_AND_EXIT;
    }

    GetScalarString(hFolderPkg, "Key", pszPackageID, cchPackageID);
    GetScalarString(hFolderProgItem, "ItemKey", pszProgItemID, cchProgItemID);
    stat = SMS_OK;

CLEANUP_AND_EXIT:

    if (hFolderProgItem) {
        SmsCloseFolder(hFolderProgItem);
    }

    if (hFolderPkg) {
        SmsCloseFolder(hFolderPkg);
    }

    SmsCloseContainer(hContainer);
    return( stat );
}

// ====================================================================

//
// ServerWriteJob
//
// This function sets the values of the job folder's scalars, links the
// job folder into the folder hierachy, and then inserts the folder
// into the data source.
//
// Parameters:
//      HANDLE hFolderJob
//          The handle to the server install job folder.
//
//      TServerParams *psp
//          Pointer to the "share package on server" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
//      char *pszPackageID
//          Pointer to the server install package ID.
//
// Returns:
//      SMS_OK if no error occurred, otherwise the SDK status code.
//
// ====================================================================
BOOL ServerWriteJob( HANDLE hFolderJob,
                     TServerParams *psp,
                     char *pszPackageID )
{
    // Submit the job immediately, so set the job submission time
    // to the current time.
    //===========================================================
    time_t timeJob;
    time( &timeJob );

    SetScalarString(hFolderJob, "Job comment",      "This is an SDK sample job");
    SetScalarTime(  hFolderJob, "Activate time",    timeJob);
    SetScalarDword( hFolderJob, "Priority",         3);
    SetScalarDword( hFolderJob, "Repeat mode",      1);
    SetScalarDword( hFolderJob, "Cancel mode",      0);
    SetScalarString(hFolderJob, "Package ID",       pszPackageID);
    SetScalarDword( hFolderJob, "PackageOverwriteFlag", 0);
    SetScalarDword( hFolderJob, "Job target",       WKSTAJOB_TGT_MACHPATH);
    SetScalarDword( hFolderJob, "Limit to sites",   JOBTGT_SITE);
    SetScalarDword( hFolderJob, "Include subsites", JOBTGT_INCLUDESUBSITES);
    SetScalarString(hFolderJob, "Site limit name",  psp->szSiteName);

    // At this point we have a folder in memory, but it has not
    // been linked into the folder hierarchy yet.  We do so by
    // calling SmsLinkFolder.
    //=========================================================
    SMS_STATUS stat;
    stat = SmsLinkFolder(hFolderJob);
    if (stat != SMS_OK) {
        DisplaySmsError("SmsLinkFolder(hFolderJob) failed", stat);
        return(stat);
    }

    // The folder is now linked into the folder hierachy, but it
    // has not yet been inserted into the data source.  We do so
    // now by calling SmsCommitFolder.
    //==========================================================
    stat = SmsCommitFolder(hFolderJob);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to insert the job into the datasource", stat);
        return(stat);
    }

    return( SMS_OK );
}

// ====================================================================
//
// ServerSubmitShareJob
//
// This function creates a share package on server job.
//
// Parameters:
//      HANDLE hConnect
//          The datasource connection handle.
//
//      TServerParams *psp
//          Pointer to the "share package on server" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
//      char *pszPackageID
//          Pointer to the server install package ID.
//
//      char *pszJobID
//          Pointer to the buffer where the server install job ID will
//          be returned.
//
//      DWORD cchJobID
//          The size of the job ID buffer.
//
// Returns;
//      SMS_OK if everything was successful, otherwise the SMS SDK
//      status code.
//      The job ID is in the job name buffer.
//
// ====================================================================
SMS_STATUS ServerSubmitShareJob(HANDLE hConnect,
                                TServerParams *psp,
                                char *pszPackageID,
                                char *pszJobID,
                                DWORD cchJobID)
{
    *pszJobID = 0;

    SMS_STATUS stat;
    HANDLE hContainerJob;
    stat = SmsOpenContainer( C_JOB, hConnect, &hContainerJob );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to open job container", stat);
        return( stat );
    }

    HANDLE hFolderJob;
    stat = SmsCreateFolder( hContainerJob, F_SRVINSTALLJOB, "Dummy", &hFolderJob );
    if (stat == SMS_OK) {
        if (ServerWriteJob(hFolderJob, psp, pszPackageID) == SMS_OK) {

            // We will need the Job ID later so that we can wait for
            // the job to complete before submitting the client setup
            // package.  We get the Job ID below.
            //==================================================
            GetScalarString(hFolderJob, "Job ID", pszJobID, cchJobID);
        }

        // We close the folder to avoid a memory leak.
        //============================================
        stat = SmsCloseFolder(hFolderJob);
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to close job folder", stat);
        }

    }
    else {
        DisplaySmsError("Failed to create the job folder", stat);
    }

    stat = SmsCloseContainer(hContainerJob);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to close the job container", stat);
    }
    return stat;
}

// ====================================================================
//
// JobStatusToString
//
// Convert the job status value to a human readable string.
//
// Parameters:
//      DWORD dwJobStatus
//          The status value from the job's "Job status" scalar.
//
//      char *pszJobStatus
//          Pointer to a buffer to recieve the human readable version
//          of the job status.
//          The size of the job ID buffer.
//
// Returns;
//      SMS_OK if everything was successful, otherwise the SMS SDK
//      status code.
//
// ====================================================================
void JobStatusToString( DWORD dwJobStatus, char *pszJobStatus )
{
    char *psz;
    switch(dwJobStatus) {
    case JOBSTAT_PENDING:
        psz = "pending";
        break;
    case JOBSTAT_ACTIVE:
        psz = "active";
        break;
    case JOBSTAT_CANCELLED:
        psz = "cancelled";
        break;
    case JOBSTAT_COMPLETE:
        psz = "complete";
        break;
    case JOBSTAT_FAILED:
        psz = "failed";
        break;
    case JOBSTAT_RETRYING:
        psz = "retrying";
        break;
    default:
        sprintf(pszJobStatus, "unkown status %ld", dwJobStatus);
        return;
        break;
    }
    strcpy(pszJobStatus, psz);
}

// ====================================================================
//
// ServerWaitJobComplete
//
// This function polls the status of the server install job and
// continues doing so until the job is complete.  As this is done,
// status given to the user as to the progress of the job.
//
// Parameters:
//      HANDLE hConnect
//          The datasource connection handle.
//
//      char *pszJobID
//          Pointer to the ID string for the job to wait on.
//
// Returns:
//      SMS_OK if everything went ok and the job has completed,
//      otherwise the SDK status code.
//
// ====================================================================
SMS_STATUS ServerWaitJobComplete( HANDLE hConnect, char *pszJobID )
{
    printf("***************************************\n");
    printf("* Share package on server job.        *\n");
    printf("* Waiting for job to complete.        *\n");
    printf("***************************************\n");

    while(TRUE) {
        SMS_STATUS stat;
        HANDLE hContainerJob;
        stat = SmsOpenContainer( C_JOB, hConnect, &hContainerJob );
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to open job container while waiting for server share job", stat);
            return( stat );
        }

        stat = SmsPopulate(hContainerJob, POP_SYNC, NULL);
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to populate job container while waiting for server share job", stat);
            SmsCloseContainer(hContainerJob);
            return(stat);
        }

        HANDLE hFolderJob;
        stat = FindFolder(hContainerJob,  F_SRVINSTALLJOB, "Job ID", pszJobID, &hFolderJob);
        if (stat != SMS_OK) {
            DisplaySmsError("Server share job not found while waiting", stat);
            SmsCloseContainer(hContainerJob);
            return(stat);
        }

        DWORD dwScalarValue;
        GetScalarDword(hFolderJob, "Job status", &dwScalarValue);

        SmsCloseFolder(hFolderJob);
        SmsCloseContainer(hContainerJob);

        char szJobStatus[CCH_MAXSTRING];
        JobStatusToString(dwScalarValue, szJobStatus);

        printf("Share package on server job, status = %s\n", szJobStatus);

        if (dwScalarValue == 4) {
             return(SMS_OK);
        }

        // Sleep for 10 seconds before checking the status of the server
        // share job again.
        //=============================================================
        Sleep(10000);
    }

    // Control should never come here. So no return value is
    // necessary.
    //======================================================

}

// ====================================================================
//
// ServerDoShare
//
// This function creates a server install package and then submits
// a share package on server job.
//
// Parameters:
//      HANDLE hConnect
//          The datasource connection handle.
//
//      TServerParams *psp
//          Pointer to the "share package on server" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
// Returns:
//      SMS_OK if everything went ok, otherwise the SDK status code.
//
// ====================================================================
void ServerDoShare( HANDLE hConnect, TServerParams *psp )
{
    char szPackageID[CCH_MAXSTRING];
    char szProgItemID[CCH_MAXSTRING];
    char szJobID[CCH_MAXSTRING];
    SMS_STATUS stat;

    stat = ServerCreatePkg(hConnect,
                           psp,
                           szPackageID, sizeof(szPackageID),
                           szProgItemID, sizeof(szProgItemID));
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to create server share package", stat);
        return;
    }

    // Submit the share package on server job and get the
    // job ID.
    //====================================================
    stat = ServerSubmitShareJob(hConnect,
                    psp,
                    szPackageID,
                    szJobID,
                    sizeof(szJobID));
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to submit server share package", stat);
        return;
    }

    ServerWaitJobComplete(hConnect, szJobID);
}

// ====================================================================
//
// ClientWritePkg
//
// This function defines the attributes (scalar values) for the client
// setup package.
//
// Parameters:
//      HANDLE hFolderPkg
//          The handle to the package folder for the client setup job.
//
//      TClientParams *pcp
//          Pointer to the "run setup command on client" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
// Returns:
//      SMS_OK if everything went ok, otherwise the SDK status code.
//
// ====================================================================
BOOL ClientWritePkg( HANDLE hFolderPkg, TClientParams *pcp )
{
    SetScalarString(hFolderPkg, "Name", pcp->szPackageName);
    SetScalarString(hFolderPkg, "Comment", pcp->szPackageComment);
    SetScalarString(hFolderPkg, "Workstation root", pcp->szPackagePath);
    SetScalarDword(hFolderPkg,  "Share permissions", (DWORD) SHARE_PERMISSIONS);

    SMS_STATUS stat;
    stat = SmsCommitFolder(hFolderPkg);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to insert the package into the datasource", stat);
        return(FALSE);
    }

    return(TRUE);
}

// ====================================================================
//
// ClientWriteWcl
//
// This function defines the attributes (scalar values) for the
// workstation command line folder that is part of the client setup
// package.
//
// Parameters:
//      HANDLE hFolderWcl
//          The handle to the workstation command line folder for the
//          client setup job.
//
//      TClientParams *pcp
//          Pointer to the "run setup command on client" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
// Returns:
//      SMS_OK if everything went ok, otherwise the SDK status code.
//
// ====================================================================
BOOL ClientWriteWcl( HANDLE hFolderWCL, TClientParams *pcp )
{

    SetScalarString(hFolderWCL, "Name",      pcp->szWclName);
    SetScalarString(hFolderWCL, "Command",   pcp->szWclCommand);
    SetScalarDword( hFolderWCL, "Flags",     0);
    SetScalarDword( hFolderWCL, "Platforms", pcp->dwPlatforms);

    SMS_STATUS stat;
    stat = SmsLinkFolder(hFolderWCL);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to link the WCL folder into the hierarchy", stat);
        return( FALSE );
    }

    stat = SmsCommitFolder(hFolderWCL);
    if ((stat != SMS_OK) && (stat != SMS_PARENT_NEEDS_COMMIT)) {
        DisplaySmsError("Failed to insert the WCL folder into the package", stat);
        return( FALSE );
    }

    // Note WCL folders are not actually inserted into the
    // datasource until a commit is also done on the parent
    // package folder.
    //=====================================================

    return( TRUE );
}

// ====================================================================
//
// ClientPkgCreate
//
// This function defines creates an SMS package for the client setup
// program.
//
// Parameters:
//      HANDLE hConnect
//          The datasource connection handle.
//
//      TClientParams *pcp
//          Pointer to the "run setup command on client" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
//      char *pszPackageID
//          Pointer to the buffer where the package folder ID string
//          will be returned.
//
//      DWORD cchPackageID
//          The size of the package ID buffer.
//
//      char *pszWclID
//          Pointer to the buffer where the workstation command line
//          folder ID will be returned.
//
//      DWORD cchWclID
//          The size of the workstation command line ID buffer.
//
// Returns:
//      SMS_OK if everything went ok, otherwise the SMS SDK status
//      code.
//      The package ID and the WCL IDs are returned in their respective
//      buffers.
//
// ====================================================================
SMS_STATUS ClientPkgCreate( HANDLE hConnect,
                            TClientParams *pcp,
                            char *pszPackageID,
                            DWORD cchPackageID,
                            char *pszWclID,
                            DWORD cchWclID )
{
    SMS_STATUS stat;
    HANDLE hContainer;

    // First open the package container.  If we fail to open it,
    // display an error message and give up.
    //=========================================================
    stat = SmsOpenContainer( C_PACKAGE, hConnect, &hContainer );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to open the package container", stat);
        return( stat );
    }

    // Now create a new package folder.
    //=================================
    HANDLE hFolderPkg;
    stat = SmsCreateFolder( hContainer, F_PACKAGE, "Dummy", &hFolderPkg );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to create package folder for setup command", stat);
        SmsCloseContainer(hContainer);
        return(stat);
    }

    HANDLE hFolderWCL;
    stat = SmsCreateFolder(hFolderPkg, F_WCL, "Dummy", &hFolderWCL);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to create WCL folder for setup command", stat);
        SmsCloseContainer(hContainer);
        return(stat);
    }

    // At this point we have created both the WCL and the package folder
    // and now we define their attributes.  Note that the WCL attributes
    // are defined first because the call to SmsCommitFolder for the WCL
    // must precede the call to SmsCommitFolder for the package.  This is
    // because the WCL is part of the package and the package can't be
    // modified after a commit is done on it.
    //==================================================================
    ClientWriteWcl(hFolderWCL, pcp);
    ClientWritePkg(hFolderPkg, pcp);

    // We now get the identification strings for the package and the WCL.
    // This is done here because the WCL key is not defined until the
    // package it is contained in has been committed.
    //==================================================================
    GetScalarString(hFolderPkg, "Key", pszPackageID, cchPackageID);
    GetScalarString(hFolderWCL, "Name", pszWclID, cchWclID);

    stat = SmsCloseFolder(hFolderWCL);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to close the WCL folder", stat);
    }

    stat = SmsCloseFolder(hFolderPkg);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to close the package folder", stat);
    }

    stat = SmsCloseContainer(hContainer);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to close the package container", stat);
    }

    return( stat );
}

// ====================================================================
//
// ClientWriteJob
//
// This function defines the values of the job folder, links the folder
// into the folder hierarchy, and finally inserts the folder into the
// datasource.
//
// Parameters:
//      HANDLE hFolderJob
//          The job folder's handle.
//
//      TClientParams *pcp
//          Pointer to the "run command on client" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
//      char *pszPackageID
//          Pointer to the package folder ID string.
//
//      char *pszWclID
//          Pointer to the workstation command line folder ID.
//
// Returns:
//   SMS_OK if everything went OK, otherwise the SMS SDK status code.
//
// ====================================================================
SMS_STATUS ClientWriteJob(  HANDLE hFolderJob,
                            TClientParams *pcp,
                            char *pszPackageID,
                            char *pszWCLID)
{
    // Submit the job immediately. To do this we set the job
    // submission time to the current time.
    //======================================================
    time_t timeJob;
    time( &timeJob );

    SetScalarString(hFolderJob, "Job comment",          "This is an SDK sample job");
    SetScalarTime(  hFolderJob, "Activate time",        timeJob);
    SetScalarDword( hFolderJob, "Priority",             JOBPRI_LOW);
    SetScalarDword( hFolderJob, "Repeat mode",          JOBRPT_NEVER);
    SetScalarDword( hFolderJob, "Cancel mode",          JOBCANCEL_DONT_CANCEL);
    SetScalarString(hFolderJob, "Package ID",           pszPackageID);
    SetScalarString(hFolderJob, "WCL name",             pcp->szWclName);
    SetScalarString(hFolderJob, "Machine path",         "*|*|*");
    SetScalarTime(  hFolderJob, "Offer time",           timeJob);
    SetScalarTime(  hFolderJob, "Mandatory time",       timeJob);
    SetScalarDword( hFolderJob, "Use mandatory time",   0);
    SetScalarDword( hFolderJob, "Force over slow link", 0);
    SetScalarDword( hFolderJob, "Use expire time",      0);
    SetScalarDword( hFolderJob, "Job target",           WKSTAJOB_TGT_MACHPATH);
    SetScalarDword( hFolderJob, "Limit to sites",       JOBTGT_SITE);
    SetScalarDword( hFolderJob, "Include subsites",     JOBTGT_INCLUDESUBSITES);
    SetScalarString(hFolderJob, "Site limit name",      pcp->szSiteName);
    SetScalarDword( hFolderJob, "Run workstation command",  TRUE);

    // At this point, the job folder exists in memory.  Now
    // we must insert the folder into the data source.  We do
    // this by calling SmsCommitFolder.
    //=========================================================
    SMS_STATUS stat;
    stat = SmsLinkFolder(hFolderJob);
    if (stat != SMS_OK) {
        DisplaySmsError("SmsLinkFolder(hFolderJob) failed", stat);
        return(stat);
    }

    stat = SmsCommitFolder(hFolderJob);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to insert the job into the datasource", stat);
        return(stat);
    }

    return( SMS_OK );
}

// ====================================================================
//
// ClientJobCreate
//
// This function creates an SMS job to run a command on the client
// workstation.   The name of the network share path is passed
// as a parameter to the client setup program.
//
// Parameters:
//      HANDLE hConnect
//          The datasource connection handle.
//
//      TClientParams *pcp
//          Pointer to the "run command on client" parameters
//          that the user was asked to input at the beginning of the
//          program.
//
//      char *pszPackageID
//          Pointer to the package folder ID string.
//
//      char *pszWclID
//          Pointer to the workstation command line folder ID.
//
// Returns:
//      SMS_OK if everything went OK, otherwise the SMS SDK status
//      code.
//
// ====================================================================
SMS_STATUS ClientJobCreate( HANDLE hConnect,
                            TClientParams *pcp,
                            char *pszPackageID,
                            char *pszWCLID)
{

    SMS_STATUS stat;

    HANDLE hContainerJob;
    stat = SmsOpenContainer( C_JOB, hConnect, &hContainerJob );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to open job container", stat);
        return( stat );
    }

    HANDLE hFolderJob;
    stat = SmsCreateFolder( hContainerJob, F_INSTALLJOB, "Dummy", &hFolderJob );
    if (stat == SMS_OK) {
        ClientWriteJob(hFolderJob, pcp, pszPackageID, pszWCLID);

        stat = SmsCloseFolder(hFolderJob);
        if (stat != SMS_OK) {
            DisplaySmsError("Failed to close job folder", stat);
        }

    }
    else {
        DisplaySmsError("Failed to create the job folder", stat);
    }

    stat = SmsCloseContainer(hContainerJob);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to close the job container", stat);
    }
    return stat;
}

// ====================================================================
//
// ClientDoRunCommand
//
// This function submits a run command on workstation job.
//
//
// Parameters:
//      HANDLE hConnect
//          The datasource connection handle.
//
//      TClientParams *pcp
//          Pointer to the "client" parameters that the user was asked to
//          input at the beginning of the program.
//
// Returns:  Nothing.
//
// ====================================================================
void ClientDoRunCommand( HANDLE hConnect, TClientParams *pcp )
{
    char szPackageID[CCH_MAXSTRING];
    char szWclID[CCH_MAXSTRING];

    SMS_STATUS stat;
    stat = ClientPkgCreate(hConnect,
                           pcp,
                           szPackageID, sizeof(szPackageID),
                           szWclID, sizeof(szWclID));
    if (stat != SMS_OK) {
        DisplaySmsError("Could not create client package", stat);
        return;
    }

    stat =  ClientJobCreate(hConnect, pcp, szPackageID, szWclID);
    if (stat != SMS_OK) {
        DisplaySmsError("Could not create client job", stat);
    }
}

// ====================================================================
//
// GetSiteName
//
// This function query's the user to select the SMS site that will be
// used in this sample
//
// Parameters:
//      HANDLE hConnect
//          The datasource connection handle.
//
//      char *pszSiteName
//          Pointer to the buffer where the site name will be returned.
//
// Returns:  Nothing.
//
// ====================================================================
void GetSiteName( HANDLE hConnect, char *pszSiteName )
{
    *pszSiteName = '\0';

    HANDLE hContainerSite;
    SMS_STATUS stat;

    stat = SmsOpenContainer( C_SITE, hConnect, &hContainerSite );
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to open site container", stat);
        return;
    }

    stat = SmsPopulate(hContainerSite, POP_SYNC, NULL);
    if (stat != SMS_OK) {
        DisplaySmsError("Failed to populate site container", stat);
        return;
    }

    HANDLE ahFolderSite[C_MAXSITES];
    HANDLE hFolderSite;
    int ctFolders = 0;
    int iFolder;

    // Get the folder handle for each folder in the site container.  Also
    // count the folders so that we know how many we have.
    //===================================================================
    while(SmsGetNextFolder(hContainerSite, F_SITE, &hFolderSite) == SMS_OK) {
        ahFolderSite[ctFolders] = hFolderSite;
        ++ctFolders;
    }

    // Ask the user to select one of the sites.  After the user
    // select's a site, copy its name to pszSiteName.
    //=========================================================
    char szValue[CCH_MAXSTRING];
    if (ctFolders > 0) {
        printf("******************************************\n");
        printf("* Select a site from the following list. *\n");
        printf("******************************************\n");
        printf("\n");

        for(iFolder=0; iFolder < ctFolders; ++iFolder) {
            GetScalarString(ahFolderSite[iFolder], "Site name", szValue, sizeof(szValue));
            printf("[%d]. %s\n", iFolder + 1, szValue);
        }
        printf("\n");

        InputString("Enter site number", szValue);
        printf("\n");
        sscanf(szValue, "%d", &iFolder);

        if (iFolder > 0 && iFolder <= ctFolders) {
            GetScalarString(ahFolderSite[iFolder-1], "Site name", szValue, sizeof(szValue));
            strcpy(pszSiteName, szValue);
        }
    }

    // Close all of the site folders that we opened via SmsGetNextFolder.
    //===================================================================
    for (iFolder = 0; iFolder < ctFolders; ++iFolder) {
        SmsCloseFolder(ahFolderSite[iFolder]);
    }

}

// ====================================================================
//
// GetJobType
//
// This function query's the user to select the type of SMS job he or
// she would like to create.
//
// Parameters: None.
//
// Returns.  A status code indicating the job type.
//      CMD_RUN_COMMAND = Run command on workstation job.
//      CMD_SHARE_PACKAGE = Share package on server job.
//      CMD_QUIT = Exit without creating a job.
//
// ====================================================================
int GetJobType()
{
    printf("***************************************\n");
    printf("* Select the job type to create.      *\n");
    printf("***************************************\n");
    printf("1. Run command on workstation.\n");
    printf("2. Share package on server.\n");
    printf("3. Quit.\n");
    printf("\n");

    char szReply[CCH_MAXSTRING];
    InputString("Enter the job type", szReply);
    printf("\n");

    int iType = 0;
    sscanf(szReply, "%d", &iType);
    switch(iType) {
    case 1:
        return(CMD_RUN_COMMAND);
    case 2:
        return(CMD_SHARE_PACKAGE);
    default:
        return(CMD_QUIT);
    }
}

// ********************************************************************
//      Helper functions.
// ********************************************************************

// ====================================================================
//
// DidRequestHelp
//
// Check the program's arguments to see if the user asked for
// the help screen to be displayed.
//
// Parameters:
//      int argc
//          The argc value from main(argc, argv)
//
//      char** argv
//          The argv value from main(argc, argv)
//
// Returns:
//      TRUE if command line parameters included a request
//      for help to be displayed.
//
// ====================================================================
BOOL DidRequestHelp(int argc, char** argv)
{
    if (argc == 2  && (strcmp(argv[1], "-help") == 0)) {

return(TRUE);
    }
    else {
        return(FALSE);
    }
}

// ====================================================================
//
// DisplayHelp
//
// This function displays the samples help screen.
//
// Parameters:
//      None
//
// Returns:
//      Nothing.
//
// ====================================================================
void DisplayHelp()
{
    printf("\n\n");
    printf("***********************************************************\n");
    printf("* srvinst.exe:                                            *\n");
    printf("*                                                         *\n");
    printf("* SMS API Example: Creation of jobs.                      *\n");
    printf("*                                                         *\n");
    printf("* This sample program demonstrates how the SMS SDK can    *\n");
    printf("* be used to create a \"share package on server\" job or a  *\n");
    printf("* \"run command on workstation\" job.                       *\n");
    printf("*                                                         *\n");
    printf("* Prior to running this program, you must first share the *\n");
    printf("* directory where the source files are for the package.   *\n");
    printf("*                                                         *\n");
    printf("* Syntax:                                                 *\n");
    printf("*     srvinst.exe [-help]                                 *\n");
    printf("*                                                         *\n");
    printf("* Switches:                                               *\n");
    printf("*     -help       Display this help message.              *\n");
    printf("*                                                         *\n");
    printf("***********************************************************\n");
    printf("\n");
}

// ====================================================================
//
// DisplayGreeting
//
// Display the initial greeting banner.
//
// Parameters:
//     None.
//
// Returns:
//     Nothing.
//
// ====================================================================
void DisplayGreeting()
{
    // For this sample, the greeting is identical to the help screen.
    //===============================================================
    DisplayHelp();
}

/* EOF: srvinst.cpp */


README.TXT

Sample: testall
===============

DESCRIPTION:

    This program illustrates the use of the SMS APIs to examine either
    a site container, a jobs container, or a package container. The
    only difference in code between them is the setting of filters.

    For the specified container, this program will set a standard set of
    filters for that container type, and then begin enumeration of that
    container and all the folders within it.

    After the preliminary work of soliciting from the user the datasource
    information and making a connection to that datasource, the program
    then solicits the container type to enumerate and sets appropriate
    filters (see below). A loop is then entered which retrieves and
    stores the handles of all folders in that container. The container
    is then closed. Closing a container as soon as possible has a space
    advantage in that an open parent causes a folder's reference count to
    be incremented. The folder will not be closed until this count reaches
    zero. If the container is left open then no folders at all will be
    removed from memory.

    We then loop through the list of folders and display them one at a time,
    after which the program either terminates or restarts (using the same
    datasource), according to the user's request.

    Displaying folders.
    This is accomplished in the DisplayFolder() function.

API USAGE:
    The following APIs are used in this program:

      SmsAPIVer

      SmsDataSourceConnect
      SmsDataSourceDisconnect

      SmsOpenContainer
      SmsSetFilter
      SmsPopulate
      SmsGetFolderCount
      SmsCloseContainer

      SmsCreateFilter
      SmsGetToken
      SmsGetTokenCount
      SmsAddToken
      SmsCloseFilter

      SmsGetFolderID
      SmsGetFolderType
      SmsGetNextFolder
      SmsGetScalarCount
      SmsGetNextScalar
      SmsCloseFolder

1. Site hierarchy
-----------------
This program will display the site/domain/machine/MIF group
tree for the associated datasource according to certain filter
conditions.
o site filter:         select either root site or specific site.
o architecture filter: select machines of architecture Personal Computer.
o machine filter:      only machines with SystemRole=Server.
o group filter:        select the identification group.
o greoup filter:       select the netcard group.

2. Jobs
-------
The user can select from the following types of jobs: workstation
install, server share, remove package, system. Selections are
ORed together, thus it is possible to select both workstation
install and remove package jobs. It is also possible to select
none of the above in which case the system defaults to selecting
all of them.
This might sound illogical, quite the contrary. A filter is a
restriction, if there are no restrictions then none are disallowed.

3. Packages
-----------
This uses the package filter to allow selection of any or all of the
following types of packages:
o Workstation
o Server
o Inventory

The program will enumerate all folders in the specified container,
for each folder it will display all scalars and then enumerate
all sub-folders.


TESTALL.CPP

// ====================================================================
//
//  File: testall.cpp
//
//  Copyright (C) 1994, 1995 by Microsoft Corporation.
//
//  Author:
//      Jonathan Shuval     Microsoft Corp.
//
//
//  This program illustrates the use of the SMS APIs to examine either
//  a site container, a jobs container, or a package container. The
//  only difference in code between them is the setting of filters.
//
//  The following APIs are used in this program:
//
//      SmsAPIVer
//
//      SmsDataSourceConnect
//      SmsDataSourceDisconnect
//
//      SmsOpenContainer
//      SmsSetFilter
//      SmsPopulate
//      SmsGetFolderCount
//      SmsCloseContainer
//
//      SmsCreateFilter
//      SmsGetToken
//      SmsGetTokenCount
//      SmsAddToken
//      SmsCloseFilter
//
//      SmsGetFolderID
//      SmsGetFolderType
//      SmsGetNextFolder
//      SmsGetScalarCount
//      SmsGetNextScalar
//      SmsCloseFolder
//
//  See the readme.txt file in this directory for more information.
//
// ====================================================================

// ====================================================================
//  Includes.
// ====================================================================
#include <afx.h>
#include <smsapi.h>             // Header for the APIs.
#include <time.h>               // For converting time scalars into string.

// Include the GetStatusName function.
// -------------------------------------
#include "..\common\status.inc"

// ====================================================================
//  Defines.
// ====================================================================
#define CCH_MAXINPUT 256

// ====================================================================
//  Local prototypes.
// ====================================================================

// Does the real work.
// -------------------------------------------------------------
void doTheWork( HANDLE hConnect );

// Ask user if they want to see site, jobs or package container.
// -------------------------------------------------------------
DWORD getDumpOption();

// Set filters according to what container we're looking at.
// -------------------------------------------------------------
BOOL setSiteFilters( HANDLE hConnect, HANDLE hContainer );
BOOL setJobFilters( HANDLE hConnect, HANDLE hContainer );
BOOL setPkgFilters( HANDLE hConnect, HANDLE hContainer );

// Functions to display folders, scalars, and expressions.
// -------------------------------------------------------------
void DisplayFolder( HANDLE hFolder );
void DisplayScalars( HANDLE hFolder );
void DisplayExpression( HANDLE hFolder );

// Prompt the user for input and return the reply.
// -------------------------------------------------------------
void InputString(const char* pszMessage, char* pszResult);

// Connect to the SMS datasource.
// -------------------------------------------------------------
HANDLE ConnectToDatasource();

// Display the help message.
// -------------------------------------------------------------
void DisplayHelp();

// Display the greeting.
// -------------------------------------------------------------
void DisplayGreeting();

// Check to see if there was a request for help
// on the command line.
// -------------------------------------------------------------
BOOL DidRequestHelp(int argc, char** argv);

// Display an error message with its SMS status value.
// -------------------------------------------------------------
void DisplaySmsError(const char* pszMessage, SMS_STATUS stat);

// Get the string equivallent for an SMS status code.
// -------------------------------------------------------------
const char* GetStatusName(SMS_STATUS stat);

// ====================================================================
//
//  Macros for checking status
//
// ====================================================================
        // Void return
#define CHKSTAT(str) if (stat != SMS_OK) { \
        DisplaySmsError(str, stat);         \
        return; }

        // Bool return
#define CHKSTAT_RTN(str) if (stat != SMS_OK) { \
        DisplaySmsError(str, stat);             \
        return(FALSE); }

// ====================================================================
//
//  The work starts here.
//
// ====================================================================
void main(int argc, char** argv)
{
    // Check to see if this is a request to display the help
    // screen.  If so, display it. Otherwise, display a greeting
    // banner.
    //=========================================================
    if (DidRequestHelp(argc, argv)) {
        DisplayHelp();
        return;
    }
    else {
        DisplayGreeting();
    }

    HANDLE hConnect;

    // Get and display API version.
    // ----------------------------
    char *pszVersion;
    SmsAPIVer( &pszVersion );
    printf("%s\n", pszVersion );

    //===========================================
    // Connect to the SMS datasource.
    //===========================================
    hConnect = ConnectToDatasource();
    if (hConnect == NULL) {
        return;
    }

    // This loop allows us to do the real work multiple times.
    // =======================================================
    BOOL bDone = FALSE;
    char reply[10];

    while (!bDone) {

        doTheWork( hConnect );

        printf("Restart [y/n]? "); gets(reply);
        bDone = (reply[0] != 'y' && reply[0] != 'Y');
    }

    // Disconnect from the datasource.
    // ===============================
    SmsDataSourceDisconnect( hConnect );

}  /* main */

// ====================================================================
//
//  This function does whatever work is required.
//  In this case, it will:
//      - open a container
//      - set filters
//      - populate the container
//      - retrieve all folders from the container and store the handles
//      - cause these folder to be displayed.
//
// ====================================================================
void doTheWork( HANDLE hConnect )
{
    HANDLE hContainer;
    SMS_STATUS stat;

    // Ask the user if he wants a site hierarchy, jobs, or package dump.
    // =================================================================
    DWORD cType = getDumpOption();
    BOOL bRet;

    stat = SmsOpenContainer( cType, hConnect, &hContainer );
    if (stat != SMS_OK) {
        DisplaySmsError("SmsOpenContainer failed", stat);
        return;
    }

    switch (cType) {
    case C_SITE:
        bRet = setSiteFilters( hConnect, hContainer );
        break;

    case C_JOB:
        bRet = setJobFilters( hConnect, hContainer );
        break;

    case C_PACKAGE:
        bRet = setPkgFilters( hConnect, hContainer );
        break;
    }

    if (!bRet) {
        printf("Failure in filters.\n");
        SmsCloseContainer( hContainer );
        return;
    }

    // Select all objects matching filter.
    // -----------------------------------
    stat = SmsPopulate( hContainer, POP_SYNC, NULL );
    if (stat != SMS_OK) {
        DisplaySmsError("Bad return from SmsPopulate", stat);
        SmsCloseContainer( hContainer );
        return;
    }

    DWORD numFolders;
    SmsGetFolderCount( hContainer, F_ANY, &numFolders );
    printf("\n======== Container has %d folders ==========================\n\n",
                                numFolders);

    // For each object in the list enumerate its properties.
    // What I'm going to do is open all folders in a loop and
    // then close the container. The open container causes all
    // objects to be left open until the container closes. This
    // chews up memory big time.
    // ------------------------------------------------------
    CObArray topFolders;
    HANDLE hFolder;

    // Open all top-level folders.
    // ===========================
    while (1) {
        stat = SmsGetNextFolder( hContainer, F_ANY, &hFolder );
        if (stat != SMS_OK) {
            // Either error or no more objects in list.
            break;
        }

        // Store the folder's handle for later processing.
        // -----------------------------------------------
        topFolders.Add( (CObject *)hFolder );
    }

    printf("*** Retrieved %d folders ***\n", topFolders.GetSize());
    // Check why we exited the loop.
    // -----------------------------
    if (stat != SMS_NO_MORE_DATA) {
        printf("Failed to retrieve folder: %d\n", stat);
    }

    // Close the container.
    // --------------------
    SmsCloseContainer( hContainer );
    printf("*** Container closed ***\n");

    for (int iLoop = 0; iLoop < topFolders.GetSize(); iLoop++) {

        // Retrieve the handle.
        // --------------------
        hFolder = (HANDLE)topFolders[iLoop];

        printf("\n*** Top folder #%d\n", iLoop);

        // Display the folder.
        // -------------------
        DisplayFolder( hFolder );

        // The folder is closed inside DisplayFolder.
    }

    printf("\n********* All done ***************\n");

}

// ====================================================================
//  We are passed in a handle to a folder. We retrieve the folder's
//  information, displaying scalars if any.
//  We then recursively examine sub-folders (after closing the
//  current folder).
// ====================================================================
void DisplayFolder( HANDLE hFolder )
{

    SMS_STATUS stat;
    HANDLE hSubFolder;
    DWORD fType;                   // type of folder we're dealing with.
    char szfType[30];                   // type as a string.
    DWORD totFolders;                   // Total number of sub-folders of
                                        // all types.
    char szFolderID[100];               // This folder's ID.
    DWORD ctScalars;                    // How many scalars in this folder.

    printf("\n============================================================\n");

    // Get folder type and id.
    // -----------------------
    stat = SmsGetFolderID( hFolder, szFolderID );
    CHKSTAT("SmsGetFolderID");

    stat = SmsGetFolderType( hFolder, &fType, szfType );
    CHKSTAT("SmsGetFolderType");
    printf("Folder ID \"%s\" is a %s\n\n", szFolderID, szfType);

    // How many scalars in this folder.
    // --------------------------------------------------------
    stat = SmsGetScalarCount( hFolder, &ctScalars );
    CHKSTAT("SmsGetScalarCount");
    printf("Contains %d scalars\n\n", ctScalars);
    DisplayScalars( hFolder );

    // If folder has an associated expression, display it.
    // ---------------------------------------------------
    DisplayExpression( hFolder );

    // Get count of all sub-folders (ie of all types).
    // and allocate space for their handles.
    // -----------------------------------------------
    stat = SmsGetFolderCount( hFolder, F_ANY, &totFolders );
    printf("Contains %d folders\n\n", totFolders);

    HANDLE *phFolders = (HANDLE *)malloc(totFolders * sizeof(HANDLE));

    // This loop gets the sub-folder IDs and displays them.
    // ====================================================
    char szSubFolderID[100];
    DWORD ctFolders;

    for (ctFolders = 0; ctFolders < totFolders; ctFolders++) {

        // Get a handle to a sub-folder.
        // -----------------------------
        stat = SmsGetNextFolder( hFolder, F_ANY, &hSubFolder );
        CHKSTAT("SmsGetNextFolder");
        phFolders[ctFolders] = hSubFolder;

        // Get and display the sub-folder's ID.
        // ------------------------------------
        stat = SmsGetFolderID( hSubFolder, szSubFolderID );
        CHKSTAT("SmsGetFolderID");
        printf("\tSub-folder: \"%s\"\n", szSubFolderID);
    }

    // =======================================================
    // We can now release the handle that was passed in to us.
    // If we wait until the function returns then, because we
    // recurse, we don't release it till the very end.
    // =======================================================
    SmsCloseFolder( hFolder );

    // This loop gets the sub-folders and displays them.
    // =================================================
    for (ctFolders = 0; ctFolders < totFolders; ctFolders++) {
        DisplayFolder( phFolders[ctFolders] );
    }

    // Free the folder handle array.
    // -----------------------------
    free( phFolders );

}  /* DisplayFolder */

// ====================================================================
//
//  Display all the scalars for the folder.
//
// Note: the buffer for string scalars has been deliberately set to a
// value lower than the maximum. If we encounter a string scalar whose
// value exceeds this amount then we will print the 'data truncated'
// message.
//
// ====================================================================
void DisplayScalars( HANDLE hFolder )
{
    SMS_STATUS stat = SMS_OK;
    SCALAR scalar;
    char szName[50];                        // Buffer for name.
    char szValue[SMS_DATA_BUFF_SIZE+1];     // Buffer for string value.
    BYTE byValue[SMS_DATA_BUFF_SIZE+1];     // Buffer for binary scalars.
    scalar.pszName  = szName;
    scalar.pszValue = szValue;
    scalar.pValue   = byValue;

    char *pszTime;          // For time scalars.

    while (1) {

        scalar.dwLen = sizeof(szValue)-1;       // must tell him the size

        stat = SmsGetNextScalar( hFolder, &scalar);
        if (stat != SMS_OK && stat != SMS_MORE_DATA) {
            break;
        }

        if (stat == SMS_MORE_DATA) {
            printf("Receive buffer too small, should be %d. Data truncated\n",
                            scalar.dwLen);
        }
        // Check scalar type, display accordingly.
        // ---------------------------------------
        switch (scalar.scType) {
        case SCALAR_STRING:
            printf("\t%30s: %s\n", scalar.pszName, scalar.pszValue);
            break;

        case SCALAR_INT:
            printf("\t%30s: %ld\n", scalar.pszName, scalar.dwValue);
            break;

        case SCALAR_TIME:
            // If there is a string equivalence use it.
            if (scalar.bStringEquivalence) {
                printf("\t%30s: %s\n", scalar.pszName, scalar.pszValue);
            } else {
                pszTime = ctime( &scalar.tValue );
                printf("\t%30s: %s", scalar.pszName, pszTime);
            }
            break;

        case SCALAR_BINARY:
            // Got binary data.
            // Just tell the user how much data there is.
            printf("\t%30s: Binary data - %d bytes of data\n",
                        scalar.pszName, scalar.dwLen);
            break;
        }
    }

    // Why did we exit (other than no more scalars)?
    // ---------------------------------------------
    if (stat != SMS_NO_MORE_DATA) {
        DisplaySmsError("Bad return from Scalar access", stat);
    }

    // Terminate with newline.
    // -----------------------
    printf("\n");

}

// ====================================================================
//
// DisplayExpression -- inv pkg has an expression, display it.
//
// ====================================================================
void DisplayExpression( HANDLE hFolder )
{
    DWORD ctTokens;
    SMS_STATUS stat;

    stat = SmsGetTokenCount( hFolder, &ctTokens );
    if (stat != SMS_OK) {
        printf("Folder has no expression\n\n");
        return;
    }

    printf("Folder has expression with %d tokens\n\n", ctTokens);

    TOKEN Token;

    for (DWORD dwI = 0; dwI < ctTokens; dwI++) {
        stat = SmsGetToken( hFolder, (INT)dwI, &Token );
        if (stat != SMS_OK) {
            DisplaySmsError("GetToken failed", stat);
            break;
        }
        // print out token
        CString sTokenType;

        switch (Token.tokenType) {
        case TOKEN_USER:
            sTokenType = "User token ";
            break;

        case TOKEN_OR:
            sTokenType = "OR token   ";
            break;

        case TOKEN_AND:
            sTokenType = "AND token  ";
            break;

        case TOKEN_OPENPAREN:
            sTokenType = "Open paren ";
            break;

        case TOKEN_CLOSEPAREN:
            sTokenType = "Close paren";
            break;

        default:
            sTokenType = "Invalid token type!";
            break;
        }

        printf("[%d: %s] %s\n", dwI,
                (const char *)sTokenType, Token.szTokenString );

    }

    // Terminate with newline.
    // -----------------------
    printf("\n");

}

// ********************************************************************
//      Helper functions.
// ********************************************************************

// ====================================================================
// Ask the user if they want to look at site, jobs, pkg hierarchy.
// ====================================================================
DWORD getDumpOption()
{
    DWORD cType;
    char reply[10];
    BOOL valid = FALSE;

    while (!valid) {
        printf("Options: (S)ites (J)obs (P)ackages: ");
        gets(reply);

        switch (reply[0]) {
        case 'S': case 's':
            cType = C_SITE;
            valid = TRUE;
            break;

        case 'J': case 'j':
            cType = C_JOB;
            valid = TRUE;
            break;

        case 'P': case 'p':
            cType = C_PACKAGE;
            valid = TRUE;
            break;

        default:
            printf("Invalid - try again (S, J, P): ");
            break;

        }
    }

    return(cType);
}

// ====================================================================
// This function sets the filters when we are looking at a site
// container.
//
// This illustrates the use of the following filters: Site filter,
// architecture filter, machine filter, group filter.
//
// The user is given the choice of using some of these filters or not.
// ====================================================================
BOOL setSiteFilters( HANDLE hConnect, HANDLE hContainer )
{
    SMS_STATUS stat;
    char reply[5];
    HANDLE hFilter;

    // =========================================================
    // Site filter: RootSite or SiteByCode
    // =========================================================
    TOKEN SiteToken;
    memset( &SiteToken, 0, sizeof(TOKEN) );
    stat = SmsCreateFilter( SITE_FILTER, hConnect, &hFilter );
    CHKSTAT_RTN("SmsCreateFilter");

    printf("Site filter. Rootsite or Site by code [R/S]? ");
    gets(reply);
    if (reply[0] == 'R' || reply[0] == 'r') {
        strcpy( SiteToken.szName, "RootSite" );
        stat = SmsAddToken( hFilter, OP_OR, &SiteToken, 0 );
        CHKSTAT_RTN("SmsAddToken (site filter)");

    } else {
        printf("Give 3 letter site code: ");
        gets(reply);
        strcpy( SiteToken.szName, "SiteByCode" );
        strcpy( SiteToken.szValue, reply );
        SiteToken.dwOp = QOP_STR_EQ;
        stat = SmsAddToken( hFilter, OP_OR, &SiteToken, 0 );
        CHKSTAT_RTN("SmsAddToken (site filter)");
    }
    stat = SmsSetFilter( hContainer, hFilter );
    SmsCloseFilter( hFilter );
    if (stat != SMS_OK) {
        printf("SmsSetFilter failed: %d\n", stat);
        return(FALSE);
    }

    // =========================================================
    // Architecture filter.
    // Must set an architecture filter else we get no machines.
    // Architecture = Personal Computer.
    // =========================================================
    TOKEN ArchToken;
    memset( &ArchToken, 0, sizeof(TOKEN) );

    stat = SmsCreateFilter( ARCHITECTURE_FILTER, hConnect, &hFilter );
    CHKSTAT_RTN("SmsCreateFilter");

    strcpy( ArchToken.szName, "Architecture" );
    strcpy( ArchToken.szValue, "Personal Computer" );
    ArchToken.dwOp = QOP_STR_EQ;

    stat = SmsAddToken( hFilter, OP_OR, &ArchToken, 0 );
    CHKSTAT_RTN("SmsAddToken (architecture filter)");

    stat = SmsSetFilter( hContainer, hFilter );
    SmsCloseFilter( hFilter );

    CHKSTAT_RTN("SmsSetFilter (architecture filter)");

    // =========================================================
    // Machine filter. This filter will result in only machines
    // with a SystemRole of Server being included in the parent
    // (domain) folder.
    // =========================================================
    printf("Filter SystemRole=Server [y/n]? ");
    gets(reply);
    if (reply[0] == 'y') {
        TOKEN MachToken;
        memset( &MachToken, 0, sizeof(TOKEN) );
        strcpy( MachToken.szArchitecture, "Personal Computer" );
        strcpy( MachToken.szGroupClass, "MICROSOFT|IDENTIFICATION|1.0" );
        strcpy( MachToken.szAttributeName, "SystemRole" );
        strcpy( MachToken.szValue, "Server" );
        MachToken.dwOp = QOP_STR_EQ;

        stat = SmsCreateFilter( MACHINE_FILTER, hConnect, &hFilter );
        CHKSTAT_RTN("SmsCreateFilter (machine filter)");

        stat = SmsAddToken( hFilter, OP_AND, &MachToken, 0 );
        CHKSTAT_RTN("SmsAddToken (machine filter)");

        stat = SmsSetFilter( hContainer, hFilter );
        SmsCloseFilter( hFilter );
        CHKSTAT_RTN("SmsSetFilter (machine filter)");
    }

    // =========================================================
    // Group filter.
    //  Only one filter of a particular type can be applied to a
    //  container. So we just record whether or not there is a
    //  group filter to be set.
    //  This group filter says that we only want the
    //  identification group for a machine.
    // =========================================================
    BOOL bSetGroupFilter = FALSE;

    stat = SmsCreateFilter( GROUP_FILTER, hConnect, &hFilter );
    CHKSTAT_RTN("SmsCreateFilter (group filter)");
    TOKEN GroupToken;
    memset( &GroupToken, 0, sizeof(TOKEN) );

    printf("Filter Group= MICROSOFT|IDENTIFICATION|1.0 [y/n]? ");
    gets(reply);
    if (reply[0] == 'y') {

        // Define a (perculating) filter: Group=Ident.
        // -------------------------------------------------
        strcpy( GroupToken.szName, "GroupClass" );
        strcpy( GroupToken.szValue, "MICROSOFT|IDENTIFICATION|1.0" );
        GroupToken.dwOp = QOP_STR_EQ;

        stat = SmsAddToken( hFilter, OP_OR, &GroupToken, 0 );
        CHKSTAT_RTN("SmsAddToken (group filter - ID group)");
        bSetGroupFilter = TRUE;
    }

    // =========================================================
    //  This one says that we only want the netcard
    //  group for a machine.
    // =========================================================
    printf("Filter Group= MICROSOFT|NETCARD|1.0 [y/n]? ");
    gets(reply);
    if (reply[0] == 'y') {

        // Define a (perculating) filter: Group=Netcard.
        // -------------------------------------------------
        strcpy( GroupToken.szName, "GroupClass" );
        strcpy( GroupToken.szValue, "MICROSOFT|NETCARD|1.0" );
        GroupToken.dwOp = QOP_STR_EQ;

        stat = SmsAddToken( hFilter, OP_OR, &GroupToken, 0 );
        CHKSTAT_RTN("SmsAddToken (group filter - Netcard group)");
        bSetGroupFilter = TRUE;
    }

    // Did user select a group filter? If so apply it to the container now.
    // ====================================================================
    if (bSetGroupFilter) {
        stat = SmsSetFilter( hContainer, hFilter );
        CHKSTAT_RTN("SmsSetFilter (group filter)");
    }
    SmsCloseFilter( hFilter );

    return(TRUE);

}  /* setSiteFilters */

// ====================================================================
// This function sets filters appropriate for a jobs container.
// It illustrates the use of jobs filters.
//
// If no job filter is selected, then all jobs will be retrieved.
// Currently, this will only test out the JobType tokens.
// The user can select one or more of the following JobType tokens in
// a job filter:
//  look at workstation install jobs
//  look at server share jobs
//  look at remove package jobs
//  look at system jobs (ie all the rest).
//
// NOTE: minimal error checking at the moment.
//
// ====================================================================
BOOL setJobFilters( HANDLE hConnect, HANDLE hContainer )
{

    SMS_STATUS stat = SMS_OK;
    HANDLE hFilter;
    BOOL done = FALSE;
    char reply[10];
    BOOL bRet = TRUE;
    BOOL bTokenSelected = FALSE;    // Has token been added to filter?
    const INT iIndex = -2;          // Token index in filter. -2 means add to end.

    printf("Select filters for jobs container.\n");
    printf("Choices are: \n\tWorkstation install 'w'\n\tServer share 's'\n\tRemove package 'r'\n\tSystem 'y'\n");

    stat = SmsCreateFilter( JOB_FILTER, hConnect, &hFilter );
    CHKSTAT_RTN("SmsCreateFilter (job filter)");

    TOKEN JobToken;
    memset( &JobToken, 0, sizeof(TOKEN) );

    while (!done) {
        printf("\nSelect one of [wsry] 'x' to complete: ");
        gets(reply);

        switch (reply[0]) {
        case 'W':
        case 'w':
            // Workstation install jobs
            strcpy( JobToken.szName, "JobType" );
            strcpy( JobToken.szValue, "Install" );
            JobToken.dwOp = QOP_STR_EQ;
            stat = SmsAddToken( hFilter, OP_OR, &JobToken, iIndex );
            CHKSTAT_RTN("SmsAddToken (job filter)");
            bTokenSelected = TRUE;
            break;

        case 'S':
        case 's':
            // Server share jobs
            strcpy( JobToken.szName, "JobType" );
            strcpy( JobToken.szValue, "Server" );
            JobToken.dwOp = QOP_STR_EQ;
            stat = SmsAddToken( hFilter, OP_OR, &JobToken, iIndex );
            CHKSTAT_RTN("SmsAddToken (job filter)");
            bTokenSelected = TRUE;
            break;

        case 'R':
        case 'r':
            // Remove package jobs
            strcpy( JobToken.szName, "JobType" );
            strcpy( JobToken.szValue, "Remove package" );
            JobToken.dwOp = QOP_STR_EQ;
            stat = SmsAddToken( hFilter, OP_OR, &JobToken, iIndex );
            CHKSTAT_RTN("SmsAddToken (job filter)");
            bTokenSelected = TRUE;
            break;

        case 'Y':
        case 'y':
            // System jobs
            strcpy( JobToken.szName, "JobType" );
            strcpy( JobToken.szValue, "System" );
            JobToken.dwOp = QOP_STR_EQ;
            stat = SmsAddToken( hFilter, OP_OR, &JobToken, iIndex );
            CHKSTAT_RTN("SmsAddToken (job filter)");
            bTokenSelected = TRUE;
            break;

        case 'X':
        case 'x':
            // finished
            done = TRUE;
            break;

        default:
            // give message again
            printf("Invalid response\n");
            printf("Choices are: \n\tWorkstation install 'w'\n\tServer share 's'\n\tRemove package 'r'\n\tSystem 'y'\n");
        }
    }

    // Only apply the filter if a token has been selected.
    // ---------------------------------------------------
    if (bTokenSelected) {
        stat = SmsSetFilter( hContainer, hFilter );
        CHKSTAT_RTN("SmsSetFilter (job filter)");
    }
    SmsCloseFilter( hFilter );

    return(bRet);
}

// ====================================================================
//
// This function sets filters appropriate for a packages container.
// It illustrates the use of packages filters.
//
// If no package filter is selected, then all packages will be retrieved.
//
// Package filter only uses the value field, values are:
//  "Workstation", "Server", "Inventory"
//
// NOTE: minimal error checking at the moment.
//
// ====================================================================
BOOL setPkgFilters( HANDLE hConnect, HANDLE hContainer )
{

    SMS_STATUS stat = SMS_OK;
    HANDLE hFilter;
    BOOL done = FALSE;
    char reply[10];
    BOOL bRet = TRUE;
    BOOL bTokenSelected = FALSE;    // Has token been added to filter?
    const INT iIndex = -2;          // Token index in filter. -2 means add to end.

    printf("Select filters for packages container.\n");
    printf("Choices are: \n\tWorkstation 'w'\n\tServer 's'\n\tInventory 'i'\n");

    stat = SmsCreateFilter( PACKAGE_FILTER, hConnect, &hFilter );
    CHKSTAT_RTN("SmsCreateFilter (package filter)");

    TOKEN PkgToken;
    memset( &PkgToken, 0, sizeof(TOKEN) );

    while (!done) {
        printf("\nSelect one of [wsi] 'x' to complete: ");
        gets(reply);

        switch (reply[0]) {
        case 'W': case 'w':
            // Workstation packages
            strcpy( PkgToken.szName, "PackageType" );
            strcpy( PkgToken.szValue, "Workstation" );
            PkgToken.dwOp = QOP_STR_EQ;
            stat = SmsAddToken( hFilter, OP_OR, &PkgToken, iIndex );
            CHKSTAT_RTN("SmsAddToken (package filter)");
            bTokenSelected = TRUE;
            break;

        case 'S': case 's':
            // Server share packages
            strcpy( PkgToken.szName, "PackageType" );
            strcpy( PkgToken.szValue, "Server" );
            PkgToken.dwOp = QOP_STR_EQ;
            stat = SmsAddToken( hFilter, OP_OR, &PkgToken, iIndex );
            CHKSTAT_RTN("SmsAddToken (package filter)");
            bTokenSelected = TRUE;
            break;

        case 'I': case 'i':
            // Inventory package.
            strcpy( PkgToken.szName, "PackageType" );
            strcpy( PkgToken.szValue, "Inventory" );
            PkgToken.dwOp = QOP_STR_EQ;
            stat = SmsAddToken( hFilter, OP_OR, &PkgToken, iIndex );
            CHKSTAT_RTN("SmsAddToken (package filter)");
            bTokenSelected = TRUE;
            break;

        case 'X':
        case 'x':
            // finished
            done = TRUE;
            break;

        default:
            // give message again
            printf("Invalid response\n");
            printf("Choices are: \n\tWorkstation 'w'\n\tServer 's'\n\tInventory 'i'\n");
        }
    }

    // Only apply the filter if a token has been selected.
    // ---------------------------------------------------
    if (bTokenSelected) {
        stat = SmsSetFilter( hContainer, hFilter );
        CHKSTAT_RTN("SmsSetFilter (package filter)");
    }

    SmsCloseFilter( hFilter );

    return(bRet);
}

// ====================================================================
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char* pszMessage
//          The user prompt to display.
//
//      char* pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//
// ====================================================================
void InputString(const char* pszMessage, char* pszResult)
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

// ====================================================================
// ConnectToDatasource
//
// Get the datasource connection information from the user and use it
// to connect to the datasource.
//
// Parameters:  None.
//
// Returns:
//      The connection handle or NULL if the connection failed.
//
// ====================================================================
HANDLE ConnectToDatasource()
{
    // Get the information we need to connect to the
    // data source from the user.
    //==============================================
    char szServer[CCH_MAXINPUT];
    char szUser[CCH_MAXINPUT];
    char szPasswd[CCH_MAXINPUT];
    char szDatabase[CCH_MAXINPUT];

    printf("\n");
    printf("**************************\n");
    printf("* Connect to data source *\n");
    printf("**************************\n");
    InputString("SQL server name", szServer);
    InputString("SQL database name", szDatabase);
    InputString("User name for SQL server", szUser);
    InputString("Password for SQL server", szPasswd);
    printf("\n");

    // Connect to a data source. SQL in this case.
    // ===========================================
    DATASOURCE dsParams;

    dsParams.sqlParams.ds          = DB_SQL;
    dsParams.sqlParams.pszServer   = szServer;
    dsParams.sqlParams.pszUserName = szUser;
    dsParams.sqlParams.pszPasswd   = szPasswd;
    dsParams.sqlParams.pszDbName   = szDatabase;
    dsParams.sqlParams.pFunc       = NULL;         // No encryption.
    dsParams.sqlParams.pszKey      = "";

    HANDLE hConnect;
    SMS_STATUS stat;
    stat = SmsDataSourceConnect( &dsParams, &hConnect);

    if (stat != SMS_OK) {
        hConnect = NULL;
        DisplaySmsError("Connect to data source failed", stat);
    }

    return( hConnect );
}

// ====================================================================
// DidRequestHelp
//
// Check the program's arguments to see if the user asked for
// the help screen to be displayed.
//
// Parameters:
//      int argc
//          The argc value from main(argc, argv)
//
//      char** argv
//          The argv value from main(argc, argv)
//
// Returns:
//      TRUE if command line parameters included a request
//      for help to be displayed.
//
// ====================================================================
BOOL DidRequestHelp( int argc, char **argv )
{
    if (argc == 2  && (strcmp(argv[1], "-help") == 0)) {
        return(TRUE);
    }
    else {
        return(FALSE);
    }
}

// ====================================================================
// DisplayHelp
//
// This function displays the samples help screen.
//
// Parameters:
//      None
//
// Returns:
//      Nothing.
//
// ====================================================================
void DisplayHelp()
{
    printf("\n\n");
    printf("********************************************************\n");
    printf("* testall.exe:                                         *\n");
    printf("*                                                      *\n");
    printf("* This is a sample program for the SMS SDK. It can be  *\n");
    printf("* used to view the contents of site, job, and package  *\n");
    printf("* folders in the SMS database.                         *\n");
    printf("*                                                      *\n");
    printf("* Syntax:                                              *\n");
    printf("*     testall.exe [-help]                              *\n");
    printf("*                                                      *\n");
    printf("* Switches:                                            *\n");
    printf("*     -help       Display this help screen.            *\n");
    printf("*                                                      *\n");
    printf("********************************************************\n");
    printf("\n");
}

// ====================================================================
// DisplayGreeting
//
// Display the initial greeting banner.
//
// Parameters:
//     None.
//
// Returns:
//     Nothing.
//
// ====================================================================
void DisplayGreeting()
{
    // For this sample, the greeting is identical to the help screen.
    //===============================================================
    DisplayHelp();
}

/* EOF: testall.cpp */


README.TXT

Sample: tree
============

DESCRIPTION:

This program illustrates the self-describing nature of the objects in the
API set. Using the SmsEnum APIs it stores lists of all containers, folders,
and filters known to the API engine.

It then scans each container in the container list, displaying first of all
information about the container, and then information about what is
contained within the container. The following information is displayed:

* the container tag and type
* the number of filters activated by this container
* a description of each of these filters

* the number of folder types that can be container in the container
* a description of each of these folders.

The information displayed for a filter is:
* the filter's tag and type
* the names of the fields used by the filter's tokens
  (these will be used as the labels for edit controls in a GUI program).

The information displayed for a folder is:
* the folder's tag and type
* filter details (as in container)
* the number of scalars along with their names, type, and access mode
* a recursive display of any sub-folders that can be contained within this
  folder.

API USAGE:
    SmsAPIVer
    SmsEnumContainers
    SmsEnumFilters
    SmsEnumFolders

Psuedo-code:
The following is a psuedo-code for this program.

begin
    Call the SmsEnum APIs to retrieve lists of
    containers, folders, and filters.

    for each container
    begin

        display container tag and type

        display count of filters activated by this container

        for each filter that can be activated here
        begin
            display filter
        end

        for each folder that can be contained here
        begin
            display folder
        end

    end

end

display filter
begin
    display filter tag and type
    display its properties (ie fields used in tokens)
end

display folder
begin
    locate folder in list returned by SmsEnumFolders
    display tag and type
    check if already displayed in this container hierarchy
    display number of filters activated by this folder
    display filters (as above)
    display scalars
    recursively display sub-folders
end

display scalars
begin
    display number of scalars

    for each scalar
        display its name, type, and access mode
    end
end


TREE.CPP

// ====================================================================
//
//  File: tree.cpp
//
//  Copyright (c) 1994, 1995 Microsoft Corp.
//
//  Author:
//      Jonathan Shuval     Microsoft Corp.
//
//
//  This sample builds a tree of the container/folder hierarchy and
//  displays it to the console.
//
//  The information that is displayed is as follows:
//  For each container we display the container's type and tag followed
//  by a list of filters (possibly empty). This lists filters that are
//  acted upon directly at that level.
//  Following the filter list is a folder list. This gives the folder's
//  type and tag, its filter list, and then a list of scalars for the
//  folder.
//  The scalar display shows the scalar name, it's type, and its access
//  mode.
//
//  See the readme.txt file in this directory for more information.
//
// ====================================================================

// ====================================================================
//
//      Includes
//
// ====================================================================
#include <afx.h>
#include <smsapi.h>

// ====================================================================
//
//      Defines.
//
// ====================================================================
#define CCH_MAXSTRING 256

// ====================================================================
//
//      Prototypes.
//
// ====================================================================

// Process a container.
// -------------------------------------------------------------
void doContainer( FOLDER_INFO *pContainer );

// Process a folder.
// -------------------------------------------------------------
void doFolder( DWORD folderType, int indent, DWORD dwConType );

// Adds a container to the container list.
// -------------------------------------------------------------
void AddContainer( DWORD dwConType );

// Given a filter type display its properties.
// -------------------------------------------------------------
void doFilter( DWORD dwFilterType, int indent );

// Locates filter of specified type from the global filter list.
// -------------------------------------------------------------
FILTER_INFO *FindFilter( DWORD filterType );

// Mark folder as already enumerated within this container graph.
// -------------------------------------------------------------
void Enumerated( DWORD dwConType, DWORD folderType );

// Call this to find out if this foldertype already been
// enumerated within this container graph.
// -------------------------------------------------------------
BOOL IsEnumerated(DWORD dwConType, DWORD folderType);

// Prompt the user for input and return the reply.
// -------------------------------------------------------------
void InputString( const char *pszMessage, char *pszResult );

// Display the help message.
// -------------------------------------------------------------
void DisplayHelp();

// Display the greeting.
// -------------------------------------------------------------
void DisplayGreeting();

// Check to see if there was a request for help
// on the command line.
// -------------------------------------------------------------
BOOL DidRequestHelp( int argc, char **argv );

// ====================================================================
//
//      Globals.
//
// ====================================================================
FOLDER_INFO **gpFolders = NULL;     // List of FOLDER_INFOs.
DWORD gctFolders = 0;               // Count of them.

FILTER_INFO *gpFilters = NULL;      // List of FILTER_INFOs.
DWORD gctFilters = 0;               // Count of them.

//
// This struct is used to hold information about what folders
// have already been enumerated for a particular container.
//
typedef struct tagContainerEntry {
    DWORD dwConType;                // Container's type.
    CDWordArray aEnumFolders;       // Folders already enumerated.
} ContainerEntry;

CObArray ContainerList;             // List of containers (contains list
                                    // of ContainerEntry structs.)

// ====================================================================
//
//      Start here.
//
// ====================================================================
void main( int argc, char** argv )
{
    // Check to see if this is a request to display the help
    // screen.  If so, display it. Otherwise, display a greeting
    // banner.
    //=========================================================
    if (DidRequestHelp( argc, argv )) {
        DisplayHelp();
        return;
    }
    else {
        DisplayGreeting();
    }

    // Data.
    // =====

    DWORD ctContainers;
    FOLDER_INFO **pContainers = NULL;
    FOLDER_INFO *pCont = NULL;

    // Enumerate containers.
    // =====================

        // Get number of containers.
    SmsEnumContainers( NULL, &ctContainers );

        // Allocate memory and get the containers.
    pContainers = new FOLDER_INFO *[ctContainers];
    SmsEnumContainers( pContainers, &ctContainers );

    // Enumerate all filters.
    // ======================
        // Get number of filters.
    SmsEnumFilters(NULL, &gctFilters);

        // Allocate memory and get the filters.
    gpFilters = new FILTER_INFO[gctFilters];
    SmsEnumFilters( gpFilters, &gctFilters );

    // Enumerate all folders.
    // ======================
        // Get number of folders.
    SmsEnumFolders( NULL, &gctFolders );

        // Allocate memory and get the folders.
    gpFolders = new FOLDER_INFO *[gctFolders];
    SmsEnumFolders( gpFolders, &gctFolders );

    // Loop through each container.
    // ============================

    for (DWORD i = 0; i < ctContainers; i++) {

        // Process the container
        doContainer( pContainers[i] );
    }

}

// ====================================================================
//
//  doContainer -- process container.
//
//  This is called from main. We print out information for this
//  container, and then process each folder within the container.
//
// ====================================================================

void doContainer( FOLDER_INFO *pContainer )
{
    DWORD dwI;

    // Add this container to our container list.
    // =========================================
    AddContainer( pContainer->dwTag );

    // We are now looking at the FOLDER_INFO for a container.
    // We want to print out the count and list of filters,
    // the folder type count, and then process each folder.
    // ======================================================
    printf("Container: %s [%d]\n", pContainer->pszTag, pContainer->dwTag);

    // Print out list of filters.
    // =============================
    printf("%d filter(s)\n", pContainer->ctFilters);
    DWORD *pFilters = pContainer->pFilterTags;

    for (dwI = 0; dwI < pContainer->ctFilters; dwI++) {
        doFilter( pFilters[dwI], 1 );
    }

    // Print out list of folders.
    // ==========================
    printf("%d folder-type(s)\n", pContainer->ctFolders);

    DWORD *pFolderIDs = pContainer->pFolderTags;
    for (dwI = 0; dwI < pContainer->ctFolders; dwI++) {

        // Now cause the folder to print himself out.
        // ------------------------------------------
        doFolder( pFolderIDs[dwI], 1, pContainer->dwTag );
    }

    // No scalars for container.
    // =========================

    printf("\n\n");
}

// ====================================================================
//
//  FindFilter -- locate filter of specified type from the list of
//  filter descriptors returned by the API SmsEnumFilters.
//
//  Return a pointer to the FILTER_INFO for the filter.
//
//  Called from doFilter.
//
// ====================================================================

FILTER_INFO *FindFilter( DWORD filterType )
{
    FILTER_INFO *pFI;
    for (DWORD dwI = 0; dwI < gctFilters; dwI++) {
        pFI = &gpFilters[dwI];
        if (pFI->filterType == filterType) {
            return(pFI);
        }
    }

    return(NULL);
}

// ====================================================================
//
//  dumpScalar -- given a SCALAR_INFO, display the scalar.
//
//  We display the scalar's name, type, and access mode.
//
//  Params:
//      SCALAR_INFO                 Scalar to display.
//      indent                      indent level
//
//
// ====================================================================

void dumpScalar( SCALAR_INFO *pSc, int indent )
{
    // Indentation.
    // ============
    char szIndent[100];
    memset( szIndent, '\0', 100 );
    for (int iLevel = 0; iLevel < indent; iLevel++) {
        strcat( szIndent, "\t");
    }

    char *pszType, *pszAccess;

    // Determine scalar type, display accordingly.
    // ===========================================
    switch (pSc->scType) {
    case SCALAR_STRING:
        pszType = "string";     break;

    case SCALAR_INT:
        pszType = "integer";    break;

    case SCALAR_TIME:
        pszType = "time";       break;

    case SCALAR_BINARY:
        pszType = "binary";     break;
    }

    // Determine access mode, display accordingly.
    // ===========================================
    switch (pSc->fAccess) {
    case ACCESS_READ:
        pszAccess = "read";     break;

    case ACCESS_CREATE:
        pszAccess = "create";   break;

    case ACCESS_MODIFY:
        pszAccess = "modify";   break;

    case ACCESS_DELETE:
        pszAccess = "delete";   break;
    }

    printf("%s%s (%s, %s)\n", szIndent, pSc->szName, pszType, pszAccess);

}

// ====================================================================
//
//  doScalars -- display a folder's scalar.
//
//  Loop through the list of SCALAR structures that are embedded in the
//  FOLDER_INFO, and display them.
//
//  The 'indent' parameter says how far to indent the resulting display
//  string so it looks almost presentable.
//
//  Params:
//      FOLDER_INFO *pFolder        The structure describing the folder
//                                  under consideration.
//      int indent                  Indent level.
//
//  Returns:
//      Nothing.
//
// ====================================================================

void doScalars( FOLDER_INFO *pFolder, int indent )
{
    SCALAR_INFO *pScalar;

    for (DWORD dwI = 0; dwI < pFolder->ctScalars; dwI++) {
        pScalar = &pFolder->pScalars[dwI];
        dumpScalar( pScalar, indent );
    }
}

// ====================================================================
//
//  doFolder -- Given a folder type print out this folder's information.
//
//  The folder has already been retrieved in SmsEnumFolders, we just
//  access it from there.
//
//  Params:
//      folderType                  Folder type we're interested in.
//      dwConType                   Container's type.
//
//  Returns:
//      Nothing.
//
// ====================================================================

void doFolder( DWORD folderType, int indent, DWORD dwConType )
{

    FOLDER_INFO *pFolder;
    DWORD dwI;
    BOOL bFound = FALSE;

    // Indentation.
    // ============
    char szIndent[100];
    memset( szIndent, '\0', 100 );
    for (int iLevel = 0; iLevel < indent; iLevel++) {
        strcat( szIndent, "\t");
    }

    // Loop through the retrieved folders looking for
    // the one with a tag for folderType.
    // ==============================================
    for (dwI = 0; dwI < gctFolders; dwI++) {
        pFolder = gpFolders[dwI];
        if (pFolder->dwTag == folderType) {
            bFound = TRUE;
            break;
        }
    }

    if (!bFound) {
        printf("<<< Error: couldn't locate folder %d >>>\n", folderType);
        return;
    }

    // Display folder type and tag and filter count.
    // =============================================
    printf("%s********** %s folder [%d] **********\n",
        szIndent,    pFolder->pszTag, pFolder->dwTag);

    // Check to see if it's already been displayed.
    // We only want to do this in the context of the current container.
    // ----------------------------------------------------------------
    if (IsEnumerated(dwConType, folderType)) {
        printf("%sFolder already enumerated in this container\n", szIndent);
        printf("%s********** End of %s folder **********\n\n",
                    szIndent, pFolder->pszTag );
        return;
    }

    printf("%s%d filter(s)\n", szIndent, pFolder->ctFilters);

    // Print out list of filter types.
    // ===============================
    DWORD *pFilters = pFolder->pFilterTags;

    for (dwI = 0; dwI < pFolder->ctFilters; dwI++) {
        doFilter( pFilters[dwI], indent );
    }

    // Print out number of scalars, then display them.
    // ===============================================
    printf("%s%d scalars\n", szIndent, pFolder->ctScalars);
    doScalars( pFolder, indent );

    // Prepare to display sub-folders.
    // ===============================
    printf("%s%d folder-type(s)\n", szIndent, pFolder->ctFolders);

    // Before processing sub-folder types mark this folder as already
    // enumerated in this container. Prevents infinite recursion.
    Enumerated(dwConType, folderType);

    // Print out list of folder types.
    DWORD *pFolderIDs = pFolder->pFolderTags;
    for (dwI = 0; dwI < pFolder->ctFolders; dwI++) {
        // Now cause the folder to print himself out.
        doFolder( pFolderIDs[dwI], indent+1, dwConType );

    }

    printf("%s********** End of %s folder **********\n\n",
                szIndent, pFolder->pszTag );

}

// ====================================================================
//
//  doFilter -- given a filter type display its properties.
//
//  Called from doContainer and doFolder.
//
// ====================================================================

void doFilter( DWORD dwFilterType, int indent )
{

    FILTER_INFO *pFilter = FindFilter( dwFilterType );

    if (!pFilter) {
        printf("<<< Error: couldn't locate filter %d >>>\n", dwFilterType);
        return;
    }

    // Indentation.
    // ============
    char szIndent[100];
    memset( szIndent, '\0', 100 );
    for (int iLevel = 0; iLevel < indent; iLevel++) {
        strcat( szIndent, "\t");
    }

    // Display filter type and tag.
    // ============================
    printf("%s%s (%d)\n", szIndent, pFilter->szTag, dwFilterType);

    // Display filter properties.
    // ==========================
    char *ppszField[10];            // Store pointers here.
    DWORD ctFields = 0;             // Number of pointers.

    char szBuff[256];               // Build up output string here.
    sprintf(szBuff, "%s\t[", szIndent); // Additional level of indent.

    if (*(pFilter->szName)) {
        ppszField[ctFields++] = "Token.szName = ";
        ppszField[ctFields++] = pFilter->szName;
    }

    if (*(pFilter->szValue)) {
        ppszField[ctFields++] = "Token.szValue = ";
        ppszField[ctFields++] = pFilter->szValue;
    }

    if (*(pFilter->szOperator)) {
        ppszField[ctFields++] = "Token.szOperator = ";
        ppszField[ctFields++] = pFilter->szOperator;
    }

    if (*(pFilter->szArchitecture)) {
        ppszField[ctFields++] = "Token.szArchitecture = ";
        ppszField[ctFields++] = pFilter->szArchitecture;
    }

    if (*(pFilter->szGroupClass)) {
        ppszField[ctFields++] = "Token.szGroupClass = ";
        ppszField[ctFields++] = pFilter->szGroupClass;
    }

    if (*(pFilter->szAttributeName)) {
        ppszField[ctFields++] = "Token.szAttributeName = ";
        ppszField[ctFields++] = pFilter->szAttributeName;
    }

    for (DWORD dwI = 0; dwI < ctFields; dwI += 2) {
        printf("\t%s%s\"%s\"\n", szIndent, ppszField[dwI], ppszField[dwI+1]);
    }

}

// ====================================================================
//
//  AddContainer -- adds a container to the container list.
//
// This is only for detecting folders that have already been
// enumerated. Nothing more.
// Trouble is we duplicate!
//
// ====================================================================

void AddContainer( DWORD dwConType )
{
    ContainerEntry *pCEntry;
    pCEntry = new ContainerEntry;
    pCEntry->dwConType = dwConType;

    ContainerList.Add((CObject *)pCEntry );
}

// ====================================================================
//
//  Enumerated -- Mark this folder as already enumerated within this
//  container graph.
//
// ====================================================================

void Enumerated(DWORD dwConType, DWORD folderType)
{
    // Locate the container.
    BOOL bFound = FALSE;
    DWORD dwI;                  // loop index.
    ContainerEntry *pCEntry;

    // We keep a global ContainerList (CObArray) which contains
    // a list of container entries (above).

    // Locate container entry.
    // =======================
    for (dwI = 0; dwI < (DWORD)ContainerList.GetSize(); dwI++) {
        pCEntry = (ContainerEntry *)ContainerList[dwI];
        if (pCEntry->dwConType == dwConType) {
            bFound = TRUE;
            break;
        }
    }

    if (!bFound) {
        printf("<<< Error: container tag %d not found in list >>>\n", dwConType);
        return;
    }

    // Add this folder to our list of enumerated folders.
    // ==================================================
    pCEntry->aEnumFolders.Add( folderType );

}

// ====================================================================
//
//  IsEnumerated -- Has this foldertype already been enumerated within
//  this container graph?
//
// ====================================================================

BOOL IsEnumerated(DWORD dwConType, DWORD folderType)
{
    BOOL bFound = FALSE;
    DWORD dwI;                  // loop index.
    ContainerEntry *pCEntry;

    // We keep a global ContainerList (CObArray) which contains
    // a list of container entries (above).

    // Locate container entry.
    // =======================
    for (dwI = 0; dwI < (DWORD)ContainerList.GetSize(); dwI++) {
        pCEntry = (ContainerEntry *)ContainerList[dwI];
        if (pCEntry->dwConType == dwConType) {
            bFound = TRUE;
            break;
        }
    }

    if (!bFound) {
        printf("<<< Error: container tag %d not found in list >>>\n", dwConType);
        return(FALSE);
    }

    // Now look through the array of enumerated folders.
    // =================================================
    for (dwI = 0; dwI < (DWORD)pCEntry->aEnumFolders.GetSize(); dwI++) {
        if (pCEntry->aEnumFolders[dwI] == folderType) {

            // Found it. Folder already enumerated.
            // ------------------------------------
            return(TRUE);
        }
    }

    // Folder hasn't been enumerated.
    // ------------------------------
    return(FALSE);
}

// ====================================================================
// InputString
//
// Prompt the user to input a string and return the string in the
// specified buffer.
//
// Parameters:
//      const char* pszMessage
//          The user prompt to display.
//
//      char* pszResult
//          Pointer to the buffer where the user's input will be returned.
//
// Returns;
//      The user's input is returned via the given buffer.
//
// ====================================================================
void InputString( const char *pszMessage, char *pszResult)
{
    printf("%s: ", pszMessage);
    gets(pszResult);
}

// ====================================================================
// DidRequestHelp
//
// Check the program's arguments to see if the user asked for
// the help screen to be displayed.
//
// Parameters:
//      int argc
//          The argc value from main(argc, argv)
//
//      char** argv
//          The argv value from main(argc, argv)
//
// Returns:
//      TRUE if command line parameters included a request
//      for help to be displayed.
//
// ====================================================================
BOOL DidRequestHelp(int argc, char** argv)
{
    if (argc == 2  && (strcmp(argv[1], "-help") == 0)) {
        return(TRUE);
    }
    else {
        return(FALSE);
    }
}

// ====================================================================
// DisplayHelp
//
// This function displays the samples help screen.
//
// Parameters:
//      None
//
// Returns:
//      Nothing.
//
// ====================================================================
void DisplayHelp()
{
    // Version information.
    // ====================
    char *pVer;
    SmsAPIVer( &pVer );
    printf("\n%s\n\n", pVer);

    // Description.
    // ============

    printf("********************************************************************\n");
    printf("* tree.exe:                                                        *\n");
    printf("*                                                                  *\n");
    printf("* This is a sample program for the SMS SDK.  It shows how the      *\n");
    printf("* container hierarchy can be displayed.                            *\n");
    printf("*                                                                  *\n");
    printf("* For each container in SMS, the following details are displayed:  *\n");
    printf("*     the container's tag and type                                 *\n");
    printf("*     any filters that are acted upon directly by the container    *\n");
    printf("*                                                                  *\n");
    printf("* Following this is a display of all folders within that container.*\n");
    printf("*                                                                  *\n");
    printf("* For folders the following is displayed:                          *\n");
    printf("*     the folder's type and tag                                    *\n");
    printf("*     any filters that are acted upon by the folder                *\n");
    printf("*     a list of the folder's scalars                               *\n");
    printf("* Note that folder enumeration is recursive, that is, some folders *\n");
    printf("* contain sub-folders.                                             *\n");
    printf("*                                                                  *\n");
    printf("* For filters a list of the filter's attributes are displayed.     *\n");
    printf("*                                                                  *\n");
    printf("* Syntax:                                                          *\n");
    printf("*     tree.exe [-help]                                             *\n");
    printf("*                                                                  *\n");
    printf("* Switches:                                                        *\n");
    printf("*     -help       Display this help screen.                        *\n");
    printf("*                                                                  *\n");
    printf("********************************************************************\n");
    printf("\n");
}

// ====================================================================
// DisplayGreeting
//
// Display the initial greeting banner.
//
// Parameters:
//     None.
//
// Returns:
//     Nothing.
//
// ====================================================================
void DisplayGreeting()
{
    // For this sample, the greeting is identical to the help screen.
    //===============================================================
    DisplayHelp();

    // Pause so the description doesn't fly off the screen.
    // ====================================================
    char szReply[CCH_MAXSTRING];
    InputString("Press ENTER to continue", szReply);
    printf("\n");
}

/* EOF: tree.cpp */


RESOURCE.H

//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by SMSVIEW.RC
//
#define IDI_ICON1                       108
#define IDI_SMSVIEW                     108
#define IDC_SERVERNAME                  1000
#define IDC_LOGINID                     1001
#define IDC_PASSWORD                    1002
#define IDC_DBNAME                      1003
#define IDC_DROPONLY                    1004
#define IDC_STATIC                      -1

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        111
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1005
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SMSVIEW.C

/*************************************************************************

SMSVIEW.EXE - This Module creates Views based on the current Group Map in
SMS

Copyright (c), 1994 by Microsoft Corp.

Modified: 7/11/95 a-cvadai  Added error message messageboxes.

*************************************************************************/

#define DBNTWIN32       // must identify operating system environment
#include "windows.h"

#include <ctl3d.h>

#include <sqlfront.h>
#include <sqldb.h>     // DB-LIB header file (should always be included)
#include <stdio.h>
#include <winnt.h>
#include "smsview.h"
#include "smsviews.h"
#include "resource.h"

typedef int (*PFNDLL)();
struct DBLOGIN {
 char ServerName[MAX_NAME];
 char LoginId   [MAX_NAME];
 char Password  [MAX_NAME];
 char DBName    [MAX_NAME];
 int DropOnly;
 } dbLoginInfo;
int CmdComplete;

void ErrorMsg(int ,int);

int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
HINSTANCE hInstance;                 /* current instance         */
HINSTANCE hPrevInstance;                 /* previous instance        */
LPSTR lpCmdLine;                 /* command line             */
int nCmdShow;                    /* show-window type (open/icon) */
{

    PFNDLL GenViews = NULL;
    static HANDLE hLib = NULL;

    // Forward declarations of the error handler and message handler.

    if (!hPrevInstance)         /* Has application been initialized? */
      if (!SqlTestInit(hInstance))
        return (0);             /* Exits if unable to initialize     */

    hInst = hInstance;          /* Saves the current instance        */

    Ctl3dRegister( hInst );
    Ctl3dAutoSubclass( hInst );

    CmdComplete = CheckCommandLine(lpCmdLine);
    if ( ! GetConnectionInfo(CmdComplete))
     return(0);

   Ctl3dUnregister( hInst );

   return(1);
}

 int CheckCommandLine(char * lpCmdLine)
 {
  int Len;
  char * pSlash1 = lpCmdLine;

  memset (&dbLoginInfo,'\0',sizeof(dbLoginInfo));

  if ( strlen(lpCmdLine) == 0)
   return(0);
  while ( (pSlash1 = (strchr(pSlash1,'/'))) ) { // Find First Param

       // Find Next Param
  //  pSlash2 = (strchr(pSlash1+1,'/');

   if ( (Len = (strchr(pSlash1,' ') - pSlash1)) < 0 )
     Len = MAX_NAME;
   if ( Len > MAX_NAME){
    ErrorMsg(IDS_ERR_CMD_TOO_LONG,DISP);
    return(0);
   }

    Len-=(strchr(pSlash1,':')-pSlash1)+1;    //Subtract the /?: off the string Length
    if ( Len < 0)  //Invalid switch
     return(0);

    pSlash1++; //Point to First char of the keyword
    switch (toupper(pSlash1[0])) {
     case  SERVERNAMEPARAM :
           strncpy(dbLoginInfo.ServerName,strchr(pSlash1,':')+1,Len);
           break;
     case  LOGINIDPARAM :
           strncpy(dbLoginInfo.LoginId,strchr(pSlash1,':')+1,Len);
           break;
     case  PASSWORDPARAM:
           strncpy(dbLoginInfo.Password,strchr(pSlash1,':')+1,Len);
           break;
     case  DBNAMEPARAM:
           strncpy(dbLoginInfo.DBName,strchr(pSlash1,':')+1,Len);
           break;
    }
  } // End While

   return(1);
 }
//GetConnectionInfo
GetConnectionInfo(CmdComplete)
 int CmdComplete;
{
    DWORD Er;
    if(  !CmdComplete ) {
         if( DialogBoxParam(hInst, TEXT("IDD_ConnectionDlg"), NULL,
                              (DLGPROC)dbConnect, 2 )== -1){
                    Er = GetLastError();
                    return(0);
         }
     }
     else
        if ( SetupDbConnection() == FAIL)
             return(FAIL);

     return(1);
}
/***************************************************************************\
* dbConnect()
*
* GetConnection Details from DLG Box
*
* History:
* 6/5/92  2am
*   Created.
\***************************************************************************/
LRESULT WINAPI dbConnect(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{

    static char ServerName[MAX_NAME]  ;
    static char * lpszHold = ServerName;
    HCURSOR CurArrow,CurWait;

    switch (message){
      case WM_INITDIALOG:{

             // Set Default Server Name
        SendDlgItemMessage( hDlg, IDC_SERVERNAME, MAX_NAME,
                            (WPARAM)MAX_NAME, (LPARAM)0);

        if( SendDlgItemMessage( hDlg, IDC_SERVERNAME, WM_SETTEXT, 0,
                                (LPARAM)lpszHold) == LB_ERR ){
            //ErrorMsg(TEXT("Rename: Add Source String failure."),DISP);
            return(1);
        }
        //Set default Login ID
        lstrcpy(lpszHold,"sa");

         SendDlgItemMessage( hDlg, IDC_LOGINID, MAX_NAME,
                            (WPARAM)MAX_NAME, (LPARAM)0);

        if( SendDlgItemMessage( hDlg, IDC_LOGINID, WM_SETTEXT, 0,
                                (LPARAM)lpszHold) == LB_ERR ){
            //ErrorMsg(TEXT("Rename: Add Source String failure."),DISP);
            return(1);
        }
         // Set deafult Data Base Name
         lstrcpy (lpszHold,"SMS");

         SendDlgItemMessage( hDlg, IDC_DBNAME, MAX_NAME,
                            (WPARAM)MAX_NAME, (LPARAM)0);

        if( SendDlgItemMessage( hDlg, IDC_DBNAME ,WM_SETTEXT, 0,
                                (LPARAM)lpszHold) == LB_ERR ){
            //ErrorMsg(TEXT("Rename: Add Source String failure."),DISP);
            return(1);
        }
        //Set Deafult password length
         SendDlgItemMessage( hDlg, IDC_PASSWORD, MAX_NAME,
                            (WPARAM)MAX_NAME, (LPARAM)0);

        return(1);
      }
      case WM_COMMAND:{
        switch(LOWORD(wParam)){
            case IDOK:{

                if( SendDlgItemMessage( hDlg, IDC_SERVERNAME, WM_GETTEXT,
                                        MAX_NAME,
                                        (LPARAM)lpszHold) == LB_ERR ){
                    //ErrorMsg(TEXT("Rename: Get Source String failure."),DISP);
                    EndDialog(hDlg, wParam);
                    return(1);
                }
                lstrcpy(dbLoginInfo.ServerName,lpszHold);
                if( SendDlgItemMessage( hDlg, IDC_LOGINID, WM_GETTEXT,
                                        MAX_NAME,
                                        (LPARAM)lpszHold) == LB_ERR ){
                    //ErrorMsg(TEXT("Rename: Get Source String failure."),DISP);
                    EndDialog(hDlg, wParam);
                    return(1);
                }
                lstrcpy(dbLoginInfo.LoginId,lpszHold);
                if( SendDlgItemMessage( hDlg, IDC_DBNAME, WM_GETTEXT,
                                        MAX_NAME,
                                        (LPARAM)lpszHold) == LB_ERR ){
                    //ErrorMsg(TEXT("Rename: Get Source String failure."),DISP);
                    EndDialog(hDlg, wParam);
                    return(1);
                }
                lstrcpy(dbLoginInfo.DBName,lpszHold);
                if( SendDlgItemMessage( hDlg, IDC_PASSWORD, WM_GETTEXT,
                                        MAX_NAME,
                                        (LPARAM)lpszHold) == LB_ERR ){
                    //ErrorMsg(TEXT("Rename: Get Source String failure."),DISP);
                    EndDialog(hDlg, wParam);
                    return(1);
                }
                lstrcpy(dbLoginInfo.Password,lpszHold);

                if ( (dbLoginInfo.DropOnly = SendDlgItemMessage( hDlg, IDC_DROPONLY, BM_GETCHECK,
                                        MAX_NAME,
                                        (LPARAM)0)) == LB_ERR ){
                    //ErrorMsg(TEXT("Rename: Get Source String failure."),DISP);
                    EndDialog(hDlg, wParam);
                    return(1);
                }
                CurWait = LoadCursor(NULL, IDC_WAIT);
                CurArrow = SetCursor(CurWait);
                if ( SetupDbConnection() == FAIL){
                    SetCursor(CurArrow);
                 break;
                 }
              SetCursor(CurArrow);
            }
            case IDCANCEL:{

                EndDialog(hDlg, wParam);
                return(1);
            }
        }
        return(1);
      } // WM_COMMAND
    }

    return(0);
}

/*********************************************************************
* Connect to the DataBase with Indo supplied                         *
**********************************************************************/

int SetupDbConnection()
{
    DBPROCESS *dbproc;      // allocate a DB-LIB process structure
    DBPROCESS *dbprocView;  // Allocate a proc to create the Views
    LOGINREC  *login;       // allocate a DB-LIB login structure
    static HANDLE hLib = NULL;
    PFNDLL GenViews = NULL;
    char CreateGroupName[MAX_COLUMN_NAME];
    int RetCode;

    CreateGroupName[0] = '\0';

    if (dbinit() == (char *)NULL)
    {
        ErrorMsg(IDS_ERR_COM_NOT_LOADED, DISP);
        return(1);
    }

    // Install the user-supplied error-handling and message-handling
    // routines. They are defined at the bottom of this source file.

    dberrhandle((void *)err_handler);
    dbmsghandle((void *)err_handler);

    login = dblogin();                   // get login record from DB-LIB
    DBSETLUSER (login, (char *)dbLoginInfo.LoginId); // set the username
    DBSETLAPP (login, (char *)"smsview");              // set the application name
    DBSETLPWD (login, (char *)dbLoginInfo.Password); // set the SQL Server password

    // Now attempt to create and initialize a DBPROCESS structure
    if ((dbproc = dbopen (login, dbLoginInfo.ServerName)) == NULL)
        return (FAIL);

    // and another one for The Create View Process
    if ((dbprocView = dbopen (login, dbLoginInfo.ServerName)) == NULL)
        return (FAIL);

    if ( dbuse (dbproc, dbLoginInfo.DBName) == FAIL) {     // use the "SMS" database
        dbexit();
      return(FAIL);
    }
    if ( dbuse (dbprocView,dbLoginInfo.DBName) == FAIL) {
        dbexit();
      return(FAIL);
    }

    if (!(hLib = LoadLibrary ("VIEWDLL.DLL")))
            ErrorMsg(IDS_ERR_LOAD_VIEWDLL, DISP);
    else {
       GenViews = (PFNDLL) GetProcAddress (hLib,"GenViews");
       RetCode = (GenViews)(dbproc,dbprocView,CreateGroupName,dbLoginInfo.DropOnly,CmdComplete);
       GenResults(RetCode);
     }
    // Close the connection and exit
    dbexit();

   return(1);
  }
//GenResults

void GenResults(int RetCode)
{
    char szMsg[MAX_PATH];
    char szMsgTitle[32];

    LoadString(hInst, IDS_MSC_CREATE_VIEWS, szMsgTitle, 32);

    if (RetCode == SUCCEED) {
        if ( !dbLoginInfo.DropOnly) {
            LoadString(hInst, IDS_MSG_CREATE_VIEW, szMsg, MAX_PATH);
        }
        else {
            LoadString(hInst, IDS_MSG_DROP_VIEW, szMsg, MAX_PATH);
        }
    }
    else {
        if ( !dbLoginInfo.DropOnly) {
            LoadString(hInst, IDS_ERR_CREATE_VIEW, szMsg, MAX_PATH);
        }
        else {
            LoadString(hInst, IDS_ERR_DROP_VIEW, szMsg, MAX_PATH);
        }
    }

    MessageBox(NULL, szMsg, szMsgTitle, MB_ICONINFORMATION );

}

//ErrorMsg
void ErrorMsg(int iString, int Disp)
{
#if 0
  static HANDLE hEventLog = NULL;
  char *pErrorStrings[1];

  pErrorStrings[0] = errorString;

  if (hEventLog == (HANDLE) NULL)
     hEventLog = RegisterEventSource((HANDLE)NULL,"SMS View");

  if (! CmdComplete && Disp)  // is it run with command line parameters
   MessageBox( NULL, errorString, "Data Base Error", MB_ICONSTOP );

  if ( hEventLog != NULL)
   ReportEvent(hEventLog, EVENTLOG_INFORMATION_TYPE, (WORD)0, (DWORD) 0, NULL
   ,1,0, pErrorStrings, NULL);
#endif

    char    szString[MAX_PATH];
    char    szMsgTitle[32];

    if (Disp == DISP) {
        LoadString(hInst, iString, szString, MAX_PATH);
        LoadString(hInst, IDS_MSC_DBERROR, szMsgTitle, 32);
        MessageBox(NULL, szString, szMsgTitle, MB_ICONSTOP);
    }
  }

/****************************************************************************

    FUNCTION: SqlTestInit(HANDLE)

    PURPOSE: Initializes window data and registers window class

    COMMENTS:

    Sets up a structure to register the window class.  Structure includes
    such information as what function will process messages, what cursor
    and icon to use, etc.

****************************************************************************/

BOOL SqlTestInit(hInstance)
HANDLE hInstance;                  /* current instance       */
{
    HANDLE hMemory;                /* handle to allocated memory */
    PWNDCLASS pWndClass;               /* structure pointer      */
    BOOL bSuccess;                 /* RegisterClass() result     */

    hMemory = LocalAlloc(LPTR, sizeof(WNDCLASS));
    pWndClass = (PWNDCLASS)hMemory;

    pWndClass->style = 0; /*CS_HREDRAW | CS_VREDRAW; */
    pWndClass->lpfnWndProc = (WNDPROC)NULL;
    pWndClass->hInstance = hInstance;
    pWndClass->hIcon = LoadIcon(hInstance,MAKEINTRESOURCE( IDI_SMSVIEW));
    pWndClass->hCursor = LoadCursor(NULL, IDC_ARROW);
    pWndClass->hbrBackground = GetStockObject(WHITE_BRUSH);
    pWndClass->lpszMenuName = (LPSTR)"SMS Views";
    pWndClass->lpszClassName = (LPSTR)"SMS Views";

    bSuccess = RegisterClass(pWndClass);

    LocalFree(hMemory);                 /* Returns it to NT */
    return (bSuccess);       /* Returns result of registering the window */
}

//
int err_handler(DBPROCESS * dbproc, int severity, int dberr, int oserr, char * dberrstr, char * oserrstr)
{
    char    szString[512];
    char    szMsgTitle[32];
    int     iString;

    // Ignore any 'Changed context' errors. (#5701 dec)

    if (severity == 0x00001645) {
        return 0;
    }

    LoadString(hInst, IDS_ERR_SQL_ERROR, szString, MAX_PATH);
    LoadString(hInst, IDS_MSC_DBERROR, szMsgTitle, 32);

    iString = sizeof(szString) - strlen(szString);
    if (dberrstr != NULL) {
        strncpy(szString, dberrstr, iString);
    }
    else if (oserr != DBNOERR && oserrstr != NULL) {
        strncpy(szString, oserrstr, iString);
    }
    szString[sizeof(szString)-1] = 0;

    if (strstr(szString, "General SQL Server") == NULL) {
        MessageBox(NULL, szString, szMsgTitle, MB_ICONSTOP);
    }

    return(INT_CANCEL);
}


SMSVIEW.H

#define PREFIX "v"
#define INDIRECTACCESS 0
#define COMMON 1
#define MAX_COLUMN_NAME 255 // Max Width Col Name
#define MAX_COLUMNS     254   // Max # of COLUMNS we can Create on our View
                            // DdwMachineID takes one
#define MAX_NAME  255       // SIze of string we can take for the Server name

#define DLLExport __declspec(dllexport)

#define DISP 1
#define NO_DISP 0

//Command Line switches
#define LOGINIDPARAM    'L'
#define PASSWORDPARAM   'P'
#define DBNAMEPARAM     'D'
#define SERVERNAMEPARAM 'S'

struct VIEWDEF {
      char ColName[MAX_COLUMN_NAME];
      int  ComSpec;
      };

DLLExport int GenViews(DBPROCESS *,DBPROCESS *,char * ,int ,int );
int CheckSQLReturn(DBPROCESS * , char *);
int CreateGroupView(DBPROCESS *);
int CreateSNMPVarbindsView(DBPROCESS *);
int DropView ( DBPROCESS * , char *);
int CreateV(char *, struct VIEWDEF *,DBPROCESS *,int ,int,int ,
            char * , char *,int ,char *, int, int);
LRESULT WINAPI dbConnect(HWND , UINT , WPARAM , LPARAM );
int GetConnectionInfo( int);
int SetupDbConnection();
void   MakeUName(char *,char *,char *) ;
int CheckCommandLine(char * );
void GenResults(int);

int  err_handler();

DBPROCESS *dbproc = (DBPROCESS *)NULL;
                  /* dbprocess pointer for dblib connection*/
HANDLE hInst;    /* current instance                */
HWND ghWnd;      /* global window handle for handlers    */
HWND errhWnd;    /* global window handle for current error*/
BOOL SqlTestInit();


SMSVIEWS.H

//****************************************************************************
//
//  Copyright (c) 1994, Microsoft Corporation
//
//  File:  smsviews.h
//
//  History:
//
//      kenp    12-5-94         Created
//
//****************************************************************************
#define IDS_MSC_DBERROR                     1
#define IDS_MSC_CREATE_VIEWS                2

#define IDS_MSG_CREATE_VIEW                 101
#define IDS_MSG_DROP_VIEW                   102
#define IDS_ERR_BADGROUP                    103

#define IDS_ERR_CMD_TOO_LONG                201
#define IDS_ERR_COM_NOT_LOADED              202
#define IDS_ERR_LOAD_VIEWDLL                203
#define IDS_ERR_CREATE_VIEW                 204
#define IDS_ERR_DROP_VIEW                   205
#define IDS_ERR_SQL_ERROR                   206


VIEWDLL.C

/*************************************************************************

VIEWC.DLL - This Module creates Views based on the current Group Map in
SMS

Copyright (c), 1994 by Microsoft Corp.

Modified: 7/11/95 a-cvadai  Added error message messageboxes.
          9/13/95 a-cvadai  Changed outer- to inner-joins on all views

*************************************************************************/

#define DBNTWIN32               // must identify operating system environment
#include "windows.h"

#include <sqlfront.h>
#include <sqldb.h>         // DB-LIB header file (should always be included)
#include <stdio.h>
#include "smsview.h"
#include "viewdlls.h"

int RetCode, CmdComplete;

void ErrorMsg(int ,int);
int GenViews(dbproc,dbprocView,CreateGroupName,DropOnlyFlag,CommandLine)
DBPROCESS *dbproc;
DBPROCESS *dbprocView;
char *CreateGroupName;
int DropOnlyFlag;
int CommandLine;

{
  RETCODE result_code;
  struct VIEWDEF TableDef[MAX_COLUMNS];
  char szTmpText[500];
  char szText[500];
  char szMsgTitle[32];

  // Variables to bind to Group Map select
  char  ViewName[MAX_COLUMN_NAME],
        ArchitectureName[MAX_COLUMN_NAME],
        GroupName[MAX_COLUMN_NAME],
        AttributeName[MAX_COLUMN_NAME],
        ColumnName[MAX_COLUMN_NAME] ,
        CommonTableName[MAX_COLUMN_NAME],
        SpecificTableName[MAX_COLUMN_NAME];

   // Variables to keep Current View Details
   char CurCommonTableName[MAX_COLUMN_NAME],
        CurArchitectureName[MAX_COLUMN_NAME],
        CurSpecificTableName[MAX_COLUMN_NAME];

  int CurKey = 0,
      CurArchitectureKey,
      i= 0, LastMode,
      ViewSuffix = 0;

  int GroupKey,
      ArchitectureKey,
      Mode,
      CommonSpecificFlag;

      RetCode = SUCCEED;
      CmdComplete=CommandLine;

     dberrhandle((void *)err_handler);
     dbmsghandle((void *)err_handler);
    // Set up a View that we can use to get all the Group Info

    dbcmd (dbproc , (char *) "SELECT AttributeMap.GroupKey,");
    dbcmd (dbproc , (char *) " ArchitectureMap.ArchitectureName, ");
    dbcmd (dbproc , (char *) " GroupMap.ArchitectureKey , ");
    dbcmd (dbproc , (char *) " GroupMap.GroupName,");
    dbcmd (dbproc , (char *) " AttributeMap.AttributeName,");
    dbcmd (dbproc , (char *) " AttributeMap.ColumnName,");
    dbcmd (dbproc , (char *) " AttributeMap.CommonSpecificFlag,");
    dbcmd (dbproc , (char *) " GroupMap.CommonTableName, ");
    dbcmd (dbproc , (char *) " GroupMap.SpecificTableName, ");
    dbcmd (dbproc , (char *) " ArchitectureMap.Mode ");

    dbcmd (dbproc , (char *) " FROM ArchitectureMap ArchitectureMap,");
    dbcmd (dbproc , (char *) " AttributeMap AttributeMap,");
    dbcmd (dbproc , (char *) " GroupMap GroupMap     ");

    dbcmd (dbproc , (char *) " WHERE GroupMap.ArchitectureKey = "
                             "AttributeMap.ArchitectureKey AND ");
    dbcmd (dbproc , (char *) " AttributeMap.ArchitectureKey = "
                             "ArchitectureMap.ArchitectureKey AND ");
    dbcmd (dbproc , (char *) " AttributeMap.GroupKey = GroupMap.GroupKey");

    // Check if only specific group's selected
    if (strlen(CreateGroupName) != 0) {
        dbcmd (dbproc , (char *) " AND GroupMap.GroupName ='");
        dbcmd (dbproc , (char *) CreateGroupName );
        dbcmd (dbproc,  (char *) "'");
    }

    dbcmd (dbproc , (char *) " ORDER BY ");
    dbcmd (dbproc , (char *) " ArchitectureMap.ArchitectureKey ,");
    dbcmd (dbproc , (char *) " AttributeMap.GroupKey");

    dbsqlexec (dbproc);

    // now check the results from the SQL server
    while ((result_code = dbresults(dbproc)) != NO_MORE_RESULTS) {
        if (result_code == SUCCEED) {
            dbbind(dbproc, 1, INTBIND, (DBINT) MAX_COLUMN_NAME,
                  (char *)&GroupKey);
            dbbind(dbproc, 2, NTBSTRINGBIND, (DBINT) MAX_COLUMN_NAME,
                   (char *)ArchitectureName);
            dbbind(dbproc, 3, INTBIND, (DBINT) MAX_COLUMN_NAME,
                   (char *)&ArchitectureKey);
            dbbind(dbproc, 4, NTBSTRINGBIND, (DBINT) MAX_COLUMN_NAME,
                  (char *)GroupName);
            dbbind(dbproc, 5, NTBSTRINGBIND, (DBINT) MAX_COLUMN_NAME,
                   (char *)AttributeName);
            dbbind(dbproc, 6, NTBSTRINGBIND, (DBINT) MAX_COLUMN_NAME,
                   (char *)ColumnName);
            dbbind(dbproc, 7, INTBIND, (DBINT) MAX_COLUMN_NAME,
                   (char *)&CommonSpecificFlag);
            dbbind(dbproc, 8, NTBSTRINGBIND, (DBINT) MAX_COLUMN_NAME,
                   (char *)CommonTableName);
            dbbind(dbproc, 9, NTBSTRINGBIND, (DBINT) MAX_COLUMN_NAME,
                   (char *)SpecificTableName);
            dbbind(dbproc, 10, INTBIND, (DBINT) MAX_COLUMN_NAME,
                  (char *)&Mode);

            // Loop through all the groups Breaking when a Arctecture , Group Changes

            CurKey = -1;
            i = 0;
            ViewSuffix = 0;
            while (dbnextrow(dbproc) != NO_MORE_ROWS ) {
                if ( ((CurKey != GroupKey || CurArchitectureKey != ArchitectureKey) && CurKey != -1)
                     || (i == MAX_COLUMNS) ) {
                    if (CreateV(ViewName, &TableDef[0],dbprocView,i,
                            CurArchitectureKey, CurKey, CurCommonTableName,
                            CurSpecificTableName, ViewSuffix,
                            CurArchitectureName, DropOnlyFlag, LastMode) == FAIL) {

                        LoadString(hInst, IDS_ERR_BADGROUP, szTmpText, 500);
                        LoadString(hInst, IDS_MSC_DBERROR, szMsgTitle, 32);

                        sprintf(szText, (const char *)szTmpText, ViewName);
                        MessageBox(NULL, szText, szMsgTitle, MB_ICONSTOP);
                    }
                    if ( i == MAX_COLUMNS && CurKey == GroupKey)
                        ViewSuffix++;
                    else
                        ViewSuffix = 0;

                    i = 0;
                }      // End if
                strcpy(ViewName, GroupName);
                strcpy(TableDef[i].ColName,ColumnName);
                TableDef[i].ComSpec = CommonSpecificFlag;

                CurArchitectureKey = ArchitectureKey;
                CurKey = GroupKey;
                strcpy(CurCommonTableName,CommonTableName);
                strcpy(CurSpecificTableName,SpecificTableName);
                strcpy(CurArchitectureName,ArchitectureName);
                LastMode = Mode;
                i++;

            }

            // Make sure we Create a view for the last group

            if (CreateV(ViewName,TableDef,dbprocView,i,CurArchitectureKey,
                    CurKey, CurCommonTableName, CurSpecificTableName,
                    ViewSuffix, CurArchitectureName, DropOnlyFlag, LastMode) == FAIL) {

                    LoadString(hInst, IDS_ERR_BADGROUP, szTmpText, 500);
                    LoadString(hInst, IDS_MSC_DBERROR, szMsgTitle, 32);

                    sprintf(szText, (const char *)szTmpText, ViewName);
                    MessageBox(NULL, szText, szMsgTitle, MB_ICONSTOP);

            }

            DropView(dbproc,"SNMP_Varbinds");
            DropView(dbproc,"GroupNames");

            if (!DropOnlyFlag) {
                CreateSNMPVarbindsView(dbprocView);
                CreateGroupView(dbprocView);
            }
        }
        else {
            return(FAIL);
            break;
        }
    } // while (TRUE)

    return(RetCode);
}

/******************************************************************************

Create a View that will logicaly represent the DataBase
Typical View Of the Disk Group would be

Create View SMS_Disk as select dwMachineID,
             Disk_COMM.Disk_Index0 , Disk_COMM.File_System0 ,
             Disk_COMM.Storage_Type0 , Disk_SPEC.Volume_Name0 ,
             Disk_SPEC.Serial_Number0 ,Disk_SPEC.Heads0 ,
             Disk_SPEC.Sectors0 , Disk_SPEC.Cylinders0 ,
             Disk_SPEC.__Disk_Full0 , Disk_SPEC.Free_Storage__MByte_0 ,
             Disk_SPEC.Storage_Size__MByte_0 , Disk_SPEC.Storage_Used__MByte_0
        from Disk_COMM  , Disk_SPEC, MachineDataTable
        where Disk_COMM.key =* CommonKey and Disk_SPEC.key =* SpecificKey
         and ArchitectureKey = 5 and GroupKey = 5
*****************************************************************************/

int CreateV(ViewName, TableDef,dbproc,NoCols,ArchitectureKey,GroupKey ,
        CurCommonTableName , CurSpecificTableName,ViewSuffix,
        CurArchitectureName,DropOnlyFlag, Mode)

char * ViewName;
struct VIEWDEF * TableDef;
DBPROCESS * dbproc;
int NoCols,
    ArchitectureKey ,
    GroupKey;
char * CurCommonTableName ,
     * CurSpecificTableName;
int ViewSuffix;
char * CurArchitectureName;
int DropOnlyFlag;

{
    int i ;
    char TableName[MAX_COLUMN_NAME];
    int CommonFlag = FALSE,
    SpecFlag = FALSE;
    BOOL bDirect = FALSE;

    char cmd[MAX_COLUMN_NAME];

    if (Mode == 1) {
        bDirect = TRUE;
    }

    for ( i= 0; ViewName[i]; i++)  // Change all Spaces to _ to derive Table Name
        if (ViewName[i] == ' ')
            ViewName[i] = '_';

    MakeUName(ViewName, CurSpecificTableName, CurCommonTableName);

    if (ViewSuffix) // If this is not the first view
        sprintf(ViewName,"%s%d",ViewName,ViewSuffix);

    DropView ( dbproc,ViewName);

    if( DropOnlyFlag )
        return(SUCCEED);

    // 9/13/1995 a-cvadai
    // First, make sure there is a 0-datakey row in each table
    // so the views can use inner-joins.
    // =======================================================

    if (!bDirect) {

        if (strlen(CurSpecificTableName) > 0) {
            dbcmd (dbproc, (char *) " IF NOT EXISTS (select * from ");
            dbcmd (dbproc, (char *) CurSpecificTableName);
            dbcmd (dbproc, (char *) " where datakey = 0) INSERT ");
            dbcmd (dbproc, (char *) CurSpecificTableName);
            dbcmd (dbproc, (char *) " (datakey) values (0)");
            dbsqlexec (dbproc);
            dbresults(dbproc);
            do {
                while (dbnextrow(dbproc) == REG_ROW) {
                    ;
                }
            } while (dbresults(dbproc) == SUCCEED);

        }

        if (strlen(CurCommonTableName) > 0) {
            dbcmd (dbproc, (char *) " IF NOT EXISTS (select * from ");
            dbcmd (dbproc, (char *) CurCommonTableName);
            dbcmd (dbproc, (char *) " where datakey = 0) INSERT ");
            dbcmd (dbproc, (char *) CurCommonTableName);
            dbcmd (dbproc, (char *) " (datakey) values (0)");
            dbsqlexec (dbproc);
            dbresults(dbproc);
            do {
                while (dbnextrow(dbproc) == REG_ROW) {
                    ;
                }
            } while (dbresults(dbproc) == SUCCEED);

        }
    }

    dbcmd (dbproc , (char *) "Create View ");
    dbcmd (dbproc , (char *) PREFIX);
    dbcmd (dbproc , (char *) ViewName);
    dbcmd (dbproc , (char *) " as select ");

    if (!bDirect) {
        dbcmd (dbproc ,(char *) "dwMachineID , ");
    }
    else {
        dbcmd (dbproc ,(char *) "MachineID , ");
    }

    for ( i = 0; i < NoCols;i++) {
         // Check if this attribute is in the COMM or SPEC tables
        if (TableDef[i].ComSpec == COMMON)
            strcpy(TableName,CurCommonTableName);
        else
            strcpy(TableName,CurSpecificTableName);

        if (i)    // We need a , after the first Table name
            dbcmd(dbproc,(char *) " , ");

            dbcmd(dbproc,(char *) TableName); // Append the Table Name eg DISK_COMM
            dbcmd(dbproc,(char *) ".");
            dbcmd(dbproc,(char *) TableDef[i].ColName);

    } // End For

    dbcmd (dbproc,(char *)" from ");

    if (!bDirect) {
        dbcmd (dbproc,(char *)" MachineDataTable ");

        if (strlen (CurCommonTableName) != 0) {
            dbcmd (dbproc,(char *)" ,");

            // Append the Table Name eg DISK_COMM
            dbcmd(dbproc,(char *) CurCommonTableName);
            dbcmd(dbproc,(char *) "  ");
        }
    }

    if ( strlen (CurSpecificTableName) != 0) {
        if (!bDirect) {
            dbcmd (dbproc,(char *)" , ");
        }

        // Append the Table Name eg DISK_SPEC
        dbcmd(dbproc,(char *) CurSpecificTableName);
        dbcmd(dbproc,(char *) " ");
    }

    if (!bDirect) {

        dbcmd(dbproc ,(char *) "where " );
        if (strlen (CurCommonTableName) != 0) {
            dbcmd(dbproc ,(char *) CurCommonTableName);
            dbcmd(dbproc ,(char *) ".datakey = ");
            dbcmd(dbproc ,(char *) " CommonKey and ");
        }

        if ( strlen (CurSpecificTableName) != 0) {
            dbcmd(dbproc ,(char *) CurSpecificTableName);
            dbcmd(dbproc ,(char *) ".datakey = ");
            dbcmd(dbproc ,(char *) "SpecificKey and ");
        }
        dbcmd(dbproc ,(char *) "ArchitectureKey = ");
        sprintf(cmd,"%d", ArchitectureKey);
        dbcmd(dbproc ,(char *) cmd);
        dbcmd(dbproc ,(char *) " and GroupKey = ");
        sprintf(cmd,"%d", GroupKey);
        dbcmd(dbproc ,(char *) cmd);
    }

    dbsqlexec (dbproc);

    return ( CheckSQLReturn(dbproc, "Create View"));
}

DropView(DBPROCESS *dbproc, char * ViewName)
{

    // Check if View already exists and if so Drop it
    dbcmd (dbproc ,(char *) "IF EXISTS (SELECT * FROM sysobjects ");
    dbcmd (dbproc ,(char *) " WHERE id = object_id('dbo.");
    dbcmd (dbproc ,(char *) PREFIX);
    dbcmd (dbproc ,(char *) ViewName);
    dbcmd (dbproc ,(char *) "'))");
    dbcmd (dbproc ,(char *) " DROP view dbo.");
    dbcmd (dbproc ,(char *) PREFIX);
    dbcmd (dbproc ,(char *) ViewName);

    dbsqlexec (dbproc);

    return( CheckSQLReturn(dbproc, "Drop View"));

}

// Create a View of all the groups that have been made into Views

CreateGroupView(DBPROCESS * dbproc)
{

    dbcmd (dbproc , (char *) "Create View ");
    dbcmd (dbproc , (char *) PREFIX);
    dbcmd (dbproc , (char *) "GroupNames");
    dbcmd (dbproc , (char *) " as select ");
    dbcmd (dbproc , (char *) " GM.GroupName  ");

    dbcmd (dbproc , (char *) " FROM ArchitectureMap AM,");

    dbcmd (dbproc , (char *) " GroupMap GM ");

    dbcmd (dbproc , (char *) " WHERE ");

    dbcmd (dbproc , (char *) " GM.ArchitectureKey = AM.ArchitectureKey ");

    dbsqlexec (dbproc);
    return ( CheckSQLReturn(dbproc, "Create Group View"));
}

CreateSNMPVarbindsView(DBPROCESS * dbproc)
{

    dbcmd (dbproc , (char *) "Create View ");
    dbcmd (dbproc , (char *) PREFIX);
    dbcmd (dbproc , (char *) "SNMP_Varbinds");
    dbcmd (dbproc , (char *) " as select ");
    dbcmd (dbproc , (char *) " MachineID, Variable, ObjectIdentifier, DataType, Data1, Data2, Data3, Data4, Data5, Data6 ");

    dbcmd (dbproc , (char *) " FROM SNMP_Varbinds");

    dbsqlexec (dbproc);
    return ( CheckSQLReturn(dbproc, "Create Group View"));
}

//Make UniqueName

void MakeUName(vName,uName,cName)
    char * vName ,
         * uName , * cName;

{
    char p1[MAX_COLUMNS] = "";
    char p2[MAX_COLUMNS] = "";
    char commstr[] = "_COMM";
    char specstr[] = "_SPEC";
    char *psearch;
    int  lenp1;

    if ( strlen (uName) != 0) {
        if ((psearch = strstr(uName,specstr)) == NULL)
        strcpy(vName,uName);
    else {
        lenp1 = psearch-uName;
        strncpy (p1,uName,lenp1);
        strcpy (p2,psearch+5);
        sprintf(vName,"%s%s",p1,p2);

        }
    }
    else {
        if ((psearch = strstr(cName,commstr)) == NULL)
        strcpy(vName,cName);
    else {
        lenp1 = psearch-cName;
        strncpy (p1,cName,lenp1);
        strcpy (p2,psearch+5);
        sprintf(vName,"%s%s",p1,p2);
        }

    }

}

//     Check SQL return and empty return buffer

CheckSQLReturn(DBPROCESS * dbproc, char *Action)
{
    RETCODE result_code;
    int iResult = FAIL;

    while ((result_code = dbresults(dbproc)) != NO_MORE_RESULTS) {
        if (result_code == SUCCEED) {
            iResult = SUCCEED;
            while (dbnextrow(dbproc) != NO_MORE_ROWS)
                ;  /* do nothing */
        }
        else {
             return(FAIL);
             break;
        }
    }
    return (iResult);
}

//
int err_handler(DBPROCESS * dbproc, int severity, int dberr, int oserr,
                char * dberrstr, char * oserrstr)
{

    char    szString[512];
    char    szMsgTitle[32];
    int     iString;

    // Ignore any 'Changed context' errors.

    if (dberr == 5701 || dberr == 5703) {
        return 0;
    }

    LoadString(hInst, IDS_ERR_SQL_ERROR, szString, MAX_PATH);
    LoadString(hInst, IDS_MSC_DBERROR, szMsgTitle, 32);

    iString = sizeof(szString) - strlen(szString);
    if (dberrstr != NULL) {
        strncpy(szString, dberrstr, iString);
    }
    else if (oserr != DBNOERR && oserrstr != NULL) {
        strncpy(szString, oserrstr, iString);
    }
    szString[sizeof(szString)-1] = 0;

    if (strstr(szString, "General SQL Server") == NULL) {
        MessageBox(NULL, szString, szMsgTitle, MB_ICONSTOP);
    }

    return(INT_CANCEL);
}


VIEWDLLS.H

//****************************************************************************
//
//  Copyright (c) 1994, Microsoft Corporation
//
//  File:  smsviews.h
//
//  History:
//
//      kenp    12-5-94         Created
//
//****************************************************************************
#define IDS_MSC_DBERROR                     1

#define IDS_ERR_SQL_ERROR                   101
#define IDS_ERR_BADGROUP                    103


README.TXT

This directory provides code that allows the SMSAPI to be
used with Visual Basic 4.0. It also provides sample Visual
Basic code which interfaces to SMS.

For more information about these samples or the Visual
Basic SMSAPI set, see the SDK documentation (section
entitled "Using the SMS API with Visual Basic").


OBJECTTY.BAS

Attribute VB_Name = "OBJECTTY"
Option Explicit
'// *************************************************************************
'//
'//  File: OBJECTTY.H
'//
'//  Copyright (c) 1994, 1995 Microsoft Corp.
'//
'//  This is the header file for the obejctity engine. The objectity
'//  engine is the core of the SMS API set.
'//
'//  This file contains externally visible structures, and certain manifests
'//  needed by them.
'//
'//
'// *************************************************************************
'//
'
'// ====================================================================
'//
'//      Manifests.
'//
'// ====================================================================
'
'typedef DWORD   SMS_STATUS;         // API return codes are all of this type.
'/* VB: All API's return Long  */
'
 Global Const F_ANY = 0              'Indicates any folder type
'
'
'// ====================================================================
'//  Scalar values can be strings, integers (DWORDs), time, or binary.
'//NOTE: binary not yet implemented.
'// ====================================================================
 Type SCALARTYPE
     enum As Long
 End Type

 ' These are initialized in Init_SCALARTYPE_enums()
 Global SCALAR_STRING As SCALARTYPE
 Global SCALAR_INT As SCALARTYPE
 Global SCALAR_TIME As SCALARTYPE
 Global SCALAR_BINARY As SCALARTYPE
'
'
'// ====================================================================
'//      SCALAR
'//  This structure is used by the application and the engine to
'//  communicate a scalar's details.
'//
'//  All scalars have a type (SCALARTYPE), a name (a character string),
'//  and a value, which is determined by the scalar's type.
'//  There are separate fields here for the value in all its guises.
'//  Additionally, if the scalar is a string or a binary, a length
'//  field indicates the datum's size.
'//
'//  This length field can also be set by the application when
'//  retrieving a scalar. In this case it indicates the size of the
'//  receiving buffer (string and binary).
'//
'// ====================================================================
 Type SCALAR
     scType As SCALARTYPE       '// string, int, time etc
     pszName As String          '// Scalar name.
     pszValue As String         '// Scalar value as a string.
     dwValue As Long            '// Scalar value as a DWORD.
     tValue As Long             '// Scalar value as a time.
     pValue() As Byte           '// Scalar as a binary.
     dwLen As Long              '// Size of the scalar value either as a string
                                '// or as a binary.
                                '// If a get scalar API is called with NULL
                                '// for the appropriate value, then the
                                '// length of that value is returned here.
     bStringEquivalence As Long
                                '// True if a string exists for an INT or
                                '// TIME scalar. String will be in pszValue.
     fAccess As Long            '// Access mode.
 End Type
'
'
'
'
'// --------------------------------------------------------------------
'// Manifests used in filter token APIs.
'// --------------------------------------------------------------------
 Global Const OTT_AT_START = -1 '// Token will be inserted at start of
                                '// expression.
 Global Const OTT_AT_END = -2   '// Token will be appended to end of
                                '// expression.
'
'
'
'
'// ====================================================================
'//
'//  Return codes from the objectity engine.
'//
'// ====================================================================
 Global Const OTT_OK = 0
 Global Const OTT_MORE_DATA = 1
 Global Const OTT_NO_MORE_DATA = 2
 Global Const OTT_FOLDER_NO_UPDATE = 3
 Global Const OTT_DUPLICATE_CONTAINER_ID = 4
 Global Const OTT_DUPLICATE_FOLDER_ID = 5
 Global Const OTT_DUPLICATE_FILTER_ID = 6
 Global Const OTT_SCALAR_NO_UPDATE = 7
 Global Const OTT_SCALAR_WRONG_TYPE = 8
 Global Const OTT_INVALID_CONTAINER_ID = 9
 Global Const OTT_INVALID_FOLDER_ID = 10
 Global Const OTT_INVALID_FILTER_ID = 11
'
 Global Const OTT_CONTAINER_NO_UPDATE = 12
'
 Global Const OTT_PARAMETER_ERROR = 13
 Global Const OTT_UNSUPPORTED = 14
 Global Const OTT_NOT_FOUND = 15
 Global Const OTT_PARENT_NOT_FOUND = 16
 Global Const OTT_CANT_DELETE = 17
 Global Const OTT_NEW_FOLDER = 18
'
'// Error when attempting to multiply link a folder.
 Global Const OTT_FOLDER_LINKED = 19
'
'//
'// Filter operations.
 Global Const OTT_RANGE_ERROR = 20
'
 Global Const OTT_DUPLICATE_FILTER = 21
'
'// Container operations that are out of sequence.
 Global Const OTT_CONTAINER_NOT_POPULATED = 22
 Global Const OTT_CONTAINER_POPULATED = 23
'
'// Only persistent filters have scalars.
 Global Const OTT_FILTER_NOT_PERSISTENT = 24
'
'// Error return if the connection handle of a filter doesn't match
'// that of a container to which the filter is being applied.
 Global Const OTT_INVALID_CONNECTION_HANDLE = 25

 Global Const OTT_INVALID_FILTER_TYPE = 26
'
'// Folder's can't be unlinked if there are multiple handles
'// to the same object.
 Global Const OTT_IN_USE_BY_OTHER = 27
'
'
'// The last one, user codes start from here to give
'// leaway for future expansion.
'//
 Global Const OTT_END = 50
'
'
'// ====================================================================
'//
'//  Other manifests.
'//
'// ====================================================================
'
'// Access rights to containers/folders/scalars.
'// -------------------------------------------------------------
 Global Const ACCESS_READ = &H1
 Global Const ACCESS_CREATE = &H2
 Global Const ACCESS_MODIFY = &H4
 Global Const ACCESS_DELETE = &H8
'
'// Modification flags
'// ------------------
 Global Const MOD_UNMODIFIED = &H0
 Global Const MOD_CREATED = &H1
 Global Const MOD_DELETED = &H2
 Global Const MOD_DENY_ACCESS = &H4
'
'// Tokens are ANDed or ORed into filters.
'// -------------------------------------------------------------
 Type ANDOR
     enum As Long
 End Type

 ' These are initialized in Init_ANDOR_enums()
 Global OP_AND As ANDOR
 Global OP_OR As ANDOR
'
'// Options for Rewind on a folder.
'// -------------------------------------------------------------
 Global Const RW_FOLDER = &H80000000      '// Rewind folder lists.
 Global Const RW_SCALAR = &H40000000      '// Rewind scalar list only.
 Global Const RW_ALL = RW_FOLDER + RW_SCALAR ' // Rewind both.
'
'
'
'
'
'
'
'// These structures are used to retrieve container and filter
'// information from the engine.
'// These are used in enumertaing containers and filters.
'// -------------------------------------------------------------
 Global Const BUFF_SIZE = 256
'
'
'// An objectity is either a container or a folder.
'// -------------------------------------------------------------
 Type BASETYPE
     enum As Long
 End Type

 ' These are initialized in Init_BASETYPE_enums()
 Global T_CONTAINER As BASETYPE
 Global T_FOLDER As BASETYPE
'
'
'// ====================================================================
'//
'//  The structures.
'//
'// ====================================================================
'
'//
'// NOTIFY structure.
'//
'// This struct used for async population. Enables async operation to
'// use either event handle or window handle/message.
'
 Type NOTIFY
    dwFreq As Long     '// Notify after this number of rows retrieved.
    hEvent As Long     '// Event handle if type=POP_EVENT
    hwnd As Long       '// hwnd if type=POP_WINDOW
    uiMsg As Long      '// Message if type=POP_WINDOW
 End Type
'
'
'//
'// TOKEN structure.
'//
 Global Const TOKEN_STRING_SIZE = 4608 '// 4.5k
'// The manifest is this large because it can used in the package inventory
'// rules. In these rules we are allowed to define upto four string fields,
'// each of which can be 1k long. This size allows us to accommodate it all.
'
 Type TOKEN
     tokenType As Long            '// Returns: type of token (ie USER, AND,
                                         '// OR, LEFT_PAREN, RIGHT_PAREN).
     szName As String
     szValue As String
     dwOp As Long

     szArchitecture As String
     szGroupClass As String
     szAttributeName As String
     szTokenString As String           '// Display form of the token.
     bIndent As Long                   '// Request display indentation.
 End Type
'
'
'
'//
'// FILTER_INFO
'//
'//  This structure is used to provide the user a filter template.
'//  An array of these structures is returned via the SmsEnumFilters API.
'//  This returns information about all filters registered with the engine.
'//
'//  It is considered that the user will use this information in order to
'//  display information. The information that is provided here is:
'//  a) the type and tag of the filter, b) the fields that comprise a
'//  particular filter's tokens, and c) the names that should be used
'//  for these fields for display purposes. That is, for a GUI application,
'//  they provide the labels for edit controls. If a field in the structure
'//  is empty (ie NULL) then that field is not used for that particular
'//  filter.
'//
'//  As an example consider a group filter. If the developer wants to
'//  present, say, a dialogue box to the end user, they would use the
'//  following fields: szName, szValue. Looking at the template they
'//  will see that the strings associated with these fields are 'Name'
'//  and 'GroupClass'. They would thus display a dialogue box with
'//  two edit controls, the first labelled 'Name', the second labelled
'//  'GroupClass. Since these are the only two fields that are filled in
'//  no other fields should be presented to the user.
'//
'//NOTE: what about default values?
'//
'//  Job filter:
'//      szName          = 'Type'
'//      szValue         = 'Value'
'//      szOperator      = 'Operator'
'//
'//  Machine filter:
'//      szArchitecture  = 'Architecture'
'//      szGroupClass    = 'GroupClass'
'//      szAttributeName = 'Attribute name'
'//      szValue         = 'Value'
'//      szOperator      = 'Operator'
'//
'//  Site filter:
'//      szName          = 'Type'
'//      szValue         = 'Site code'
'//      szOperator      = 'Operator'
'//
'//  Group filter:
'//      szName          = 'Name'
'//      szValue         = 'GroupClass'
'//      szOperator      = 'Operator'
'//
'//  Architecture filter:
'//      szName          = 'Name'
'//      szValue         = 'Architecture'
'//      szOperator      = 'Operator'
'//
'//  Package filter:
'//      szName          = 'Type'
'//      szValue         = 'Value'
'//      szOperator      = 'Operator'
'//
'//  Attribute filter:
'//      szArchitecture  = 'Architecture'
'//      szGroupClass    = 'GroupClass'
'//      szAttributeName = 'Attribute name'
'//
'//  SiteLimit filter:
'//      szName          = 'Site'
'//      szValue         = 'Domain'
'//
'//
 Global Const NAME_SIZE = 52
'
 Type FILTER_INFO
     szTag As String                 '// Filter tag (eg "Architecture filter").
'     tokenType As Integer
     filterType As Long              '// Type number (eg ARCHITECTURE_FILTER).

     szName As String
     szValue As String
     szOperator As String

     szArchitecture As String
     szGroupClass As String
     szAttributeName As String
 End Type
'
'
'
'// This is what the user gets as a description of a scalar.
'// Currently this is only used internally in objectty.cpp (RegisterObjectity).
'// I'll proably make this externally visible though very soon now.
 Type SCALAR_INFO
     szName As String                '// Scalar's name.
     scType As SCALARTYPE            '// Type: SCALAR_STRING, INT, TIME etc
     fAccess As Long                 '// Access mode: ACCESS_READ, MODIFY etc
 End Type
'
'
'// Now the C struct form of CFolderInfo. This is what we'll
'// return in a call to DescribeFolder().
'// This is created during a call to RegisterObjectity. It is stored
'// in the CFolderInfo object, itself held in the Folder registry.
'// 29-Sep-94. Expanded to deal with Containers as well as folders.
'
'
 Type FOLDER_INFO
     tObjectity As BASETYPE          '// T_CONTAINER or T_FOLDER.
     pszTag As String
     dwTag As Long

     ctFolders As Long
     FolderTags() As Long

     ctFilters As Long
     FilterTags() As Long

     ctScalars As Long               '// Not used by for containers.
     Scalars() As SCALAR_INFO        '// ditto.

     NewFunc As Long                 '// needs cast!! Used when registering
                                     '// a container. This is a pointer to
                                     '// the container's 'new' function.
 End Type

 Sub Init_ANDOR_enums()
     OP_AND.enum = 3
     OP_OR.enum = 2
 End Sub

 Sub Init_BASETYPE_enums()
     T_CONTAINER.enum = 0
     T_FOLDER.enum = 1
 End Sub

 Sub Init_OBJECTTY()
     Init_SCALARTYPE_enums
     Init_ANDOR_enums
     Init_BASETYPE_enums
 End Sub

 Sub Init_SCALARTYPE_enums()
     SCALAR_STRING.enum = 0
     SCALAR_INT.enum = 1
     SCALAR_TIME.enum = 2
     SCALAR_BINARY.enum = 3
 End Sub


QRYCODES.BAS

Option Explicit
'// *************************************************************************
'//
'//  File: QRYCODES.H
'//
'//  Copyright (c) 1994, Microsoft Corp.
'//
'//
'//
'//  This file contains the Query operator manifests for queries against
'//  machines. This is part of the SMS SDK.
'//
'//
'//  History:
'//      JonShu      01-April-1994       Created.
'//                                      This was extracted from query.h
'//      GFF@SEA     17-Mar-95           VBified.
'//
'// *************************************************************************
'
'// Query Operators.
'// ================
'
 Global Const QOP_NOOP = 0
'
 Global Const QOP_BOOL_EQ = 1
 Global Const QOP_BOOL_NEQ = 2
 Global Const QOP_BOOL_FIRST = QOP_BOOL_EQ
 Global Const QOP_BOOL_LAST = QOP_BOOL_NEQ
'
 Global Const QOP_NUMERIC_EQ = 3
 Global Const QOP_NUMERIC_NEQ = 4
 Global Const QOP_NUMERIC_GT = 5
 Global Const QOP_NUMERIC_LT = 6
 Global Const QOP_NUMERIC_GTE = 7
 Global Const QOP_NUMERIC_LTE = 8
 Global Const QOP_NUMERIC_FIRST = QOP_NUMERIC_EQ
 Global Const QOP_NUMERIC_LAST = QOP_NUMERIC_LTE
'
 Global Const QOP_STR_EQ = 9
 Global Const QOP_STR_NEQ = 10
 Global Const QOP_STR_GT = 11
 Global Const QOP_STR_LT = 12
 Global Const QOP_STR_GTE = 13
 Global Const QOP_STR_LTE = 14
 Global Const QOP_STR_LIKE = 15
 Global Const QOP_STR_NOTLIKE = 16
 Global Const QOP_STR_FIRST = QOP_STR_EQ
 Global Const QOP_STR_LAST = QOP_STR_NOTLIKE
'
 Global Const QOP_UPCASE_EQ = 17
 Global Const QOP_UPCASE_NEQ = 18
 Global Const QOP_UPCASE_GT = 19
 Global Const QOP_UPCASE_LT = 20
 Global Const QOP_UPCASE_GTE = 21
 Global Const QOP_UPCASE_LTE = 22
 Global Const QOP_UPCASE_LIKE = 23
 Global Const QOP_UPCASE_NOTLIKE = 24
 Global Const QOP_UPCASE_FIRST = QOP_UPCASE_EQ
 Global Const QOP_UPCASE_LAST = QOP_UPCASE_NOTLIKE
'
 Global Const QOP_DATETIME_EQ = 25
 Global Const QOP_DATETIME_NEQ = 26
 Global Const QOP_DATETIME_GT = 27
 Global Const QOP_DATETIME_LT = 28
 Global Const QOP_DATETIME_GTE = 29
 Global Const QOP_DATETIME_LTE = 30
 Global Const QOP_DATETIME_FIRST = QOP_DATETIME_EQ
 Global Const QOP_DATETIME_LAST = QOP_DATETIME_LTE
'
 Global Const QOP_DATE_EQ = 31
 Global Const QOP_DATE_NEQ = 32
 Global Const QOP_DATE_GT = 33
 Global Const QOP_DATE_LT = 34
 Global Const QOP_DATE_GTE = 35
 Global Const QOP_DATE_LTE = 36
 Global Const QOP_DATE_FIRST = QOP_DATE_EQ
 Global Const QOP_DATE_LAST = QOP_DATE_LTE
'
 Global Const QOP_TIME_EQ = 37
 Global Const QOP_TIME_NEQ = 38
 Global Const QOP_TIME_GT = 39
 Global Const QOP_TIME_LT = 40
 Global Const QOP_TIME_GTE = 41
 Global Const QOP_TIME_LTE = 42
 Global Const QOP_TIME_FIRST = QOP_TIME_EQ
 Global Const QOP_TIME_LAST = QOP_TIME_LTE
'
 Global Const QOP_YEAR_EQ = 43
 Global Const QOP_YEAR_NEQ = 44
 Global Const QOP_YEAR_GT = 45
 Global Const QOP_YEAR_LT = 46
 Global Const QOP_YEAR_GTE = 47
 Global Const QOP_YEAR_LTE = 48
 Global Const QOP_YEAR_FIRST = QOP_YEAR_EQ
 Global Const QOP_YEAR_LAST = QOP_YEAR_LTE
'
 Global Const QOP_MONTH_EQ = 49
 Global Const QOP_MONTH_NEQ = 50
 Global Const QOP_MONTH_GT = 51
 Global Const QOP_MONTH_LT = 52
 Global Const QOP_MONTH_GTE = 53
 Global Const QOP_MONTH_LTE = 54
 Global Const QOP_MONTH_FIRST = QOP_MONTH_EQ
 Global Const QOP_MONTH_LAST = QOP_MONTH_LTE
'
 Global Const QOP_DAY_EQ = 55
 Global Const QOP_DAY_NEQ = 56
 Global Const QOP_DAY_GT = 57
 Global Const QOP_DAY_LT = 58
 Global Const QOP_DAY_GTE = 59
 Global Const QOP_DAY_LTE = 60
 Global Const QOP_DAY_FIRST = QOP_DAY_EQ
 Global Const QOP_DAY_LAST = QOP_DAY_LTE
'
 Global Const QOP_HOUR_EQ = 61
 Global Const QOP_HOUR_NEQ = 62
 Global Const QOP_HOUR_GT = 63
 Global Const QOP_HOUR_LT = 64
 Global Const QOP_HOUR_GTE = 65
 Global Const QOP_HOUR_LTE = 66
 Global Const QOP_HOUR_FIRST = QOP_HOUR_EQ
 Global Const QOP_HOUR_LAST = QOP_HOUR_LTE
'
 Global Const QOP_MINUTE_EQ = 67
 Global Const QOP_MINUTE_NEQ = 68
 Global Const QOP_MINUTE_GT = 69
 Global Const QOP_MINUTE_LT = 70
 Global Const QOP_MINUTE_GTE = 71
 Global Const QOP_MINUTE_LTE = 72
 Global Const QOP_MINUTE_FIRST = QOP_MINUTE_EQ
 Global Const QOP_MINUTE_LAST = QOP_MINUTE_LTE
'
 Global Const QOP_SECOND_EQ = 73
 Global Const QOP_SECOND_NEQ = 74
 Global Const QOP_SECOND_GT = 75
 Global Const QOP_SECOND_LT = 76
 Global Const QOP_SECOND_GTE = 77
 Global Const QOP_SECOND_LTE = 78
 Global Const QOP_SECOND_FIRST = QOP_SECOND_EQ
 Global Const QOP_SECOND_LAST = QOP_SECOND_LTE
'
 Global Const QOP_LAST = QOP_SECOND_LAST
'
'
'//***************************************************************************
'// OPERATOR STRING TABLE.
'//
'// The sequence of the operators in this array is identical to the
'// sequence of of the QOP_ symbols in QUERYTOK.H.  If the order of one
'// is changed, the order of the other must be changed to match.
'// Eventually, this table will be replaced with a string table resource
'// of some kind.   See CQueryToken::OperatorToString().
'//
'//***************************************************************************
'This array is initialized in Init_OpName_array()
 Global OpName$(0 To QOP_LAST)

 Sub Init_OpName_array ()
     OpName(QOP_NOOP) = "<none>"

     'BOOL operators

     OpName(QOP_BOOL_EQ) = "is (boolean)"
     OpName(QOP_BOOL_NEQ) = "is not (boolean)"

     'NUMERIC operators

     OpName(QOP_NUMERIC_EQ) = "is equal to"
     OpName(QOP_NUMERIC_NEQ) = "is not equal to"
     OpName(QOP_NUMERIC_GT) = "is greater than"
     OpName(QOP_NUMERIC_LT) = "is less than"
     OpName(QOP_NUMERIC_GTE) = "is greater than or equal to"
     OpName(QOP_NUMERIC_LTE) = "is less than or equal to"

     'Case-sensitive string operators

     OpName(QOP_STR_EQ) = "is"
     OpName(QOP_STR_NEQ) = "is not"
     OpName(QOP_STR_GT) = "comes after"
     OpName(QOP_STR_LT) = "comes before"
     OpName(QOP_STR_GTE) = "is same as or comes after"
     OpName(QOP_STR_LTE) = "is same as or comes before"
     OpName(QOP_STR_LIKE) = "is like"
     OpName(QOP_STR_NOTLIKE) = "is not like"

     'Upper-case-evaluation string operators

     OpName(QOP_UPCASE_EQ) = "is (upper case)"
     OpName(QOP_UPCASE_NEQ) = "is not (upper case)"
     OpName(QOP_UPCASE_GT) = "comes after (upper case)"
     OpName(QOP_UPCASE_LT) = "comes before (upper case)"
     OpName(QOP_UPCASE_GTE) = "is same as or comes after (upper case)"
     OpName(QOP_UPCASE_LTE) = "is same as or comes before (upper case)"
     OpName(QOP_UPCASE_LIKE) = "is like (upper case)"
     OpName(QOP_UPCASE_NOTLIKE) = "is not like (upper case)"

     'Date-Time operators

     OpName(QOP_DATETIME_EQ) = "date/time is"
     OpName(QOP_DATETIME_NEQ) = "date/time is not"
     OpName(QOP_DATETIME_GT) = "date/time is after"
     OpName(QOP_DATETIME_LT) = "date/time is before"
     OpName(QOP_DATETIME_GTE) = "date/time is on or after"
     OpName(QOP_DATETIME_LTE) = "date/time is on or before"

     'Date operators

     OpName(QOP_DATE_EQ) = "date is"
     OpName(QOP_DATE_NEQ) = "date is not"
     OpName(QOP_DATE_GT) = "date is after"
     OpName(QOP_DATE_LT) = "date is before"
     OpName(QOP_DATE_GTE) = "date is on or after"
     OpName(QOP_DATE_LTE) = "date is on or before"

     'Time operators

     OpName(QOP_TIME_EQ) = "time is"
     OpName(QOP_TIME_NEQ) = "time is not"
     OpName(QOP_TIME_GT) = "time is after"
     OpName(QOP_TIME_LT) = "time is before"
     OpName(QOP_TIME_GTE) = "time is at or after"
     OpName(QOP_TIME_LTE) = "time is at or before"

     'Year operators

     OpName(QOP_YEAR_EQ) = "year is"
     OpName(QOP_YEAR_NEQ) = "year is not"
     OpName(QOP_YEAR_GT) = "year is after"
     OpName(QOP_YEAR_LT) = "year is before"
     OpName(QOP_YEAR_GTE) = "year is on or after"
     OpName(QOP_YEAR_LTE) = "year is on or before"

     'Month operators

     OpName(QOP_MONTH_EQ) = "month is"
     OpName(QOP_MONTH_NEQ) = "month is not"
     OpName(QOP_MONTH_GT) = "month is after"
     OpName(QOP_MONTH_LT) = "month is before"
     OpName(QOP_MONTH_GTE) = "month is on or after"
     OpName(QOP_MONTH_LTE) = "month is on or before"

     'Day operators

     OpName(QOP_DAY_EQ) = "day is"
     OpName(QOP_DAY_NEQ) = "day is not"
     OpName(QOP_DAY_GT) = "day is after"
     OpName(QOP_DAY_LT) = "day is before"
     OpName(QOP_DAY_GTE) = "day is on or after"
     OpName(QOP_DAY_LTE) = "day is on or before"

     'Hour operators

     OpName(QOP_HOUR_EQ) = "hour is"
     OpName(QOP_HOUR_NEQ) = "hour is not"
     OpName(QOP_HOUR_GT) = "hour is after"
     OpName(QOP_HOUR_LT) = "hour is before"
     OpName(QOP_HOUR_GTE) = "hour is at or after"
     OpName(QOP_HOUR_LTE) = "hour is at or before"

     'Minute operators

     OpName(QOP_MINUTE_EQ) = "minute is"
     OpName(QOP_MINUTE_NEQ) = "minute is not"
     OpName(QOP_MINUTE_GT) = "minute is after"
     OpName(QOP_MINUTE_LT) = "minute is before"
     OpName(QOP_MINUTE_GTE) = "minute is at or after"
     OpName(QOP_MINUTE_LTE) = "minute is at or before"

     'Second operators

     OpName(QOP_SECOND_EQ) = "second is"
     OpName(QOP_SECOND_NEQ) = "second is not"
     OpName(QOP_SECOND_GT) = "second is after"
     OpName(QOP_SECOND_LT) = "second is before"
     OpName(QOP_SECOND_GTE) = "second is at or after"
     OpName(QOP_SECOND_LTE) = "second is at or before"
 End Sub

 Sub Init_QRYCODES ()
     Init_OpName_array
 End Sub


SMSAPI.BAS

Option Explicit
'// **************************************************************************
'//
'//  File: SMSAPI.H
'//
'//  Copyright (c) 1994, 1995 Microsoft Corp.
'//
'//  This is the header file for the SMS API.
'//
'//  This file contains all data definitions, manifests, return codes and
'//  API declarations that constitute the API set.
'//
'//
'// **************************************************************************
'
'
'
'// **************************************************************************
'//
'//  The SMS API set provides a set of interfaces to enable querying and,
'//  in some cases creation and deletion, of data from an SMS database.
'//
'//  SMS contains numerous objects to which an ISV needs access. These objects
'//  are (not in any particular order): packages, jobs, workstation command
'//  lines, program items, sites, domains, machines, machine groups.
'//
'//  The SMS API is built using a technology known as the objectity technique.
'//  This technique expresses all objects in terms of containers, folders,
'//  and scalars. All SMS objects are thus expressed in these terms.
'//  The advantage of this is that we have a standard interface to all
'//  objects. That is, the API does not distinguish between, say, a job and
'//  a machine.
'//
'//  The starting point is the container. There are certain types of container
'//  defined (container types below). A container is opened and then
'//  populated. The act of populating a container causes it to be filled with
'//  folders of defned types. A folder can contain sub-folders and scalars.
'//  Scalars are akin to leaf nodes in a hierarchy graph. The contain a
'//  name, type, and value.
'//
'//  Much of the power to the objectity technique is provided by filters.
'//  These are objects which specify selection criteria. Filters are applied
'//  by the user at the container level, at which point the implementation of
'//  the API will assume ownership of the filter. It is guaranteed that only
'//  folders matching the filters will be included in the container hierarchy.
'//
'//  Every folder can contain a set of scalars. Not all folders do, for
'//  example a machine folder does not. In some cases a folder contains a
'//  defined set of scalars, in other cases the scalar set can only be
'//  determined at run time. In cases where the scalar set is predetermined
'//  it will be documented in supporting documentation.
'//
'//  The APIs behave in an uniform manner.
'//  1. They all return a status code (SMS_STATUS).
'//  2. Where a value needs to be returned, it is done via a reference
'//     parameter.
'//
'//  An API is provided to determine the API version. Any problems involving
'//  the SMS API should include the information provided by this API.
'//
'//  The first step in using the APIs to to establish a connection to a
'//  datasource. Currently the only datasource supported in SQL, but the
'//  API is designed in such a way that additional datasources can be
'//  added. Two APIs are provided for dealing with datasources:
'//  DataSourceConnect and DataSourceDisonnect. The connect API returns a
'//  connection handle, this is used in subsequent calls to open containers.
'//
'//  Apart from these APIs, the rest of the API set is divided into four
'//  groups:
'//  1. Container APIs.
'//  2. Folder APIs.
'//  3. Scalar APIs.
'//  4. Filter APIs.
'//
'//  1. Container APIs.
'//  These allow a container to be opened, a filter to be applied to a
'//  container, a container to be populated with folders, a folder to be
'//  retrieved, and the container to be closed. An API also exists to
'//  return the number of folders currently contained in a container.
'//
'//  2. Folder APIs.
'//  These allow a for retrieval of various types of information about a
'//  folder (its ID or name, its type, the number of sub-folders, the number
'//  of scalars). Retrieval of sub-folders within a folder in both a
'//  sequential fashion and by name. Retrieval of scalars (sequential and by
'//  name. And closure of the folder.
'//  An API exists to allow folder creation. However, this is implementation
'//  dependant. That is, most folders do not permit creation, some (eg
'//  ceratin types of jobs) do.
'//
'//  3. Scalar APIs.
'//  There is a bit of overlap between this and the previous group. Although
'//  scalar retrieval is included in the Folder API group, it can also be
'//  considered a scalar API.
'//  An API is provided to set/update the value of a scalar. This can
'//  currently only be done in a folder that is being newly created.
'//
'//  4. Filter APIs.
'//  APIs for opening and closing a filter, and adding tokens into a filter.
'//  Filters are composed of one or more tokens of the same type. Some filters
'//  permit grouping of tokens within the filter (ie machine filter). This
'//  allows for complex boolean expressions.
'//  Tokens differ between different filters, but have a common interface.
'//  Tokens are contructed via the SmsAddToken API.
'//
'//  5. Filter container APIs
'//  Certain filters can be persistent, that is they are saved in the database
'//  and can be reloaded by separate processes. These filters can be created
'//  either the APIs, or via the SMS Admin.
'//   These APIs enable the retrieval of these filters from the datasource
'//  and the commital of new filters into the datasource.
'//  Currently machine filters are the only filters capable of exhibiting
'//  persistence.
'//
'// **************************************************************************
'
'// ====================================================================
'//
'//  Includes
'//
'// ====================================================================
'
'/* VB: These includes must be explicitly listed in the VB makefile   */
'
'include qrycodes.bas         // The query codes for Machine queries.
'
'include smsinfo.bas          // Manifests related to scalar settings.
'
'include objectty.bas         // Objectity engine.
'
'
'// ====================================================================
'//
'//  Datasource definition information.
'//
'// ====================================================================
'
'//
'// Supported datasources.
'// (NOTE: only sql supported currently).
'//
 Type DATASOURCE_TYPE
     enum As Long
 End Type

 ' These are initialized in Init_DATASOURCE_TYPE_enums()
 Global DB_SQL As DATASOURCE_TYPE
 Global DB_SQL_CONNECTED As DATASOURCE_TYPE
 Global DB_OTHER As DATASOURCE_TYPE
'
'
'//
'// Prototype for a decryption function. This is the last parameter
'// to the SQL_CONNECT_PARAMS structure.
'// When a user calls the DataSourceConnect API he can pass in the
'// user name and password in encrypted form. If the decryption function
'// pointer is non-null the the API will attempt to call this function
'// both for the user name and password.
'// The decryption function is user-supplied, it is expected to return
'// the decrypted data in the first parameter.
'//
'
'typedef void (*PDECRYPTFUNC)(char *pszBuffer, char *pszDecryptMe, char *pszKey);
'/* VB: This can not be done this way in VB  */
'
'
'//
'//  The information block needed for the DataSourceConnect API.
'//  1. SQL datasource.
'//
'
 Type SQL_CONNECT_PARAMS
     ds As DATASOURCE_TYPE
     pszServer As String                 '// SQL Server name.
     pszUserName As String               '// User name (possibly encrypted).
     pszPasswd As String                 '// Password (possibly encrypted).
     pszDbName As String                 '// Database name.
     pszKey As String                    '// Decryption key.
     pFunc As Long                       '// Pointer to a decryption function.
 End Type
'
'
'//
'//  2. RESERVED DO NOT USE
'//
'
'typedef struct _SQL_CONNECT_CONNECTED_PARAMS {
'    DATASOURCE_TYPE ds;
'    HANDLE hDataSource;
'    const char *pszAccessType;
'} SQL_CONNECT_CONNECTED_PARAMS;
'
'
'
'//
'// This only has the type field. It is used for extracting the type
'// in such a fashion that user code doesn't have to imply any
'// particular data source. It has no use other than that.
'//
'
 Type GENERIC
     ds As DATASOURCE_TYPE
 End Type
'
'
'
'// We can now define the DATASOURCE. It is a union of the structs above.
'typedef union {
'    GENERIC             type;
'    SQL_CONNECT_PARAMS  sqlParams;
'    SQL_CONNECT_CONNECTED_PARAMS sqlConnectedParams;
'} DATASOURCE;
'
'/* VB: Unions can not be done in VB  */
'
'
'
'// ====================================================================
'//
'//  Return codes. See also objectty.h.
'//
'// ====================================================================
'typedef DWORD SMS_STATUS;           // All APIs return an SMS_STATUS.
'/* VB: All API's return Long  */
'
 Global Const SMS_OK = OTT_OK
 Global Const SMS_MORE_DATA = OTT_MORE_DATA
 Global Const SMS_NO_MORE_DATA = OTT_NO_MORE_DATA
 Global Const SMS_FOLDER_NO_UPDATE = OTT_FOLDER_NO_UPDATE
'
'// Error codes when registering (and accessing) containers and filters
 Global Const SMS_DUPLICATE_CONTAINER_ID = OTT_DUPLICATE_CONTAINER_ID
 Global Const SMS_DUPLICATE_FOLDER_ID = OTT_DUPLICATE_FOLDER_ID
 Global Const SMS_DUPLICATE_FILTER_ID = OTT_DUPLICATE_FILTER_ID
'
 Global Const SMS_DUPLICATE_FILTER = OTT_DUPLICATE_FILTER
'
 Global Const SMS_SCALAR_NO_UPDATE = OTT_SCALAR_NO_UPDATE
 Global Const SMS_SCALAR_WRONG_TYPE = OTT_SCALAR_WRONG_TYPE
 Global Const SMS_INVALID_CONTAINER_ID = OTT_INVALID_CONTAINER_ID
 Global Const SMS_INVALID_FOLDER_ID = OTT_INVALID_FOLDER_ID
 Global Const SMS_INVALID_FILTER_ID = OTT_INVALID_FILTER_ID
'
 Global Const SMS_CONTAINER_NO_UPDATE = OTT_CONTAINER_NO_UPDATE
'
 Global Const SMS_PARAMETER_ERROR = OTT_PARAMETER_ERROR
 Global Const SMS_UNSUPPORTED = OTT_UNSUPPORTED
'
'// This is returned when a specific object (folder or scalar) is
'// requested and not found. APIs returning this are GetFolderByID
'// and GetScalarByName.
 Global Const SMS_NOT_FOUND = OTT_NOT_FOUND
'
 Global Const SMS_PARENT_NOT_FOUND = OTT_PARENT_NOT_FOUND
'
 Global Const SMS_CANT_DELETE = OTT_CANT_DELETE
'
 Global Const SMS_NEW_FOLDER = OTT_NEW_FOLDER
'
'// Error when attempting to multiply link a folder.
 Global Const SMS_FOLDER_LINKED = OTT_FOLDER_LINKED
'
'// Filter operations.
 Global Const SMS_RANGE_ERROR = OTT_RANGE_ERROR
'
'// These are returned when a container operation is tried
'// when it shouldn't be.
 Global Const SMS_CONTAINER_NOT_POPULATED = OTT_CONTAINER_NOT_POPULATED
 Global Const SMS_CONTAINER_POPULATED = OTT_CONTAINER_POPULATED
'
'
'// Attempt to perform persistent operation on a non-persistent filter.
 Global Const SMS_FILTER_NOT_PERSISTENT = OTT_FILTER_NOT_PERSISTENT
'
'// Error return if the connection handle of a filter doesn't match
'// that of a container to which the filter is being applied.
 Global Const SMS_INVALID_CONNECTION_HANDLE = OTT_INVALID_CONNECTION_HANDLE
 Global Const SMS_INVALID_FILTER_TYPE = OTT_INVALID_FILTER_TYPE

'// Folder's can't be unlinked if there are multiple handles
'// to the same object.
 Global Const SMS_IN_USE_BY_OTHER = OTT_IN_USE_BY_OTHER
'
'
'// User (ie non-engine) codes begin here.
 Global Const SMS_ERROR = OTT_END + 1
 Global Const SMS_SQL_ERROR = OTT_END + 2
 Global Const SMS_INVALID_DATASOURCE = OTT_END + 3
 Global Const SMS_INVALID_HANDLE = OTT_END + 4
 Global Const SMS_INVALID_FOLDER_TYPE = OTT_END + 6
'
 Global Const SMS_CONNECT_FAILED = OTT_END + 7
 Global Const SMS_NO_CREATE = OTT_END + 8
 Global Const SMS_FOLDER_NOT_MODIFIED = OTT_END + 9
 Global Const SMS_INCORRECT_FOLDER_TYPE = OTT_END + 10 'Unused.
 Global Const SMS_INVALID_PARAMETER = OTT_END + 11
 Global Const SMS_EMPTY = OTT_END + 12
'
'// Error when attempting to commit a child folder when the
'// parent folder must be committed.
 Global Const SMS_PARENT_NEEDS_COMMIT = OTT_END + 13
 Global Const SMS_PARENT_ALREADY_COMMITTED = OTT_END + 14
'
'// Used in asynchronous folder retrieval. Indicates that there
'// might be more data but it isn't available yet.
 Global Const SMS_NOT_READY = OTT_END + 15
'
 Global Const SMS_FILTER_NO_MODIFY = OTT_END + 16
 Global Const SMS_FOLDER_ALREADY_DELETED = OTT_END + 17
'
'// Only allow one active connection. This is an SMS restriction.
 Global Const SMS_ALREADY_CONNECTED = OTT_END + 18

'// Some failure in committing a folder
 Global Const SMS_COMMIT_FAILED = OTT_END + 19

'// Specific failure in linking a folder
 Global Const SMS_DUPLICATE_FOLDER = OTT_END + 20

 Global Const SMS_ERROR_LAST = OTT_END + 20

'Error strings
'This array is initialized in Init_SMSError_array()
Global SMSError$(0 To SMS_ERROR_LAST)

'
'// ====================================================================
'//
'//  Various manifests.
'//
'// ====================================================================
'
'// The location of the SMS registry root.
 Global Const SMS_REG_ROOT = "SOFTWARE\Microsoft\SMS\"
'
'// The max size of character (string) data that we return.
 Global Const SMS_DATA_BUFF_SIZE = 255
'
'
'// 'SMSBUFF' and 'OBJDESCRIPTOR' used in object type enumeration APIs.
'// ===================================================================

'// A character buffer.
'// -------------------
'typedef char SMSBUFF[SMS_DATA_BUFF_SIZE+1];

'// Describes an object returned from SmsEnumObjects.
'// -------------------------------------------------
 Type OBJDESCRIPTOR
     objType As Long                   '// Type of this object.
     szName As String                  '// Object name (eg 'Personal Computer')
     szFriendlyName As String          '// Friendly name. Only used in groups
                                       '// where szName would be, eg,
                                       '// 'MICROSOFT|IDENTIFICATION|1.0', the
                                       '// friendly name would be 'Identification'.
     bGotFriendlyName As Long          '// TRUE if we have a friendly name.

     dwRelopMin As Long                '// For attributes, indicates range of
     dwRelopMax As Long                '// relational operators that can be used
                                       '// for this attribute.
     bGotRelops As Long                '// TRUE if we have the relops fields set.

 End Type

'// Object types.
'// =============
'// This is the value that is inserted by the object
'// enumeration APIs into the OBJDESCRIPTOR structure.
'// --------------------------------------------------
 Global Const OT_ARCHLIST = 1
 Global Const OT_ARCH = 2
 Global Const OT_GROUP = 3
 Global Const OT_ATTRIBUTE = 4

 Global Const OT_PLATFORMS = 5
 Global Const OT_PLATFORM_PDF = 6
 Global Const OT_PLATFORM_WINST = 7
 Global Const OT_PLATFORM_NAD = 8

 Global Const OT_UNKNOWN = 100

'// --------------------------------------------------------------------
'// Flags for Populate options.
'// --------------------------------------------------------------------
 Global Const POP_SYNC = &H0&
 Global Const POP_ASYNC = &H1&
 Global Const POP_EVENT = &H10&
 Global Const POP_WINDOW = &H20&
'
'
'// --------------------------------------------------------------------
'// Folder types supported by smsapi (MUST NOT BEGIN AT ZERO).
'// --------------------------------------------------------------------
 Global Const F_SITE = 1                 'Site folder
 Global Const F_MACHINE = 2              'Machine folder
 Global Const F_GROUP = 3                'Subfolder of machine
 Global Const F_DOMAIN = 4               'Domain folder, subfolder of a site

'// Jobs
 Global Const F_SYSTEMJOB = 5            'System (NAD/Site) job folder
'Global Const F_MINIJOB = 6              'Mini-job folder
 Global Const F_INSTALLJOB = 7           'Job folder (wksta install)
 Global Const F_SRVINSTALLJOB = 8        'Job folder (server install)
 Global Const F_REMPKGJOB = 9            'Remove package Job folder
'
 Global Const F_PACKAGE = 10             'Packages
 Global Const F_WCL = 11                 'Workstation command lines
 Global Const F_PROGITEM = 12            'Program items
 Global Const F_INVENTORY = 13           'Inventory rules.
'
 Global Const F_MACHINEGROUP = 14        'Machine group
 Global Const F_SITEGROUP = 15           'Site group folder
'
'// --------------------------------------------------------------------
'// Container types supported by smsapi (MUST NOT BEGIN AT ZERO).
'// --------------------------------------------------------------------
 Global Const C_SITE = 1                 'Site container
 Global Const C_JOB = 2                  'Job container
 Global Const C_MACHINE = 3              'Machine container
 Global Const C_PACKAGE = 4              'Package container
 Global Const C_MACHINEGROUP = 5         'Machine group
 Global Const C_SITEGROUP = 6            'Site group
'
'
'// ====================================================================
'//
'//      Filter information.
'//
'// ====================================================================
'
'
'// --------------------------------------------------------------------
'//  Filter types
'// --------------------------------------------------------------------
 Global Const SITE_FILTER = 1
 Global Const MACHINE_FILTER = 2
 Global Const GROUP_FILTER = 3
 Global Const JOB_FILTER = 4
 Global Const ARCHITECTURE_FILTER = 5
 Global Const PACKAGE_FILTER = 6
 Global Const ATTRIBUTE_FILTER = 7
 Global Const SITELIMIT_FILTER = 8
'
'
'// --------------------------------------------------------------------
'// Token types.
'// --------------------------------------------------------------------
 Global Const TOKEN_USER = &H8      '1000 bit 3 always set for user-token
 Global Const TOKEN_OR = &H2        '0010 bit 1 always set for AND/OR
 Global Const TOKEN_AND = &H3       '0011 bit 1 always set for AND/OR
 Global Const TOKEN_OPENPAREN = &H4 '0100 bit 2 always set for PAREN
 Global Const TOKEN_CLOSEPAREN = &H5'0101 bit 2 always set for PAREN
 Global Const TOKEN_NOTIN = &H20    '10100 bit 2 set for a PAREN plus bit 4 for an extra indicator.
 Global Const TOKEN_OPERATOR = &H2  'Detects both AND or OR
 Global Const TOKEN_PAREN = &H4     'Detects either open or close paren
'
'
'// --------------------------------------------------------------------
'// Manifests used in filter token APIs.
'// --------------------------------------------------------------------
 Global Const AT_START = OTT_AT_START 'Token will be inserted at start of expression
 Global Const AT_END = OTT_AT_END     'Token will be appended to end of expression
'
'//
'// Flags fields in SmsManipulateTokens.
'//
 Global Const TOK_GROUP = 1          'Flags field in SmsManipulateTokens
 Global Const TOK_UNGROUP = 2
 Global Const TOK_DELETE = 3
 Global Const TOK_GROUP_NOT = 4
'
'
 Sub Init_DATASOURCE_TYPE_enums ()
     DB_SQL.enum = 0
     DB_SQL_CONNECTED.enum = 1
     DB_OTHER.enum = 2
 End Sub

 Function Init_SMSAPI% ()
     'Default return value True (DLL loaded)
     Init_SMSAPI% = True

     'Initialize "included" modules
     Init_OBJECTTY
     Init_QRYCODES

     'Initialize this module
     Init_DATASOURCE_TYPE_enums

     'Initialize error string array
     Init_SMSError_array
 End Function

 Sub Init_SMSError_array ()
     SMSError$(SMS_OK) = "SMS_OK"
     SMSError$(SMS_MORE_DATA) = "SMS_MORE_DATA"
     SMSError$(SMS_NO_MORE_DATA) = "SMS_NO_MORE_DATA"
     SMSError$(SMS_FOLDER_NO_UPDATE) = "SMS_FOLDER_NO_UPDATE"

'// Error codes when registering (and accessing) containers and filters
     SMSError$(SMS_DUPLICATE_CONTAINER_ID) = "SMS_DUPLICATE_CONTAINER_ID"
     SMSError$(SMS_DUPLICATE_FOLDER_ID) = "SMS_DUPLICATE_FOLDER_ID"
     SMSError$(SMS_DUPLICATE_FILTER_ID) = "SMS_DUPLICATE_FILTER_ID"
     SMSError$(SMS_DUPLICATE_FILTER) = "SMS_DUPLICATE_FILTER"

     SMSError$(SMS_SCALAR_NO_UPDATE) = "SMS_SCALAR_NO_UPDATE"
     SMSError$(SMS_SCALAR_WRONG_TYPE) = "SMS_SCALAR_WRONG_TYPE"
     SMSError$(SMS_INVALID_CONTAINER_ID) = "SMS_INVALID_CONTAINER_ID"
     SMSError$(SMS_INVALID_FOLDER_ID) = "SMS_INVALID_FOLDER_ID"
     SMSError$(SMS_INVALID_FILTER_ID) = "SMS_INVALID_FILTER_ID"

     SMSError$(SMS_CONTAINER_NO_UPDATE) = "SMS_CONTAINER_NO_UPDATE"

     SMSError$(SMS_PARAMETER_ERROR) = "SMS_PARAMETER_ERROR"
     SMSError$(SMS_UNSUPPORTED) = "SMS_UNSUPPORTED"

'// This is returned when a specific object (folder or scalar) is
'// requested and not found. APIs returning this are GetFolderByID
'// and GetScalarByName.
     SMSError$(SMS_NOT_FOUND) = "SMS_NOT_FOUND"

     SMSError$(SMS_PARENT_NOT_FOUND) = "SMS_PARENT_NOT_FOUND"

     SMSError$(SMS_CANT_DELETE) = "SMS_CANT_DELETE"

     SMSError$(SMS_NEW_FOLDER) = "SMS_NEW_FOLDER"

'// Error when attempting to multiply link a folder.
     SMSError$(SMS_FOLDER_LINKED) = "SMS_FOLDER_LINKED"

'// Filter operations.
     SMSError$(SMS_RANGE_ERROR) = "SMS_RANGE_ERROR"

'// These are returned when a container operation is tried
'// when it shouldn't be.
     SMSError$(SMS_CONTAINER_NOT_POPULATED) = "SMS_CONTAINER_NOT_POPULATED"
     SMSError$(SMS_CONTAINER_POPULATED) = "SMS_CONTAINER_POPULATED"

'// Attempt to perform persistent operation on a non-persistent filter.
     SMSError$(SMS_FILTER_NOT_PERSISTENT) = "SMS_FILTER_NOT_PERSISTENT"

'// Error return if the connection handle of a filter doesn't match
'// that of a container to which the filter is being applied.
     SMSError$(SMS_INVALID_CONNECTION_HANDLE) = "SMS_INVALID_CONNECTION_HANDLE"

'// User (ie non-engine) codes begin here.
     SMSError$(SMS_ERROR) = "SMS_ERROR"
     SMSError$(SMS_SQL_ERROR) = "SMS_SQL_ERROR"
     SMSError$(SMS_INVALID_DATASOURCE) = "SMS_INVALID_DATASOURCE"
     SMSError$(SMS_INVALID_HANDLE) = "SMS_INVALID_HANDLE"
     SMSError$(SMS_INVALID_FILTER_TYPE) = "SMS_INVALID_FILTER_TYPE"
     SMSError$(SMS_INVALID_FOLDER_TYPE) = "SMS_INVALID_FOLDER_TYPE"

     SMSError$(SMS_CONNECT_FAILED) = "SMS_CONNECT_FAILED"
     SMSError$(SMS_NO_CREATE) = "SMS_NO_CREATE"
     SMSError$(SMS_FOLDER_NOT_MODIFIED) = "SMS_FOLDER_NOT_MODIFIED"
     SMSError$(SMS_INCORRECT_FOLDER_TYPE) = "SMS_INCORRECT_FOLDER_TYPE"
     SMSError$(SMS_INVALID_PARAMETER) = "SMS_INVALID_PARAMETER"
     SMSError$(SMS_EMPTY) = "SMS_EMPTY"

'// Error when attempting to commit a child folder when the
'// parent folder must be committed.
     SMSError$(SMS_PARENT_NEEDS_COMMIT) = "SMS_PARENT_NEEDS_COMMIT"
     SMSError$(SMS_PARENT_ALREADY_COMMITTED) = "SMS_PARENT_ALREADY_COMMITTED"

'// Used in asynchronous folder retrieval. Indicates that there
'// might be more data but it isn't available yet.
     SMSError$(SMS_NOT_READY) = "SMS_NOT_READY"

     SMSError$(SMS_FILTER_NO_MODIFY) = "SMS_FILTER_NO_MODIFY"
     SMSError$(SMS_FOLDER_ALREADY_DELETED) = "SMS_FOLDER_ALREADY_DELETED"

'// Only allow one active connection (SMS restriction).
     SMSError$(SMS_ALREADY_CONNECTED) = "SMS_ALREADY_CONNECTED"
 End Sub


SMSINFO.BAS

Option Explicit
'// **************************************************************************
'//
'//  File: SMSINFO.H
'//
'//  Copyright (c) 1995 Microsoft Corp.
'//
'//  This is an auxiliary header file for the SMS API.
'//  It contains manifests used in scalars and filters.
'//
'//  GFF@SEA          17-Mar-95   VBified.
'//
'// **************************************************************************
'
'#ifndef _SMSINFO_H_
'#define _SMSINFO_H_
'
'
'// ==========================================================================
'//
'//  Package folders.
'//
'// ==========================================================================
'
'//
'//  These defines are used in Share and Workstation permissions scalars.
'//  These scalars must be a combination of any (or none) of these defines.
'//
 Global Const PACKAGE_USER_READ = &H1&
 Global Const PACKAGE_USER_WRITE = &H2&
 Global Const PACKAGE_GUEST_READ = &H4&
 Global Const PACKAGE_GUEST_WRITE = &H8&
'
'
'//
'//  Values for scalar: 'Platforms'
'//  ----------------------------------------------
'// Both the Workstation Command Line folder and the Program Item folders
'// have a 'Platforms' scalar. The values are identical.
'// NOTE: This list may be periodically updated by Microsoft when new
'// versions of SMS are released.
'//
 Global Const PLTFRM_WIN16 = &H1&
 Global Const PLTFRM_WIN32_X86 = &H2&
 Global Const PLTFRM_WIN32_MIPS = &H4&
 Global Const PLTFRM_WIN32_ALPHA = &H8&
 Global Const PLTFRM_DOS5 = &H10&
 Global Const PLTFRM_DOS6 = &H20&
 Global Const PLTFRM_MACINTOSH = &H40&
 Global Const PLTFRM_WIN95 = &H80&
 Global Const PLTFRM_MSDOS = PLTFRM_DOS5 + PLTFRM_DOS6
'
'
'// ========================================================================
'// This is a list of the platforms that are supported for PDF files
'// in SMS 1.2. This list is for reference.
'//      "Macintosh"
'//      "MS-DOS 5.0"
'//      "MS-DOS 6.0"
'//      "MS-DOS 6.2"
'//      "MS-DOS 6.21"
'//      "MS-DOS 6.22"
'//      "Windows 3.1"
'//      "Windows NT 3.1 (Alpha)"
'//      "Windows NT 3.1 (MIPS)"
'//      "Windows NT 3.1 (x86)"
'//      "Windows NT 3.5 (Alpha)"
'//      "Windows NT 3.5 (MIPS)"
'//      "Windows NT 3.5 (x86)"
'//      "Windows NT 3.51 (Alpha)"
'//      "Windows NT 3.51 (MIPS)"
'//      "Windows NT 3.51 (x86)"
'//      "Windows NT 4.0 (Alpha)"
'//      "Windows NT 4.0 (MIPS)"
'//      "Windows NT 4.0 (x86)"
'//      "Windows95"
'// ========================================================================
'
'
'
'
'// ==========================================================================
'//
'//  Program Item folders.
'//
'// ==========================================================================
'
'//
'//  Values for scalar: 'Drive mode'.
'//  ----------------------------------------------
'// This scalar corresponds directly to the 'Drive Mode' group of radio
'// buttons in the 'Program Item Properties' dialogue (share package
'// job only). One of these must be selected.
'//
 Global Const PI_DRIVEMODE_UNC = 1
 Global Const PI_DRIVEMODE_ANY_LETTER = 2
 Global Const PI_DRIVEMODE_SPECIFIC_LETTER = 3
'
'
'
'// ==========================================================================
'//
'//  Workstation Command Line (WCL) Job folder.
'//  ------------------------------------------
'//
'// ==========================================================================
'
'
'//
'//  Scalar: Flags
'//  ----------------------------------------------
'// This scalar covers the 'Automated Command Line' and
'// 'System (Background) Task' check boxes in the Command Line
'// Properties dialogue.
'// Note that setting the flags to BACKGROUND_TASK also sets the
'// AUTOMATED_COMMAND_LINE bit.
'//
 Global Const WCL_AUTOMATED_COMMAND_LINE = &H1
 Global Const WCL_BACKGROUND_TASK = &H3
'
'
'
'// ==========================================================================
'//
'//  Job folders.
'//
'//  General values that are common to all types of job folders
'//  ----------------------------------------------------------
'//
'// ==========================================================================
'
'
'// ==========================================================================
'//
'//      Job types as strings.
'//
'// ==========================================================================
'
 Global Const STR_JOBTYPE_INSTALL = "Install"
 Global Const STR_JOBTYPE_SERVER = "Server"
 Global Const STR_JOBTYPE_RMPKG = "Remove package"
 Global Const STR_JOBTYPE_SYSTEM = "System"
 Global Const STR_JOBTYPE_MINIJOB = "Minijob"
'
'
'// ==========================================================================
'//
'//      Job stati as strings.
'//
'// ==========================================================================
'
 Global Const STR_JOB_PENDING = "Pending"
 Global Const STR_JOB_ACTIVE = "Active"
 Global Const STR_JOB_CANCELLED = "Cancelled"
 Global Const STR_JOB_COMPLETE = "Complete"
 Global Const STR_JOB_FAILED = "Failed"
 Global Const STR_JOB_ACTIVE_FAILED = "Active failed"
 Global Const STR_JOB_ACTIVE_UNKNOWN = "Unknown"
'
'
'//
'//  Values for scalar: 'Limit to sites'
'//  ----------------------------------------------
'// This can be either JOBTGT_SITE or JOBTGT_SITEGROUP. If either of
'// these are set then the scalar 'Include subsites' can be set.
'// If neither site nor sitegroup limiting is required then this scalar
'// should be set to JOBTGT_NOSITELIMIT (default).
'//
 Global Const JOBTGT_NOSITELIMIT = 0
 Global Const JOBTGT_SITEGROUP = 1
 Global Const JOBTGT_SITE = 2
'
'
'//
'//  Values for scalar: 'Include subsites'
'//  ----------------------------------------------
'// This can be set to either 1 or 0. If the 'Limit to sites'
'// scalar is not set then this scalar will be ignored.
'//
 Global Const JOBTGT_NOSUBSITES = 0
 Global Const JOBTGT_INCLUDESUBSITES = 1
'
'
'//
'//  Values for scalar: 'Job status'
'//  ----------------------------------------------
'// This scalar corresponds to the 'Overall Status' display in
'// in the 'Job Status' dialogue. It is a read-only scalar, the values
'// are presented here for clarity.
'//
 Global Const JOBSTAT_PENDING = 1
 Global Const JOBSTAT_ACTIVE = 2
 Global Const JOBSTAT_CANCELLED = 3
 Global Const JOBSTAT_COMPLETE = 4
 Global Const JOBSTAT_FAILED = 5
 Global Const JOBSTAT_RETRYING = 6
'
'
'//
'//  Values for scalar: 'Priority'
'//  ----------------------------------------------
'// This scalar corresponds to the 'Priority' edit control in the
'// 'Job Schedule' dialogue box.
'//
 Global Const JOBPRI_HIGH = 1
 Global Const JOBPRI_MEDIUM = 2
 Global Const JOBPRI_LOW = 3
'
'//
'//  Values for scalar: 'Repeat mode'
'//  ----------------------------------------------
'// This scalar corresponds to the 'Repeat' edit control in the
'// 'Job Schedule' dialogue box.
'//
 Global Const JOBRPT_NEVER = 1
 Global Const JOBRPT_DAILY = 2
 Global Const JOBRPT_WEEKLY = 3
 Global Const JOBRPT_BIWEEKLY = 4
 Global Const JOBRPT_MONTHLY = 5
'
'
'//
'//  Values for scalar: 'Cancel mode'
'//  ----------------------------------------------
'// This scalar has no direct equivalent in any dialogue box. It corresponds
'// instead to the 'Cancel Job' menu selection in the Edit menu when the
'// focus is on the Jobs window.
'//
 Global Const JOBCANCEL_DONT_CANCEL = 0
 Global Const JOBCANCEL_CANCEL = 1
'
'// ==========================================================================
'//
'//  Job folders: Remove package job.
'//  --------------------------------
'//
'// ==========================================================================
'
'//
'//  Scalar: Package type
'//  ----------------------------------------------
'//  This scalar is either workstation, server, or both as below.
'//
 Global Const RMPKGJOB_PKGTYPE_WKSTA = &H1                               '*/
 Global Const RMPKGJOB_PKGTYPE_SERVER = &H2                              '*/
 Global Const RMPKGJOB_PKGTYPE_BOTH = RMPKGJOB_PKGTYPE_WKSTA + RMPKGJOB_PKGTYPE_SERVER '*/
'
'//
'//  Scalar: Job tasks
'//  ----------------------------------------------
'//
 Global Const RMPKGJOB_TASKS_RM_ALL = 1
 Global Const RMPKGJOB_TASKS_RM_SELECTED = 2
'
'//  Setting 'Job tasks' to TASKS_RM_SELECTED means that the user
'//  must set the 'Site group' scalar to a valid name.
'
'
'//  Setting the 'Job target' scalar to TGT_SITEGROUP means that the user
'//  must provide, in the 'Site limit name' scalar, a name of a machine group.
'//  The TGT_INCLUDESUBSITES flag is only valid if TGT_SITEGROUP has also
'//  been specified.
'
'
'
'// ==========================================================================
'//
'//  Job folders: Run command on workstation job.
'//  --------------------------------------------
'//
'// ==========================================================================
'
'//
'//  Scalar: Job target
'//  ----------------------------------------------
'// Select from one of the following values. These correspond directly
'// to the radio buttons in the 'Job Target' group in the Job Details
'// dialogue for a run command on workstation job.
'//
 Global Const WKSTAJOB_TGT_QUERY = 1
 Global Const WKSTAJOB_TGT_MACHGROUP = 2
 Global Const WKSTAJOB_TGT_MACHPATH = 3
'
'//
'//  Scalar: Distribute phase
'//  ----------------------------------------------
'// This scalar corresponds to the 'Distribute Phase' group.
'// The value can be either, both, or neither of the following values,
'// ie 0, 1, 2, or 3.
'//
 Global Const WKSTAJOB_DIST_EXISTING = &H1
 Global Const WKSTAJOB_DIST_SPECIFIED = &H2
'
'//
'//  Scalar: Send phase
'//  ----------------------------------------------
'// These values correspond to the 'Only if not previously sent' and
'// 'Even if previously sent' radio buttons in the SMS Admin for the
'// send phase group for a Run Command on Workstation job.
'//
 Global Const WKSTAJOB_SEND_IF_NOT_SENT = 1
 Global Const WKSTAJOB_SEND_ALWAYS = 2
'
'
'
'// ==========================================================================
'//
'//  Job folders: Share package job.
'//  -------------------------------
'//
'// ==========================================================================
'
'
'//
'//  Scalar: Send phase
'//  ----------------------------------------------
'// These values correspond to the 'Only if not previously sent' and
'// 'Even if previously sent' radio buttons in the SMS Admin for the
'// send phase group for a Share package job.
'//
 Global Const SHAREJOB_SEND_IF_NOT_SENT = 1
 Global Const SHAREJOB_SEND_ALWAYS = 2
'
'
'//
'//  Scalar: Distribute phase
'//  ----------------------------------------------
'// This scalar corresponds to the 'Distribute Phase' group.
'// The value can be either, both, or neither of the following values,
'// ie 0, 1, 2, or 3.
'//
 Global Const SHAREJOB_DIST_EXISTING = &H1
 Global Const SHAREJOB_DIST_SPECIFIED = &H2
'
'
'
'
'
'#endif  // _SMSINFO_H_
'
'/* EOF: smsinfo.h */


VB4SMS32.BAS

Attribute VB_Name = "basVB4SMS32"
Option Explicit
'//****************************************************************************
'//
'//  Copyright (c) 1996, Microsoft Corporation
'//
'//  File:  VB4SMS32.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   3/5/96      Created.
'//
'//****************************************************************************
'
'The Declare statements that used to appear in this module are no longer
'necessary because they are now declared in the type library.  Use the
'Object Browser to view these declarations (see the View menu).  However,
'Because of some "features" of VB4, some of the declarations in the
'type library can not be used and must be overridden with a Declare statement.
'Only the functions for which this is the case appear here.

'Because of yet another "feature" of VB4, some of the functions also needed a
'VB function wrapper.  These VB wrappers appear in this module as well.
'
'

'// ====================================================================
'//      Version number API.
'// ====================================================================
'
'  See also the Object Browser for declarations in the type library.
'
'
'// ====================================================================
'//      Engine APIs.
'// ====================================================================
'
   Declare Function SmsDescribeFolderU& Lib "vb4sms32.olb" Alias "SmsDescribeFolderVB" (tObjectity As BASETYPE, ByVal dwTag As Long, pFolderInfo() As FOLDER_INFO)
'
   Declare Function SmsEnumContainers& Lib "vb4sms32.olb" Alias "SmsEnumContainersVB" (ContainerInfos() As FOLDER_INFO, pCount As Long)
'
   Declare Function SmsEnumFolders& Lib "vb4sms32.olb" Alias "SmsEnumFoldersVB" (FolderInfos() As FOLDER_INFO, pCount As Long)
'
   Declare Function SmsEnumFilters& Lib "vb4sms32.olb" Alias "SmsEnumFiltersVB" (FilterInfos() As FILTER_INFO, pCount As Long)
'
'  See also the Object Browser for declarations in the type library.
'
'
'// ====================================================================
'//      Connection APIs.
'// ====================================================================
'
   Declare Function SmsDataSourceConnect& Lib "vb4sms32.olb" Alias "SmsDataSourceConnectVB" (pDataSource As SQL_CONNECT_PARAMS, phConn As Long)
'
'  See also the Object Browser for declarations in the type library.
'
'
'// ====================================================================
'//      Container APIS.
'// ====================================================================
'
   Declare Function SmsPopulate& Lib "vb4sms32.olb" Alias "SmsPopulateVB" (ByVal hContainer As Long, ByVal dwOptions As Long, pNotify As Any)
'
'  See also the Object Browser for declarations in the type library.
'
'
'// ====================================================================
'//  Folder (collection) APIs.
'//  A collection contains other things, that is it can have embedded
'//  collections and it can also have properties (scalars).
'// ====================================================================
'
'  See also the Object Browser for declarations in the type library.
'
'
'// ====================================================================
'//  Scalar APIs.
'// ====================================================================
'
   Declare Function SmsGetNextScalarU& Lib "vb4sms32.olb" Alias "SmsGetNextScalarVB" (ByVal hFolder As Long, pScalar() As SCALAR)
'
   Declare Function SmsGetScalarByNameU& Lib "vb4sms32.olb" Alias "SmsGetScalarByNameVB" (ByVal hFolder As Long, ByVal pszName As String, pScalar() As SCALAR)
'
   Declare Function SmsSetScalarU& Lib "vb4sms32.olb" Alias "SmsSetScalarVB" (ByVal hFolder As Long, pScalar() As SCALAR)
'
'  See also the Object Browser for declarations in the type library.
'
'
'// ====================================================================
'//  FilterContainer APIs
'// ====================================================================
'
'  See also the Object Browser for declarations in the type library.
'
'
'// ====================================================================
'//  Filter APIs
'// ====================================================================
'
   Declare Function SmsGetToken& Lib "vb4sms32.olb" Alias "SmsGetTokenVB" (ByVal hObject As Long, ByVal iIndex As Long, pTokenInfo As TOKEN)
'
   Declare Function SmsAddToken& Lib "vb4sms32.olb" Alias "SmsAddTokenVB" (ByVal hObject As Long, opAndOr As ANDOR, pTokenInfo As TOKEN, ByVal iIndex As Long)
'
   Declare Function SmsEnumObjects& Lib "vb4sms32.olb" Alias "SmsEnumObjectsVB" (ByVal hConnection As Long, ByVal pszObjectType As String, pPredecessors() As String, ByVAl CtPredecessors As Long, pObjects() As OBJDESCRIPTOR, pCtObjects As Long)
'
'  See also the Object Browser for declarations in the type library.

Function SmsDescribeFolder&(tObjectity As BASETYPE, ByVal dwTag As Long, pFolderInfo As FOLDER_INFO)
    Dim FolderInfos(0) As FOLDER_INFO
    Dim lRet&

    lRet& = SmsDescribeFolderU&(tObjectity, dwTag, FolderInfos)
    If (lRet& = SMS_OK) Then pFolderInfo = FolderInfos(0)

    SmsDescribeFolder& = lRet&
End Function
Function SmsGetNextScalar&(ByVal hFolder As Long, pScalar As SCALAR)
    Dim Scalars(0) As SCALAR
    Dim lRet&

    lRet& = SmsGetNextScalarU&(hFolder, Scalars)
    If (lRet& = SMS_OK) Then pScalar = Scalars(0)

    SmsGetNextScalar& = lRet&
End Function
Function SmsGetScalarByName&(ByVal hFolder As Long, ByVal pszName As String, pScalar As SCALAR)
    Dim Scalars(0) As SCALAR
    Dim lRet&

    lRet& = SmsGetScalarByNameU&(hFolder, pszName, Scalars)
    If (lRet& = SMS_OK) Then pScalar = Scalars(0)

    SmsGetScalarByName& = lRet&
End Function

Function SmsSetScalar&(ByVal hFolder As Long, pScalar As SCALAR)
    Dim Scalars(0) As SCALAR

    Scalars(0) = pScalar
    SmsSetScalar& = SmsSetScalarU&(hFolder, Scalars)
End Function


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by VB4SMS32.rc
//

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


VB4SMS32.CPP

//****************************************************************************
//
//  Copyright (c) 1995, Microsoft Corporation
//
//  File:  VB4SMS32.CPP
//
//  Implementation file for the 32-bit Visual Basic interface DLL
//  for SMSAPI.DLL (a Win32 dll)
//
//  History:
//
//      Gary F. Fuehrer, SEA     01 Mar 96    Created.
//
//****************************************************************************

/*********************  Header Files **********************/

#define STRICT
#define WIN32_EXTRA_LEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winnls.h>
#include <ole2.h>
#include <time.h>
#include <string.h>

/****************************************************************************/
#include "smsapi.h"
#include "vb4sms32.h"
typedef void* HLSTR;
typedef USHORTERR;// err

// Default selector limit for 32bit char*
#define MAX_VB_STRING  SMS_DATA_BUFF_SIZE+1

/**************************** Globals ***************************************/
static HANDLE  ghInst = NULL;

// An array of open connections.  This is merely a convenience for the user.
// Any connections made beyond MAX_CONNECTIONS won't be disconnected by WEP.
#define MAX_CONNECTIONS  16
static HANDLE     gahConnections[MAX_CONNECTIONS];

// General use buffer of length SMS_DATA_BUFF_SIZE
static char gStrBuff[SMS_DATA_BUFF_SIZE + 1];

// General use buffer of length SMS_DATA_BUFF_SIZE
static char gNameBuff[SMS_DATA_BUFF_SIZE + 1];

// General use TOKEN buffer
static TOKEN gTokenBuff;

/****************************************************************************/

/********************************************************************\
* Function: int FAR PASCAL LibMain( HINSTANCE, WORD, WORD, LPSTR )   *
*                                                                    *
*  Purpose: DLL entry point                                          *
*                                                                    *
* Comments: Loads the Win32 stub application                         *
*                                                                    *
*                                                                    *
\********************************************************************/

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    int nIndex;

    switch( ul_reason_for_call ) {
    case DLL_PROCESS_ATTACH:
        ghInst = hModule;
        break;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_THREAD_DETACH:
        break;

    case DLL_PROCESS_DETACH:
        // Disconnect any known open connections
        for (nIndex = 0; nIndex < MAX_CONNECTIONS; nIndex++)
            if (gahConnections[nIndex])
                SmsDataSourceDisconnect(gahConnections[nIndex]);
        break;
    }
    return TRUE;
}

/*********************** Utility Routines and Macros ************************/

BYTE gbANSI = FALSE;
#define VBStringLength(String) ((gbANSI) ? SysStringByteLen(String) \
                                         : SysStringLen(String))

// Copy VB string to an ANSI SZ string
inline SMS_STATUS
CopyString2Sms(char *pszDest, BSTR pszSrc, UINT uLimit = MAX_VB_STRING)
{
    if (uLimit <= 0) return SMS_OK;

    UINT uLen = min(VBStringLength(pszSrc), uLimit-1);
    if (gbANSI)
        strncpy(pszDest, (char*)pszSrc, uLen);
    else WideCharToMultiByte(CP_ACP, 0, pszSrc, uLen, pszDest, uLimit,
                             NULL, NULL);
    pszDest[uLen] = '\0';

    return SMS_OK;
}

// Copy ANSI SZ string to a VB string (freeing the old one)
inline SMS_STATUS
CopyString2VB(BSTR* pBStr, const char *pszSrc, UINT uLimit = MAX_VB_STRING)
{
    UINT uLen = min(strlen(pszSrc), uLimit-1);

    // Allocate a VB string of this length
    BSTR pwsz = NULL;
    if (uLen != 0)
        pwsz = SysAllocStringByteLen(NULL, (gbANSI) ? uLen : uLen << 1);

    if (pwsz != NULL || uLen == 0)
    {
        if (*pBStr != NULL) SysFreeString(*pBStr);
        *pBStr = pwsz;

        if (pwsz == NULL) return SMS_OK;

        if (gbANSI)
            strncpy((char*)pwsz, pszSrc, uLen);
        else MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pszSrc, uLen,
                                 pwsz, uLen);

        return SMS_OK;
    }

    return SMS_ERROR;
}

///////////////////////////////////////////////////////////////////////
///////////     The APIs     //////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

// ====================================================================
//      Version number API.
// ====================================================================
                                                                        /* VB:
  Declare Function SmsAPIVer& Lib "vb4sms32.dll" (pVersion As String)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsAPIVerVB(BSTR* pbstrVersion)
{
    char      *pszVer32;
    SMS_STATUS ret;

    // Call 32bit SMS API routine
    ret = SmsAPIVer(&pszVer32);

    if (ret == SMS_OK)
        ret = CopyString2VB(pbstrVersion, pszVer32);

    return ret;
}

// ====================================================================
//      Connection APIs.
// ====================================================================
                                                                        /* VB:
  Declare Function SmsDataSourceConnect& Lib "vb4sms32.dll" (pDataSource As SQL_CONNECT_PARAMS, phConn As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsDataSourceConnectVB(SQL_CONNECT_PARAMS *pSqlCP, HANDLE *phConn)
{
    int nIndex;
    DATASOURCE ds;
    SMS_STATUS ret = SMS_ERROR;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to SQL_CONNECT_PARAMS is invalid)
    if (IsBadWritePtr(pSqlCP, sizeof(*pSqlCP)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // See if we can find an availiable connection slot.
    for (nIndex = 0; nIndex < MAX_CONNECTIONS; nIndex++)
        if (!gahConnections[nIndex]) break;

    // Call 32bit SMS API routine
    ds.sqlParams = *pSqlCP;
    ret = SmsDataSourceConnect(&ds, phConn);

    if (ret == SMS_OK && nIndex < MAX_CONNECTIONS)
        gahConnections[nIndex] = *phConn;

    return ret;
}

                                                                        /* VB:
  Declare Function SmsDataSourceDisconnect& Lib "vb4sms32.dll" (ByVal hConn As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsDataSourceDisconnectVB(HANDLE hConn)
{
    int        nIndex;
    SMS_STATUS ret = SMS_ERROR;

    // Try to find the connection in the list of known connections
    for (nIndex = 0; nIndex < MAX_CONNECTIONS; nIndex++)
        if (hConn == gahConnections[nIndex]) break;

    // Call 32bit SMS API routine
    ret = SmsDataSourceDisconnect(hConn);

    if (ret == SMS_OK && nIndex < MAX_CONNECTIONS)
        gahConnections[nIndex] = 0;

    return ret;
}

// ====================================================================
//      Engine APIs.
// ====================================================================

HRESULT
AllocVBArray(SAFEARRAY* &pArray, DWORD cArray, UINT uElemSize)
{

    if (pArray != NULL)
    {
        // See if the array is the right kind
        if (SafeArrayGetElemsize(pArray) != uElemSize) return E_INVALIDARG;

        // See if the array is already large enough
        if (cArray <= pArray->rgsabound[0].cElements) return SMS_OK;

        SAFEARRAYBOUND sab = pArray->rgsabound[0];
        sab.cElements = cArray;
        return SafeArrayRedim(pArray, &sab);
    }
    else
    {
        // Allocate an array descriptor for an array of user defined types
        HRESULT ret = SafeArrayAllocDescriptor(1, &pArray);
        if (ret != S_OK) return ret;

        // Set the element size and array bounds
        pArray->cbElements = uElemSize;
        pArray->rgsabound[0].lLbound = 0;
        pArray->rgsabound[0].cElements = cArray;
        return SafeArrayAllocData(pArray);
    }
}

SMS_STATUS
CopyFolderInfo2VB(FOLDER_INFO *pFolderInfoVB, FOLDER_INFO *pFolderInfo)
{
    SMS_STATUS ret;

    // Preserve the BSTR and SAFEARRAY members in the FOLDER_INFO from VB
    char*           pszTagVB = pFolderInfoVB->pszTag;
    DWORD*          pFolderTagsVB = pFolderInfoVB->pFolderTags;
    DWORD*          pFilterTagsVB = pFolderInfoVB->pFilterTags;
    SCALAR_INFOVB*  pScalarsVB = (SCALAR_INFOVB*)pFolderInfoVB->pScalars;

    // Copy the folder info structure into the VB array
    *pFolderInfoVB = *pFolderInfo;

    // Restore members of FOLDER_INFO before possible exit
    pFolderInfoVB->pszTag = pszTagVB;
    pFolderInfoVB->pFolderTags = pFolderTagsVB;
    pFolderInfoVB->pFilterTags = pFilterTagsVB;
    pFolderInfoVB->pScalars = (SCALAR_INFO*)pScalarsVB;

    // Copy the pszTag member
    ret = CopyString2VB((BSTR*)&pFolderInfoVB->pszTag, pFolderInfo->pszTag);
    if (ret != SMS_OK) return ret;

    // Copy the pFolderTags array
    if (pFolderInfo->ctFolders > 0)
    {
        // Create or Redim as necessary
        if (AllocVBArray((SAFEARRAY*&)pFolderInfoVB->pFolderTags,
                         pFolderInfo->ctFolders,
                         sizeof(*pFolderTagsVB)) != S_OK)
            return SMS_ERROR;

        // Lock down the array and obtain pointer to the data
        if (SafeArrayAccessData((SAFEARRAY*)pFolderInfoVB->pFolderTags,
                                (void**)&pFolderTagsVB) != S_OK)
            return SMS_ERROR;

        // Copy the array and unlock it
        memcpy(pFolderTagsVB, pFolderInfo->pFolderTags,
               pFolderInfo->ctFolders * sizeof(*pFolderTagsVB));
        SafeArrayUnaccessData((SAFEARRAY*)pFolderInfoVB->pFolderTags);
    }

    // Copy the pFilterTags array
    if (pFolderInfo->ctFilters > 0)
    {
        // Create or Redim as necessary
        if (AllocVBArray((SAFEARRAY*&)pFolderInfoVB->pFilterTags,
                         pFolderInfo->ctFilters,
                         sizeof(*pFilterTagsVB)) != S_OK)
            return SMS_ERROR;

        // Lock down the array and obtain pointer to the data
        if (SafeArrayAccessData((SAFEARRAY*)pFolderInfoVB->pFilterTags,
                                (void**)&pFilterTagsVB) != S_OK)
            return SMS_ERROR;

        // Copy the array and unlock it
        memcpy(pFilterTagsVB, pFolderInfo->pFilterTags,
               pFolderInfo->ctFilters * sizeof(*pFilterTagsVB));
        SafeArrayUnaccessData((SAFEARRAY*)pFolderInfoVB->pFilterTags);
    }

    // Copy the pScalars array
    if (pFolderInfo->ctScalars > 0)
    {
        // Create or Redim as necessary
        if (AllocVBArray((SAFEARRAY*&)pFolderInfoVB->pScalars,
                         pFolderInfo->ctScalars,
                         sizeof(*pScalarsVB)) != S_OK) return SMS_ERROR;

        // Lock down the array and obtain pointer to the data
        if (SafeArrayAccessData((SAFEARRAY*)pFolderInfoVB->pScalars,
                                (void**)&pScalarsVB) != S_OK) return SMS_ERROR;

        // Copy the array and unlock it
        DWORD dwIndex = pFolderInfo->ctScalars;
        SCALAR_INFO* pScalars = pFolderInfo->pScalars;
        while (dwIndex-- > 0)
        {
            pScalarsVB->scType = pScalars->scType;
            pScalars->fAccess = pScalars->fAccess;
            ret = CopyString2VB(&pScalarsVB->szName, pScalars->szName);
            if (ret != SMS_OK) break;

            // Advance to next scalar
            pScalarsVB++;
            pScalars++;
        }
        SafeArrayUnaccessData((SAFEARRAY*)pFolderInfoVB->pScalars);
    }

    return ret;
}

                                                                        /* VB:
  Declare Function SmsDescribeFolder& Lib "vb4sms32.dll" (tObjectity As BASETYPE, ByVal dwTag As Long, pFolderInfo As FOLDER_INFO)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsDescribeFolderVB(BASETYPE* tObjectity, DWORD dwTag,
                    LPSAFEARRAY* ppArray)
{
    SAFEARRAY* &pArray = *ppArray;
    SMS_STATUS   ret;

    // Create or Redim as necessary
    FOLDER_INFO* pFoldersVB;
    if (AllocVBArray(pArray, 1, sizeof(*pFoldersVB)) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Lock down the array and obtain pointer to the data
    if (SafeArrayAccessData(pArray, (void**)&pFoldersVB) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Call 32bit SMS API routine
    FOLDER_INFO *pFolderInfo;
    ret = SmsDescribeFolder(*tObjectity, dwTag, &pFolderInfo);
    if (ret == SMS_OK)
        // Copy the FOLDER_INFO struct from 32bit land
        ret = CopyFolderInfo2VB(pFoldersVB, pFolderInfo);

    // Unlock the VB array
    SafeArrayUnaccessData(pArray);
    return ret;
}

typedef SMS_STATUS (*ENUM_FN)(FOLDER_INFO**, DWORD*);

SMS_STATUS
SmsEnumFoldersContainers(LPSAFEARRAY* ppArray, DWORD *pCount, ENUM_FN SmsEnum)
{
    SAFEARRAY* &pArray = *ppArray;
    FOLDER_INFO* pFoldersVB;
    SMS_STATUS  ret;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (pCount is invalid)
    if (IsBadWritePtr(pCount, sizeof(*pCount)))
        return SMS_INVALID_PARAMETER;

    // if (Count is too big)
    if (*pCount >= 0x80000000 / sizeof(*pFoldersVB))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Get the count of FOLDER_INFOs
    *pCount = 0;
    ret = SmsEnum(NULL, pCount);
    if (ret != SMS_MORE_DATA) return ret;

    // Create or Redim as necessary
    if (AllocVBArray(pArray, *pCount, sizeof(*pFoldersVB)) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Lock down the array and obtain pointer to the data
    if (SafeArrayAccessData(pArray, (void**)&pFoldersVB) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Get an array large enough to accept FOLDER_INFO pointers
    DWORD cFolders = *pCount;
    FOLDER_INFO** ppFolders = new FOLDER_INFO*[cFolders];
    if (ppFolders != NULL)
    {
        // Call 32bit SMS API routine
        ret = SmsEnum(ppFolders, pCount);

        if (ret == SMS_OK)
            for (DWORD dwIndex = 0; dwIndex < cFolders; dwIndex++)
            {
                ret = CopyFolderInfo2VB(&pFoldersVB[dwIndex],
                                        ppFolders[dwIndex]);
                if (ret != SMS_OK) break;
            }

        // Free the array of FOLDER_INFO pointers
        delete[] ppFolders;
        ppFolders = NULL;
    }
    else ret = SMS_ERROR;

    // Unlock the VB array of FOLDER_INFOs
    SafeArrayUnaccessData(pArray);
    return ret;
}

                                                                        /* VB:
  Declare Function SmsEnumContainers& Lib "vb4sms32.dll" (Containers() As FOLDER_INFO, pCount As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsEnumContainersVB(LPSAFEARRAY* ppArray, DWORD *pCount)
{
    return SmsEnumFoldersContainers(ppArray, pCount, SmsEnumContainers);
}

                                                                        /* VB:
  Declare Function SmsEnumFolders& Lib "vb4sms32.dll" (Folders() As FOLDER_INFO, pCount As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsEnumFoldersVB(LPSAFEARRAY* ppArray, DWORD *pCount)
{
    return SmsEnumFoldersContainers(ppArray, pCount, SmsEnumFolders);
}

SMS_STATUS
CopyFilterInfo2VB(FILTER_INFOVB *pFilterInfoVB, FILTER_INFO *pFilterInfo)
{
    SMS_STATUS ret = SMS_OK;

    // szTag
    if (ret == SMS_OK)
        ret = CopyString2VB(&pFilterInfoVB->szTag,
                            pFilterInfo->szTag,
                            sizeof(pFilterInfo->szTag));

    // filterType
    pFilterInfoVB->filterType = pFilterInfo->filterType;

    // szName
    if (ret == SMS_OK)
        ret = CopyString2VB(&pFilterInfoVB->szName,
                            pFilterInfo->szName,
                            sizeof(pFilterInfo->szName));

    // szValue
    if (ret == SMS_OK)
        ret = CopyString2VB(&pFilterInfoVB->szValue,
                            pFilterInfo->szValue,
                            sizeof(pFilterInfo->szValue));

    // szOperator
    if (ret == SMS_OK)
        ret = CopyString2VB(&pFilterInfoVB->szOperator,
                            pFilterInfo->szOperator,
                            sizeof(pFilterInfo->szOperator));

    // szArchitecture
    if (ret == SMS_OK)
        ret = CopyString2VB(&pFilterInfoVB->szArchitecture,
                            pFilterInfo->szArchitecture,
                            sizeof(pFilterInfo->szArchitecture));

    // szGroupClass
    if (ret == SMS_OK)
        ret = CopyString2VB(&pFilterInfoVB->szGroupClass,
                            pFilterInfo->szGroupClass,
                            sizeof(pFilterInfo->szGroupClass));

    // szAttributeName
    if (ret == SMS_OK)
        ret = CopyString2VB(&pFilterInfoVB->szAttributeName,
                            pFilterInfo->szAttributeName,
                            sizeof(pFilterInfo->szAttributeName));

    return ret;
}

                                                                        /* VB:
  Declare Function SmsEnumFilters& Lib "vb4sms32.dll" (Filters() As FILTER_INFO, pCount As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsEnumFiltersVB(LPSAFEARRAY* ppArray, DWORD *pCount)
{
    SAFEARRAY* &pArray = *ppArray;
    FILTER_INFOVB* pFiltersVB;
    SMS_STATUS  ret;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (pCount is invalid)
    if (IsBadWritePtr(pCount, sizeof(*pCount)))
        return SMS_INVALID_PARAMETER;

    // if (Count is too big)
    if (*pCount >= 0x80000000 / sizeof(*pFiltersVB))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Get the count of FILTER_INFOs
    *pCount = 0;
    ret = SmsEnumFilters(NULL, pCount);
    if (ret != SMS_MORE_DATA) return ret;

    // Create or Redim as necessary
    if (AllocVBArray(pArray, *pCount, sizeof(*pFiltersVB)) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Lock down the array and obtain pointer to the data
    if (SafeArrayAccessData(pArray, (void**)&pFiltersVB) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Get an array large enough to accept FILTER_INFO pointers
    DWORD cFilters = *pCount;
    FILTER_INFO* pFilters = new FILTER_INFO[cFilters];
    if (pFilters != NULL)
    {
        // Call 32bit SMS API routine
        ret = SmsEnumFilters(pFilters, pCount);

        if (ret == SMS_OK)
            for (DWORD dwIndex = 0; dwIndex < cFilters; dwIndex++)
            {
                ret = CopyFilterInfo2VB(&pFiltersVB[dwIndex],
                                        &pFilters[dwIndex]);
                if (ret != SMS_OK) break;
            }

        // Unlock the array of FILTER_INFO pointers
        delete[] pFilters;
        pFilters = NULL;
    }
    else ret = SMS_ERROR;

    // Unlock the VB array of FILTER_INFOs
    SafeArrayUnaccessData(pArray);
    return ret;
}

// ====================================================================
//      Container APIS.
// ====================================================================
                                                                        /* VB:
  Declare Function SmsOpenContainer& Lib "vb4sms32.dll" (ByVal cType As Long, ByVal hConnection As Long, phContainer As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsOpenContainerVB(DWORD cType, HANDLE hConnection, HANDLE *phContainer)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Container Handle from VB is invalid)
    if (IsBadWritePtr((void*)phContainer, sizeof(*phContainer)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsOpenContainer(cType, hConnection, phContainer);
}

                                                                        /* VB:
  Declare Function SmsSetFilter& Lib "vb4sms32.dll" (ByVal hContainer As Long, ByVal hFilter As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsSetFilterVB(HANDLE hContainer, HANDLE hFilter)
{
    // Call 32bit SMS API routine
    return SmsSetFilter(hContainer, hFilter);
}

                                                                        /* VB:
  Declare Function SmsPopulate& Lib "vb4sms32.dll" (ByVal hContainer As Long, ByVal dwOptions As Long, pNotify As Any)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsPopulateVB(HANDLE hContainer, DWORD dwOptions, NOTIFY* pNotify)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Folder Handle from VB is invalid)
    if (dwOptions != POP_SYNC
                           && IsBadWritePtr((void*)pNotify, sizeof(*pNotify)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsPopulate(hContainer, dwOptions, pNotify);
}

                                                                        /* VB:
  Declare Function SmsGetNextFolder& Lib "vb4sms32.dll" (ByVal hParent As Long, ByVal fType As Long, phFolder As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetNextFolderVB(HANDLE hParent, DWORD fType, HANDLE *phFolder)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Folder Handle from VB is invalid)
    if (IsBadWritePtr((void*)phFolder, sizeof(*phFolder)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsGetNextFolder(hParent, fType, phFolder);
}

                                                                        /* VB:
  Declare Function SmsCloseContainer& Lib "vb4sms32.dll" (ByVal hContainer As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsCloseContainerVB(HANDLE hContainer)
{
    // Call 32bit SMS API routine
    return SmsCloseContainer(hContainer);
}

// ====================================================================
//  Folder (collection) APIs.
//  A collection contains other things, that is it can have embedded
//  collections and it can also have properties (scalars).
// ====================================================================
                                                                        /* VB:
  Declare Function SmsCreateFolder& Lib "vb4sms32.dll" (ByVal hParent As Long, ByVal fType As Long, ByVal pszFolderID As String, phFolder As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsCreateFolderVB(HANDLE hParent, DWORD fType, const char *pszFolderID,
                  HANDLE *phFolder)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to string from VB is invalid)
    if (IsBadStringPtr(pszFolderID, MAX_VB_STRING))
        return SMS_INVALID_PARAMETER;

    // if (Pointer to Folder Handle from VB is invalid)
    if (IsBadWritePtr((void*)phFolder, sizeof(*phFolder)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsCreateFolder(hParent, fType, pszFolderID, phFolder);
}

                                                                        /* VB:
  Declare Function SmsGetFolderID& Lib "vb4sms32.dll" (ByVal hFolder As Long, pszFolderID As String)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetFolderIDVB(HANDLE hFolder, BSTR* pszFolderID)
{
    SMS_STATUS ret;

    // Call 32bit SMS API routine
    ret = SmsGetFolderID(hFolder, gStrBuff);
    if (ret == SMS_OK)
        // Copy the string to VB land
        ret = CopyString2VB(pszFolderID, gStrBuff, sizeof(gStrBuff));

    return ret;
}
                                                                        /* VB:
  Declare Function SmsGetFolderType& Lib "vb4sms32.dll" (ByVal hFolder As Long, pfType As Long, pszfType As String)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetFolderTypeVB(HANDLE hFolder, DWORD *pfType, BSTR* pszfType)
{
    SMS_STATUS ret;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Type from VB is invalid)
    if (IsBadWritePtr(pfType, sizeof(*pfType)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    ret = SmsGetFolderType(hFolder, pfType, gStrBuff);
    if (ret == SMS_OK)
        // Copy the string to VB land
        ret = CopyString2VB(pszfType, gStrBuff, sizeof(gStrBuff));

    return ret;
}

                                                                        /* VB:
  Declare Function SmsEnumFolderTypes& Lib "vb4sms32.dll" (ByVal hFolder As Long, pfTypes As Long, pCount As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsEnumFolderTypesVB(HANDLE hFolder, LPSAFEARRAY* ppArray, DWORD *pCount)
{
    SAFEARRAY* &pArray = *ppArray;
    DWORD* pfTypes;
    SMS_STATUS  ret;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Folder Type Count from VB is invalid)
    if (IsBadWritePtr(pCount, sizeof(*pCount)))
        return SMS_INVALID_PARAMETER;

    // if (Count is too big)
    if (*pCount >= 0x80000000 / sizeof(*pfTypes))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Get the count of folder types
    *pCount = 0;
    ret = SmsEnumFolderTypes(hFolder, NULL, pCount);
    if (ret != SMS_MORE_DATA) return ret;

    // Create or Redim as necessary
    if (AllocVBArray(pArray, *pCount, sizeof(*pfTypes)) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Lock down the array and obtain pointer to the data
    if (SafeArrayAccessData(pArray, (void**)&pfTypes) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Call 32bit SMS API routine
    ret = SmsEnumFolderTypes(hFolder, pfTypes, pCount);

    // Unlock the array
    SafeArrayUnaccessData(pArray);

    return ret;
}

                                                                        /* VB:
  Declare Function SmsGetFolderCount& Lib "vb4sms32.dll" (ByVal hFolder As Long, ByVal fType As Long, pCount As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetFolderCountVB(HANDLE hFolder, DWORD fType, DWORD *pCount)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Folder Type Count from VB is invalid)
    if (IsBadWritePtr(pCount, sizeof(*pCount)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsGetFolderCount(hFolder, fType, pCount);
}

                                                                        /* VB:
  Declare Function SmsGetFolderByID& Lib "vb4sms32.dll" (ByVal hFolder As Long, ByVal fType As Long, ByVal pszFolderID As String, phSubFolder As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetFolderByIDVB(HANDLE hFolder, DWORD fType, const char *pszFolderID,
                   HANDLE *phSubFolder)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to string from VB is invalid)
    if (IsBadStringPtr(pszFolderID, MAX_VB_STRING))
        return SMS_INVALID_PARAMETER;

    // if (Pointer to SubFolder Handle from VB is invalid)
    if (IsBadWritePtr((void*)phSubFolder, sizeof(*phSubFolder)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    char *pTemp = const_cast<char*>(pszFolderID);  // FIX: SMSAPI incorrectly declared 3rd parameter
    return SmsGetFolderByID(hFolder, fType, pTemp, phSubFolder);
}

                                                                        /* VB:
  Declare Function SmsRewind& Lib "vb4sms32.dll" (ByVal hFolder As Long, ByVal dwOptions As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsRewindVB(HANDLE hFolder, DWORD dwOptions)
{
    // Call 32bit SMS API routine
    return SmsRewind(hFolder, dwOptions);
}

                                                                        /* VB:
  Declare Function SmsCloseFolder& Lib "vb4sms32.dll" (ByVal hFolder As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsCloseFolderVB(HANDLE hFolder)
{
    // Call 32bit SMS API routine
    return SmsCloseFolder(hFolder);
}

                                                                        /* VB:
  Declare Function SmsLinkFolder& Lib "vb4sms32.dll" (ByVal hFolder As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsLinkFolderVB(HANDLE hFolder)
{
    // Call 32bit SMS API routine
    return SmsLinkFolder(hFolder);
}

                                                                        /* VB:
  Declare Function SmsUnlinkFolder& Lib "vb4sms32.dll" (ByVal hFolder As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsUnlinkFolderVB(HANDLE hFolder)
{
    // Call 32bit SMS API routine
    return SmsUnlinkFolder(hFolder);
}

                                                                        /* VB:
  Declare Function SmsCommitFolder& Lib "vb4sms32.dll" (ByVal hFolder As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsCommitFolderVB(HANDLE hFolder)
{
    // Call 32bit SMS API routine
    return SmsCommitFolder(hFolder);
}

                                                                        /* VB:
  Declare Function SmsDupFolder& Lib "vb4sms32.dll" (ByVal hParent As Long, ByVal hFolder As Long, phNewFolder As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsDupFolderVB(HANDLE hParent, HANDLE hFolder, HANDLE* phNewFolder)
{
    // Call 32bit SMS API routine
    return SmsDupFolder(hParent, hFolder, phNewFolder);
}

                                                                        /* VB:
  Declare Function SmsGetScalarCount& Lib "vb4sms32.dll" (ByVal hFolder As Long, pCount As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetScalarCountVB(HANDLE hFolder, DWORD *pCount)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Folder Count from VB is invalid)
    if (IsBadWritePtr(pCount, sizeof(*pCount)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsGetScalarCount(hFolder, pCount);
}

                                                                        /* VB:
  Declare Function SmsGetNextScalar& Lib "vb4sms32.dll" (ByVal hFolder As Long, pScalarStruct As SCALAR)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetNextScalarVB(HANDLE hFolder, LPSAFEARRAY* ppArray)
{
    SMS_STATUS ret;
    char *pszName;
    BSTR pszValue;
    SAFEARRAY* pValue;
    SAFEARRAY* &pArray = *ppArray;

    // Create or Redim as necessary
    SCALAR *pScalar;
    if (AllocVBArray(pArray, 1, sizeof(*pScalar)) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Lock down the array and obtain pointer to the data
    if (SafeArrayAccessData(pArray, (void**)&pScalar) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Preserve the BSTR members, pszName & pszValue, in the SCALAR from VB
    pszName = pScalar->pszName;
    pszValue = (BSTR)pScalar->pszValue;
    pValue = (SAFEARRAY*)pScalar->pValue;

    // Provide SCALAR with buffers to receive data
    pScalar->pszName = gNameBuff;
    pScalar->pszValue = NULL;
    pScalar->pValue = NULL;
    pScalar->dwLen = 0;

    // Call 32bit SMS API routine to get buffer lengths
    ret = SmsGetNextScalar(hFolder, pScalar);

    // Check for BINARY scalar and wrong return code
    if (ret == SMS_OK && pScalar->scType == SCALAR_BINARY &&
        pScalar->dwLen > 0)
        ret = SMS_MORE_DATA;

    if (ret == SMS_MORE_DATA)
    {
        // Provide SCALAR with buffers to receive data
        if (pScalar->scType == SCALAR_STRING)
        {
            pScalar->pszValue = new char[pScalar->dwLen];
            if (pScalar->pszValue == NULL) ret = SMS_ERROR;
        }
        else if (pScalar->scType == SCALAR_BINARY)
        {
            // Create or Redim as necessary
            if (AllocVBArray(pValue, pScalar->dwLen, 1) != S_OK)
                ret = SMS_INVALID_PARAMETER;

            // Lock down the array and obtain pointer to the data
            else if (SafeArrayAccessData(pValue, &pScalar->pValue) != S_OK)
                ret = SMS_INVALID_PARAMETER;
        }
        else ret = SMS_ERROR;

        if (ret == SMS_MORE_DATA)
        {
            // Call 32bit SMS API routine to get data
            ret = SmsGetScalarByName(hFolder, pScalar->pszName, pScalar);

            // Copy string buffer to pszValue
            if (ret == SMS_OK && pScalar->pszValue != NULL)
            {
                ret = CopyString2VB(&pszValue, pScalar->pszValue);

                // Free the string buffer
                delete[] pScalar->pszValue;
            }

            // Unlock the VB Byte array
            if (pScalar->pValue != NULL)
                SafeArrayUnaccessData(pValue);
        }
    }

    // Restore pszName & pszValue members of SCALAR before possible exit
    pScalar->pszName = pszName;
    pScalar->pszValue = (char*)pszValue;
    pScalar->pValue = pValue;

    // Copy the name from buffer to VB BSTR
    if (ret == SMS_OK)
        ret = CopyString2VB((BSTR*)&pScalar->pszName, gNameBuff);

    // Unlock the VB array
    SafeArrayUnaccessData(pArray);

    return ret;
}

                                                                        /* VB:
  Declare Function SmsGetScalarByName& Lib "vb4sms32.dll" (ByVal hFolder As Long, ByVal pszName As String, pScalarStruct As SCALAR)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetScalarByNameVB(HANDLE hFolder, const char *pszNameVB,
                     LPSAFEARRAY* ppArray)
{
    SMS_STATUS ret;
    char *pszName;
    BSTR pszValue;
    SAFEARRAY* pValue;
    SAFEARRAY* &pArray = *ppArray;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to string from VB is invalid)
    if (IsBadStringPtr(pszNameVB, MAX_VB_STRING))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Create or Redim as necessary
    SCALAR *pScalar;
    if (AllocVBArray(pArray, 1, sizeof(*pScalar)) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Lock down the array and obtain pointer to the data
    if (SafeArrayAccessData(pArray, (void**)&pScalar) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Preserve the BSTR members, pszName & pszValue, in the SCALAR from VB
    pszName = pScalar->pszName;
    pszValue = (BSTR)pScalar->pszValue;
    pValue = (SAFEARRAY*)pScalar->pValue;

    // Provide SCALAR with buffers to receive data
    pScalar->pszName = gNameBuff;
    pScalar->pszValue = NULL;
    pScalar->pValue = NULL;
    pScalar->dwLen = 0;

    // Call 32bit SMS API routine to get buffer lengths
    ret = SmsGetScalarByName(hFolder, pszNameVB, pScalar);

    // Check for BINARY scalar and wrong return code
    if (ret == SMS_OK && pScalar->scType == SCALAR_BINARY &&
        pScalar->dwLen > 0)
        ret = SMS_MORE_DATA;

    if (ret == SMS_MORE_DATA ||
        (pScalar->scType == SCALAR_BINARY) && pScalar->dwLen > 0)
    {
        // Provide SCALAR with buffers to receive data
        if (pScalar->scType == SCALAR_STRING)
        {
            pScalar->pszValue = new char[pScalar->dwLen];
            if (pScalar->pszValue == NULL) ret = SMS_ERROR;
        }
        else if (pScalar->scType == SCALAR_BINARY)
        {
            // Create or Redim as necessary
            if (AllocVBArray(pValue, pScalar->dwLen, 1) != S_OK)
                ret = SMS_INVALID_PARAMETER;

            // Lock down the array and obtain pointer to the data
            else if (SafeArrayAccessData(pValue, &pScalar->pValue) != S_OK)
                ret = SMS_INVALID_PARAMETER;
        }
        else ret = SMS_ERROR;

        if (ret == SMS_MORE_DATA)
        {
            // Call 32bit SMS API routine to get data
            ret = SmsGetScalarByName(hFolder, pszNameVB, pScalar);

            // Copy string buffer to pszValue
            if (ret == SMS_OK && pScalar->pszValue != NULL)
            {
                ret = CopyString2VB(&pszValue, pScalar->pszValue);

                // Free the string buffer
                delete[] pScalar->pszValue;
            }

            // Unlock the VB Byte array
            if (pScalar->pValue != NULL)
                SafeArrayUnaccessData(pValue);
        }
    }

    // Restore pszTag & pszValue members of SCALAR before possible exit
    pScalar->pszName = pszName;
    pScalar->pszValue = (char*)pszValue;
    pScalar->pValue = pValue;

    // Copy the name from buffer to VB BSTR
    if (ret == SMS_OK)
        ret = CopyString2VB((BSTR*)&pScalar->pszName, gNameBuff);

    // Unlock the VB array
    SafeArrayUnaccessData(pArray);

    return ret;
}

                                                                        /* VB:
  Declare Function SmsSetScalar& Lib "vb4sms32.dll" (ByVal hFolder As Long, pScalarStruct As SCALAR)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsSetScalarVB(HANDLE hFolder, LPSAFEARRAY* ppArray)
{
    BSTR pszName, pszValue;
    SAFEARRAY* pValue;
    SAFEARRAY* &pArray = *ppArray;
    char NullCharBuff = '\0', *pCharBuff = &NullCharBuff;
    BYTE NullByteBuff = NULL, *pByteBuff = &NullByteBuff;

    // Lock down the array and obtain pointer to the data
    SCALAR *pScalar;
    if (SafeArrayAccessData(pArray, (void**)&pScalar) != S_OK)
        return SMS_INVALID_PARAMETER;

    if (pScalar->scType == SCALAR_STRING && pScalar->pszValue == NULL &&
        pScalar->dwLen > 0) return SMS_INVALID_PARAMETER;

    if (pScalar->scType == SCALAR_BINARY && pScalar->pValue == NULL)
        if (pScalar->dwLen > 0) return SMS_INVALID_PARAMETER;

    // Preserve the BSTR members, pszName & pszValue, in the SCALAR from VB
    pszName = (BSTR)pScalar->pszName;
    pszValue = (BSTR)pScalar->pszValue;
    pValue = (SAFEARRAY*)pScalar->pValue;

    // Get a value string buffer
    if (pScalar->scType == SCALAR_STRING && pszValue != NULL &&
        (pCharBuff = new char[VBStringLength(pszValue) + 1]) == NULL)
        return SMS_ERROR;

    // Lock down the array and obtain pointer to the byte data
    if (pScalar->scType == SCALAR_BINARY && pValue != NULL &&
        SafeArrayAccessData(pValue, (void**)&pByteBuff) != S_OK)
        return SMS_ERROR;

    // Copy the strings from VB to SMS
    CopyString2Sms(gNameBuff, pszName);
    if (pCharBuff != NULL) CopyString2Sms(pCharBuff, pszValue);

    // Set SCALAR value to buffers with data
    pScalar->pszName = gNameBuff;
    pScalar->pszValue = pCharBuff;
    pScalar->pValue = pByteBuff;

    // Call 32bit SMS API routine
    SMS_STATUS ret = SmsSetScalar(hFolder, pScalar);

    // Restore pszTag, pszValue, & pValue members of SCALAR before exit
    pScalar->pszName = (char*)pszName;
    pScalar->pszValue = (char*)pszValue;
    pScalar->pValue = pValue;

    // Unlock the array and delete the string value buffer
    if (pByteBuff != &NullByteBuff) SafeArrayUnaccessData(pValue);
    if (pCharBuff != &NullCharBuff) delete[] pCharBuff;

    // Unlock the VB array
    SafeArrayUnaccessData(pArray);

    return ret;
}

// ====================================================================
//  FilterContainer APIs
// ====================================================================
                                                                        /* VB:
  Declare Function SmsOpenFilterContainer& Lib "vb4sms32.dll" (ByVal hConnection As Long, phFContainer As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsOpenFilterContainerVB(HANDLE hConnection, HANDLE *phFContainer)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Container Handle from VB is invalid)
    if (IsBadWritePtr((void*)phFContainer, sizeof(*phFContainer)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsOpenFilterContainer(hConnection, phFContainer);
}

                                                                        /* VB:
  Declare Function SmsCloseFilterContainer& Lib "vb4sms32.dll" (ByVal hFContainer As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsCloseFilterContainerVB(HANDLE hFContainer)
{
    // Call 32bit SMS API routine
    return SmsCloseFilterContainer(hFContainer);
}

                                                                        /* VB:
  Declare Function SmsGetNextFilter& Lib "vb4sms32.dll" (ByVal hFContainer As Long, ByVal frType As Long, phFilter As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetNextFilterVB(HANDLE hFContainer, DWORD frType, HANDLE *phFilter)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Filter Handle from VB is invalid)
    if (IsBadWritePtr((void*)phFilter, sizeof(*phFilter)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsGetNextFilter(hFContainer, frType, phFilter);
}

                                                                        /* VB:
  Declare Function SmsGetFilterByID& Lib "vb4sms32.dll" (ByVal hFContainer As Long, ByVal frType As Long, ByVal pszFilterID As String, phFilter As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetFilterByIDVB(HANDLE hFContainer, DWORD frType, const char *pszFilterID,
                   HANDLE *phFilter)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to string from VB is invalid)
    if (IsBadStringPtr(pszFilterID, MAX_VB_STRING))
        return SMS_INVALID_PARAMETER;

    // if (Pointer to Filter Handle from VB is invalid)
    if (IsBadWritePtr((void*)phFilter, sizeof(*phFilter)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsGetFilterByID(hFContainer, frType, pszFilterID, phFilter);
}

                                                                        /* VB:
  Declare Function SmsGetFilterID& Lib "vb4sms32.dll" (ByVal hFilter As Long, pszFilterID As String)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetFilterIDVB(HANDLE hFilter, BSTR* pszFilterID)
{
    SMS_STATUS ret;

    // Call 32bit SMS API routine
    ret = SmsGetFilterID(hFilter, gStrBuff);
    if (ret == SMS_OK)
        ret = CopyString2VB(pszFilterID, gStrBuff, sizeof(gStrBuff));

    return ret;
}

                                                                        /* VB:
  Declare Function SmsCommitFilter& Lib "vb4sms32.dll" (ByVal hFilter As Long, ByVal hFContainer As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsCommitFilterVB(HANDLE hFilter, HANDLE hFContainer)
{
    // Call 32bit SMS API routine
    return SmsCommitFilter(hFilter, hFContainer);
}

// ====================================================================
//  Filter APIs
// ====================================================================
                                                                        /* VB:
  Declare Function SmsCreateFilter& Lib "vb4sms32.dll" (ByVal frType As Long, ByVal hConnection As Long, phFilter As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsCreateFilterVB(DWORD frType, HANDLE hConnection, HANDLE *phFilter)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Filter Handle from VB is invalid)
    if (IsBadWritePtr((void*)phFilter, sizeof(*phFilter)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsCreateFilter(frType, hConnection, phFilter);
}

                                                                        /* VB:
  Declare Function SmsCloseFilter& Lib "vb4sms32.dll" (ByVal hFilter As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsCloseFilterVB(HANDLE hFilter)
{
    // Call 32bit SMS API routine
    return SmsCloseFilter(hFilter);
}

                                                                        /* VB:
  Declare Function SmsGetFilterType& Lib "vb4sms32.dll" (ByVal hFilter As Long, pfilterType As Long, pszTag As String)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetFilterTypeVB(HANDLE hFilter, DWORD *pfilterType, BSTR* pszTag)
{
    SMS_STATUS ret;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Filter Handle from VB is invalid)
    if (IsBadWritePtr(pfilterType, sizeof(*pfilterType)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    ret = SmsGetFilterType(hFilter, pfilterType, gStrBuff);
    if (ret == SMS_OK)
        ret = CopyString2VB(pszTag, gStrBuff, sizeof(gStrBuff));

    return ret;
}

                                                                        /* VB:
  Declare Function SmsGetToken& Lib "vb4sms32.dll" (ByVal hFilter As Long, ByVal iIndex As Long, pTokenInfo As TOKEN)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetTokenVB(HANDLE hFilter, INT iIndex, TOKENVB *pTokenInfoVB)
{
    SMS_STATUS ret;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to FOLDER_INFO struct from VB is invalid)
    if (IsBadWritePtr(pTokenInfoVB, sizeof(*pTokenInfoVB)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Turn on ANSI string copy
    gbANSI = TRUE;

    // Call 32bit SMS API routine
    ret = SmsGetToken(hFilter, iIndex, &gTokenBuff);

    // tokenType
    pTokenInfoVB->tokenType = gTokenBuff.tokenType;

    // szName
    if (ret == SMS_OK)
        ret = CopyString2VB(&pTokenInfoVB->szName,
                            gTokenBuff.szName,
                            sizeof(gTokenBuff.szName));

    // szValue
    if (ret == SMS_OK)
        ret = CopyString2VB(&pTokenInfoVB->szValue,
                            gTokenBuff.szValue,
                            sizeof(gTokenBuff.szValue));

    // dwOp
    pTokenInfoVB->dwOp = gTokenBuff.dwOp;

    // szArchitecture
    if (ret == SMS_OK)
        ret = CopyString2VB(&pTokenInfoVB->szArchitecture,
                            gTokenBuff.szArchitecture,
                            sizeof(gTokenBuff.szArchitecture));

    // szGroupClass
    if (ret == SMS_OK)
        ret = CopyString2VB(&pTokenInfoVB->szGroupClass,
                            gTokenBuff.szGroupClass,
                            sizeof(gTokenBuff.szGroupClass));

    // szAttributeName
    if (ret == SMS_OK)
        ret = CopyString2VB(&pTokenInfoVB->szAttributeName,
                            gTokenBuff.szAttributeName,
                            sizeof(gTokenBuff.szAttributeName));

    // szTokenString
    if (ret == SMS_OK)
        ret = CopyString2VB(&pTokenInfoVB->szTokenString,
                            gTokenBuff.szTokenString,
                            sizeof(gTokenBuff.szTokenString));

    // bIndent
    pTokenInfoVB->bIndent = gTokenBuff.bIndent;

    gbANSI = FALSE;
    return ret;
}
                                                                        /* VB:
  Declare Function SmsAddToken& Lib "vb4sms32.dll" (ByVal hFilter As Long, opAndOr As ANDOR, pTokenInfo As TOKEN, ByVal iIndex As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsAddTokenVB(HANDLE hFilter, ANDOR* opAndOr, TOKENVB *pTokenInfoVB, INT iIndex)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to TOKENVB struct from VB is invalid)
    if (IsBadReadPtr(pTokenInfoVB, sizeof(*pTokenInfoVB)))
        return SMS_INVALID_PARAMETER;

    if (VBStringLength(pTokenInfoVB->szName) >= sizeof(gTokenBuff.szName))
        return SMS_INVALID_PARAMETER;

    if (VBStringLength(pTokenInfoVB->szValue) >= sizeof(gTokenBuff.szValue))
        return SMS_INVALID_PARAMETER;

    if (VBStringLength(pTokenInfoVB->szArchitecture)
                                         >= sizeof(gTokenBuff.szArchitecture))
        return SMS_INVALID_PARAMETER;

    if (VBStringLength(pTokenInfoVB->szGroupClass)
                                           >= sizeof(gTokenBuff.szGroupClass))
        return SMS_INVALID_PARAMETER;

    if (VBStringLength(pTokenInfoVB->szAttributeName)
                                        >= sizeof(gTokenBuff.szAttributeName))
        return SMS_INVALID_PARAMETER;

    if (VBStringLength(pTokenInfoVB->szTokenString)
                                          >= sizeof(gTokenBuff.szTokenString))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Turn on ANSI string copy
    gbANSI = TRUE;

    // tokenType
    gTokenBuff.tokenType = pTokenInfoVB->tokenType;

    // szName
    CopyString2Sms(gTokenBuff.szName, pTokenInfoVB->szName,
                   sizeof(gTokenBuff.szName));

    // szValue
    CopyString2Sms(gTokenBuff.szValue, pTokenInfoVB->szValue,
                   sizeof(gTokenBuff.szValue));

    // dwOp
    gTokenBuff.dwOp = pTokenInfoVB->dwOp;

    // szArchitecture
    CopyString2Sms(gTokenBuff.szArchitecture, pTokenInfoVB->szArchitecture,
                   sizeof(gTokenBuff.szArchitecture));

    // szGroupClass
    CopyString2Sms(gTokenBuff.szGroupClass, pTokenInfoVB->szGroupClass,
                   sizeof(gTokenBuff.szGroupClass));

    // szAttributeName
    CopyString2Sms(gTokenBuff.szAttributeName, pTokenInfoVB->szAttributeName,
                   sizeof(gTokenBuff.szAttributeName));

    // szTokenString
    CopyString2Sms(gTokenBuff.szTokenString, pTokenInfoVB->szTokenString,
                   sizeof(gTokenBuff.szTokenString));

    // bIndent
    gTokenBuff.bIndent = pTokenInfoVB->bIndent;

    // Turn back off ANSI copy
    gbANSI = FALSE;

    // Call 32bit SMS API routine
    return SmsAddToken(hFilter, *opAndOr, &gTokenBuff, iIndex);
}

                                                                        /* VB:
  Declare Function SmsGetTokenCount& Lib "vb4sms32.dll" (ByVal hFilter As Long, pCount As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetTokenCountVB(HANDLE hFilter, DWORD *pCount)
{
    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Filter Handle from VB is invalid)
    if (IsBadWritePtr(pCount, sizeof(*pCount)))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Call 32bit SMS API routine
    return SmsGetTokenCount(hFilter, pCount);
}

                                                                        /* VB:
  Declare Function SmsManipulateTokens& Lib "vb4sms32.dll" (ByVal hFilter As Long, ByVal dwFlags As Long, ByVal iStart As Long, ByVal iEnd As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsManipulateTokensVB(HANDLE hFilter, DWORD dwFlags, INT iStart, INT iEnd)
{
    // Call 32bit SMS API routine
    return SmsManipulateTokens(hFilter, dwFlags, iStart, iEnd);
}

                                                                        /* VB:
  Declare Function SmsGetAllFilters& Lib "vb4sms32.dll" (ByVal hContainer As Long, pahFilters As Long, pCount As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsGetAllFiltersVB(HANDLE hContainer, LPSAFEARRAY* ppArray, DWORD *pCount)
{
    SMS_STATUS ret;
    HANDLE *pahFilters;
    SAFEARRAY* &pArray = *ppArray;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (Pointer to Count from VB is invalid)
    if (IsBadWritePtr(pCount, sizeof(*pCount)))
        return SMS_INVALID_PARAMETER;

    // if (Count is too big)
    if (*pCount >= 0x80000000 / sizeof(*pahFilters))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Get the count of FILTER_INFOs
    *pCount = 0;
    ret = SmsGetAllFilters(hContainer, NULL, pCount);
    if (ret != SMS_MORE_DATA) return ret;

    // Create or Redim as necessary
    if (AllocVBArray(pArray, *pCount, sizeof(*pahFilters)) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Lock down the array and obtain pointer to the data
    if (SafeArrayAccessData(pArray, (void**)&pahFilters) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Call 32bit SMS API routine
    ret = SmsGetAllFilters(hContainer, pahFilters, pCount);

    // Unlock the VB array of FILTER_INFOs
    SafeArrayUnaccessData(pArray);
    return ret;
}

                                                                        /* VB:
  Declare Function SmsEnumObjectTypes& Lib "vb4sms32.dll" (ByVal hConnection As Long, pObjects() As String, pCount As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsEnumObjectTypesVB(HANDLE hConnection, LPSAFEARRAY* ppArray, DWORD *pCount)
{
    SAFEARRAY* &pArray = *ppArray;
    BSTR* paObjTypesVB;
    SMS_STATUS  ret;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    // if (pCount is invalid)
    if (IsBadWritePtr(pCount, sizeof(*pCount)))
        return SMS_INVALID_PARAMETER;

    // if (Count is too big)
    if (*pCount >= 0x80000000 / sizeof(*paObjTypesVB))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Get the count of object types
    *pCount = 0;
    ret = SmsEnumObjectTypes(hConnection, NULL, pCount);
    if (ret != SMS_MORE_DATA) return ret;

    // Create or Redim as necessary
    if (AllocVBArray(pArray, *pCount, sizeof(*paObjTypesVB)) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Lock down the array and obtain pointer to the data
    if (SafeArrayAccessData(pArray, (void**)&paObjTypesVB) != S_OK)
        return SMS_INVALID_PARAMETER;

    // Get an array large enough to accept object types

DWORD cObjTypes = *pCount;
    SMSBUFF* pObjTypes = new SMSBUFF[cObjTypes];
    if (pObjTypes != NULL)
    {
        // Call 32bit SMS API routine
        ret = SmsEnumObjectTypes(hConnection, pObjTypes, pCount);

        if (ret == SMS_OK)
            for (DWORD dwIndex = 0; dwIndex < cObjTypes; dwIndex++)
            {
                ret = CopyString2VB(&paObjTypesVB[dwIndex],
                                    pObjTypes[dwIndex]);
                if (ret != SMS_OK) break;
            }

        // Unlock the array of object types
        delete[] pObjTypes;
        pObjTypes = NULL;
    }
    else ret = SMS_ERROR;

    // Unlock the VB array of object types
    SafeArrayUnaccessData(pArray);
    return ret;
}

SMS_STATUS CopyObjectDescriptor2VB(OBJDESCRIPTORVB* pObjectVB,
                                   OBJDESCRIPTOR* pObject)
{
    SMS_STATUS ret = SMS_OK;

    // objType
    pObjectVB->objType = pObject->objType;

    // szName
    if (ret == SMS_OK)
        ret = CopyString2VB(&pObjectVB->szName,
                            pObject->szName);

    // szFriendlyName
    if (ret == SMS_OK)
        ret = CopyString2VB(&pObjectVB->szFriendlyName,
                            pObject->szFriendlyName);

    // bGotFriendlyName
    pObjectVB->bGotFriendlyName = pObject->bGotFriendlyName;

    // dwRelopMin
    pObjectVB->dwRelopMin = pObject->dwRelopMin;

    // dwRelopMax
    pObjectVB->dwRelopMax = pObject->dwRelopMax;

    // bGotRelops
    pObjectVB->bGotRelops = pObject->bGotRelops;

    return ret;
}
                                                                        /* VB:
  Declare Function SmsEnumObjects& Lib "vb4sms32.dll" (ByVal hConnection As Long, ByVal pszObjectType As String, pPredecessors() As String, ByVal CtPredecessors As Long, pObjects() As OBJDESCRIPTOR, pCtObjects As Long)
                                                                        '*/
extern "C" SMS_STATUS WINAPI
SmsEnumObjectsVB(HANDLE hConnection, char* pszObjectType, LPSAFEARRAY* ppPredecessorsVB, DWORD CtPredecessors, LPSAFEARRAY* ppArray, DWORD *pCount)
{
    SAFEARRAY* &pArray = *ppArray;
    SAFEARRAY* &pPredecessorsVB = *ppPredecessorsVB;
    BSTR* paPredecessorsVB;
    OBJDESCRIPTORVB* paObjectsVB;
    SMS_STATUS  ret = SMS_OK;

    //////////////////////////////////////////////////////////////////////////
    // Check parameters

    if (ppPredecessorsVB != NULL && CtPredecessors > 0)
    {
        if (pPredecessorsVB->cDims != 1)
            return SMS_INVALID_PARAMETER;

        if (pPredecessorsVB->cbElements!= 4)
            return SMS_INVALID_PARAMETER;

        if (pPredecessorsVB->rgsabound[0].cElements < CtPredecessors)
            return SMS_INVALID_PARAMETER;
    }

    // if (pCount is invalid)
    if (IsBadWritePtr(pCount, sizeof(*pCount)))
        return SMS_INVALID_PARAMETER;

    // if (Count is too big)
    if (*pCount >= 0x80000000 / sizeof(*paObjectsVB))
        return SMS_INVALID_PARAMETER;

    //////////////////////////////////////////////////////////////////////////

    // Copy Predecessors array
    // Get an array large enough to accept object types
    SMSBUFF* pPredecessors = NULL;
    if (ppPredecessorsVB != NULL && CtPredecessors > 0)
    {
        pPredecessors = new SMSBUFF[CtPredecessors];
        if (pPredecessors == NULL) return SMS_ERROR;

        // Lock down the array and obtain pointer to the data
        if (SafeArrayAccessData(pPredecessorsVB, (void**)&paPredecessorsVB) != S_OK)
            {ret = SMS_INVALID_PARAMETER; goto SmsEnumObjectsVB_Quit;}

        // Turn on ANSI string copy
        gbANSI = TRUE;

        for (DWORD dwIndex = 0; dwIndex < CtPredecessors; dwIndex++)
        {
            ret = CopyString2Sms(pPredecessors[dwIndex],
                                 paPredecessorsVB[dwIndex]);
            if (ret != SMS_OK) break;
        }

        // Turn back off ANSI string copy
        gbANSI = FALSE;

        // Unlock the VB array of object types
        SafeArrayUnaccessData(pPredecessorsVB);
    }

    if (ret == SMS_OK)
    {
        // Get the count of object types
        *pCount = 0;
        ret = SmsEnumObjects(hConnection, pszObjectType, pPredecessors,
                             CtPredecessors, NULL, pCount);
        if (ret != SMS_MORE_DATA) goto SmsEnumObjectsVB_Quit;

        // Create or Redim as necessary
        if (AllocVBArray(pArray, *pCount, sizeof(*paObjectsVB)) != S_OK)
            {ret = SMS_INVALID_PARAMETER; goto SmsEnumObjectsVB_Quit;}

        // Lock down the array and obtain pointer to the data
        if (SafeArrayAccessData(pArray, (void**)&paObjectsVB) != S_OK)
            {ret = SMS_INVALID_PARAMETER; goto SmsEnumObjectsVB_Quit;}

        // Get an array large enough to accept object types
        DWORD cObjects = *pCount;
        OBJDESCRIPTOR* pObjects = new OBJDESCRIPTOR[cObjects];
        if (pObjects != NULL)
        {
            // Call 32bit SMS API routine
            ret = SmsEnumObjects(hConnection, pszObjectType, pPredecessors,
                                 CtPredecessors, pObjects, pCount);

            if (ret == SMS_OK)
                for (DWORD dwIndex = 0; dwIndex < cObjects; dwIndex++)
                {
                    ret = CopyObjectDescriptor2VB(&paObjectsVB[dwIndex],
                                                  &pObjects[dwIndex]);
                    if (ret != SMS_OK) break;
                }

            // Unlock the array of object types
            delete[] pObjects;
            pObjects = NULL;
        }
        else ret = SMS_ERROR;

        // Unlock the VB array of object types
        SafeArrayUnaccessData(pArray);
    }

SmsEnumObjectsVB_Quit:
    // Free the array of predecessors
    if (pPredecessors != NULL) delete[] pPredecessors;
    pPredecessors = NULL;

    return ret;
}


VB4SMS32.H

//****************************************************************************
//
//  Copyright (c) 1995, Microsoft Corporation
//
//  File:  VB4SMS32.H
//
//  Definitions for the 32-bit Visual Basic interface DLL
//  for SMSAPI.DLL (a Win32 dll)
//
//  History:
//
//      Gary F. Fuehrer, SEA     21 Mar 95    Created.
//
//****************************************************************************

// NOTE:  This file is NOT a C or C++ interface file as are most .H files.
//        Instead, this is where all assumptions about SMSAPI.H are kept.
//        If SMSAPI.H changes, this file will need to be updated, and
//        hopefully VB4SMS32.CPP won't compile until it too is updated.

typedef struct _TOKENVB {
    UINT    tokenType;
    BSTR    szName;
    BSTR    szValue;
    DWORD   dwOp;
    BSTR    szArchitecture;
    BSTR    szGroupClass;
    BSTR    szAttributeName;
    BSTR    szTokenString;
    BOOL    bIndent;
} TOKENVB;

typedef struct _FILTER_INFOVB {
    BSTR    szTag;
    DWORD   filterType;
    BSTR    szName;
    BSTR    szValue;
    BSTR    szOperator;
    BSTR    szArchitecture;
    BSTR    szGroupClass;
    BSTR    szAttributeName;
} FILTER_INFOVB;

typedef struct _SCALAR_INFOVB {
    BSTR    szName;                 // Scalar's name.
    SCALARTYPE scType;              // Type: SCALAR_STRING, INT, TIME etc
    DWORD   fAccess;                // Access mode: ACCESS_READ, MODIFY etc
} SCALAR_INFOVB;

typedef struct _OBJDESCRIPTORVB {
    DWORD objType;
    BSTR szName;
    BSTR szFriendlyName;
    BOOL bGotFriendlyName;
    DWORD dwRelopMin;
    DWORD dwRelopMax;
    BOOL bGotRelops;
} OBJDESCRIPTORVB;

//////////////////////////////////////////////////////////////////////////////


BROWSER.BAS

Attribute VB_Name = "basBROWSER"
Option Explicit

'Horizontal tab character (Can't have string constants)
'This is initialized in InitBrowser()
Dim HT$

'Size of one pixel in Twips (Set in Browser Form Load)
Global X_PIX_SIZE%
Global Y_PIX_SIZE%

'Here are a few extra BASETYPE for my convenience
'This is initialized in InitBrowser()
Global T_ROOTOBJECT As BASETYPE
Global T_SCALAR As BASETYPE

'Data structure for an entry in the Outline control
Type OutlineEntryData
    Type As BASETYPE
    Tag As Long
    Handle As Long
    RetrieveData As Integer
End Type

'Collection of OutlineEntryData structures.
'This is intialized in InitOutlineEntries()
Dim OutlineEntryDataArray() As OutlineEntryData
Const OUTLINE_ENTRY_ALLOC = 64  'How to allocated each time we run out

Function AddOutlineEntry&(sType As BASETYPE, lTag&, lHandle&, bRetrieveData%)
    Dim NewEntryIndex%

    'Initially an invalid value
    NewEntryIndex% = -1

    If OutlineEntryDataArray(0).Handle& <= 0 Then
        'Need to make more room
        OutlineEntryDataArray(0).Handle& = UBound(OutlineEntryDataArray) + 1
        ReDim Preserve OutlineEntryDataArray(0 To UBound(OutlineEntryDataArray) + OUTLINE_ENTRY_ALLOC)
        For NewEntryIndex% = OutlineEntryDataArray(0).Handle& To UBound(OutlineEntryDataArray) - 1
            OutlineEntryDataArray(NewEntryIndex%).Handle& = NewEntryIndex% + 1
        Next NewEntryIndex%
        OutlineEntryDataArray(NewEntryIndex%).Handle& = 0
    End If

    'Allocate an entry
    NewEntryIndex% = OutlineEntryDataArray(0).Handle&
    OutlineEntryDataArray(0).Handle& = OutlineEntryDataArray(NewEntryIndex%).Handle&

    'Copy the info into the new entry
    OutlineEntryDataArray(NewEntryIndex%).Type = sType
    OutlineEntryDataArray(NewEntryIndex%).Tag = lTag&
    OutlineEntryDataArray(NewEntryIndex%).Handle = lHandle&
    OutlineEntryDataArray(NewEntryIndex%).RetrieveData% = bRetrieveData%

    'Return handle to new entry
    AddOutlineEntry& = NewEntryIndex%
End Function

Sub FreeOutlineEntry(ByVal Handle&)
    Dim index%

    index% = ValidateOutlineEntryHandle%(Handle&)
    If index% > 0 Then
        OutlineEntryDataArray(index%).Handle& = OutlineEntryDataArray(0).Handle&
        OutlineEntryDataArray(0).Handle& = index%
    End If
End Sub

Sub GetOutlineEntry(ByVal Handle&, Entry As OutlineEntryData)
    Dim index%

    index% = ValidateOutlineEntryHandle%(Handle&)
    If index% > 0 Then
        Entry = OutlineEntryDataArray(index%)
    End If
End Sub

Function InitBrowser%()
    'Default return value is True
    InitBrowser = True

    'Horizontal tab character
    HT$ = Chr$(9)

    'Init the OutlineEntryData collection
    InitOutlineEntrys

    'Init Visual Basic SMS_API stuff
    T_ROOTOBJECT.enum = &HFFFFFFFE
    T_SCALAR.enum = &HFFFFFFFF
    If Not Init_SMSAPI() Then InitBrowser% = False
End Function

Sub InitOutlineEntrys()
    ReDim OutlineEntryDataArray(0 To 0)
    OutlineEntryDataArray(0).Handle& = 0
End Sub

Sub Main()
    'Initialize the application
    If InitBrowser() = True Then
        On Error Resume Next
        frmBrowser.Show
        On Error GoTo 0
    End If
End Sub

Sub SetOutlineEntry(ByVal Handle&, Entry As OutlineEntryData)
    Dim index%

    index% = ValidateOutlineEntryHandle%(Handle&)
    If index% > 0 Then
        OutlineEntryDataArray(index%) = Entry
    End If
End Sub

Function ValidateOutlineEntryHandle%(Handle&)
    Dim index%, i%

    'Default is an error
    ValidateOutlineEntryHandle% = 0

    index% = CInt(Handle&)

    'Debugging Stuff
    If index% <= 0 Or index% > UBound(OutlineEntryDataArray) Then
        MsgBox "Entry data index out of bounds!"
        Exit Function
    End If

    i% = CInt(OutlineEntryDataArray(0).Handle&)
    Do While i% <> 0
        If i% = index% Then
            MsgBox "Entry data index is invalid!"
            Exit Function
        Else: i% = CInt(OutlineEntryDataArray(i%).Handle&)
        End If
    Loop
    'End Debugging stuff

    ValidateOutlineEntryHandle% = index%
End Function


BROWSER.FRM

VERSION 4.00
Begin VB.MDIForm frmBrowser
   Appearance      =   0  'Flat
   BackColor       =   &H8000000C&
   Caption         =   "SMS Browser"
   ClientHeight    =   4725
   ClientLeft      =   1995
   ClientTop       =   1785
   ClientWidth     =   7935
   Height          =   5415
   Icon            =   "BROWSER.frx":0000
   Left            =   1935
   LinkTopic       =   "Form1"
   Top             =   1155
   Width           =   8055
   Begin VB.PictureBox picFormBackdrop
      Align           =   1  'Align Top
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      BorderStyle     =   0  'None
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   0
         weight          =   700
         size            =   8.25
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      ForeColor       =   &H80000008&
      Height          =   4335
      Left            =   0
      ScaleHeight     =   4335
      ScaleWidth      =   7935
      TabIndex        =   4
      TabStop         =   0   'False
      Top             =   0
      Width           =   7935
      Begin VB.PictureBox picSeperator
         Appearance      =   0  'Flat
         BackColor       =   &H80000005&
         BeginProperty Font
            name            =   "MS Sans Serif"
            charset         =   0
            weight          =   700
            size            =   8.25
            underline       =   0   'False
            italic          =   0   'False
            strikethrough   =   0   'False
         EndProperty
         ForeColor       =   &H80000008&
         Height          =   4215
         Left            =   3420
         MousePointer    =   9  'Size W E
         ScaleHeight     =   4185
         ScaleWidth      =   45
         TabIndex        =   5
         TabStop         =   0   'False
         Top             =   0
         Width           =   75
      End
      Begin VB.PictureBox picRightView
         Appearance      =   0  'Flat
         BackColor       =   &H80000005&
         BorderStyle     =   0  'None
         BeginProperty Font
            name            =   "MS Sans Serif"
            charset         =   0
            weight          =   700
            size            =   8.25
            underline       =   0   'False
            italic          =   0   'False
            strikethrough   =   0   'False
         EndProperty
         ForeColor       =   &H80000008&
         Height          =   3495
         Left            =   3480
         ScaleHeight     =   3495
         ScaleWidth      =   3735
         TabIndex        =   7
         TabStop         =   0   'False
         Top             =   0
         Width           =   3735
         Begin Threed.SSPanel pnlInfoHeader
            Height          =   255
            Left            =   0
            TabIndex        =   2
            Top             =   0
            Width           =   2895
            _Version        =   65536
            _ExtentX        =   5106
            _ExtentY        =   450
            _StockProps     =   15
            Caption         =   "&Object Information"
            BackColor       =   -2147483633
            BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851}
               name            =   "MS Sans Serif"
               charset         =   0
               weight          =   700
               size            =   8.25
               underline       =   0   'False
               italic          =   0   'False
               strikethrough   =   0   'False
            EndProperty
            BevelWidth      =   2
            BorderWidth     =   0
            Autosize        =   3
         End
         Begin VB.ListBox lstObjectInfo
            Appearance      =   0  'Flat
            BeginProperty Font
               name            =   "MS Sans Serif"
               charset         =   0
               weight          =   700
               size            =   8.25
               underline       =   0   'False
               italic          =   0   'False
               strikethrough   =   0   'False
            EndProperty
            Height          =   3150
            Left            =   0
            TabIndex        =   3
            Top             =   240
            Width           =   2865
         End
      End
      Begin VB.PictureBox picLeftView
         Appearance      =   0  'Flat
         BackColor       =   &H80000005&
         BorderStyle     =   0  'None
         BeginProperty Font
            name            =   "MS Sans Serif"
            charset         =   0
            weight          =   700
            size            =   8.25
            underline       =   0   'False
            italic          =   0   'False
            strikethrough   =   0   'False
         EndProperty
         ForeColor       =   &H80000008&
         Height          =   4095
         Left            =   0
         ScaleHeight     =   4095
         ScaleWidth      =   3075
         TabIndex        =   6
         TabStop         =   0   'False
         Top             =   0
         Width           =   3075
         Begin Threed.SSPanel pnlContainerHeader
            Height          =   255
            Left            =   0
            TabIndex        =   0
            Top             =   0
            Width           =   2175
            _Version        =   65536
            _ExtentX        =   3836
            _ExtentY        =   450
            _StockProps     =   15
            Caption         =   "&Container"
            BackColor       =   -2147483633
            BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851}
               name            =   "MS Sans Serif"
               charset         =   0
               weight          =   700
               size            =   8.25
               underline       =   0   'False
               italic          =   0   'False
               strikethrough   =   0   'False
            EndProperty
            BevelWidth      =   2
            BorderWidth     =   0
            Autosize        =   3
         End
         Begin MSOutl.Outline olnContainer
            Height          =   2715
            Left            =   0
            TabIndex        =   1
            Top             =   240
            Width           =   2175
            _Version        =   65536
            _ExtentX        =   3836
            _ExtentY        =   4789
            _StockProps     =   77
            ForeColor       =   -2147483630
            BeginProperty Font {0BE35203-8F91-11CE-9DE3-00AA004BB851}
               name            =   "MS Sans Serif"
               charset         =   0
               weight          =   700
               size            =   8.25
               underline       =   0   'False
               italic          =   0   'False
               strikethrough   =   0   'False
            EndProperty
            BorderStyle     =   0
            PicturePlus     =   "BROWSER.frx":030A
            PictureMinus    =   "BROWSER.frx":047C
            PictureLeaf     =   "BROWSER.frx":05EE
            PictureOpen     =   "BROWSER.frx":0760
            PictureClosed   =   "BROWSER.frx":08D2
         End
      End
   End
   Begin VB.Menu mnuFile
      Caption         =   "&File"
      Begin VB.Menu mnuFileNewContainer
         Caption         =   "New &Container"
      End
      Begin VB.Menu mnuFileExit
         Caption         =   "&Exit"
      End
   End
   Begin VB.Menu mnuHelp
      Caption         =   "&Help"
      Begin VB.Menu mnuHelpAbout
         Caption         =   "&About..."
      End
   End
End
Attribute VB_Name = "frmBrowser"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

Dim ghConnect&
Dim gContainerCount&

'Seperator dragging state and cursor offset
Dim SeperatorDragging%
Dim SeperatorDragOffset%

Private Function ConvertTimetToDateTime(timet&) As Double
    Dim dtimet As Double

    If timet < 0 Then
        dtimet = CDbl(&H7FFFFFFF) + 1 + (timet Or &H7FFFFFFF)
    Else: dtimet = CDbl(timet)
    End If
    ConvertTimetToDateTime = CVDate("1 Jan 1970") + dtimet / 86400
End Function

Private Function GetScalarTypeName$(scType As SCALARTYPE)
    Select Case scType.enum
        Case SCALAR_STRING.enum
            GetScalarTypeName$ = "String"
        Case SCALAR_INT.enum
            GetScalarTypeName$ = "Number"
        Case SCALAR_TIME.enum
            GetScalarTypeName$ = "DateTime"
        Case SCALAR_BINARY.enum
            GetScalarTypeName$ = "Binary"
    End Select
End Function

Private Sub MDIForm_Load()
    Dim SqlConnectParams As SQL_CONNECT_PARAMS

    'Get the size of one pixel size in twips
    GetPixelSizeInTwips Me, X_PIX_SIZE, Y_PIX_SIZE

    'Login to SMS
    If DBLogin%(SqlConnectParams, ghConnect&) = False Then
        'User canceled, so quit application
        Unload Me
        Exit Sub
    End If

    'Position Left View and controls (will be resized later)
    picLeftView.left = 0
    picLeftView.top = 0
        pnlContainerHeader.left = 0
        pnlContainerHeader.top = 0
        olnContainer.left = 0
        olnContainer.top = pnlContainerHeader.Height
    picLeftView.Width = 2000

    'Position View Seperator (will be resized later)
    picSeperator.left = picLeftView.Width
    picSeperator.top = -Y_PIX_SIZE

    'Position Right View and controls (will be resized later)
    picRightView.left = picSeperator.left + picSeperator.Width
    picRightView.top = 0
        pnlInfoHeader.left = 0
        pnlInfoHeader.top = 0
        lstObjectInfo.left = -X_PIX_SIZE
        lstObjectInfo.top = pnlInfoHeader.Height - Y_PIX_SIZE

    olnContainer.AddItem "SMS Containers", 0
    olnContainer.Indent(0) = 1
    olnContainer.ItemData(0) = AddOutlineEntry&(T_ROOTOBJECT, 0, 0, True)
    olnContainer.ListIndex = 0

End Sub

Private Sub MDIForm_Resize()
    Static OldHeight
    Dim r As Rect, VBorderPixels%

    'See if we're minimized
    If WindowState = MINIMIZED Then Exit Sub

    'Get client size of form (already in pixels, I find)
    GetClientRect hwnd, r
    VBorderPixels% = Height / Y_PIX_SIZE - r.bottom

    If OldHeight <> Height Then
        picFormBackdrop.Height = Height - Y_PIX_SIZE * VBorderPixels% - picFormBackdrop.top

        OldHeight = Height
    End If
End Sub

Private Sub MDIForm_Unload(Cancel As Integer)
    Dim lRet&, ListIndex%

    Me.Hide

    Do While olnContainer.ListCount
        olnContainer.Expand(0) = False
        FreeOutlineEntry olnContainer.ItemData(0)
        olnContainer.RemoveItem 0
    Loop

    DBLogout ghConnect&
End Sub

Private Sub mnuFileExit_Click()
    Unload Me
End Sub

Private Sub mnuFileNewContainer_Click()
    Dim ContainerName$, ContainerTag&, FolderInfo As FOLDER_INFO
    Dim ListIndex%, lRet&

    If NewContainer%(ghConnect&, ContainerName$, ContainerTag&) Then
        lRet& = SmsDescribeFolder&(T_CONTAINER, ContainerTag&, FolderInfo)
        If (lRet& <> SMS_OK) Then GoTo mnuFileNewContainerClickErr

        ListIndex% = olnContainer.ListCount
        olnContainer.AddItem ContainerName$ + " (" + FolderInfo.pszTag + ")", ListIndex%
        olnContainer.Indent(ListIndex%) = 2
        olnContainer.ItemData(ListIndex%) = AddOutlineEntry&(T_CONTAINER, ContainerTag&, 0&, True)
        olnContainer.PictureType(ListIndex%) = MSOUTLINE_PICTURE_CLOSED
        If Not olnContainer.Expand(0) Then olnContainer.Expand(0) = True
    End If

    Exit Sub

mnuFileNewContainerClickErr:
    MsgBox "mnuFileNewContainer_Click: Unexpected error", MB_OK, Me.Caption
End Sub

Private Sub mnuHelpAbout_Click()
    Dim Info$

    Info$ = "SMS Browser" + Chr$(10) + Chr$(10) + "A Microsoft SMS SDK sample application"
    Info$ = Info$ + Chr$(10) + "written in Microsoft Visual Basic 3.0 Professional Edition that"
    Info$ = Info$ + Chr$(10) + "demonstrates the Visual Basic Wrapper DLL for SMS API."

    SmsAbout "About SMS Browser", Info$
End Sub

Private Sub olnContainer_Click()
    Dim SelEntry As OutlineEntryData
    Static NextScalar As SCALAR, FilterInfo As FILTER_INFO
    Dim Filters() As FILTER_INFO, FilterCount&
    Dim FolderInfo As FOLDER_INFO, SubInfo As FOLDER_INFO, FolderTag&, FolderTagName$
    Dim SMSAPIVersion$, Text$
    Dim i%, j%, Row%, ListIndex%, DateTime#, lRet&, FilterIndex%, lFilterCount&
    ReDim FolderTags&(0), FilterTags&(0), Scalars(0)

    lstObjectInfo.Clear
    Row% = 0

    ListIndex% = olnContainer.ListIndex
    GetOutlineEntry olnContainer.ItemData(ListIndex%), SelEntry
    If SelEntry.Type.enum = T_ROOTOBJECT.enum Then
        'Display the SMSAPI version string
        lRet& = SmsAPIVer&(SMSAPIVersion$)
        If (lRet& <> SMS_OK) Then GoTo olnContainerClickErr

        lstObjectInfo.AddItem SMSAPIVersion$, Row%
        Row% = Row% + 1

    ElseIf SelEntry.Type.enum = T_CONTAINER.enum Then
        'Get info on selected container
        lRet& = SmsDescribeFolder&(T_CONTAINER, SelEntry.Tag&, FolderInfo)
        If (lRet& <> SMS_OK) Then GoTo olnContainerClickErr

        lstObjectInfo.AddItem FolderInfo.pszTag, Row%
        Row% = Row% + 1

        lstObjectInfo.AddItem Chr$(9) + "Folder Types:", Row%
        Row% = Row% + 1

        For i% = Row% To FolderInfo.ctFolders - 1 + Row%
            lRet& = SmsDescribeFolder&(T_FOLDER, FolderInfo.FolderTags&(i% - Row%), SubInfo)
            If (lRet& <> SMS_OK) Then GoTo olnContainerClickErr

            lstObjectInfo.AddItem Chr$(9) + Chr$(9) + SubInfo.pszTag, Row%
        Next i%
        Row% = Row% + FolderInfo.ctFolders

        lstObjectInfo.AddItem Chr$(9) + "Filter Types:", Row%
        Row% = Row% + 1

        lRet& = SmsEnumFilters(Filters, FilterCount&)
        If (lRet& <> SMS_OK) Then GoTo olnContainerClickErr
        For i% = Row% To FolderInfo.ctFilters - 1 + Row%
            lstObjectInfo.AddItem Chr$(9) + Chr$(9) + Filters(FolderInfo.FilterTags(i% - Row%) - 1).szTag, Row%
        Next i%
        Row% = Row% + FolderInfo.ctFilters

    ElseIf SelEntry.Type.enum = T_FOLDER.enum Then
        'Get info on selected Folder
        lRet& = SmsDescribeFolder&(T_FOLDER, SelEntry.Tag&, FolderInfo)
        If (lRet& <> SMS_OK) Then GoTo olnContainerClickErr

        lstObjectInfo.AddItem FolderInfo.pszTag, Row%
        Row% = Row% + 1

        lstObjectInfo.AddItem Chr$(9) + "Folder Types:", Row%
        Row% = Row% + 1

        For i% = Row% To FolderInfo.ctFolders - 1 + Row%
            lRet& = SmsDescribeFolder&(T_FOLDER, FolderInfo.FolderTags&(i% - Row%), SubInfo)
            If (lRet& <> SMS_OK) Then GoTo olnContainerClickErr

            lstObjectInfo.AddItem Chr$(9) + Chr$(9) + SubInfo.pszTag, Row%
        Next i%
        Row% = Row% + FolderInfo.ctFolders

        'Get list of possible scalar types
        lstObjectInfo.AddItem Chr$(9) + "Scalar Types:", Row%
        Row% = Row% + 1

        For i% = Row% To FolderInfo.ctScalars - 1 + Row%
            lstObjectInfo.AddItem Chr$(9) + Chr$(9) + FolderInfo.Scalars(i% - Row%).szName, Row%
        Next i%
        Row% = Row% + FolderInfo.ctScalars

    ElseIf SelEntry.Type.enum = T_SCALAR.enum Then
        lRet& = SmsRewind&(SelEntry.Handle&, RW_ALL)
        If lRet& <> SMS_OK Then GoTo olnContainerClickErr

        lRet& = SmsGetNextScalar&(SelEntry.Handle&, NextScalar)
        Do While lRet& = SMS_OK
            If olnContainer.List(ListIndex%) = NextScalar.pszName$ + " (" + GetScalarTypeName$(NextScalar.scType) + ")" Then
                lstObjectInfo.AddItem GetScalarTypeName$(NextScalar.scType), Row%
                Row% = Row% + 1
                lstObjectInfo.AddItem Chr$(9) + "Value:", Row%
                Row% = Row% + 1
                Select Case NextScalar.scType.enum
                    Case SCALAR_STRING.enum
                        lstObjectInfo.AddItem Chr$(9) + Chr$(9) + CStr(NextScalar.pszValue), Row%
                        Row% = Row% + 1
                    Case SCALAR_INT.enum
                        lstObjectInfo.AddItem Chr$(9) + Chr$(9) + CStr(NextScalar.dwValue), Row%
                        Row% = Row% + 1
                        If NextScalar.bStringEquivalence Then
                            lstObjectInfo.AddItem Chr$(9) + "String Equivalent:", Row%
                            Row% = Row% + 1
                            lstObjectInfo.AddItem Chr$(9) + Chr$(9) + NextScalar.pszValue, Row%
                            Row% = Row% + 1
                        End If
                    Case SCALAR_TIME.enum
                        DateTime# = ConvertTimetToDateTime(NextScalar.tValue)
                        If IsDate(CVDate(DateTime#)) Then
                            lstObjectInfo.AddItem Chr$(9) + Chr$(9) + Format$(DateTime#, "ddd mmm dd yyyy hh:mm:ss") + " GMT", Row%
                        Else: lstObjectInfo.AddItem Chr$(9) + Chr$(9) + "<invalid date time>", Row%
                        End If
                        Row% = Row% + 1
                        If NextScalar.bStringEquivalence Then
                            lstObjectInfo.AddItem Chr$(9) + "String Equivalent:", Row%
                            Row% = Row% + 1
                            lstObjectInfo.AddItem Chr$(9) + Chr$(9) + NextScalar.pszValue, Row%
                            Row% = Row% + 1
                        End If
                    Case SCALAR_BINARY.enum
                        For i% = 0 To NextScalar.dwLen - 1 Step 16
                            If (i% = &H100 And NextScalar.dwLen > &H110) Then
                                i% = (NextScalar.dwLen - 1) And &HFFFFFFF0
                                lstObjectInfo.AddItem Chr$(9) + ":", Row%
                                Row% = Row% + 1
                            End If
                            Text$ = Hex$(i%)
                            Text$ = String$(8 - Len(Text$), "0") + Text$ + " "
                            For j% = 0 To 15
                                If (i% + j% >= NextScalar.dwLen) Then
                                    Text$ = Text$ + ""
                                Else: Text$ = Text$ + " " + String$((NextScalar.pValue(i% + j%) > 15) + 1, "0") + Hex$(NextScalar.pValue(i% + j%))
                                End If
                            Next j%
                            lstObjectInfo.AddItem Chr$(9) + Text$, Row%
                            Row% = Row% + 1
                        Next i%
                    Case Else
                        GoTo olnContainerClickErr
                End Select
                lRet& = SMS_NO_MORE_DATA
                Exit Do
            End If
            lRet& = SmsGetNextScalar&(SelEntry.Handle&, NextScalar)
        Loop
        If lRet& <> SMS_NO_MORE_DATA Then GoTo olnContainerClickErr

    Else: GoTo olnContainerClickErr
    End If

    Exit Sub

olnContainerClickErr:
    MsgBox "olnContainerClick:"
End Sub

Private Sub olnContainer_Collapse(ListIndex%)
    Dim SelEntry As OutlineEntryData, DelEntry As OutlineEntryData
    Dim NextListIndex%, FreeEntry%, lRet&
    Static DepthOfRecursion%

    DepthOfRecursion% = DepthOfRecursion% + 1

    NextListIndex% = ListIndex% + 1
    Do While NextListIndex% < olnContainer.ListCount
        If olnContainer.Indent(NextListIndex%) <= olnContainer.Indent(ListIndex%) Then Exit Do
        olnContainer.Expand(NextListIndex%) = False

        FreeEntry% = False
        GetOutlineEntry olnContainer.ItemData(NextListIndex%), DelEntry
        If DelEntry.Type.enum = T_ROOTOBJECT.enum Then
            'Nothing to do

        ElseIf DelEntry.Type.enum = T_CONTAINER.enum Then
            If DelEntry.Handle& <> 0 Then
                lRet& = SmsCloseContainer&(DelEntry.Handle&)
                If (lRet& <> SMS_OK) Then GoSub olnContainerCollapseErr

                DelEntry.Handle& = 0
                DelEntry.RetrieveData% = True
                SetOutlineEntry olnContainer.ItemData(NextListIndex%), DelEntry
            End If

        ElseIf DelEntry.Type.enum = T_FOLDER.enum Then
            If DelEntry.Handle& <> 0 Then
                lRet& = SmsCloseFolder&(DelEntry.Handle&)
            End If
            If (lRet& <> SMS_OK) Then GoSub olnContainerCollapseErr
            FreeEntry% = True

        ElseIf DelEntry.Type.enum = T_SCALAR.enum Then
            FreeEntry% = True

        Else: GoTo olnContainerCollapseErr
        End If

        If FreeEntry% Then
            FreeOutlineEntry olnContainer.ItemData(NextListIndex%)
            olnContainer.RemoveItem NextListIndex%
        Else
            NextListIndex% = NextListIndex% + 1
        End If
    Loop

    GetOutlineEntry olnContainer.ItemData(ListIndex%), SelEntry
    If SelEntry.RetrieveData% = False Then
        SelEntry.RetrieveData% = True
        SetOutlineEntry olnContainer.ItemData(ListIndex%), SelEntry
    End If

    If olnContainer.PictureType(ListIndex%) = MSOUTLINE_PICTURE_OPEN Then
        olnContainer.PictureType(ListIndex%) = MSOUTLINE_PICTURE_CLOSED
    End If

    DepthOfRecursion% = DepthOfRecursion% - 1
    If DepthOfRecursion% = 0 Then
        olnContainer.ListIndex = ListIndex%
        olnContainer_Click
    End If

    Exit Sub

olnContainerCollapseErr:
    MsgBox "Unexpected error collapsing item " + CStr(ListIndex%), MB_ICONEXCLAMATION, Me.Caption
    DepthOfRecursion% = DepthOfRecursion% - 1
    Return
End Sub

Private Sub olnContainer_DblClick()
    olnContainer.Expand(olnContainer.ListIndex) = Not olnContainer.Expand(olnContainer.ListIndex)
End Sub

Private Sub olnContainer_Expand(ListIndex%)
    Dim SelEntry As OutlineEntryData
    Dim FolderInfo As FOLDER_INFO, NextScalar As SCALAR
    Dim hSubFolder&, SubFolderType&, SubFolderTypeName$
    Dim SubFolderID$
    Dim i%, OldMousePointer%, lRet&

    GetOutlineEntry olnContainer.ItemData(ListIndex%), SelEntry
    If SelEntry.RetrieveData% Then
        'Put up the wait cursor
        OldMousePointer = MousePointer
        MousePointer = HOURGLASS

        If SelEntry.Type.enum = T_ROOTOBJECT.enum Then
            'Nothing to do

        ElseIf SelEntry.Type.enum = T_CONTAINER.enum Then
            'Rewind or open/fill the container
            If SelEntry.Handle& <> 0& Then
                lRet& = SmsRewind&(SelEntry.Handle&, RW_ALL)
                If lRet& <> SMS_OK Then GoTo olnContainerExpandErr
            Else
                lRet& = SmsOpenContainer&(SelEntry.Tag&, ghConnect&, SelEntry.Handle&)
                If lRet& <> SMS_OK Then GoTo olnContainerExpandErr

                lRet& = SmsPopulate&(SelEntry.Handle&, POP_SYNC, ByVal 0&)
                If lRet& <> SMS_OK And lRet& <> SMS_EMPTY Then GoTo olnContainerExpandErr
            End If

            'List the folders in this container
            i% = 1
            lRet& = SmsGetNextFolder&(SelEntry.Handle&, F_ANY, hSubFolder&)
            Do While lRet& = SMS_OK
                lRet& = SmsGetFolderID&(hSubFolder&, SubFolderID$)
                If lRet& <> SMS_OK Then GoTo olnContainerExpandErr

                lRet& = SmsGetFolderType&(hSubFolder&, SubFolderType&, SubFolderTypeName$)
                If lRet& <> SMS_OK Then GoTo olnContainerExpandErr

                If SelEntry.Tag = C_SITE Then
                    'Only list top level Site folders (Depth = 0)
                    lRet& = SmsGetScalarByName&(hSubFolder&, "Depth", NextScalar)
                    If lRet& <> SMS_OK Then GoTo olnContainerExpandErr
                Else
                    'List all sub folders
                    NextScalar.dwValue = 0
                End If

                If NextScalar.dwValue = 0 Then
                    olnContainer.AddItem SubFolderID$ + " (" + SubFolderTypeName$ + ")", ListIndex% + i%
                    olnContainer.Indent(ListIndex% + i%) = olnContainer.Indent(ListIndex%) + 1
                    olnContainer.ItemData(ListIndex% + i%) = AddOutlineEntry(T_FOLDER, SubFolderType&, hSubFolder&, True)
                    olnContainer.PictureType(ListIndex% + i%) = MSOUTLINE_PICTURE_CLOSED
                Else
                    lRet& = SmsCloseFolder&(hSubFolder&)
                End If

                lRet& = SmsGetNextFolder&(SelEntry.Handle&, F_ANY, hSubFolder&)
                i% = i% + 1
            Loop
            If lRet& <> SMS_NO_MORE_DATA Then GoTo olnContainerExpandErr

        ElseIf SelEntry.Type.enum = T_FOLDER.enum Then
            If SelEntry.Handle& = 0& Then
                GoTo olnContainerExpandErr
            Else
                lRet& = SmsRewind&(SelEntry.Handle&, RW_ALL)
                If lRet& <> SMS_OK Then GoTo olnContainerExpandErr
            End If

            'First list the sub-folders in this folder
            i% = 1
            lRet& = SmsGetNextFolder&(SelEntry.Handle&, F_ANY, hSubFolder&)
            Do While lRet& = SMS_OK
                lRet& = SmsGetFolderID&(hSubFolder&, SubFolderID$)
                If lRet& <> SMS_OK Then GoTo olnContainerExpandErr

                lRet& = SmsGetFolderType&(hSubFolder&, SubFolderType&, SubFolderTypeName$)
                If lRet& <> SMS_OK Then GoTo olnContainerExpandErr

                olnContainer.AddItem SubFolderID$ + " (" + SubFolderTypeName$ + ")", ListIndex% + i%
                olnContainer.Indent(ListIndex% + i%) = olnContainer.Indent(ListIndex%) + 1
                olnContainer.ItemData(ListIndex% + i%) = AddOutlineEntry(T_FOLDER, SubFolderType&, hSubFolder&, True)
                olnContainer.PictureType(ListIndex% + i%) = MSOUTLINE_PICTURE_CLOSED

                lRet& = SmsGetNextFolder&(SelEntry.Handle&, F_ANY, hSubFolder&)
                i% = i% + 1
            Loop
            If lRet& <> SMS_NO_MORE_DATA Then GoTo olnContainerExpandErr

            'Next list the scalars in this folder
            lRet& = SmsGetNextScalar&(SelEntry.Handle&, NextScalar)
            Do While lRet& = SMS_OK
                olnContainer.AddItem NextScalar.pszName$ + " (" + GetScalarTypeName$(NextScalar.scType) + ")", ListIndex% + i%
                olnContainer.Indent(ListIndex% + i%) = olnContainer.Indent(ListIndex%) + 1
                olnContainer.ItemData(ListIndex% + i%) = AddOutlineEntry(T_SCALAR, NextScalar.scType.enum, SelEntry.Handle&, True)
                olnContainer.PictureType(ListIndex% + i%) = MSOUTLINE_PICTURE_LEAF

                lRet& = SmsGetNextScalar&(SelEntry.Handle&, NextScalar)
                i% = i% + 1
            Loop
            If lRet& <> SMS_NO_MORE_DATA Then GoTo olnContainerExpandErr

        ElseIf SelEntry.Type.enum = T_SCALAR.enum Then
            'Nothing to do

        Else: GoTo olnContainerExpandErr
        End If

        SelEntry.RetrieveData% = False
        SetOutlineEntry olnContainer.ItemData(ListIndex%), SelEntry
        olnContainer.Expand(ListIndex%) = True

        'Restore the mouse pointer
        MousePointer = OldMousePointer%
    Else

        If olnContainer.PictureType(ListIndex%) = MSOUTLINE_PICTURE_CLOSED Then
            olnContainer.PictureType(ListIndex%) = MSOUTLINE_PICTURE_OPEN
        ElseIf olnContainer.PictureType(ListIndex%) = MSOUTLINE_PICTURE_OPEN Then
            olnContainer.Expand(ListIndex%) = False
        End If

        olnContainer.ListIndex = ListIndex%
        olnContainer_Click
    End If

    Exit Sub

olnContainerExpandErr:
    MsgBox "olnContainer_Expand: Unexpected error"

    'Restore the mouse pointer
    MousePointer = OldMousePointer%
End Sub

Private Sub olnContainer_PictureClick(ListIndex As Integer)
    olnContainer.ListIndex = ListIndex
    olnContainer_Click
End Sub

Private Sub olnContainer_PictureDblClick(ListIndex As Integer)
    olnContainer_PictureClick ListIndex
    olnContainer_DblClick
End Sub

Private Sub picFormBackdrop_Resize()
    Static OldWidth, OldHeight, OldSepLeft%

    If (OldWidth <> picFormBackdrop.Width Or OldSepLeft <> picSeperator.left) And picFormBackdrop.Width > picRightView.left Then
        picRightView.Width = picFormBackdrop.Width - picRightView.left

        OldWidth = picFormBackdrop.Width
    End If

    If OldHeight <> picFormBackdrop.Height And picFormBackdrop.Height > picLeftView.top Then
        picLeftView.Height = picFormBackdrop.Height - picLeftView.top
        picRightView.Height = picFormBackdrop.Height - picRightView.top
        picSeperator.Height = picFormBackdrop.Height - picSeperator.top + Y_PIX_SIZE

        OldHeight = picFormBackdrop.Height
        OldSepLeft = picSeperator.left
    End If
End Sub

Private Sub picLeftView_Resize()
    Static OldWidth, OldHeight

    If OldWidth <> picLeftView.Width Then
        pnlContainerHeader.Width = picLeftView.Width
        olnContainer.Width = picLeftView.Width

        OldWidth = picLeftView.Width
    End If

    If OldHeight <> picLeftView.Height And picLeftView.Height > olnContainer.top Then

olnContainer.Height = picLeftView.Height - olnContainer.top

        OldHeight = picLeftView.Height
    End If
End Sub

Private Sub picRightView_Resize()
    Static OldWidth, OldHeight, lItemHeight&

    If OldWidth <> picRightView.Width Then
        pnlInfoHeader.Width = picRightView.Width
        lstObjectInfo.Width = picRightView.Width - lstObjectInfo.left + X_PIX_SIZE

        OldWidth = picLeftView.Width
    End If

    If OldHeight <> picRightView.Height And picRightView.Height > pnlInfoHeader.Height Then
        lItemHeight& = SendMessage(lstObjectInfo.hwnd, LB_GETITEMHEIGHT, 0, 0)
        lstObjectInfo.Height = picRightView.Height - lstObjectInfo.top + lItemHeight& * Y_PIX_SIZE

        OldWidth = picLeftView.Width
    End If
End Sub

Private Sub picSeperator_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
    If Button And LEFT_BUTTON Then
        SeperatorDragging% = True
        SeperatorDragOffset% = x
    End If
End Sub

Private Sub picSeperator_MouseMove(Button As Integer, Shift As Integer, x As Single, y As Single)
    Dim OldLeft%

    If SeperatorDragging% And x <> OldLeft% And picSeperator.left + x - SeperatorDragOffset% > 0 Then
        picSeperator.left = picSeperator.left + x - SeperatorDragOffset%
        picLeftView.Width = picSeperator.left
        picRightView.left = picSeperator.left + picSeperator.Width
        picFormBackdrop_Resize
    End If
End Sub

Private Sub picSeperator_MouseUp(Button As Integer, Shift As Integer, x As Single, y As Single)
    If Button And LEFT_BUTTON Then
        SeperatorDragging% = False
    End If
End Sub


NEWCONTA.BAS

Attribute VB_Name = "basNEWCONTA"
'Global variables (For NewConta.FRM use ONLY!)
Global NewContaUserCanceled%
Global hNewContaConnection&
Global NewContaName$
Global NewContaTag&

Function NewContainer%(hConnect&, NewName$, NewTag&)
    hNewContaConnection& = hConnect&

    frmNewContainer.Show MODAL

    If Not NewContaUserCanceled% Then
        NewName$ = NewContaName$
        NewTag& = NewContaTag&
    End If

    NewContainer% = Not NewContaUserCanceled%
End Function


NEWCONTA.FRM

VERSION 4.00
Begin VB.Form frmNewContainer
   Appearance      =   0  'Flat
   BackColor       =   &H80000005&
   BorderStyle     =   1  'Fixed Single
   Caption         =   "New Container"
   ClientHeight    =   1995
   ClientLeft      =   2055
   ClientTop       =   1935
   ClientWidth     =   5670
   ControlBox      =   0   'False
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   0
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   2400
   Left            =   1995
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   1995
   ScaleWidth      =   5670
   Top             =   1590
   Width           =   5790
   Begin Threed.SSPanel pnlDialogBackdrop
      Height          =   1995
      Left            =   0
      TabIndex        =   8
      Top             =   0
      Width           =   5655
      _Version        =   65536
      _ExtentX        =   9975
      _ExtentY        =   3519
      _StockProps     =   15
      BackColor       =   -2147483633
      Begin Threed.SSPanel pnlTextBoxBackdrop
         Height          =   1635
         Left            =   240
         TabIndex        =   9
         Top             =   180
         Width           =   3915
         _Version        =   65536
         _ExtentX        =   6906
         _ExtentY        =   2884
         _StockProps     =   15
         BackColor       =   -2147483633
         Begin Threed.SSPanel pnlContainerType
            Height          =   375
            Left            =   120
            TabIndex        =   1
            Top             =   420
            Width           =   3495
            _Version        =   65536
            _ExtentX        =   6165
            _ExtentY        =   661
            _StockProps     =   15
            BackColor       =   -2147483633
            BevelOuter      =   1
            Begin VB.ComboBox cboContainerType
               Appearance      =   0  'Flat
               Height          =   300
               Left            =   30
               TabIndex        =   2
               Top             =   30
               Width           =   3435
            End
         End
         Begin Threed.SSPanel pnlContainerName
            Height          =   315
            Left            =   120
            TabIndex        =   4
            Top             =   1080
            Width           =   3495
            _Version        =   65536
            _ExtentX        =   6165
            _ExtentY        =   556
            _StockProps     =   15
            BackColor       =   -2147483633
            BevelOuter      =   1
            Begin VB.TextBox txtContainerName
               Appearance      =   0  'Flat
               BorderStyle     =   0  'None
               Height          =   255
               Left            =   30
               MaxLength       =   32
               TabIndex        =   5
               Top             =   30
               Width           =   3435
            End
         End
         Begin VB.Label lblContainerType
            Appearance      =   0  'Flat
            Caption         =   "&Type of new container:"
            ForeColor       =   &H80000008&
            Height          =   195
            Left            =   120
            TabIndex        =   0
            Top             =   180
            Width           =   1980
         End
         Begin VB.Label lblContainerName
            Appearance      =   0  'Flat
            Caption         =   "&Name of new container:"
            ForeColor       =   &H80000008&
            Height          =   195
            Left            =   120
            TabIndex        =   3
            Top             =   840
            Width           =   2040
         End
      End
      Begin VB.CommandButton cmdOk
         Appearance      =   0  'Flat
         Caption         =   "OK"
         Default         =   -1  'True
         Height          =   375
         Left            =   4320
         TabIndex        =   6
         Top             =   180
         Width           =   1095
      End
      Begin VB.CommandButton cmdCancel
         Appearance      =   0  'Flat
         Cancel          =   -1  'True
         Caption         =   "Cancel"
         Height          =   375
         Left            =   4320
         TabIndex        =   7
         Top             =   600
         Width           =   1095
      End
   End
End
Attribute VB_Name = "frmNewContainer"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

Dim OldMousePointer%
Dim CallingForm As Form

Private Sub cmdCancel_Click()
    Unload Me
End Sub

Private Sub cmdOk_Click()
    NewContaName$ = txtContainerName
    NewContaTag& = cboContainerType.ItemData(cboContainerType.ListIndex)

    NewContaUserCanceled% = False
    Unload Me
End Sub

Private Sub Form_Load()
    Dim NewLeft%, NewTop%, i%, Count&, lRet&
    Dim FolderInfos() As FOLDER_INFO

    'Save the old mouse pointer
    Set CallingForm = Screen.ActiveForm
    OldMousePointer% = CallingForm.MousePointer

    'Turn on normal pointer
    Me.MousePointer = NORMAL

    'Center form on calling form
    NewLeft% = CallingForm.left + (CallingForm.Width - Width) / 2
    If NewLeft% + Width > Screen.Width Then NewLeft% = Screen.Width - Width
    If NewLeft% < 0 Then NewLeft% = 0
    NewTop% = CallingForm.top + (CallingForm.Height - Height) / 2
    If NewTop% + Height > Screen.Height Then NewTop% = Screen.Height - Height
    If NewTop% < 0 Then NewTop% = 0
    left = NewLeft%
    top = NewTop%

    'Set default to be user canceled
    NewContaUserCanceled% = True

    'Enumerate containers
    lRet& = SmsEnumContainers&(FolderInfos, Count&)
    If (lRet& <> SMS_OK) Then GoTo FormLoadErr

    'Add each container to the dropdown list box
    For i% = 0 To CInt(Count&) - 1
        cboContainerType.AddItem FolderInfos(i%).pszTag, i%
        cboContainerType.ItemData(i%) = FolderInfos(i%).dwTag
    Next i%
    cboContainerType.ListIndex = 0

    Exit Sub

FormLoadErr:
    MsgBox "FormLoadErr: Unexpected error", MB_OK, Me.Caption
End Sub

Private Sub Form_Unload(Cancel As Integer)
    'Replace mouse pointer
    CallingForm.MousePointer = OldMousePointer%
End Sub


DBLOGIN.BAS

Attribute VB_Name = "basDBLOGIN"
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  DBLOGIN.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

'Global database variables (For DBLOGIN.FRM use ONLY!)
Global OpenDBInfo As SQL_CONNECT_PARAMS
Global OpenDBUserCanceled%
Global OpenDBConnect&

Function DBLogin%(DbInfo As SQL_CONNECT_PARAMS, hConnect&)

    'Copy values to OpenDB form
    OpenDBInfo.ds = DbInfo.ds
    OpenDBInfo.pszServer = DbInfo.pszServer
    OpenDBInfo.pszUserName = DbInfo.pszUserName
    OpenDBInfo.pszPasswd = DbInfo.pszPasswd
    OpenDBInfo.pszDbName = DbInfo.pszDbName
    OpenDBInfo.pszKey = DbInfo.pszKey
    OpenDBInfo.pFunc = DbInfo.pFunc

    OpenDBConnect& = hConnect&

    frmDBLogin.Show MODAL

    If Not OpenDBUserCanceled% Then
        'Set return values
        DbInfo.ds = OpenDBInfo.ds
        DbInfo.pszServer = OpenDBInfo.pszServer
        DbInfo.pszUserName = OpenDBInfo.pszUserName
        DbInfo.pszPasswd = OpenDBInfo.pszPasswd
        DbInfo.pszDbName = OpenDBInfo.pszDbName
        DbInfo.pszKey = OpenDBInfo.pszKey
        DbInfo.pFunc = OpenDBInfo.pFunc

        hConnect& = OpenDBConnect&
    End If

    DBLogin% = Not OpenDBUserCanceled%
End Function

Sub DBLogout(hConnect&)
    Dim lRet&

    If hConnect& Then
        'No sense checking this for an error
        lRet& = SmsDataSourceDisconnect&(hConnect&)
    End If
End Sub


DBLOGIN.FRM

VERSION 4.00
Begin VB.Form frmDBLogin
   Appearance      =   0  'Flat
   BorderStyle     =   3  'Fixed Dialog
   Caption         =   "Microsoft SMS Login"
   ClientHeight    =   3195
   ClientLeft      =   2535
   ClientTop       =   5070
   ClientWidth     =   5430
   ControlBox      =   0   'False
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   0
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   3600
   Left            =   2475
   LinkTopic       =   "Form2"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   3195
   ScaleWidth      =   5430
   Top             =   4725
   Width           =   5550
   Begin Threed.SSPanel pnlDialogBackdrop
      Height          =   3195
      Left            =   0
      TabIndex        =   18
      Top             =   0
      Width           =   5415
      _Version        =   65536
      _ExtentX        =   9551
      _ExtentY        =   5636
      _StockProps     =   15
      Begin Threed.SSPanel pnlIconBackdrop
         Height          =   1035
         Left            =   4140
         TabIndex        =   20
         Top             =   1980
         Width           =   1095
         _Version        =   65536
         _ExtentX        =   1931
         _ExtentY        =   1826
         _StockProps     =   15
         Begin VB.Image imgIcon
            Appearance      =   0  'Flat
            Height          =   480
            Left            =   300
            Picture         =   "DBLOGIN.frx":0000
            Top             =   300
            Width           =   480
         End
      End
      Begin VB.CommandButton cmdCancel
         Appearance      =   0  'Flat
         Cancel          =   -1  'True
         Caption         =   "Cancel"
         Height          =   375
         Left            =   4140
         TabIndex        =   17
         Top             =   600
         Width           =   1095
      End
      Begin VB.CommandButton cmdOk
         Appearance      =   0  'Flat
         Caption         =   "OK"
         Default         =   -1  'True
         Height          =   375
         Left            =   4140
         TabIndex        =   16
         Top             =   180
         Width           =   1095
      End
      Begin Threed.SSPanel pnlTextBoxBackdrop
         Height          =   2835
         Left            =   240
         TabIndex        =   19
         Top             =   180
         Width           =   3735
         _Version        =   65536
         _ExtentX        =   6588
         _ExtentY        =   5001
         _StockProps     =   15
         Begin Threed.SSPanel pnlPassword
            Height          =   315
            Left            =   120
            TabIndex        =   13
            Top             =   2400
            Width           =   3495
            _Version        =   65536
            _ExtentX        =   6165
            _ExtentY        =   556
            _StockProps     =   15
            BevelOuter      =   1
            Begin VB.PictureBox picPassword
               Appearance      =   0  'Flat
               BackColor       =   &H80000005&
               BorderStyle     =   0  'None
               ForeColor       =   &H80000008&
               Height          =   255
               Left            =   30
               ScaleHeight     =   255
               ScaleWidth      =   3435
               TabIndex        =   14
               TabStop         =   0   'False
               Top             =   30
               Width           =   3435
               Begin VB.TextBox txtPassword
                  Appearance      =   0  'Flat
                  BorderStyle     =   0  'None
                  Height          =   195
                  Left            =   30
                  PasswordChar    =   "*"
                  TabIndex        =   15
                  Tag             =   "OL"
                  Top             =   30
                  Width           =   3375
               End
            End
         End
         Begin Threed.SSPanel pnlUserName
            Height          =   315
            Left            =   120
            TabIndex        =   9
            Top             =   1740
            Width           =   3495
            _Version        =   65536
            _ExtentX        =   6165
            _ExtentY        =   556
            _StockProps     =   15
            BevelOuter      =   1
            Begin VB.PictureBox picUserName
               Appearance      =   0  'Flat
               BackColor       =   &H80000005&
               BorderStyle     =   0  'None
               ForeColor       =   &H80000008&
               Height          =   255
               Left            =   30
               ScaleHeight     =   255
               ScaleWidth      =   3435
               TabIndex        =   10
               TabStop         =   0   'False
               Top             =   30
               Width           =   3435
               Begin VB.TextBox txtUserName
                  Appearance      =   0  'Flat
                  BorderStyle     =   0  'None
                  Height          =   195
                  Left            =   30
                  TabIndex        =   11
                  Tag             =   "OL"
                  Top             =   30
                  Width           =   3375
               End
            End
         End
         Begin Threed.SSPanel pnlDBName
            Height          =   315
            Left            =   120
            TabIndex        =   1
            Top             =   420
            Width           =   3495
            _Version        =   65536
            _ExtentX        =   6165
            _ExtentY        =   556
            _StockProps     =   15
            BevelOuter      =   1
            Begin VB.PictureBox picDBName
               Appearance      =   0  'Flat
               BackColor       =   &H80000005&
               BorderStyle     =   0  'None
               ForeColor       =   &H80000008&
               Height          =   255
               Left            =   30
               ScaleHeight     =   255
               ScaleWidth      =   3435
               TabIndex        =   2
               TabStop         =   0   'False
               Top             =   30
               Width           =   3435
               Begin VB.TextBox txtDBName
                  Appearance      =   0  'Flat
                  BorderStyle     =   0  'None
                  Height          =   195
                  Left            =   30
                  TabIndex        =   3
                  Top             =   30
                  Width           =   3375
               End
            End
         End
         Begin Threed.SSPanel pnlDataBase
            Height          =   315
            Left            =   120
            TabIndex        =   5
            Top             =   1080
            Width           =   3495
            _Version        =   65536
            _ExtentX        =   6165
            _ExtentY        =   556
            _StockProps     =   15
            BevelOuter      =   1
            Begin VB.PictureBox picDataBase
               Appearance      =   0  'Flat
               BackColor       =   &H80000005&
               BorderStyle     =   0  'None
               ForeColor       =   &H80000008&
               Height          =   255
               Left            =   30
               ScaleHeight     =   255
               ScaleWidth      =   3435
               TabIndex        =   6
               TabStop         =   0   'False
               Top             =   30
               Width           =   3435
               Begin VB.TextBox txtDataBase
                  Appearance      =   0  'Flat
                  BorderStyle     =   0  'None
                  Height          =   195
                  Left            =   30
                  TabIndex        =   7
                  Tag             =   "OL"
                  Top             =   30
                  Width           =   3375
               End
            End
         End
         Begin VB.Label lblPassword
            Appearance      =   0  'Flat
            Caption         =   "&Password:"
            ForeColor       =   &H80000008&
            Height          =   255
            Left            =   120
            TabIndex        =   12
            Top             =   2160
            Width           =   1335
         End
         Begin VB.Label lblUserName
            Appearance      =   0  'Flat
            Caption         =   "&Login ID:"
            ForeColor       =   &H80000008&
            Height          =   255
            Left            =   120
            TabIndex        =   8
            Top             =   1500
            Width           =   1335
         End
         Begin VB.Label lblDBName
            Appearance      =   0  'Flat
            Caption         =   "&SQL Server Name:"
            ForeColor       =   &H80000008&
            Height          =   255
            Left            =   120
            TabIndex        =   0
            Top             =   180
            Width           =   1590
         End
         Begin VB.Label lblDataBase
            Appearance      =   0  'Flat
            Caption         =   "&Database:"
            ForeColor       =   &H80000008&
            Height          =   255
            Left            =   120
            TabIndex        =   4
            Top             =   840
            Width           =   1335
         End
      End
   End
End
Attribute VB_Name = "frmDBLogin"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  DBLOGIN.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim OldMousePointer%
Dim CallingForm As Form

Private Sub CheckOkStatus()
    cmdOK.Enabled = (txtDBName > "" And txtDatabase > "" And txtUserName > "")
End Sub

Private Sub cmdCancel_Click()
    Unload Me
End Sub

Private Sub cmdOk_Click()
    Dim lRet&, LoadDLLFailed%

    OpenDBInfo.ds = DB_SQL
    OpenDBInfo.pszServer = txtDBName
    OpenDBInfo.pszUserName = txtUserName
    OpenDBInfo.pszPasswd = txtPassword
    OpenDBInfo.pszDbName = txtDatabase
    OpenDBInfo.pszKey = ""
    OpenDBInfo.pFunc = 0

    'Put up wait cursor
    MousePointer = HOURGLASS

    'Login to SMS
    ' (This loads the DLLs since it is the first call)
    LoadDLLFailed% = False
    On Error Resume Next
    lRet& = SmsDataSourceConnect&(OpenDBInfo, OpenDBConnect&)
    If (Err > 0) Then LoadDLLFailed% = True
    On Error GoTo 0

    'Restore normal cursor
    MousePointer = DEFAULT

    If LoadDLLFailed% Then
        MsgBox "Unable to load VB4SMS32.OLB:" + Chr$(10) + "Please check that all SMSAPI dlls are installed and search paths are correct.", MB_OK + MB_ICONINFORMATION
        Exit Sub
    End If

    If (lRet& <> SMS_OK) Then
        MsgBox "Login Failed!", MB_ICONEXCLAMATION, Caption
        txtPassword.SetFocus
        Exit Sub
    End If

    'Set to login success
    OpenDBUserCanceled% = False
    MousePointer = NORMAL
    Unload Me
End Sub

Private Sub Form_Activate()
    If txtDBName = "" Then
        txtDBName.SetFocus
    ElseIf txtDatabase = "" Then
        txtDatabase.SetFocus
    ElseIf txtUserName = "" Then
        txtUserName.SetFocus
    Else
        txtPassword.SetFocus
    End If
End Sub

Private Sub Form_Load()
    Dim NewLeft%, NewTop%

    'Save the old mouse pointer
    Set CallingForm = Screen.ActiveForm
    OldMousePointer% = CallingForm.MousePointer

    'Turn on normal pointer
    MousePointer = NORMAL

    'Center form on calling form
    NewLeft% = CallingForm.left + (CallingForm.Width - Width) / 2
    If NewLeft% + Width > Screen.Width Then NewLeft% = Screen.Width - Width
    If NewLeft% < 0 Then NewLeft% = 0
    NewTop% = CallingForm.top + (CallingForm.Height - Height) / 2
    If NewTop% + Height > Screen.Height Then NewTop% = Screen.Height - Height
    If NewTop% < 0 Then NewTop% = 0
    left = NewLeft%
    top = NewTop%

    'Set default to be user canceled
    OpenDBUserCanceled% = True

    CheckOkStatus
End Sub

Private Sub Form_Unload(Cancel As Integer)
    'Replace mouse pointer
    CallingForm.MousePointer = OldMousePointer%
End Sub

Private Sub txtDataBase_Change()
    CheckOkStatus
End Sub

Private Sub txtDataBase_GotFocus()
    txtDatabase.SelStart = 0
    txtDatabase.SelLength = Len(txtDatabase)
End Sub

Private Sub txtDBName_Change()
    CheckOkStatus
End Sub

Private Sub txtDBName_GotFocus()
    txtDBName.SelStart = 0
    txtDBName.SelLength = Len(txtDBName)
End Sub

Private Sub txtPassword_GotFocus()
    txtPassword.SelStart = 0
    txtPassword.SelLength = Len(txtPassword)
End Sub

Private Sub txtUserName_Change()
    CheckOkStatus
End Sub

Private Sub txtUserName_GotFocus()
    txtUserName.SelStart = 0
    txtUserName.SelLength = Len(txtUserName)
End Sub


PROGRESS.FRM

VERSION 2.00
Begin Form Progress
   BorderStyle     =   3  'Fixed Double
   ClientHeight    =   1380
   ClientLeft      =   1110
   ClientTop       =   1470
   ClientWidth     =   5550
   DrawMode        =   10  'Not Xor Pen
   FillColor       =   &H80000008&
   Height          =   1785
   Left            =   1050
   LinkTopic       =   "Form2"
   ScaleHeight     =   1380
   ScaleWidth      =   5550
   Top             =   1125
   Width           =   5670
   Begin CommandButton Cancel
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   315
      Left            =   2100
      TabIndex        =   1
      Top             =   960
      Width           =   1335
   End
   Begin PictureBox PIC_Bar
      BackColor       =   &H00FFFFFF&
      DrawMode        =   4  'Not Copy Pen
      FillColor       =   &H00FF0000&
      FillStyle       =   0  'Solid
      ForeColor       =   &H00FF0000&
      Height          =   375
      Left            =   360
      ScaleHeight     =   345
      ScaleWidth      =   4845
      TabIndex        =   0
      Top             =   390
      Width           =   4875
      Begin PictureBox PIC_Percent
         AutoRedraw      =   -1  'True
         BackColor       =   &H00FFFFFF&
         BorderStyle     =   0  'None
         DrawMode        =   10  'Not Xor Pen
         FillColor       =   &H00FF0000&
         ForeColor       =   &H00FF0000&
         Height          =   255
         Left            =   2160
         ScaleHeight     =   255
         ScaleWidth      =   615
         TabIndex        =   6
         Top             =   60
         Width           =   615
      End
   End
   Begin Label Text
      Alignment       =   2  'Center
      Height          =   195
      Left            =   0
      TabIndex        =   5
      Top             =   120
      Width           =   5535
   End
   Begin Label Value
      Caption         =   " 0"
      Height          =   195
      Left            =   1380
      TabIndex        =   4
      Top             =   750
      Visible         =   0   'False
      Width           =   2055
   End
   Begin Label Max
      Caption         =   " 100"
      Height          =   195
      Left            =   3480
      TabIndex        =   3
      Top             =   750
      Visible         =   0   'False
      Width           =   2055
   End
   Begin Label Min
      Caption         =   " 0"
      Height          =   195
      Left            =   0
      TabIndex        =   2
      Top             =   750
      Visible         =   0   'False
      Width           =   2055
   End
End
Option Explicit

Dim OldPer, CallingForm As Form

'SetWindowPos() declaration
Declare Sub SetWindowPos Lib "User" (ByVal hWnd%, ByVal hWndInsertAfter%, ByVal x%, ByVal y%, ByVal cx%, ByVal cy%, ByVal fuFlags%)

'SetWindowPos() and WINDOWPOS flags
Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Const SWP_NOZORDER = &H4
Const SWP_NOREDRAW = &H8
Const SWP_NOACTIVATE = &H10

'SetWindowPos() hwndInsertAfter field values
Const HWND_TOP = 0
Const HWND_BOTTOM = 1
Const HWND_TOPMOST = -1
Const HWND_NOTOPMOST = -2

Sub Cancel_Click ()
   Cancel.Enabled = False
   Progress.Hide
End Sub

Sub Form_Load ()
   'Identify the calling form
   Set CallingForm = Screen.ActiveForm

   'Center form on currently active form
   Me.Left = CallingForm.Left + CallingForm.Width / 2 - Me.Width / 2
   If Me.Left < 0 Then Me.Left = 0
   If Me.Left + Me.Width > Screen.Width Then Me.Left = Screen.Width - Me.Width
   Me.Top = CallingForm.Top + CallingForm.Height / 4 - Me.Height / 2
   If Me.Top < 0 Then Me.Top = 0
   If Me.Top + Me.Height > Screen.Height Then Me.Top = Screen.Height - Me.Height

   'Make form topmost
   SetWindowPos Me.hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE Or SWP_NOMOVE Or SWP_NOSIZE Or SWP_NOREDRAW

   'Size and Center the PIC_Percent in the PIC_Bar
   PIC_Percent.Width = PIC_Percent.TextWidth("100%")
   PIC_Percent.Height = PIC_Percent.TextHeight("100%")
   PIC_Percent.Left = (PIC_Bar.Width - PIC_Percent.Width) / 2
   PIC_Percent.Top = (PIC_Bar.Height - PIC_Percent.Height) / 2

   PIC_Percent.CurrentX = (PIC_Percent.Width - PIC_Percent.TextWidth("0%")) / 2
   PIC_Percent.CurrentY = (PIC_Percent.Height - PIC_Percent.TextHeight("0%")) / 2
   PIC_Percent.Print "0%"

   'Enable the Cancel button
   Cancel.Enabled = True
End Sub

Sub Form_Unload (Cancel As Integer)
    CallingForm.SetFocus
End Sub

Sub Max_Change ()
   Value_Change
End Sub

Sub Min_Change ()
   Value_Change
End Sub

Sub PIC_Bar_Paint ()
   OldPer = 0
   Value_Change
End Sub

Sub Value_Change ()
   Dim Value, Min, Max, Per, Percent$, TempClr&, x1%, x2%

   'Convert label captions into real values
   Value = Val(Me.Value)
   Min = Val(Me.Min)
   Max = Val(Me.Max)

   'Determine Percent complete and convert to a string
   If Value < Min Then Value = Min
   If Value > Max Then Value = Max
   If Min < Max Then
      Per = (Value - Min) / (Max - Min)
      Percent$ = LTrim$(Str$(CInt(Per * 100))) + "%"
   Else
      Per = 1
      Percent$ = "100%"
   End If

   'Quit early if no change
   If CInt(Per * 100) = CInt(OldPer * 100) Then
      DoEvents
      Exit Sub
   End If

   x1% = PIC_Bar.Width * OldPer
   x2% = PIC_Bar.Width * Per

   'Update percent text
   PIC_Percent.Cls
   PIC_Percent.CurrentX = (PIC_Percent.Width - PIC_Percent.TextWidth(Percent$)) / 2
   PIC_Percent.CurrentY = (PIC_Percent.Height - PIC_Percent.TextHeight(Percent$)) / 2
   PIC_Percent.Print Percent$

   'Invert up to the end of the progress bar
   PIC_Percent.Line (-15, 0)-(x2% - PIC_Percent.Left, PIC_Percent.Height), PIC_Percent.FillColor, BF

   'Now update the progress bar
   If x1% > x2% Then
      'Going backwards, so erase the bar
      PIC_Bar.DrawMode = WHITENESS
      PIC_Bar.Line (x1%, 0)-(x2%, PIC_Bar.Height), , BF
      'Fix-up 1 pixel wide line (line drawn below)
      x1% = x2%
   End If

   'Set the bar if going forward, or fix-up if going backward
   PIC_Bar.DrawMode = COPY_PEN
   PIC_Bar.Line (x1%, 0)-(x2%, PIC_Bar.Height), , BF

   'Save current percent value and Do Events
   OldPer = Per
   DoEvents
End Sub


SMSABOUT.BAS

Attribute VB_Name = "basSMSABOUT"
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  STATBAR.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   6/13/95      Created.
'//
'//****************************************************************************

'For use by frmSMSAbout ONLY!
Global SMSAboutCaption$, SMSAboutInfo$

Sub SmsAbout(Caption$, Info$)
    SMSAboutCaption$ = Caption$
    SMSAboutInfo$ = Info$

    frmSMSAbout.Show MODAL
End Sub


SMSABOUT.FRM

VERSION 4.00
Begin VB.Form frmSMSAbout
   Appearance      =   0  'Flat
   BackColor       =   &H80000005&
   BorderStyle     =   3  'Fixed Dialog
   Caption         =   "About SMS Report"
   ClientHeight    =   2895
   ClientLeft      =   1080
   ClientTop       =   1485
   ClientWidth     =   7755
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   0
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   3300
   Left            =   1020
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   2895
   ScaleWidth      =   7755
   Top             =   1140
   Width           =   7875
   Begin Threed.SSPanel pnlDialogBackdrop
      Height          =   2895
      Left            =   0
      TabIndex        =   0
      Top             =   0
      Width           =   7755
      _Version        =   65536
      _ExtentX        =   13679
      _ExtentY        =   5106
      _StockProps     =   15
      BackColor       =   -2147483633
      Begin VB.Timer tmrAnimation
         Interval        =   100
         Left            =   180
         Top             =   2340
      End
      Begin VB.CommandButton cmdOk
         Appearance      =   0  'Flat
         Caption         =   "OK"
         Default         =   -1  'True
         Height          =   375
         Left            =   6480
         TabIndex        =   3
         Top             =   2400
         Width           =   1095
      End
      Begin Threed.SSPanel pnlOutlineBackdrop
         Height          =   1995
         Left            =   180
         TabIndex        =   1
         Top             =   240
         Width           =   7395
         _Version        =   65536
         _ExtentX        =   13044
         _ExtentY        =   3519
         _StockProps     =   15
         BackColor       =   -2147483633
         Begin Threed.SSPanel pnlVersion
            Height          =   315
            Left            =   240
            TabIndex        =   2
            Top             =   1500
            Width           =   6915
            _Version        =   65536
            _ExtentX        =   12197
            _ExtentY        =   556
            _StockProps     =   15
            BackColor       =   -2147483633
            BevelOuter      =   1
            Autosize        =   3
         End
         Begin VB.PictureBox picLogo
            Appearance      =   0  'Flat
            BackColor       =   &H80000005&
            BorderStyle     =   0  'None
            ForeColor       =   &H80000008&
            Height          =   960
            Left            =   240
            ScaleHeight     =   960
            ScaleWidth      =   960
            TabIndex        =   4
            Top             =   180
            Width           =   960
         End
         Begin PicClip.PictureClip pcpAnimation
            Left            =   -3885
            Top             =   -1965
            _Version        =   65536
            _ExtentX        =   8625
            _ExtentY        =   5186
            _StockProps     =   0
            Rows            =   3
            Cols            =   5
            Picture         =   "SMSABOUT.frx":0000
         End
         Begin VB.Label Label1
            Appearance      =   0  'Flat
            Caption         =   "Version Information:"
            ForeColor       =   &H80000008&
            Height          =   255
            Left            =   240
            TabIndex        =   6
            Top             =   1260
            Width           =   1875
         End
         Begin VB.Label lblInfo
            Alignment       =   2  'Center
            Appearance      =   0  'Flat
            ForeColor       =   &H80000008&
            Height          =   975
            Left            =   1380
            TabIndex        =   5
            Top             =   180
            Width           =   5775
         End
      End
   End
End
Attribute VB_Name = "frmSMSAbout"
Attribute VB_Creatable = False
Attribute VB_Exposed = False

Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  SMSABOUT.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   6/13/95      Created.
'//
'//****************************************************************************

Dim OldMousePointer%
Dim CallingForm As Form

Private Sub cmdOk_Click()
    Unload Me
End Sub

Private Sub Form_Load()
    Dim NewLeft%, NewTop%
    Dim lRet&, Ver$

    'Save the old mouse pointer
    Set CallingForm = Screen.ActiveForm
    OldMousePointer% = CallingForm.MousePointer

    'Turn on normal pointer
    MousePointer = NORMAL

    'Center form on calling form
    NewLeft% = CallingForm.left + (CallingForm.Width - Width) / 2
    If NewLeft% + Width > Screen.Width Then NewLeft% = Screen.Width - Width
    If NewLeft% < 0 Then NewLeft% = 0
    NewTop% = CallingForm.top + (CallingForm.Height - Height) / 2
    If NewTop% + Height > Screen.Height Then NewTop% = Screen.Height - Height
    If NewTop% < 0 Then NewTop% = 0
    left = NewLeft%
    top = NewTop%

    'Set caption and info fields
    Caption = SMSAboutCaption$
    lblInfo = SMSAboutInfo$

    'Get SMS version string
    lRet& = SmsAPIVer&(Ver$)
    If lRet& = SMS_OK Then
        pnlVersion.Caption = Ver$
    Else: pnlVersion.Caption = "<Error obtaining version string>"
    End If
End Sub

Private Sub Form_Unload(Cancel As Integer)
    'Replace mouse pointer
    CallingForm.MousePointer = OldMousePointer%
End Sub

Private Sub tmrAnimation_Timer()
    Static index%

    picLogo.Picture = pcpAnimation.GraphicCell(index%)
    index% = (index% + 1) Mod 14
End Sub


STATBAR.BAS

Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  STATBAR.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim CurrentStatusBar As Label

Function GetStatBarMsg$ ()
    If CurrentStatusBar Is Nothing Then Exit Function
    GetStatBarMsg$ = CurrentStatusBar
End Function

Sub SetCurrentStatusBar (Bar As Label)
    Set CurrentStatusBar = Bar
End Sub

Sub SetStatBarMsg (msg As String)
    ' displays message on status bar
    If CurrentStatusBar Is Nothing Then Exit Sub
    If msg = "" Then
        CurrentStatusBar = "Ready"
    Else
        CurrentStatusBar = msg
    End If
    CurrentStatusBar.Refresh
End Sub


WINSTUFF.BAS

Attribute VB_Name = "basWINSTUFF"
Option Explicit

' Private Window Messages Start Here:
Public Const WM_USER = &H400

Public Const LB_SETTABSTOPS = &H192
Public Const LB_GETITEMHEIGHT = &H1A1
Public Const EM_SETTABSTOPS = &HCB

' SetWindowPos Flags
Public Const SWP_NOSIZE = &H1
Public Const SWP_NOMOVE = &H2
Public Const SWP_NOZORDER = &H4
Public Const SWP_NOREDRAW = &H8
Public Const SWP_NOACTIVATE = &H10
Public Const SWP_FRAMECHANGED = &H20        '  The frame changed: send WM_NCCALCSIZE
Public Const SWP_SHOWWINDOW = &H40
Public Const SWP_HIDEWINDOW = &H80
Public Const SWP_NOCOPYBITS = &H100
Public Const SWP_NOOWNERZORDER = &H200      '  Don't do owner Z ordering

' SetWindowPos() hwndInsertAfter values
Public Const HWND_TOP = 0
Public Const HWND_BOTTOM = 1
Public Const HWND_TOPMOST = -1
Public Const HWND_NOTOPMOST = -2

'Const for GetTempFileName()
Public Const TF_FORCEDRIVE = &H80

Type Rect
    left As Long
    top As Long
    right As Long
    bottom As Long
End Type

Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Declare Function WritePrivateProfileString Lib "kernel32" Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As Any, ByVal lpString As Any, ByVal lpFileName As String) As Long
Declare Function GetWindowsDirectory Lib "kernel32" Alias "GetWindowsDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) As Long
Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, lpRect As Rect) As Long
Declare Function GetClientRect Lib "user32" (ByVal hwnd As Long, lpRect As Rect) As Long
Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Declare Function GetTempFileName Lib "kernel32" Alias "GetTempFileNameA" (ByVal lpszPath As String, ByVal lpPrefixString As String, ByVal wUnique As Long, ByVal lpTempFileName As String) As Long
Declare Function GetDialogBaseUnits Lib "user32" () As Long

Sub GetPixelSizeInTwips(frm As Form, x%, y%)
    Dim r As Rect

    GetWindowRect frm.hwnd, r
    x% = frm.Width / (r.right - r.left)
    y% = frm.Height / (r.bottom - r.top)
End Sub


GENERIC.BAS

Attribute VB_Name = "GENERIC"
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  GENERIC.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Function NewGenericReportWindow%(hConnect&, ByVal FileSpec$, ErrStr$)
    Dim i%, Ret&, TempPath$, TempSpec$, frm As Form

    'Default return value zero (Failed)
    NewGenericReportWindow% = 0

    'Allocate space for returned string
    TempSpec$ = String$(256, 0)

    'Get a temp file name
    TempPath$ = Environ$("TEMP")
    Ret& = GetTempFileName(TempPath$, "MDB", 0, TempSpec$)
    ErrStr$ = "Could not get a temporary file name for the new report."
    If (Ret& = 0) Then Exit Function

    'Clean up return string of null terminator
    i% = InStr(TempSpec$, Chr$(0))
    If i% = 0 Then i% = Len(TempSpec$) + 1
    TempSpec$ = left$(TempSpec$, i% - 1)

    'Create a temporary document file
    On Error GoTo NewGenericReportWindowErr
    If FileSpec$ > "" Then
        'File Open: so copy the file the user selected
        ErrStr$ = "Could not create a temporary copy of " + FileSpec$ + ".+"
        FileCopy FileSpec$, TempSpec$
    Else
        'File New: so create a new empty document HERE
        ErrStr$ = "Could not create a temporary file for the new report."
    End If
    On Error GoTo 0

    'Pass the new document the file name to use
    InitReporthConnect& = hConnect&
    InitReportFileSpec$ = FileSpec$
    InitReportTempSpec$ = TempSpec$
    If InitReportFileSpec$ <= "" Then InitReportNewNumber% = InitReportNewNumber% + 1

    'Create a new document window
    On Error GoTo NewGenericReportWindowErr
    ErrStr$ = "Could not create site report document window."
    Set frm = New frmGenericReport
    Load frm
    On Error GoTo 0

    'Register document window with MDI

    i% = RegisterReportWindow%(frm, ErrStr$)
    If i% <= 0 Then GoTo NewGenericReportWindowErr

    'Show the document window
    frm.Show
    NewGenericReportWindow% = i%
    Exit Function

NewGenericReportWindowErr:
    On Error Resume Next
    If Not frm Is Nothing Then Unload frm
    Kill TempSpec$
    Exit Function
End Function


GENERIC.FRM

VERSION 4.00
Begin VB.Form frmGenericReport
   Appearance      =   0  'Flat
   BackColor       =   &H80000005&
   Caption         =   "Generic Report"
   ClientHeight    =   4350
   ClientLeft      =   1860
   ClientTop       =   2580
   ClientWidth     =   4530
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   4755
   Left            =   1800
   LinkTopic       =   "Form1"
   MDIChild        =   -1  'True
   ScaleHeight     =   4350
   ScaleWidth      =   4530
   Top             =   2235
   Width           =   4650
   Begin VB.CommandButton cmdSave
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Save"
      Height          =   315
      Left            =   420
      TabIndex        =   4
      TabStop         =   0   'False
      Top             =   2100
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdSaveAs
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Save As"
      Height          =   315
      Left            =   420
      TabIndex        =   3
      TabStop         =   0   'False
      Top             =   2400
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CheckBox chkDirty
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Dirty Bit"
      ForeColor       =   &H80000008&
      Height          =   195
      Left            =   420
      TabIndex        =   2
      TabStop         =   0   'False
      Top             =   1860
      Visible         =   0   'False
      Width           =   1155
   End
   Begin VB.CommandButton cmdPrintPreview
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Print Preview"
      Height          =   315
      Left            =   420
      TabIndex        =   1
      TabStop         =   0   'False
      Top             =   3000
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdPrint
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Print"
      Height          =   315
      Left            =   420
      TabIndex        =   0
      TabStop         =   0   'False
      Top             =   2700
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdAbort
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Abort"
      Height          =   315
      Left            =   420
      TabIndex        =   7
      TabStop         =   0   'False
      Top             =   3900
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdInitialize
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Initialize"
      Height          =   315
      Left            =   420
      TabIndex        =   8
      TabStop         =   0   'False
      Top             =   3600
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdExport
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Export"
      Height          =   315
      Left            =   420
      TabIndex        =   6
      TabStop         =   0   'False
      Top             =   3300
      Visible         =   0   'False
      Width           =   3735
   End
   Begin Crystal.CrystalReport CrystalReport
      Left            =   960
      Top             =   360
      _ExtentX        =   741
      _ExtentY        =   741
      _StockProps     =   0
      ReportFileName  =   ""
      Destination     =   0
      WindowLeft      =   100
      WindowTop       =   100
      WindowWidth     =   480
      WindowHeight    =   300
      WindowTitle     =   ""
      WindowBorderStyle=   2
      WindowControlBox=   -1  'True
      WindowMaxButton =   -1  'True
      WindowMinButton =   -1  'True
      CopiesToPrinter =   1
      PrintFileName   =   ""
      PrintFileType   =   2
      SelectionFormula=   ""
      GroupSelectionFormula=   ""
      Connect         =   ""
      UserName        =   ""
      ReportSource    =   0
      BoundReportHeading=   ""
      BoundReportFooter=   -1  'True
   End
   Begin MSComDlg.CommonDialog CommonDialog
      Left            =   480
      Top             =   360
      _ExtentX        =   847
      _ExtentY        =   847
      _StockProps     =   0
   End
   Begin VB.Label lblFileSpec
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "File Specification"
      ForeColor       =   &H80000008&
      Height          =   195
      Left            =   1740
      TabIndex        =   5
      Top             =   1860
      Visible         =   0   'False
      Width           =   2175
   End
End
Attribute VB_Name = "frmGenericReport"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  GENERIC.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim hConnect&
Dim ReportTempSpec$
Dim NewNumber%
Dim db As Database
Dim dbt As Database

Private Sub chkDirty_Click()
    UpdateCaption
End Sub

Private Sub cmdAbort_Click()
    'Handle user abort of current lengthy operation HERE
End Sub

Private Sub cmdExport_Click()
    Dim FileSpec$, nPos%, Resp%, FileKilled%

    'Build suggested file spec
    If lblFileSpec > "" Then
        nPos% = InStr(lblFileSpec, ".") - 1
        If nPos% < 0 Then nPos% = Len(lblFileSpec)
        FileSpec$ = left$(lblFileSpec, nPos) + ".TXT"
    Else: FileSpec$ = "*.TXT"
    End If

    'Get from the user the print file name
    FileSpec$ = GetSaveAsFileSpec$(FileSpec$, "ASCII Text (*.TXT) |*.TXT |All Files (*.*) |*.*")
    If FileSpec$ <= "" Then Exit Sub

    'See if we need to delete existing file
    '(Crystal Reports can't overwrite an existing file)
    If Dir$(FileSpec$) > "" Then
        FileKilled% = 0
        Do
            On Error Resume Next
            Kill FileSpec
            If Err > 0 Then
                Resp% = MsgBox("An error occured writing to file " + FileSpec$ + ":" + Chr$(10) + Error$ + Chr$(10) + Chr$(10) + "Do you want to try again?", MB_RETRYCANCEL Or MB_ICONQUESTION, Caption)
                If Resp% = IDCANCEL Then Exit Sub
            Else: FileKilled% = True
            End If
        Loop Until FileKilled%
    End If

    'Export report code HERE
End Sub

Private Sub cmdInitialize_Click()
    If lblFileSpec > "" Then
        'Flag that we are in data reading mode
        'Initialize for File Open HERE
    Else
        'Flag that we are in gathering data mode
        'Initialize for File New HERE
    End If
End Sub

Private Sub cmdPrint_Click()
    'See if we need the print dialog
    If gbNeedPrintDialog% = True Then
        'Put up the print dialog
        CommonDialog.Flags = PD_NOSELECTION Or PD_NOPAGENUMS Or PD_HIDEPRINTTOFILE
        CommonDialog.Copies = 1
        CommonDialog.CancelError = True
        On Error Resume Next
        CommonDialog.Action = DLG_PRINT
        If Err = CDERR_CANCEL Then Exit Sub
        If Err > 0 Then
            MsgBox "An unexpected error occured:" + Chr$(10) + Chr$(10) + Error$, MB_OK Or MB_ICONEXCLAMATION, Caption
            Exit Sub
        End If
        On Error GoTo 0

        'Handle user printer settings HERE
    Else
        'Supply printer default settings HERE
    End If

    'Print document HERE
End Sub

Private Sub cmdPrintPreview_Click()
    'Print Preview document HERE
End Sub

Private Sub cmdSave_Click()
    'See if we need to do a Save or a Save As
    If lblFileSpec > "" Then
        SaveDoc lblFileSpec
    Else: cmdSaveAs_Click
    End If
End Sub

Private Sub cmdSaveAs_Click()
    Dim NewFileSpec$

    NewFileSpec$ = GetSaveAsFileSpec$(lblFileSpec, "Xxxxx (*.XXX)|*.XXX|All Files (*.*)|*.*") 'HERE
    If NewFileSpec$ <= "" Then Exit Sub

    SaveDoc NewFileSpec$
End Sub

Private Sub Form_Load()
    chkDirty = False

    NewNumber% = InitReportNewNumber%
    hConnect& = InitReporthConnect&
    lblFileSpec = InitReportFileSpec$
    ReportTempSpec$ = InitReportTempSpec$
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    Dim Resp%, FileSpec$

    'Give the user a chance to save document
    If chkDirty Then
        Resp% = MsgBox("This report has been modified. Save data before closing?", MB_YESNOCANCEL Or MB_ICONQUESTION, Caption)
        If Resp% = IDYES Then
            cmdSave_Click
            If chkDirty Then Cancel = True
        ElseIf Resp% = IDCANCEL Then: Cancel = True
        End If
    End If
End Sub

Private Sub Form_Resize()
    Static OldWidth, OldHeight

    If WindowState = MINIMIZED Then Exit Sub

    If OldWidth <> Width Then

        OldWidth = Width
    End If

    If OldHeight <> Height Then

        OldHeight = Height
    End If
End Sub

Private Sub Form_Unload(Cancel As Integer)
    'Kill temp file
    On Error Resume Next
    Kill ReportTempSpec$
    On Error GoTo 0

    FreeReportWindow Me
End Sub

Private Function GetSaveAsFileSpec$(ByVal InitFileSpec$, Filter$)
    'Default return value empty (User canceled)
    GetSaveAsFileSpec$ = ""

    CommonDialog.CancelError = True
    CommonDialog.filename = InitFileSpec$
    CommonDialog.Filter = Filter$
    CommonDialog.FilterIndex = 1
    CommonDialog.Flags = OFN_OVERWRITEPROMPT Or OFN_PATHMUSTEXIST Or OFN_HIDEREADONLY
    On Error Resume Next
    CommonDialog.Action = DLG_FILE_SAVE
    If Err = CDERR_CANCEL Then Exit Function
    If Err > 0 Then
        MsgBox "An unexpected error occured:" + Chr$(10) + Error$, MB_OK Or MB_ICONEXCLAMATION, Caption
        Exit Function
    End If
    On Error GoTo 0

    GetSaveAsFileSpec$ = CommonDialog.filename
End Function

Private Sub lblFileSpec_Change()
    UpdateCaption
End Sub

Private Sub SaveDoc(ByVal FileSpec$)
    Dim ReportSaved%, Resp%

    ReportSaved% = False
    Do
        On Error Resume Next
        FileCopy ReportTempSpec$, FileSpec$
        If Err > 0 Then
            Resp% = MsgBox("An error occured trying to save " + FileSpec$ + ":" + Chr$(10) + Error$ + Chr$(10) + Chr$(10) + "Do you want to try again?", MB_OKCANCEL Or MB_ICONQUESTION, Caption)
            If Resp% = IDCANCEL Then Exit Do
        Else: ReportSaved% = True
        End If
    Loop Until ReportSaved%

    If ReportSaved% Then
        lblFileSpec = FileSpec$
        chkDirty = False
    End If
End Sub

Private Sub UpdateCaption()
    Dim NewCaption$

    If lblFileSpec > "" Then
        NewCaption$ = lblFileSpec
    Else: NewCaption$ = "New Generic Report " + CStr(NewNumber)
    End If

    If chkDirty Then NewCaption$ = NewCaption$ + "*"

    Caption = NewCaption$
End Sub


GLOBAL.FRM

VERSION 4.00
Begin VB.Form frmGlobal
   Appearance      =   0  'Flat
   BackColor       =   &H80000005&
   Caption         =   "Global Utilities"
   ClientHeight    =   1050
   ClientLeft      =   3015
   ClientTop       =   4380
   ClientWidth     =   3480
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   1455
   Left            =   2955
   LinkTopic       =   "Form1"
   ScaleHeight     =   1050
   ScaleWidth      =   3480
   Top             =   4035
   Visible         =   0   'False
   Width           =   3600
   Begin Crystal.CrystalReport CrystalReport
      Left            =   1800
      Top             =   300
      _ExtentX        =   741
      _ExtentY        =   741
      _StockProps     =   0
      ReportFileName  =   ""
      Destination     =   0
      WindowLeft      =   100
      WindowTop       =   100
      WindowWidth     =   480
      WindowHeight    =   300
      WindowTitle     =   ""
      WindowBorderStyle=   2
      WindowControlBox=   -1  'True
      WindowMaxButton =   -1  'True
      WindowMinButton =   -1  'True
      CopiesToPrinter =   1
      PrintFileName   =   ""
      PrintFileType   =   2
      SelectionFormula=   ""
      GroupSelectionFormula=   ""
      Connect         =   ""
      UserName        =   ""
      ReportSource    =   -4083
      BoundReportHeading=   ""
      BoundReportFooter=   -1  'True
   End
   Begin MSComDlg.CommonDialog CommonDialog
      Left            =   1020
      Top             =   300
      _Version        =   65536
      _ExtentX        =   847
      _ExtentY        =   847
      _StockProps     =   0
   End
End
Attribute VB_Name = "frmGlobal"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit


MACHGRP.BAS

Attribute VB_Name = "MACHGRP"
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  MACHGRP.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Type IDLookupRec
    ID As Long
    StrID As String
    StrName As String
End Type

Type AttributeRec
    MachineGroupID As Long
    MachineID As Long
    PropertyID As Long
    PropertyNum As Integer
    AttributeID As Long
    Value As String
End Type

'For use by frmMachineGrpReport ONLY
Global InitMachineGroupID$
Global InitMachineGroupProperties$()
Global InitMachineGroupPropertyCount%
Global InitMachineGroupIsFiltered%

'For use by QueryMachineGroups%() and FetchMachineGroups%() ONLY
Dim dyQueryMachineGroups As Recordset

'For use by QueryMachines%() and FetchMachines%() ONLY
Dim dyQueryMachines As Recordset

Function AddAttribute&(db As Database, Attrib$, AttributeName$)
    Dim IDLookup As IDLookupRec, Count%

    ' Default return value zero (Failed)
    AddAttribute& = 0

    IDLookup.StrID$ = Attrib$
    IDLookup.StrName$ = AttributeName$
    Count% = FindAttributeID%(db, "AttributeIDs", IDLookup)
    If Count% = 0 Then
        IDLookup.ID& = GetUniqueID&()
        If InsertAttributeID&(db, "AttributeIDs", IDLookup) = 1 Then AddAttribute& = IDLookup.ID&
    ElseIf Count% = 1 Then
        AddAttribute& = IDLookup.ID&
    End If
End Function

Function AddMachine&(db As Database, Machine$, MachineName$)
    Dim IDLookup As IDLookupRec, Count%

    ' Default return value zero (Failed)
    AddMachine& = 0

    IDLookup.StrID$ = Machine$
    IDLookup.StrName$ = MachineName$
    Count% = FindMachineID%(db, "MachineIDs", IDLookup)
    If Count% = 0 Then
        IDLookup.ID& = GetUniqueID&()
        If InsertMachineID&(db, "MachineIDs", IDLookup) = 1 Then AddMachine& = IDLookup.ID&
    ElseIf Count% = 1 Then
        AddMachine& = IDLookup.ID&
    End If
End Function

Function AddMachineGroup&(db As Database, Group$, GroupName$)
    Dim IDLookup As IDLookupRec, Count%

    ' Default return value zero (Failed)
    AddMachineGroup& = 0

    IDLookup.StrID$ = Group$
    IDLookup.StrName$ = GroupName$
    Count% = FindMachineGroupID%(db, "MachineGroupID", IDLookup)
    If Count% = 0 Then
        IDLookup.ID& = GetUniqueID&()
        If InsertMachineGroupID&(db, "MachineGroupID", IDLookup) = 1 Then AddMachineGroup& = IDLookup.ID&
    ElseIf Count% = 1 Then
        AddMachineGroup& = IDLookup.ID&
    End If
End Function

Function AddProperty&(db As Database, Property$, PropertyName$)
    Dim IDLookup As IDLookupRec, Count%

    ' Default return value zero (Failed)
    AddProperty& = 0

    IDLookup.StrID$ = Property$
    IDLookup.StrName$ = PropertyName$
    Count% = FindPropertyID%(db, "PropertyIDs", IDLookup)
    If Count% = 0 Then
        IDLookup.ID& = GetUniqueID&()
        If InsertPropertyID&(db, "PropertyIDs", IDLookup) = 1 Then AddProperty& = IDLookup.ID&
    ElseIf Count% = 1 Then
        AddProperty& = IDLookup.ID&
    End If
End Function

Function FetchMachineGroups%(IDLookup As IDLookupRec, Flush%, DBError%)
'   Fetches another Machine Group from the dyQueryMachineGroups dynaset
'Parameters:
'   IDLookup - Filled in with next machine name and ID.
'   Flush% - Usually False. If True, the fetch is ended.
'   DBError% - Returns True when DB error occures.
'Return Value:
'   True if another record fetched, else False (no more)

    DBError% = True
    FetchMachineGroups% = False

    If dyQueryMachineGroups.EOF Or Flush% Then
        dyQueryMachineGroups.Close
    Else
        ' Populate the site record from the dyQueryMachineGroups
        ' dynaset and find the next record
        IDLookup.ID& = dyQueryMachineGroups("ID")
        If IsNull(dyQueryMachineGroups("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyQueryMachineGroups("StrID"))
        End If
        If IsNull(dyQueryMachineGroups("StrName")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyQueryMachineGroups("StrName"))
        End If

        On Error Resume Next
        dyQueryMachineGroups.MoveNext
        If Err > 0 Then Exit Function
        On Error GoTo 0

        FetchMachineGroups% = True
    End If

    DBError% = False
End Function

Function FetchMachines%(IDLookup As IDLookupRec, Flush%, DBError%)
'   Fetches another Machine from the dyQueryMachines dynaset
'Parameters:
'   IDLookup - Filled in with next machine name and ID.
'   Flush% - Usually False. If True, the fetch is ended.
'   DBError% - Returns True when DB error occures.
'Return Value:
'   True if another record fetched, else False (no more)

    DBError% = True
    FetchMachines% = False

    If dyQueryMachines.EOF Or Flush% Then
        dyQueryMachines.Close
    Else
        ' Populate the site record from the dyQueryMachines
        ' dynaset and find the next record
        IDLookup.ID& = dyQueryMachines("ID")
        If IsNull(dyQueryMachines("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyQueryMachines("StrID"))
        End If
        If IsNull(dyQueryMachines("StrName")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyQueryMachines("StrName"))
        End If

        On Error Resume Next
        dyQueryMachines.MoveNext
        If Err > 0 Then Exit Function
        On Error GoTo 0

        FetchMachines% = True
    End If

    DBError% = False
End Function

Function FindAttributeID%(db As Database, TableName$, IDLookup As IDLookupRec)
    Dim qy As QueryDef, dyIDLookup As Recordset

    'Set default return value -1 (Database error)
    FindAttributeID% = -1

    'Build query
    Set qy = db.QueryDefs("FindAttributeID")
    qy![INPUT StrID] = IDLookup.StrID$

    'Perform query
    On Error Resume Next
    Set dyIDLookup = qy.OpenRecordset(dbOpenDynaset, dbReadOnly)
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    ' Populate the site record
    If Not dyIDLookup.EOF Then
        IDLookup.ID& = dyIDLookup("ID")
        If IsNull(dyIDLookup("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyIDLookup("StrID"))
        End If
        If IsNull(dyIDLookup("StrName")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyIDLookup("StrName"))
        End If
    End If

    ' Return the record count
    FindAttributeID% = dyIDLookup.RecordCount
End Function

Function FindMachineGroupID%(db As Database, TableName$, IDLookup As IDLookupRec)
    Dim qy As QueryDef, dyIDLookup As Recordset

    'Set default return value -1 (Database error)
    FindMachineGroupID% = -1

    'Build query
    Set qy = db.QueryDefs("FindMachineGroupID")
    qy![INPUT StrID] = IDLookup.StrID$

    'Perform query
    On Error Resume Next
    Set dyIDLookup = qy.OpenRecordset(dbOpenDynaset, dbReadOnly)
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    ' Populate the site record
    If Not dyIDLookup.EOF Then
        IDLookup.ID& = dyIDLookup("ID")
        If IsNull(dyIDLookup("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyIDLookup("StrID"))
        End If
        If IsNull(dyIDLookup("StrName")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyIDLookup("StrName"))
        End If
    End If

    ' Return the record count
    FindMachineGroupID% = dyIDLookup.RecordCount
End Function

Function FindMachineID%(db As Database, TableName$, IDLookup As IDLookupRec)
    Dim qy As QueryDef, dyIDLookup As Recordset

    'Set default return value -1 (Database error)
    FindMachineID% = -1

    'Build query
    Set qy = db.QueryDefs("FindMachineID")
    qy![INPUT StrID] = IDLookup.StrID$

    'Perform query
    On Error Resume Next
    Set dyIDLookup = qy.OpenRecordset(dbOpenDynaset, dbReadOnly)
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    ' Populate the site record
    If Not dyIDLookup.EOF Then
        IDLookup.ID& = dyIDLookup("ID")
        If IsNull(dyIDLookup("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyIDLookup("StrID"))
        End If
        If IsNull(dyIDLookup("StrName")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyIDLookup("StrName"))
        End If
    End If

    ' Return the record count
    FindMachineID% = dyIDLookup.RecordCount
End Function

Function FindPropertyID%(db As Database, TableName$, IDLookup As IDLookupRec)
    Dim qy As QueryDef, dyIDLookup As Recordset

    'Set default return value -1 (Database error)
    FindPropertyID% = -1

    'Build query
    Set qy = db.QueryDefs("FindPropertyID")
    qy![INPUT StrID] = IDLookup.StrID$

    'Perform query
    On Error Resume Next
    Set dyIDLookup = qy.OpenRecordset(dbOpenDynaset, dbReadOnly)
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    ' Populate the site record
    If Not dyIDLookup.EOF Then
        IDLookup.ID& = dyIDLookup("ID")
        If IsNull(dyIDLookup("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyIDLookup("StrID"))
        End If
        If IsNull(dyIDLookup("StrName")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyIDLookup("StrName"))
        End If
    End If

    ' Return the record count
    FindPropertyID% = dyIDLookup.RecordCount
End Function

Function GetMachineGroup%(db As Database, IDLookup As IDLookupRec)
    Dim Sql$
    Dim dyIDLookup As Recordset

    'Set default return value False (Database error)
    GetMachineGroup% = False

    Sql$ = "SELECT ID, StrID, StrName FROM [MachineGroupID]"

    ' If an error occurs exit the function
    On Error Resume Next
    Set dyIDLookup = db.OpenRecordset(Sql$, dbOpenDynaset)
    If Err > 0 Then Exit Function
    On Error GoTo 0

    ' Populate the site record
    If Not dyIDLookup.EOF Then
        IDLookup.ID& = dyIDLookup("ID")
        If IsNull(dyIDLookup("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyIDLookup("StrID"))
        End If
        If IsNull(dyIDLookup("StrName")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyIDLookup("StrName"))
        End If
    End If

    ' Return the record count
    GetMachineGroup% = True
End Function

Function GetUniqueID&()
    Static UniqueID&

    UniqueID& = UniqueID& + 1
    GetUniqueID& = UniqueID&
End Function

Function InsertAttribute&(db As Database, Attrib As AttributeRec)
'   Inserts the attribute record into the given data base
'Parameters
'   db     - The database used for the query
'   Attrib - The attribute record
'Return Value:
'   The number of records inserted

    Dim qy As QueryDef

    ' Default return value False (Failed)
    InsertAttribute& = -1

    ' Build the insert statement
    Set qy = db.QueryDefs("InsertAttribute")
    qy![INPUT MachineID] = Attrib.MachineID&
    qy![INPUT PropertyID] = Attrib.PropertyID&
    qy![INPUT PropertyNum] = Attrib.PropertyNum%
    qy![INPUT AttributeID] = Attrib.AttributeID&
    qy![INPUT Value] = Attrib.Value$

    'Try to insert the record
    On Error Resume Next
    qy.Execute
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    'Return insert count
    InsertAttribute& = 1
End Function

Function InsertAttributeID&(db As Database, TableName$, IDLookup As IDLookupRec)
    Dim qy As QueryDef

    ' Default return value False (Failed)
    InsertAttributeID& = -1

    ' Build the insert statement
    Set qy = db.QueryDefs("InsertAttributeID")
    qy![INPUT ID] = IDLookup.ID&
    qy![INPUT StrID] = IDLookup.StrID$
    qy![INPUT StrName] = IDLookup.StrName$

    'Try to insert the record
    On Error Resume Next
    qy.Execute
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    'Return insert count
    InsertAttributeID& = 1
End Function

Function InsertMachineGroupID&(db As Database, TableName$, IDLookup As IDLookupRec)
    Dim qy As QueryDef

    ' Default return value False (Failed)
    InsertMachineGroupID& = -1

    ' Build the insert statement
    Set qy = db.QueryDefs("InsertMachineGroupID")
    qy![INPUT ID] = IDLookup.ID&
    qy![INPUT StrID] = IDLookup.StrID$
    qy![INPUT StrName] = IDLookup.StrName$

    'Try to insert the record
    On Error Resume Next
    qy.Execute
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    'Return insert count
    InsertMachineGroupID& = 1
End Function

Function InsertMachineID&(db As Database, TableName$, IDLookup As IDLookupRec)
    Dim qy As QueryDef

    ' Default return value False (Failed)
    InsertMachineID& = -1

    ' Build the insert statement
    Set qy = db.QueryDefs("InsertMachineID")
    qy![INPUT ID] = IDLookup.ID&
    qy![INPUT StrID] = IDLookup.StrID$
    qy![INPUT StrName] = IDLookup.StrName$

    'Try to insert the record
    On Error Resume Next
    qy.Execute
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    'Return insert count
    InsertMachineID& = 1
End Function

Function InsertPropertyID&(db As Database, TableName$, IDLookup As IDLookupRec)
    Dim qy As QueryDef

    ' Default return value False (Failed)
    InsertPropertyID& = -1

    ' Build the insert statement
    Set qy = db.QueryDefs("InsertPropertyID")
    qy![INPUT ID] = IDLookup.ID&
    qy![INPUT StrID] = IDLookup.StrID$
    qy![INPUT StrName] = IDLookup.StrName$

    'Try to insert the record
    On Error Resume Next
    qy.Execute
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    'Return insert count
    InsertPropertyID& = 1
End Function

Function MakeNiceForCrystalReports$(ByVal s$)
    Dim nPos%

    If Len(s$) > 32 Then
        'Maybe a path environment variable. Add spaces after semicolons
        nPos% = InStr(s$, ";")
        Do While nPos% > 0
            s$ = left$(s$, nPos%) + " " + Mid$(s$, nPos% + 1)
            nPos% = InStr(nPos% + 1, s$, ";")
        Loop
    End If
    MakeNiceForCrystalReports$ = s$
End Function

Function NewMachineGrpDatabase%(db As Database, ErrStr$)
    Dim td As TableDef, qy As QueryDef, fd As Field, ix As Index

    'Default return value False (Failed)
    NewMachineGrpDatabase% = False

    On Error GoTo NewMachineGrpDatabaseErr

    Set td = New TableDef
    td.Name = "MachineGroupID"

        'Fields
        Set fd = New Field
        fd.Name = "ID"
        fd.Type = DB_LONG
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "StrID"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "StrName"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        'Index
        Set ix = New Index
        ix.Name = "Key"
        ix.Primary = True
        ix.Unique = True
        ix.Fields = "ID"
        td.Indexes.Append ix

    db.TableDefs.Append td

    Set td = New TableDef
    td.Name = "MachineIDs"

        'Fields
        Set fd = New Field
        fd.Name = "ID"
        fd.Type = DB_LONG
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "StrID"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "StrName"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        'Index
        Set ix = New Index
        ix.Name = "Key"
        ix.Primary = True
        ix.Unique = True
        ix.Fields = "ID"
        td.Indexes.Append ix

    db.TableDefs.Append td

    Set td = New TableDef
    td.Name = "PropertyIDs"

        'Fields
        Set fd = New Field
        fd.Name = "ID"
        fd.Type = DB_LONG
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "StrID"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "StrName"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        'Index
        Set ix = New Index
        ix.Name = "Key"
        ix.Primary = True
        ix.Unique = True
        ix.Fields = "ID"
        td.Indexes.Append ix

    db.TableDefs.Append td

    Set td = New TableDef
    td.Name = "AttributeIDs"

        'Fields
        Set fd = New Field
        fd.Name = "ID"
        fd.Type = DB_LONG
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "StrID"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "StrName"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        'Index
        Set ix = New Index
        ix.Name = "Key"
        ix.Primary = True
        ix.Unique = True
        ix.Fields = "ID"
        td.Indexes.Append ix

    db.TableDefs.Append td

    Set td = New TableDef
    td.Name = "Attributes"

        'Fields
        Set fd = New Field
        fd.Name = "MachineID"
        fd.Type = DB_LONG
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "PropertyID"
        fd.Type = DB_LONG
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "PropertyNum"
        fd.Type = DB_INTEGER
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "AttributeID"
        fd.Type = DB_LONG
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "Value"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        'Index
        Set ix = New Index
        ix.Name = "SortKey"
        ix.Primary = True
        ix.Unique = True
        ix.Fields = "MachineID;PropertyID;PropertyNum;AttributeID"
        td.Indexes.Append ix

    db.TableDefs.Append td

    'Make QueryDefs for common insert queries and selects
    Set qy = db.CreateQueryDef("InsertAttribute", "PARAMETERS [INPUT MachineID] Long, [INPUT PropertyID] Long, [INPUT PropertyNum] Short, [INPUT AttributeID] Long, [INPUT Value] Text; INSERT INTO [Attributes] (MachineID, PropertyID, PropertyNum, AttributeID, [Value]) SELECT [INPUT MachineID] As MachineID, [INPUT PropertyID] As PropertyID, [INPUT PropertyNum] As PropertyNum, [INPUT AttributeID] As AttributeID, '|[INPUT Value]|' As [Value]")

    Set qy = db.CreateQueryDef("InsertMachineGroupID", "PARAMETERS [INPUT ID] Long, [INPUT StrID] Text, [INPUT StrName] Text; INSERT INTO MachineGroupID (ID, StrID, StrName) SELECT [INPUT ID] As ID, '|[INPUT StrID]|' As StrID, '|[INPUT StrName]|' As StrName")
    Set qy = db.CreateQueryDef("InsertMachineID", "PARAMETERS [INPUT ID] Long, [INPUT StrID] Text, [INPUT StrName] Text; INSERT INTO MachineIDs (ID, StrID, StrName) SELECT [INPUT ID] As ID, '|[INPUT StrID]|' As StrID, '|[INPUT StrName]|' As StrName")
    Set qy = db.CreateQueryDef("InsertPropertyID", "PARAMETERS [INPUT ID] Long, [INPUT StrID] Text, [INPUT StrName] Text; INSERT INTO PropertyIDs (ID, StrID, StrName) SELECT [INPUT ID] As ID, '|[INPUT StrID]|' As StrID, '|[INPUT StrName]|' As StrName")
    Set qy = db.CreateQueryDef("InsertAttributeID", "PARAMETERS [INPUT ID] Long, [INPUT StrID] Text, [INPUT StrName] Text; INSERT INTO AttributeIDs (ID, StrID, StrName) SELECT [INPUT ID] As ID, '|[INPUT StrID]|' As StrID, '|[INPUT StrName]|' As StrName")

    Set qy = db.CreateQueryDef("FindMachineGroupID", "PARAMETERS [INPUT StrID] Text; SELECT ID, StrID, StrName FROM MachineGroupID WHERE StrID = '|[INPUT StrID]|'")
    Set qy = db.CreateQueryDef("FindMachineID", "PARAMETERS [INPUT StrID] Text; SELECT ID, StrID, StrName FROM MachineIDs WHERE StrID = '|[INPUT StrID]|'")
    Set qy = db.CreateQueryDef("FindPropertyID", "PARAMETERS [INPUT StrID] Text; SELECT ID, StrID, StrName FROM PropertyIDs WHERE StrID = '|[INPUT StrID]|'")
    Set qy = db.CreateQueryDef("FindAttributeID", "PARAMETERS [INPUT StrID] Text; SELECT ID, StrID, StrName FROM AttributeIDs WHERE StrID = '|[INPUT StrID]|'")

    'Return success
    NewMachineGrpDatabase% = True
    Exit Function

NewMachineGrpDatabaseErr:
    ErrStr$ = Error$
    Exit Function
End Function

Function NewMachineGrpReportWindow%(hConnect&, ByVal FileSpec$, ErrStr$)
    Dim i%, Ret&, TempPath$, TempSpec$, frm As Form, MachineGroupID$
    Dim db As Database, td As TableDef, fd As Field, ix As Index
    Dim Resp%

    'Default return value zero (Failed)
    NewMachineGrpReportWindow% = 0

    'Allocate space for returned string
    TempSpec$ = String$(256, 0)

    'Get a temp file name
    TempPath$ = Environ$("TEMP")
    Ret& = GetTempFileName(TempPath$, "MDB", 0, TempSpec$)
    ErrStr$ = "Could not get a temporary file name for the new report."
    If (Ret& = 0) Then Exit Function

    'Clean up return string of null terminator
    i% = InStr(TempSpec$, Chr$(0))
    If i% = 0 Then i% = Len(TempSpec$) + 1
    TempSpec$ = left$(TempSpec$, i% - 1)

    'Create a temporary document file
    On Error GoTo NewMachineGrpReportWindowErr
    If FileSpec$ > "" Then
        'File Open: so copy the file the user selected
        ErrStr$ = "Could not create a temporary copy of " + FileSpec$ + ".+"
        FileCopy FileSpec$, TempSpec$
        Resp% = True
    Else
        'File New: so create a new empty document HERE
        ErrStr$ = "Could not create a temporary file for the new report."
        Kill TempSpec$
        Set db = CreateDatabase(TempSpec$, DB_LANG_GENERAL)
        i% = NewMachineGrpDatabase%(db, ErrStr$)
        db.Close
        If Not i% Then GoTo NewMachineGrpReportWindowErr

        'Let the user pick the machine groups
        Resp% = PickMachineGroups%(hConnect&, db, MachineGroupID$)
        If Resp% Then
            Resp% = PickProperties%(hConnect&, MachineGroupID$, InitMachineGroupIsFiltered%, InitMachineGroupProperties$(), InitMachineGroupPropertyCount%)
        End If
    End If
    On Error GoTo 0

    If Resp% Then
        'Pass the new document the file name to use
        InitMachineGroupID$ = MachineGroupID$
        InitReporthConnect& = hConnect&
        InitReportFileSpec$ = FileSpec$
        InitReportTempSpec$ = TempSpec$
        If InitReportFileSpec$ <= "" Then InitReportNewNumber% = InitReportNewNumber% + 1

        'Create a new document window
        On Error GoTo NewMachineGrpReportWindowErr
        ErrStr$ = "Could not create site report document window."
        Set frm = New frmMachineGrpReport
        Load frm
        On Error GoTo 0

        'Register document window with MDI
        i% = RegisterReportWindow%(frm, ErrStr$)
        If i% <= 0 Then GoTo NewMachineGrpReportWindowErr

        'Show the document window
        frm.Show
        NewMachineGrpReportWindow% = i%
    Else
        'User canceled
        Kill TempSpec$
        NewMachineGrpReportWindow% = -1
    End If

    Exit Function

NewMachineGrpReportWindowErr:
    On Error Resume Next
    If Not frm Is Nothing Then Unload frm
    Kill TempSpec$
    Exit Function
End Function

Function QueryMachineGroups%(db As Database, Attrib As AttributeRec)
'   Queries the Attribute table for all machine groups and creates
'   a dynaset (dyQueryMachineGroups) used by FetchMachineGroups()
'Parameters
'   db - The database used for the query
'   Attrib - Not used.  All machine groups are fetched.
'Return Value:
'   True if no error, else False

    Dim Sql$, Where$

    'Default return value False (Database Error)
    QueryMachineGroups% = False

    ' Build the SQL statement
    Sql$ = "SELECT DISTINCT ID, StrID, StrName FROM MachineGroupIDs, Attributes"

    'Build the Where clause
    Where$ = " WHERE MachineGroupID = ID"

    'Append the where clause onto query string
    Sql$ = Sql$ + Where$

    'Perform the query
    On Error Resume Next
    Set dyQueryMachineGroups = db.OpenRecordset(Sql$, dbOpenDynaset)
    If Err > 0 Then Exit Function
    On Error GoTo 0

    'Every thing is ok
    QueryMachineGroups% = True
End Function

Function QueryMachines%(db As Database, Attrib As AttributeRec)
'   Queries the Attribute table and creates
'   a dynaset (dyQueryMachines) used by FetchMachines()
'Parameters
'   db - The database used for the query
'   Attrib - The machines to fetch are indicated by the following fields
'       MachineGroupID& - Specifies the Machine Group
'Return Value:
'   True if no error, else False

    Dim Sql$, Where$

    'Default return value False (Database Error)
    QueryMachines% = False

    ' Build the SQL statement
    Sql$ = "SELECT DISTINCT ID, StrID, StrName FROM MachineIDs, Attributes"

    'Build the Where clause
    Where$ = " WHERE MachineID = ID"

    'Append the where clause onto query string
    Sql$ = Sql$ + Where$

    'Perform the query
    On Error Resume Next
    Set dyQueryMachines = db.OpenRecordset(Sql$, dbOpenDynaset)
    If Err > 0 Then Exit Function
    On Error GoTo 0

    'Every thing is ok
    QueryMachines% = True
End Function


MACHGRP.FRM

VERSION 4.00
Begin VB.Form frmMachineGrpReport
   Appearance      =   0  'Flat
   BackColor       =   &H80000005&
   Caption         =   "Machine Group Report"
   ClientHeight    =   4215
   ClientLeft      =   1560
   ClientTop       =   2610
   ClientWidth     =   8505
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   4620
   Icon            =   "MACHGRP.frx":0000
   Left            =   1500
   LinkTopic       =   "Form1"
   MDIChild        =   -1  'True
   ScaleHeight     =   4215
   ScaleWidth      =   8505
   Top             =   2265
   Width           =   8625
   Begin VB.CommandButton cmdSave
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Save"
      Height          =   315
      Left            =   480
      TabIndex        =   4
      TabStop         =   0   'False
      Top             =   2580
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdSaveAs
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Save As"
      Height          =   315
      Left            =   480
      TabIndex        =   3
      TabStop         =   0   'False
      Top             =   2880
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CheckBox chkDirty
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Dirty Bit"
      ForeColor       =   &H80000008&
      Height          =   195
      Left            =   4440
      TabIndex        =   2
      TabStop         =   0   'False
      Top             =   3540
      Visible         =   0   'False
      Width           =   1155
   End
   Begin VB.CommandButton cmdPrintPreview
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Print Preview"
      Height          =   315
      Left            =   480
      TabIndex        =   1
      TabStop         =   0   'False
      Top             =   3480
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdPrint
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Print"
      Height          =   315
      Left            =   480
      TabIndex        =   0
      TabStop         =   0   'False
      Top             =   3180
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdAbort
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Abort"
      Height          =   315
      Left            =   4440
      TabIndex        =   7
      TabStop         =   0   'False
      Top             =   3180
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdInitialize
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Initialize"
      Height          =   315
      Left            =   4440
      TabIndex        =   8
      TabStop         =   0   'False
      Top             =   2880
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdExport
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Export"
      Height          =   315
      Left            =   4440
      TabIndex        =   6
      TabStop         =   0   'False
      Top             =   2580
      Visible         =   0   'False
      Width           =   3735
   End
   Begin Crystal.CrystalReport CrystalReport
      Left            =   840
      Top             =   960
      _ExtentX        =   741
      _ExtentY        =   741
      _StockProps     =   0
      ReportFileName  =   ""
      Destination     =   0
      WindowLeft      =   100
      WindowTop       =   100
      WindowWidth     =   480
      WindowHeight    =   300
      WindowTitle     =   ""
      WindowBorderStyle=   2
      WindowControlBox=   -1  'True
      WindowMaxButton =   -1  'True
      WindowMinButton =   -1  'True
      CopiesToPrinter =   1
      PrintFileName   =   ""
      PrintFileType   =   2
      SelectionFormula=   ""
      GroupSelectionFormula=   ""
      Connect         =   ""
      UserName        =   ""
      ReportSource    =   0
      BoundReportHeading=   ""
      BoundReportFooter=   -1  'True
   End
   Begin MSComDlg.CommonDialog CommonDialog
      Left            =   360
      Top             =   960
      _ExtentX        =   847
      _ExtentY        =   847
      _StockProps     =   0
   End
   Begin MSOutl.Outline olnData
      Height          =   750
      Left            =   2340
      TabIndex        =   9
      Top             =   240
      Width           =   1500
      _Version        =   65536
      _ExtentX        =   2646
      _ExtentY        =   1323
      _StockProps     =   77
      BorderStyle     =   0
      Style           =   5
   End
   Begin VB.Label lblFileSpec
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "File Specification"
      ForeColor       =   &H80000008&
      Height          =   195
      Left            =   5940
      TabIndex        =   5
      Top             =   3540
      Visible         =   0   'False
      Width           =   2175
   End
End
Attribute VB_Name = "frmMachineGrpReport"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  MACHGRP.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim hConnect&
Dim ReportTempSpec$
Dim NewNumber%
Dim db As Database
Dim dbt As Database

Dim MachineGroupID$
Dim PropertyList$()
Dim PropertyCount%
Dim bFilterProperties%

'Some relavent names for the outline control pictures
Const MSOUTLINE_PICTURE_GROUP = MSOUTLINE_PICTURE_CLOSED
Const MSOUTLINE_PICTURE_MACHINE = MSOUTLINE_PICTURE_OPEN
Const MSOUTLINE_PICTURE_PROPERTY = MSOUTLINE_PICTURE_LEAF

'State flags
Dim bGetData%  'If True, getting machine data from SMS.
Dim bReadData% 'If True, reading machine data from file.

Private Sub chkDirty_Click()
    UpdateCaption
End Sub

Private Sub cmdAbort_Click()
    'Handle user abort of current lengthy operation
    If bGetData% Then
        If MsgBox("Do you want to stop gathering machine data?" + Chr$(10) + Chr$(10) + "Click Yes to abort, No to continue.", MB_YESNO + MB_ICONQUESTION, Caption) = IDYES Then
            bGetData% = False
        End If
    ElseIf bReadData% Then
        If MsgBox("Do you want to stop reading machine data?" + Chr$(10) + Chr$(10) + "Click Yes to abort, No to continue.", MB_YESNO + MB_ICONQUESTION, Caption) = IDYES Then
            bReadData% = False
        End If
    End If
End Sub

Private Sub cmdExport_Click()
    Dim FileSpec$, nPos%, Resp%, FileKilled%

    'Build suggested file spec
    If lblFileSpec > "" Then
        nPos% = InStr(lblFileSpec, ".") - 1
        If nPos% < 0 Then nPos% = Len(lblFileSpec)
        FileSpec$ = left$(lblFileSpec, nPos) + ".TXT"
    Else: FileSpec$ = "*.TXT"
    End If

    'Get from the user the print file name
    FileSpec$ = GetSaveAsFileSpec$(FileSpec$, "ASCII Text (*.TXT) |*.TXT |All Files (*.*) |*.*")
    If FileSpec$ <= "" Then Exit Sub

    'See if we need to delete existing file
    '(Crystal Reports can't overwrite an existing file)
    If Dir$(FileSpec$) > "" Then
        FileKilled% = 0
        Do
            On Error Resume Next
            Kill FileSpec
            If Err > 0 Then
                Resp% = MsgBox("An error occured writing to file " + FileSpec$ + ":" + Chr$(10) + Error$ + Chr$(10) + Chr$(10) + "Do you want to try again?", MB_RETRYCANCEL Or MB_ICONQUESTION, Caption)
                If Resp% = IDCANCEL Then Exit Sub
            Else: FileKilled% = True
            End If
        Loop Until FileKilled%
    End If

    'Set the print file name and type
    CrystalReport.PrintFileName = CommonDialog.filename
    CrystalReport.PrintFileType = 2 'ASCII text

    'Export report code
    PrintDoc CRW_PRINT_TO_FILE
End Sub

Private Sub cmdInitialize_Click()
    If lblFileSpec > "" Then
        'Flag that we are in data reading mode
        bReadData% = True
        ReadMachineData -1
        bReadData% = False
    Else
        'Flag that we are in gathering data mode
        bGetData% = True
        GetMachineGroups
        bGetData% = False
    End If
End Sub

Private Sub cmdPrint_Click()
    'See if we need the print dialog
    If gbNeedPrintDialog% = True Then
        'Put up the print dialog
        CommonDialog.Flags = PD_NOSELECTION Or PD_NOPAGENUMS Or PD_HIDEPRINTTOFILE
        CommonDialog.Copies = 1
        CommonDialog.CancelError = True
        On Error Resume Next
        CommonDialog.Action = DLG_PRINT
        If Err = CDERR_CANCEL Then Exit Sub
        If Err > 0 Then
            MsgBox "An unexpected error occured:" + Chr$(10) + Chr$(10) + Error$, MB_OK Or MB_ICONEXCLAMATION, Caption
            Exit Sub
        End If
        On Error GoTo 0

        'Handle user printer settings
        CrystalReport.CopiesToPrinter = CommonDialog.Copies
    Else
        'Supply printer default settings
        CrystalReport.CopiesToPrinter = 1
    End If

    'Print document
    PrintDoc CRW_PRINT_TO_PRINTER
End Sub

Private Sub cmdPrintPreview_Click()
    'Print Preview document
    PrintDoc CRW_PRINT_TO_WINDOW
End Sub

Private Sub cmdSave_Click()
    'See if we need to do a Save or a Save As
    If lblFileSpec > "" Then
        SaveDoc lblFileSpec
    Else: cmdSaveAs_Click
    End If
End Sub

Private Sub cmdSaveAs_Click()
    Dim NewFileSpec$

    NewFileSpec$ = GetSaveAsFileSpec$(lblFileSpec, "Access 1.0 (*.MDB) |*.MDB |All Files (*.*) |*.*")
    If NewFileSpec$ <= "" Then Exit Sub

    SaveDoc NewFileSpec$
End Sub

Private Sub Form_Load()
    Dim i%

    'Position outline control (will be resized in Form Resize)
    olnData.left = 0
    olnData.top = 0

    chkDirty = False

    'Get local copy of parameters
    MachineGroupID$ = InitMachineGroupID$
    NewNumber% = InitReportNewNumber%
    hConnect& = InitReporthConnect&
    lblFileSpec = InitReportFileSpec$
    ReportTempSpec$ = InitReportTempSpec$
    bFilterProperties% = InitMachineGroupIsFiltered%
    PropertyCount% = InitMachineGroupPropertyCount%
    ReDim PropertyList$(0 To PropertyCount%)
    For i% = 0 To PropertyCount% - 1
        PropertyList$(i%) = InitMachineGroupProperties$(i%)
    Next i%

    Set dbt = OpenDatabase(ReportTempSpec$, True)
    If lblFileSpec > "" Then Set db = OpenDatabase(lblFileSpec, True, True)
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    Dim Resp%, FileSpec$

    'Make sure we're not in the middle of something
    If bGetData% Or bReadData% Then
        cmdAbort_Click
        Cancel = True
        Exit Sub
    End If

    'Give the user a chance to save document
    If chkDirty Then
        Resp% = MsgBox("This report has been modified. Save data before closing?", MB_YESNOCANCEL Or MB_ICONQUESTION, Caption)
        If Resp% = IDYES Then
            cmdSave_Click
            If chkDirty Then Cancel = True
        ElseIf Resp% = IDCANCEL Then: Cancel = True
        End If
    End If
End Sub

Private Sub Form_Resize()
    Static OldWidth, OldHeight
    Dim r As Rect, HBorderPixels%, VBorderPixels%

    'See if we're minimized
    If WindowState = MINIMIZED Then Exit Sub

    'Get client size of form (already in pixels, I find)
    GetClientRect hwnd, r
    HBorderPixels% = Width / X_PIX_SIZE - r.right
    VBorderPixels% = Height / Y_PIX_SIZE - r.bottom

    If OldWidth <> Width And Width - X_PIX_SIZE * HBorderPixels% > olnData.left Then
        olnData.Width = Width - X_PIX_SIZE * HBorderPixels% - olnData.left

        OldWidth = Width
    End If

    If OldHeight <> Height And Height - Y_PIX_SIZE * VBorderPixels% > olnData.top Then
        olnData.Height = Height - Y_PIX_SIZE * VBorderPixels% - olnData.top

        OldHeight = Height
    End If
End Sub

Private Sub Form_Unload(Cancel As Integer)
    dbt.Close
    If Not db Is Nothing Then db.Close

    'Kill temp file
    On Error Resume Next
    Kill ReportTempSpec$
    On Error GoTo 0

    FreeReportWindow Me
End Sub

Private Function GetAttributeValue%(db As Database, sScalar As SCALAR, GroupID&, MachineID&, PropertyID&, PropertyIndex%)
    Dim Attrib As AttributeRec, AttributeID&
    Dim lRet&

    'Add item to database
    AttributeID& = AddAttribute&(db, sScalar.pszName, "")
    If AttributeID& > 0 Then
        'Insert entry into database
        Attrib.MachineGroupID& = GroupID&
        Attrib.MachineID& = MachineID&
        Attrib.PropertyID& = PropertyID&
        Attrib.PropertyNum% = PropertyIndex%
        Attrib.AttributeID& = AttributeID&
        Attrib.Value$ = MakeNiceForCrystalReports$(sScalar.pszValue)
        If InsertAttribute&(db, Attrib) = 1 Then
            GetAttributeValue% = IDOK
        Else: GetAttributeValue% = MsgBox("Database Error:" + Chr$(10) + Chr$(10) + Error$, MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        End If
    Else: GetAttributeValue% = MsgBox("Database Error:" + Chr$(10) + Chr$(10) + Error$, MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
    End If
End Function

Private Function GetItemID%(Attrib As AttributeRec, ByVal ListIndex%)
    Dim Indent%

    'Default return value 0 (nothing at list index)
    GetItemID% = 0
    If ListIndex% < 0 Or ListIndex% >= olnData.ListCount Then Exit Function

    Indent% = olnData.Indent(ListIndex%)
    GetItemID% = Indent% + 1

    If Indent% > 0 Then
        Attrib.MachineID = olnData.ItemData(ListIndex%)
        Do
            ListIndex% = ListIndex% - 1
        Loop Until olnData.Indent(ListIndex%) < Indent%
        Indent% = Indent% - 1
    End If

    Attrib.MachineGroupID = olnData.ItemData(ListIndex%)
End Function

Private Sub GetMachineGroups()
    Dim hContainer&, hSubFolder&, hFilter&
    Dim sScalar As SCALAR, sToken As TOKEN
    Dim lRet&, Resp%, PropertyIndex%

    lRet& = SmsOpenContainer&(C_MACHINEGROUP, hConnect&, hContainer&)
    If lRet& <> SMS_OK Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
        Exit Sub
    End If

    'See if we need to filter certain properties
    If bFilterProperties% And PropertyCount% > 0 Then
        'Create a machine property filter
        lRet& = SmsCreateFilter&(GROUP_FILTER, hConnect&, hFilter)
        If lRet& <> SMS_OK Then
            MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
            GoTo GetMachineGroupsCleanup
        End If

        'Add the desired properties into the filter as tokens
        sToken.szName = "GroupClass"
        sToken.dwOp = QOP_STR_EQ
        For PropertyIndex% = 0 To PropertyCount% - 1
            sToken.szValue = PropertyList$(PropertyIndex%)
            lRet& = SmsAddToken&(hFilter, OP_OR, sToken, AT_END)
            If lRet& <> SMS_OK Then
                MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
                GoTo GetMachineGroupsCleanup
            End If
        Next PropertyIndex%

        'Set the property filter into the machine group container
        lRet& = SmsSetFilter&(hContainer&, hFilter&)
        If lRet& <> SMS_OK Then
            MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
            GoTo GetMachineGroupsCleanup
        End If
    End If

    'Populate the machine group container
    lRet& = SmsPopulate&(hContainer&, POP_SYNC, ByVal 0&)
    If lRet& <> SMS_OK And lRet& <> SMS_EMPTY Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
        GoTo GetMachineGroupsCleanup
    End If

    'List the machine groups in this container
    Resp% = IDOK
    lRet& = SmsGetNextFolder&(hContainer&, F_ANY, hSubFolder&)
    Do While lRet& = SMS_OK
        'Get subfolders
        Resp% = GetMachines%(dbt, hSubFolder&, CInt(olnData.ListCount))

        'See if the user canceled
        If Not bGetData% Then Resp% = IDABORT

        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SmsGetNextFolder&(hContainer&, F_ANY, hSubFolder&)
                Resp% = IDOK
            Case IDABORT
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SMS_NO_MORE_DATA
            Case IDRETRY
                lRet& = SMS_OK
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

GetMachineGroupsCleanup:
    If hFilter Then lRet& = SmsCloseFilter&(hFilter&)
    lRet& = SmsCloseContainer&(hContainer&)
    chkDirty = CHECKED
End Sub

Private Function GetMachineProperties%(db As Database, ByVal hFolder&, ParentListIndex%, GroupID&)
    Dim hSubFolder&, TypeName$, FolderType&
    Dim sScalar As SCALAR, MachineID&, Machine$, MachineName$
    Dim ListIndex%, bGetDataOld%, PropertyIndex%
    Dim lRet&, Resp%, Index%, OldStatBarMsg$

    'Default return value IDOK (Entry added)
    GetMachineProperties% = IDOK

    'Location of next item to add to outline control
    ListIndex% = olnData.ListCount

    'Get this folder's type (expect Machine)
    lRet& = SmsGetFolderType&(hFolder&, FolderType&, TypeName$)
    If lRet& <> SMS_OK Then
        GetMachineProperties% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        Exit Function
    End If

    Select Case TypeName$
        Case "Machine"
            lRet& = SmsGetFolderID&(hFolder&, Machine$)
            If lRet& <> SMS_OK Then
                GetMachineProperties% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If

            'Machine Name not available. Use Machine ID string
            MachineName$ = Machine$

        Case Else
            Exit Function
    End Select

    DoEvents

    'Check to see if the user wants to cancel
    If bGetData% Then
        'Add item to database
        MachineID& = AddMachine&(db, Machine$, MachineName$)
        If MachineID& > 0 Then
            'Add item to outline control
            olnData.AddItem MachineName$, ListIndex%
            olnData.Indent(ListIndex%) = 1
            olnData.PictureType(ListIndex%) = MSOUTLINE_PICTURE_MACHINE
            olnData.ItemData(ListIndex%) = MachineID&
            If olnData.IsItemVisible(ParentListIndex%) And Not olnData.Expand(ParentListIndex%) Then
                olnData.Expand(ParentListIndex%) = True
            End If
            If olnData.ListIndex = ListIndex% - 1 Then
                olnData.ListIndex = ListIndex%
            End If
            olnData.Refresh
            Resp% = IDOK
        Else: Resp% = MsgBox("Database Error:" + Chr$(10) + Chr$(10) + Error$, MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        End If
    Else: Resp% = IDABORT
    End If

    'Preserve current status bar message
    OldStatBarMsg$ = GetStatBarMsg$()

    'Make sure everything is OK and get first subfolder
    If Resp% = IDOK Then
        lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
    Else: lRet& = SMS_NO_MORE_DATA
    End If

    'List the properties in this machine folder
    PropertyIndex% = 0
    Do While lRet& = SMS_OK
        'Get subfolders
        Resp% = GetPropertyAttributes%(db, hSubFolder&, ListIndex%, GroupID&, MachineID&, PropertyIndex%)

        'See if user canceled
        If Not bGetData% Then Resp% = IDABORT

        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
                PropertyIndex% = PropertyIndex% + 1
                Resp% = IDOK
            Case IDABORT
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SMS_NO_MORE_DATA
            Case IDRETRY
                lRet& = SMS_OK
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

    'Restore status bar message and menu caption
    SetStatBarMsg OldStatBarMsg$

    'Set the return value (either IDABORT or IDOK)
    GetMachineProperties% = Resp%
End Function

Private Function GetMachines%(db As Database, ByVal hFolder&, ParentListIndex%)
    Dim hSubFolder&, TypeName$, FolderType&
    Dim sScalar As SCALAR, GroupID&, Group$, GroupName$
    Dim ListIndex%, bGetDataOld%
    Dim lRet&, Resp%, Index%

    'Default return value IDOK (Entry added)
    GetMachines% = IDOK

    'Location of next item to add to outline control
    ListIndex% = olnData.ListCount

    'Get this folder's type (expect Machine Group)
    lRet& = SmsGetFolderType&(hFolder&, FolderType&, TypeName$)
    If lRet& <> SMS_OK Then
        GetMachines% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        Exit Function
    End If

    Select Case TypeName$
        Case "Machine Group"
            lRet& = SmsGetFolderID&(hFolder&, Group$)
            If lRet& <> SMS_OK Then
                GetMachines% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If

            lRet& = SmsGetScalarByName&(hFolder&, "Name", sScalar)
            If lRet& <> SMS_OK Then
                GetMachines% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If
            GroupName$ = sScalar.pszValue

            'See if this is the choosen group
            If Group$ <> MachineGroupID$ Then Exit Function

        Case Else
            Exit Function
    End Select

    DoEvents

    'Check to see if the user wants to cancel
    If bGetData% Then
        'Add item to database
        GroupID& = AddMachineGroup&(db, Group$, GroupName$)
        If GroupID& > 0 Then
            'Add item to outline control
            olnData.AddItem GroupName$, ListIndex%
            olnData.Indent(ListIndex%) = 0
            olnData.PictureType(ListIndex%) = MSOUTLINE_PICTURE_GROUP
            olnData.ItemData(ListIndex%) = GroupID&
            If olnData.IsItemVisible(ParentListIndex%) And Not olnData.Expand(ParentListIndex%) Then
                olnData.Expand(ParentListIndex%) = True
            End If
            If olnData.ListIndex = ListIndex% - 1 Then
                olnData.ListIndex = ListIndex%
            End If
            olnData.Refresh
            Resp% = IDOK
        Else: Resp% = MsgBox("Database Error:" + Chr$(10) + Chr$(10) + Error$, MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        End If
    Else: Resp% = IDABORT
    End If

    'Make sure everything is OK and get first subfolder
    If Resp% = IDOK Then
        lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
    Else: lRet& = SMS_NO_MORE_DATA
    End If

    'List the machines in this machine group folder
    Do While lRet& = SMS_OK
        'Get subfolders
        Resp% = GetMachineProperties%(db, hSubFolder&, ListIndex%, GroupID&)

        'See if user canceled
        If Not bGetData% Then Resp% = IDABORT

        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
                Resp% = IDOK
            Case IDABORT
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SMS_NO_MORE_DATA
            Case IDRETRY
                lRet& = SMS_OK
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

    'If we're done with the item, collapse it
    If Resp% = IDOK Then
        If (olnData.ListIndex <= ListIndex% Or olnData.ListIndex = olnData.ListCount - 1) And olnData.HasSubItems(ListIndex%) Then
            'Preserve flag and allow Collapse to remove nodes
            bGetDataOld% = bGetData%
            bGetData% = False

            'Collapse the site now that its done
            olnData_Collapse ListIndex%

            'Restore flag
            bGetData% = bGetDataOld%
        End If
    End If

    'Set the return value (either IDABORT or IDOK)
    GetMachines% = Resp%
End Function

Private Function GetPropertyAttributes%(db As Database, ByVal hFolder&, ParentListIndex%, GroupID&, MachineID&, PropertyIndex%)
    Dim TypeName$, FolderType&
    Dim sScalar As SCALAR, PropertyID&, Property$, PropertyName$
    Dim ListIndex%, bGetDataOld%
    Dim Pos1%, Pos2%
    Dim lRet&, Resp%, Index%

    'Default return value IDOK (Entry added)
    GetPropertyAttributes% = IDOK

    'Location of next item to add to outline control
    ListIndex% = olnData.ListCount

    'Get this folder's type (expect Group)
    lRet& = SmsGetFolderType&(hFolder&, FolderType&, TypeName$)
    If lRet& <> SMS_OK Then
        GetPropertyAttributes% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        Exit Function
    End If

    Select Case TypeName$
        Case "Group"
            lRet& = SmsGetFolderID&(hFolder&, Property$)
            If lRet& <> SMS_OK Then
                GetPropertyAttributes% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If

            'Property Name not available. Use part of Property ID string.
            Pos1% = InStr(Property$, "|")
            Pos2% = InStr(Pos1% + 1, Property$, "|")
            If Pos2% <= 0 Then Pos2% = Len(Property$)
            PropertyName$ = LCase$(Mid$(Property$, Pos1% + 1, Pos2% - Pos1% - 1))

        Case Else
            Exit Function
    End Select

    DoEvents

    'Check to see if the user wants to cancel
    If bGetData% Then
        'Add item to database
        PropertyID& = AddProperty&(db, Property$, PropertyName$)
        If PropertyID& > 0 Then
            SetStatBarMsg olnData.List(ParentListIndex%) + " " + PropertyName$
            Resp% = IDOK
        Else: Resp% = MsgBox("Database Error:" + Chr$(10) + Chr$(10) + Error$, MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        End If
    Else: Resp% = IDABORT
    End If

    'Make sure everything is OK and get first subfolder
    If Resp% = IDOK Then
        lRet& = SmsGetNextScalar&(hFolder&, sScalar)
    Else: lRet& = SMS_NO_MORE_DATA
    End If

    'List the attributes in this property folder
    Do While lRet& = SMS_OK
        'Get Scalar
        Resp% = GetAttributeValue%(db, sScalar, GroupID&, MachineID&, PropertyID&, PropertyIndex%)

        'See if user canceled
        If Not bGetData% Then Resp% = IDABORT

        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsGetNextScalar&(hFolder&, sScalar)
                Resp% = IDOK
            Case IDABORT
                lRet& = SMS_NO_MORE_DATA
            Case IDRETRY
                lRet& = SMS_OK
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

    'Set the return value (either IDABORT or IDOK)
    GetPropertyAttributes% = Resp%
End Function

Private Function GetSaveAsFileSpec$(ByVal InitFileSpec$, Filter$)
    'Default return value empty (User canceled)

GetSaveAsFileSpec$ = ""

    CommonDialog.CancelError = True
    CommonDialog.filename = InitFileSpec$
    CommonDialog.Filter = Filter$
    CommonDialog.FilterIndex = 1
    CommonDialog.Flags = OFN_OVERWRITEPROMPT Or OFN_PATHMUSTEXIST Or OFN_HIDEREADONLY
    On Error Resume Next
    CommonDialog.Action = DLG_FILE_SAVE
    If Err = CDERR_CANCEL Then Exit Function
    If Err > 0 Then
        MsgBox "An unexpected error occured:" + Chr$(10) + Error$, MB_OK Or MB_ICONEXCLAMATION, Caption
        Exit Function
    End If
    On Error GoTo 0

    GetSaveAsFileSpec$ = CommonDialog.filename
End Function

Private Sub lblFileSpec_Change()
    UpdateCaption
End Sub

Private Sub olnData_Click()
    '
End Sub

Private Sub olnData_Collapse(ListIndex As Integer)
    If Not bGetData% And Not bReadData% Then
        Do While olnData.HasSubItems(ListIndex%)
            olnData_Collapse ListIndex% + 1
            olnData.RemoveItem ListIndex% + 1
        Loop
    End If
End Sub

Private Sub olnData_DblClick()
    If olnData.HasSubItems(olnData.ListIndex) Then
        olnData_Collapse CInt(olnData.ListIndex)
    Else: olnData_Expand CInt(olnData.ListIndex)
    End If
End Sub

Private Sub olnData_Expand(ListIndex As Integer)
    Dim Attrib As AttributeRec
    Static bExpanded%

    'Prevent infinite recursion
    If bExpanded% Then Exit Sub

    If Not bReadData% Then
        If Not olnData.HasSubItems(ListIndex) Then
            bReadData% = True
            ReadMachineData ListIndex
            bReadData% = False
        End If

        'Expand the node to show children
        If olnData.HasSubItems(ListIndex) Then
            bExpanded% = True
            olnData.Expand(ListIndex) = True
            bExpanded% = False
        End If
    End If
End Sub

Private Sub olnData_PictureClick(ListIndex As Integer)
    olnData.ListIndex = ListIndex
    olnData.Refresh
    olnData_Click
End Sub

Private Sub olnData_PictureDblClick(ListIndex As Integer)
    olnData_PictureClick ListIndex
    olnData_DblClick
End Sub

Private Sub PrintDoc(Destination%)
    CrystalReport.WindowTitle = Caption
    CrystalReport.Destination = Destination%
    CrystalReport.WindowParentHandle = 0
    CrystalReport.DataFiles(0) = ReportTempSpec$
    CrystalReport.ReportFileName = App.Path + "\MACHGRP.RPT"

    'Print the report
    CrystalReport.Action = 1
End Sub

Private Sub ReadMachineData(ParentListIndex%)
    Dim ListIndex%, TooMany%, DBError%
    Dim Attrib As AttributeRec, IDLookup As IDLookupRec
    Dim Windex%, OldStatBarMsg$, ErrStr$

    TooMany = False
    Select Case GetItemID%(Attrib, ParentListIndex%)
        Case 0 'Empty list: Get all machine groups
            If GetMachineGroup%(dbt, IDLookup) = False Then
                MsgBox "Error accessing database"
                Exit Sub
            End If

            ListIndex% = ParentListIndex% + 1
            If DBError% Then
                MsgBox "Error accessing database"
                Exit Sub
            ElseIf olnData.ListCount >= MAX_OUTLINE_ENTRIES Then
                TooMany% = True
            Else
                olnData.AddItem IDLookup.StrName$, ListIndex%
                olnData.Indent(ListIndex%) = 0
                olnData.PictureType(ListIndex%) = MSOUTLINE_PICTURE_GROUP
                olnData.ItemData(ListIndex%) = IDLookup.ID&
                ListIndex% = ListIndex% + 1

                'DoEvents 'Turned off because its too slow!
                            'Turn back on to check for user cancel
            End If
        Case 1 'Machine Group: Get machines in group
            If QueryMachines%(dbt, Attrib) = False Then
                MsgBox "Error accessing database"
                Exit Sub
            End If

            ListIndex% = ParentListIndex% + 1
            Do While FetchMachines%(IDLookup, Not bReadData% Or TooMany%, DBError%)
                If DBError% Then
                    MsgBox "Error accessing database"
                    Exit Sub
                ElseIf olnData.ListCount >= MAX_OUTLINE_ENTRIES Then
                    TooMany% = True
                Else
                    olnData.AddItem IDLookup.StrName$, ListIndex%
                    olnData.Indent(ListIndex%) = 1
                    olnData.PictureType(ListIndex%) = MSOUTLINE_PICTURE_MACHINE
                    olnData.ItemData(ListIndex%) = IDLookup.ID&
                    ListIndex% = ListIndex% + 1

                    'DoEvents 'Turned off because its too slow!
                              'Turn back on to check for user cancel
                End If
            Loop
        Case 2 'Machine: Get properties of machine
            'Indicate in status bar what's going on
            OldStatBarMsg$ = GetStatBarMsg$()
            SetStatBarMsg "Opening Machine Properties..."

            'Create new document window
            Windex% = NewMachineReportWindow%(hConnect&, "", dbt, Attrib, ErrStr$)

            'If window created OK then initialize it
            If Windex% <= 0 Then
                MsgBox "Error opening machine properties " + CommonDialog.filename + ":" + Chr$(10) + Chr$(10) + ErrStr$, MB_OK Or MB_ICONEXCLAMATION, Screen.ActiveForm.Caption
            Else: ReportWindow(Windex%).cmdInitialize = True
            End If

            'Restore status bar message and menu caption
            SetStatBarMsg OldStatBarMsg$
            Windex% = 0
    End Select

    If TooMany% Then
        MsgBox "No more machines and properties can be displayed in this window until some are collapsed."
    End If
End Sub

Private Sub SaveDoc(ByVal FileSpec$)
    Dim ReportSaved%, Resp%

    'Temporarily close the database and temp database
    dbt.Close
    If Not db Is Nothing Then db.Close

    ReportSaved% = False
    Do
        On Error Resume Next
        FileCopy ReportTempSpec$, FileSpec$
        If Err > 0 Then
            Resp% = MsgBox("An error occured trying to save " + FileSpec$ + ":" + Chr$(10) + Error$ + Chr$(10) + Chr$(10) + "Do you want to try again?", MB_OKCANCEL Or MB_ICONQUESTION, Caption)
            If Resp% = IDCANCEL Then Exit Do
        Else: ReportSaved% = True
        End If
    Loop Until ReportSaved%

    'Re-open the database and temp database
    Set dbt = OpenDatabase(ReportTempSpec$, True)
    Set db = OpenDatabase(FileSpec$, True, True)

    If ReportSaved% Then
        lblFileSpec = FileSpec$
        chkDirty = False
    End If
End Sub

Private Sub UpdateCaption()
    Dim NewCaption$

    If lblFileSpec > "" Then
        NewCaption$ = lblFileSpec
    Else: NewCaption$ = "New Machine Group Report " + CStr(NewNumber)
    End If

    If chkDirty Then NewCaption$ = NewCaption$ + "*"

    Caption = NewCaption$
End Sub


MACHINE.BAS

Attribute VB_Name = "MACHINE"
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  MACHINE.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

'For use by QueryProperties%() and FetchProperties%() ONLY
Dim dyQueryProperties As Recordset

'For use by QueryAttributes%() and FetchAttributes%() ONLY
Dim dyQueryAttributes As Recordset

Function FetchAttributes%(IDLookup As IDLookupRec, Flush%, DBError%)
'   Fetches another Attribute & Value from the dyQueryAttributes dynaset
'Parameters:
'   IDLookup - Filled in with next attribute and value:
'       .ID      - Recieves the ID of the attribute
'       .StrID   - Recieves the name of the attribute
'       .StrName - Recieves the value of the attribute
'   Flush% - Usually False. If True, the fetch is ended.
'   DBError% - Returns True when DB error occures.
'Return Value:
'   True if another record fetched, else False (no more)

    DBError% = True
    FetchAttributes% = False

    If dyQueryAttributes.EOF Or Flush% Then
        dyQueryAttributes.Close
    Else
        ' Populate the site record from the dyQueryProperties
        ' dynaset and find the next record
        IDLookup.ID& = dyQueryAttributes("ID")
        If IsNull(dyQueryAttributes("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyQueryAttributes("StrID"))
        End If
        If IsNull(dyQueryAttributes("Value")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyQueryAttributes("Value"))
        End If

        On Error Resume Next
        dyQueryAttributes.MoveNext
        If Err > 0 Then Exit Function
        On Error GoTo 0

        FetchAttributes% = True
    End If

    DBError% = False
End Function

Function FetchProperties%(IDLookup As IDLookupRec, Flush%, DBError%)
'   Fetches another Property from the dyQueryProperties dynaset
'Parameters:
'   IDLookup - Filled in with next property name and ID.
'   Flush% - Usually False. If True, the fetch is ended.
'   DBError% - Returns True when DB error occures.
'Return Value:
'   True if another record fetched, else False (no more)

    DBError% = True
    FetchProperties% = False

    If dyQueryProperties.EOF Or Flush% Then
        dyQueryProperties.Close
    Else
        ' Populate the site record from the dyQueryProperties
        ' dynaset and find the next record
        IDLookup.ID& = dyQueryProperties("ID")
        If IsNull(dyQueryProperties("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyQueryProperties("StrID"))
        End If
        If IsNull(dyQueryProperties("StrName")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyQueryProperties("StrName"))
        End If

        On Error Resume Next
        dyQueryProperties.MoveNext
        If Err > 0 Then Exit Function
        On Error GoTo 0

        FetchProperties% = True
    End If

    DBError% = False
End Function

Function GetMachine%(db As Database, IDLookup As IDLookupRec)
    Dim Sql$
    Dim dyIDLookup As Recordset

    'Set default return value False (Database error)
    GetMachine% = False

    Sql$ = "SELECT ID, StrID, StrName FROM [MachineID]"

    ' If an error occurs exit the function
    On Error Resume Next
    Set dyIDLookup = db.OpenRecordset(Sql$, dbOpenDynaset)
    If Err > 0 Then Exit Function
    On Error GoTo 0

    ' Populate the site record
    If Not dyIDLookup.EOF Then
        IDLookup.ID& = dyIDLookup("ID")
        If IsNull(dyIDLookup("StrID")) Then
            IDLookup.StrID$ = ""
        Else: IDLookup.StrID$ = Trim$(dyIDLookup("StrID"))
        End If
        If IsNull(dyIDLookup("StrName")) Then
            IDLookup.StrName$ = ""
        Else: IDLookup.StrName$ = Trim$(dyIDLookup("StrName"))
        End If
    End If

    ' Return the record count
    GetMachine% = True
End Function

Function NewMachineDatabase%(db As Database, dbSrce As Database, Attrib As AttributeRec, ErrStr$)
    Dim ix As Index

    'Default return value False (Failed)
    NewMachineDatabase% = False

    On Error GoTo NewMachineDatabaseErr

    dbSrce.Execute "SELECT PropertyID, PropertyNum, AttributeID, Value INTO Attributes IN '" + db.Name + "' FROM Attributes WHERE MachineID = " + CStr(Attrib.MachineID)
    dbSrce.Execute "SELECT DISTINCT ID, StrID, StrName INTO MachineID IN '" + db.Name + "' FROM MachineIDs, Attributes WHERE MachineID = MachineIDs.ID AND MachineID = " + CStr(Attrib.MachineID)
    dbSrce.Execute "SELECT DISTINCT ID, StrID, StrName INTO AttributeIDs IN '" + db.Name + "' FROM AttributeIDs, Attributes WHERE AttributeID = AttributeIDs.ID AND MachineID = " + CStr(Attrib.MachineID)
    dbSrce.Execute "SELECT DISTINCT ID, StrID, StrName INTO PropertyIDs IN '" + db.Name + "' FROM PropertyIDs, Attributes WHERE PropertyID = PropertyIDs.ID AND MachineID = " + CStr(Attrib.MachineID)

    'Set key on Attributes
    Set ix = New Index
    ix.Name = "SortKey"
    ix.Primary = True
    ix.Unique = True
    ix.Fields = "PropertyID;PropertyNum;AttributeID"
    db.TableDefs("Attributes").Indexes.Append ix

    'Set key on MachineID
    Set ix = New Index
    ix.Name = "Key"
    ix.Primary = True
    ix.Unique = True
    ix.Fields = "ID"
    db.TableDefs("MachineID").Indexes.Append ix

    'Set key on PropertyIDs
    Set ix = New Index
    ix.Name = "Key"
    ix.Primary = True
    ix.Unique = True
    ix.Fields = "ID"
    db.TableDefs("PropertyIDs").Indexes.Append ix

    'Set key on AttributeIDs
    Set ix = New Index
    ix.Name = "Key"
    ix.Primary = True
    ix.Unique = True
    ix.Fields = "ID"
    db.TableDefs("AttributeIDs").Indexes.Append ix

    'Return success
    NewMachineDatabase% = True
    Exit Function

NewMachineDatabaseErr:
    ErrStr$ = Error$
    Exit Function
End Function

Function NewMachineReportWindow%(hConnect&, ByVal FileSpec$, dbSrce As Database, Attrib As AttributeRec, ErrStr$)
    Dim i%, Ret&, TempPath$, TempSpec$, frm As Form
    Dim db As Database, td As TableDef, fd As Field, ix As Index

    'Default return value zero (Failed)
    NewMachineReportWindow% = 0

    'Allocate space for returned string
    TempSpec$ = String$(256, 0)

    'Get a temp file name
    TempPath$ = Environ$("TEMP")
    Ret& = GetTempFileName(TempPath$, "MDB", 0, TempSpec$)
    ErrStr$ = "Could not get a temporary file name for the new report."
    If (Ret& = 0) Then Exit Function

    'Clean up return string of null terminator
    i% = InStr(TempSpec$, Chr$(0))
    If i% = 0 Then i% = Len(TempSpec$) + 1
    TempSpec$ = left$(TempSpec$, i% - 1)

    'Create a temporary document file
    On Error GoTo NewMachineReportWindowErr
    If FileSpec$ > "" Then
        'File Open: so copy the file the user selected
        ErrStr$ = "Could not create a temporary copy of " + FileSpec$ + ".+"
        FileCopy FileSpec$, TempSpec$
    Else
        'File New: so create a new empty document
        ErrStr$ = "Could not create a temporary file for the new report."
        Kill TempSpec$
        Set db = CreateDatabase(TempSpec$, DB_LANG_GENERAL)

        'Populate it with data from source database
        i% = NewMachineDatabase%(db, dbSrce, Attrib, ErrStr$)
        db.Close
        If Not i% Then GoTo NewMachineReportWindowErr
    End If
    On Error GoTo 0

    'Pass the new document the file name to use
    InitReporthConnect& = hConnect&
    InitReportFileSpec$ = FileSpec$
    InitReportTempSpec$ = TempSpec$
    If InitReportFileSpec$ <= "" Then InitReportNewNumber% = InitReportNewNumber% + 1

    'Create a new document window
    On Error GoTo NewMachineReportWindowErr
    ErrStr$ = "Could not create site report document window."
    Set frm = New frmMachineReport
    Load frm
    On Error GoTo 0

    'Register document window with MDI
    i% = RegisterReportWindow%(frm, ErrStr$)
    If i% <= 0 Then GoTo NewMachineReportWindowErr

    'Show the document window
    frm.Show
    NewMachineReportWindow% = i%
    Exit Function

NewMachineReportWindowErr:
    On Error Resume Next
    If Not frm Is Nothing Then Unload frm
    Kill TempSpec$
    Exit Function
End Function

Function QueryAttributes%(db As Database, Attrib As AttributeRec)
'Queries the Attribute table and creates
'   a dynaset (dyQueryAttributes) used by FetchAttributes()
'Parameters
'   db - The database used for the query
'   Attrib - Not used. All the properties are fetched.
'Return Value:
'   True if no error, else False

    Dim Sql$, Where$

    'Default return value False (Database Error)
    QueryAttributes% = False

    ' Build the SQL statement
    Sql$ = "SELECT DISTINCT PropertyID, PropertyNum, AttributeID, ID, StrID, Value FROM AttributeIDs, Attributes "

    'Build the Where clause
    Where$ = " WHERE AttributeID = ID And PropertyID = " + CStr(Attrib.PropertyID&) + " ORDER BY PropertyID ASC, PropertyNum ASC, AttributeID ASC"

    'Append the where clause onto query string
    Sql$ = Sql$ + Where$

    'Perform the query
    On Error Resume Next
    Set dyQueryAttributes = db.OpenRecordset(Sql$, dbOpenDynaset)
    If Err > 0 Then Exit Function
    On Error GoTo 0

    'Every thing is ok
    QueryAttributes% = True
End Function

Function QueryProperties%(db As Database, Attrib As AttributeRec)
'Queries the Attribute table for all properties and creates
'   a dynaset (dyQueryProperties) used by FetchProperties()
'Parameters
'   db - The database used for the query
'   Attrib - Not used. All the properties are fetched.
'Return Value:
'   True if no error, else False

    Dim Sql$, Where$

    'Default return value False (Database Error)
    QueryProperties% = False

    ' Build the SQL statement
    Sql$ = "SELECT DISTINCT ID, StrID, StrName FROM PropertyIDs, Attributes"

    'Build the Where clause
    Where$ = " WHERE PropertyID = ID"

    'Append the where clause onto query string
    Sql$ = Sql$ + Where$

    'Perform the query
    On Error Resume Next
    Set dyQueryProperties = db.OpenRecordset(Sql$, dbOpenDynaset)
    If Err > 0 Then Exit Function
    On Error GoTo 0

    'Every thing is ok
    QueryProperties% = True
End Function


MACHINE.FRM

VERSION 4.00
Begin VB.Form frmMachineReport
   Appearance      =   0  'Flat
   BackColor       =   &H80000005&
   Caption         =   "Machine Report"
   ClientHeight    =   4215
   ClientLeft      =   1560
   ClientTop       =   2610
   ClientWidth     =   8505
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   4620
   Icon            =   "MACHINE.frx":0000
   Left            =   1500
   LinkTopic       =   "Form1"
   MDIChild        =   -1  'True
   ScaleHeight     =   4215
   ScaleWidth      =   8505
   Top             =   2265
   Width           =   8625
   Begin VB.PictureBox picSeperator
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      ForeColor       =   &H80000008&
      Height          =   2475
      Left            =   4200
      MousePointer    =   9  'Size W E
      ScaleHeight     =   2445
      ScaleWidth      =   45
      TabIndex        =   12
      Top             =   -15
      Width           =   75
   End
   Begin VB.PictureBox picRightView
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      BorderStyle     =   0  'None
      ForeColor       =   &H80000008&
      Height          =   2535
      Left            =   4275
      ScaleHeight     =   2535
      ScaleWidth      =   4200
      TabIndex        =   9
      Top             =   0
      Width           =   4200
      Begin Threed.SSPanel pnlTableHeader
         Height          =   270
         Left            =   0
         TabIndex        =   11
         Top             =   0
         Width           =   1500
         _Version        =   65536
         _ExtentX        =   2646
         _ExtentY        =   476
         _StockProps     =   15
         Caption         =   "Table Header"
         BackColor       =   -2147483633
         BevelWidth      =   2
         BorderWidth     =   0
      End
      Begin MSGrid.Grid grdTable
         Height          =   750
         Left            =   0
         TabIndex        =   10
         Top             =   240
         Width           =   1500
         _Version        =   65536
         _ExtentX        =   2646
         _ExtentY        =   1323
         _StockProps     =   77
         BorderStyle     =   0
         FixedCols       =   0
      End
   End
   Begin VB.PictureBox picLeftView
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      BorderStyle     =   0  'None
      ForeColor       =   &H80000008&
      Height          =   2535
      Left            =   0
      ScaleHeight     =   2535
      ScaleWidth      =   4215
      TabIndex        =   13
      Top             =   0
      Width           =   4215
      Begin Threed.SSPanel pnlPropertyHeader
         Height          =   270
         Left            =   0
         TabIndex        =   15
         Top             =   0
         Width           =   1500
         _Version        =   65536
         _ExtentX        =   2646
         _ExtentY        =   476
         _StockProps     =   15
         Caption         =   "Properties"
         BackColor       =   -2147483633
         BevelWidth      =   2
         BorderWidth     =   0
      End
      Begin MSOutl.Outline olnData
         Height          =   750
         Left            =   0
         TabIndex        =   14
         Top             =   240
         Width           =   1500
         _Version        =   65536
         _ExtentX        =   2646
         _ExtentY        =   1323
         _StockProps     =   77
         BorderStyle     =   0
         Style           =   5
      End
   End
   Begin VB.CommandButton cmdSave
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Save"
      Height          =   315
      Left            =   480
      TabIndex        =   4
      TabStop         =   0   'False
      Top             =   2580
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdSaveAs
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Save As"
      Height          =   315
      Left            =   480
      TabIndex        =   3
      TabStop         =   0   'False
      Top             =   2880
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CheckBox chkDirty
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Dirty Bit"
      ForeColor       =   &H80000008&
      Height          =   195
      Left            =   4440
      TabIndex        =   2
      TabStop         =   0   'False
      Top             =   3540
      Visible         =   0   'False
      Width           =   1155
   End
   Begin VB.CommandButton cmdPrintPreview
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Print Preview"
      Height          =   315
      Left            =   480
      TabIndex        =   1
      TabStop         =   0   'False
      Top             =   3480
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdPrint
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Print"
      Height          =   315
      Left            =   480
      TabIndex        =   0
      TabStop         =   0   'False
      Top             =   3180
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdAbort
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Abort"
      Height          =   315
      Left            =   4440
      TabIndex        =   7
      TabStop         =   0   'False
      Top             =   3180
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdInitialize
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Initialize"
      Height          =   315
      Left            =   4440
      TabIndex        =   8
      TabStop         =   0   'False
      Top             =   2880
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdExport
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Export"
      Height          =   315
      Left            =   4440
      TabIndex        =   6
      TabStop         =   0   'False
      Top             =   2580
      Visible         =   0   'False
      Width           =   3735
   End
   Begin Crystal.CrystalReport CrystalReport
      Left            =   0
      Top             =   3120
      _ExtentX        =   741
      _ExtentY        =   741
      _StockProps     =   0
      ReportFileName  =   ""
      Destination     =   0
      WindowLeft      =   100
      WindowTop       =   100
      WindowWidth     =   480
      WindowHeight    =   300
      WindowTitle     =   ""
      WindowBorderStyle=   2
      WindowControlBox=   -1  'True
      WindowMaxButton =   -1  'True
      WindowMinButton =   -1  'True
      CopiesToPrinter =   1
      PrintFileName   =   ""
      PrintFileType   =   2
      SelectionFormula=   ""
      GroupSelectionFormula=   ""
      Connect         =   ""
      UserName        =   ""
      ReportSource    =   0
      BoundReportHeading=   ""
      BoundReportFooter=   -1  'True
   End
   Begin MSComDlg.CommonDialog CommonDialog
      Left            =   0
      Top             =   2640
      _ExtentX        =   847
      _ExtentY        =   847
      _StockProps     =   0
   End
   Begin VB.Label lblFileSpec
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "File Specification"
      ForeColor       =   &H80000008&
      Height          =   195
      Left            =   5940
      TabIndex        =   5
      Top             =   3540
      Visible         =   0   'False
      Width           =   2175
   End
End
Attribute VB_Name = "frmMachineReport"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  MACHINE.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim hConnect&
Dim ReportTempSpec$
Dim NewNumber%
Dim db As Database
Dim dbt As Database

'Current machine name and ID
Dim Machine As IDLookupRec

'Currently selected item
Dim SelectedProperty%

'Some relavent names for the outline control pictures
Const MSOUTLINE_PICTURE_MACHINE = MSOUTLINE_PICTURE_CLOSED
Const MSOUTLINE_PICTURE_PROPERTY = MSOUTLINE_PICTURE_OPEN

'Seperator dragging state and cursor offset
Dim SeperatorDragging%
Dim SeperatorDragOffset%

'State flags
Dim bReadData% 'If True, reading property data from file.

Private Sub chkDirty_Click()
    UpdateCaption
End Sub

Private Sub cmdAbort_Click()
    'Handle user abort of current lengthy operation
    If bReadData% Then
        If MsgBox("Do you want to stop reading machine data?" + Chr$(10) + Chr$(10) + "Click Yes to abort, No to continue.", MB_YESNO + MB_ICONQUESTION, Caption) = IDYES Then
            bReadData% = False
        End If
    End If
End Sub

Private Sub cmdExport_Click()
    Dim FileSpec$, nPos%, Resp%, FileKilled%

    'Build suggested file spec
    If lblFileSpec > "" Then
        nPos% = InStr(lblFileSpec, ".") - 1
        If nPos% < 0 Then nPos% = Len(lblFileSpec)
        FileSpec$ = left$(lblFileSpec, nPos) + ".TXT"
    Else: FileSpec$ = "*.TXT"
    End If

    'Get from the user the print file name
    FileSpec$ = GetSaveAsFileSpec$(FileSpec$, "ASCII Text (*.TXT) |*.TXT |All Files (*.*) |*.*")
    If FileSpec$ <= "" Then Exit Sub

    'See if we need to delete existing file
    '(Crystal Reports can't overwrite an existing file)
    If Dir$(FileSpec$) > "" Then
        FileKilled% = 0
        Do
            On Error Resume Next
            Kill FileSpec
            If Err > 0 Then
                Resp% = MsgBox("An error occured writing to file " + FileSpec$ + ":" + Chr$(10) + Error$ + Chr$(10) + Chr$(10) + "Do you want to try again?", MB_RETRYCANCEL Or MB_ICONQUESTION, Caption)
                If Resp% = IDCANCEL Then Exit Sub
            Else: FileKilled% = True
            End If
        Loop Until FileKilled%
    End If

    'Set the print file name and type
    CrystalReport.PrintFileName = CommonDialog.filename
    CrystalReport.PrintFileType = 2 'ASCII text

    'Export report code
    PrintDoc CRW_PRINT_TO_FILE
End Sub

Private Sub cmdInitialize_Click()
    'Size the grid columns
    If picRightView.Width > grdTable.ColWidth(0) + X_PIX_SIZE Then
        grdTable.ColWidth(1) = picRightView.Width - grdTable.ColWidth(0) - X_PIX_SIZE
    End If

    If GetMachine%(dbt, Machine) Then
        'Add a root item to represent this machine
        olnData.AddItem Machine.StrName$, 0
        olnData.Indent(0) = 0
        olnData.PictureType(0) = MSOUTLINE_PICTURE_MACHINE
        olnData.ItemData(0) = Machine.ID&

        'Flag that we are in data reading mode
        bReadData% = True
        ReadMachineProperties 0
        bReadData% = False

        'Select the first property item
        If olnData.ListCount > 1 Then olnData.ListIndex = SelectedProperty%
        olnData_Click
    End If
End Sub

Private Sub cmdPrint_Click()
    'See if we need the print dialog
    If gbNeedPrintDialog% = True Then
        'Put up the print dialog
        CommonDialog.Flags = PD_NOSELECTION Or PD_NOPAGENUMS Or PD_HIDEPRINTTOFILE
        CommonDialog.Copies = 1
        CommonDialog.CancelError = True
        On Error Resume Next
        CommonDialog.Action = DLG_PRINT
        If Err = CDERR_CANCEL Then Exit Sub
        If Err > 0 Then
            MsgBox "An unexpected error occured:" + Chr$(10) + Chr$(10) + Error$, MB_OK Or MB_ICONEXCLAMATION, Caption
            Exit Sub
        End If
        On Error GoTo 0

        'Handle user printer settings
        CrystalReport.CopiesToPrinter = CommonDialog.Copies
    Else
        'Supply printer default settings
        CrystalReport.CopiesToPrinter = 1
    End If

    'Print document
    PrintDoc CRW_PRINT_TO_PRINTER
End Sub

Private Sub cmdPrintPreview_Click()
    'Print Preview document
    PrintDoc CRW_PRINT_TO_WINDOW
End Sub

Private Sub cmdSave_Click()
    'See if we need to do a Save or a Save As
    If lblFileSpec > "" Then
        SaveDoc lblFileSpec
    Else: cmdSaveAs_Click
    End If
End Sub

Private Sub cmdSaveAs_Click()
    Dim NewFileSpec$

    NewFileSpec$ = GetSaveAsFileSpec$(lblFileSpec, "Access 1.0 (*.MDB) |*.MDB |All Files (*.*) |*.*")
    If NewFileSpec$ <= "" Then Exit Sub

    SaveDoc NewFileSpec$
End Sub

Private Sub Form_Load()
    Dim i%, ImgTop%, TestTop%, NoMoreProperties%

    chkDirty = False

    NewNumber% = InitReportNewNumber%
    hConnect& = InitReporthConnect&
    lblFileSpec = InitReportFileSpec$
    ReportTempSpec$ = InitReportTempSpec$

    'Position Left View and controls (will be resized later)
    picLeftView.left = 0
    picLeftView.top = 0
        pnlPropertyHeader.left = 0
        pnlPropertyHeader.top = 0
        olnData.left = 0
        olnData.top = pnlPropertyHeader.Height
    picLeftView.Width = 3000

    'Position View Seperator (will be resized later)
    picSeperator.left = picLeftView.Width
    picSeperator.top = -Y_PIX_SIZE

    'Position Right View and controls (will be resized later)
    picRightView.left = picSeperator.left + picSeperator.Width
    picRightView.top = 0
        pnlTableHeader.left = 0
        pnlTableHeader.top = 0
        grdTable.left = 0
        grdTable.top = pnlTableHeader.Height
        grdTable.ColWidth(0) = 2500

    'Set column header captions
    grdTable.Col = 0
    grdTable.Row = 0
    grdTable.Text = "Attribute"
    grdTable.Col = 1
    grdTable.Row = 0
    grdTable.Text = "Value"

    Set dbt = OpenDatabase(ReportTempSpec$, True)
    If lblFileSpec > "" Then Set db = OpenDatabase(lblFileSpec, True, True)

    SelectedProperty% = 1
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    Dim Resp%, FileSpec$

    'Make sure we're not in the middle of something
    If bReadData% Then
        cmdAbort_Click
        Cancel = True
        Exit Sub
    End If

    'Give the user a chance to save document
    If chkDirty Then
        Resp% = MsgBox("This report has been modified. Save data before closing?", MB_YESNOCANCEL Or MB_ICONQUESTION, Caption)
        If Resp% = IDYES Then
            cmdSave_Click
            If chkDirty Then Cancel = True
        ElseIf Resp% = IDCANCEL Then: Cancel = True
        End If
    End If
End Sub

Private Sub Form_Resize()
    Static OldWidth, OldHeight, OldSepLeft%
    Dim r As Rect, HBorderPixels%, VBorderPixels%

    'See if we're minimized
    If WindowState = MINIMIZED Then Exit Sub

    'Get client size of form (already in pixels, I find)
    GetClientRect hwnd, r
    HBorderPixels% = Width / X_PIX_SIZE - r.right
    VBorderPixels% = Height / Y_PIX_SIZE - r.bottom

    If (OldWidth <> Width Or OldSepLeft <> picSeperator.left) And Width - X_PIX_SIZE * HBorderPixels% > picRightView.left Then
        picRightView.Width = Width - X_PIX_SIZE * HBorderPixels% - picRightView.left

        OldWidth = Width
    End If

    If OldHeight <> Height And Height - Y_PIX_SIZE * VBorderPixels% > picLeftView.top Then
        picLeftView.Height = Height - Y_PIX_SIZE * VBorderPixels% - picLeftView.top
        picRightView.Height = Height - Y_PIX_SIZE * VBorderPixels% - picRightView.top
        picSeperator.Height = Height - Y_PIX_SIZE * VBorderPixels% - picSeperator.top + Y_PIX_SIZE

        OldHeight = Height
        OldSepLeft = picSeperator.left
    End If
End Sub

Private Sub Form_Unload(Cancel As Integer)
    dbt.Close
    If Not db Is Nothing Then db.Close

    'Kill temp file
    On Error Resume Next
    Kill ReportTempSpec$
    On Error GoTo 0

    FreeReportWindow Me
End Sub

Private Function GetItemID%(Attrib As AttributeRec, ByVal ListIndex%)
    'Default return value 0 (nothing at list index)
    GetItemID% = 0
    If ListIndex% < 1 Or ListIndex% >= olnData.ListCount Then Exit Function

    Attrib.PropertyID = olnData.ItemData(ListIndex%)
    GetItemID% = 1
End Function

Private Function GetSaveAsFileSpec$(ByVal InitFileSpec$, Filter$)
    'Default return value empty (User canceled)
    GetSaveAsFileSpec$ = ""

    CommonDialog.CancelError = True
    CommonDialog.filename = InitFileSpec$
    CommonDialog.Filter = Filter$
    CommonDialog.FilterIndex = 1
    CommonDialog.Flags = OFN_OVERWRITEPROMPT Or OFN_PATHMUSTEXIST Or OFN_HIDEREADONLY
    On Error Resume Next
    CommonDialog.Action = DLG_FILE_SAVE
    If Err = CDERR_CANCEL Then Exit Function
    If Err > 0 Then
        MsgBox "An unexpected error occured:" + Chr$(10) + Error$, MB_OK Or MB_ICONEXCLAMATION, Caption
        Exit Function
    End If
    On Error GoTo 0

    GetSaveAsFileSpec$ = CommonDialog.filename
End Function

Private Sub lblFileSpec_Change()
    UpdateCaption
End Sub

Private Sub olnData_Click()
    SetSelectedProperty olnData.ListIndex
End Sub

Private Sub olnData_Expand(ListIndex As Integer)
    olnData.ListIndex = ListIndex
    olnData.Refresh
    olnData_Click
End Sub

Private Sub olnData_PictureClick(ListIndex As Integer)
    olnData.ListIndex = ListIndex
    olnData.Refresh
    olnData_Click
End Sub

Private Sub picLeftView_Resize()
    Static OldWidth, OldHeight

    If OldWidth <> picLeftView.Width Then
        pnlPropertyHeader.Width = picLeftView.Width
        olnData.Width = picLeftView.Width

        OldWidth = picLeftView.Width
    End If

    If OldHeight <> picLeftView.Height Then
        olnData.Height = picLeftView.Height - olnData.top

        OldHeight = picLeftView.Height
    End If
End Sub

Private Sub picRightView_Resize()
    Static OldWidth, OldHeight

    If OldWidth <> picRightView.Width Then
        pnlTableHeader.Width = picRightView.Width
        grdTable.Width = picRightView.Width

        OldWidth = picRightView.Width
    End If

    If OldHeight <> picRightView.Height And picRightView.Height > pnlTableHeader.Height Then
        grdTable.Height = picRightView.Height - grdTable.top

        OldWidth = picRightView.Width
    End If
End Sub

Private Sub picSeperator_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If Button And LEFT_BUTTON Then
        SeperatorDragging% = True
        SeperatorDragOffset% = X
    End If
End Sub

Private Sub picSeperator_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
    Dim OldLeft%

    If SeperatorDragging% And X <> OldLeft% And picSeperator.left + X - SeperatorDragOffset% > 0 Then
        picSeperator.left = picSeperator.left + X - SeperatorDragOffset%
        picLeftView.Width = picSeperator.left
        picRightView.left = picSeperator.left + picSeperator.Width
        Form_Resize
    End If
End Sub

Private Sub picSeperator_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
    If Button And LEFT_BUTTON Then
        SeperatorDragging% = False
    End If
End Sub

Private Sub PrintDoc(Destination%)
    CrystalReport.WindowTitle = Caption
    CrystalReport.Destination = Destination%
    CrystalReport.WindowParentHandle = 0
    CrystalReport.DataFiles(0) = ReportTempSpec$
    CrystalReport.ReportFileName = App.Path + "\MACHPROP.RPT"

    'Print the report
    CrystalReport.Action = 1
End Sub

Private Sub ReadMachineProperties(ParentListIndex%)
    Dim ListIndex%, TooMany%, DBError%, bFirstAttribute%, FirstAttributeID&
    Dim Attrib As AttributeRec, IDLookup As IDLookupRec

    Attrib.PropertyID = olnData.ItemData(ParentListIndex%)
    TooMany = False
    Select Case GetItemID%(Attrib, ParentListIndex%)
        Case 0 'Empty list: Get all machine properties
            If QueryProperties%(dbt, Attrib) = False Then
                MsgBox "Error accessing database"
                Exit Sub
            End If

            ListIndex% = ParentListIndex% + 1
            Do While FetchProperties%(IDLookup, Not bReadData% Or TooMany%, DBError%)
                If DBError% Then
                    MsgBox "Error accessing database"
                    Exit Sub
                ElseIf olnData.ListCount >= MAX_OUTLINE_ENTRIES Then
                    TooMany% = True
                Else
                    olnData.AddItem IDLookup.StrName$, ListIndex%
                    olnData.Indent(ListIndex%) = 1
                    olnData.PictureType(ListIndex%) = MSOUTLINE_PICTURE_PROPERTY
                    olnData.ItemData(ListIndex%) = IDLookup.ID&
                    ListIndex% = ListIndex% + 1

                    'DoEvents 'Turned off because its too slow!
                              'Turn back on to check for user cancel
                End If
            Loop
        Case 1 'Machine Property: Update table
            If QueryAttributes%(dbt, Attrib) = False Then
                MsgBox "Error accessing database"
                Exit Sub
            End If

            ListIndex% = 1
            bFirstAttribute% = True
            Do While FetchAttributes%(IDLookup, Not bReadData% Or TooMany%, DBError%)
                If DBError% Then
                    MsgBox "Error accessing database"
                    Exit Sub
                ElseIf olnData.ListCount >= MAX_OUTLINE_ENTRIES Then
                    TooMany% = True
                Else
                    If bFirstAttribute% Then
                        FirstAttributeID& = IDLookup.ID&
                        bFirstAttribute% = False
                    ElseIf IDLookup.ID& = FirstAttributeID& Then
                        'Add a blank entry to separate repeating section
                        grdTable.AddItem Chr$(9), ListIndex%
                        ListIndex% = ListIndex% + 1
                    End If

                    grdTable.AddItem IDLookup.StrID$ + Chr$(9) + IDLookup.StrName$, ListIndex%
                    ListIndex% = ListIndex% + 1

                    'DoEvents 'Turned off because its too slow!
                              'Turn back on to check for user cancel
                End If
            Loop
    End Select

    If TooMany% Then
        MsgBox "No more machines and properties can be displayed in this window until some are collapsed."
    End If
End Sub

Private Sub SaveDoc(ByVal FileSpec$)
    Dim ReportSaved%, Resp%

    'Temporarily close the database and temp database
    dbt.Close
    If Not db Is Nothing Then db.Close

    ReportSaved% = False
    Do
        On Error Resume Next
        FileCopy ReportTempSpec$, FileSpec$
        If Err > 0 Then
            Resp% = MsgBox("An error occured trying to save " + FileSpec$ + ":" + Chr$(10) + Error$ + Chr$(10) + Chr$(10) + "Do you want to try again?", MB_OKCANCEL Or MB_ICONQUESTION, Caption)
            If Resp% = IDCANCEL Then Exit Do
        Else: ReportSaved% = True
        End If
    Loop Until ReportSaved%

    'Re-open the database and temp database
    Set dbt = OpenDatabase(ReportTempSpec$, True)
    Set db = OpenDatabase(FileSpec$, True, True)

    If ReportSaved% Then
        lblFileSpec = FileSpec$
        chkDirty = False
    End If
End Sub

Private Sub SetSelectedProperty(ByVal Index%)
    Static Initialized%

    'Prevent re-entrancy
    If bReadData% Then Exit Sub

    'Don't re-select if its already selected
    If Initialized% And SelectedProperty% = Index% Or Index% < 1 Then Exit Sub
    Initialized% = True

    'Set the table header caption
    pnlTableHeader.Caption = olnData.List(Index%)

    'Clear out the table
    Do While grdTable.Rows > 2
        grdTable.RemoveItem 1
    Loop

    'Update the table
    bReadData% = True
    ReadMachineProperties Index%
    bReadData% = False

    'Preserve the current selection
    SelectedProperty% = Index%
End Sub

Private Sub UpdateCaption()
    Dim NewCaption$

    If lblFileSpec > "" Then
        NewCaption$ = lblFileSpec
    Else: NewCaption$ = "New Machine Report " + CStr(NewNumber)
    End If

    If chkDirty Then NewCaption$ = NewCaption$ + "*"

    Caption = NewCaption$
End Sub


MDIREPRT.BAS

Attribute VB_Name = "basMDIReprt"
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  MDIREPRT.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

'Types of reports that can be created
Global Const UNKNOWN_REPORT = 0
Global Const SITE_HIERARCHY_REPORT = 1
Global Const MACHINE_GROUP_REPORT = 2
Global Const MACHINE_REPORT = 3
Global Const GENERIC_REPORT = 4

'Size of one pixel in Twips (Set in MDIReport Form Load)
Global X_PIX_SIZE%
Global Y_PIX_SIZE%

'Got this value from outline control error message 32002
Global Const MAX_OUTLINE_ENTRIES = 6536

'Some constants for crystal reports
Global Const CRW_PRINT_TO_WINDOW = 0
Global Const CRW_PRINT_TO_PRINTER = 1
Global Const CRW_PRINT_TO_FILE = 2

'For MDIReport.bas and Report Forms ONLY!
Global Const MAX_REPORT_WINDOWS = 16
Global ReportWindow(1 To MAX_REPORT_WINDOWS) As Form
Global InitReporthConnect&
Global InitReportFileSpec$
Global InitReportTempSpec$
Global InitReportNewNumber%

'For all report forms
Global gbNeedPrintDialog%
Sub FreeReportWindow(frm As Form)
    Dim i%

    For i% = 1 To MAX_REPORT_WINDOWS
        If ReportWindow(i%) Is frm Then
            Set ReportWindow(i%) = Nothing
            Exit For
        End If
    Next i%
End Sub

Sub Main()
    'Initialize the application
    If Init_SMSAPI() Then
        'Put up the main MDI form
        frmMDIReport.Show
    Else
        MsgBox "Error initializing application.  SMS Report can not run.", MB_OK + MB_ICONINFORMATION
    End If
End Sub

Function RegisterReportWindow%(frm As Form, ErrStr$)
    Dim i%

    'Find a free entry in ReportWindow array
    For i% = 1 To MAX_REPORT_WINDOWS
        If ReportWindow(i%) Is Nothing Then
            Set ReportWindow(i%) = frm
            RegisterReportWindow% = i%
            Exit Function
        End If
    Next i%

    'Return value 0 (Failed)
    RegisterReportWindow% = 0
    ErrStr$ = "Too many windows already open."
End Function


MDIREPRT.FRM

VERSION 4.00
Begin VB.MDIForm frmMDIReport
   Appearance      =   0  'Flat
   BackColor       =   &H8000000C&
   Caption         =   "SMS Report"
   ClientHeight    =   5445
   ClientLeft      =   1350
   ClientTop       =   1935
   ClientWidth     =   8520
   Height          =   6135
   Icon            =   "MDIREPRT.frx":0000
   Left            =   1290
   LinkTopic       =   "Form1"
   Top             =   1305
   Width           =   8640
   Begin Threed.SSPanel pnlStatusBar
      Align           =   2  'Align Bottom
      Height          =   405
      Left            =   0
      TabIndex        =   0
      Top             =   5040
      Width           =   8520
      _Version        =   65536
      _ExtentX        =   15028
      _ExtentY        =   714
      _StockProps     =   15
      BackColor       =   -2147483633
      BevelInner      =   1
      Alignment       =   1
      Autosize        =   3
      Begin VB.Label lblStatusBar
         Appearance      =   0  'Flat
         BeginProperty Font
            name            =   "MS Sans Serif"
            charset         =   1
            weight          =   700
            size            =   8.25
            underline       =   0   'False
            italic          =   0   'False
            strikethrough   =   0   'False
         EndProperty
         ForeColor       =   &H80000008&
         Height          =   225
         Left            =   75
         TabIndex        =   1
         Top             =   75
         Width           =   8370
      End
   End
   Begin ComctlLib.ImageList imgToolBar
      Left            =   2040
      Top             =   -120
      _Version        =   65536
      _ExtentX        =   1005
      _ExtentY        =   1005
      _StockProps     =   1
      ImageWidth      =   16
      ImageHeight     =   16
      MaskColor       =   8421376
      NumImages       =   5
      i1              =   "MDIREPRT.frx":030A
      i2              =   "MDIREPRT.frx":04C1
      i3              =   "MDIREPRT.frx":0678
      i4              =   "MDIREPRT.frx":082F
      i5              =   "MDIREPRT.frx":09E6
   End
   Begin ComctlLib.Toolbar tlbToolbar
      Align           =   1  'Align Top
      Height          =   405
      Left            =   0
      TabIndex        =   2
      Top             =   0
      Width           =   8520
      _Version        =   65536
      _ExtentX        =   15028
      _ExtentY        =   714
      _StockProps     =   96
      BorderStyle     =   1
      ImageList       =   "imgToolBar"
      MouseIcon       =   "MDIREPRT.frx":0B9D
      ButtonWidth     =   609
      AllowCustomize  =   0   'False
      NumButtons      =   5
      i1              =   "MDIREPRT.frx":0BB9
      i2              =   "MDIREPRT.frx":0D60
      i3              =   "MDIREPRT.frx":0F07
      i4              =   "MDIREPRT.frx":10AE
      i5              =   "MDIREPRT.frx":1259
      AlignSet        =   -1  'True
   End
   Begin MSComDlg.CommonDialog CommonDialog
      Left            =   1320
      Top             =   2280
      _ExtentX        =   847
      _ExtentY        =   847
      _StockProps     =   0
   End
   Begin VB.Menu mnuFile
      Caption         =   "&File"
      Begin VB.Menu mnuFileNew
         Caption         =   "&New..."
         Shortcut        =   ^N
      End
      Begin VB.Menu mnuFileOpen
         Caption         =   "&Open..."
         Shortcut        =   ^O
      End
      Begin VB.Menu mnuFileSave
         Caption         =   "&Save..."
         Shortcut        =   ^S
      End
      Begin VB.Menu mnuFileSaveAs
         Caption         =   "Save &As..."
         Shortcut        =   {F12}
      End
      Begin VB.Menu mnuFileSeperator0
         Caption         =   "-"
      End
      Begin VB.Menu mnuFilePrintPreview
         Caption         =   "Print Pre&view"
      End
      Begin VB.Menu mnuFilePrint
         Caption         =   "&Print..."
         Shortcut        =   ^P
      End
      Begin VB.Menu mnuFileExport
         Caption         =   "&Export..."
      End
      Begin VB.Menu mnuFileSeperator1
         Caption         =   "-"
      End
      Begin VB.Menu mnuFileExit
         Caption         =   "E&xit"
      End
   End
   Begin VB.Menu mnuWindow
      Caption         =   "&Window"
      WindowList      =   -1  'True
      Begin VB.Menu mnuWindowCascade
         Caption         =   "&Cascade"
         Shortcut        =   {F5}
      End
      Begin VB.Menu mnuWindowArrangeIcons
         Caption         =   "&Arrange Icons"
      End
   End
   Begin VB.Menu mnuHelp
      Caption         =   "&Help"
      Begin VB.Menu mnuHelpAbout
         Caption         =   "&About SMS Report..."
      End
   End
End
Attribute VB_Name = "frmMDIReport"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  MDIREPRT.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim hConnect&
Dim FileNewMenuCaption$
Dim FileNewToolTip$
Private Function GetReportType%(ByVal FileSpec$)
    Dim db As Database, i%

    'Default return value UNKNOWN_REPORT (Failed)
    GetReportType% = UNKNOWN_REPORT

    'Open the database file
    On Error Resume Next
    Set db = OpenDatabase(FileSpec$, True, True)
    If Err > 0 Then Exit Function
    On Error GoTo 0

    For i% = 0 To db.TableDefs.Count - 1
        Select Case db.TableDefs(i%).Name
            Case "Site Hierarchy"
                GetReportType% = SITE_HIERARCHY_REPORT
            Case "Generic"
                GetReportType% = GENERIC_REPORT
            Case "MachineGroupID"
                GetReportType% = MACHINE_GROUP_REPORT
            Case "MachineID"
                GetReportType% = MACHINE_REPORT
            'Case "GenericTableName"
            '    GetReportType% = GENERIC_REPORT
        End Select
    Next i%

    db.Close
End Function

Private Sub MDIForm_Load()
    'Get the size of one pixel size in twips
    GetPixelSizeInTwips Me, X_PIX_SIZE, Y_PIX_SIZE

    'Initially, File New menu is a way to login to SMS
    FileNewMenuCaption$ = mnuFileNew.Caption
    FileNewToolTip$ = tlbToolbar.Buttons(1).ToolTipText
    mnuFileNew.Caption = "&Login to SMS..."
    tlbToolbar.Buttons(1).ToolTipText = "Login to SMS"

    'Login to SMS
    hConnect& = 0
    mnuFileNew_Click

    'Initialize the status bar
    SetCurrentStatusBar lblStatusBar
    SetStatBarMsg ""
End Sub

Private Sub MDIForm_Resize()
    lblStatusBar.Width = pnlStatusBar.Width - lblStatusBar.left * 2
End Sub

Private Sub MDIForm_Unload(Cancel As Integer)
    DBLogout hConnect&
End Sub

Private Sub mnuFileExit_Click()
    Unload Me
End Sub

Private Sub mnuFileExport_Click()
    Dim OldStatBarMsg$

    If Not ActiveForm Is Nothing Then
        'Turn on wait cursor and set status bar
        MousePointer = HOURGLASS
        OldStatBarMsg$ = GetStatBarMsg$()
        SetStatBarMsg "Exporting Report..."

        'Call Print Preview method of active document
        ActiveForm.cmdExport = True

        'Restore status bar message and mouse pointer
        SetStatBarMsg OldStatBarMsg$
        MousePointer = NORMAL
    End If
End Sub

Private Sub mnuFileNew_Click()
    Dim SqlConnectParams As SQL_CONNECT_PARAMS
    Dim ErrStr$, OldMenuCaption$, OldStatBarMsg$
    Dim ReportType%
    Static Windex%

    'Check for reentrancy
    If Windex% > 0 Then
        ReportWindow(Windex%).cmdAbort = True
        Exit Sub
    End If

    'Check for user needing to log in first
    If hConnect& = 0 Then
        'Indicate in status bar what's going on
        OldStatBarMsg$ = GetStatBarMsg$()
        SetStatBarMsg "Login to SMS..."

        'Let user try to log in to SMS
        If DBLogin%(SqlConnectParams, hConnect&) Then
            mnuFileNew.Caption = FileNewMenuCaption$
            tlbToolbar.Buttons(1).ToolTipText = FileNewToolTip$
        End If

        'Restore status bar message and quit
        SetStatBarMsg OldStatBarMsg$
        Exit Sub
    End If

    'Get from user what kind of report to create
    If Not NewReport%(ReportType%) Then Exit Sub

    'Provide a mechanism for user to cancel
    OldMenuCaption$ = mnuFileNew.Caption
    mnuFileNew.Caption = "&Abort New Report..."

    'Indicate in status bar what's going on
    OldStatBarMsg$ = GetStatBarMsg$()
    SetStatBarMsg "Creating Report..."

    'Create new document window of selected type
    Select Case ReportType%
        Case SITE_HIERARCHY_REPORT
            Windex% = NewSiteHierarchyReportWindow%(hConnect&, "", ErrStr$)
        Case MACHINE_GROUP_REPORT
            Windex% = NewMachineGrpReportWindow%(hConnect&, "", ErrStr$)
        Case GENERIC_REPORT
            Windex% = NewGenericReportWindow%(hConnect&, "", ErrStr$)
    End Select

    'If window created OK then initialize it
    If Windex% <= 0 Then
        'If zero, report error, else user canceled
        If Windex% = 0 Then
            MsgBox "Error creating a new report:" + Chr$(10) + Chr$(10) + ErrStr$, MB_OK Or MB_ICONEXCLAMATION, Screen.ActiveForm.Caption
        End If
    Else: ReportWindow(Windex%).cmdInitialize = True
    End If

    'Restore status bar message and menu caption
    SetStatBarMsg OldStatBarMsg$
    mnuFileNew.Caption = OldMenuCaption$
    Windex% = 0
End Sub

Private Sub mnuFileOpen_Click()
    Dim ErrStr$, OldMenuCaption$, OldStatBarMsg$
    Dim dbSrce As Database, Attrib As AttributeRec 'Dummy arguements
    Dim ReportType%
    Static Windex%

    'Check for reentrancy
    If Windex% > 0 Then
        ReportWindow(Windex%).cmdAbort = True
        Exit Sub
    End If

    CommonDialog.CancelError = True
    CommonDialog.filename = ""
    'Supply file types for all possible reports HERE
    CommonDialog.Filter = "Microsoft Access Database (*.mdb)|*.mdb|All Files (*.*)|*.*"
    CommonDialog.FilterIndex = 1
    CommonDialog.Flags = OFN_PATHMUSTEXIST Or OFN_FILEMUSTEXIST Or OFN_HIDEREADONLY
    On Error Resume Next
    CommonDialog.Action = DLG_FILE_OPEN
    If Err = CDERR_CANCEL Then Exit Sub
    If Err > 0 Then
        MsgBox "An unexpected error occured:" + Chr$(10) + Chr$(10) + Error$, MB_OK Or MB_ICONEXCLAMATION, Caption
        Exit Sub
    End If
    On Error GoTo 0

    ReportType% = GetReportType%(CommonDialog.filename)
    If ReportType% <= UNKNOWN_REPORT Then
        MsgBox "The file " + CommonDialog.filename + " is not an SMS report file.", MB_OK, Caption
        Exit Sub
    End If

    'Provide a mechanism for user to cancel
    OldMenuCaption$ = mnuFileNew.Caption
    mnuFileNew.Caption = "&Abort Open Report..."

    'Indicate in status bar what's going on
    OldStatBarMsg$ = GetStatBarMsg$()
    SetStatBarMsg "Opening Report..."

    'Create new document window of selected type
    Select Case ReportType%
        Case SITE_HIERARCHY_REPORT
            Windex% = NewSiteHierarchyReportWindow%(hConnect&, CommonDialog.filename, ErrStr$)
        Case MACHINE_GROUP_REPORT
            Windex% = NewMachineGrpReportWindow%(hConnect&, CommonDialog.filename, ErrStr$)
        Case MACHINE_REPORT
            Windex% = NewMachineReportWindow%(hConnect&, CommonDialog.filename, dbSrce, Attrib, ErrStr$)
        Case GENERIC_REPORT
            Windex% = NewGenericReportWindow%(hConnect&, CommonDialog.filename, ErrStr$)
    End Select

    'If window created OK then initialize it
    If Windex% <= 0 Then
        MsgBox "Error opening report " + CommonDialog.filename + ":" + Chr$(10) + Chr$(10) + ErrStr$, MB_OK Or MB_ICONEXCLAMATION, Screen.ActiveForm.Caption
    Else: ReportWindow(Windex%).cmdInitialize = True
    End If

    'Restore status bar message and menu caption
    SetStatBarMsg OldStatBarMsg$
    mnuFileNew.Caption = OldMenuCaption$
    Windex% = 0
End Sub

Private Sub mnuFilePrint_Click()
    Dim OldStatBarMsg$

    If Not ActiveForm Is Nothing Then
        'Turn on wait cursor and set status bar
        MousePointer = HOURGLASS
        OldStatBarMsg$ = GetStatBarMsg$()
        SetStatBarMsg "Printing Report..."

        'Set flag for needing print dialog
        gbNeedPrintDialog% = True

        'Call Print method of active document
        ActiveForm.cmdPrint = True

        'Restore status bar message and mouse pointer
        SetStatBarMsg OldStatBarMsg$
        MousePointer = NORMAL
    End If
End Sub

Private Sub mnuFilePrintPreview_Click()
    Dim OldStatBarMsg$

    If Not ActiveForm Is Nothing Then
        'Turn on wait cursor and set status bar
        MousePointer = HOURGLASS
        OldStatBarMsg$ = GetStatBarMsg$()
        SetStatBarMsg "Previewing Report..."

        'Call Print Preview method of active document
        ActiveForm.cmdPrintPreview = True

        'Restore status bar message and mouse pointer
        SetStatBarMsg OldStatBarMsg$
        MousePointer = NORMAL
    End If
End Sub

Private Sub mnuFileSave_Click()
    Dim OldStatBarMsg$

    If Not ActiveForm Is Nothing Then
        MousePointer = HOURGLASS
        OldStatBarMsg$ = GetStatBarMsg$()
        SetStatBarMsg "Saving Report..."

        ActiveForm.cmdSave = True

        SetStatBarMsg OldStatBarMsg$
        MousePointer = NORMAL
    End If
End Sub

Private Sub mnuFileSaveAs_Click()
    Dim OldStatBarMsg$

    If Not ActiveForm Is Nothing Then
        MousePointer = HOURGLASS
        OldStatBarMsg$ = GetStatBarMsg$()
        SetStatBarMsg "Saving Report..."

        ActiveForm.cmdSaveAs = True

        SetStatBarMsg OldStatBarMsg$
        MousePointer = NORMAL
    End If
End Sub

Private Sub mnuHelpAbout_Click()
    Dim Info$

    Info$ = "SMS Report" + Chr$(10) + Chr$(10) + "A Microsoft SMS SDK sample application"
    Info$ = Info$ + Chr$(10) + "written in Microsoft Visual Basic 3.0 Professional Edition that"
    Info$ = Info$ + Chr$(10) + "demonstrates the Visual Basic Wrapper DLL for SMS API."

    SmsAbout "About SMS Report", Info$
End Sub

Private Sub mnuWindowArrangeIcons_Click()
    Arrange ARRANGE_ICONS
End Sub

Private Sub mnuWindowCascade_Click()
    Arrange CASCADE
End Sub

Private Sub tlbToolbar_ButtonClick(ByVal Button As Button)
    Select Case Button.Index
    Case 1
        mnuFileNew_Click
    Case 2
        mnuFileOpen_Click
    Case 3
        mnuFileSave_Click
    Case 4
        'Indicate to document that no Pring dialog is needed
        'This flag is reset by mnuFilePrint_Click()
        gbNeedPrintDialog% = False

        mnuFilePrint_Click
    Case 5
        mnuFilePrintPreview_Click
    End Select
End Sub


NEWREPRT.BAS

Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  NEWREPRT.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

'Global database variables (For NEWREPRT.FRM use ONLY!)
Global NewReportUserCanceled%
Global NewReportSelection%

Function NewReport% (Selection%)
    frmNewReport.Show MODAL

    If Not NewReportUserCanceled% Then
        Selection% = NewReportSelection%
    End If

    NewReport% = Not NewReportUserCanceled%
End Function


NEWREPRT.FRM

VERSION 4.00
Begin VB.Form frmNewReport
   Appearance      =   0  'Flat
   BorderStyle     =   3  'Fixed Dialog
   Caption         =   "New Report"
   ClientHeight    =   3210
   ClientLeft      =   3045
   ClientTop       =   1710
   ClientWidth     =   5250
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   3615
   Left            =   2985
   LinkTopic       =   "Form1"
   ScaleHeight     =   3210
   ScaleWidth      =   5250
   Top             =   1365
   Width           =   5370
   Begin VB.CommandButton cmdCancel
      Appearance      =   0  'Flat
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   375
      Left            =   4020
      TabIndex        =   6
      Top             =   540
      Width           =   1095
   End
   Begin VB.CommandButton cmdOk
      Appearance      =   0  'Flat
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   375
      Left            =   4020
      TabIndex        =   5
      Top             =   120
      Width           =   1095
   End
   Begin Threed.SSPanel pnlTextBoxBackdrop
      Height          =   2955
      Left            =   120
      TabIndex        =   0
      Top             =   120
      Width           =   3735
      _Version        =   65536
      _ExtentX        =   6588
      _ExtentY        =   5212
      _StockProps     =   15
      Begin VB.Label lblReportType
         Appearance      =   0  'Flat
         Caption         =   "Type of Report:"
         ForeColor       =   &H80000008&
         Height          =   255
         Left            =   120
         TabIndex        =   4
         Top             =   180
         Width           =   1590
      End
      Begin Threed.SSOption optReportType
         Height          =   270
         Index           =   0
         Left            =   480
         TabIndex        =   3
         Top             =   480
         Width           =   3135
         _Version        =   65536
         _ExtentX        =   5530
         _ExtentY        =   476
         _StockProps     =   78
         Caption         =   "&Site Hierarchy"
      End
      Begin Threed.SSOption optReportType
         Height          =   270
         Index           =   1
         Left            =   480
         TabIndex        =   2
         TabStop         =   0   'False
         Top             =   840
         Width           =   3135
         _Version        =   65536
         _ExtentX        =   5530
         _ExtentY        =   476
         _StockProps     =   78
         Caption         =   "&Machine Properties"
      End
      Begin Threed.SSOption optReportType
         Height          =   270
         Index           =   2
         Left            =   480
         TabIndex        =   1
         TabStop         =   0   'False
         Top             =   1200
         Visible         =   0   'False
         Width           =   3135
         _Version        =   65536
         _ExtentX        =   5530
         _ExtentY        =   476
         _StockProps     =   78
         Caption         =   "&Generic"
      End
   End
End
Attribute VB_Name = "frmNewReport"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  NEWREPRT.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim OldMousePointer%
Dim CallingForm As Form

Private Sub cmdCancel_Click()
    Unload Me
End Sub

Private Sub cmdOk_Click()
    'Set to login success
    NewReportUserCanceled% = False
    Unload Me
End Sub

Private Sub Form_Load()
    Dim NewLeft%, NewTop%

    'Save the old mouse pointer
    Set CallingForm = Screen.ActiveForm
    OldMousePointer% = CallingForm.MousePointer

    'Turn on normal pointer
    MousePointer = NORMAL

    'Center form on calling form
    NewLeft% = CallingForm.left + (CallingForm.Width - Width) / 2
    If NewLeft% + Width > Screen.Width Then NewLeft% = Screen.Width - Width
    If NewLeft% < 0 Then NewLeft% = 0
    NewTop% = CallingForm.top + (CallingForm.Height - Height) / 2
    If NewTop% + Height > Screen.Height Then NewTop% = Screen.Height - Height
    If NewTop% < 0 Then NewTop% = 0
    left = NewLeft%
    top = NewTop%

    'Set default to be user canceled
    NewReportUserCanceled% = True

    'Set the first radio button true
    optReportType(0) = True
End Sub

Private Sub Form_Unload(Cancel As Integer)
    'Replace mouse pointer
    CallingForm.MousePointer = OldMousePointer%
End Sub

Private Sub optReportType_Click(Index As Integer, Value As Integer)
    Select Case Index
        Case 0
            NewReportSelection% = SITE_HIERARCHY_REPORT
        Case 1
            NewReportSelection% = MACHINE_GROUP_REPORT
        Case 2
            NewReportSelection% = GENERIC_REPORT
        Case Else
            NewReportSelection% = UNKNOWN_REPORT
    End Select
End Sub


PICKMACH.BAS

Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  PICKMACH.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

'Global variables (For PICKMACH.FRM use ONLY!)
Global PickMachineGroupsUserCanceled%
Global PickMachineGroupshConnect&
Global PickMachineGroupsDB As Database

'Global variables (For use by PICKMACH.FRM ONLY!)
Global MachineGroupPicked$

Function PickMachineGroups% (hConnect&, db As Database, MachineGroupID$)
    'Pass parameters to form module
    PickMachineGroupshConnect& = hConnect&
    Set PickMachineGroupsDB = db
    MachineGroupPicked$ = ""

    'Show dialog
    frmPickMachineGroups.Show Modal

    'Don't leave behind any references to data base object
    Set PickMachineGroupsDB = Nothing

    'Return user response to dialog
    If Not PickMachineGroupsUserCanceled% Then MachineGroupID$ = MachineGroupPicked$
    PickMachineGroups% = Not PickMachineGroupsUserCanceled%
End Function


PICKMACH.FRM

VERSION 4.00
Begin VB.Form frmPickMachineGroups
   Appearance      =   0  'Flat
   BorderStyle     =   3  'Fixed Dialog
   Caption         =   "Pick Machine Group for Report"
   ClientHeight    =   4125
   ClientLeft      =   1740
   ClientTop       =   4650
   ClientWidth     =   5115
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   4530
   Left            =   1680
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   4125
   ScaleWidth      =   5115
   Top             =   4305
   Width           =   5235
   Begin VB.CommandButton cmdOk
      Appearance      =   0  'Flat
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   375
      Left            =   3900
      TabIndex        =   5
      Top             =   120
      Width           =   1095
   End
   Begin VB.CommandButton cmdCancel
      Appearance      =   0  'Flat
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   375
      Left            =   3900
      TabIndex        =   4
      Top             =   540
      Width           =   1095
   End
   Begin Threed.SSPanel pnlOutlineBackdrop
      Height          =   3870
      Left            =   120
      TabIndex        =   0
      Top             =   120
      Width           =   3615
      _Version        =   65536
      _ExtentX        =   6376
      _ExtentY        =   6826
      _StockProps     =   15
      Begin Threed.SSPanel pnlSrceFrame
         Height          =   3390
         Left            =   240
         TabIndex        =   1
         Top             =   360
         Width           =   3255
         _Version        =   65536
         _ExtentX        =   5741
         _ExtentY        =   5980
         _StockProps     =   15
         BevelOuter      =   1
         Autosize        =   3
         Begin MSOutl.Outline olnData
            Height          =   3360
            Left            =   15
            TabIndex        =   2
            Top             =   15
            Width           =   3225
            _Version        =   65536
            _ExtentX        =   5689
            _ExtentY        =   5927
            _StockProps     =   77
            BackColor       =   -2147483643
            Style           =   1
         End
      End
      Begin VB.Label Label1
         Appearance      =   0  'Flat
         AutoSize        =   -1  'True
         Caption         =   "&Report on machines in:"
         ForeColor       =   &H80000008&
         Height          =   195
         Left            =   240
         TabIndex        =   3
         Top             =   120
         Width           =   1980
      End
   End
End
Attribute VB_Name = "frmPickMachineGroups"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  PICKMACH.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim OldMousePointer%
Dim CallingForm As Form

Dim hConnect&
Dim db As Database

'State flags
Dim bGetMachineGroupData%  'If True, getting machine group data from SMS.
Dim MachineGroupID$()

'Some relavent names for the outline control pictures
Const MSOUTLINE_PICTURE_GROUP = MSOUTLINE_PICTURE_CLOSED
Const MSOUTLINE_PICTURE_MACHINE = MSOUTLINE_PICTURE_OPEN

Private Sub cmdCancel_Click()
    Unload Me
End Sub

Private Sub cmdOk_Click()
    If olnData.ListIndex >= 0 Then
        'Return list of group selected
        MachineGroupPicked$ = MachineGroupID$(olnData.ItemData(olnData.ListIndex))

        'Set to login success
        PickMachineGroupsUserCanceled% = False
    End If

    Unload Me
End Sub

Private Sub Form_Activate()
    bGetMachineGroupData% = True
    GetMachineGroups
    If bGetMachineGroupData% Then
        bGetMachineGroupData% = False
    Else: Unload Me
    End If
End Sub

Private Sub Form_Load()
    Dim NewLeft%, NewTop%

    'Save the old mouse pointer
    Set CallingForm = Screen.ActiveForm
    OldMousePointer% = CallingForm.MousePointer

    'Turn on normal pointer
    MousePointer = NORMAL

    'Center form on calling form
    NewLeft% = CallingForm.left + (CallingForm.Width - Width) / 2
    If NewLeft% + Width > Screen.Width Then NewLeft% = Screen.Width - Width
    If NewLeft% < 0 Then NewLeft% = 0
    NewTop% = CallingForm.top + (CallingForm.Height - Height) / 2
    If NewTop% + Height > Screen.Height Then NewTop% = Screen.Height - Height
    If NewTop% < 0 Then NewTop% = 0
    left = NewLeft%
    top = NewTop%

    'Copy parameters locally
    hConnect& = PickMachineGroupshConnect&
    Set db = PickMachineGroupsDB

    'Set default to be user canceled
    PickMachineGroupsUserCanceled% = True
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    If bGetMachineGroupData% Then
        bGetMachineGroupData% = False
        Cancel = True
        Exit Sub
    End If
End Sub

Private Sub Form_Unload(Cancel As Integer)
    'Replace mouse pointer
    CallingForm.MousePointer = OldMousePointer%
End Sub

Private Sub GetMachineGroups()
    Dim hContainer&, hSubFolder&, FolderType&, TypeName$
    Dim sScalar As SCALAR, GroupID$, GroupName$
    Dim lRet&, Resp%, ListIndex%

    'Reset list index
    ListIndex% = 0
    ReDim MachineGroupID$(0 To 0)

    lRet& = SmsOpenContainer&(C_MACHINEGROUP, hConnect&, hContainer&)
    If lRet& <> SMS_OK Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
        Exit Sub
    End If

    lRet& = SmsPopulate&(hContainer&, POP_SYNC, ByVal 0&)
    If lRet& <> SMS_OK And lRet& <> SMS_EMPTY Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
        GoTo GetMachineGroupsCleanup
    End If

    'List the machine groups in this container
    Resp% = IDOK
    lRet& = SmsGetNextFolder&(hContainer&, F_ANY, hSubFolder&)
    Do While lRet& = SMS_OK
        'Get this folder's type (expect Machine Group)
        lRet& = SmsGetFolderType&(hSubFolder&, FolderType&, TypeName$)
        If lRet& <> SMS_OK Or TypeName <> "Machine Group" Then
            If lRet& <> SMS_OK Then Resp% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
            GoTo GetMachineGroupsCont
        End If

        'Get this folder's ID
        lRet& = SmsGetFolderID&(hSubFolder&, GroupID$)
        If lRet& <> SMS_OK Then
            Resp% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
            GoTo GetMachineGroupsCont
        End If

        'Get this group's name
        lRet& = SmsGetScalarByName&(hSubFolder&, "Name", sScalar)
        If lRet& <> SMS_OK Then
            Resp% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
            GoTo GetMachineGroupsCont
        End If
        GroupName$ = sScalar.pszValue

        DoEvents

        'Check to see if the user wants to cancel
        If bGetMachineGroupData% Then
            'Add item to outline control and array
            olnData.AddItem GroupName$, ListIndex%
            olnData.Indent(ListIndex%) = 1
            olnData.PictureType(ListIndex%) = MSOUTLINE_PICTURE_GROUP
            olnData.ItemData(ListIndex%) = ListIndex%

            'Add GroupID to the array
            If ListIndex% >= UBound(MachineGroupID$, 1) Then ReDim Preserve MachineGroupID$(0 To ListIndex% + 32)
            MachineGroupID$(ListIndex%) = GroupID$

            ListIndex% = ListIndex% + 1
            Resp% = IDOK
        Else: Resp% = IDABORT
        End If

GetMachineGroupsCont:
        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SmsGetNextFolder&(hContainer&, F_ANY, hSubFolder&)
                Resp% = IDOK
            Case IDABORT
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SMS_NO_MORE_DATA
            Case IDRETRY
                lRet& = SMS_OK
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

GetMachineGroupsCleanup:
    lRet& = SmsCloseContainer&(hContainer&)
End Sub

Private Sub olnData_DblClick()
    cmdOk_Click
End Sub


PICKPROP.BAS

Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  PICKPROP.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

'Global variables (For PICKPROP.FRM use ONLY!)
Global PickPropertiesUserCanceled%
Global PickPropertieshConnect&
Global PickPropertiesMachineGroupID$
Global PickPropertiesIsFiltered%

'Global variables (For use by PICKPROP.FRM and MACHGRP.FRM ONLY!)
Global PickPropertiesList$()
Global PickPropertiesCount%

Function PickProperties% (hConnect&, MachineGroupID$, IsFiltered%, Properties$(), PropertyCount%)
    'Pass parameters to form module
    PickPropertieshConnect& = hConnect&
    PickPropertiesMachineGroupID$ = MachineGroupID$
    ReDim PickPropertiesList$(0 To 0)
    PickPropertiesCount% = 0

    'Show dialog
    frmPickProperties.Show Modal

    'Copy user selections
    If Not PickPropertiesUserCanceled% Then
        ReDim Properties$(0 To PickPropertiesCount%)
        For PropertyCount% = 0 To PickPropertiesCount% - 1
            Properties$(PropertyCount%) = PickPropertiesList$(PropertyCount%)
        Next PropertyCount%

        IsFiltered% = PickPropertiesIsFiltered%
    End If

    'Return user response to dialog
    PickProperties% = Not PickPropertiesUserCanceled%
End Function


PICKPROP.FRM

VERSION 4.00
Begin VB.Form frmPickProperties
   Appearance      =   0  'Flat
   BorderStyle     =   3  'Fixed Dialog
   Caption         =   "Pick Machine Properties"
   ClientHeight    =   5085
   ClientLeft      =   1740
   ClientTop       =   4650
   ClientWidth     =   9930
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   5490
   Left            =   1680
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   5085
   ScaleWidth      =   9930
   Top             =   4305
   Width           =   10050
   Begin VB.CommandButton cmdOk
      Appearance      =   0  'Flat
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   375
      Left            =   8700
      TabIndex        =   13
      Top             =   120
      Width           =   1095
   End
   Begin VB.CommandButton cmdCancel
      Appearance      =   0  'Flat
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   375
      Left            =   8700
      TabIndex        =   12
      Top             =   540
      Width           =   1095
   End
   Begin Threed.SSPanel pnlOutlineBackdrop
      Height          =   4830
      Left            =   120
      TabIndex        =   0
      Top             =   120
      Width           =   8475
      _Version        =   65536
      _ExtentX        =   14949
      _ExtentY        =   8520
      _StockProps     =   15
      Begin Threed.SSFrame frameProperties
         Height          =   3990
         Left            =   480
         TabIndex        =   1
         Top             =   720
         Width           =   7815
         _Version        =   65536
         _ExtentX        =   13785
         _ExtentY        =   7038
         _StockProps     =   14
         ForeColor       =   0
         Enabled         =   0   'False
         Begin VB.CommandButton cmdRemove
            Appearance      =   0  'Flat
            Caption         =   "&Remove >"
            Height          =   375
            Left            =   3330
            TabIndex        =   7
            Top             =   900
            Width           =   1035
         End
         Begin VB.CommandButton cmdAdd
            Appearance      =   0  'Flat
            Caption         =   "< &Add"
            Height          =   375
            Left            =   3330
            TabIndex        =   6
            Top             =   480
            Width           =   1035
         End
         Begin Threed.SSPanel pnlDestFrame
            Height          =   3390
            Left            =   180
            TabIndex        =   2
            Top             =   480
            Width           =   3075
            _Version        =   65536
            _ExtentX        =   5424
            _ExtentY        =   5980
            _StockProps     =   15
            BevelOuter      =   1
            Autosize        =   3
            Begin MSOutl.Outline olnDest
               Height          =   3360
               Left            =   15
               TabIndex        =   3
               Top             =   15
               Width           =   3045
               _Version        =   65536
               _ExtentX        =   5371
               _ExtentY        =   5927
               _StockProps     =   77
               BackColor       =   -2147483643
               PathSeparator   =   "|"
               Style           =   1
            End
         End
         Begin Threed.SSPanel pnlSrceFrame
            Height          =   3390
            Left            =   4440
            TabIndex        =   4
            Top             =   480
            Width           =   3255
            _Version        =   65536
            _ExtentX        =   5741
            _ExtentY        =   5980
            _StockProps     =   15
            BevelOuter      =   1
            Autosize        =   3
            Begin MSOutl.Outline olnSrce
               Height          =   3360
               Left            =   15
               TabIndex        =   5
               Top             =   15
               Width           =   3225
               _Version        =   65536
               _ExtentX        =   5689
               _ExtentY        =   5927
               _StockProps     =   77
               BackColor       =   -2147483643
               PathSeparator   =   "|"
               Style           =   1
            End
         End
         Begin VB.Label lblDest
            Appearance      =   0  'Flat
            AutoSize        =   -1  'True
            Caption         =   "&Don't include these properties"
            ForeColor       =   &H80000008&
            Height          =   195
            Left            =   4440
            TabIndex        =   9
            Top             =   240
            Width           =   2565
         End
         Begin VB.Label lblSrce
            Appearance      =   0  'Flat
            AutoSize        =   -1  'True
            Caption         =   "&Include these properties"
            ForeColor       =   &H80000008&
            Height          =   195
            Left            =   240
            TabIndex        =   8
            Top             =   240
            Width           =   2070
         End
      End
      Begin Threed.SSOption optAllProperties
         Height          =   195
         Left            =   240
         TabIndex        =   11
         Top             =   180
         Width           =   4215
         _Version        =   65536
         _ExtentX        =   7435
         _ExtentY        =   344
         _StockProps     =   78
         Caption         =   "Include all properties in report"
         Value           =   -1  'True
      End
      Begin Threed.SSOption optSelectProperties
         Height          =   195
         Left            =   240
         TabIndex        =   10
         TabStop         =   0   'False
         Top             =   540
         Width           =   4335
         _Version        =   65536
         _ExtentX        =   7646
         _ExtentY        =   344
         _StockProps     =   78
         Caption         =   "Select properties in report"
      End
   End
End
Attribute VB_Name = "frmPickProperties"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  PICKPROP.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim OldMousePointer%
Dim CallingForm As Form

Dim hConnect&
Dim MachineGroupID$

'State flag and Arrays
Dim bGetData%  'If True, getting machine Attributes from SMS.
Dim PropertyList$()
Dim PropertyCount%

'Some relavent names for the outline control pictures
Const MSOUTLINE_PICTURE_GROUP = MSOUTLINE_PICTURE_CLOSED
Const MSOUTLINE_PICTURE_MACHINE = MSOUTLINE_PICTURE_OPEN

Private Sub AddPropertyToOutline(Property$, oln As Outline, ByVal PropertyData&)
    Dim Pos1%, Pos2%, PropertyCategory$, PropertyVersion$, PropertyName$
    Dim ListIndex%, Found%

    'Property Name not available. Use part of Property ID string.
    Pos1% = InStr(Property$, "|")
    Pos2% = InStr(Pos1% + 1, Property$, "|")
    If Pos1% <= 0 Or Pos2% <= 0 Then Exit Sub
    PropertyCategory$ = LCase$(left$(Property$, Pos1% - 1))
    PropertyVersion$ = LCase$(Mid$(Property$, Pos2% + 1))
    PropertyName$ = LCase$(Mid$(Property$, Pos1% + 1, Pos2% - Pos1% - 1))

    'Find category
    Found% = False
    For ListIndex% = 0 To oln.ListCount - 1
        If oln.Indent(ListIndex%) <= 1 Then
            If oln.List(ListIndex%) = PropertyCategory$ Then
                Found% = True
                Exit For
            End If
        End If
    Next ListIndex%
    If Not Found% Then
        'Add a new category item
        oln.AddItem PropertyCategory$, ListIndex%
        oln.Indent(ListIndex%) = 1
        oln.PictureType(ListIndex) = MSOUTLINE_PICTURE_CLOSED
        oln.ItemData(ListIndex%) = -1
    End If

    'Find Property
    Found% = False
    For ListIndex% = ListIndex% + 1 To oln.ListCount - 1
        If oln.Indent(ListIndex%) <= 2 Then
            If oln.Indent(ListIndex%) <= 1 Then Exit For
            If oln.List(ListIndex%) = PropertyName$ Then
                Found% = True
                Exit For
            End If
        End If
    Next ListIndex%
    If Not Found% Then
        'Add a new version item
        oln.AddItem PropertyName$, ListIndex%
        oln.Indent(ListIndex%) = 2
        oln.PictureType(ListIndex) = MSOUTLINE_PICTURE_LEAF
        oln.ItemData(ListIndex%) = PropertyData&

        oln.Refresh
    End If
End Sub

Private Sub cmdAdd_Click()
    Dim ListIndex%, Indent%

    ListIndex% = olnSrce.ListIndex
    If ListIndex% < 0 Then Exit Sub
    Indent% = olnSrce.Indent(ListIndex%)

    'Add selection
    If olnSrce.ItemData(ListIndex%) >= 0 Then
        AddPropertyToOutline PropertyList$(olnSrce.ItemData(ListIndex%)), olnDest, olnSrce.ItemData(ListIndex%)
    End If

    'Add children of selection
    Do While ListIndex% + 1 < olnSrce.ListCount
        ListIndex% = ListIndex% + 1
        If olnSrce.Indent(ListIndex%) > Indent% Then
            If olnSrce.ItemData(ListIndex%) >= 0 Then
                AddPropertyToOutline PropertyList$(olnSrce.ItemData(ListIndex%)), olnDest, olnSrce.ItemData(ListIndex%)
            End If
        Else: Exit Do
        End If
    Loop
End Sub

Private Sub cmdCancel_Click()
    Unload Me
End Sub

Private Sub cmdOk_Click()
    Dim ListIndex%

    PickPropertiesCount% = 0

    PickPropertiesIsFiltered% = optSelectProperties.Value
    If PickPropertiesIsFiltered% Then
        'Return list of properties picked
        ReDim PickPropertiesList$(0 To olnDest.ListCount)
        For ListIndex% = 0 To olnDest.ListCount - 1
            If olnDest.Indent(ListIndex%) = 2 Then
                PickPropertiesList$(PickPropertiesCount%) = PropertyList$(olnDest.ItemData(ListIndex%))
                PickPropertiesCount% = PickPropertiesCount% + 1
            End If
        Next ListIndex%
    End If

    'User didn't cancel
    PickPropertiesUserCanceled% = False
    Unload Me
End Sub

Private Sub cmdRemove_Click()
    Dim ListIndex%, Indent%

    ListIndex% = olnDest.ListIndex
    If ListIndex% < 0 Then Exit Sub
    Indent% = olnDest.Indent(ListIndex%)

    'Remove selection
    olnDest.RemoveItem ListIndex%

    'Remove children of selection
    Do While ListIndex% < olnDest.ListCount
        If olnDest.Indent(ListIndex%) > Indent% Then
            olnDest.RemoveItem ListIndex%
        Else: Exit Do
        End If
    Loop
End Sub

Private Sub Form_Activate()
    bGetData% = True
    GetMachineGroups
    If bGetData% Then
        bGetData% = False
    Else: Unload Me
    End If
End Sub

Private Sub Form_Load()
    Dim NewLeft%, NewTop%

    'Save the old mouse pointer
    Set CallingForm = Screen.ActiveForm
    OldMousePointer% = CallingForm.MousePointer

    'Turn on normal pointer
    MousePointer = NORMAL

    'Center form on calling form
    NewLeft% = CallingForm.left + (CallingForm.Width - Width) / 2
    If NewLeft% + Width > Screen.Width Then NewLeft% = Screen.Width - Width
    If NewLeft% < 0 Then NewLeft% = 0
    NewTop% = CallingForm.top + (CallingForm.Height - Height) / 2
    If NewTop% + Height > Screen.Height Then NewTop% = Screen.Height - Height
    If NewTop% < 0 Then NewTop% = 0
    left = NewLeft%
    top = NewTop%

    'Copy parameters locally
    hConnect& = PickPropertieshConnect&
    MachineGroupID$ = PickPropertiesMachineGroupID$

    'Set default to be user canceled
    PickPropertiesUserCanceled% = True
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    If bGetData% Then
        bGetData% = False
        Cancel = True
        Exit Sub
    End If
End Sub

Private Sub Form_Unload(Cancel As Integer)
    'Replace mouse pointer
    CallingForm.MousePointer = OldMousePointer%
End Sub

Private Sub GetMachineGroups()
    Dim hContainer&, hSubFolder&
    Dim lRet&, Resp%

    ReDim PropertyList$(0 To 0)
    PropertyCount% = 0

    lRet& = SmsOpenContainer&(C_MACHINEGROUP, hConnect&, hContainer&)
    If lRet& <> SMS_OK Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
        Exit Sub
    End If

    lRet& = SmsPopulate&(hContainer&, POP_SYNC, ByVal 0&)
    If lRet& <> SMS_OK And lRet& <> SMS_EMPTY Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
        GoTo GetMachineGroupsQuit
    End If

    'List the machine groups in this container
    Resp% = IDOK
    lRet& = SmsGetNextFolder&(hContainer&, F_ANY, hSubFolder&)
    Do While lRet& = SMS_OK
        'Get subfolders
        Resp% = GetMachines%(hSubFolder&)

        'See if the user canceled
        If Not bGetData% Then Resp% = IDABORT

        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SmsGetNextFolder&(hContainer&, F_ANY, hSubFolder&)
                Resp% = IDOK
            Case IDABORT
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SMS_NO_MORE_DATA
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

GetMachineGroupsQuit:
    lRet& = SmsCloseContainer&(hContainer&)
End Sub

Private Function GetMachineProperties%(ByVal hFolder&)
    Dim hSubFolder&, TypeName$, FolderType&
    Dim lRet&, Resp%

    'Default return value IDOK (Entry added)
    GetMachineProperties% = IDOK

    'Get this folder's type (expect Machine)
    lRet& = SmsGetFolderType&(hFolder&, FolderType&, TypeName$)
    If lRet& <> SMS_OK Then
        GetMachineProperties% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        Exit Function
    End If

    Select Case TypeName$
        Case "Machine"

        Case Else
            Exit Function
    End Select

    'List the properties in this machine folder
    Resp% = IDOK
    lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
    Do While lRet& = SMS_OK
        'Get subfolders
        Resp% = GetProperty%(hSubFolder&)

        'See if user canceled
        If Not bGetData% Then Resp% = IDABORT

        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
                Resp% = IDOK
            Case IDABORT
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SMS_NO_MORE_DATA
            Case IDRETRY
                lRet& = SMS_OK
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

    'Set the return value (either IDABORT or IDOK)
    GetMachineProperties% = Resp%
End Function

Private Function GetMachines%(hFolder&)
    Dim hSubFolder&, TypeName$, FolderType&, Group$
    Dim lRet&, Resp%

    'Default return value IDOK (Entry added)
    GetMachines% = IDOK

    'Get this folder's type (expect Machine Group)
    lRet& = SmsGetFolderType&(hFolder&, FolderType&, TypeName$)
    If lRet& <> SMS_OK Then
        GetMachines% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        Exit Function
    End If

    Select Case TypeName$
        Case "Machine Group"
            lRet& = SmsGetFolderID&(hFolder&, Group$)
            If lRet& <> SMS_OK Then
                GetMachines% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If

            'See if this is the choosen group
            If Group$ <> MachineGroupID$ Then Exit Function

        Case Else
            Exit Function
    End Select

    'List the machine groups in this container
    Resp% = IDOK
    lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
    Do While lRet& = SMS_OK
        'Get subfolders
        Resp% = GetMachineProperties%(hSubFolder&)

        'See if the user canceled
        If Not bGetData% Then Resp% = IDABORT

        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
                Resp% = IDOK
            Case IDABORT
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SMS_NO_MORE_DATA
            Case IDRETRY
                lRet& = SMS_OK
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

    'Set the return value (either IDABORT or IDOK)
    GetMachines% = Resp%
End Function

Private Function GetProperty%(ByVal hFolder&)
    Dim TypeName$, FolderType&
    Dim Property$
    Dim lRet&, Resp%, Index%

    'Default return value IDOK (Entry added)
    GetProperty% = IDOK

    'Get this folder's type (expect Group)
    lRet& = SmsGetFolderType&(hFolder&, FolderType&, TypeName$)
    If lRet& <> SMS_OK Then
        GetProperty% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        Exit Function
    End If

    Select Case TypeName$
        Case "Group"
            lRet& = SmsGetFolderID&(hFolder&, Property$)

            If lRet& <> SMS_OK Then
                GetProperty% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If
        Case Else
            Exit Function
    End Select

    DoEvents

    If bGetData% Then
        'Add item to outline
        AddPropertyToOutline Property$, olnSrce, PropertyCount%

        'Add to property list
        If UBound(PropertyList$) >= PropertyCount% Then ReDim Preserve PropertyList$(0 To PropertyCount% + 32)
        PropertyList$(PropertyCount%) = Property$
        PropertyCount% = PropertyCount% + 1

        Resp% = IDOK
    Else: Resp% = IDABORT
    End If

    'Set the return value (either IDABORT or IDOK)
    GetProperty% = Resp%
End Function

Private Sub olnDest_Click()
    '
End Sub

Private Sub olnDest_Collapse(ListIndex As Integer)
    If olnDest.PictureType(ListIndex) = MSOUTLINE_PICTURE_OPEN Then
        olnDest.PictureType(ListIndex) = MSOUTLINE_PICTURE_CLOSED
    End If
End Sub

Private Sub olnDest_DblClick()
    olnDest.Expand(olnDest.ListIndex) = Not olnDest.Expand(olnDest.ListIndex)
End Sub

Private Sub olnDest_Expand(ListIndex As Integer)
    If olnDest.Indent(ListIndex) <= 1 Then
        If olnDest.Expand(ListIndex) Then
            olnDest.PictureType(ListIndex) = MSOUTLINE_PICTURE_OPEN
        Else: olnDest.PictureType(ListIndex) = MSOUTLINE_PICTURE_CLOSED
        End If
    End If
End Sub

Private Sub olnDest_PictureClick(ListIndex As Integer)
    olnDest.ListIndex = ListIndex
    olnDest_Click
End Sub

Private Sub olnDest_PictureDblClick(ListIndex As Integer)
    olnDest_PictureClick ListIndex
    olnDest_DblClick
End Sub

Private Sub olnSrce_Click()
    '
End Sub

Private Sub olnSrce_Collapse(ListIndex As Integer)
    If olnSrce.PictureType(ListIndex) = MSOUTLINE_PICTURE_OPEN Then
        olnSrce.PictureType(ListIndex) = MSOUTLINE_PICTURE_CLOSED
    End If
End Sub

Private Sub olnSrce_DblClick()
    olnSrce.Expand(olnSrce.ListIndex) = Not olnSrce.Expand(olnSrce.ListIndex)
End Sub

Private Sub olnSrce_Expand(ListIndex As Integer)
    If olnSrce.Indent(ListIndex) <= 1 Then
        If olnSrce.Expand(ListIndex) Then
            olnSrce.PictureType(ListIndex) = MSOUTLINE_PICTURE_OPEN
        Else: olnSrce.PictureType(ListIndex) = MSOUTLINE_PICTURE_CLOSED
        End If
    End If
End Sub

Private Sub olnSrce_PictureClick(ListIndex As Integer)
    olnSrce.ListIndex = ListIndex
    olnSrce_Click
End Sub

Private Sub olnSrce_PictureDblClick(ListIndex As Integer)
    olnSrce_PictureClick ListIndex
    olnSrce_DblClick
End Sub

Private Sub optAllProperties_Click(Value As Integer)
    frameProperties.Enabled = Not Value
End Sub

Private Sub optSelectProperties_Click(Value As Integer)
    frameProperties.Enabled = Value
End Sub


SITEHIER.BAS

Attribute VB_Name = "SITEHIER"
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  SITEHIER.BAS
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

'These need to be the same as outline control picture types
'  (see SITEHIER.FRM in global declarations section)
Global Const SITEHIERTYPE_ROOT = MSOUTLINE_PICTURE_CLOSED
Global Const SITEHIERTYPE_SITE = MSOUTLINE_PICTURE_OPEN
Global Const SITEHIERTYPE_DOMAIN = MSOUTLINE_PICTURE_LEAF

Type SiteRec
    ItemNum As Long
    Parent As String
    Code As String
    Type As Integer
    TypeName As String
    Depth As Integer
    Name As String
End Type

'For use by QuerySite%() and FetchSite%() ONLY
Dim dyQuerySite As Recordset

Function FetchSite%(Site As SiteRec, Flush%, DBError%)
'   Fetches another site record from the dyQuerySite dynaset
'Parameters:
'   Site   - Filled in with next site record.
'   Flush% - Usually False. If True, the fetch is ended.
'   DBError% - Returns True when DB error occures.
'Return Value:
'   True if another record fetched, else False (no more)

    DBError% = True
    FetchSite% = False

    If dyQuerySite.EOF Or Flush% Then
        dyQuerySite.Close
    Else
        ' Populate the site record from the dyQuerySite
        ' dynaset and find the next record
        Site.ItemNum& = dyQuerySite("ItemNum")
        If IsNull(dyQuerySite("Parent")) Then
            Site.Parent$ = ""
        Else: Site.Parent$ = Trim$(dyQuerySite("Parent"))
        End If
        If IsNull(dyQuerySite("Code")) Then
            Site.Code$ = ""
        Else: Site.Code$ = Trim$(dyQuerySite("Code"))
        End If
        Site.Type% = dyQuerySite("Type")
        Site.TypeName$ = Trim$(dyQuerySite("TypeName"))
        Site.Depth% = dyQuerySite("Depth")
        Site.Name$ = Trim$(dyQuerySite("Name"))

        On Error Resume Next
        dyQuerySite.MoveNext
        If Err > 0 Then Exit Function
        On Error GoTo 0

        FetchSite% = True
    End If

    DBError% = False
End Function

Function FindSite%(db As Database, Site As SiteRec)
'   Finds a specific site given the ItemNum
'Parameters
'   db - The database variable
'   Site - The following members are used to identify the site
'       ItemNum&
'Return Value:
'   Count of number of records found
'   Site - Filled with one of those found

    Dim Sql$
    Dim dySite As Recordset

    'Set default return value -1 (Database error)
    FindSite% = -1

    Sql$ = "SELECT ItemNum, Parent, Code, Type, TypeName, Depth, Name FROM [Site Hierarchy]"
    Sql$ = Sql$ + " WHERE ItemNum = " + CStr(Site.ItemNum&)

    ' If an error occurs exit the function
    On Error Resume Next
    Set dySite = db.OpenRecordset(Sql$, dbOpenDynaset)
    If Err > 0 Then Exit Function
    On Error GoTo 0

    ' Populate the site record
    If Not dySite.EOF Then
        Site.ItemNum& = dySite("ItemNum")
        If IsNull(dySite("Parent")) Then
            Site.Parent$ = ""
        Else: Site.Parent$ = Trim$(dySite("Parent"))
        End If
        If IsNull(dySite("Code")) Then
            Site.Code$ = ""
        Else: Site.Code$ = Trim$(dySite("Code"))
        End If
        Site.Type% = dySite("Type")
        Site.TypeName$ = Trim$(dySite("TypeName"))
        Site.Depth% = dySite("Depth")
        Site.Name$ = Trim$(dySite("Name"))
    End If

    ' Return the record count
    FindSite% = dySite.RecordCount
End Function

Function InsertSite&(db As Database, Site As SiteRec)
'   Inserts the Site record into the given data base
'Parameters
'   db    - The database used for the query
'   Site  - The Site record
'Return Value:
'   The number of records inserted

    Dim qy As QueryDef

    ' Default return value False (Failed)
    InsertSite& = -1

    ' Build the insert statement
    Set qy = db.QueryDefs("InsertSite")
    qy![INPUT ItemNum] = Site.ItemNum&
    qy![INPUT Parent] = Site.Parent$
    qy![INPUT Code] = Site.Code$
    qy![INPUT Type] = Site.Type%
    qy![INPUT TypeName] = Site.TypeName$
    qy![INPUT Depth] = Site.Depth%
    qy![INPUT Name] = Site.Name$

    'Try to insert the record
    On Error Resume Next
    qy.Execute
    If Err > 0 Then Exit Function
    qy.Close
    On Error GoTo 0

    'Return insert count
    InsertSite& = 1
End Function

Function NewSiteHierarchyDatabase%(db As Database, ErrStr$)
    Dim td As TableDef, qy As QueryDef, fd As Field, ix As Index

    'Default return value False (Failed)
    NewSiteHierarchyDatabase% = False

    On Error GoTo NewMachineDatabaseErr

    Set td = New TableDef
    td.Name = "Site Hierarchy"

        'Fields
        Set fd = New Field
        fd.Name = "ItemNum"
        fd.Type = DB_LONG
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "Parent"
        fd.Type = DB_TEXT
        fd.Size = 4
        fd.AllowZeroLength = True
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "Code"
        fd.Type = DB_TEXT
        fd.Size = 4
        fd.AllowZeroLength = True
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "Type"
        fd.Type = DB_INTEGER
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "TypeName"
        fd.Type = DB_TEXT
        fd.Size = 16
        fd.AllowZeroLength = True
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "Depth"
        fd.Type = DB_INTEGER
        td.Fields.Append fd

        Set fd = New Field
        fd.Name = "Name"
        fd.Type = DB_TEXT
        fd.Size = 255
        fd.AllowZeroLength = True
        td.Fields.Append fd

        'Indexes
        Set ix = New Index
        ix.Name = "SortKey"
        ix.Primary = True
        ix.Unique = True
        ix.Fields = "ItemNum"
        td.Indexes.Append ix

    db.TableDefs.Append td

    'Make QueryDefs for common insert queries and selects
    Set qy = db.CreateQueryDef("InsertSite", "PARAMETERS [INPUT ItemNum] Long, [INPUT Parent] Text, [INPUT Code] Text, [INPUT Type] Short, [INPUT TypeName] Text, [INPUT Depth] Short, [INPUT Name] Text; INSERT INTO [Site Hierarchy] (ItemNum, Parent, Code, Type, TypeName, Depth, Name) SELECT [INPUT ItemNum] As ItemNum, '|[INPUT Parent]|' As Parent, '|[INPUT Code]|' As Code, [INPUT Type] As Type, '|[INPUT TypeName]|' As TypeName, [INPUT Depth] As Depth, '|[INPUT Name]|' As Name")

    'Return success
    NewSiteHierarchyDatabase% = True
    Exit Function

NewMachineDatabaseErr:
    ErrStr$ = Error$
    Exit Function
End Function

Function NewSiteHierarchyReportWindow%(hConnect&, ByVal FileSpec$, ErrStr$)
    Dim i%, Ret&, TempPath$, TempSpec$, frm As Form
    Dim db As Database, td As TableDef, fd As Field, ix As Index

    'Default return value zero (Failed)
    NewSiteHierarchyReportWindow% = 0

    'Allocate space for returned string
    TempSpec$ = String$(256, 0)

    'Get a temp file name
    TempPath$ = Environ$("TEMP")
    Ret& = GetTempFileName(TempPath$, "MDB", 0, TempSpec$)
    ErrStr$ = "Could not get a temporary file name for the new report."
    If (Ret& = 0) Then Exit Function

    'Clean up return string of null terminator
    i% = InStr(TempSpec$, Chr$(0))
    If i% = 0 Then i% = Len(TempSpec$) + 1
    TempSpec$ = left$(TempSpec$, i% - 1)

    'Create a temporary document file
    On Error GoTo NewSiteHierarchyReportWindowErr
    If FileSpec$ > "" Then
        'File Open: so copy the file the user selected
        ErrStr$ = "Could not create a temporary copy of " + FileSpec$ + ".+"
        FileCopy FileSpec$, TempSpec$
    Else
        'File New: so create a new empty document
        ErrStr$ = "Could not create a temporary file for the new report."
        Kill TempSpec$
        Set db = CreateDatabase(TempSpec$, DB_LANG_GENERAL)
        i% = NewSiteHierarchyDatabase%(db, ErrStr$)
        db.Close
        If Not i% Then GoTo NewSiteHierarchyReportWindowErr
    End If
    On Error GoTo 0

    'Pass the new document the file name to use
    InitReporthConnect& = hConnect&
    InitReportFileSpec$ = FileSpec$
    InitReportTempSpec$ = TempSpec$
    If InitReportFileSpec$ <= "" Then InitReportNewNumber% = InitReportNewNumber% + 1

    'Create a new document window
    On Error GoTo NewSiteHierarchyReportWindowErr
    ErrStr$ = "Could not create site report document window."
    Set frm = New frmSiteHierarchyReport
    Load frm
    On Error GoTo 0

    'Register document window with MDI

    i% = RegisterReportWindow%(frm, ErrStr$)
    If i% <= 0 Then GoTo NewSiteHierarchyReportWindowErr

    'Show the document window
    frm.Show
    NewSiteHierarchyReportWindow% = i%
    Exit Function

NewSiteHierarchyReportWindowErr:
    On Error Resume Next
    If Not frm Is Nothing Then Unload frm
    If Not db Is Nothing Then db.Close
    Kill TempSpec$
    Exit Function
End Function

Function QuerySite%(db As Database, Site As SiteRec)
'   Queries the Site Hierarchy table and creates
'   a dynaset (dyQuerySite) used by FetchSite()
'   ordered by path
'Parameters
'   db - The database used for the query
'   Site - The following are used in the where clause
'       .Parent$ - Specifies the parent site code
'       .Code$   - Specifies the item (wildcard if enpty)
'Return Value:
'   True if no error, else False

    Dim Sql$, Where$

    'Default return value False (Database Error)
    QuerySite% = False

    ' Build the SQL statement
    Sql$ = "SELECT ItemNum, Parent, Code, Type, TypeName, Depth, Name FROM [Site Hierarchy]"

    'Build the Where clause
    Where$ = ""

    'Specify the parent
    If Site.Parent$ > "" Then
        Where$ = " WHERE Parent = '" + Site.Parent$ + "'"
    Else: Where$ = " WHERE Depth = 0"
    End If

    'Optionally specify the item (Site or Domain)
    If Site.Code$ > "" Then
        If Where$ > "" Then
            Where$ = Where$ + " AND "
        Else: Where$ = " WHERE "
        End If
        Where$ = Where$ + "Code = '" + Site.Code$ + "'"
    End If

    'Append the where clause onto query string
    Sql$ = Sql$ + Where$

    'Perform the query
    On Error Resume Next
    Set dyQuerySite = db.OpenRecordset(Sql$, dbOpenDynaset)
    If Err > 0 Then Exit Function
    On Error GoTo 0

    'Every thing is ok
    QuerySite% = True
End Function


SITEHIER.FRM

VERSION 4.00
Begin VB.Form frmSiteHierarchyReport
   Appearance      =   0  'Flat
   BackColor       =   &H80000005&
   Caption         =   "Site Hierarchy Report"
   ClientHeight    =   4350
   ClientLeft      =   1860
   ClientTop       =   2580
   ClientWidth     =   4530
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   4755
   Icon            =   "SITEHIER.frx":0000
   Left            =   1800
   LinkTopic       =   "Form1"
   MDIChild        =   -1  'True
   ScaleHeight     =   4350
   ScaleWidth      =   4530
   Top             =   2235
   Width           =   4650
   Begin VB.CommandButton cmdSave
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Save"
      Height          =   315
      Left            =   420
      TabIndex        =   4
      TabStop         =   0   'False
      Top             =   2100
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdSaveAs
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Save As"
      Height          =   315
      Left            =   420
      TabIndex        =   3
      TabStop         =   0   'False
      Top             =   2400
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CheckBox chkDirty
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Dirty Bit"
      ForeColor       =   &H80000008&
      Height          =   195
      Left            =   420
      TabIndex        =   2
      TabStop         =   0   'False
      Top             =   1860
      Visible         =   0   'False
      Width           =   1155
   End
   Begin VB.CommandButton cmdPrintPreview
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Print Preview"
      Height          =   315
      Left            =   420
      TabIndex        =   1
      TabStop         =   0   'False
      Top             =   3000
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdPrint
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Print"
      Height          =   315
      Left            =   420
      TabIndex        =   0
      TabStop         =   0   'False
      Top             =   2700
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdAbort
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Abort"
      Height          =   315
      Left            =   420
      TabIndex        =   7
      TabStop         =   0   'False
      Top             =   3900
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdInitialize
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Initialize"
      Height          =   315
      Left            =   420
      TabIndex        =   8
      TabStop         =   0   'False
      Top             =   3600
      Visible         =   0   'False
      Width           =   3735
   End
   Begin VB.CommandButton cmdExport
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "Export"
      Height          =   315
      Left            =   420
      TabIndex        =   9
      TabStop         =   0   'False
      Top             =   3300
      Visible         =   0   'False
      Width           =   3735
   End
   Begin MSComDlg.CommonDialog CommonDialog
      Left            =   2400
      Top             =   360
      _ExtentX        =   847
      _ExtentY        =   847
      _StockProps     =   0
   End
   Begin Crystal.CrystalReport CrystalReport
      Left            =   2880
      Top             =   360
      _ExtentX        =   741
      _ExtentY        =   741
      _StockProps     =   0
      ReportFileName  =   ""
      Destination     =   0
      WindowLeft      =   100
      WindowTop       =   100
      WindowWidth     =   480
      WindowHeight    =   300
      WindowTitle     =   ""
      WindowBorderStyle=   2
      WindowControlBox=   -1  'True
      WindowMaxButton =   -1  'True
      WindowMinButton =   -1  'True
      CopiesToPrinter =   1
      PrintFileName   =   ""
      PrintFileType   =   2
      SelectionFormula=   ""
      GroupSelectionFormula=   ""
      Connect         =   ""
      UserName        =   ""
      ReportSource    =   0
      BoundReportHeading=   ""
      BoundReportFooter=   -1  'True
   End
   Begin MSOutl.Outline olnData
      Height          =   750
      Left            =   420
      TabIndex        =   6
      Top             =   240
      Width           =   1500
      _Version        =   65536
      _ExtentX        =   2646
      _ExtentY        =   1323
      _StockProps     =   77
      BorderStyle     =   0
      Style           =   5
   End
   Begin VB.Label lblFileSpec
      Appearance      =   0  'Flat
      BackColor       =   &H80000005&
      Caption         =   "File Specification"
      ForeColor       =   &H80000008&
      Height          =   195
      Left            =   1740
      TabIndex        =   5
      Top             =   1860
      Visible         =   0   'False
      Width           =   2175
   End
End
Attribute VB_Name = "frmSiteHierarchyReport"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Option Explicit

'//****************************************************************************
'//
'//  Copyright (c) 1995, Microsoft Corporation
'//
'//  File:  SITEHIER.FRM
'//
'//  History:
'//
'//      Gary Fuehrer, SEA   5/9/95      Created.
'//
'//****************************************************************************

Dim hConnect&
Dim ReportTempSpec$
Dim NewNumber%
Dim db As Database
Dim dbt As Database

'Some relavent names for the outline control pictures
Const MSOUTLINE_PICTURE_ROOT = SITEHIERTYPE_ROOT
Const MSOUTLINE_PICTURE_SITE = SITEHIERTYPE_SITE
Const MSOUTLINE_PICTURE_DOMAIN = SITEHIERTYPE_DOMAIN

'State flags
Dim bGetData%  'If True, getting site data from SMS.
Dim bReadData% 'If True, reading site data from file.

Private Sub chkDirty_Click()
    UpdateCaption
End Sub

Private Sub cmdAbort_Click()
    'Handle user abort of current lengthy operation
    If bGetData% Then
        If MsgBox("Do you want to stop gathering site data?" + Chr$(10) + Chr$(10) + "Click Yes to abort, No to continue.", MB_YESNO + MB_ICONQUESTION, Caption) = IDYES Then
            bGetData% = False
        End If
    ElseIf bReadData% Then
        If MsgBox("Do you want to stop reading site data?" + Chr$(10) + Chr$(10) + "Click Yes to abort, No to continue.", MB_YESNO + MB_ICONQUESTION, Caption) = IDYES Then
            bReadData% = False
        End If
    End If
End Sub

Private Sub cmdExport_Click()
    Dim FileSpec$, nPos%, Resp%, FileKilled%

    'Build suggested file spec
    If lblFileSpec > "" Then
        nPos% = InStr(lblFileSpec, ".") - 1
        If nPos% < 0 Then nPos% = Len(lblFileSpec)
        FileSpec$ = left$(lblFileSpec, nPos) + ".TXT"
    Else: FileSpec$ = "*.TXT"
    End If

    'Get from the user the print file name
    FileSpec$ = GetSaveAsFileSpec$(FileSpec$, "ASCII Text (*.TXT) |*.TXT |All Files (*.*) |*.*")
    If FileSpec$ <= "" Then Exit Sub

    'See if we need to delete existing file
    '(Crystal Reports can't overwrite an existing file)
    If Dir$(FileSpec$) > "" Then
        FileKilled% = 0
        Do
            On Error Resume Next
            Kill FileSpec
            If Err > 0 Then
                Resp% = MsgBox("An error occured writing to file " + FileSpec$ + ":" + Chr$(10) + Error$ + Chr$(10) + Chr$(10) + "Do you want to try again?", MB_RETRYCANCEL Or MB_ICONQUESTION, Caption)
                If Resp% = IDCANCEL Then Exit Sub
            Else: FileKilled% = True
            End If
        Loop Until FileKilled%
    End If

    'Set the print file name and type
    CrystalReport.PrintFileName = CommonDialog.filename
    CrystalReport.PrintFileType = 2 'ASCII text

    'Export report code
    PrintDoc CRW_PRINT_TO_FILE
End Sub

Private Sub cmdInitialize_Click()
    If lblFileSpec > "" Then
        'Flag that we are in data reading mode
        bReadData% = True
        ReadSiteData "", -1
        bReadData% = False
    Else
        'Flag that we are in gathering data mode
        bGetData% = True
        GetSiteData
        bGetData% = False
    End If
End Sub

Private Sub cmdPrint_Click()
    'See if we need the print dialog
    If gbNeedPrintDialog% = True Then
        'Put up the print dialog
        CommonDialog.Flags = PD_NOSELECTION Or PD_NOPAGENUMS Or PD_HIDEPRINTTOFILE
        CommonDialog.Copies = 1
        CommonDialog.CancelError = True
        On Error Resume Next
        CommonDialog.Action = DLG_PRINT
        If Err = CDERR_CANCEL Then Exit Sub
        If Err > 0 Then
            MsgBox "An unexpected error occured:" + Chr$(10) + Chr$(10) + Error$, MB_OK Or MB_ICONEXCLAMATION, Caption
            Exit Sub
        End If
        On Error GoTo 0

        'Handle user settings
        CrystalReport.CopiesToPrinter = CommonDialog.Copies
    Else
        'Supply default settings
        CrystalReport.CopiesToPrinter = 1
    End If

    'Print document
    PrintDoc CRW_PRINT_TO_PRINTER
End Sub

Private Sub cmdPrintPreview_Click()
    'Print Preview document
    PrintDoc CRW_PRINT_TO_WINDOW
End Sub

Private Sub cmdSave_Click()
    'See if we need to do a Save or a Save As
    If lblFileSpec > "" Then
        SaveDoc lblFileSpec
    Else: cmdSaveAs_Click
    End If
End Sub

Private Sub cmdSaveAs_Click()
    Dim NewFileSpec$

    NewFileSpec$ = GetSaveAsFileSpec$(lblFileSpec, "Access 1.0 (*.MDB) |*.MDB |All Files (*.*) |*.*")
    If NewFileSpec$ <= "" Then Exit Sub

    SaveDoc NewFileSpec$
End Sub

Private Sub Form_Load()
    'Position outline control (will be resized in Form Resize)
    olnData.left = 0
    olnData.top = 0

    chkDirty = False

    NewNumber% = InitReportNewNumber%
    hConnect& = InitReporthConnect&
    lblFileSpec = InitReportFileSpec$
    ReportTempSpec$ = InitReportTempSpec$

    Set dbt = OpenDatabase(ReportTempSpec$, True)
    If lblFileSpec > "" Then Set db = OpenDatabase(lblFileSpec, True, True)
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    Dim Resp%, FileSpec$

    'Make sure we're not in the middle of something
    If bGetData% Or bReadData% Then
        cmdAbort_Click
        Cancel = True
        Exit Sub
    End If

    'Give the user a chance to save document
    If chkDirty Then
        Resp% = MsgBox("This report has been modified. Save data before closing?", MB_YESNOCANCEL Or MB_ICONQUESTION, Caption)
        If Resp% = IDYES Then
            cmdSave_Click
            If chkDirty Then Cancel = True
        ElseIf Resp% = IDCANCEL Then: Cancel = True
        End If
    End If
End Sub

Private Sub Form_Resize()
    Static OldWidth, OldHeight
    Dim r As Rect, HBorderPixels%, VBorderPixels%

    'See if we're minimized
    If WindowState = MINIMIZED Then Exit Sub

    'Get client size of form (already in pixels, I find)
    GetClientRect hwnd, r
    HBorderPixels% = Width / X_PIX_SIZE - r.right
    VBorderPixels% = Height / Y_PIX_SIZE - r.bottom

    If OldWidth <> Width And Width - X_PIX_SIZE * HBorderPixels% > olnData.left Then
        olnData.Width = Width - X_PIX_SIZE * HBorderPixels% - olnData.left

        OldWidth = Width
    End If

    If OldHeight <> Height And Height - Y_PIX_SIZE * VBorderPixels% > olnData.top Then
        olnData.Height = Height - Y_PIX_SIZE * VBorderPixels% - olnData.top

        OldHeight = Height
    End If
End Sub

Private Sub Form_Unload(Cancel As Integer)
    dbt.Close
    If Not db Is Nothing Then db.Close

    'Kill temp file
    On Error Resume Next
    Kill ReportTempSpec$
    On Error GoTo 0

    FreeReportWindow Me
End Sub

Private Function GetSaveAsFileSpec$(ByVal InitFileSpec$, Filter$)
    'Default return value empty (User canceled)
    GetSaveAsFileSpec$ = ""

    CommonDialog.CancelError = True
    CommonDialog.filename = InitFileSpec$
    CommonDialog.Filter = Filter$
    CommonDialog.FilterIndex = 1
    CommonDialog.Flags = OFN_OVERWRITEPROMPT Or OFN_PATHMUSTEXIST Or OFN_HIDEREADONLY
    On Error Resume Next
    CommonDialog.Action = DLG_FILE_SAVE
    If Err = CDERR_CANCEL Then Exit Function
    If Err > 0 Then
        MsgBox "An unexpected error occured:" + Chr$(10) + Error$, MB_OK Or MB_ICONEXCLAMATION, Caption
        Exit Function
    End If
    On Error GoTo 0

    GetSaveAsFileSpec$ = CommonDialog.filename
End Function

Private Sub GetSiteData()
    Dim hContainer&, hSubFolder&, ItemNum&, RootSite As SiteRec
    Dim sScalar As SCALAR
    Dim lRet&, Resp%

    'Reset item number
    ItemNum& = 0

    lRet& = SmsOpenContainer&(C_SITE, hConnect&, hContainer&)
    If lRet& <> SMS_OK Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
        Exit Sub
    End If

    lRet& = SmsPopulate&(hContainer&, POP_SYNC, ByVal 0&)
    If lRet& <> SMS_OK And lRet& <> SMS_EMPTY Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
        GoTo GetSiteDataCleanup
    End If

    'List the root sites in this container
    Resp% = IDOK
    lRet& = SmsGetNextFolder&(hContainer&, F_ANY, hSubFolder&)
    Do While lRet& = SMS_OK
        'Get this site's depth
        lRet& = SmsGetScalarByName&(hSubFolder&, "Depth", sScalar)
        If lRet& <> SMS_OK Then
            Resp% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        Else
            'Only recurse from top level Site (Depth = 0)
            If sScalar.dwValue& <> 0 Then
                Resp% = IDOK
            Else
                'Get subfolders recursively
                Resp% = GetSiteDataRecursive%(dbt, hSubFolder&, CInt(olnData.ListCount), RootSite, ItemNum&)

                'See if the user canceled
                If Not bGetData% Then Resp% = IDABORT
            End If
        End If

        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SmsGetNextFolder&(hContainer&, F_ANY, hSubFolder&)
                Resp% = IDOK
            Case IDABORT
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SMS_NO_MORE_DATA
            Case IDRETRY
                lRet& = SMS_OK
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

GetSiteDataCleanup:
    lRet& = SmsCloseContainer&(hContainer&)
    chkDirty = CHECKED
End Sub

Private Function GetSiteDataRecursive%(db As Database, ByVal hFolder&, ParentListIndex%, ParentSite As SiteRec, ItemNum&)
    Dim hSubFolder&, FolderType&
    Dim sScalar As SCALAR
    Dim Site As SiteRec
    Dim ListIndex%, bGetDataOld%
    Dim lRet&, Resp%

    'Default return value IDOK (Entry added)
    GetSiteDataRecursive% = IDOK

    'Location of next item to add to outline control
    ListIndex% = olnData.ListCount

    'Get this folder's type (site or domain)
    lRet& = SmsGetFolderType&(hFolder&, FolderType&, Site.TypeName$)
    If lRet& <> SMS_OK Then
        GetSiteDataRecursive% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        Exit Function
    End If

    Select Case Site.TypeName$
        Case "Domain"
            lRet& = SmsGetScalarByName&(hFolder&, "Site code", sScalar)
            If lRet& <> SMS_OK Then
                GetSiteDataRecursive% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If
            Site.Parent$ = sScalar.pszValue

            lRet& = SmsGetFolderID&(hFolder&, Site.Name$)
            If lRet& <> SMS_OK Then
                GetSiteDataRecursive% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If
            Site.Name$ = Site.Name$

            Site.Code$ = ""
            Site.Type% = SITEHIERTYPE_DOMAIN
            Site.Depth% = ParentSite.Depth% + 1

            ParentSite.ItemNum& = ParentSite.ItemNum& + 1
            Site.ItemNum& = ParentSite.ItemNum&

        Case "Site"
            lRet& = SmsGetFolderID&(hFolder&, Site.Code$)
            If lRet& <> SMS_OK Then
                GetSiteDataRecursive% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If

            lRet& = SmsGetScalarByName&(hFolder&, "Parent site", sScalar)
            If lRet& <> SMS_OK Then
                GetSiteDataRecursive% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If
            Site.Parent$ = sScalar.pszValue

            lRet& = SmsGetScalarByName&(hFolder&, "Depth", sScalar)
            If lRet& <> SMS_OK Then
                GetSiteDataRecursive% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If
            Site.Depth = sScalar.dwValue&

            lRet& = SmsGetScalarByName&(hFolder&, "Site name", sScalar)
            If lRet& <> SMS_OK Then
                GetSiteDataRecursive% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If
            Site.Name$ = sScalar.pszValue

            lRet& = SmsGetScalarByName&(hFolder&, "Site type", sScalar)
            If lRet& <> SMS_OK Then
                GetSiteDataRecursive% = MsgBox("SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
                Exit Function
            End If
            Site.TypeName$ = sScalar.pszValue + " " + Site.TypeName$

            If Site.Parent$ > "" Then
                Site.Type% = SITEHIERTYPE_SITE
            Else: Site.Type% = SITEHIERTYPE_ROOT
            End If

            Site.ItemNum& = ItemNum&
            ItemNum& = ItemNum& + &H10000

        Case Else
            Exit Function
    End Select

    DoEvents

    'Check to see if the user wants to cancel
    If bGetData% Then
        'Add item to database
        If InsertSite&(db, Site) = 1 Then
            'Add item to outline control
            olnData.AddItem Site.Name$, ListIndex%
            olnData.Indent(ListIndex%) = Site.Depth%
            olnData.PictureType(ListIndex%) = Site.Type%
            olnData.ItemData(ListIndex%) = Site.ItemNum&
            If olnData.IsItemVisible(ParentListIndex%) And Not olnData.Expand(ParentListIndex%) Then
                olnData.Expand(ParentListIndex%) = True
            End If
            If olnData.ListIndex = ListIndex% - 1 Then
                olnData.ListIndex = ListIndex%
            End If
            olnData.Refresh
            Resp% = IDOK
        Else: Resp% = MsgBox("Database Error:" + Chr$(10) + Chr$(10) + Error$, MB_ICONEXCLAMATION + MB_ABORTRETRYIGNORE, Caption)
        End If
    Else: Resp% = IDABORT
    End If

    'Make sure everything is OK and get first subfolder
    If Site.Type% <> SITEHIERTYPE_DOMAIN And Resp% = IDOK Then
        lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
    Else: lRet& = SMS_NO_MORE_DATA
    End If

    'List the sub-folders in this folder
    Do While lRet& = SMS_OK
        'Get subfolders recursively
        Resp% = GetSiteDataRecursive%(db, hSubFolder&, ListIndex%, Site, ItemNum&)

        'See if user canceled
        If Not bGetData% Then Resp% = IDABORT

        'Check the user response
        Select Case Resp%
            Case IDOK, IDIGNORE
                lRet& = SmsCloseFolder&(hSubFolder&)
                lRet& = SmsGetNextFolder&(hFolder&, F_ANY, hSubFolder&)
                Resp% = IDOK
            Case IDABORT
                lRet& = SmsCloseFolder&(hFolder&)
                lRet& = SMS_NO_MORE_DATA
            Case IDRETRY
                lRet& = SMS_OK
        End Select
    Loop
    If lRet& <> SMS_NO_MORE_DATA Then
        MsgBox "SMS Error:" + Chr$(10) + Chr$(10) + SMSError$(lRet&), MB_ICONEXCLAMATION + MB_OK, Caption
    End If

    'If we're done with the item, collapse it
    If Resp% = IDOK Then
        If (olnData.ListIndex <= ListIndex% Or olnData.ListIndex = olnData.ListCount - 1) And olnData.HasSubItems(ListIndex%) Then
            'Preserve flag and allow Collapse to remove nodes
            bGetDataOld% = bGetData%
            bGetData% = False

            'Collapse the site now that its done
            olnData_Collapse ListIndex%

            'Restore flag
            bGetData% = bGetDataOld%
        End If
    End If

    'Set the return value (either IDABORT or IDOK)
    GetSiteDataRecursive% = Resp%
End Function

Private Sub lblFileSpec_Change()
    UpdateCaption
End Sub

Private Sub olnData_Collapse(ListIndex As Integer)
    If Not bGetData% And Not bReadData% Then
        Do While olnData.HasSubItems(ListIndex%)
            olnData_Collapse ListIndex% + 1
            olnData.RemoveItem ListIndex% + 1
        Loop
    End If
End Sub

Private Sub olnData_DblClick()
    If olnData.HasSubItems(olnData.ListIndex) Then
        olnData_Collapse CInt(olnData.ListIndex)
    Else: olnData_Expand CInt(olnData.ListIndex)
    End If
End Sub

Private Sub olnData_Expand(ListIndex As Integer)
    Dim Site As SiteRec
    Static bExpanded%

    'Prevent infinite recursion
    If bExpanded% Then Exit Sub

    If Not bReadData% Then
        If Not olnData.HasSubItems(ListIndex) Then
            'Find the info on the node to expand
            Site.ItemNum& = olnData.ItemData(ListIndex)
            If FindSite(dbt, Site) <> 1 Then
                MsgBox "Unexpected error trying to expand site:" + Chr$(10) + Chr$(10) + Error$, MB_OK + MB_ICONEXCLAMATION, Caption
                Exit Sub
            Else: If Site.Type = SITEHIERTYPE_DOMAIN Then Exit Sub
            End If

            'Read the sites children from the data file
            bReadData% = True
            ReadSiteData Site.Code$, ListIndex
            bReadData% = False
        End If

        'Expand the node to show children
        If olnData.HasSubItems(ListIndex) Then
            bExpanded% = True
            olnData.Expand(ListIndex%) = True
            bExpanded% = False
        End If
    End If
End Sub

Private Sub olnData_PictureClick(ListIndex As Integer)
    olnData.ListIndex = ListIndex
End Sub

Private Sub olnData_PictureDblClick(ListIndex As Integer)
    olnData.ListIndex = ListIndex
    olnData_DblClick
End Sub

Private Sub PrintDoc(Destination%)
    CrystalReport.WindowTitle = Caption
    CrystalReport.Destination = Destination%
    CrystalReport.WindowParentHandle = 0
    CrystalReport.DataFiles(0) = ReportTempSpec$
    CrystalReport.ReportFileName = App.Path + "\SITEHIER.RPT"

    'Print the report
    CrystalReport.Action = 1
End Sub

Private Sub ReadSiteData(Parent$, ParentListIndex%)
    Dim ListIndex%, TooMany%, DBError%
    Dim Site As SiteRec

    'Select all children of Parent$ sites
    Site.Parent$ = Parent$
    Site.Code$ = ""
    If QuerySite%(dbt, Site) = False Then
        MsgBox "Error accessing database"
        Exit Sub
    End If

    TooMany% = False
    ListIndex% = ParentListIndex% + 1
    Do While FetchSite%(Site, Not bReadData% Or TooMany%, DBError%)
        If DBError% Then
            MsgBox "Error accessing database"
            Exit Sub
        ElseIf olnData.ListCount >= MAX_OUTLINE_ENTRIES Then
            TooMany% = True
        Else
            olnData.AddItem Site.Name$, ListIndex%
            olnData.Indent(ListIndex%) = Site.Depth%
            olnData.PictureType(ListIndex%) = Site.Type%
            olnData.ItemData(ListIndex%) = Site.ItemNum&
            ListIndex% = ListIndex% + 1

            'DoEvents 'Turned off because its too slow!
                      'Turn back on to check for user cancel
        End If
    Loop

    If TooMany% Then
        MsgBox "No more sites and domains can be displayed in this window until some are collapsed."
    End If
End Sub

Private Sub SaveDoc(ByVal FileSpec$)
    Dim ReportSaved%, Resp%

    'Temporarily close the database and temp database
    dbt.Close
    If Not db Is Nothing Then db.Close

    ReportSaved% = False
    Do
        On Error Resume Next
        FileCopy ReportTempSpec$, FileSpec$
        If Err > 0 Then
            Resp% = MsgBox("An error occured trying to save " + FileSpec$ + ":" + Chr$(10) + Error$ + Chr$(10) + Chr$(10) + "Do you want to try again?", MB_OKCANCEL Or MB_ICONQUESTION, Caption)
            If Resp% = IDCANCEL Then Exit Sub
        Else: ReportSaved% = True
        End If
    Loop Until ReportSaved%

    'Re-open the database and temp database
    Set dbt = OpenDatabase(ReportTempSpec$, True)
    Set db = OpenDatabase(FileSpec$, True, True)

    If ReportSaved% Then
        lblFileSpec = FileSpec$
        chkDirty = False
    End If
End Sub

Private Sub UpdateCaption()
    Dim NewCaption$

    If lblFileSpec > "" Then
        NewCaption$ = lblFileSpec
    Else: NewCaption$ = "New Site Hierarchy Report " + CStr(NewNumber)
    End If

    If chkDirty Then NewCaption$ = NewCaption$ + "*"

    Caption = NewCaption$
End Sub


README.TXT

Overview:
    SA.EXE is sample code that demonstrates the new scheduling agent API
provided with installation of the Scheduling Agent package;  complete
documentation describing the interfaces is available elsewhere in the
ActiveX SDK.  The sample mimics the functionality of the AT command
through the Scheduling Agent APIs, as opposed to the NetSchedule API
used by AT.  This sample implements only a small subset of the AT
command set, but should be easily extensible.

Usage:
        sa                                                - Show All Tasks
        sa /?                                             - Display Help
        sa JobName.job /DELETE                            - delete job
        sa JobName.job Time Command [UserName [Password]] - submit a job

Job Time is in 24 hour format (such as 15:30) and is the next instance
of this specific time within 24 hours.

Command is the name of the application to run.  Path names may be included.
However, spaces are not allowed in the path name, so an application residing
in C:\program files\foo.exe should be specified as C:\progra~1\foo.exe,
using the old-style 8.3 filenames.  Should any command line parameters
be required, enclose the entire Command string in quotes:
    "c:\progra~1\foo.exe /bar 5"

Username and password are required to run the specified task under Windows NT.
Under Windows NT, if they are not specified as part of the command line,
a prompt for the missing data will be displayed.  Under Windows 95,
both fields are ignored, if specified.  Regardless of specification,
under Windows 95, no prompt will be displayed.

Error Reporting:
    The program performs error checking within the code over the parameters
passed in.  Any errors are returned as the exit value of "main", which may
be read by checking the environmental value %ERRORLEVEL% under Windows NT,
or the corresponding variable in Windows 95.

Additional Notes:
    The program makes use of the following interfaces and methods:

    1. ISchedulingAgent::Enum
       ISchedulingAgent::Activate
       ISchedulingAgent::Delete
       ISchedulingAgent::NewTask

    2. IEnumTasks::Next

    3. ITask::GetTriggerCount
       ITask::GetTriggerString
       ITask::SetAccountInformation
       ITask::SetApplicationName
       ITask::SetParameters
       ITask::SetComment
       ITask::SetFlags
       ITask::CreateTrigger

    4. ITaskTrigger::SetTrigger

    In addition, various methods on IUnknown and IPersistFile are called.
It is assumed that a user of this code has familiarity with those
interfaces and calling conventions.

    The output of the program is one of four possibilities:

    First, and most obviously, is the presentation of a help file.  This
case occurs from command line arguments requesting help, or from input
parameter lists that are longer than the expected range. Output is to
stdout.

    Second, the program can enumerate all task objects in the tasks folder.
Output is provided to stdout, and consists of a line containing
"Job: filename.job" followed by indented lines containing strings of the
type "Trigger: text representation of trigger values".  Empty task folders
cause no output.

    Third, the program can delete jobs from the job folder.  This action
will fail in a secure environment (Windows NT on an NTFS partition) if
the caller does not have permissions to remove the file.  Deletion
of tasks is not limited to those that were created from this program.
If the deletion succeeds, no output is displayed.

    Finally, the program can add a task from the command line.  If the
scheduling agent is to recognize the task, the filename (jobname.job)
given MUST have the extension ".job" on it.  Otherwise, it will fail
to be enumerated from this tool, the task scheduler, or anywhere else.
In addition, that task will fail to run if ".job" is not the file
extension.  If the addition succeeds, no output is displayed.

    The task type that is created has a single trigger of type "ONCE"
and is limited to running within the next 24 hours.  Also, like AT,
these jobs will be deleted upon completion of their run.  For
creation of other types of triggers, please consult the documentation
for the TASK_TRIGGER structure.


SA.CPP

// ===========================================================================
// 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 1997 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================
//
//+--------------------------------------------------------------------------
//
// File:        sa.cpp
//
// Contents:    All code needed to build the simple job submission tool
//
// Purpose:     Demonstration of calls to methods of the following new
//              interfaces:
//                  1. ITaskScheduler
//                  2. ITask
//                  3. ITaskTrigger
//                  4. IEnumWorkItems
//
// Comments:    This application is similar in functionality to
//              the "AT" command provided with Windows NT.  However,
//              the AT command functions through the NetSchedule APIs,
//              as opposed to the COM interfaces demonstrated here.
//
//              The Task Scheduler replaces ATSVC.EXE and implements
//              those APIs as a restricted subset of the new ones
//              demonstrated here.
//
//              On Win9x systems, the Task Scheduler replaces
//              the System Agent, providing more flexible and reliable
//              scheduling options than previously available.
//              However, although the SAGE jobs are converted
//              to Task Scheduler work items, the actual
//              SAGE API has been abandoned and is not implemented.
//
//              The Task Scheduler APIs provide a greater degree of
//              freedom and robustness than those APIs.  This code
//              demonstrates a stripped version of AT rewritten to
//              use the new APIs.
//
// Additional:  The Task Scheduler service must be running.  It is easily
//              started by the command "net start schedule" if it is not
//              running.  It is also trivial to start it through
//              the service control manager APIs. Doing it in the code
//              directly makes a good enhancement to the program below.
//
//              Since there is not a service controller on Win9x,
//              the user may start the service by executing the
//              binary directly from the command line (mstask.exe).
//              Win9x users will see a system tray icon if the service
//              is running.
//
//              The service may also be started from the folder UI available
//              directly by opening "My Computer" and then opening the
//              "Scheduled Tasks" folder.  The start command appears on the
//              "Advanced" menu.
//
//              Sample code to start and stop the service, on both
//              Windows NT and Win9x based systems appears in the
//              documentation.
//
//              If the Task Scheduler service is not running, most
//              of this program will fail to execute.  If there are
//              tasks waiting to execute after login, the service
//              will have been started by default.
//
//---------------------------------------------------------------------------

#include <wchar.h>
#include <windows.h>
#include <mbctype.h>
#include <initguid.h>
#include <mstask.h>
#include <msterr.h>

//+--------------------------------------------------------------------------
// Global variables
//---------------------------------------------------------------------------

ITaskScheduler *g_pITaskScheduler = NULL;

//+--------------------------------------------------------------------------
// Function Prototypes
//---------------------------------------------------------------------------

HRESULT Init(void);
void Cleanup(void);
void DisplayHelp(LPWSTR);
HRESULT EnumerateJobs(void);
HRESULT DeleteJob(LPWSTR);
HRESULT AddJob(LPWSTR, LPSYSTEMTIME, LPWSTR, LPWSTR, LPWSTR);
HRESULT ConvertToSystemTime(LPWSTR, LPSYSTEMTIME);

//+--------------------------------------------------------------------------
//
// Function:        main
//
// Synopsis:        Entry point for code.  Parses command line,
//                  and calls appropriate subfunction as a result.
//
//  Arguments:      See DisplayHelp().  Uses argv, argc to get
//                  command line args.
//
//  Returns:        S_OK for success or the failure code for failure.
//
//---------------------------------------------------------------------------

LONG _CRTAPI1 main (int argc, char **argv)
{
    HRESULT hr = S_OK;
    UINT uCodePage;
    char *lpcszDeleteFlag = "/DELETE";
    WCHAR lpwszJobName[255], lpwszTime[32];
    WCHAR lpwszUserName[64], lpwszPassword[64], lpwszCommand[255];
    WCHAR lpwszProgName[255];
    SYSTEMTIME tTime;

    // For protection

    g_pITaskScheduler = NULL;

    // String conversion initialization

    uCodePage = _getmbcp();

    MultiByteToWideChar(uCodePage, 0, argv[0],
                        -1, lpwszProgName,
                        255);

    // Check number of command line arguments.
    //      1 argument      =   enumerate work items
    //      2 args          =   display help (don't init OLE)
    //      3 args          =   delete work item
    //      4, 5 or 6 args  =   add a work item
    //      all others      =   error, display help

    if ((argc == 2) || (argc > 6))
    {
        DisplayHelp(lpwszProgName);
        return hr;
    }

    // Attempt to initialize OLE and fill in the global g_pITaskScheduler

    hr = Init();
    if (FAILED(hr))
    {
        wprintf(L"Error: OLE initialization and instantiation failed\n");
        return hr;
    }

    switch(argc)
    {
        case 1:
            // User would like to enumerate work items

            hr = EnumerateJobs();
            break;

        case 3:
            if (! lstrcmpiA(argv[2], lpcszDeleteFlag))
            {
                // User would like to delete work item in argv[1]

                MultiByteToWideChar(uCodePage, 0, argv[1],
                                    -1, lpwszJobName,
                                    255);

                hr = DeleteJob(lpwszJobName);
            }
            else
            {
                // User has made an error in command line args

                wprintf(L"Error: Must be %s taskname.job /DELETE\n", lpwszProgName);
                wprintf(L"\n %s /?  for more help.\n", lpwszProgName);

                Cleanup();
                return E_FAIL;
            }
            break;

        case 4:
        case 5:
        case 6:
            // User would like to add a work item.  The following
            // argument cases apply:
            //      4 - User has specified work item name, but not given
            //          username or password.  Will prompt later.
            //      5 - User has specified work item name and username,
            //          but no password.  Will prompt later.
            //      6 - User has specified all information.

                uCodePage = _getmbcp();

                MultiByteToWideChar(uCodePage, 0, argv[1],
                                    -1, lpwszJobName,
                                    255);
                MultiByteToWideChar(uCodePage, 0, argv[2],
                                    -1, lpwszTime,
                                    32);

            hr = ConvertToSystemTime(lpwszTime, &tTime);
            if (FAILED(hr))
            {
                wprintf(L"Error: Conversion of command line time to system time failed.\n");
                Cleanup();
                return hr;
            }

            *lpwszUserName = L'\0';
            *lpwszPassword = L'\0';

            MultiByteToWideChar(uCodePage, 0, argv[3],
                                -1, lpwszCommand,
                                255);

            if (argc > 4)
            {

                MultiByteToWideChar(uCodePage, 0, argv[4],
                                    -1, lpwszUserName,
                                    64);
            }
            if (argc == 6)
            {

                MultiByteToWideChar(uCodePage, 0, argv[5],
                                    -1, lpwszPassword,
                                    64);
            }

            hr = AddJob(lpwszJobName, &tTime, lpwszCommand, lpwszUserName, lpwszPassword);

            break;

        default:
            hr = E_FAIL;
            DisplayHelp(lpwszProgName);
    }

    Cleanup();
    return hr;
}

//+--------------------------------------------------------------------------
//
// Function:        Init()
//
// Synopsis:        Called to initialize and instantiate a task
//                  scheduler object.
//
// Arguments:       none (void)
//
// Returns:         HRESULT indicating success or failure.  S_OK on success.
//
// Side effect:     Sets global pointer g_pITaskScheduler, for use in other
//                  functions.
//
//---------------------------------------------------------------------------

HRESULT Init()
{
    HRESULT hr = S_OK;

    // Bring in the library

    hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
        return hr;
    }

    // Create the pointer to Task Scheduler object
    // CLSID from the header file mstask.h

    hr = CoCreateInstance(
        CLSID_CTaskScheduler,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_ITaskScheduler,
        (void **) &g_pITaskScheduler);

    // Should we fail, unload the library

    if (FAILED(hr))
    {
        CoUninitialize();
    }

    return hr;
}

//+--------------------------------------------------------------------------
//
// Function:    Cleanup()
//
// Synopsis:    Called to clean up OLE, memory, etc. before termination
//
// Arguments:   none (void)
//
// Returns:     nothing (void)
//
// Side effect: Cancels the global pointer g_pITaskScheduler.
//
//---------------------------------------------------------------------------

void Cleanup()
{
    if (g_pITaskScheduler)
    {
        g_pITaskScheduler->Release();
        g_pITaskScheduler = NULL;
    }

    // Unload the library, now that our pointer is freed.

    CoUninitialize();
    return;
}

//+--------------------------------------------------------------------------
//
// Function:    DisplayHelp()
//
// Synopsis:    Prints out help and usage information
//
// Arguments:   lpwszProgName - a pointer to a WSTR containing argv[0]
//
// Returns:     nothing (void)
//
//---------------------------------------------------------------------------

void DisplayHelp(LPWSTR lpwszProgName)
{
    wprintf(L"%s -- a small program to replicate some\n", lpwszProgName);
    wprintf(L"\tfunctionality of the AT command, using the\n");
    wprintf(L"\tnew Task Scheduler interfaces.\n\n");

    wprintf(L"Usage:\n");
    wprintf(L"\t%s /?\n\t\t\t\t\t\t- Display this help\n",lpwszProgName);
    wprintf(L"\t%s\n\t\t\t\t\t\t- Show all work items\n",lpwszProgName);
    wprintf(L"\t%s TaskName.job /DELETE \n\t\t\t\t\t\t- delete work item\n",lpwszProgName);
    wprintf(L"\t%s TaskName.job Time Command [UserName [Password]]\n\t\t\t\t\t\t- submit a new work item\n\n",lpwszProgName);

    wprintf(L"TaskName.job is the name of the work item object on disk.\n");
    wprintf(L"The work item will appear in the Scheduled Tasks folder as TaskName\n");
    wprintf(L"but must be given the extension \".job\" for the service \n");
    wprintf(L"to recognize and run it.\n\n");

    wprintf(L"Task Time is in 24 hour format (such as 15:30) and is the\n");
    wprintf(L"next instance of this time within 24 hours.\n\n");

    wprintf(L"The Command should contain the name of the executable to run.\n");
    wprintf(L"Note that the pathname may NOT contain spaces.  If the\n");
    wprintf(L"program requires command line parameters, enclose the entire\n");
    wprintf(L"command string in quotation marks.\n\n");

    wprintf(L"Username and password are required to run the specified work item under Windows NT.\n");
    wprintf(L"Under Windows NT, if not specified, you will be prompted.\n");
    wprintf(L"Under Windows 95, both fields are ignored.  If not specified\n");
    wprintf(L"on the command line, you will not be prompted.\n\n");

    return;
}

//+--------------------------------------------------------------------------
//
// Function:    EnumerateJobs()
//
// Synopsis:    Goes through all scheduled work items on a system and
//              lists them, along with a short trigger string.
//
// Arguments:   none (void).  Requires g_pITaskScheduler.
//
// Returns:     HRESULT indicating success (S_OK) or other.
//
//---------------------------------------------------------------------------

HRESULT EnumerateJobs()
{
    HRESULT hr = S_OK, hrLoop = S_OK;
    IEnumWorkItems *pIEnumWorkItems;
    IUnknown *pIU;
    ITask *pITask;
    ULONG ulTasksToGet = 1, ulActualTasksRetrieved = 0;
    LPWSTR *rgpwszNames, pwszTrigger;
    WORD wTrigCount = 0;
    WORD wTemp;

    //
    // Get an enumeration pointer, using ITaskScheduler::Enum
    //

    hr = g_pITaskScheduler->Enum(&pIEnumWorkItems);
    if (FAILED(hr))
    {
        wprintf(L"Failed to get enumerator\n");
        return hr;
    }

    do
    {
        // Get a single work item, using IEnumWorkItems::Next

        hrLoop = pIEnumWorkItems->Next(ulTasksToGet, &rgpwszNames,
                                       &ulActualTasksRetrieved);
        if (hrLoop == S_FALSE)
        {
            // There are no more waiting tasks to look at
            break;
        }

        // Attach to the work item, using ITaskScheduler::Activate

        hr = g_pITaskScheduler->Activate(rgpwszNames[0], IID_ITask, &pIU);
        if (FAILED(hr))
        {
            wprintf(L"Error: Activate Failed\n",hr);
            break;
        }

        // QI pIU for pITask

        hr = pIU->QueryInterface(IID_ITask, (void **) &pITask);
        pIU->Release();
        pIU = NULL;
        if (FAILED(hr))
        {
            wprintf(L"Error: QI for ITask failed in Activate.\n");
            break;
        }

        // Display task name

        wprintf(L"Task: %s\n",rgpwszNames[0]);

        // Use ITask::GetTriggerCount to get count of triggers

        hr = pITask->GetTriggerCount(&wTrigCount);
        if (FAILED(hr))
        {
            wprintf(L"Error: Failed to count triggers\n");
            pITask->Release();
            break;
        }

        for (wTemp = 0; wTemp < wTrigCount; wTemp++)
        {
            // Dump Triggers using ITask::GetTriggerString

            hr = pITask->GetTriggerString(wTemp, &pwszTrigger);
            if (FAILED(hr))
            {
                wprintf(L"Error: Failed to get trigger string\n");
                pITask->Release();
                break;
            }

            wprintf(L"\tTrigger: %s\n",pwszTrigger);

            // Clean up the memory we were allocated for trig string

            CoTaskMemFree(pwszTrigger);
        }

        // Clean up each element in the array of job names, then
        // clean up the final array.

        CoTaskMemFree(rgpwszNames[0]);
        CoTaskMemFree(rgpwszNames);

        // Free the ITask pointer

        pITask->Release();

    } while(1);

    // Release the enumeration pointer

    pITask = NULL;

    pIEnumWorkItems->Release();
    pIEnumWorkItems = NULL;

    return hr;
}

//+--------------------------------------------------------------------------
//
// Function:        DeleteJob()
//
// Synopsis:        Deletes a work item from the Scheduled Tasks folder.
//
// Arguments:       lpwszJobName - the name of the work item to delete
//
// Returns:         HRESULT indicating success (S_OK) or failure.
//
//---------------------------------------------------------------------------

HRESULT DeleteJob(LPWSTR lpwszJobName)
{
    HRESULT hr = S_OK;

    hr = g_pITaskScheduler->Delete(lpwszJobName);

    return hr;
}

//+--------------------------------------------------------------------------
//
// Function:        AddJob()
//
// Synopsis:        Adds a new work item to the Scheduled Tasks folder.
//
// Arguments:       lpwszJobName - name of the task file
//                  lptTime      - pointer to SYSTEMTIME struct containing
//                                      the time the job should run.
//                  lpwszCommand - name of application (command) to run.
//                  lpwszUserName- user name to run job under
//                  lpwszPassword- password for that user
//
// Returns:         HRESULT indicating success (S_OK) or failure.
//
// Notes:           The password or BOTH the username and password
//                  may be passed in as '\0' strings, in which case
//                  this function will prompt on stdout for password
//                  and username
//
//---------------------------------------------------------------------------

HRESULT AddJob(LPWSTR lpwszJobName, LPSYSTEMTIME lptTime,
                LPWSTR lpwszCommand, LPWSTR lpwszUserName,
                LPWSTR lpwszPassword)
{
    HRESULT hr = S_OK;
    IUnknown *pIU;
    IPersistFile *pIPF;
    ITask *pITask;
    ITaskTrigger *pITaskTrig;
    DWORD dwTaskFlags, dwTrigFlags;
    WORD wTrigNumber;
    TASK_TRIGGER TaskTrig;
    WCHAR lpwszAppName[255];
    int i;

    // Add the task.  Most likely failure is that work item already exists.
    // Uses ITaskScheduler::NewWorkItem

    hr = g_pITaskScheduler->NewWorkItem(lpwszJobName,
                                        CLSID_CTask,
                                        IID_ITask,
                                        &pIU);
    if (FAILED(hr))
    {
        wprintf(L"Error:  Create New Task failure\n");
        return hr;
    }

    // We now have an IUnknown pointer.  This is queried for an ITask
    // pointer on the work item we just added.

    hr = pIU->QueryInterface(IID_ITask, (void **) &pITask);
    if (FAILED(hr))
    {
        wprintf(L"Error: IUnknown failed to yield ITask\n");
        pIU->Release();
        return hr;
    }

    // Cleanup pIUnknown, as we are done with it.

    pIU->Release();
    pIU = NULL;

    //
    // We need to see if we support security, and we
    // do this by calling ITask::SetAccountInformation
    // and checking if the failure code is SCHED_E_NO_SECURITY_SERVICES
    //

    hr = pITask->SetAccountInformation(lpwszUserName, lpwszPassword);
    if (hr != SCHED_E_NO_SECURITY_SERVICES)
    {

        // Check to see if username is null

        if (*lpwszPassword == L'\0')
        {
            // If password was null, chance username is, too.

            if (*lpwszUserName == L'\0')
            {
                wprintf(L"Enter username to run job %s as: ",lpwszJobName);
                wscanf(L"%s",lpwszUserName);
                wprintf(L"\n");
            }

            wprintf(L"Enter password to user %s: ",lpwszUserName);
            wscanf(L"%s",lpwszPassword);
            wprintf(L"\n");
        }
    }

    // Set the account information using ITask::SetAccountInformation
    // This fails for Win9x, but we ignore the failure.

    hr = pITask->SetAccountInformation(lpwszUserName, lpwszPassword);
    if ((FAILED(hr)) && (hr != SCHED_E_NO_SECURITY_SERVICES))
    {
        wprintf(L"Error: Failed to set credentials on task object %x\n",hr);
        pITask->Release();
        return hr;
    }

    // Extract parameters from the application name
    // Note that there might not be any parameters at all.
    // We butcher a path like C:\Program Files\foo.exe, because
    // we break on space, but this is ok, because user could say
    // C:\progra~1\foo.exe instead.

    i = 0;
    while ((*lpwszCommand != L' ') && (*lpwszCommand != '\0'))
    {
        lpwszAppName[i] = *lpwszCommand;
        i++;
        lpwszCommand++;
    }
    lpwszAppName[i] = L'\0';
    if (*lpwszCommand == L' ')
    {
        lpwszCommand++;
    }

    // Set command name with ITask::SetApplicationName

    hr = pITask->SetApplicationName(lpwszAppName);
    if (FAILED(hr))
    {
        wprintf(L"Error: Failed to set command name (with parms)\n");
        pITask->Release();
        return hr;
    }

    // Set task parameters with ITask::SetParameters

    hr = pITask->SetParameters(lpwszCommand);
    if (FAILED(hr))
    {
        wprintf(L"Error: Failed to set parameters\n");
        pITask->Release();
        return hr;
    }

    // Set the comment, so we know how this job go there
    // Uses ITask::SetComment

    hr = pITask->SetComment(L"This scheduled task created by command line SDK sample tool");
    if (FAILED(hr))
    {
        wprintf(L"Error: Task comment could not be set\n");
        pITask->Release();
        return hr;
    }

    // Set the flags on the task object
    // Use ITask::SetFlags

    dwTaskFlags = TASK_FLAG_DELETE_WHEN_DONE;
    hr = pITask->SetFlags(dwTaskFlags);
    if (FAILED(hr))
    {
        wprintf(L"Error: Could not set task flags\n");
        pITask->Release();
        return hr;
    }

    // Now, create a trigger to run the task at our specified time.
    // Uses ITask::CreateTrigger()

    hr = pITask->CreateTrigger(&wTrigNumber, &pITaskTrig);
    if (FAILED(hr))
    {
        wprintf(L"Error: Could not create a new trigger\n");
        pITask->Release();
        return hr;
    }

    // Now, fill in the trigger as necessary.

    dwTrigFlags = 0;

    TaskTrig.cbTriggerSize = sizeof(TASK_TRIGGER);
    TaskTrig.Reserved1 = 0;
    TaskTrig.wBeginYear = lptTime->wYear;
    TaskTrig.wBeginMonth = lptTime->wMonth;
    TaskTrig.wBeginDay = lptTime->wDay;
    TaskTrig.wEndYear = 0;
    TaskTrig.wEndMonth = 0;
    TaskTrig.wEndDay = 0;
    TaskTrig.wStartHour = lptTime->wHour;
    TaskTrig.wStartMinute = lptTime->wMinute;
    TaskTrig.MinutesDuration = 0;
    TaskTrig.MinutesInterval = 0;
    TaskTrig.rgFlags = dwTrigFlags;
    TaskTrig.TriggerType = TASK_TIME_TRIGGER_ONCE;
    TaskTrig.wRandomMinutesInterval = 0;
    TaskTrig.Reserved2 = 0;

    // Add this trigger to the task using ITaskTrigger::SetTrigger

    hr = pITaskTrig->SetTrigger(&TaskTrig);
    if (FAILED(hr))
    {
        wprintf(L"Error: Failed to set trigger to desired values\n");
        pITaskTrig->Release();
        pITask->Release();
        return hr;
    }

    // Make the changes permanent
    // Requires using IPersistFile::Save()

    hr = pITask->QueryInterface(IID_IPersistFile, (void **) &pIPF);
    if (FAILED(hr))
    {
        wprintf(L"Error: Could not get IPersistFile on task\n");
        pITaskTrig->Release();
        pITask->Release();
        return hr;
    }

    hr = pIPF->Save(NULL, FALSE);
    if (FAILED(hr))
    {
        wprintf(L"Error: Could not save object\n");
        pITaskTrig->Release();
        pITask->Release();
        pIPF->Release();
        return hr;
    }

    // We no longer need ITask

    pITask->Release();
    pITask = NULL;

    // Done with ITaskTrigger pointer

    pITaskTrig->Release();
    pITaskTrig = NULL;

    // Done with IPersistFile

    pIPF->Release();
    pIPF = NULL;

    return hr;
}

//+--------------------------------------------------------------------------
//
// Function:        ConvertToSystemTime()
//
// Synopsis:        Given a text string from the command line of the form
//                  XX:YY where XX = 0 - 23, and YY = 0 - 59,
//                  obtain the next instance in 24 hours of said time
//                  and return this time in a SYSTEMTIME structure
//                  so that it can be used to set triggers
//
// Arguments:       lpwszTime    -   The text string of time
//                  lptTime     -   The SYSTEMTIME pointer
//
// Returns:         HRESULT of success (S_OK) or failure
//
//---------------------------------------------------------------------------

HRESULT ConvertToSystemTime(LPWSTR lpwszTime, LPSYSTEMTIME lptTime)
{
    WORD wMin = 0, wHr = 0, wMaxFeb = 28;
    WCHAR szTemp[3], *szEnd1, *szEnd2;
    SYSTEMTIME tNow;
    int i, j;

    // Extract minutes and hour from string

    if ((wcslen(lpwszTime) > 5) || (wcslen(lpwszTime) < 4))
    {
        wprintf(L"Error: Invalid time string given\n");
        return E_FAIL;
    }

    // Hours first.

    i = 0;
    while (*lpwszTime != ':')
    {
        szTemp[i] = *lpwszTime;
        i++;
        if (i > 2)
        {
            // User gave a bad time string
            wprintf(L"Error: Bad hours in time string\n");
            return E_FAIL;
        }
        lpwszTime++;
    }
    szTemp[i] = L'\0';
    lpwszTime++;

    // Convert to value

    wHr = (WORD) wcstoul(szTemp, &szEnd1, 10);

    // Now do minutes

    i = wcslen(lpwszTime);
    j = 0;
    while (i)
    {
        szTemp[j] = *lpwszTime;
        j++;
        i--;
        lpwszTime++;
    }
    szTemp[j] = L'\0';

    wMin = (WORD) wcstoul(szTemp, &szEnd2, 10);

    // Now figure out if we are running today or tomorrow

    GetLocalTime(&tNow);
    if ((wHr < tNow.wHour) ||
       ((wHr == tNow.wHour) && (wMin < tNow.wMinute)))
    {
        // Job is tomorrow - we must figure out what tomorrow is

        switch(tNow.wMonth)
        {
            case 4:
            case 6:
            case 9:
            case 11:
                // Thirty day months

                if ((tNow.wDay + 1) > 30)
                {
                    tNow.wDay = 1;
                    tNow.wMonth++;
                }
                else
                {
                    tNow.wDay++;
                }
                break;

            case 2:
                // February
                // Leap Year?

                if ((tNow.wYear % 4) == 0)
                {
                    wMaxFeb = 29;
                }
                if (((tNow.wYear % 100) == 0) &&
                    ((tNow.wYear % 400) != 0))
                {
                    wMaxFeb = 28;
                }

                if ((tNow.wDay + 1) > wMaxFeb)
                {
                    tNow.wDay = 1;
                    tNow.wMonth++;
                }
                else
                {
                    tNow.wDay++;
                }
                break;

            default:
                // 31 day months.  Handle Dec. later

                if ((tNow.wDay + 1) > 31)
                {
                    tNow.wDay = 1;
                    tNow.wMonth++;
                }
                else
                {
                    tNow.wDay++;
                }

                if (tNow.wMonth > 12)
                {
                    tNow.wMonth = 1;
                    tNow.wYear++;
                }
        }
    }

    if ((wMin < 0) || (wMin > 59))
    {
        wprintf(L"Error: Invalid minutes (need 0 - 59)\n");
        return E_FAIL;
    }
    else
    {
        tNow.wMinute = wMin;
    }
    if ((wHr < 0) || (wHr > 23))
    {
        wprintf(L"Error: Invalid hours (need 0 - 23)\n");
        return E_FAIL;
    }
    else
    {
        tNow.wHour = wHr;
    }

    lptTime->wHour = tNow.wHour;
    lptTime->wMinute = tNow.wMinute;
    lptTime->wYear = tNow.wYear;
    lptTime->wMonth = tNow.wMonth;
    lptTime->wDay = tNow.wDay;
    lptTime->wDayOfWeek = 0;
    lptTime->wSecond = 0;
    lptTime->wMilliseconds = 0;
    return S_OK;
}


README.TXT

THIS IS A VERY EARLY PRE-RELEASE OF WINDOWS NT 5.0, THE MIGRATION SAMPLE CODE AND DOCUMENTATION

Known issues with sample code
*****************************
Migrated screen saver may not work properly if user logs in using a non-administrator account on Windows NT 5.0 after the migration.

Blank screen savers may not work properly until a non-blank screen saver is used.
VC5 users can open migrate.dsp to build the project.

Other Notes
*****************************
Read sysdiff.doc before running sysdiff.exe or sysdiffa.exe

You may want to change which C runtime is used if you do not want to use the static multithreaded runtime.

A .mak file (migrate.mak) for non-VC5 users is exported

This build joins ProductID to the Object Section to generate incompatibility messages.  This is different that what is described on page 19 of migext.doc.  Migext.doc describes the correct behavior.


MIGINF.C

#include "poolmem.h"
#include "miginf.h"
#include <setupapi.h>

#define MIGRATEINF              ".\\migrate.inf"
#define INITIALBUFFERSIZE       1024
#define MIGINF_NOCREATE         FALSE
#define MIGINF_CREATE           TRUE

typedef struct tagMIGOBJECT MIGOBJECT, *PMIGOBJECT;
struct tagMIGOBJECT {

    PSTR        Key;
    PSTR        Value;

    PMIGOBJECT  Next;
};

typedef struct tagMIGSECTION MIGSECTION, * PMIGSECTION;
struct tagMIGSECTION {

    PSTR        Name;
    PMIGOBJECT  Items;

    PMIGSECTION Next;
};

PMIGSECTION g_MigrationInf;
POOLHANDLE  g_Pool = NULL;

static
PCSTR
pGetTypeAsString (
    IN MIGTYPE Type
    )
{
    //
    // Note: Strings must be in the same order as the
    // corresponding types in the MIGTYPE enumeration above.
    //
    static PCHAR typeStrings[] = {
            "FIRST - Invalid",
            "File",
            "Path",
            "Registry",
            "Message - Invalid",
            "LAST - Invalid"
        };

    assert(Type > MIG_FIRSTTYPE && Type < MIG_LASTTYPE);

    return typeStrings[Type];
}

static
PMIGSECTION
pFindSection (
    IN PCSTR SectionString,
    IN BOOL  CreateIfNotExist
    )
{
    PMIGSECTION rSection;

    //
    // We assume that SectionString is not null.
    //
    assert(SectionString);

    rSection = g_MigrationInf;

    while (rSection && (_mbsicmp(rSection -> Name,SectionString) != 0)) {

        //
        // Continue looking.
        //
        rSection = rSection -> Next;
    }

    if (!rSection && CreateIfNotExist) {
        //
        // No section was found matching this name. Make a new section and add it
        // to the list.
        //
        rSection = PoolMemGetMemory(g_Pool,sizeof(MIGSECTION));
        if (rSection) {

            ZeroMemory(rSection,sizeof(MIGSECTION));
            rSection -> Name  = PoolMemDuplicateStringA(g_Pool,SectionString);
            rSection -> Next  = g_MigrationInf;
            g_MigrationInf    = rSection;

            if (!rSection -> Name) {
                //
                // Something went wrong when we tried to duplicate the SectionString.
                // NULL out the rSection so that the caller doesn't get back a
                // malformed section object.
                //
                rSection = NULL;
            }
        }
    }

    return rSection;
}

static
BOOL
pPathIsInPath(
    IN PCSTR    SubPath,
    IN PCSTR    ParentPath
    )
{
    DWORD parentLength;
    BOOL  rInPath;

    //
    // This function assumes both parameters are non-NULL.
    //
    assert(SubPath);
    assert(ParentPath);

    parentLength = _mbslen(ParentPath);

    //
    // A path is considered "in" another path if the path is in the ParentPath
    // or a subdirectory of it.
    //
    rInPath = !_mbsnicmp(SubPath,ParentPath,parentLength);

    if (rInPath) {
        rInPath = SubPath[parentLength] == 0 || SubPath[parentLength] == '\\';
    }

    return rInPath;

}

static
DWORD
pGetMbsSize (
    IN  LPCSTR  String
    )
{
    DWORD rLength;

    rLength = (DWORD) _mbschr(String,0) - (DWORD) String + 1;

    return rLength;

}

static
LPSTR
pEscapeString (
    IN  MIGTYPE Type,
    OUT LPSTR   EscapedString,
    IN  LPCSTR  String
    )

{
    LPSTR   stringStart;
    static  CHAR exclusions[] = "[]~,;%\"";
    INT     currentChar;

    //
    // We assume that all parameters are valid.
    //
    assert(EscapedString && String);

    stringStart = EscapedString;

    while (*String)  {
        currentChar = _mbsnextc (String);

        if (Type == MIG_REGKEY) {

            //
            // Registry keys require more complex escaping than do normal INF strings.
            //
            if (!_ismbcprint (currentChar) || _mbschr (exclusions, currentChar)) {

                //
                // Escape unprintable or excluded character
                //
                wsprintfA (EscapedString, "~%X~", currentChar);
                EscapedString = _mbschr (EscapedString, 0);
                String = _mbsinc (String);
            }
            else {
                //
                // Copy multibyte character
                //
                if (isleadbyte (*String)) {
                    *EscapedString = *String;
                    EscapedString++;
                    String++;
                }

                *EscapedString = *String;
                EscapedString++;
                String++;
            }
        }
        else {

            //
            // Escaping is pretty simple for non-registry keys. All we do is double up
            // quotes and percents.
            //
            if (*String == '\"' || *String == '%') {

                *EscapedString = *String;
                EscapedString++;
            }

            //
            // Copy multibyte character
            //
            if (isleadbyte (*String)) {
                *EscapedString = *String;
                EscapedString++;
                String++;
            }

            *EscapedString = *String;
            EscapedString++;
            String++;
        }
    }

    //
    // Ensure that returned string is NULL terminated.
    //
    *EscapedString = 0;

    return stringStart;
}

static
PSTR
pGetValueString (
    IN MIGTYPE    ObjectType,
    IN LPCSTR     StringOne,
    IN LPCSTR     StringTwo
    )
{
    static PSTR     buffer;
    static DWORD    bufferSize;
    DWORD           maxLength;
    PSTR            bufferEnd;

    //
    // This function assumes that StringOne exists.
    //
    assert(StringOne);

    if (ObjectType == MIG_REGKEY) {
        //
        // Size: size of both strings, plus the size of the quotes, plus the size of the brackets
        // for the value, * 6. This is the maximum size one of these could grow to, if every
        // character had to be escaped out.
        //
        maxLength = (pGetMbsSize(StringOne) + (StringTwo ? pGetMbsSize(StringTwo) + 2 : 0)) * 6 + 2;
    }
    else {
        //
        // Size: size of the string * 2 (The max size if every char was a '%' or '"' plus the quotes.
        //
        maxLength = pGetMbsSize(StringOne) * 2 + 2;
    }

    if (maxLength > bufferSize) {

        //
        // Initialize our buffer, or create a larger one.
        //
        bufferSize = (maxLength > INITIALBUFFERSIZE) ? maxLength : INITIALBUFFERSIZE;
        buffer = PoolMemCreateStringA(g_Pool,bufferSize);
    }

    if (buffer != NULL) {

        //
        // Insert initial quote.
        //
        *buffer = '"';

        //
        // Massage the string to ensure it is a valid INF file string.
        //
        pEscapeString(ObjectType,_mbsinc(buffer),StringOne);

        //
        // If this is a REGISTRY entry, then we also need to add the value part of the string,
        // if one was specified (In StringTwo)
        //

        if (ObjectType == MIG_REGKEY && StringTwo) {

            //
            // Add the opening bracket.
            //
            bufferEnd = _mbschr(buffer,0);
            *bufferEnd = '[';

            //
            // Add the value string in, again making sure the string is valid for an INF file.
            //
            pEscapeString(ObjectType,_mbsinc(bufferEnd),StringTwo);

            //
            // Now, add the closing braket.
            //
            bufferEnd = _mbschr(buffer,0);
            *bufferEnd = ']';

            //
            // Terminate the string.
            //
            bufferEnd = _mbsinc(bufferEnd);
            *bufferEnd = 0;
        }

        //
        // Add the final quote.
        //
        bufferEnd = _mbschr(buffer,0);
        *bufferEnd = '"';
        bufferEnd = _mbsinc(bufferEnd);
        *bufferEnd = 0;
    }

    return buffer;
}

static
BOOL
pCreateMigObject (
    IN MIGTYPE          ObjectType,
    IN PCSTR            ParamOne,
    IN PCSTR            ParamTwo,
    IN PMIGSECTION      Section
    )
{
    BOOL            rSuccess;
    PMIGOBJECT      newObject = NULL;

    //
    // pCreateMigObject uses a set of hueristics to correctly assemble an object.
    // These hueristics are based on the ObjectType and the contents of ParamTwo.
    //
    // ObjectType       ParamTwo      Key                   Value
    // -------------------------------------------------------------------------
    // MIG_REGKEY       <any>         ParamOne[ParamTwo]    Registry
    // <other>          NULL          ParamOne              <ObjectType As String>
    // <other>          non-NULL      ParamOne              ParamTwo
    //
    //

    if (Section) {

        //
        // First, create an object...
        //
        newObject = PoolMemGetMemory(g_Pool,sizeof(MIGOBJECT));

        if (newObject) {

            if (ObjectType == MIG_REGKEY) {

                newObject -> Key =
                    PoolMemDuplicateStringA(g_Pool,pGetValueString(ObjectType,ParamOne,ParamTwo));

                newObject -> Value = PoolMemDuplicateStringA(g_Pool,pGetTypeAsString(ObjectType));
            }
            else {

                newObject -> Key =
                    PoolMemDuplicateStringA(g_Pool,pGetValueString(ObjectType,ParamOne,NULL));

                if (ParamTwo) {
                    newObject -> Value =
                        PoolMemDuplicateStringA(g_Pool,pGetValueString(ObjectType,ParamTwo,NULL));
                }
                else {

                    newObject -> Value =
                        PoolMemDuplicateStringA(g_Pool,pGetTypeAsString(ObjectType));
                }
            }
        }
    }

    if (newObject && newObject -> Key && newObject -> Value) {

        //
        // The object has been successfully created. Link it into the section.
        //
        newObject -> Next = Section -> Items;
        Section -> Items = newObject;
        rSuccess = TRUE;
    }
    else {
        rSuccess = FALSE;
    }

    return newObject && newObject -> Key && newObject -> Value;
}

static
BOOL
pWriteInfSectionToDisk (
    IN PMIGSECTION Section
    )
{
    PMIGOBJECT curObject;
    BOOL       rSuccess = TRUE;

    if (Section) {

        curObject = Section -> Items;

        while (curObject && rSuccess) {

            if (Section -> Name && curObject -> Key && curObject -> Value) {

                rSuccess = WritePrivateProfileString(
                    Section   -> Name,
                    curObject -> Key,
                    curObject -> Value,
                    MIGRATEINF
                    );
            }

            curObject = curObject -> Next;
        }
    }
    else {
        rSuccess = FALSE;
    }

    return rSuccess;
}

static
BOOL
pBuildListFromSection (
    IN PCSTR    SectionString
    )
{
    HINF            infHandle;
    PMIGSECTION     section;
    PMIGOBJECT      currentObject;
    INFCONTEXT      ic;
    DWORD           size;
    BOOL            rSuccess = TRUE;

    //
    // This function assumes that Section is non-NULL.
    //
    assert(SectionString);

    currentObject = NULL;

    //
    // First find the section specified.
    //
    section = pFindSection(SectionString,MIGINF_CREATE);

    if (section) {

        infHandle = SetupOpenInfFileA(MIGRATEINF,NULL,INF_STYLE_WIN4,NULL);

        if (infHandle != INVALID_HANDLE_VALUE) {

            if (SetupFindFirstLine(infHandle,SectionString,NULL,&ic)) {

                do {

                    //
                    // Create the object.
                    //
                    currentObject = (PMIGOBJECT) PoolMemGetMemory(g_Pool,sizeof(MIGOBJECT));

                    if (!currentObject) {
                        rSuccess = FALSE;
                        break;
                    }

                    //
                    // Get the size of the string.
                    //
                    if (!SetupGetLineTextA(&ic,NULL,NULL,NULL,NULL,0,&size)) {
                        rSuccess = FALSE;
                        break;
                    }

                    //
                    // Create a string large enough.
                    //
                    currentObject -> Key = PoolMemCreateStringA(g_Pool,size);

                    if (!currentObject -> Key) {
                        rSuccess = FALSE;
                        break;
                    }

                    //
                    // Get the string.
                    //
                    if (!SetupGetLineTextA(&ic,NULL,NULL,NULL,currentObject -> Key,size,NULL)) {
                        rSuccess = FALSE;
                        break;
                    }

                    //
                    // Successfully retrieved the line.
                    //
                    currentObject -> Value  = (PSTR) pGetTypeAsString(MIG_FILE);
                    currentObject -> Next   = section -> Items;
                    section -> Items        = currentObject;

                } while(SetupFindNextLine(&ic,&ic));

            }

            SetupCloseInfFile(infHandle);
        }
    }
    else {
        rSuccess = FALSE;
    }

    return rSuccess;
}

BOOL
MigInf_Initialize(
    VOID
    )
{

    //
    // First, initialize our pool and Zero out the structure.
    //
    g_Pool = PoolMemInitPool();

    if (g_Pool) {

        //
        // Now, read in the migration paths and excluded paths sections.
        //
        if (!pBuildListFromSection(SECTION_MIGRATIONPATHS) ||
            !pBuildListFromSection(SECTION_EXCLUDEDPATHS)) {
            //
            // Something went wrong (i.e. out of memory. Destroy and NULL our pool.
            //
            PoolMemDestroyPool(g_Pool);
            g_Pool = NULL;
        }
    }

    //
    // If our memory pool initialized successfully, return TRUE.
    //
    return (g_Pool != NULL);

}

VOID
MigInf_CleanUp (
    VOID
    )
{
    //
    // Only thing we need to do is clean out pool mem. We'll NULL out the list header to make
    // sure it isn't usable.
    //
    if (g_Pool) {
        PoolMemDestroyPool(g_Pool);
        g_Pool = NULL;
    }

    g_MigrationInf = NULL;

}

BOOL
MigInf_AddObject (
    IN MIGTYPE  ObjectType,
    IN PCSTR    SectionString,
    IN PCSTR    ParamOne,
    IN PCSTR    ParamTwo
    )
{

    return pCreateMigObject(
        ObjectType,
        ParamOne,
        ParamTwo,
        pFindSection(SectionString,MIGINF_CREATE)
        );
}

BOOL

MigInf_FirstInSection(
    IN PCSTR SectionName,
    OUT PMIGINFSECTIONENUM Enum
    )
{
    PMIGSECTION section;

    //
    // We assume that Enum is valid.
    //
    assert(Enum);

    section = pFindSection(SectionName,MIGINF_NOCREATE);

    if (section) {
        Enum -> EnumKey = (PVOID) section -> Items;
    }

    return MigInf_NextInSection(Enum);
}

BOOL
MigInf_NextInSection(
    IN OUT PMIGINFSECTIONENUM Enum
    )
{

    BOOL            rSuccess = FALSE;

    //
    // We assume that the Enum is valid.
    //
    assert(Enum);

    if (Enum -> EnumKey) {

        Enum -> Key     = ((PMIGOBJECT) (Enum -> EnumKey)) -> Key;
        Enum -> Value   = ((PMIGOBJECT) (Enum -> EnumKey)) -> Value;
        Enum -> EnumKey = ((PVOID) ((PMIGOBJECT) (Enum -> EnumKey)) -> Next);
        rSuccess = TRUE;
    }

    return rSuccess;
}

BOOL
MigInf_WriteInfToDisk (
    VOID
    )
{

    BOOL        rSuccess = TRUE;
    PMIGSECTION curSection;

    //
    // Simply loop through all of the sections, writing each of them to disk.
    // As long as WriteSectionToDisk works, we work.
    //
    curSection = g_MigrationInf;

    while (curSection && rSuccess) {

        //
        // We skip the [Excluded Paths] and [Migration Paths] sections.
        //
        if (_mbsicmp(curSection -> Name,SECTION_EXCLUDEDPATHS) &&
            _mbsicmp(curSection -> Name,SECTION_MIGRATIONPATHS)) {

            rSuccess = pWriteInfSectionToDisk(curSection);
        }

        curSection = curSection -> Next;

    }

    return rSuccess;
}

BOOL
MigInf_PathIsExcluded (
    IN PCSTR    Path
    )
{
    PMIGOBJECT  curExcluded;
    PMIGSECTION section;
    BOOL        rIsExcluded = FALSE;

    //
    // We assume Path is valid.
    //
    assert(Path);

    section = pFindSection(SECTION_EXCLUDEDPATHS,MIGINF_NOCREATE);

    if (section) {

        curExcluded = section -> Items;

        while (curExcluded && !rIsExcluded) {

            rIsExcluded = pPathIsInPath(Path,curExcluded -> Key);
            curExcluded = curExcluded -> Next;
        }
    }

    return rIsExcluded;
}

BOOL
MigInf_UseSpace (
    IN PCSTR DriveRoot,
    IN LONGLONG Space
    )
{

    BOOL rSuccess;
    PMIGSECTION section;
    static CHAR spaceString[MAX_PATH];

    section = pFindSection(SECTION_DISKSPACEUSED,MIGINF_CREATE);

    if (section) {

        sprintf(spaceString,"%I64u",Space);
        rSuccess = pCreateMigObject (MIG_FILE,DriveRoot,spaceString,section);
    }
    else {
        rSuccess = FALSE;
    }

    return rSuccess;
}


MIGINF.H

#ifndef MIGINF_H
#define MIGINF_H

#define SECTION_MIGRATIONPATHS  "Migration Paths"
#define SECTION_EXCLUDEDPATHS   "Excluded Paths"
#define SECTION_HANDLED         "Handled"
#define SECTION_MOVED           "Moved"
#define SECTION_INCOMPATIBLE    "Incompatible Messages"
#define SECTION_DISKSPACEUSED   "NT Disk Space Requirements"

typedef enum {

    MIG_FIRSTTYPE,
    MIG_FILE,
    MIG_PATH,
    MIG_REGKEY,
    MIG_MESSAGE,
    MIG_LASTTYPE

} MIGTYPE, *PMIGTYPE;

typedef struct tagMIGINFSECTIONENUM {

    PCSTR        Key;
    PCSTR        Value;
    PVOID        EnumKey;            // Internal.

} MIGINFSECTIONENUM, * PMIGINFSECTIONENUM;

BOOL MigInf_Initialize (VOID);
VOID MigInf_CleanUp (VOID);
BOOL MigInf_PathIsExcluded (IN PCSTR Path);
BOOL MigInf_FirstInSection(IN PCSTR SectionName, OUT PMIGINFSECTIONENUM Enum);
BOOL MigInf_NextInSection(IN OUT PMIGINFSECTIONENUM Enum);
BOOL MigInf_AddObject (IN MIGTYPE ObjectType,IN PCSTR SectionString,IN PCSTR ParamOne,IN PCSTR ParamTwo);
BOOL MigInf_WriteInfToDisk (VOID);
BOOL MigInf_UseSpace (IN PCSTR DriveRoot,IN LONGLONG Space);

//
// Macros for common miginf actions.
//

//
// Adding Objects.
//
#define MigInf_AddHandledFile(file)                      MigInf_AddObject(MIGTYPE_FILE,SECTION_HANDLED,(file),NULL)
#define MigInf_AddHandledDirectory(directory)            MigInf_AddObject(MIGTYPE_PATH,SECTION_HANDLED,(directory),NULL)
#define MigInf_AddHandledRegistry(key,value)             MigInf_AddObject(MIGTYPE_REGKEY,SECTION_HANDLED,(key),(value))

#define MigInf_AddMovedFile(from,to)                     MigInf_AddObject(MIGTYPE_FILE,SECTION_MOVED,(from),(to))
#define MigInf_AddMovedDirectory(from,to)                MigInf_AddObject(MIGTYPE_PATH,SECTION_MOVED,(from),(to))

#define MigInf_AddMessage(msgSection,msg)                MigInf_AddObject(MIGTYPE_MESSAGE,SECTION_INCOMPATIBLE,(msgSection),(msg))

#define MigInf_AddMessageFile(msgSection,file)           MigInf_AddObject(MIGTYPE_FILE,(msgSection),(file),NULL)
#define MigInf_AddMessageDirectory(msgSection,directory) MigInf_AddObject(MIGTYPE_PATH,(msgSection,(directory),NULL)
#define MigInf_AddMessageRegistry(msgSection,key,value)  MigInf_AddObject(MIGTYPE_REGKEY,(msgSection),(key),(value))

//
// Enumerating Sections
//
#define MigInf_GetFirstMigrationPath(Enum)               MigInf_FirstInSection(SECTION_MIGRATIONPATHS,(Enum))
#define MigInf_GetFirstExcludedPath(Enum)                MigInf_FirstInSection(SECTION_EXCLUDEDPATHS,(Enum))

#endif


POOLMEM.C

#include "poolmem.h"

// Tree Memory Allocation structure.

typedef struct _POOLMEMORYBLOCK POOLMEMORYBLOCK, *PPOOLMEMORYBLOCK;

struct _POOLMEMORYBLOCK {
    DWORD                 Index;
    DWORD                 Size;
    PPOOLMEMORYBLOCK      NextBlock;
    PPOOLMEMORYBLOCK      PrevBlock;
    PBYTE                 RawMemory;
};

typedef struct _POOLHEADER {
    PPOOLMEMORYBLOCK PoolHead;
    HANDLE           Heap;
} POOLHEADER, *PPOOLHEADER;

BOOL
PoolMemAddMemory (
    IN  POOLHANDLE  Handle,
    IN  DWORD       Size
    )
{
    PBYTE               allocedMemory;
    PPOOLMEMORYBLOCK    newBlock;
    PPOOLHEADER         poolHeader = (PPOOLHEADER) Handle;
    DWORD               sizeNeeded;

    assert(poolHeader != NULL);

    //
    // Determine size needed and attempt to allocate memory.
    //
    if (Size + sizeof(POOLMEMORYBLOCK) > POOLMEMORYBLOCKSIZE) {
        sizeNeeded = Size + sizeof(POOLMEMORYBLOCK);
    }
    else {
        sizeNeeded = POOLMEMORYBLOCKSIZE;
    }

    allocedMemory = HeapAlloc(poolHeader -> Heap,0,sizeNeeded);

    if (allocedMemory) {

        //
        // Use the beginning of the alloc'ed block as the poolblock structure.
        //
        newBlock                = (PPOOLMEMORYBLOCK) allocedMemory;
        newBlock -> Size        = sizeNeeded - sizeof(POOLMEMORYBLOCK);
        newBlock -> RawMemory   = allocedMemory + sizeof(POOLMEMORYBLOCK);
        newBlock -> Index       = 0;

        //
        // Link the block into the list.
        //
        if (poolHeader -> PoolHead) {
            poolHeader -> PoolHead -> PrevBlock = newBlock;
        }
        newBlock   -> NextBlock   = poolHeader -> PoolHead;
        newBlock   -> PrevBlock   = NULL;
        poolHeader -> PoolHead    = newBlock;

    }
    //
    // Assuming allocedMemory is non-NULL, we have succeeded.
    //
    return allocedMemory != NULL;
}

POOLHANDLE
WINAPI
PoolMemInitPool (
    )
{
    BOOL        ableToAddMemory;
    PPOOLHEADER header = NULL;
    HANDLE      procHeap;

    procHeap = GetProcessHeap();
    //
    // Allocate the header of this pool.
    //
    header = HeapAlloc(procHeap,0,sizeof(POOLHEADER));

    if (header) {

        //
        // Allocation was successful. Now, initialize the pool.
        //
        header -> PoolHead = NULL;
        header -> Heap = procHeap;

        //
        // Actually add some memory to the pool.
        //
        ableToAddMemory = PoolMemAddMemory(header,0);

        if (!ableToAddMemory) {
            //
            // Unable to add memory to the pool.
            //
            HeapFree(header -> Heap,0,header);
            header = NULL;
        }

    }
    return (POOLHANDLE) header;
}

VOID
WINAPI
PoolMemDestroyPool (
    POOLHANDLE Handle
    )
{
    PPOOLMEMORYBLOCK nextBlock;
    PPOOLMEMORYBLOCK blockToFree;
    PPOOLHEADER      poolHeader;

    assert(Handle != NULL);

    poolHeader = (PPOOLHEADER) Handle;

    //
    // Walk the list, freeing as we go.
    //
    blockToFree = poolHeader ->  PoolHead;

    while (blockToFree != NULL) {

        nextBlock = blockToFree->NextBlock;
        HeapFree(poolHeader -> Heap,0,blockToFree);
        blockToFree = nextBlock;
    }

    //
    // Also, deallocate the poolheader itself.
    //
    HeapFree(poolHeader -> Heap,0,poolHeader);

}

PVOID
WINAPI
PoolMemGetAlignedMemory (
    IN POOLHANDLE Handle,
    IN DWORD      Size,
    IN DWORD      AlignSize
    )

{
    BOOL                haveEnoughMemory = TRUE;
    PVOID               rMemory          = NULL;
    PPOOLHEADER         poolHeader       = (PPOOLHEADER) Handle;
    PPOOLMEMORYBLOCK    currentBlock;
    DWORD               sizeNeeded;
    DWORD               padLength;

    assert(poolHeader != NULL);

    currentBlock = poolHeader -> PoolHead;

    // Determine if more memory is needed, attempt to add if needed.
    sizeNeeded = Size;

    if (currentBlock -> Size - currentBlock -> Index < sizeNeeded + AlignSize) {

        haveEnoughMemory = PoolMemAddMemory(poolHeader,sizeNeeded + AlignSize);
        currentBlock = poolHeader -> PoolHead;
    }

    // If there is enough memory available, return it.
    if (haveEnoughMemory) {
        if (AlignSize) {

            padLength = (DWORD) currentBlock + sizeof(POOLMEMORYBLOCK)
                + currentBlock -> Index;
            currentBlock -> Index += (AlignSize - (padLength % AlignSize)) % AlignSize;

        }

        //Now, get the address of the memory to return.
        rMemory = (PVOID)
            &(currentBlock->RawMemory[currentBlock -> Index]);

        currentBlock->Index += sizeNeeded;
    }

    return rMemory;
}


POOLMEM.H

#ifndef POOLMEM_H
#define POOLMEM_H

#define STRICT
#define LEAN_AND_MEAN
#include <windows.h>
#include <assert.h>
#include <stdio.h>
#include <mbstring.h>

typedef LPVOID POOLHANDLE;

#define POOLMEMORYBLOCKSIZE 8192

POOLHANDLE WINAPI PoolMemInitPool ();
VOID       WINAPI PoolMemDestroyPool (IN POOLHANDLE Handle);
LPVOID     WINAPI PoolMemGetAlignedMemory(IN POOLHANDLE Handle, IN DWORD Size, IN DWORD AlignSize);

#define PoolMemCreateString(h,x)        ((LPTSTR) PoolMemGetAlignedMemory((h),(x)*sizeof(TCHAR)))
#define PoolMemCreateDword(h)           ((PDWORD) PoolMemGetMemory((h),sizeof(DWORD)))

__inline
LPVOID
PoolMemGetMemory (
    IN POOLHANDLE Handle,
    IN DWORD      Size
    )
{

    return PoolMemGetAlignedMemory(Handle,Size,0);

}

__inline
LPTSTR
PoolMemCreateStringA (
    IN POOLHANDLE Handle,
    IN DWORD      Size
    )
{
    return (LPSTR) PoolMemGetAlignedMemory(Handle,Size * sizeof(CHAR),sizeof(CHAR));
}

__inline
LPWSTR
PoolMemCreateStringW (
    IN POOLHANDLE Handle,
    IN DWORD      Size
    )
{
    return (LPWSTR) PoolMemGetAlignedMemory(Handle,Size * sizeof(WCHAR),sizeof(WCHAR));
}

__inline
PTSTR
PoolMemDuplicateStringA (
    IN POOLHANDLE    Handle,
    IN LPCSTR       StringToCopy
    )

{

    DWORD   stringLength;
    PSTR    rString;

    assert(StringToCopy);

    stringLength = (DWORD) _mbschr(StringToCopy,0) - (DWORD) StringToCopy + 1;
    rString      = PoolMemGetAlignedMemory(Handle,stringLength,sizeof(CHAR));

    if (rString) {

        _mbscpy(rString,StringToCopy);
    }

    return rString;
}

__inline
PWSTR
PoolMemDuplicateStringW (
    IN POOLHANDLE    Handle,
    IN LPCWSTR       StringToCopy
    )

{

    DWORD    stringLength;
    PWSTR    rString;

    assert(StringToCopy);

    stringLength = ((wcslen(StringToCopy)+1)*sizeof(WCHAR));
    rString      = PoolMemGetAlignedMemory(Handle,stringLength,sizeof(WCHAR));

    if (rString) {

        wcscpy(rString,StringToCopy);
    }

    return rString;
}

#endif


PVIEWMIG.C

#define STRICT
#define LEAN_AND_MEAN
#include <windows.h>
#include <setupapi.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <mbstring.h>
#include "miginf.h"

#define DIRECTORYKEY                "SOFTWARE\\Microsoft\\DevStudio\\5.0\\Products\\Microsoft Visual C++"
#define DIRECTORYVALUE               "ProductDir"
#define DIR_95SPECIFIC               "\\bin\\win95"
#define FILE_PVIEW                   "pview.exe"
#define MESSAGE                                                                                 \
    "PVIEW.EXE has been found in one or more directories outside of your Visual C++ 5.0"        \
    " installation directory. These copies will not be updated with the NT version."            \

#define MESSAGE_SECTION              "Microsoft\\Visual C++ 5.0\\Process Viewer"
#define SIZENEEDED                   100000L

#define CP_USASCII          1252

//
// 9x side globals.
//
const CHAR  g_ProductId[]                       = {"Microsoft Visual C++ 5.0"};
UINT        g_DllVersion                        = 1;
INT         g_CodePageArray[]                   = {CP_USASCII,-1};
CHAR        g_ExeNamesBuffer[]                  = {"pview95.exe\0""\0"};

//
// Nt side globals.
//

//
// Uncomment next line to get popups.
//
#define MYDEBUG
#ifdef  MYDEBUG
#   define INFO(x)     (MessageBoxA(NULL,(x),"PVIEW Sample Migration Dll",MB_OK | MB_ICONINFORMATION))
#else
#   define INFO(x)
#endif

static
BOOL
PathIsInPath(
    IN PCSTR    SubPath,
    IN PCSTR    ParentPath
    )
{
    DWORD parentLength;
    BOOL  rInPath;

    //
    // This function assumes both parameters are non-NULL.
    //
    assert(SubPath);
    assert(ParentPath);

    parentLength = _mbslen(ParentPath);

    //
    // A path is considered "in" another path if the path is in the ParentPath
    // or a subdirectory of it.
    //
    rInPath = !_mbsnicmp(SubPath,ParentPath,parentLength);

    if (rInPath) {
        rInPath = SubPath[parentLength] == 0 || SubPath[parentLength] == '\\';
    }

    return rInPath;

}

static
PSTR
GetPviewDirectoryNt (
    VOID
    )
{
    HKEY    softwareKey;
    LONG    rc;
    LONG    valueType;
    LONG    sizeNeeded;
    PSTR    rString = NULL;

    //
    // First, open the key.
    //
    rc = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        DIRECTORYKEY,
        0,
        KEY_READ,
        &softwareKey
        );

    if (rc == ERROR_SUCCESS) {

        //
        // Determine how large of a buffer to allocate.
        //

        rc = RegQueryValueEx(
            softwareKey,
            DIRECTORYVALUE,
            0,
            &valueType,
            NULL,
            &sizeNeeded
            );

        //
        // Allocate enough space for the registry path, with the additional
        // subpath to Visual C++ 5.0's win 95 specific binaries.
        //
        rString = LocalAlloc(0,sizeNeeded + lstrlen(DIR_95SPECIFIC) + 1);

    }

    if (rc == ERROR_SUCCESS && rString != NULL) {

        //
        // Read in the buffer.
        //

        rc = RegQueryValueEx(
            softwareKey,
            DIRECTORYVALUE,
            0,
            &valueType,
            (PBYTE) rString,
            &sizeNeeded
            );

        if (rc == ERROR_SUCCESS) {

            if (valueType != REG_SZ) {
                rc = ERROR_INVALID_DATATYPE;
            }
        }
    }

    //
    // If we didn't complete successfully, set the last error, and free the
    // return string if it was allocated.
    //
    if (rc != ERROR_SUCCESS) {
        SetLastError(rc);

        if (rString) {
            LocalFree(rString);
            rString = NULL;
        }
    }

    return rString;
}

static
PSTR
GetPviewDirectory9x (
    VOID
    )
{
    HKEY    softwareKey;
    LONG    rc;
    LONG    valueType;
    LONG    sizeNeeded;
    PSTR    rString = NULL;

    //
    // First, open the key.
    //
    rc = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        DIRECTORYKEY,
        0,
        KEY_READ,
        &softwareKey
        );

    if (rc == ERROR_SUCCESS) {

        //
        // Determine how large of a buffer to allocate.
        //

        rc = RegQueryValueEx(
            softwareKey,
            DIRECTORYVALUE,
            0,
            &valueType,
            NULL,
            &sizeNeeded
            );

        //
        // Allocate enough space for the registry path, with the additional
        // subpath to Visual C++ 5.0's win 95 specific binaries.
        //
        rString = LocalAlloc(0,sizeNeeded + lstrlen(DIR_95SPECIFIC) + 1);

    }

    if (rc == ERROR_SUCCESS && rString != NULL) {

        //
        // Read in the buffer.
        //

        rc = RegQueryValueEx(
            softwareKey,
            DIRECTORYVALUE,
            0,
            &valueType,
            (PBYTE) rString,
            &sizeNeeded
            );

        if (rc == ERROR_SUCCESS) {

            if (valueType == REG_SZ) {

                //
                // We have successfully read in the value of the installation
                // directory into rString. Now, all we need to do is tack on
                // the win 95 specific portion that we care about.
                //
                lstrcat(rString,DIR_95SPECIFIC);

            }
            else {
                rc = ERROR_INVALID_DATATYPE;
            }
        }
    }

    //
    // If we didn't complete successfully, set the last error, and free the
    // return string if it was allocated.
    //
    if (rc != ERROR_SUCCESS) {
        SetLastError(rc);

        if (rString) {
            LocalFree(rString);
            rString = NULL;
        }
    }

    return rString;
}

static
LONG
CheckForInstalledComponents (
    VOID
    )
{
    BOOL    rc;
    HKEY    softwareKey;

    //
    // Attempt to open the key.
    //
    rc = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        DIRECTORYKEY,
        0,
        KEY_READ,
        &softwareKey
        );

    //
    // If the key exists, then assume that Microsoft Visual C++ 5.0 is installed.
    //
    RegCloseKey(softwareKey);

    return rc;
}

BOOL
WINAPI
DllMain (
    IN      HANDLE Instance,
    IN      ULONG  Reason,
    IN      LPVOID Reserved
    )
{

    switch (Reason)  {

    case DLL_PROCESS_ATTACH:
        break;

    case DLL_PROCESS_DETACH:
        //
        // Ensure that the MigInf structure is cleaned up before unloading this DLL.
        //
        MigInf_CleanUp();
        break;
    }

    return TRUE;
}

LONG
CALLBACK
QueryVersion (
    OUT LPCSTR  *   ProductId,
    OUT LPUINT      DllVersion,
    OUT LPINT   *   CodePageArray,OPTIONAL
    OUT LPCSTR  *   ExeNamesBuf,OPTIONAL
        LPVOID      Reserved
    )
{

    LONG rc;

    INFO("Entering QueryVersion.");

    assert(ProductId);
    assert(DllVersion);
    assert(CodePageArray);
    assert(ExeNamesBuf);

    //
    // Setup is calling us to query our version information and to identify if
    // we need processing. We always need to provide the product ID and the
    // DLL version.
    //
    *ProductId = g_ProductId;
    *DllVersion = g_DllVersion;

    //
    // Check to see if there is anything to do.
    //
    if (CheckForInstalledComponents() == ERROR_SUCCESS) {

        //
        // There are installed components. Return the information Setup is
        // asking for.
        //
        *CodePageArray  = g_CodePageArray; // Use the CP_ACP code page for conversion to unicode,
        *ExeNamesBuf    = g_ExeNamesBuffer;

        //
        // Since there is work to do, return EXIT_SUCCESS. This informs Setup
        // that this dll does require processing during migration.
        //
        rc = ERROR_SUCCESS;
    }
    else {
        rc = ERROR_NOT_INSTALLED;
    }

    return rc;

}

LONG
CALLBACK
Initialize9x (
    IN      LPCSTR   WorkingDirectory,
    IN      LPCSTR   SourceDirectories,
            LPVOID   Reserved
            )
{
    LONG rc;

    //
    // Setup guarantees that the source directories parameter is valid.
    //
    assert(SourceDirectories);

    INFO("Entering Initialize9x.");

    //
    // Initialize the MigInf structure.
    //
    if (!MigInf_Initialize()) {
        rc = GetLastError();
    }
    else {
        rc = ERROR_SUCCESS;
    }

    return rc;
}

LONG
CALLBACK
MigrateUser9x (
    IN      HWND        ParentWnd,
    IN      LPCSTR      UnattendFile,
    IN      HKEY        UserRegKey,
    IN      LPCSTR      UserName,
            LPVOID      Reserved
    )
{
    //
    // Setup guarantees that UnattendFile,UserRegKey will be non-NULL
    //
    assert(UnattendFile);
    assert(UserRegKey);
    INFO("Entering MigrateUser9x.");

    //
    // Nothing to do per user, so, return ERROR_NO_MORE_FILES
    //
    return ERROR_NOT_INSTALLED;
}

LONG
CALLBACK
MigrateSystem9x (
    IN      HWND        ParentWnd,
    IN      LPCSTR      UnattendFile,
            LPVOID      Reserved
    )
{
    LONG                rc                  = EXIT_SUCCESS;
    PSTR                visualCppDirectory  = NULL;
    MIGINFSECTIONENUM   sectionEnum;
    BOOL                firstMessage        = TRUE;
    PCSTR               messageSection      = NULL;

    //
    // Setup guarantees that UnattendFile will be non-NULL.
    //
    assert(UnattendFile);

    INFO("Entering MigrateSystem9x");

    //
    // Since we are in this function, Initialize9x MUST have returned ERROR_SUCCESS.
    // Microsoft Visual C++ is installed on this machine.
    //

    //
    // Initialize the miginf module to handle interfacing with Migrate.Inf and retrieve
    // the installation directory for Visual C++.
    //
    visualCppDirectory = GetPviewDirectory9x();
    if (!visualCppDirectory) {
        rc = GetLastError();
    }
    else {

        //
        // The migration INF was successfully initialized. See if there is anything for
        // us to do. There is work to be done if (1) the [Migration Paths] section of
        // Migrate.inf contains some paths (Indicating that setup found some of the files
        // we asked it to look for in ExeNamesBuf) and (2) The Visual CPP Install directory
        // is not in the Excluded Paths Section.
        //

        if (MigInf_FirstInSection(SECTION_MIGRATIONPATHS,&sectionEnum)
            && !MigInf_PathIsExcluded(visualCppDirectory)) {

            //
            // All checks are good. We have work to do.
            // we need to sift through the files that
            // Setup returned in the Migration paths section. If the files
            // returned are in the installation directory, we will write them
            // to both the [Handled Files] sections and the [Moved Files]
            // sections. If not, we will write the file to the [Handled Files]
            // section and then write a message to the [Incompatible Messages]
            // section. This will allow us to override the message that
            // Setup is providing for these files with a more meaningful one.
            //
            do {

                if (PathIsInPath(sectionEnum.Key,visualCppDirectory)) {

                    //
                    // This file is in our installation path. We'll be handling it.
                    //
                    if (!MigInf_AddObject(
                            MIG_FILE,
                            SECTION_HANDLED,
                            sectionEnum.Key,
                            NULL
                            )) {

                        rc = ERROR_CANTWRITE;
                        break;
                    }

                    //
                    // We also need to note the amount of space that we will use.
                    //
                    if (!MigInf_UseSpace(sectionEnum.Key,SIZENEEDED)) {
                        rc = ERROR_CANTWRITE;
                        break;
                    }

                }
                else {

                    //
                    // This file is not in our installation path.
                    //
                    if (firstMessage) {

                        //
                        // We'll only add one message to the incompatible messages
                        // section, no matter how many PVIEW's we find outside of
                        // the installation directory.  However, we'll add all of
                        // those files to the section that controls that message.
                        // That way, the message will always appear unless _every_
                        // file in that section has been handled by something
                        // (i.e. another migration DLL.)
                        //

                        firstMessage    = FALSE;

                        if (!MigInf_AddObject(
                                MIG_MESSAGE,
                                SECTION_INCOMPATIBLE,
                                MESSAGE_SECTION,
                                MESSAGE
                                )) {

                            rc = ERROR_CANTWRITE;
                            break;
                        }
                    }

                    if (!MigInf_AddObject(
                            MIG_FILE,
                            MESSAGE_SECTION,
                            sectionEnum.Key,
                            NULL
                            )) {
                        rc = ERROR_CANTWRITE;
                        break;
                    }
                }

            } while (MigInf_NextInSection(&sectionEnum));
        }
        else {

            //
            // There is nothing for us to do.
            //
            rc = ERROR_NOT_INSTALLED;
        }

        MigInf_WriteInfToDisk();
    }

    //
    // Free the memory allocated in GetVisualCppDirectory.
    //

    if (visualCppDirectory) {
        LocalFree(visualCppDirectory);
    }

    return rc;
}

LONG
CALLBACK
InitializeNT (
    IN      LPCWSTR     WorkingDirectory,
    IN      LPCWSTR     SourceDirectory,
            LPVOID      Reserved
    )
{

    //
    // Setup ensures that WorkingDirectory and SourceDirectory will be non-NULL.
    //
    assert(WorkingDirectory != NULL && SourceDirectory != NULL);

    //
    // We do not need to do anything in this call. Simply return ERROR_SUCCES
    //
    return ERROR_SUCCESS;
}

LONG
CALLBACK
MigrateUserNT (
    IN HINF         UnattendInfHandle,
    IN HKEY         UserRegHandle,
    IN LPCWSTR      UserName,
       LPVOID       Reserved
    )
{

    //
    // Setup guarantees that UnattendInfHandle and UserRegHandle are non-NULL and valid.
    // UserName can be NULL, however, for the default user.
    //
    assert(UnattendInfHandle);

    //
    // Nothing to do per user. Simply return ERROR_SUCCESS.
    // (Note the difference in return codes between MigrateUser9x and MigrateUserNT.)
    //
    return ERROR_SUCCESS;
}

LONG
CALLBACK
MigrateSystemNT (
    IN HINF         UnattendInfHandle,
       LPVOID       Reserved
    )
{
    LONG    rc;
    PSTR    pviewDir;
    CHAR    fromPath[MAX_PATH];
    CHAR    toPath[MAX_PATH];

    //
    // Setup guarantees that UnattendInfHandle is non-NULL and is valid.
    //
    assert(UnattendInfHandle && UnattendInfHandle != INVALID_HANDLE_VALUE);

    //
    // If we have gotten to this point, we know that we are installed. All we need to do is copy
    // the NT version of PVIEW into the installation directory of Visual C++ 5.0 Note that we do
    // not replace the 9x version as a normal Visual C++ install on NT would have the 9x of PVIEW
    // as well.
    //
    pviewDir = GetPviewDirectoryNt();

    if (pviewDir) {

        sprintf(fromPath,".\\%s",FILE_PVIEW);
        sprintf(toPath,"%s\\%s",pviewDir,FILE_PVIEW);

        if (!CopyFileA(fromPath,toPath,FALSE)) {
            rc = GetLastError();
        }
        else {
            rc = ERROR_SUCCESS;
        }

    }
    else {
        rc = GetLastError();
    }

    return rc = ERROR_SUCCESS;
}


DATACONV.C

/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    dataconv.c

Abstract:

    This source file implements data conversion routines that know
    the format of Windows 95 screen saver settings and can convert
    them to the Windows NT format.

Author:

    Jim Schmidt (jimschm) 14-Apr-1997

Revision History:

--*/

#include "pch.h"

static CHAR g_Data[MAX_PATH];

BOOL
pHasSpecialProcessing (
    IN      LPCSTR ScreenSaverName
    );

BOOL
pTranslateBezier (
    IN      HKEY RegRoot
    );

BOOL
pTranslateMarquee (
    IN      HKEY RegRoot
    );

HKEY
pCreateControlPanelKey (
    IN      HKEY RootKey,
    IN      LPCSTR SubKeyName,
    IN      BOOL CreateEmptyKey
    )
{
    CHAR FullRegKey[MAX_PATH];

    wsprintf (FullRegKey, S_CONTROL_PANEL_MASK, SubKeyName);

    if (CreateEmptyKey) {
        RegDeleteKey (RootKey, FullRegKey);
    }

    return CreateRegKey (RootKey, FullRegKey);
}

HKEY
pCreateScreenSaverKey (
    IN      HKEY RegRoot,
    IN      LPCSTR ScreenSaverName
    )
{
    CHAR FullScreenSaverName[MAX_PATH];

    wsprintf (FullScreenSaverName, S_SCRNSAVE_MASK, ScreenSaverName);
    return pCreateControlPanelKey (RegRoot, FullScreenSaverName, FALSE);
}

BOOL
pCopyValuesFromSettingsFileToRegistry (
    IN      HKEY RegKeyRoot,
    IN      LPCSTR RegKeyName,
    IN      LPCSTR ScreenSaverName,
    IN      LPCSTR ValueArray[]
    )
{
    INT i;
    CHAR IniKeyName[MAX_PATH];
    HKEY RegKey;
    BOOL b = TRUE;

    //
    // This function takes the values stored in our settings file and
    // copies them to a brand new key in the NT registry.
    //
    // In the settings file, we store screen saver parameters in the
    // format of <screen saver name len>/<screen saver name>/<parameter>=<value>.
    //

    //
    // Create new registry key
    //

    RegKey = pCreateControlPanelKey (RegKeyRoot, RegKeyName, TRUE);
    if (!RegKey) {
        return FALSE;
    }

    //
    // Copy values to reg key
    //

    for (i = 0 ; ValueArray[i] ; i++) {
        if (!CreateScreenSaverParamKey (ScreenSaverName, ValueArray[i], IniKeyName)) {
            // fail if screen saver name is huge for some unknown reason
            SetupLogError (S_HUGEDATA_ERROR, LogSevWarning);
            b = FALSE;
            break;
        }

        GetSettingsFileVal (IniKeyName);
        if (!SetRegValueString (RegKey, ValueArray[i], g_Data)) {
            b = FALSE;
            break;
        }
    }

    CloseRegKey (RegKey);
    return b;
}

BOOL
TranslateGeneralSetting (
    IN      HKEY RegKey,
    IN      LPCSTR Win9xSetting,
    IN      LPCSTR WinNTSetting
    )
{
    BOOL b = TRUE;

    if (!WinNTSetting) {
        WinNTSetting = Win9xSetting;
    } else {
        //
        // Delete the Win9x setting that was copied to NT, ignore
        // any failures.
        //
        RegDeleteValue (RegKey, Win9xSetting);
    }

    //
    // Obtain setting from data file
    //

    if (GetSettingsFileVal (Win9xSetting)) {
        //
        // Save settings to registry
        //

        b = SetRegValueString (RegKey, WinNTSetting, g_Data);
    }

    return b;
}

typedef struct {
    LPCSTR Win9xName;
    LPCSTR WinNtName;
} FILE_TRANS, *PFILE_TRANS;

FILE_TRANS g_FileNameTranslation[] = {
    // Win9x name                   // WinNT name (NULL=no change)
    "black16.scr",                  NULL,
    "Blank Screen.scr",             "black16.scr",

    "ssbezier.scr",                 NULL,
    "Curves and Colors.scr",        "ssbezier.scr",

    "ssstars.scr",                  NULL,
    "Flying Through Space.scr",     "ssstars.scr",

    "ssmarque.scr",                 NULL,
    "Scrolling Marquee.scr",        "ssmarque.scr",

    "ssmyst.scr",                   NULL,
    "Mystify Your Mind.scr",        "ssmyst.scr",

    NULL, NULL
};

LPCSTR
GetSettingsFileVal (
    IN      LPCSTR Key
    )
{
    GetPrivateProfileString (
        g_User,
        Key,
        S_EMPTY,
        g_Data,
        MAX_PATH,
        g_SettingsFile
        );

    return g_Data[0] ? g_Data : NULL;
}

BOOL
pTranslateScrName (
    IN OUT  LPSTR KeyName,
    OUT     LPSTR FullPath      OPTIONAL
    )
{
    int i;

    //
    // Compare against translation list
    //

    for (i = 0 ; g_FileNameTranslation[i].Win9xName ; i++) {
        if (!_mbsicmp (KeyName, g_FileNameTranslation[i].Win9xName)) {
            break;
        }
    }

    //
    // Translate filename only if a match was found in our list
    //

    if (g_FileNameTranslation[i].Win9xName) {

        //
        // If WinNtName is NULL, there is no renaming necessary.  Otherwise,
        // use the NT name, which is always a file in system32.
        //

        if (g_FileNameTranslation[i].WinNtName && FullPath) {
            // Rebuild path
            GetSystemDirectory (FullPath, MAX_PATH);
            _mbscat (FullPath, "\\");
            _mbscat (FullPath, g_FileNameTranslation[i].WinNtName);
        }

        _mbscpy (KeyName, g_FileNameTranslation[i].WinNtName);
    }
    else if (FullPath) {
        FullPath[0] = 0;
    }

    return TRUE;
}

BOOL
SaveScrName (
    IN      HKEY RegKey,
    IN      LPCSTR KeyName
    )
{
    LPSTR p;
    CHAR FullPath[MAX_PATH];
    CHAR ShortName[MAX_PATH];

    //
    // The Windows 95 screen saver names are different than
    // Windows NT.
    //

    if (!GetSettingsFileVal (KeyName)) {
        // Unexpected: .SCR name does not exist in our file
        return TRUE;
    }

    //
    // Locate the screen saver name within the full path
    //

    p = _mbsrchr (g_Data, '\\');
    if (!p) {
        p = g_Data;
    } else {
        p = _mbsinc (p);
    }

    //
    // Translate it if necessary
    //

    if (!pTranslateScrName (p, FullPath)) {
        return FALSE;
    }

    if (!FullPath[0]) {
        //
        // No change was made, so copy original path to FullPath
        //

        _mbscpy (FullPath, g_Data);
    }

    //
    // Screen savers are always stored in short filename format
    //

    GetShortPathName (FullPath, ShortName, MAX_PATH);

    return SetRegValueString (RegKey, KeyName, ShortName);
}

INT
GetHexDigit (
    IN      CHAR c
    )
{
    if (c >= '0' && c <= '9') {
        return c - '0';
    }

    c = tolower (c);
    if (c >= 'a' && c <= 'f') {
        return c - 'a' + 10;
    }

    return -1;
}

BYTE
GetNextHexByte (
    IN      LPCSTR HexString,
    OUT     LPCSTR *HexStringReturn
    )
{
    INT a, b;

    a = GetHexDigit (HexString[0]);
    b = GetHexDigit (HexString[1]);

    if (a == -1 || b == -1) {
        *HexStringReturn = NULL;
        return 0;
    }

    *HexStringReturn = &(HexString[2]);

    return a * 16 + b;
}

BOOL
GetNextDword (
    IN      LPCSTR HexString,
    OUT     LPCSTR *HexStringReturn,
    OUT     PDWORD ValuePtr
    )
{
    INT i;
    BYTE NextByte;

    *ValuePtr = 0;

    for (i = 0 ; i < 4 ; i++) {
        NextByte = GetNextHexByte (HexString, &HexString);
        if (!HexString) {
            return FALSE;
        }

        *ValuePtr = ((*ValuePtr) << 8) | NextByte;
    }

    return TRUE;
}

BOOL
VerifyBezierChecksum (
    IN      LPCSTR HexString
    )
{
    BYTE Checksum = 0;
    INT Len;

    Len = _mbslen (HexString);
    Len -= 2;

    if (Len < 1) {
        return FALSE;
    }

    while (Len > 0) {
        Checksum += GetNextHexByte (HexString, &HexString);
        if (!HexString) {
            return FALSE;
        }
    }

    if (Checksum != GetNextHexByte (HexString, &HexString)) {
        return FALSE;
    }

    return TRUE;
}

BOOL
CopyUntranslatedSettings (
    IN      HKEY RegRoot
    )
{
    LPSTR KeyBuffer;
    DWORD KeyBufferSize;
    LPSTR p;
    CHAR ScreenSaverName[MAX_PATH];
    CHAR ValueName[MAX_PATH];
    HKEY Key;

    //
    // Enumerate each entry in our private settings file for the user
    //

    KeyBufferSize = 32768;
    KeyBuffer = (LPSTR) HeapAlloc (g_hHeap, 0, KeyBufferSize);
    if (!KeyBuffer) {
        return FALSE;
    }

    //
    // Get all keys in the user's section
    //

    GetPrivateProfileString (
        g_User,
        NULL,
        S_DOUBLE_EMPTY,
        KeyBuffer,
        KeyBufferSize,
        g_SettingsFile
        );

    for (p = KeyBuffer ; *p ; p = _mbschr (p, 0) + 1) {
        //
        // Process only if key is encoded
        //

        if (!DecodeScreenSaverParamKey (p, ScreenSaverName, ValueName)) {
            continue;
        }

        //
        // Key is encoded, so perform migration!
        //

        pTranslateScrName (ScreenSaverName, NULL);

        //
        // Skip screen savers that have special processing
        //

        if (pHasSpecialProcessing (ScreenSaverName)) {
            continue;
        }

        //
        // Save the value to the registry
        //

        GetSettingsFileVal (p);

        Key = pCreateScreenSaverKey (RegRoot, ScreenSaverName);
        if (Key) {
            if (SetRegValueString (Key, ValueName, g_Data))
            {
                CHAR DebugMsg[MAX_PATH*2];
                wsprintf (DebugMsg, "Saved %s=%s\r\n", ValueName, g_Data);
                SetupLogError (DebugMsg, LogSevInformation);
            } else {
                CHAR DebugMsg[MAX_PATH*2];
                wsprintf (DebugMsg, "Could not save %s=%s\r\n", ValueName, g_Data);
                SetupLogError (DebugMsg, LogSevError);
            }

            CloseRegKey (Key);
        }
    }

    HeapFree (g_hHeap, 0, KeyBuffer);

    return TRUE;
}

BOOL
pHasSpecialProcessing (
    IN      LPCSTR ScreenSaverName
    )
{
    //
    // Return TRUE if we are doing something special for the
    // named screen saver.
    //

    if (!_mbsicmp (ScreenSaverName, S_BEZIER) ||
        !_mbsicmp (ScreenSaverName, S_MARQUEE)
        ) {
        return TRUE;
    }

    return FALSE;
}

BOOL
TranslateScreenSavers (
    IN      HKEY RegRoot
    )
{
    BOOL b = TRUE;

    b &= pTranslateBezier (RegRoot);
    b &= pTranslateMarquee (RegRoot);

    return b;
}

BOOL
pTranslateBezier (
    IN      HKEY RegRoot
    )
{
    DWORD Value;
    CHAR StrValue[32];
    LPCSTR p;
    HKEY RegKey;
    BOOL b;

    //
    // NT's Bezier has three settings:
    //
    // Length (REG_SZ)      = Curve Count on Win9x
    // LineSpeed (REG_SZ)   = Speed on Win9x
    // Width (REG_SZ)       = Density on Win9x
    //
    // Win9x's Bezier has a big string of hex in the following format:
    //
    // Clear Screen Flag (DWORD)
    // Random Colors Flag (DWORD)
    // Curve Count (DWORD)
    // Line Count (DWORD)
    // Density (DWORD)
    // Speed (DWORD)
    // Current Color (DWORD RGB)
    // Checksum (BYTE)
    //

    //
    // Verify structure
    //

    GetSettingsFileVal (S_BEZIER_SETTINGS);

    if (!VerifyBezierChecksum (g_Data)) {
        return TRUE;
    }

    //
    // Open reg key
    //

    RegKey = pCreateControlPanelKey (RegRoot, S_BEZIER_SETTINGS, TRUE);
    if (!RegKey) {
        return FALSE;
    }

    p = g_Data;

    // Get clear screen flag (but ignore it)
    b = GetNextDword (p, &p, &Value);

    // Get random colors flag (but ignore it)
    if (b) {
        b = GetNextDword (p, &p, &Value);
    }

    //
    // Get curve count
    //

    if (b) {
        b = GetNextDword (p, &p, &Value);
    }

    if (b) {
        wsprintf (StrValue, "%u", Value);
        b = SetRegValueString (RegKey, S_LENGTH, StrValue);
    }

    // Get line count (but ignore it)
    if (b) {
        b = GetNextDword (p, &p, &Value);
    }

    //
    // Get density
    //

    if (b) {
        b = GetNextDword (p, &p, &Value);
    }

    if (b) {
        wsprintf (StrValue, "%u", Value);
        b = SetRegValueString (RegKey, S_WIDTH, StrValue);
    }

    //
    // Get speed
    //

    if (b) {
        b = GetNextDword (p, &p, &Value);
    }

    if (b) {
        wsprintf (StrValue, "%u", Value);
        b = SetRegValueString (RegKey, S_LINESPEED, StrValue);
    }

    CloseRegKey (RegKey);

    if (!b) {
        SetupLogError (S_BEZIER_DATA_ERROR, LogSevError);
    }

    return TRUE;
}

LPCSTR g_MarqueeValues[] = {
    S_BACKGROUND_COLOR,
    S_CHARSET,
    S_FONT,
    S_MODE,
    S_SIZE,
    S_SPEED,
    S_TEXT,
    S_TEXTCOLOR,
    NULL
};

BOOL
pTranslateMarquee (
    IN      HKEY RegRoot
    )
{
    BOOL b;

    //
    // Marquee has the same settings on Win9x and NT.  They just need
    // to be copied from the control.ini file to the NT registry.
    //

    b = pCopyValuesFromSettingsFileToRegistry (
                RegRoot,
                S_MARQUEE_SETTINGS,
                S_MARQUEE,
                g_MarqueeValues
                );

    //
    // We need to divide the speed by two to be compatible
    //

    if (b) {
        HKEY MarqueeKey;
        LPCSTR Value;
        CHAR NewValue[32];

        // Read the setting we just wrote in the registry
        MarqueeKey = pCreateControlPanelKey (RegRoot, S_MARQUEE_SETTINGS, FALSE);

        if (MarqueeKey) {
            Value = GetRegValueString (MarqueeKey, S_SPEED);
            if (Value) {
                // Write speed divided by two
                wsprintf (NewValue, "%i", atoi (Value) / 2);
                SetRegValueString (MarqueeKey, S_SPEED, NewValue);
            }

            CloseRegKey (MarqueeKey);
        }
    }

    return b;
}


MIGINF.C

#include "pch.h"

#define MIGRATEINF              ".\\migrate.inf"
#define INITIALBUFFERSIZE       1024
#define MIGINF_NOCREATE         FALSE
#define MIGINF_CREATE           TRUE

typedef struct tagMIGOBJECT MIGOBJECT, *PMIGOBJECT;
struct tagMIGOBJECT {

    PSTR        Key;
    PSTR        Value;

    PMIGOBJECT  Next;
};

typedef struct tagMIGSECTION MIGSECTION, * PMIGSECTION;
struct tagMIGSECTION {

    PSTR        Name;
    PMIGOBJECT  Items;

    PMIGSECTION Next;
};

PMIGSECTION g_MigrationInf;
POOLHANDLE  g_Pool = NULL;

static
PCSTR
pGetTypeAsString (
    IN MIGTYPE Type
    )
{
    //
    // Note: Strings must be in the same order as the
    // corresponding types in the MIGTYPE enumeration above.
    //
    static PCHAR typeStrings[] = {
            "FIRST - Invalid",
            "File",
            "Path",
            "Registry",
            "Message - Invalid",
            "LAST - Invalid"
        };

    assert(Type > MIG_FIRSTTYPE && Type < MIG_LASTTYPE);

    return typeStrings[Type];
}

static
PMIGSECTION
pFindSection (
    IN PCSTR SectionString,
    IN BOOL  CreateIfNotExist
    )
{
    PMIGSECTION rSection;

    //
    // We assume that SectionString is not null.
    //
    assert(SectionString);

    rSection = g_MigrationInf;

    while (rSection && (_mbsicmp(rSection -> Name,SectionString) != 0)) {

        //
        // Continue looking.
        //
        rSection = rSection -> Next;
    }

    if (!rSection && CreateIfNotExist) {
        //
        // No section was found matching this name. Make a new section and add it
        // to the list.
        //
        rSection = PoolMemGetMemory(g_Pool,sizeof(MIGSECTION));
        if (rSection) {

            ZeroMemory(rSection,sizeof(MIGSECTION));
            rSection -> Name  = PoolMemDuplicateStringA(g_Pool,SectionString);
            rSection -> Next  = g_MigrationInf;
            g_MigrationInf    = rSection;

            if (!rSection -> Name) {
                //
                // Something went wrong when we tried to duplicate the SectionString.
                // NULL out the rSection so that the caller doesn't get back a
                // malformed section object.
                //
                rSection = NULL;
            }
        }
    }

    return rSection;
}

static
BOOL
pPathIsInPath(
    IN PCSTR    SubPath,
    IN PCSTR    ParentPath
    )
{
    DWORD parentLength;
    BOOL  rInPath;

    //
    // This function assumes both parameters are non-NULL.
    //
    assert(SubPath);
    assert(ParentPath);

    parentLength = _mbslen(ParentPath);

    //
    // A path is considered "in" another path if the path is in the ParentPath
    // or a subdirectory of it.
    //
    rInPath = !_mbsnicmp(SubPath,ParentPath,parentLength);

    if (rInPath) {
        rInPath = SubPath[parentLength] == 0 || SubPath[parentLength] == '\\';
    }

    return rInPath;

}

static
DWORD
pGetMbsSize (
    IN  LPCSTR  String
    )
{
    DWORD rLength;

    rLength = (DWORD) _mbschr(String,0) - (DWORD) String + 1;

    return rLength;

}

static
LPSTR
pEscapeString (
    IN  MIGTYPE Type,
    OUT LPSTR   EscapedString,
    IN  LPCSTR  String
    )

{
    LPSTR   stringStart;
    static  CHAR exclusions[] = "[]~,;%\"";
    INT     currentChar;

    //
    // We assume that all parameters are valid.
    //
    assert(EscapedString && String);

    stringStart = EscapedString;

    while (*String)  {
        currentChar = _mbsnextc (String);

        if (Type == MIG_REGKEY) {

            //
            // Registry keys require more complex escaping than do normal INF strings.
            //
            if (!_ismbcprint (currentChar) || _mbschr (exclusions, currentChar)) {

                //
                // Escape unprintable or excluded character
                //
                wsprintfA (EscapedString, "~%X~", currentChar);
                EscapedString = _mbschr (EscapedString, 0);
                String = _mbsinc (String);
            }
            else {
                //
                // Copy multibyte character
                //
                if (isleadbyte (*String)) {
                    *EscapedString = *String;
                    EscapedString++;
                    String++;
                }

                *EscapedString = *String;
                EscapedString++;
                String++;
            }
        }
        else {

            //
            // Escaping is pretty simple for non-registry keys. All we do is double up
            // quotes and percents.
            //
            if (*String == '\"' || *String == '%') {

                *EscapedString = *String;
                EscapedString++;
            }

            //
            // Copy multibyte character
            //
            if (isleadbyte (*String)) {
                *EscapedString = *String;
                EscapedString++;
                String++;
            }

            *EscapedString = *String;
            EscapedString++;
            String++;
        }
    }

    //
    // Ensure that returned string is NULL terminated.
    //
    *EscapedString = 0;

    return stringStart;
}

static
PSTR
pGetValueString (
    IN MIGTYPE    ObjectType,
    IN LPCSTR     StringOne,
    IN LPCSTR     StringTwo
    )
{
    static PSTR     buffer;
    static DWORD    bufferSize;
    DWORD           maxLength;
    PSTR            bufferEnd;

    //
    // This function assumes that StringOne exists.
    //
    assert(StringOne);

    if (ObjectType == MIG_REGKEY) {
        //
        // Size: size of both strings, plus the size of the quotes, plus the size of the brackets
        // for the value, * 6. This is the maximum size one of these could grow to, if every
        // character had to be escaped out.
        //
        maxLength = (pGetMbsSize(StringOne) + (StringTwo ? pGetMbsSize(StringTwo) + 2 : 0)) * 6 + 2;
    }
    else {
        //
        // Size: size of the string * 2 (The max size if every char was a '%' or '"' plus the quotes.
        //
        maxLength = pGetMbsSize(StringOne) * 2 + 2;
    }

    if (maxLength > bufferSize) {

        //
        // Initialize our buffer, or create a larger one.
        //
        bufferSize = (maxLength > INITIALBUFFERSIZE) ? maxLength : INITIALBUFFERSIZE;
        buffer = PoolMemCreateStringA(g_Pool,bufferSize);
    }

    if (buffer != NULL) {

        //
        // Insert initial quote.
        //
        *buffer = '"';

        //
        // Massage the string to ensure it is a valid INF file string.
        //
        pEscapeString(ObjectType,_mbsinc(buffer),StringOne);

        //
        // If this is a REGISTRY entry, then we also need to add the value part of the string,
        // if one was specified (In StringTwo)
        //

        if (ObjectType == MIG_REGKEY && StringTwo) {

            //
            // Add the opening bracket.
            //
            bufferEnd = _mbschr(buffer,0);
            *bufferEnd = '[';

            //
            // Add the value string in, again making sure the string is valid for an INF file.
            //
            pEscapeString(ObjectType,_mbsinc(bufferEnd),StringTwo);

            //
            // Now, add the closing braket.
            //
            bufferEnd = _mbschr(buffer,0);
            *bufferEnd = ']';

            //
            // Terminate the string.
            //
            bufferEnd = _mbsinc(bufferEnd);
            *bufferEnd = 0;
        }

        //
        // Add the final quote.
        //
        bufferEnd = _mbschr(buffer,0);
        *bufferEnd = '"';
        bufferEnd = _mbsinc(bufferEnd);
        *bufferEnd = 0;
    }

    return buffer;
}

static
BOOL
pCreateMigObject (
    IN MIGTYPE          ObjectType,
    IN PCSTR            ParamOne,
    IN PCSTR            ParamTwo,
    IN PMIGSECTION      Section
    )
{
    BOOL            rSuccess;
    PMIGOBJECT      newObject = NULL;

    //
    // pCreateMigObject uses a set of hueristics to correctly assemble an object.
    // These hueristics are based on the ObjectType and the contents of ParamTwo.
    //
    // ObjectType       ParamTwo      Key                   Value
    // -------------------------------------------------------------------------
    // MIG_REGKEY       <any>         ParamOne[ParamTwo]    Registry
    // <other>          NULL          ParamOne              <ObjectType As String>
    // <other>          non-NULL      ParamOne              ParamTwo
    //
    //

    if (Section) {

        //
        // First, create an object...
        //
        newObject = PoolMemGetMemory(g_Pool,sizeof(MIGOBJECT));

        if (newObject) {

            if (ObjectType == MIG_REGKEY) {

                newObject -> Key =
                    PoolMemDuplicateStringA(g_Pool,pGetValueString(ObjectType,ParamOne,ParamTwo));

                newObject -> Value = PoolMemDuplicateStringA(g_Pool,pGetTypeAsString(ObjectType));
            }
            else {

                newObject -> Key =
                    PoolMemDuplicateStringA(g_Pool,pGetValueString(ObjectType,ParamOne,NULL));

                if (ParamTwo) {
                    newObject -> Value =
                        PoolMemDuplicateStringA(g_Pool,pGetValueString(ObjectType,ParamTwo,NULL));
                }
                else {

                    newObject -> Value =
                        PoolMemDuplicateStringA(g_Pool,pGetTypeAsString(ObjectType));
                }
            }
        }
    }

    if (newObject && newObject -> Key && newObject -> Value) {

        //
        // The object has been successfully created. Link it into the section.
        //
        newObject -> Next = Section -> Items;
        Section -> Items = newObject;
        rSuccess = TRUE;
    }
    else {
        rSuccess = FALSE;
    }

    return newObject && newObject -> Key && newObject -> Value;
}

static
BOOL
pWriteInfSectionToDisk (
    IN PMIGSECTION Section
    )
{
    PMIGOBJECT curObject;
    BOOL       rSuccess = TRUE;

    if (Section) {

        curObject = Section -> Items;

        while (curObject && rSuccess) {

            if (Section -> Name && curObject -> Key && curObject -> Value) {

                rSuccess = WritePrivateProfileString(
                    Section   -> Name,
                    curObject -> Key,
                    curObject -> Value,
                    MIGRATEINF
                    );
            }

            curObject = curObject -> Next;
        }
    }
    else {
        rSuccess = FALSE;
    }

    return rSuccess;
}

static
BOOL
pBuildListFromSection (
    IN PCSTR    SectionString
    )
{
    HINF            infHandle;
    PMIGSECTION     section;
    PMIGOBJECT      currentObject;
    INFCONTEXT      ic;
    DWORD           size;
    BOOL            rSuccess = TRUE;

    //
    // This function assumes that Section is non-NULL.
    //
    assert(SectionString);

    currentObject = NULL;

    //
    // First find the section specified.
    //
    section = pFindSection(SectionString,MIGINF_CREATE);

    if (section) {

        infHandle = SetupOpenInfFileA(MIGRATEINF,NULL,INF_STYLE_WIN4,NULL);

        if (infHandle != INVALID_HANDLE_VALUE) {

            if (SetupFindFirstLine(infHandle,SectionString,NULL,&ic)) {

                do {

                    //
                    // Create the object.
                    //
                    currentObject = (PMIGOBJECT) PoolMemGetMemory(g_Pool,sizeof(MIGOBJECT));

                    if (!currentObject) {
                        rSuccess = FALSE;
                        break;
                    }

                    //
                    // Get the size of the string.
                    //
                    if (!SetupGetLineTextA(&ic,NULL,NULL,NULL,NULL,0,&size)) {
                        rSuccess = FALSE;
                        break;
                    }

                    //
                    // Create a string large enough.
                    //
                    currentObject -> Key = PoolMemCreateStringA(g_Pool,size);

                    if (!currentObject -> Key) {
                        rSuccess = FALSE;
                        break;
                    }

                    //
                    // Get the string.
                    //
                    if (!SetupGetLineTextA(&ic,NULL,NULL,NULL,currentObject -> Key,size,NULL)) {
                        rSuccess = FALSE;
                        break;
                    }

                    //
                    // Successfully retrieved the line.
                    //
                    currentObject -> Value  = (PSTR) pGetTypeAsString(MIG_FILE);
                    currentObject -> Next   = section -> Items;
                    section -> Items        = currentObject;

                } while(SetupFindNextLine(&ic,&ic));

            }

            SetupCloseInfFile(infHandle);
        }
    }
    else {
        rSuccess = FALSE;
    }

    return rSuccess;
}

BOOL
WINAPI
MigInf_Initialize(
    VOID
    )
{

    //
    // First, initialize our pool and Zero out the structure.
    //
    g_Pool = PoolMemInitPool();

    if (g_Pool) {

        //
        // Now, read in the migration paths and excluded paths sections.
        //
        if (!pBuildListFromSection(SECTION_MIGRATIONPATHS) ||
            !pBuildListFromSection(SECTION_EXCLUDEDPATHS)) {
            //
            // Something went wrong (i.e. out of memory. Destroy and NULL our pool.
            //
            PoolMemDestroyPool(g_Pool);
            g_Pool = NULL;
        }
    }

    //
    // If our memory pool initialized successfully, return TRUE.
    //
    return (g_Pool != NULL);

}

VOID
WINAPI
MigInf_CleanUp (
    VOID
    )
{
    //
    // Only thing we need to do is clean out pool mem. We'll NULL out the list header to make
    // sure it isn't usable.
    //
    if (g_Pool) {
        PoolMemDestroyPool(g_Pool);
        g_Pool = NULL;
    }

    g_MigrationInf = NULL;

}

BOOL
WINAPI
MigInf_AddObject (
    IN MIGTYPE  ObjectType,
    IN PCSTR    SectionString,
    IN PCSTR    ParamOne,
    IN PCSTR    ParamTwo
    )
{

    return pCreateMigObject(
        ObjectType,
        ParamOne,
        ParamTwo,
        pFindSection(SectionString,MIGINF_CREATE)
        );
}

BOOL
WINAPI
MigInf_FirstInSection(
    IN PCSTR SectionName,
    OUT PMIGINFSECTIONENUM Enum
    )
{
    PMIGSECTION section;

    //
    // We assume that Enum is valid.
    //
    assert(Enum);

    section = pFindSection(SectionName,MIGINF_NOCREATE);

    if (section) {
        Enum -> EnumKey = (PVOID) section -> Items;
    }

    return MigInf_NextInSection(Enum);
}

BOOL
WINAPI
MigInf_NextInSection(
    IN OUT PMIGINFSECTIONENUM Enum
    )
{

    BOOL            rSuccess = FALSE;

    //
    // We assume that the Enum is valid.
    //
    assert(Enum);

    if (Enum -> EnumKey) {

        Enum -> Key     = ((PMIGOBJECT) (Enum -> EnumKey)) -> Key;
        Enum -> Value   = ((PMIGOBJECT) (Enum -> EnumKey)) -> Value;
        Enum -> EnumKey = ((PVOID) ((PMIGOBJECT) (Enum -> EnumKey)) -> Next);
        rSuccess = TRUE;
    }

    return rSuccess;
}

BOOL
WINAPI
MigInf_WriteInfToDisk (
    VOID
    )
{

    BOOL        rSuccess = TRUE;
    PMIGSECTION curSection;

    //
    // Simply loop through all of the sections, writing each of them to disk.
    // As long as WriteSectionToDisk works, we work.
    //
    curSection = g_MigrationInf;

    while (curSection && rSuccess) {

        //
        // We skip the [Excluded Paths] and [Migration Paths] sections.
        //
        if (_mbsicmp(curSection -> Name,SECTION_EXCLUDEDPATHS) &&
            _mbsicmp(curSection -> Name,SECTION_MIGRATIONPATHS)) {

            rSuccess = pWriteInfSectionToDisk(curSection);
        }

        curSection = curSection -> Next;

    }

    return rSuccess;
}

BOOL
WINAPI
MigInf_PathIsExcluded (
    IN PCSTR    Path
    )
{
    PMIGOBJECT  curExcluded;
    PMIGSECTION section;
    BOOL        rIsExcluded = FALSE;

    //
    // We assume Path is valid.
    //
    assert(Path);

    section = pFindSection(SECTION_EXCLUDEDPATHS,MIGINF_NOCREATE);

    if (section) {

        curExcluded = section -> Items;

        while (curExcluded && !rIsExcluded) {

            rIsExcluded = pPathIsInPath(Path,curExcluded -> Key);
            curExcluded = curExcluded -> Next;
        }
    }

    return rIsExcluded;
}

PCSTR
WINAPI
MigInf_GetNewSectionName (
    VOID
    )
{

    static CHAR     sectionName[20];
    static DWORD    seedNum=0;

    sprintf(sectionName,"msg%0.7u",seedNum++);

    return sectionName;
}


MIGINF.H

#ifndef MIGINF_H
#define MIGINF_H

#define SECTION_MIGRATIONPATHS  "Migration Paths"
#define SECTION_EXCLUDEDPATHS   "Excluded Paths"
#define SECTION_HANDLED         "Handled"
#define SECTION_MOVED           "Moved"
#define SECTION_INCOMPATIBLE    "Incompatible Messages"

typedef enum {

    MIG_FIRSTTYPE,
    MIG_FILE,
    MIG_PATH,
    MIG_REGKEY,
    MIG_MESSAGE,
    MIG_LASTTYPE

} MIGTYPE, *PMIGTYPE;

typedef struct tagMIGINFSECTIONENUM {

    PCSTR        Key;
    PCSTR        Value;
    PVOID        EnumKey;            // Internal.

} MIGINFSECTIONENUM, * PMIGINFSECTIONENUM;

BOOL WINAPI MigInf_Initialize (VOID);
VOID WINAPI MigInf_CleanUp (VOID);
BOOL WINAPI MigInf_PathIsExcluded (IN PCSTR Path);
BOOL WINAPI MigInf_FirstInSection(IN PCSTR SectionName, OUT PMIGINFSECTIONENUM Enum);
BOOL WINAPI MigInf_NextInSection(IN OUT PMIGINFSECTIONENUM Enum);
BOOL WINAPI MigInf_AddObject (IN MIGTYPE ObjectType,IN PCSTR SectionString,IN PCSTR ParamOne,IN PCSTR ParamTwo);
BOOL WINAPI MigInf_WriteInfToDisk (VOID);
PCSTR WINAPI MigInf_GetNewSectionName (VOID);

//
// Macros for common miginf actions.
//

//
// Adding Objects.
//
#define MigInf_AddHandledFile(file)                      MigInf_AddObject(MIG_FILE,SECTION_HANDLED,(file),NULL)
#define MigInf_AddHandledDirectory(directory)            MigInf_AddObject(MIG_PATH,SECTION_HANDLED,(directory),NULL)
#define MigInf_AddHandledRegistry(key,value)             MigInf_AddObject(MIG_REGKEY,SECTION_HANDLED,(key),(value))

#define MigInf_AddMovedFile(from,to)                     MigInf_AddObject(MIG_FILE,SECTION_MOVED,(from),(to))
#define MigInf_AddMovedDirectory(from,to)                MigInf_AddObject(MIG_PATH,SECTION_MOVED,(from),(to))

#define MigInf_AddMessage(msgSection,msg)                MigInf_AddObject(MIG_MESSAGE,SECTION_INCOMPATIBLE,(msgSection),(msg))

#define MigInf_AddMessageFile(msgSection,file)           MigInf_AddObject(MIG_FILE,(msgSection),(file),NULL)
#define MigInf_AddMessageDirectory(msgSection,directory) MigInf_AddObject(MIG_PATH,(msgSection,(directory),NULL)
#define MigInf_AddMessageRegistry(msgSection,key,value)  MigInf_AddObject(MIG_REGKEY,(msgSection),(key),(value))

//
// Enumerating Sections
//
#define MigInf_GetFirstMigrationPath(Enum)               MigInf_FirstInSection(SECTION_MIGRATIONPATHS,(Enum))
#define MigInf_GetFirstExcludedPath(Enum)                MigInf_FirstInSection(SECTION_EXCLUDEDPATHS,(Enum))

#endif


PCH.H

#define _MBCS
#include <windows.h>
#include "nt5\setupapi.h"
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <mbstring.h>
#include <tchar.h>

#include "poolmem.h"
#include "miginf.h"
#include "scrnsave.h"

#pragma warning(4:4100)     // Lower serverity of "unreferenced formal parameter"


POOLMEM.C

#include "pch.h"

// Tree Memory Allocation structure.

typedef struct _POOLMEMORYBLOCK POOLMEMORYBLOCK, *PPOOLMEMORYBLOCK;

struct _POOLMEMORYBLOCK {
    DWORD                 Index;
    DWORD                 Size;
    PPOOLMEMORYBLOCK      NextBlock;
    PPOOLMEMORYBLOCK      PrevBlock;
    PBYTE                 RawMemory;
};

typedef struct _POOLHEADER {
    PPOOLMEMORYBLOCK PoolHead;
    HANDLE           Heap;
} POOLHEADER, *PPOOLHEADER;

BOOL
PoolMemAddMemory (
    IN  POOLHANDLE  Handle,
    IN  DWORD       Size
    )
{
    PBYTE               allocedMemory;
    PPOOLMEMORYBLOCK    newBlock;
    PPOOLHEADER         poolHeader = (PPOOLHEADER) Handle;
    DWORD               sizeNeeded;

    assert(poolHeader != NULL);

    //
    // Determine size needed and attempt to allocate memory.
    //
    if (Size + sizeof(POOLMEMORYBLOCK) > POOLMEMORYBLOCKSIZE) {
        sizeNeeded = Size + sizeof(POOLMEMORYBLOCK);
    }
    else {
        sizeNeeded = POOLMEMORYBLOCKSIZE;
    }

    allocedMemory = HeapAlloc(poolHeader -> Heap,0,sizeNeeded);

    if (allocedMemory) {

        //
        // Use the beginning of the alloc'ed block as the poolblock structure.
        //
        newBlock                = (PPOOLMEMORYBLOCK) allocedMemory;
        newBlock -> Size        = sizeNeeded - sizeof(POOLMEMORYBLOCK);
        newBlock -> RawMemory   = allocedMemory + sizeof(POOLMEMORYBLOCK);
        newBlock -> Index       = 0;

        //
        // Link the block into the list.
        //
        if (poolHeader -> PoolHead) {
            poolHeader -> PoolHead -> PrevBlock = newBlock;
        }
        newBlock   -> NextBlock   = poolHeader -> PoolHead;
        newBlock   -> PrevBlock   = NULL;
        poolHeader -> PoolHead    = newBlock;

    }
    //
    // Assuming allocedMemory is non-NULL, we have succeeded.
    //
    return allocedMemory != NULL;
}

POOLHANDLE
WINAPI
PoolMemInitPool (
    )
{
    BOOL        ableToAddMemory;
    PPOOLHEADER header = NULL;
    HANDLE      procHeap;

    procHeap = GetProcessHeap();
    //
    // Allocate the header of this pool.
    //
    header = HeapAlloc(procHeap,0,sizeof(POOLHEADER));

    if (header) {

        //
        // Allocation was successful. Now, initialize the pool.
        //
        header -> PoolHead = NULL;
        header -> Heap = procHeap;

        //
        // Actually add some memory to the pool.
        //
        ableToAddMemory = PoolMemAddMemory(header,0);

        if (!ableToAddMemory) {
            //
            // Unable to add memory to the pool.
            //
            HeapFree(header -> Heap,0,header);
            header = NULL;
        }

    }
    return (POOLHANDLE) header;
}

VOID
WINAPI
PoolMemDestroyPool (
    POOLHANDLE Handle
    )
{
    PPOOLMEMORYBLOCK nextBlock;
    PPOOLMEMORYBLOCK blockToFree;
    PPOOLHEADER      poolHeader;

    assert(Handle != NULL);

    poolHeader = (PPOOLHEADER) Handle;

    //
    // Walk the list, freeing as we go.
    //
    blockToFree = poolHeader ->  PoolHead;

    while (blockToFree != NULL) {

        nextBlock = blockToFree->NextBlock;
        HeapFree(poolHeader -> Heap,0,blockToFree);
        blockToFree = nextBlock;
    }

    //
    // Also, deallocate the poolheader itself.
    //
    HeapFree(poolHeader -> Heap,0,poolHeader);

}

PVOID
WINAPI
PoolMemGetAlignedMemory (
    IN POOLHANDLE Handle,
    IN DWORD      Size,
    IN DWORD      AlignSize
    )

{
    BOOL                haveEnoughMemory = TRUE;
    PVOID               rMemory          = NULL;
    PPOOLHEADER         poolHeader       = (PPOOLHEADER) Handle;
    PPOOLMEMORYBLOCK    currentBlock;
    DWORD               sizeNeeded;
    DWORD               padLength;

    assert(poolHeader != NULL);

    currentBlock = poolHeader -> PoolHead;

    // Determine if more memory is needed, attempt to add if needed.
    sizeNeeded = Size;

    if (currentBlock -> Size - currentBlock -> Index < sizeNeeded + AlignSize) {

        haveEnoughMemory = PoolMemAddMemory(poolHeader,sizeNeeded + AlignSize);
        currentBlock = poolHeader -> PoolHead;
    }

    // If there is enough memory available, return it.
    if (haveEnoughMemory) {
        if (AlignSize) {

            padLength = (DWORD) currentBlock + sizeof(POOLMEMORYBLOCK)
                + currentBlock -> Index;
            currentBlock -> Index += (AlignSize - (padLength % AlignSize)) % AlignSize;

        }

        //Now, get the address of the memory to return.
        rMemory = (PVOID)
            &(currentBlock->RawMemory[currentBlock -> Index]);

        currentBlock->Index += sizeNeeded;
    }

    return rMemory;
}


POOLMEM.H

#ifndef POOLMEM_H
#define POOLMEM_H

typedef LPVOID POOLHANDLE;

#define POOLMEMORYBLOCKSIZE 8192

POOLHANDLE WINAPI PoolMemInitPool ();
VOID       WINAPI PoolMemDestroyPool (IN POOLHANDLE Handle);
LPVOID     WINAPI PoolMemGetAlignedMemory(IN POOLHANDLE Handle, IN DWORD Size, IN DWORD AlignSize);

#define PoolMemCreateString(h,x)        ((LPTSTR) PoolMemGetAlignedMemory((h),(x)*sizeof(TCHAR)))
#define PoolMemCreateDword(h)           ((PDWORD) PoolMemGetMemory((h),sizeof(DWORD)))

__inline
LPVOID
PoolMemGetMemory (
    IN POOLHANDLE Handle,
    IN DWORD      Size
    )
{

    return PoolMemGetAlignedMemory(Handle,Size,0);

}

__inline
LPTSTR
PoolMemCreateStringA (
    IN POOLHANDLE Handle,
    IN DWORD      Size
    )
{
    return (LPSTR) PoolMemGetAlignedMemory(Handle,Size * sizeof(CHAR),sizeof(CHAR));
}

__inline
LPWSTR
PoolMemCreateStringW (
    IN POOLHANDLE Handle,
    IN DWORD      Size
    )
{
    return (LPWSTR) PoolMemGetAlignedMemory(Handle,Size * sizeof(WCHAR),sizeof(WCHAR));
}

__inline
PTSTR
PoolMemDuplicateStringA (
    IN POOLHANDLE    Handle,
    IN LPCSTR       StringToCopy
    )

{

    DWORD   stringLength;
    PSTR    rString;

    assert(StringToCopy);

    stringLength = (DWORD) _mbschr(StringToCopy,0) - (DWORD) StringToCopy + 1;
    rString      = PoolMemGetAlignedMemory(Handle,stringLength,sizeof(CHAR));

    if (rString) {

        _mbscpy(rString,StringToCopy);
    }

    return rString;
}

__inline
PWSTR
PoolMemDuplicateStringW (
    IN POOLHANDLE    Handle,
    IN LPCWSTR       StringToCopy
    )

{

    DWORD    stringLength;
    PWSTR    rString;

    assert(StringToCopy);

    stringLength = ((wcslen(StringToCopy)+1)*sizeof(WCHAR));
    rString      = PoolMemGetAlignedMemory(Handle,stringLength,sizeof(WCHAR));

    if (rString) {

        wcscpy(rString,StringToCopy);
    }

    return rString;
}

#endif


SAVECFG.C

/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    savecfg.c

Abstract:

    This source file implements code to save the Win9x environment to
    a settings file.  It writes a copy of all screen saver settings on
    a per-user basis.

Author:

    Jim Schmidt (jimschm) 11-Apr-1997

Revision History:

--*/

#include "pch.h"

BOOL
SaveDatFileKeyAndVal (
    IN      LPCSTR Key,
    IN      LPCSTR Val
    )
{
    //
    // This function is a wrapper to simplify writing to our settings file
    //

    return WritePrivateProfileString (g_User, Key, Val, g_SettingsFile);
}

BOOL
CopyRegValueToDatFile (
    IN      HKEY RegKey,
    IN      LPCSTR ValueName
    )
{
    LPCSTR DataPtr;
    DWORD rc;

    //
    // Obtain registry value data and copy it to our settings file
    //

    DataPtr = GetRegValueString (RegKey, ValueName);
    if (DataPtr) {
        return SaveDatFileKeyAndVal (ValueName, DataPtr);
    }

    // If not found or wrong data type, don't sweat it
    rc = GetLastError();
    return rc == ERROR_FILE_NOT_FOUND || rc == ERROR_SUCCESS;
}

#define WIN9X_MAX_SECTION  32768

BOOL
SaveControlIniSection (
    IN      LPCSTR ControlIniSection,
    IN      LPCSTR ScreenSaverName
    )
{
    LPSTR Buffer;
    LPSTR p;
    CHAR NewKey[MAX_PATH];
    BOOL b = TRUE;
    CHAR DataBuf[MAX_PATH];

    //
    // This function copies an entire section in control.ini to our
    // settings file.  It may not be necessary because control.ini will
    // still be around, but this guarantees if someone modifies
    // control.ini later, our migration will not break.
    //

    //
    // Allocate a generous buffer to hold all key names
    //

    Buffer = HeapAlloc (g_hHeap, 0, WIN9X_MAX_SECTION);
    if (!Buffer) {
        return FALSE;
    }

    //
    // Retrieve the key names
    //

    GetPrivateProfileString (
        ControlIniSection,
        NULL,
        S_EMPTY,
        Buffer,
        WIN9X_MAX_SECTION,
        S_CONTROL_INI
        );

    //
    // For each key name, copy it to our settings file
    //

    p = Buffer;

    while (*p) {
        if (CreateScreenSaverParamKey (ScreenSaverName, p, NewKey)) {
            GetPrivateProfileString (
                    ControlIniSection,
                    p,
                    S_EMPTY,
                    DataBuf,
                    MAX_PATH,
                    S_CONTROL_INI
                    );

            if (!SaveDatFileKeyAndVal (NewKey, DataBuf)) {
                b = FALSE;
                break;
            }
        }

        p = _mbschr (p, 0);
        p++;
    }

    //
    // Cleanup
    //

    HeapFree (g_hHeap, 0, Buffer);
    return b;
}


SCRNSAVE.C

/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    scrnsave.c

Abstract:

    This source file implements the seven required functions for a
    Windows NT 5.0 migration DLL.  The DLL demonstrates how the
    interface works by performing the Windows 9x screen saver
    upgrade.

    The source here is a subset of the actual screen saver DLL
    that ships with Windows NT Setup.

    This sample demonstrates:

      - How to detect installation of your application

      - A typical implementation of QueryVersion, Initialize9x,
        MigrateUser9x, MigrateSystem9x, InitializeNT,
        MigrateUserNT and MigraetSystemNT

      - How to provide language-dependent incompatibility
        messages to the user

      - How to remove Setup's incompatibility messages via
        [Handled] section

      - Saving settings to a temporarly file in the working
        directory

      - Mix use of ANSI and UNICODE

      - Use of the SetupLogError API

      - Deleting files

      - Handling the move from system to system32

Author:

    Jim Schmidt 11-Apr-1997

Revision History:

--*/

#include "pch.h"

//
// Constants
//

#define CP_USASCII          1252
#define END_OF_CODEPAGES    -1

//
// Code page array
//

INT   g_CodePageArray[] = {
            CP_USASCII,
            END_OF_CODEPAGES
            };

//
// Multi-sz (i.e., double-nul terminated) list of files to find
//

CHAR    g_ExeNamesBuf[] = "Blank Screen.scr\0"
                          "Curves and Colors.scr\0"
                          "Flying Through Space.scr\0"
                          "Scrolling Marquee.scr\0"
                          "Mystify Your Mind.scr\0";

//
// Copies of the working directory and source directory
//

LPSTR   g_WorkingDirectory = NULL;
LPSTR   g_SourceDirectories = NULL;         // multi-sz
LPSTR   g_SettingsFile = NULL;
LPSTR   g_MigrateDotInf = NULL;

//
// Registry locations and INI file sections
//

#define REGKEY_DESKTOP "Control Panel\\Desktop"
#define FULL_REGKEY_DESKTOP "HKR\\Control Panel\\Desktop"
#define FULL_REGKEY_PWD_PROVIDER "HKLM\\System\\CurrentControlSet\\Control\\PwdProvider\\SCRSAVE"

#define REGVAL_SCREENSAVEACTIVE "ScreenSaveActive"
#define REGVAL_SCREENSAVELOWPOWERACTIVE "ScreenSaveLowPowerActive"
#define REGVAL_SCREENSAVELOWPOWERTIMEOUT "ScreenSaveLowPowerTimeout"
#define REGVAL_SCREENSAVEPOWEROFFACTIVE "ScreenSavePowerOffActive"
#define REGVAL_SCREENSAVEPOWEROFFTIMEOUT "ScreenSavePowerOffTimeout"
#define REGVAL_SCREENSAVETIMEOUT "ScreenSaveTimeOut"
#define REGVAL_SCREENSAVEUSEPASSWORD "ScreenSaveUsePassword"

#define REGVAL_LOWPOWERACTIVE "LowPowerActive"
#define REGVAL_LOWPOWERTIMEOUT "LowPowerTimeout"
#define REGVAL_POWEROFFACTIVE "PowerOffActive"
#define REGVAL_POWEROFFTIMEOUT "PowerOffTimeout"
#define REGVAL_SCREENSAVERISSECURE "ScreenSaverIsSecure"

//
// State variables
//

BOOL g_FoundPassword = FALSE;
LPCSTR g_User;
CHAR g_UserNameBuf[MAX_PATH];
HANDLE g_hHeap;

BOOL
WINAPI
DllMain (
    IN      HANDLE DllInstance,
    IN      ULONG  ReasonForCall,
    IN      LPVOID Reserved
    )
{
    switch (ReasonForCall)  {

    case DLL_PROCESS_ATTACH:
        //
        // We don't need DLL_THREAD_ATTACH or DLL_THREAD_DETACH messages
        //
        DisableThreadLibraryCalls (DllInstance);

        //
        // Global init
        //
        g_hHeap = GetProcessHeap();

        if (!MigInf_Initialize()) {
            return FALSE;
        }

        // Open log; FALSE means do not delete existing log
        SetupOpenLog (FALSE);
        break;

    case DLL_PROCESS_DETACH:
        MigInf_CleanUp();

        // Clean up strings
        if (g_WorkingDirectory) {
            HeapFree (g_hHeap, 0, g_WorkingDirectory);
        }
        if (g_SourceDirectories) {
            HeapFree (g_hHeap, 0, g_SourceDirectories);
        }
        if (g_SettingsFile) {
            HeapFree (g_hHeap, 0, g_SettingsFile);
        }
        if (g_MigrateDotInf) {
            HeapFree (g_hHeap, 0, g_MigrateDotInf);
        }

        SetupCloseLog();

        break;
    }

    return TRUE;
}

LONG
CALLBACK
QueryVersion (
    OUT     LPCSTR *ProductID,
OUT     LPUINT DllVersion,
OUT     LPINT *CodePageArray,    OPTIONAL
OUT     LPCSTR *ExeNamesBuf,    OPTIONAL
        LPVOID Reserved
    )
{
    //
    // First, we do some preliminary investigation to see if
    // our components are installed.
    //

    if (!GetScrnSaveExe()) {
        //
        // We didn't detect any components, so we return
        // ERROR_NOT_INSTALLED and the DLL will stop being called.
        // Use this method as much as possible, because user enumeration
        // for MigrateUser9x is relatively slow.  However, don't spend too
        // much time here because QueryVersion is expected to run quickly.
        //
        return ERROR_NOT_INSTALLED;
    }

    //
    // Screen saver is enabled, so tell Setup who we are.  ProductID is used
    // for display, so it must be localized.  The ProductID string is
    // converted to UNICODE for use on Windows NT via the MultiByteToWideChar
    // Win32 API.  The first element of CodePageArray is used to specify
    // the code page of ProductID, and if no elements are returned in
    // CodePageArray, Setup assumes CP_ACP.
    //

    *ProductID  = g_MyProductId;

    //
    // Report our version.  Zero is reserved for use by DLLs that
    // ship with Windows NT.
    //

    *DllVersion = 1;

    //
    // Because we have English messages, we return an array that has
    // the English language ID.  The sublanguage is neutral because
    // we do not have currency, time, or other geographic-specific
    // information in our messages.
    //
    // Tip: If it makes more sense for your DLL to use locales,
    // return ERROR_NOT_INSTALLED if the DLL detects that an appropriate
    // locale is not installed on the machine.
    //

    *CodePageArray = g_CodePageArray;

    //
    // ExeNamesBuf - we pass a list of file names (the long versions)
    // and let Setup find them for us.  Keep this list short because
    // every instance of the file on every hard drive will be reported
    // in migrate.inf.
    //
    // Most applications don't need this behavior, because the registry
    // usually contains full paths to installed components.  We need it,
    // though, because there are no registry settings that give us the
    // paths of the screen saver DLLs.
    //

    *ExeNamesBuf = g_ExeNamesBuf;

    return ERROR_SUCCESS;
}

LONG
CALLBACK
Initialize9x (
IN      LPCSTR WorkingDirectory,
IN      LPCSTR SourceDirectories,
        LPVOID Reserved
    )
{
    INT Len;

    //
    // Because we returned ERROR_SUCCESS in QueryVersion, we are being
    // called for initialization.  Therefore, we know screen savers are
    // enabled on the machine at this point.
    //

    //
    // Make global copies of WorkingDirectory and SourceDirectories --
    // we will not get this information again, and we shouldn't
    // count on Setup keeping the pointer valid for the life of our
    // DLL.
    //

    Len = CountStringBytes (WorkingDirectory);
    g_WorkingDirectory = HeapAlloc (g_hHeap, 0, Len);

    if (!g_WorkingDirectory) {
        return GetLastError();
    }

    CopyMemory (g_WorkingDirectory, WorkingDirectory, Len);

    Len = CountMultiStringBytes (SourceDirectories);
    g_SourceDirectories = HeapAlloc (g_hHeap, 0, Len);

    if (!g_SourceDirectories) {
        return GetLastError();
    }

    CopyMemory (g_SourceDirectories, SourceDirectories, Len);

    //
    // Now create our private 'settings file' path
    //

    GenerateFilePaths();

    //
    // Return success to have MigrateUser9x called
    //
    // Tip: A DLL can save system settings during Initialize9x as
    //      well as MigrateSystem9x.
    //
    //

    return ERROR_SUCCESS;
}

LONG
CALLBACK
MigrateUser9x (
IN      HWND ParentWnd,
IN      LPCSTR UnattendFile,
IN      HKEY UserRegKey,
IN      LPCSTR UserName,
        LPVOID Reserved
)
{
    HKEY RegKey;
    LPCSTR ScrnSaveExe;
    DWORD rc = ERROR_SUCCESS;
    LPSTR SectionNameBuf, p;
    DWORD SectionNameSize;
    DWORD Len;

    //
    // This DLL does not require input from the user to upgrade
    // their settings, so ParentWnd is not used.  Avoid displaying
    // any user interface when possible.
    //
    // We don't need to use UnattendFile settings because we are not
    // a service (such as a network redirector).  Therefore, we do not
    // use the  UnattendFile parameter.
    //
    // We don't have any files that need to be generated or expanded on
    // the NT side of Setup, so we do not write to the
    // [NT Disk Space Requirements] section of migrate.inf.
    //

    //
    // We must collect a few registry keys:
    //
    //   HKCU\Control Panel\Desktop
    //        ScreenSaveActive
    //        ScreenSaveLowPowerActive
    //        ScreenSaveLowPowerTimeout
    //        ScreenSavePowerOffActive
    //        ScreenSavePowerOffTimeout
    //        ScreenSaveTimeOut
    //        ScreenSaveUsePassword
    //
    // If ScreenSave_Data exists, we tell the user that their
    // password is not supported by writing an incompatiility
    // message.
    //

    //
    // Save the user name in a global so our utils write to the
    // correct section.
    //

    if (UserName) {
        g_User = UserName;
    } else {
        g_User = S_DEFAULT_USER;
    }

    // OpenRegKey is our utility (in utils.c)
    RegKey = OpenRegKey (UserRegKey, REGKEY_DESKTOP);
    if (!RegKey) {
        //
        // User's registry is invalid, so skip the user
        //
        return ERROR_NOT_INSTALLED;
    }

    //
    // Note: NO changes allowed on Win9x side, we can only read our
    //       settings and save them in a file.
    //

    if (!CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVEACTIVE) ||
        !CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVELOWPOWERACTIVE) ||
        !CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVELOWPOWERTIMEOUT) ||
        !CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVEPOWEROFFACTIVE) ||
        !CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVEPOWEROFFTIMEOUT) ||
        !CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVETIMEOUT) ||
        !CopyRegValueToDatFile (RegKey, REGVAL_SCREENSAVEUSEPASSWORD)
        ) {
        rc = GetLastError();
    }

    if (atoi (GetRegValueString (RegKey, REGVAL_SCREENSAVEUSEPASSWORD))) {
        // Queue change so there is only one message
        g_FoundPassword = TRUE;
    }

    //
    // Save EXE location in our dat file
    //

    ScrnSaveExe = GetScrnSaveExe();

    if (ScrnSaveExe) {
        if (!SaveDatFileKeyAndVal (S_SCRNSAVE_EXE, ScrnSaveExe)) {
            rc = GetLastError();
        }
    }

    //
    // Copy control.ini sections to our dat file
    //

    SectionNameSize = 32768;
    SectionNameBuf = (LPSTR) HeapAlloc (g_hHeap, 0, SectionNameSize);
    if (!SectionNameBuf) {
        return GetLastError();
    }

    GetPrivateProfileString (
        NULL,
        NULL,
        S_DOUBLE_EMPTY,
        SectionNameBuf,
        SectionNameSize,
        S_CONTROL_INI
        );

    Len = _mbslen (S_SCRNSAVE_DOT);
    for (p = SectionNameBuf ; *p ; p = _mbschr (p, 0) + 1) {
        //
        // Determine if section name has "Screen Saver." at the beginning
        //

        if (!_mbsnicmp (p, S_SCRNSAVE_DOT, Len)) {
            //
            // It does, so save it to our private file
            //
            SaveControlIniSection (p, p + Len);
        }
    }

    CloseRegKey (RegKey);

    if (rc != ERROR_SUCCESS) {
        CHAR Msg[512];

        wsprintf (Msg, USER_PROCESSING_ERROR, g_User, rc);
        SetupLogError (Msg, LogSevError);
    } else {
        //
        // Write handled for every setting we are processing.  Because this
        // DLL supports only some of the values in the Desktop key, we must
        // be very specific as to which values are actually handled.  If
        // your DLL handles all registry values AND subkeys of a registry
        // key, you can specify NULL in the second parameter of
        // MigInf_AddHandledRegistry.
        //

        MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVEACTIVE);
        MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVELOWPOWERACTIVE);
        MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVELOWPOWERTIMEOUT);
        MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVEPOWEROFFACTIVE);
        MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVEPOWEROFFTIMEOUT);
        MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVETIMEOUT);
        MigInf_AddHandledRegistry (FULL_REGKEY_DESKTOP, REGVAL_SCREENSAVEUSEPASSWORD);
    }

    return rc;
}

LONG
CALLBACK

MigrateSystem9x (
IN      HWND ParentWnd,
IN      LPCSTR UnattendFile,
        LPVOID Reserved
)
{
    //
    // We handle the password provider incompatibility
    //

    MigInf_AddHandledRegistry (FULL_REGKEY_PWD_PROVIDER, NULL);

    //
    // Write incompatibility message if necessary (detected in MigrateUser9x)
    //

    if (g_FoundPassword) {
        MigInf_AddMessage (g_MyProductId, PASSWORD_ALERT);
        MigInf_AddMessageRegistry (
                g_MyProductId,
                FULL_REGKEY_DESKTOP,
                REGVAL_SCREENSAVEUSEPASSWORD
                );
    }

    //
    // Write memory version of migrate.inf to disk
    //

    if (!MigInf_WriteInfToDisk()) {
        return GetLastError();
    }

    return ERROR_SUCCESS;
}

LONG
CALLBACK
InitializeNT (
IN      LPCWSTR WorkingDirectory,
IN      LPCWSTR SourceDirectories,
        LPVOID Reserved
)
{
    INT Length;
    LPCWSTR p;

    //
    // Save our working directory and source directory.  We
    // convert UNICODE to ANSI because we know we are on a US
    // version of NT.  This conversion is not valid for far east
    // languages, because we use CP_ACP (ANSI code page).
    //
    // If your DLL supports far east languages, use UNICODE and
    // call the W version of Win32 functions that take strings.
    //

    //
    // Compute length of source directories
    //

    p = SourceDirectories;
    while (*p) {
        p = wcschr (p, 0) + 1;
    }
    p++;
    Length = (p - SourceDirectories) / sizeof (WCHAR);

    //
    // Convert the directories from UNICODE to DBCS.  This DLL is
    // compiled in ANSI.
    //

    g_WorkingDirectory = (LPSTR) HeapAlloc (g_hHeap, 0, MAX_PATH);
    if (!g_WorkingDirectory) {
        return GetLastError();
    }

    WideCharToMultiByte (
        CP_ACP,
        0,
        WorkingDirectory,
        -1,
        g_WorkingDirectory,
        MAX_PATH,
        NULL,
        NULL
        );

    g_SourceDirectories = (LPSTR) HeapAlloc (g_hHeap, 0, Length * sizeof(WCHAR));
    if (!g_SourceDirectories) {
        return GetLastError();
    }

    WideCharToMultiByte (
        CP_ACP,
        0,
        SourceDirectories,
        Length,
        g_SourceDirectories,
        Length * sizeof (WCHAR),
        NULL,
        NULL
        );

    //
    // Now generate the derived file names
    //

    GenerateFilePaths();

    //
    // Note: We have no use for g_SourceDirectories for the screen saver
    //       upgrade.  The g_SourceDirectories string points to the Windows
    //       NT media (i.e. e:\i386) and optional directories specified on
    //       the WINNT32 command line.
    //

    return ERROR_SUCCESS;
}

LONG
CALLBACK
MigrateUserNT (
IN      HINF UnattendInfHandle,
IN      HKEY UserRegKey,
IN      LPCWSTR UserName,
            LPVOID Reserved
)
{
    HKEY DesktopRegKey;
    DWORD rc = ERROR_SUCCESS;
    BOOL b = TRUE;

    //
    // Setup gives us the UnattendInfHandle instead of the file name,
    // so we don't have to open the inf file repeatitively.  Since
    // Setup opened the handle, let Setup close it.
    //

    //
    // Convert UserName to ANSI
    //

    if (UserName) {
        WideCharToMultiByte (
            CP_ACP,
            0,
            UserName,
            -1,
            g_UserNameBuf,
            MAX_PATH,
            NULL,
            NULL
            );

        g_User = g_UserNameBuf;
    } else {
        g_User = S_DEFAULT_USER;
    }

    //
    // Setup copies all of the Win9x registry, EXCEPT for the registry
    // keys that are suppressed in usermig.inf or wkstamig.inf.
    //
    // We need the HKCU\Control Panel\Desktop key, and because this is
    // an OS key, the settings have been altered. Most applications
    // store their settings in HKCU\Software, HKLM\Software or
    // HKCC\Software, and all three of these keys are copied in their
    // entirety (except the operating system settings in
    // Software\Microsoft\Windows).
    //
    // When the non-OS software settings are copied from Win9x to NT, Setup
    // sometimes alters their value.  For example, all registry values
    // that point to a file that was moved from SYSTEM to SYSTEM32
    // are modified to point to the right place.
    //

    //
    // Note: we use CreateRegKey here, but actually the key always exists
    // because the NT defaults have been copied into the user's registry
    // already.  This approach reduces the possibility of failure.
    //

    DesktopRegKey = CreateRegKey (UserRegKey, REGKEY_DESKTOP);
    if (!DesktopRegKey) {
        CHAR Msg[512];

        rc = GetLastError();
        wsprintf (Msg, S_REGISTRY_ERROR, g_User, rc);
        SetupLogError (Msg, LogSevError);

        return rc;
    }

    // The variable b is used to fall through when we fail unexpectedly

    b = TranslateGeneralSetting (
            DesktopRegKey,
            REGVAL_SCREENSAVEACTIVE,
            NULL
            );

    if (b) {
        b = TranslateGeneralSetting (
                DesktopRegKey,
                REGVAL_SCREENSAVELOWPOWERACTIVE,
                REGVAL_LOWPOWERACTIVE
                );
    }
    if (b) {
        b = TranslateGeneralSetting (
                DesktopRegKey,
                REGVAL_SCREENSAVELOWPOWERTIMEOUT,
                REGVAL_LOWPOWERTIMEOUT
                );
    }
    if (b) {
        b = TranslateGeneralSetting (
                DesktopRegKey,
                REGVAL_SCREENSAVEPOWEROFFACTIVE,
                REGVAL_POWEROFFACTIVE
                );
    }
    if (b) {
        b = TranslateGeneralSetting (
                DesktopRegKey,
                REGVAL_SCREENSAVEPOWEROFFTIMEOUT,
                REGVAL_POWEROFFTIMEOUT
                );
    }
    if (b) {
        b = TranslateGeneralSetting (
                DesktopRegKey,
                REGVAL_SCREENSAVETIMEOUT,
                NULL
                );
    }
    if (b) {
        b = TranslateGeneralSetting (
                DesktopRegKey,
                REGVAL_SCREENSAVEUSEPASSWORD,
                REGVAL_SCREENSAVERISSECURE
                );
    }

    if (b) {
        b = SaveScrName (DesktopRegKey, S_SCRNSAVE_EXE);
    }

    if (b) {
        //
        // For screen savers work differently on Win9x and NT, perform
        // translation.
        //

        TranslateScreenSavers (UserRegKey);

        //
        // The other settings just need to be copied from control.ini
        // to the registry.
        //

        CopyUntranslatedSettings (UserRegKey);
    }

    CloseRegKey (DesktopRegKey);

    //
    // Always return success, because if an error occurred for one user,
    // we don't have a reason not to process the next user.  If your DLL
    // runs into a fatal problem, such as a disk space shortage, you
    // should return the error.
    //

    return ERROR_SUCCESS;
}

LONG
CALLBACK
MigrateSystemNT (
IN      HINF UnattendInfHandle,
            LPVOID Reserved
)
{
    CHAR FileName[MAX_PATH];
    HINF MigrateInf;
    INFCONTEXT ic;
    CHAR Msg[512];

    //
    // We now delete the Win9x screen savers that were replaced
    // by Windows NT.
    //

    MigrateInf = SetupOpenInfFile (
                        g_MigrateDotInf,
                        NULL,
                        INF_STYLE_WIN4,
                        NULL
                        );

    if (MigrateInf != INVALID_HANDLE_VALUE) {

        //
        // Use Setup APIs to scan migration paths section
        //

        if (SetupFindFirstLine (MigrateInf, S_MIGRATION_PATHS, NULL, &ic)) {
            do {
                if (SetupGetStringField (&ic, 0, FileName, MAX_PATH, NULL)) {
                    //
                    // All 32-bit binaries located in the Win9x system directory
                    // were moved to system32.  However, the paths reported in
                    // [Migration Paths] are the original Win9x paths.  We
                    // convert c:\windows\system\file to c:\windows\system32\file.
                    //

                    ConvertSystemToSystem32 (FileName);

                    //
                    // Now delete the file. Ignore errors because user may have
                    // lost power, and we may be going through this a second time.
                    //

                    if (!DeleteFile (FileName)) {
                        wsprintf (Msg, DELETEFILE_ERROR, FileName, GetLastError());
                        SetupLogError (Msg, LogSevError);
                    } else {
                        wsprintf (Msg, DELETEFILE_SUCCESS, FileName);
                        SetupLogError (Msg, LogSevInformation);
                    }
                }
            } while (SetupFindNextLine (&ic, &ic));
        }

        SetupCloseInfFile (MigrateInf);
    }

    return ERROR_SUCCESS;
}


SCRNSAVE.H

#include "miginf.h"

//
// utils.c -- reusable utilities (unrelated to screen saver code)
//

VOID
LogRegistryError (
    IN      HKEY Key,
    IN      LPCSTR ValueName
    );

HKEY
OpenRegKey (
    IN      HKEY RootKey,
    IN      LPCSTR Key
    );

HKEY
CreateRegKey (
    IN      HKEY RootKey,
    IN      LPCSTR Key
    );

VOID
CloseRegKey (
    IN      HKEY Key
    );

LPCSTR
GetRegValueString (
    IN      HKEY Key,
    IN      LPCSTR ValueName
    );

BOOL
SetRegValueString (
    HKEY Key,
    LPCSTR ValueName,
    LPCSTR ValueStr
    );

DWORD
CountStringBytes (
    IN      LPCSTR str
    );

DWORD
CountMultiStringBytes (
    IN      LPCSTR str
    );

LPSTR
CopyStringAtoB (
    OUT     LPSTR mbstrDest,
    IN      LPCSTR mbstrStart,
    IN      LPCSTR mbstrEnd     // ptr to the first char NOT to copy
    );

BOOL
OurGetLongPathName (
    LPCSTR ShortPath,
    LPSTR Buffer
    );

VOID
ConvertSystemToSystem32 (
    IN OUT  LPSTR Filename
    );

//
// utils.c -- helper functions specific to screen saver settings
//

VOID
GenerateFilePaths (
    VOID
    );

LPCSTR
GetScrnSaveExe (
    VOID
    );

BOOL
CreateScreenSaverParamKey (
    IN      LPCSTR ScreenSaverName,
    IN      LPCSTR Value,
    OUT     LPSTR Buffer
    );

BOOL
DecodeScreenSaverParamKey (
    IN      LPCSTR EncodedString,
    OUT     LPSTR ScreenSaverName,
    OUT     LPSTR ValueName
    );

//
// savecfg.c -- functions that save things to our working directory
//

BOOL
SaveDatFileKeyAndVal (
    IN      LPCSTR Key,
    IN      LPCSTR Val
    );

BOOL
CopyRegValueToDatFile (
    IN      HKEY RegKey,
    IN      LPCSTR ValueName
    );

BOOL
SaveControlIniSection (
    LPCSTR ControlIniSection,
    LPCSTR ScreenSaverName
    );

//
// dataconv.c -- functions that convert Win9x settings & save them on NT
//

BOOL
CopyValuesFromDatFileToRegistry (
    IN      HKEY RegKey,
    IN      LPCSTR RegKeyName,
    IN      LPCSTR ScreenSaverName,
    IN      LPCSTR ValueArray[]
    );

LPCSTR
GetSettingsFileVal (
    IN      LPCSTR Key
    );

BOOL
TranslateGeneralSetting (
    IN      HKEY RegKey,
    IN      LPCSTR Win9xSetting,
    IN      LPCSTR WinNTSetting
    );

BOOL
SaveScrName (
    IN      HKEY RegKey,
    IN      LPCSTR KeyName
    );

BOOL
TranslateScreenSavers (
    IN      HKEY RegRoot
    );

BOOL
CopyUntranslatedSettings (
    IN      HKEY RegRoot
    );

//
// DLL globals
//

extern LPCSTR g_User;
extern LPSTR g_SettingsFile;
extern LPSTR g_MigrateDotInf;
extern LPSTR g_WorkingDirectory;
extern LPSTR g_SourceDirectories;
extern HANDLE g_hHeap;

//
// Define strings
//

#define DEFINE_LOCALIZED_STRINGS                                            \
    DEFMAC (S_HUGEDATA_ERROR,   "An invalid screen saver was detected "     \
                                "and skipped.\r\n")                         \
                                                                            \
    DEFMAC (S_DEFAULT_USER,     "Default User")                             \
                                                                            \
    DEFMAC (g_MyProductId,      "Windows 95 Screen Savers")                 \
                                                                            \
    DEFMAC (PASSWORD_ALERT,     "The current screen saver password may be " \
                                "different from the password expected by "  \
                                "Windows NT.  On Windows NT, use your "     \
                                "logon password when a screen saver "       \
                                "prompts for a password.")                  \
                                                                            \
    DEFMAC (USER_PROCESSING_ERROR, "An error occurred processing screen "   \
                                "saver settings for user %s.  The screen "  \
                                "saver will be disabled for this user.  "   \
                                "(Error: %u)\r\n")                          \
                                                                            \
    DEFMAC (DELETEFILE_SUCCESS, "Deleted Windows 95 screen saver %s\r\n")   \
                                                                            \
    DEFMAC (DELETEFILE_ERROR,   "An error occurred attempting to delete "   \
                                "the Windows 95 screen saver %s.  (Error: " \
                                "%u)\r\n")                                  \
                                                                            \
    DEFMAC (S_BEZIER_DATA_ERROR, "Some of the Bezier screen saver "         \
                                "settings are invalid and cannot be "       \
                                "transfered to Windows NT.\r\n")            \
                                                                            \
    DEFMAC (S_REGISTRY_ERROR,   "An error occurred saving the screen "      \
                                "saver settings for user %s.  (Error: %u, " \
                                "%s [%s])\r\n")                             \
                                                                            \

#define DEFINE_NONLOCALIZED_STRINGS                                         \
    DEFMAC (S_BOOT,             "boot")                                     \
    DEFMAC (S_SCRNSAVE_EXE,     "SCRNSAVE.EXE")                             \
    DEFMAC (S_SCRNSAVE_DOT,     "Screen Saver.")                            \
    DEFMAC (S_SCRNSAVE_MASK,    "Screen Saver.%s")                          \
    DEFMAC (S_EMPTY,            "")                                         \
    DEFMAC (S_DOUBLE_EMPTY,     "\0")                                       \
    DEFMAC (S_SETTINGS_MASK,    "%s\\settings.dat")                         \
    DEFMAC (S_MIGINF_MASK,      "%s\\migrate.inf")                          \
    DEFMAC (S_SYSTEM_INI,       "system.ini")                               \
    DEFMAC (S_CONTROL_INI,      "control.ini")                              \
    DEFMAC (S_BEZIER,           "Bezier")                                   \
    DEFMAC (S_MARQUEE,          "Marquee")                                  \
    DEFMAC (S_BEZIER_SETTINGS,  "Screen Saver.Bezier")                      \
    DEFMAC (S_MARQUEE_SETTINGS, "Screen Saver.Marquee")                     \
    DEFMAC (S_MIGRATION_PATHS,  "Migration Paths")                          \
    DEFMAC (S_CONTROL_PANEL_MASK, "Control Panel\\%s")                      \
    DEFMAC (S_LENGTH,           "Length")                                   \
    DEFMAC (S_WIDTH,            "Width")                                    \
    DEFMAC (S_LINESPEED,        "LineSpeed")                                \
    DEFMAC (S_BACKGROUND_COLOR, "BackgroundColor")                          \
    DEFMAC (S_CHARSET,          "CharSet")                                  \
    DEFMAC (S_FONT,             "Font")                                     \
    DEFMAC (S_MODE,             "Mode")                                     \
    DEFMAC (S_SIZE,             "Size")                                     \
    DEFMAC (S_SPEED,            "Speed")                                    \
    DEFMAC (S_TEXT,             "Text")                                     \
    DEFMAC (S_TEXTCOLOR,        "TextColor")                                \
    DEFMAC (S_ACTIVE1,          "Active1")                                  \
    DEFMAC (S_ACTIVE2,          "Active2")                                  \
    DEFMAC (S_CLEAN_SCREEN,     "Clean Screen")                             \
    DEFMAC (S_ENDCOLOR1,        "EndColor1")                                \
    DEFMAC (S_ENDCOLOR2,        "EndColor2")                                \
    DEFMAC (S_LINES1,           "Lines1")                                   \
    DEFMAC (S_LINES2,           "Lines2")                                   \
    DEFMAC (S_STARTCOLOR1,      "StartColor1")                              \
    DEFMAC (S_STARTCOLOR2,      "StartColor2")                              \
    DEFMAC (S_WALKRANDOM1,      "WalkRandom1")                              \
    DEFMAC (S_WALKRANDOM2,      "WalkRandom2")                              \
    DEFMAC (S_DENSITY,          "Denisty")                                  \
    DEFMAC (S_WARPSPEED,        "WarpSpeed")                                \
    DEFMAC (S_HKR,              "HKR")                                      \
    DEFMAC (S_DEFAULT_KEYSTR,   "(unknown regkey)")                         \
    DEFMAC (S_SYSTEM_DIR,       "\\system\\")                               \
    DEFMAC (S_SYSTEM32_DIR,     "\\system32\\")                             \

#define DEFINE_STRINGS DEFINE_LOCALIZED_STRINGS DEFINE_NONLOCALIZED_STRINGS

//
// Declare externs for all string variables
//

#define DEFMAC(var,str) extern CHAR var[];
DEFINE_STRINGS
#undef DEFMAC


UTILS.C

/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    utils.c

Abstract:

    This source file implements utility functions used by scrnsave.c.

Author:

    Jim Schmidt (jimschm) 11-Apr-1997

Revision History:

--*/

#include "pch.h"

#ifdef UNICODE
#error UNICODE cannot be defined
#endif

//
// Declare strings
//

#define DEFMAC(var,str) CHAR var[] = str;
DEFINE_STRINGS
#undef DEFMAC

//
// Temporary buffer
//

static CHAR g_Data[MAX_PATH];

//
// Buffer for string representation of registry keys (for error logging)
//

typedef struct _tagKEYTOSTR {
    struct _tagKEYTOSTR *Prev, *Next;
    HKEY Key;
    CHAR RegKey[];
} KEYTOSTR, *PKEYTOSTR;

static PKEYTOSTR g_Head = NULL;

VOID
pAddKeyToStrMapping (
    IN      HKEY Key,
    IN      LPCSTR RootStr,
    IN      LPCSTR KeyStr
    )
{
    PKEYTOSTR Node;
    DWORD Size;
    CHAR FullKeyStr[MAX_PATH];

    // We control RootStr and KeyStr, so we know it is less than MAX_PATH in length
    wsprintf (FullKeyStr, "%s\\%s", RootStr, KeyStr);

    Size = sizeof (KEYTOSTR) + CountStringBytes (FullKeyStr);

    Node = (PKEYTOSTR) HeapAlloc (g_hHeap, 0, Size);
    if (Node) {
        Node->Prev = NULL;
        Node->Next = g_Head;
        Node->Key = Key;
        _mbscpy (Node->RegKey, FullKeyStr);

        if (g_Head) {
            g_Head->Prev = Node;
        }
        g_Head = Node;
    }
}

PKEYTOSTR
pFindKeyToStrMapping (
    IN      HKEY Key
    )
{
    PKEYTOSTR Node;

    Node = g_Head;
    while (Node) {
        if (Node->Key == Key) {
            return Node;
        }
        Node = Node->Next;
    }

    return NULL;
}

VOID
pRemoveKeyToStrMapping (
    IN      HKEY Key
    )
{
    PKEYTOSTR Node;

    Node = pFindKeyToStrMapping (Key);
    if (!Node) {
        return;
    }

    if (Node->Prev) {
        Node->Prev->Next = Node->Next;
    } else {
        g_Head = Node->Next;
    }

    if (Node->Next) {
        Node->Next->Prev = Node->Prev;
    }

    HeapFree (g_hHeap, 0, Node);
}

VOID
LogRegistryError (
    IN      HKEY Key,
    IN      LPCSTR ValueName
    )
{
    DWORD rc = GetLastError();
    CHAR Msg[512];
    LPCSTR FullKeyStr;
    PKEYTOSTR Node;

    Node = pFindKeyToStrMapping (Key);
    if (Node) {
        FullKeyStr = Node->RegKey;
    } else {
        FullKeyStr = S_DEFAULT_KEYSTR;
    }

    wsprintf (Msg, S_REGISTRY_ERROR, g_User, rc, FullKeyStr, ValueName);
    SetupLogError (Msg, LogSevError);
}

VOID
GenerateFilePaths (
    VOID
    )
{
    INT Len;

    // Safety (unexpected condition)
    if (!g_WorkingDirectory) {
        return;
    }

    Len = CountStringBytes (g_WorkingDirectory) + sizeof(S_SETTINGS_MASK);
    g_SettingsFile = (LPSTR) HeapAlloc (g_hHeap, 0, Len);
    if (!g_SettingsFile) {
        return;
    }
    wsprintf (g_SettingsFile, S_SETTINGS_MASK, g_WorkingDirectory);

    Len = CountStringBytes (g_WorkingDirectory) + sizeof(S_MIGINF_MASK);
    g_MigrateDotInf = (LPSTR) HeapAlloc (g_hHeap, 0, Len);
    if (!g_MigrateDotInf) {
        return;
    }
    wsprintf (g_MigrateDotInf, S_MIGINF_MASK, g_WorkingDirectory);
}

HKEY
OpenRegKey (
    IN      HKEY RootKey,
    IN      LPCSTR KeyStr
    )
{
    HKEY Key;
    LONG rc;

    rc = RegOpenKeyEx (RootKey, KeyStr, 0, KEY_ALL_ACCESS, &Key);
    if (rc != ERROR_SUCCESS) {
        SetLastError (rc);
        return NULL;
    }

    pAddKeyToStrMapping (Key, S_HKR, KeyStr);

    return Key;
}

HKEY
CreateRegKey (
    IN      HKEY RootKey,
    IN      LPCSTR KeyStr
    )
{
    HKEY Key;
    LONG rc;
    DWORD DontCare;

    pAddKeyToStrMapping (NULL, S_HKR, KeyStr);

    rc = RegCreateKeyEx (RootKey, KeyStr, 0, S_EMPTY, 0,
                         KEY_ALL_ACCESS, NULL, &Key, &DontCare);
    if (rc != ERROR_SUCCESS) {
        SetLastError (rc);
        LogRegistryError (NULL, S_EMPTY);
        pRemoveKeyToStrMapping (NULL);
        return NULL;
    }

    pRemoveKeyToStrMapping (NULL);
    pAddKeyToStrMapping (Key, S_HKR, KeyStr);

    return Key;
}

VOID
CloseRegKey (
    IN      HKEY Key
    )
{
    pRemoveKeyToStrMapping (Key);
    RegCloseKey (Key);
}

LPCSTR
GetRegValueString (
    IN      HKEY Key,
    IN      LPCSTR ValueName
    )
{
    static CHAR DataBuf[MAX_PATH];
    DWORD Size;
    LONG rc;
    DWORD Type;
    DWORD d;

    Size = MAX_PATH;
    rc = RegQueryValueEx (Key, ValueName, NULL, &Type, DataBuf, &Size);
    SetLastError (rc);

    if (rc != ERROR_SUCCESS) {
        return NULL;
    }

    if (Type == REG_DWORD) {
        d = *((PDWORD) DataBuf);
        wsprintf (DataBuf, "%u", d);
    }
    else if (Type != REG_SZ) {
        return NULL;
    }

    return DataBuf;
}

BOOL
SetRegValueString (
    HKEY Key,
    LPCSTR ValueName,
    LPCSTR ValueStr
    )
{
    LONG rc;
    LPCSTR p;

    p = _mbschr (ValueStr, 0);
    p++;

    rc = RegSetValueEx (Key, ValueName, 0, REG_SZ, ValueStr, p - ValueStr);
    SetLastError (rc);

    if (rc != ERROR_SUCCESS) {
        LogRegistryError (Key, ValueName);
    }

    return rc == ERROR_SUCCESS;
}

LPCSTR
GetScrnSaveExe (
    VOID
    )
{
    CHAR IniFileSetting[MAX_PATH];

    GetPrivateProfileString (
            S_BOOT,
            S_SCRNSAVE_EXE,
            S_EMPTY,
            IniFileSetting,
            MAX_PATH,
            S_SYSTEM_INI
            );

    if (!IniFileSetting[0]) {
        return NULL;
    }

    if (!OurGetLongPathName (IniFileSetting, g_Data)) {
        // File does not exist
        return NULL;
    }

    return g_Data[0] ? g_Data : NULL;
}

INT
_mbsbytes (
    IN      LPCSTR str
    )
{
    LPCSTR p;

    // Find the nul terminator and return the number of bytes
    // occupied by the characters in the string, but don't
    // include the nul.

    p = _mbschr (str, 0);
    return (p - str);
}

DWORD
CountStringBytes (
    IN      LPCSTR str
    )
{
    // Return bytes in string, plus 1 for the nul
    return _mbsbytes (str) + 1;
}

DWORD
CountMultiStringBytes (
    IN      LPCSTR str
    )
{
    LPCSTR p;
    INT Total = 0;
    INT Bytes;

    p = str;

    do {
        Bytes = CountStringBytes (p);
        p += Bytes;
        Total += Bytes;
    } while (Bytes > 1);

    return Total;
}

LPSTR
CopyStringAtoB (
    OUT     LPSTR mbstrDest,
    IN      LPCSTR mbstrStart,
    IN      LPCSTR mbstrEnd     // first char NOT to copy
    )
{
    LPSTR mbstrOrg;

    mbstrOrg = mbstrDest;

    // Assume mbstrEnd is on a lead byte

    while (mbstrStart < mbstrEnd) {
        if (isleadbyte (*mbstrStart)) {
            *mbstrDest = *mbstrStart;
            mbstrDest++;
            mbstrStart++;
        }

        *mbstrDest = *mbstrStart;
        mbstrDest++;
        mbstrStart++;
    }

    *mbstrDest = 0;

    return mbstrOrg;
}

LPSTR
AppendStr (
    OUT     LPSTR mbstrDest,
    IN      LPCSTR mbstrSrc
    )

{
    // Advance mbstrDest to end of string
    mbstrDest = _mbschr (mbstrDest, 0);

    // Copy string
    while (*mbstrSrc) {
        *mbstrDest = *mbstrSrc++;
        if (isleadbyte (*mbstrDest)) {
            mbstrDest++;
            *mbstrDest = *mbstrSrc++;
        }
        mbstrDest++;
    }

    *mbstrDest = 0;

    return mbstrDest;
}

BOOL
pFindShortName (
    IN      LPCTSTR WhatToFind,
    OUT     LPTSTR Buffer
    )
{
    WIN32_FIND_DATA fd;
    HANDLE hFind;

    hFind = FindFirstFile (WhatToFind, &fd);
    if (!hFind) {
        return FALSE;
    }

    FindClose (hFind);
    _mbscpy (Buffer, fd.cFileName);

    return TRUE;
}

BOOL
OurGetLongPathName (
    IN      LPCSTR ShortPath,
    OUT     LPSTR Buffer
    )
{
    CHAR FullPath[MAX_PATH];
    LPSTR FilePart;
    LPSTR BufferEnd;
    LPSTR p, p2;
    CHAR c;

    //
    // Convert ShortPath into complete path name
    //

    if (!_mbschr (ShortPath, TEXT('\\'))) {
        if (!SearchPath (NULL, ShortPath, NULL, MAX_PATH, FullPath, &FilePart)) {
            return FALSE;
        }
    } else {
        GetFullPathName (ShortPath, MAX_PATH, FullPath, &FilePart);
    }

    //
    // Convert short path to long path
    //

    p = FullPath;

    // Don't process non-local paths
    if (!(*p) || _mbsnextc (_mbsinc (p)) != TEXT(':')) {
        _mbscpy (Buffer, FullPath);
        return TRUE;
    }

    p = _mbsinc (p);
    p = _mbsinc (p);
    if (_mbsnextc (p) != TEXT('\\')) {
        _mbscpy (Buffer, FullPath);
        return TRUE;
    }

    // Copy drive letter to buffer
    p = _mbsinc (p);
    CopyStringAtoB (Buffer, FullPath, p);
    BufferEnd = _mbschr (Buffer, 0);

    // Convert each portion of the path
    do {
        // Locate end of this file or dir
        p2 = _mbschr (p, TEXT('\\'));
        if (!p2) {
            p = _mbschr (p, 0);
        } else {
            p = p2;
        }

        // Look up file
        c = *p;
        *p = 0;
        if (!pFindShortName (FullPath, BufferEnd)) {
            return FALSE;
        }
        *p = c;

        // Move on to next part of path
        BufferEnd = _mbschr (BufferEnd, 0);
        if (*p) {
            p = _mbsinc (p);
            BufferEnd = AppendStr (BufferEnd, TEXT("\\"));
        }
    } while (*p);

    return TRUE;
}

BOOL
CreateScreenSaverParamKey (
    IN      LPCSTR ScreenSaverName,
    IN      LPCSTR ValueName,
    OUT     LPSTR Buffer
    )
{
    //
    // Make sure we cannot create a string bigger than MAX_PATH
    //

    if (_mbslen (ScreenSaverName) + 4 + _mbslen (ValueName) > MAX_PATH) {
        return FALSE;
    }

    //
    // Format the string with length of screen saver name, screen saver name,
    // and value name.
    //

    wsprintf (Buffer, "%03u/%s/%s", _mbslen (ScreenSaverName), ScreenSaverName, ValueName);

    return TRUE;
}

BOOL
DecodeScreenSaverParamKey (
    IN      LPCSTR EncodedString,
    OUT     LPSTR ScreenSaverName,
    OUT     LPSTR ValueName
    )
{
    INT Len;

    //
    // Validate encoded string.  It is in the form of ###/screen saver name/value name.
    //

    Len = atoi (EncodedString);
    if (Len < 0 || Len >= MAX_PATH || (Len - 5) > (INT) _mbslen (EncodedString)) {
        return FALSE;
    }

    if (EncodedString[3] != '/' || EncodedString[4 + Len] != '/') {
        return FALSE;
    }

    if (_mbslen (EncodedString + 5 + Len) >= MAX_PATH) {
        return FALSE;
    }

    //
    // Extract screen saver name and value name
    //

    _mbsncpy (ScreenSaverName, EncodedString + 4, Len);
    ScreenSaverName[Len] = 0;

    _mbscpy (ValueName, EncodedString + 5 + Len);
    return TRUE;
}

LPSTR
_mbsistr (
    IN      LPCSTR mbstrStr,
    IN      LPCSTR mbstrSubStr
    )
{
    LPCSTR mbstrStart, mbstrStrPos, mbstrSubStrPos;
    LPCSTR mbstrEnd;

    mbstrEnd = (LPSTR) ((LPBYTE) mbstrStr + _mbsbytes (mbstrStr) - _mbsbytes (mbstrSubStr));

    for (mbstrStart = mbstrStr ; mbstrStart <= mbstrEnd ; mbstrStart = _mbsinc (mbstrStart)) {
        mbstrStrPos = mbstrStart;
        mbstrSubStrPos = mbstrSubStr;

        while (*mbstrSubStrPos &&
               _mbctolower ((INT) _mbsnextc (mbstrSubStrPos)) == _mbctolower ((INT) _mbsnextc (mbstrStrPos)))
        {
            mbstrStrPos = _mbsinc (mbstrStrPos);
            mbstrSubStrPos = _mbsinc (mbstrSubStrPos);
        }

        if (!(*mbstrSubStrPos))
            return (LPSTR) mbstrStart;
    }

    return NULL;
}

VOID
DeletePartOfString (
    IN      LPSTR Buffer,
    IN      DWORD CharsToDelete
    )
{
    LPSTR p;
    DWORD d;

    p = Buffer;
    for (d = 0 ; *p && d < CharsToDelete ; d++) {
        p = _mbsinc (p);
    }

    if (!(*p)) {
        *Buffer = 0;
    } else {
        MoveMemory (Buffer, p, CountStringBytes(p));
    }
}

VOID
InsertStringInString (
    IN      LPSTR Buffer,
    IN      LPCSTR StringToInsert
    )
{
    DWORD BytesToMove;
    DWORD BytesOfInsertedString;

    BytesToMove = CountStringBytes (Buffer);
    BytesOfInsertedString = _mbsbytes(StringToInsert);
    MoveMemory (Buffer + BytesOfInsertedString,
                Buffer,
                BytesToMove
                );
    _mbsncpy (Buffer, StringToInsert, _mbslen (StringToInsert));
}

VOID
ConvertSystemToSystem32 (
    IN OUT  LPSTR FileName
    )
{
    LPSTR p;

    // look for \system\ in the path somewhere
    p = _mbsistr (FileName, S_SYSTEM_DIR);
    if (p) {
        // Remove "\system\" and replace with "\system32\"
        DeletePartOfString (p, _mbslen (S_SYSTEM_DIR));
        InsertStringInString (p, S_SYSTEM32_DIR);
    }
}