/*++

Module Name:

    devctl.c

Abstract:

    A Win32 application for generating I/O requests to test the functioning of a driver based
    software write blocker.
 

Author:

    Dan Allen - June, 2004
    National Institute of Standards and Technology

Environment:

    User mode.

Description:

    The Software Write Blocker test suite uses two kernel mode filter drivers to bracket
    the write blocker driver(s) under test.  An upper filter driver (Pitcher.sys) is used
    to build and throw IRP requests at the write blocker. A lower filter driver (Catcher.sys)
    is used to catch and complete all IRP's thrown by the upper filter.

    The upper filter driver implements a custom device IOCTL for communicating with
    user mode appications (IOCTL_IRP_PASSTHRU). The input argument for this IOCTL is
    a structure containing the basic data for a kernel mode IRP. When the upper filter
    receives an IOCTL_IRP_PASSTHRU request, it builds an IRP from the data in the input buffer
    and passes it to the next lower driver on the associated device stack. When the IRP completes,
    the I/O status block for the IRP is returned in the output buffer of the IOCTL request.

    The lower filter driver catches and completes any test IRP's passed by the write blocker.
    When a test IRP is caught and completed by the lower filter a custom status is returned
    in the IOSB. This status indicates the IRP was completed by Catcher.sys.

    This application uses this mechanism to generate a series of test IRP's for the write
    blocker under test and produce a report detailing which IRP's were blocked and which IRP's
    were not.
 

Revision History:

--*/

#include <ntddk.h>			// Get DDK definitions
#include "..\..\Include\swbts.h"	// Include custom IOCTL definitions
#undef NT_INCLUDED			// Pretend ntddk.h never happened
#pragma warning(push) 			// Save compiler warning settings
#pragma warning (disable: 4005) 	// Disable macro redefinition warnings
#undef InterlockedCompareExchange	// Remove brain dead macro definitions
#undef InterlockedCompareExchange64	// of these function names set by ntddk.h
#undef _SLIST_HEADER_			// Suppress SLIST definitions in winbase.h
#include <xwindows.h> 			// Use our expurgated version of windows.h
#include <winioctl.h>			// Use stock definitions
#pragma warning (pop)			// Restore compiler settings
#include <stdio.h>			// Use stock definitions
#include <scsi.h>
#include <setupapi.h>

#define NAME_COUNT  25

#define BOOLEAN_TO_STRING(_b_) \
( (_b_) ? "True" : "False" )

void PrintError(ULONG);

#if defined(_X86_)
    #define PAGE_SIZE  0x1000
    #define PAGE_SHIFT 12L
#elif defined(_AMD64_)
    #define PAGE_SIZE  0x1000
    #define PAGE_SHIFT 12L
#elif defined(_IA64_)
    #define PAGE_SIZE 0x2000
    #define PAGE_SHIFT 13L
#else
    // undefined platform?
    #define PAGE_SIZE  0x1000
    #define PAGE_SHIFT 12L
#endif


PUCHAR BusTypeStrings[] = {
    "Unknown",
    "Scsi",
    "Atapi",
    "Ata",
    "1394",
    "Ssa",
    "Fibre",
    "Usb",
    "RAID",
    "Not Defined",
};

#define NUMBER_OF_BUS_TYPE_STRINGS (sizeof(BusTypeStrings)/sizeof(BusTypeStrings[0]))

// Structure for mapping device interface "friendly names" to GUID's

typdef struct {
{
	PCHAR;
	GUID;
} DEVICE_INTERFACE_ENTRY, *PDEVICE_INTERFACE_ENTRY;

DEVICE_INTERFACE_ENTRY Interfaces[] =
{

  "DISK",	GUID_DEVINTERFACE_DISK,
  "CDROM",	GUID_DEVINTERFACE_CDROM,
  "PARTITION",	GUID_DEVINTERFACE_PARTITION,
  "VOLUME",	GUID_DEVINTERFACE_VOLUME,
  "FLOPPY",	GUID_DEVINTERFACE_FLOPPY,
};

#define MAX_INTERFACES (sizeof(Interfaces) / sizeof(DEVICE_INTERFACE_ENTRY))
			


// Function prototypes

void  	Do_IRP_MJ_SCSI();
PCHAR 	IrpMajorFunctionString (UCHAR MajorFunction);
PCHAR 	ScsiopToString(UCHAR Scsiop); 
PCHAR 	NtStatusToString( DWORD NtStatus);
PCHAR 	GuidToString(LPGUID guid);
LPGUID 	InterfaceNameToGuid(char *);

BOOL  	GetDeviceGuidList(PCHAR pClassList, GUID *pGuidList, DWORD GuidCntIn, DWORD *pGuidCntOut);

// Global variables and structures

DWORD				dwIoctlBase = CTL_CODE(FILE_DEVICE_DISK, 0, 0, 3);
ULONG				irpCode, length = 0, errorCode = 0, returned = 0, sectorSize = 512;
USER_IRP			Irp;
HANDLE	        		fileHandle = NULL;
char				*pszTestCategories[] = {"All", "Read", "Write", "Control", "Information", "Miscellaneous"};


/*++

Main entry point

--*/

VOID __cdecl main(int argc, char *argv[])
{
    char				*pClassName = argv[1];
    BOOL				status = 0;
    DWORD				accessMode = 0, shareMode = 0, dwGuidCnt, dwLen, dwIndex = 0, i;
    ULONG				alignmentMask = 0; // default == no alignment requirement
    PUCHAR				dataBuffer = NULL;
    UCHAR				string[NAME_COUNT + 1];
    TCHAR				pszClassDesc[512];
    HDEVINFO				hDeviceInfo;	
    PSP_DEVINFO_DATA      		pDeviceData;
    PSP_DEVICE_INTERFACE_DATA		pInterfaceData;
    PSP_DEVICE_INTERFACE_DETAIL_DATA	pInterfaceDetail;

    printf("ACES Software Write Blocker Test Suite V1.2\n");
    printf("Invoked with command line %s\n", argv[0]);
    printf("Testing device interface class[es]: %s\n", argv[1]);

    /* Loop through all specified device interface classes */

    while (*pClassName)
    {
       /* Insert NULL terminator after current name in list */

       cp = strchr(pClassName, ',');
       if(cp)
         *cp++ = 0;
       else
	 cp = pClassName + strlen(pClassName);

       /* Lookup GUID for this name

       pGuid = InterfaceNameToGuid(pClassName);
       if(!pGuid)
       {
          printf("Interface name '%s' not found - ignored\n");
          continue;
       }

       /* Find all the devices that expose this interface

       hDevInfo = SetupDiGetClassDevs(pGuid, NULL, NULL, DIGCF_DEVICEINTERFACE);
       if(hDevInfo == INVALID_HANDLE_VALUE)
       {
         printf("Error enumerating devices for interface class %s\n", pClassName);
         PrintError(GetLastError());
         continue;
       }

       // Enumerate and test each instance of this interface class

       dwIndex = 0;
       while(SetupDiEnumDeviceInterfaces(hDevInfo, NULL, pGuid, dwIndex++, &InterfaceData))
       {
          SetupDiGetDeviceInterfaceDetail(hDevInfo, &InterfaceData, &InterfaceDetail, &retlen,  
           
    
    if(!GetDeviceInterfaceList(argv[1], GUIDlist, sizeof(GUIDlist) / sizeof(GUID), &dwGuidCnt))
    {
       printf("Unable to get list of GUID's for all specified device classes\n");
       PrintError(GetLastError());
       return;
    }
    printf("\n%d GUID's found\n", dwGuidCnt);



    /* Get a handle to the device object under test */

    for (i=0; i<dwGuidCnt; i++)
    {
       hDeviceInfo = SetupDiGetClassDevs(GUIDlist+i, NULL, NULL, DIGCF_PRESENT);
       printf("Testing device class %s\n", GuidToString(GUIDlist+i));
       fileHandle = CreateFile(argv[1],                                 // Device name
                               GENERIC_READ || GENERIC_WRITE,            // Access mode
                               FILE_SHARE_READ || FILE_SHARE_WRITE,	     // Share mode
                               NULL,
                               OPEN_EXISTING,
                               0,
                               NULL);

       if (fileHandle == INVALID_HANDLE_VALUE)
       {
           errorCode = GetLastError();
           printf("Error opening %s,  Error: %d\n",
               argv[1], errorCode);
          PrintError(errorCode);
          return;
       }
    }

    /* Loop through all IRP function codes */

    printf("\n\n*** Testing IRP interface on %s ***\n\n", argv[1]);
    memset(&Irp, 0, sizeof(Irp));

    printf("           Function                     Function    Sub-function           Write Blocker\n");
    printf("             Name                         Code          Code               Action Taken\n");
    printf("----------------------------------------------------------------------------------------------------\n");

    for(irpCode=0; irpCode<=IRP_MJ_MAXIMUM_FUNCTION; irpCode++)
    {
       printf(" %-40.40s(0x%2.2X)  ", IrpMajorFunctionString((UCHAR)irpCode), irpCode);

       // Perform function dependent setup/or subfunction testing

       switch(irpCode)
       {
          case IRP_MJ_SCSI:

            // Test this IRP with all possible CDB's

            Do_IRP_MJ_SCSI();
            continue;

          default:

            // Just execute the "naked" IRP

            Irp.IoStack.MajorFunction = (UCHAR) irpCode;
            status = DeviceIoControl(fileHandle,
                                     IOCTL_IRP_PASSTHRU,
                                     &Irp,
                                     sizeof(Irp),
                                     &Irp,
                                     sizeof(Irp),
		                     &returned,
                                     FALSE);
            if(!status)
            {
               printf("*** IOCTL Failure ***\n");
               PrintError(GetLastError());
            }
            else
            {
	       printf("%26.26s", "");
               if(Irp.IoStatus.Status == STATUS_SWBTS_CAUGHT)
                  printf("INITIATED - passed to next lower driver\n");
               else
                  printf("COMPLETED - %s", NtStatusToString(Irp.IoStatus.Status));
	    }
       }
    }
    return;
}

/*******************************************************************************
*
*  GetDeviceInterfaceGuidList - Get the GUID's of the device interfaces to be tested
*
*  Arguments:
*
*     pszClassName [IN]   - a pointer to a character string containing the name of the
*                           device interface class list of
*                             device classes
*
*     pGuidList [OUT]     - a pointer to an array of GUID values 
*
*     dwGuidCntIn [IN]    - a dword containing the number of GUID elements in
*                           the GUID array.
*
*     pdwGuidCntOut [OUT] - a pointer to a dword to receive the number of GUID's
*                           returned 
*  Description:
*
*     On input the pszClassName parameter should contain a list of device interface
*     class names. Individual class names must be separated by commas and embedded
*     white space is not allowed. This function retrieves the device GUID's of all
*     devices in the current system configuration that expose to one or more of
*     the listed interface classes and returns those GUID's in the GUID array
*     provided by the caller. The caller must specify the size (number of GUID
*     values) of the GUID array via the dwGuidCntIn parameter. On return from
*     this function the number of GUID's returned in the GUID array will be
*     returned in pdwGuidCntOut
*
*******************************************************************************/

BOOL GetDeviceGuidList(PCHAR pClassList, LPGUID pGuidList, DWORD GuidCntIn,
                       DWORD *pGuidCntOut)
{
   SP_DEVINFO_DATA 	DeviceData;
   HDEVINFO		hDevInfo;
   BYTE			PropBuf[512];
   DWORD		dwRegDataType, dwPropLen, dwError;
   
   PCHAR cp = pClassList, szClassName[64];
   DWORD free=GuidCntIn, used=0, cnt=0, dwIndex;

   /* Loop through each element of the class name list */

   *pGuidCntOut = 0;
   while (*pClassList)
   {
      /* Insert NULL terminator after current element of list */

      cp = strchr(pClassList, ',');
      if(cp)
         *cp++ = 0;
      else
	 cp = pClassList + strlen(pClassList);

      /* Get GUID for this class name */

      printf("Checking for %s class devices present on system\n", pClassList); 
      if(!SetupDiClassGuidsFromName(pClassList, pGuidList+used, free, &cnt))
      {
         printf("GUID lookup error\n");
         return FALSE;
      }
      hDevInfo = SetupDiGetClassDevs(pGuidList+used, NULL, NULL, DIGCF_PRESENT);
      if(hDevInfo == INVALID_HANDLE_VALUE)
      {
         printf("Error getting device info for %s\n", pClassList);
         PrintError(GetLastError());
         continue;
      }

      // Get device data

      printf("Enumerating devices for class %s\n", pClassList);
      dwIndex = 0;
      DeviceData.cbSize = sizeof(DeviceData);
      while(SetupDiEnumDeviceInfo(hDevInfo, dwIndex, &DeviceData))
      {
           if(!SetupDiGetDeviceRegistryProperty(hDevInfo, &DeviceData, SPDRP_FRIENDLYNAME,&dwRegDataType,
                                              PropBuf, sizeof(PropBuf), &dwPropLen))
          {
                printf("Can't get friendly name for device #%d\n", dwIndex);
                dwIndex++;
                continue;
          }
          printf("Friendly name: %s\n", PropBuf);
          dwIndex++;
      }
      dwError = GetLastError();
      if(dwError != ERROR_NO_MORE_ITEMS)
         PrintError(dwError);
                                               
      /* Update GUID array usage counts and point to next class name */

      pClassList = cp;
      free -= cnt;
      used += cnt;
    }
    *pGuidCntOut = used;
    return TRUE;
}
       
      
/*++

Perform IRP_MJ_SCSI tests

++*/

void Do_IRP_MJ_SCSI()
{

  USHORT Scsiop;
  BOOL   status;

  // Setup IRP parameters for SCSI

  Irp.IoStack.MajorFunction = IRP_MJ_SCSI;
  memset(&Irp.Parameters.Scsi.Srb, 0, sizeof(SCSI_REQUEST_BLOCK)); 
  Irp.Parameters.Scsi.Srb.Length = sizeof(SCSI_REQUEST_BLOCK);
  Irp.Parameters.Scsi.Srb.Function = SRB_FUNCTION_EXECUTE_SCSI;
  Irp.Parameters.Scsi.Srb.SenseInfoBufferLength = sizeof(Irp.Parameters.Scsi.SenseInfo);
  Irp.Parameters.Scsi.Srb.SrbFlags = SRB_FLAGS_DATA_OUT;
  Irp.Parameters.Scsi.Srb.DataTransferLength = sizeof(Irp.Parameters.Scsi.Data);

  // Loop through all possible SCSI-2 Commands

  for(Scsiop=0; Scsiop<=255; Scsiop++)
  {
     // Issue SCSI command

     if(Scsiop == 0)
        printf("  %-24.24s", ScsiopToString((UCHAR) Scsiop));
     else
        printf("  %49.49s%-24.24s","", ScsiopToString((UCHAR) Scsiop));
     Irp.IoStatus.Status = 0;
     Irp.Parameters.Scsi.Srb.Cdb[0] = (UCHAR) Scsiop;
     status = DeviceIoControl(fileHandle,
                              IOCTL_IRP_PASSTHRU,
                              &Irp,
                              sizeof(Irp),
                              &Irp,
                              sizeof(Irp),
                              &returned,
                              FALSE);

     // Display status of request

     if(!status)
     {
        printf("*** IOCTL Failure ***\n");
        PrintError(GetLastError());
     }
     else
     {
       if(Irp.IoStatus.Status == STATUS_SWBTS_CAUGHT)
          printf("INITIATED - passed by write blocker\n");
       else
          printf("COMPLETED: %s", NtStatusToString(Irp.IoStatus.Status));
     } 
  } 
}
     
/*++

Get caching mode sense page.

Input:

	dHandle - a handle to the device to be queried

Ouput:

	None

Abstract:

	Issues a SenseMode request to a SCSI device to obtain the Caching
	Mode page and fprints the returned data to the standard out

*/
/*
VOID PrintCachingModePage(HANDLE dHandle)
{
	BOOL status;
	ULONG length = 0;
	ULONG errorCode = 0;
	ULONG returned = 0;

	printf(" *****  Caching Mode Sense Page  *****\n");

    ZeroMemory(&sptwb,sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS));
    sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH);
    sptwb.spt.PathId = 0;
    sptwb.spt.TargetId = 1;
    sptwb.spt.Lun = 0;
    sptwb.spt.CdbLength = CDB6GENERIC_LENGTH;
    sptwb.spt.SenseInfoLength = SPT_SENSE_LENGTH;
    sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN;
    sptwb.spt.DataTransferLength = 192;
    sptwb.spt.TimeOutValue = 2;
    sptwb.spt.DataBufferOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf);
    sptwb.spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucSenseBuf);
    sptwb.spt.Cdb[0] = SCSIOP_MODE_SENSE;
    sptwb.spt.Cdb[1] = 0x08; // target shall not return any block descriptors
    sptwb.spt.Cdb[2] = MODE_PAGE_CACHING;
    sptwb.spt.Cdb[4] = 192;
    length = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf) +  sptwb.spt.DataTransferLength;
    status = DeviceIoControl(dHandle,
                             IOCTL_SCSI_PASS_THROUGH,
                             &sptwb,
                             sizeof(SCSI_PASS_THROUGH),
                             &sptwb,
                             length,
                             &returned,
                             FALSE);

    PrintStatusResults(status,returned,&sptwb,length);
}
*/
/*
VOID
PrintDataBuffer(PUCHAR DataBuffer, ULONG BufferLength)
{
    ULONG Cnt, index;
	TCHAR szData[17];;

	printf("                                      Dump of %d byte buffer @%8.8x\n", BufferLength,DataBuffer);
    printf("                                      ----------------------------\n\n");
    for (Cnt = 0, index=0; Cnt < BufferLength; Cnt++)
	{
       if ((Cnt) % 16 == 0)
	   {
          printf(" %03X  ",Cnt);
		  memset(szData, 0, sizeof(szData));
       }
       printf("%02X  ", DataBuffer[Cnt]);
	   szData[Cnt%16] = __toascii(DataBuffer[Cnt]);
       if ((Cnt+1) % 8 == 0)
	   {
          printf(" ");
       }
       if ((Cnt+1) % 16 == 0)
	   {
          printf(" %s\n", szData);

       }
    }
    printf("\n\n");
}

VOID PrintAdapterDescriptor(PSTORAGE_ADAPTER_DESCRIPTOR AdapterDescriptor)
{
    ULONG trueMaximumTransferLength;
    PUCHAR busType;

    if (AdapterDescriptor->BusType <= NUMBER_OF_BUS_TYPE_STRINGS) {
        busType = BusTypeStrings[AdapterDescriptor->BusType];
    } else {
        busType = BusTypeStrings[NUMBER_OF_BUS_TYPE_STRINGS-1];
    }

    // subtract one page, as transfers do not always start on a page boundary
    if (AdapterDescriptor->MaximumPhysicalPages > 1) {
        trueMaximumTransferLength = AdapterDescriptor->MaximumPhysicalPages - 1;
    } else {
        trueMaximumTransferLength = 1;
    }
    // make it into a byte value
    trueMaximumTransferLength <<= PAGE_SHIFT;

    // take the minimum of the two
    if (trueMaximumTransferLength > AdapterDescriptor->MaximumTransferLength) {
        trueMaximumTransferLength = AdapterDescriptor->MaximumTransferLength;
    }

    // always allow at least a single page transfer
    if (trueMaximumTransferLength < PAGE_SIZE) {
        trueMaximumTransferLength = PAGE_SIZE;
    }

    puts("\n *** TARGET STORAGE ADAPTER DESCRIPTOR *****\n\n");
    printf("              Version: %08x\n"
           "            TotalSize: %08x\n"
           "MaximumTransferLength: %08x (bytes)\n"
           " MaximumPhysicalPages: %08x\n"
           "  TrueMaximumTransfer: %08x (bytes)\n"
           "        AlignmentMask: %08x\n"
           "       AdapterUsesPio: %s\n"
           "     AdapterScansDown: %s\n"
           "      CommandQueueing: %s\n"
           "  AcceleratedTransfer: %s\n"
           "             Bus Type: %s\n"
           "    Bus Major Version: %04x\n"
           "    Bus Minor Version: %04x\n",
           AdapterDescriptor->Version,
           AdapterDescriptor->Size,
           AdapterDescriptor->MaximumTransferLength,
           AdapterDescriptor->MaximumPhysicalPages,
           trueMaximumTransferLength,
           AdapterDescriptor->AlignmentMask,
           BOOLEAN_TO_STRING(AdapterDescriptor->AdapterUsesPio),
           BOOLEAN_TO_STRING(AdapterDescriptor->AdapterScansDown),
           BOOLEAN_TO_STRING(AdapterDescriptor->CommandQueueing),
           BOOLEAN_TO_STRING(AdapterDescriptor->AcceleratedTransfer),
           busType,
           AdapterDescriptor->BusMajorVersion,
           AdapterDescriptor->BusMinorVersion);
    printf("\n\n");
}

/*++

PrintDeviceDescriptor

Abstract

	Formats and prints the contents of a device descriptor to the
	standard output
	
Input

	DeviceDescriptor - a pointer to a STORAGE_DEVICE_DESCRIPTOR
	                   containing the data to be printed
					   
Output

	None
	
--*/
/*
VOID PrintDeviceDescriptor(PSTORAGE_DEVICE_DESCRIPTOR DeviceDescriptor)
{
    PUCHAR vendorId = "";
    PUCHAR productId = "";
    PUCHAR productRevision = "";
    PUCHAR serialNumber = "";
    PUCHAR busType;

    if (DeviceDescriptor->BusType <= NUMBER_OF_BUS_TYPE_STRINGS) {
        busType = BusTypeStrings[DeviceDescriptor->BusType];
    } else {
        busType = BusTypeStrings[NUMBER_OF_BUS_TYPE_STRINGS-1];
    }

    if ((DeviceDescriptor->ProductIdOffset != 0) &&
        (DeviceDescriptor->ProductIdOffset != -1)) {
        productId        = (PUCHAR)(DeviceDescriptor);
        productId       += (ULONG_PTR)DeviceDescriptor->ProductIdOffset;
    }
    if ((DeviceDescriptor->VendorIdOffset != 0) &&
        (DeviceDescriptor->VendorIdOffset != -1)) {
        vendorId         = (PUCHAR)(DeviceDescriptor);
        vendorId        += (ULONG_PTR)DeviceDescriptor->VendorIdOffset;
    }
    if ((DeviceDescriptor->ProductRevisionOffset != 0) &&
        (DeviceDescriptor->ProductRevisionOffset != -1)) {
        productRevision  = (PUCHAR)(DeviceDescriptor);
        productRevision += (ULONG_PTR)DeviceDescriptor->ProductRevisionOffset;
    }
    if ((DeviceDescriptor->SerialNumberOffset != 0) &&
        (DeviceDescriptor->SerialNumberOffset != -1)) {
        serialNumber     = (PUCHAR)(DeviceDescriptor);
        serialNumber    += (ULONG_PTR)DeviceDescriptor->SerialNumberOffset;
    }


    puts("\n ***** TARGET STORAGE DEVICE DESCRIPTOR *****\n\n");
    printf("              Version: %08x\n"
           "            TotalSize: %08x\n"
           "           DeviceType: %08x\n"
           "   DeviceTypeModifier: %08x\n"
           "       RemovableMedia: %s\n"
           "      CommandQueueing: %s\n"
           "            Vendor Id: %s\n"
           "           Product Id: %s\n"
           "     Product Revision: %s\n"
           "        Serial Number: %s\n"
           "             Bus Type: %s\n"
           "       Raw Properties: %s\n",
           DeviceDescriptor->Version,
           DeviceDescriptor->Size,
           DeviceDescriptor->DeviceType,
           DeviceDescriptor->DeviceTypeModifier,
           BOOLEAN_TO_STRING(DeviceDescriptor->RemovableMedia),
           BOOLEAN_TO_STRING(DeviceDescriptor->CommandQueueing),
           vendorId,
           productId,
           productRevision,
           serialNumber,
           busType,
           (DeviceDescriptor->RawPropertiesLength ? "Follows\n" : "None\n"));
    if (DeviceDescriptor->RawPropertiesLength != 0) {
        PrintDataBuffer(DeviceDescriptor->RawDeviceProperties,
                        DeviceDescriptor->RawPropertiesLength);
    }
    printf("\n\n");
}



PUCHAR AllocateAlignedBuffer(ULONG size, ULONG AlignmentMask)
{
    PUCHAR ptr;

    // NOTE: This routine does not allow for a way to free
    //       memory.  This is an excercise left for the reader.
    UINT_PTR    align64 = (UINT_PTR)AlignmentMask;

    if (AlignmentMask == 0) {
       ptr = malloc(size);
    } else {
       ptr = malloc(size + AlignmentMask);
       ptr = (PUCHAR)(((UINT_PTR)ptr + align64) & ~align64);
    }
    
    if (ptr == NULL) {
       printf("Memory allocation error.  Terminating program\n");
       exit(1);
    } else {
       return ptr;
    }
}

/**+

Routine Name:

	PrintSptStatus

Description:

	This routine prints a summary of the return status of a DEVICEIOCONTROL call with a 
	function code of IOCTL_SCSI_PASS_THROUGH

Parameters:

	status		[IN} - the boolean status returned by DeviceIoControl()

	cbOut		[IN] - the number of bytes returned in the output buffer by DeviceIoControl()

	pOut		[IN] - a pointer to the output bufferpassed to DeviceIoControl()

    dwOutLen	[IN] - the length in bytes of the output buffer passed to DeviceIoControl()
	
Return value:

	None

--*/
/*	 
VOID PrintSptStatus(BOOL status, DWORD cbOut, PSPTD pSptd, ULONG cbSptd)
{
    ULONG errorCode;
	
	printf(" ");
	PrintError((status) ? 0 : GetLastError());
	switch (pSptd->Type)
	{
		case SPTD_TYPE_DIRECT:
			printf(" SCSI status %02Xh, %d bytes transferred\n",
				    pSptd->Dio.ScsiStatus,
					pSptd->Dio.DataTransferLength);
			return;

		case SPTD_TYPE_BUFFERED:

			printf(" SCSI status %02Xh, %d bytes transferred\n",
				    pSptd->Bio.ScsiStatus,
					pSptd->Bio.DataTransferLength);
			return;

		default:
			BUGCHECK("Invalid descriptor type");
	}
}

BOOL GetAlignmentMaskForDevice(
    IN HANDLE DeviceHandle,
    OUT PULONG AlignmentMask
    )
{
    PSTORAGE_ADAPTER_DESCRIPTOR adapterDescriptor = NULL;
    PSTORAGE_DEVICE_DESCRIPTOR deviceDescriptor = NULL;
    STORAGE_DESCRIPTOR_HEADER header = {0};

    BOOL ok = TRUE;
    BOOL failed = TRUE;
    ULONG i;

    *AlignmentMask = 0; // default to no alignment

    // Loop twice:
    //  First, get size required for storage adapter descriptor
    //  Second, allocate and retrieve storage adapter descriptor
    //  Third, get size required for storage device descriptor
    //  Fourth, allocate and retrieve storage device descriptor
    for (i=0;i<4;i++) {

        PVOID buffer;
        ULONG bufferSize;
        ULONG returnedData;
        
        STORAGE_PROPERTY_QUERY query = {0};

        switch(i) {
            case 0: {
                query.QueryType = PropertyStandardQuery;
                query.PropertyId = StorageAdapterProperty;
                bufferSize = sizeof(STORAGE_DESCRIPTOR_HEADER);
                buffer = &header;
                break;
            }
            case 1: {
                query.QueryType = PropertyStandardQuery;
                query.PropertyId = StorageAdapterProperty;
                bufferSize = header.Size;
                if (bufferSize != 0) {
                    adapterDescriptor = LocalAlloc(LPTR, bufferSize);
                    if (adapterDescriptor == NULL) {
                        goto Cleanup;
                    }
                }
                buffer = adapterDescriptor;
                break;
            }
            case 2: {
                query.QueryType = PropertyStandardQuery;
                query.PropertyId = StorageDeviceProperty;
                bufferSize = sizeof(STORAGE_DESCRIPTOR_HEADER);
                buffer = &header;
                break;
            }
            case 3: {
                query.QueryType = PropertyStandardQuery;
                query.PropertyId = StorageDeviceProperty;
                bufferSize = header.Size;

                if (bufferSize != 0) {
                    deviceDescriptor = LocalAlloc(LPTR, bufferSize);
                    if (deviceDescriptor == NULL) {
                        goto Cleanup;
                    }
                }
                buffer = deviceDescriptor;
                break;
            }
        }

        // buffer can be NULL if the property queried DNE.
        if (buffer != NULL) {
            RtlZeroMemory(buffer, bufferSize);
            
            // all setup, do the ioctl
            ok = DeviceIoControl(DeviceHandle,
                                 IOCTL_STORAGE_QUERY_PROPERTY,
                                 &query,
                                 sizeof(STORAGE_PROPERTY_QUERY),
                                 buffer,
                                 bufferSize,
                                 &returnedData,
                                 FALSE);
            if (!ok) {
                if (GetLastError() == ERROR_MORE_DATA) {
                    // this is ok, we'll ignore it here
                } else if (GetLastError() == ERROR_INVALID_FUNCTION) {
                    // this is also ok, the property DNE
                } else if (GetLastError() == ERROR_NOT_SUPPORTED) {
                    // this is also ok, the property DNE
                } else {
                    // some unexpected error -- exit out
                    goto Cleanup;
                }
                // zero it out, just in case it was partially filled in.
                RtlZeroMemory(buffer, bufferSize);
            }
        }
    } // end i loop

    // adapterDescriptor is now allocated and full of data.
    // deviceDescriptor is now allocated and full of data.
    
    if (adapterDescriptor == NULL) {
        printf("   ***** No adapter descriptor supported on the device *****\n");
    } else {
        PrintAdapterDescriptor(adapterDescriptor);
        *AlignmentMask = adapterDescriptor->AlignmentMask;
    }
    
    if (deviceDescriptor == NULL) {
        printf("   ***** No device descriptor supported on the device  *****\n");
    } else {
        PrintDeviceDescriptor(deviceDescriptor);
    }
    
    failed = FALSE;

Cleanup:
    if (adapterDescriptor != NULL) {
        LocalFree( adapterDescriptor );
    }
    if (deviceDescriptor != NULL) {
        LocalFree( deviceDescriptor );
    }
    return (!failed);

}

/*++

Routine Description:

    Prints formatted error message

Arguments:

    ErrorCode   - Error code to print


Return Value:
    
      None
--*/

VOID PrintError(ULONG ErrorCode)
{
	UCHAR errorBuffer[80];
    ULONG count;

    count = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                    NULL,
                    ErrorCode,
                    0,
                    errorBuffer,
                    sizeof(errorBuffer),
                    NULL
                    );

    if (count != 0) {
        printf("%s", errorBuffer);
    } else {
        printf("Format message failed.  Error: %d\n", GetLastError());
    }
}

/*++

  SetMemory - set a region of memory to a user supplied data pattern
	

  Arguments:

    pDst		[IN] - Starting address of memory to be filled

	cbCount		[IN] - number of bytes to be filled

	pzPattern	[IN] - pointer to a string containing the fill pattern

 Description

	The pattern provided is repeatedly copied to the memory address
	specified until the entire range of addresses has been filled. The
	string terminator is not copied.

--*/

/*
VOID SetMemory(PUCHAR pDst, DWORD cbCount, LPTSTR pszPattern)
{
	int plen, index;
	
	plen = strlen(pszPattern);
	index = 0;
	while (cbCount--)
	{
		*pDst++ = pszPattern[index++];
		if(index == plen)
			index = 0;
	}
}

/*++

AllocateSPTD
  
	The AllocateSPTD function allocates and initializes a SCSI_PASS_THROUGH_xxxx IOCTL 
    descriptor.

Parameters

	dwType	[IN]			-	a DWORD value specifying the buffer type of the descriptor to be
								allocated

									SPTD_TYPE_BUFFERED - allocate a buffererd I/O descriptor
									SPTD_TYPE_DIRECT   - allocate a direct I/O descriptor

	dwSenseInfoLength [IN]	-	specifies the number of bytes of buffer space to allocate for the
								Sense data buffer and may be zero.

	dwDataTransferSize[IN]	-	specifies the number of bytes of data buffer space to allocate
									for the descriptor and may be zero. The data buffer allocated is
									guaranteed to be DWORD aligned.

Return Values

	A pointer to the newly allocated descriptor or NULL if the call fails.  Use GetLastError()
	to determine the cause of the failure.

Remarks

	The SCSI IOCTL data structures and associated buffers returned by this function are guaranteed
	to be DWORD aligned.

 --*/

/*
PSPTD AllocateSPTD(ULONG dwType, UCHAR ucSenseInfoLength, ULONG dwDataTransferSize)
{							  
	PSPTD	pSptd;
	DWORD	dwBufferSize;
	DWORD	dwBufferOffset;
	DWORD	dwAlignmentFill;

	// Validate parameters

	if((dwType != SPTD_TYPE_DIRECT) && (dwType != SPTD_TYPE_BUFFERED))
	{
			SetLastError(ERROR_INVALID_PARAMETER);
			return 0;
	}
	

	// Allocate SCSI pass-through header and optional buffers making sure the
	// data buffer, if any, is DWORD aligned off the SCSI header

	dwAlignmentFill = dwDataTransferSize ? (ucSenseInfoLength % sizeof(DWORD)) : 0;
	dwBufferSize = ucSenseInfoLength + dwDataTransferSize + dwAlignmentFill;
	pSptd = calloc(sizeof(SPTD) + dwBufferSize, 1);
	if(!pSptd)
	{
		SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return pSptd;
	}
	pSptd->Type = dwType;

	// Initialize the buffer offsets and lengths in the SCSI pass-through header

    dwBufferOffset = offsetof(SPTD, Buffers) - offsetof(SPTD, Header);
	pSptd->IoctlLength = dwBufferOffset + dwBufferSize;
	if(pSptd->Type = SPTD_TYPE_BUFFERED)
	{
		pSptd->Bio.Length = sizeof(SCSI_PASS_THROUGH);
		if(ucSenseInfoLength)
		{
			pSptd->Bio.SenseInfoOffset =  dwBufferOffset;
			pSptd->Bio.SenseInfoLength = ucSenseInfoLength;
		}
		if(dwDataTransferSize)
		{
			pSptd->Bio.DataTransferLength = dwDataTransferSize;
			pSptd->Bio.DataBufferOffset = dwBufferOffset + ucSenseInfoLength + dwAlignmentFill;
		}
		printf("SCSI_PASS_THROUGH Descriptor\n");
		printf("----------------------------\n");
		printf(" IOCTL Length: %d\n", pSptd->IoctlLength);
		printf(" SCSI Header Length:%d\n", pSptd->Bio.Length);
		printf(" Sense Info Offset: %d\n", pSptd->Bio.SenseInfoOffset);
		printf(" Sense Info Length: %d\n", pSptd->Bio.SenseInfoLength);
		printf(" Data Buffer Offset: %d\n", pSptd->Bio.DataBufferOffset);
		printf(" Data Transfer Length: %d\n", pSptd->Bio.DataTransferLength);
	}
	else
	{
		pSptd->Dio.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
		pSptd->IoctlLength = pSptd->Dio.Length  + dwBufferSize;
		if(ucSenseInfoLength)
		{
			pSptd->Dio.SenseInfoOffset = offsetof(SPTD, Buffers) - offsetof(SPTD, Dio);
			pSptd->Dio.SenseInfoLength = ucSenseInfoLength;
		}
		printf("SCSI_PASS_THROUGH_DIRECT Descriptor\n");
		printf("-----------------------------------\n");
		printf(" IOCTL Length: %d\n", pSptd->IoctlLength);
		printf(" SCSI Header Length:%d\n", pSptd->Dio.Length);
		printf(" Sense Info Offset: %d\n", pSptd->Dio.SenseInfoOffset);
		printf(" Sense Info Length: %d\n", pSptd->Dio.SenseInfoLength);
	}

	// Return the completed descriptor
		(USHORT) (sizeof(SPTD) - offsetof(SPTD, Bio) + dwBufferSize);
	return pSptd;

}
*/
PCHAR
IrpMajorFunctionString (
    UCHAR MajorFunction
)
{
    static char szString[12];

    switch (MajorFunction)
    {
        case IRP_MJ_CREATE:
            return "IRP_MJ_CREATE";

        case IRP_MJ_CREATE_NAMED_PIPE:
            return "IRP_MJ_CREATE_NAMED_PIPE";

        case IRP_MJ_CLOSE:
            return "IRP_MJ_CLOSE";

        case IRP_MJ_READ:
            return "IRP_MJ_READ";

        case IRP_MJ_WRITE:
            return "IRP_MJ_WRITE";

        case IRP_MJ_QUERY_INFORMATION:
            return "IRP_MJ_QUERY_INFORMATION";

        case IRP_MJ_SET_INFORMATION:
            return "IRP_MJ_SET_INFORMATION";

        case IRP_MJ_QUERY_EA:
            return "IRP_MJ_QUERY_EA";

        case IRP_MJ_SET_EA:
            return "IRP_MJ_SET_EA";

        case IRP_MJ_FLUSH_BUFFERS:
            return "IRP_MJ_FLUSH_BUFFERS";

        case IRP_MJ_QUERY_VOLUME_INFORMATION:
            return "IRP_MJ_QUERY_VOLUME_INFORMATION";

        case IRP_MJ_SET_VOLUME_INFORMATION:
            return "IRP_MJ_SET_VOLUME_INFORAMATION";

	case IRP_MJ_DIRECTORY_CONTROL:
	    return "IRP_MJ_DIRECTORY_CONTROL";

        case IRP_MJ_FILE_SYSTEM_CONTROL:
            return "IRP_MJ_FILE_SYSTEM_CONTROL";

        case IRP_MJ_DEVICE_CONTROL:
            return "IRP_MJ_DEVICE_CONTROL";

        case IRP_MJ_INTERNAL_DEVICE_CONTROL:
            return "IRP_MJ_INTERNAL_DEVICE_CONTROL";

        case IRP_MJ_SHUTDOWN:
            return "IRP_MJ_SHUTDOWN";

        case IRP_MJ_LOCK_CONTROL:
            return "IRP_MJ_LOCK_CONTROL";

        case IRP_MJ_CLEANUP:
            return "IRP_MJ_CLEANUP";

        case IRP_MJ_CREATE_MAILSLOT:
            return "IRP_MJ_CREATE_MAILSLOT";

        case IRP_MJ_QUERY_SECURITY:
            return "IRP_MJ_QUERY_SECURITY";

        case IRP_MJ_SET_SECURITY:
            return "IRP_MJ_SET_SECURITY";

        case IRP_MJ_POWER:
            return "IRP_MJ_POWER";

        case IRP_MJ_SYSTEM_CONTROL:
            return "IRP_MJ_SYSTEM_CONTROL";

        case IRP_MJ_DEVICE_CHANGE:
            return "IRP_MJ_DEVICE_CHANGE";

        case IRP_MJ_QUERY_QUOTA:
            return "IRP_MJ_QUERY_QUOTA";

        case IRP_MJ_SET_QUOTA:
            return "IRP_MJ_SET_QUOTA";

        case IRP_MJ_PNP:
            return "IRP_MJ_PNP";

        default:
            printf(szString, "IRP_MJ_0x%2.2x", MajorFunction);
            return szString;
    }
}

PCHAR ScsiopToString (
    UCHAR Scsiop
)
{
    static char szString[12];

    switch (Scsiop)
    {
        case SCSIOP_TEST_UNIT_READY:
            return "TEST_UNIT_READY";

//      case SCSIOP_REZERO_UNIT:
//          return "REZERO_UNIT";

        case SCSIOP_REWIND:
            return "REWIND";

        case SCSIOP_REQUEST_BLOCK_ADDR:
            return "REQUEST_BLOCK_ADDR";

        case SCSIOP_REQUEST_SENSE:
            return "REQUEST_SENSE";

        case SCSIOP_FORMAT_UNIT:
            return "FORMAT_UNIT";

        case SCSIOP_READ_BLOCK_LIMITS:
            return "READ_BLOCK_LIMITS";

        case SCSIOP_REASSIGN_BLOCKS:
            return "REASSIGN_BLOCKS";

//      case SCSIOP_INIT_ELEMENT_STATUS:
//          return "INIT_ELEMENT_STATUS";

        case SCSIOP_READ6:
            return "READ6";

//      case SCSIOP_RECEIVE:
//          return "RECEIVE";

        case SCSIOP_WRITE6:
            return "WRITE6";

//     case SCSIOP_PRINT:
//          return "PRINT";

//      case SCSIOP_SEND:
//          return "SEND";

        case SCSIOP_SEEK6:
            return "SEEK6";

//        case SCSIOP_TRACK_SELECT:
//            return "TRACK_SELECT";

//      case SCSIOP_SLEW_PRINT:
//          return "SLEW_PRINT";

        case SCSIOP_SEEK_BLOCK:
            return "SEEK_BLOCK";

        case SCSIOP_PARTITION:
            return "PARTITION";

        case SCSIOP_READ_REVERSE:
            return "READ_REVERSE";

        case SCSIOP_WRITE_FILEMARKS:
            return "WRITE_FILEMARKS";

//      case SCSIOP_FLUSH_BUFFER:
//          return "FLUSH_BUFFER";

        case SCSIOP_SPACE:
            return "SPACE";

        case SCSIOP_INQUIRY:
            return "INQUIRY";

        case SCSIOP_VERIFY6:
            return "VERIFY6";

        case SCSIOP_RECOVER_BUF_DATA:
            return "RECOVER_BUF_DATA";

        case SCSIOP_MODE_SELECT:
            return "MODE_SELECT";

        case SCSIOP_RESERVE_UNIT:
            return "RESERVE_UNIT";

        case SCSIOP_RELEASE_UNIT:
            return "RELEASE_UNIT";

        case SCSIOP_COPY:
            return "COPY";

        case SCSIOP_ERASE:
            return "ERASE";

        case SCSIOP_MODE_SENSE:
            return "MODE_SENSE";

        case SCSIOP_START_STOP_UNIT:
            return "START_STOP_UNIT";

//      case SCSIOP_STOP_PRINT:
//          return "STOP_PRINT";

//      case SCSIOP_LOAD_UNLOAD:
//          return "LOAD_UNLOAD";

        case SCSIOP_RECEIVE_DIAGNOSTIC:
            return "RECEIVE_DIAGNOSTIC";

        case SCSIOP_SEND_DIAGNOSTIC:
            return "SEND_DIAGNOSTIC";

        case SCSIOP_MEDIUM_REMOVAL:
            return "MEDIUM_REMOVAL";

        case SCSIOP_READ_FORMATTED_CAPACITY:
            return "READ_FORMATTED_CAPACITY";

        case SCSIOP_READ_CAPACITY:
            return "READ_CAPACITY";

        case SCSIOP_READ:
            return "READ10";

        case SCSIOP_WRITE:
            return "WRITE10";

        case SCSIOP_SEEK:
            return "SEEK10";

//      case SCSIOP_LOCATE:
//          return "LOCATE";

//      case SCSIOP_POSITION_TO_ELEMENT:
//          return "POSITION_TO_ELEMENT";

        case SCSIOP_WRITE_VERIFY:
            return "WRTE_VERIFY";

        case SCSIOP_VERIFY:
            return "VERIFY";

        case SCSIOP_SEARCH_DATA_HIGH:
            return "SEARCH_DATA_HIGH";

        case SCSIOP_SEARCH_DATA_EQUAL:
            return "SEARCH_DATA_EQUAL";

        case SCSIOP_SEARCH_DATA_LOW:
            return "SEARCH_DTAT_LOW";

        case SCSIOP_SET_LIMITS:
            return "SET_LIMITS";

        case SCSIOP_READ_POSITION:
            return "READ_POSITION";

        case SCSIOP_SYNCHRONIZE_CACHE:
            return "SYNCHRONIZE_CACHE";

        case SCSIOP_COMPARE:
            return "COMPARE";

        case SCSIOP_COPY_COMPARE:
            return "COPY_COMPARE";

        case SCSIOP_WRITE_DATA_BUFF:
            return "WRITE_DATA_BUFF";

        case SCSIOP_READ_DATA_BUFF:
            return "READ_DATA_BUFF";

        case SCSIOP_CHANGE_DEFINITION:
            return "CHANGE_DEFINITION";

        case SCSIOP_READ_SUB_CHANNEL:
            return "READ_SUB_CHANNEL";

        case SCSIOP_READ_TOC:
            return "READ_TOC";

        case SCSIOP_READ_HEADER:
            return "READ_HEADER";

        case SCSIOP_PLAY_AUDIO:
            return "PLAY_AUDIO";

        case SCSIOP_GET_CONFIGURATION:
            return "GET_CONFIGURATION";

        case SCSIOP_PLAY_AUDIO_MSF:
            return "PLAY_AUDIO_MSF";

        case SCSIOP_PLAY_TRACK_INDEX:
            return "PLAY_TRACK_INDEX";

        case SCSIOP_PLAY_TRACK_RELATIVE:
            return "PLAY_TRACJK_RELATIVE";

        case SCSIOP_GET_EVENT_STATUS:
            return "GET_EVENT_STATUS";

        case SCSIOP_PAUSE_RESUME:
            return "PAUSE_RESUME";

        case SCSIOP_LOG_SELECT:
            return "LOG_SELECT";

        case SCSIOP_LOG_SENSE:
            return "LOG_SENSE";

        case SCSIOP_STOP_PLAY_SCAN:
            return "STOP_PLAY_SCAN";

        case SCSIOP_READ_DISK_INFORMATION:
            return "READ_DISK_INFORMATION";

        case SCSIOP_READ_TRACK_INFORMATION:
            return "READ_TRACK_INFORMATION";

        case SCSIOP_RESERVE_TRACK_RZONE:
            return "RESERVE_TRACK_RZONE";

        case SCSIOP_SEND_OPC_INFORMATION:
            return "SEND_OPC_INFORMATION";

        case SCSIOP_MODE_SELECT10:
            return "MODE_SELECT10";

        case SCSIOP_RESERVE_UNIT10:
            return "RESERVE_UNIT10";

        case SCSIOP_RELEASE_UNIT10:
            return "RELEASE_UNIT10";

        case SCSIOP_MODE_SENSE10:
            return "MODE_SENSE10";

        case SCSIOP_CLOSE_TRACK_SESSION:
            return "CLOSE_TRACK_SESSION";

        case SCSIOP_READ_BUFFER_CAPACITY:
            return "READ_BUFFER_CAPACITY";

        case SCSIOP_SEND_CUE_SHEET:
            return "SEND_CUE_SHEET";

        case SCSIOP_PERSISTENT_RESERVE_IN:
            return "PERSISTENT_RESERVE_OUT";

        case SCSIOP_REPORT_LUNS:
            return "REPORT_LUNS";

        case SCSIOP_BLANK:
            return "BLANK";

        case SCSIOP_SEND_EVENT:
            return "SEND_EVENT";

        case SCSIOP_SEND_KEY:
            return "SEND_KEY";

        case SCSIOP_REPORT_KEY:
            return "REPORT_KEY";

        case SCSIOP_MOVE_MEDIUM:
            return "MOVE_MEDIUM";

        case SCSIOP_LOAD_UNLOAD_SLOT:
            return "LOAD_UNLOAD_SLOT";

//      case SCSIOP_EXCHANGE_MEDIUM:
//          return "EXCHANGE_MEDIUM";

        case SCSIOP_SET_READ_AHEAD:
            return "SET_READ_AHEAD";

        case SCSIOP_READ_DVD_STRUCTURE:
            return "READ_DVD_STRUCTURE";

        case SCSIOP_REQUEST_VOL_ELEMENT:
            return "REQUEST_VOL_ELEMENT";

        case SCSIOP_SEND_VOLUME_TAG:
            return "SEND_VOLUME_TAG";

        case SCSIOP_READ_ELEMENT_STATUS:
            return "READ_ELEMENT_STATUS";

        case SCSIOP_READ_CD_MSF:
            return "READ_CD_MSF";

        case SCSIOP_SCAN_CD:
            return "SCAN_CD";

        case SCSIOP_SET_CD_SPEED:
            return "SET_CD_SPEED";

        case SCSIOP_PLAY_CD:
            return "PLAY_CD";

        case SCSIOP_MECHANISM_STATUS:
            return "MECHANISM_STATUS";

        case SCSIOP_READ_CD:
            return "READ_CD";

        case SCSIOP_SEND_DVD_STRUCTURE:
            return "SEND_DVD_STRUCTURE";

        case SCSIOP_INIT_ELEMENT_RANGE:
            return "INIT_ELEMENT_RANGE";

        default:
            sprintf(szString, "SCSI_COMMAND_0x%2.2X", Scsiop);
            return szString;
    }
}

PCHAR NtStatusToString(DWORD ntstatus)
{
   static CHAR 		MessageBuffer[512];
   static HMODULE 	Hand;
   CHAR			*cp;

   Hand = LoadLibrary("NTDLL.DLL");
   FormatMessage(
       FORMAT_MESSAGE_FROM_SYSTEM |
       FORMAT_MESSAGE_FROM_HMODULE,
       Hand,
       ntstatus,
       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
       MessageBuffer,
       sizeof(MessageBuffer),
       NULL );
    FreeLibrary(Hand);
    cp = strchr(MessageBuffer, '\n');
    if (cp)
	*(++cp) = '\0';
    return MessageBuffer;
}

PCHAR GuidToString(LPGUID pGuid)
{
   static CHAR 		GuidBuffer[36];

   sprintf(GuidBuffer, "%8.8X-%4.4X-%4.4X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X",
                        pGuid->Data1, pGuid->Data2, pGuid->Data3, pGuid->Data4[0],
                        pGuid->Data4[1],pGuid->Data4[2],pGuid->Data4[3],pGuid->Data4[4],
                        pGuid->Data4[5],pGuid->Data4[6],pGuid->Data4[7],pGuid->Data4[8]);
    return GuidBuffer;
}
  
