News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

WMI string arrays

Started by LaptoniC, September 19, 2009, 08:44:26 AM

Previous topic - Next topic

LaptoniC

Hi,
I am trying to get BiosVersion by using WMI. I have found example code at http://www.masm32.com/board/index.php?topic=6550.msg48779#msg48779 However because returned value is string array I can't print it easily. I don't want to write workaround code. So I am wondering what is the structure of string array? After I run my code I get below buffer at ecx register.

00233070  01 00 80 01 04 00 00 00 00 00 00 00 38 69 22 00  .€.......8i".
00233080  01 00 00 00 00 00 00 00 AB AB AB AB AB AB AB AB  .......««««««««
00233090  00 00 00 00 00 00 00 00 C0 5D DA 33 F3 2C 00 18  ........À]Ú3ó,.
002330A0  14 00 00 00 48 00 50 00 51 00 4F 00 45 00 4D 00  ...H.P.Q.O.E.M.
002330B0  20 00 2D 00 20 00 31 00 00 00 AD BA 0D F0 AD BA   .-. .1...­º.ð­º
002330C0  AB AB AB AB AB AB AB AB 00 00 00 00 00 00 00 00  ««««««««........
002330D0  25 58 D9 D0 FC 2C 00 00 C4 00 21 00 30 2D 23 00  %XÙÐü,..Ä.!.0-#.


Here is the code I use.


.586
.MODEL FLAT,STDCALL
OPTION CASEMAP:NONE

INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\ole32.inc
INCLUDE \masm32\include\msvcrt.inc
;INCLUDE \masm32\include\gel32.inc

INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\ole32.lib
INCLUDELIB \masm32\lib\msvcrt.lib
;INCLUDELIB \masm32\lib\gel32.lib

INCLUDE c:\masm32\macros\ucmacros.asm

; located in ObjIdl.h

EOAC_NONE   EQU 0

; located in RpcDce.h

RPC_C_AUTHN_LEVEL_DEFAULT   EQU 0
RPC_C_IMP_LEVEL_DEFAULT     EQU 0

RPC_C_IMP_LEVEL_IMPERSONATE EQU 3

GUID2 STRUC
     dd1 DWORD ?
     dw1 WORD ?
     dw2 WORD ?
     db1 BYTE ?
     db2 BYTE ?
     db3 BYTE ?
     db4 BYTE ?
     db5 BYTE ?
     db6 BYTE ?
     db7 BYTE ?
     db8 BYTE ?
GUID2 ENDS

IWbemLocator STRUCT
    lpVtbl DWORD   ?
IWbemLocator ENDS

IWbemLocatorVtbl STRUCT
    QueryInterface DWORD   ?
    AddRef         DWORD   ?
    Release        DWORD   ?
    ConnectServer  DWORD   ?
IWbemLocatorVtbl ENDS

IWbemServices STRUCT
    lpVtbl DWORD   ?
IWbemServices ENDS

IWbemServicesVtbl STRUCT
    QueryInterface             DWORD   ?
    AddRef                     DWORD   ?
    Release                    DWORD   ?
    OpenNamespace              DWORD   ?
    CancelAsyncCall            DWORD   ?
    QueryObjectSink            DWORD   ?
    GetObject                  DWORD   ?
    GetObjectAsync             DWORD   ?
    PutClass                   DWORD   ?
    PutClassAsync              DWORD   ?
    DeleteClass                DWORD   ?
    DeleteClassAsync           DWORD   ?
    CreateClassEnum            DWORD   ?
    CreateClassEnumAsync       DWORD   ?
    PutInstance                DWORD   ?
    PutInstanceAsync           DWORD   ?
    DeleteInstance             DWORD   ?
    DeleteInstanceAsync        DWORD   ?
    CreateInstanceEnum         DWORD   ?
    CreateInstanceEnumAsync    DWORD   ?
    ExecQuery                  DWORD   ?
    ExecQueryAsync             DWORD   ?
    ExecNotificationQuery      DWORD   ?
    ExecNotificationQueryAsync DWORD   ?
    ExecMethod                 DWORD   ?
    ExecMethodAsync            DWORD   ?
IWbemServicesVtbl ENDS

IEnumWbemClassObject STRUCT
    lpVtbl          DWORD   ?
IEnumWbemClassObject ENDS

IEnumWbemClassObjectVtbl STRUCT
    QueryInterface DWORD   ?
    AddRef         DWORD   ?
    Release        DWORD   ?
    Reset          DWORD   ?
    Next           DWORD   ?
    NextAsync      DWORD   ?
    Clone          DWORD   ?
    Skip           DWORD   ?
IEnumWbemClassObjectVtbl ENDS

IWbemClassObject STRUCT
    lpVtbl DWORD   ?
IWbemClassObject ENDS

IWbemClassObjectVtbl STRUCT
    QueryInterface          DWORD   ?
    AddRef                  DWORD   ?
    Release                 DWORD   ?
    GetQualifierSet         DWORD   ?
    Get                     DWORD   ?
    Put                     DWORD   ?
    Delete                  DWORD   ?
    GetNames                DWORD   ?
    BeginEnumeration        DWORD   ?
    Next                    DWORD   ?
    EndEnumeration          DWORD   ?
    GetPropertyQualifierSet DWORD   ?
    GetObjectText           DWORD   ?
    SpawnDerivedClass       DWORD   ?
    SpawnInstance           DWORD   ?
    CompareTo               DWORD   ?
    GetPropertyOrigin       DWORD   ?
    InheritsFrom            DWORD   ?
    GetMethod               DWORD   ?
    PutMethod               DWORD   ?
    DeleteMethod            DWORD   ?
    BeginMethodEnumeration  DWORD   ?
    NextMethod              DWORD   ?
    EndMethodEnumeration    DWORD   ?
    GetMethodQualifierSet   DWORD   ?
    GetMethodOrigin         DWORD   ?
IWbemClassObjectVtbl ENDS

.CONST
   
    wszSelect  WORD "S","E","L","E","C","T"," ","*"," ","F","R","O","M"," ",0  ; the WSTR macro can't handle the asterisk
    wszCrLf    WORD 13,10,0
       
    WSTR        wszClass,    "Win32_BIOS"    ;<<< Set class here
    WSTR        wszProperty, "BIOSVersion"  ;<<< Set property here
   
    WSTR        wszNameSpace, "root\cimv2"
    WSTR        wszQueryLanguage, "WQL"
   
    WSTR        wszMsg, "Bios Version: %s"
   
WaitKeyW proto :PTR WORD
.DATA

    ; located in WbemCli.h
   
    WBEM_FLAG_CONNECT_USE_MAX_WAIT  EQU     80h
    WBEM_FLAG_FORWARD_ONLY          EQU     20h
    WBEM_INFINITE                   EQU     -1
    WBEM_E_INVALID_QUERY            EQU     80041017h
    WBEM_E_INVALID_QUERY_TYPE       EQU     80041018h
   
    IID_IWbemLocator                GUID2   <0dc12a687h,0737fh,011cfh,088h,04dh,000h,0aah,000h,04bh,02eh,024h>
   
    IID_IEnumWbemClassObject        GUID2   <027947e1h,0d731h,011ceh,0a3h,057h,000h,000h,000h,000h,000h,001h>
   
    IID_IWbemClassObject            GUID2   <0dc12a681h,0737fh,011cfh,088h,04dh,000h,0aah,000h,04bh,02eh,024h>
   
    ; located in WbemProv.h
   
    CLSID_WbemAdministrativeLocator GUID2   <0cb8555cch,09128h,011d1h,0adh,09bh,000h,0c0h,04fh,0d8h,0fdh,0ffh>
   
    locator     IWbemLocator            <>
    service     IWbemServices           <>
    enumerator  IEnumWbemClassObject    <>
    processor   IWbemClassObject        <>
   
    retCount    DWORD   ?
   
    var_val     DWORD   ?
                DWORD   ?
                DWORD   ?
               
    pwszResult  PWORD   ?             
               
    wszQuery   WORD 256 dup(?)               

.CODE

  main:
   
    INVOKE CoInitializeEx, NULL, COINIT_MULTITHREADED
   
    INVOKE CoInitializeSecurity, NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL
       
    INVOKE CoCreateInstance, ADDR CLSID_WbemAdministrativeLocator, NULL, CLSCTX_INPROC_SERVER, ADDR IID_IWbemLocator, ADDR locator

    INVOKE lstrcatW, ADDR wszQuery, ADDR wszSelect
    INVOKE lstrcatW, ADDR wszQuery, ADDR wszClass
   
    mov esi, locator
    lodsd
    push    OFFSET service
    push    NULL
    push    NULL
    push    WBEM_FLAG_CONNECT_USE_MAX_WAIT
    push    NULL
    push    NULL
    push    NULL
    push    OFFSET wszNameSpace
    push    DWORD PTR [locator]
    call    DWORD PTR [eax][IWbemLocatorVtbl.ConnectServer]

    mov esi, service
    lodsd
    push    OFFSET enumerator
    push    NULL
    push    WBEM_FLAG_FORWARD_ONLY
    push    OFFSET wszQuery
    push    OFFSET wszQueryLanguage
    push    DWORD PTR [service]
    call    DWORD PTR [eax][IWbemServicesVtbl.ExecQuery]

    mov esi, enumerator
    lodsd
    push    OFFSET retCount
    push    OFFSET processor
    push    TRUE
    push    WBEM_INFINITE
    push    DWORD PTR [enumerator]
    call    DWORD PTR [eax][IEnumWbemClassObjectVtbl.Next]
   
    mov esi, processor
    lodsd
    push    NULL
    push    NULL
    push    OFFSET var_val
    push    0
    push    OFFSET wszProperty
    push    DWORD PTR [processor]
    call    DWORD PTR [eax][IWbemClassObjectVtbl.Get]
   
    mov esi, [var_val]
    mov edi, [var_val + 4]
    mov ecx, [var_val + 8]
   
    mov pwszResult, ecx
   
    INVOKE crt_wprintf, ADDR wszCrLf
    INVOKE crt_wprintf, ADDR wszMsg, pwszResult
    INVOKE crt_wprintf, ADDR wszCrLf

    INVOKE CoUninitialize
   
    INVOKE WaitKeyW, uni$("Press any key to exit ...")
   
    INVOKE ExitProcess, 0

WaitKeyW PROC pwszPrompt:PTR WORD
    .DATA
        IFNDEF wszCrLf
            wszCrLf WORD 13,10,0
        ENDIF
    .CODE   
    .IF pwszPrompt == NULL
        INVOKE crt_wprintf, ADDR wszCrLf
        INVOKE crt_wprintf, uni$("Press any key to continue ... ")
    .ELSE
        INVOKE crt_wprintf, ADDR wszCrLf
        INVOKE crt_wprintf, pwszPrompt
    .ENDIF   
    INVOKE crt__getch
    .IF (eax == 0) || (eax == 0E0h)
        INVOKE crt__getch
    .ENDIF
    INVOKE crt_wprintf, ADDR wszCrLf
    ret
WaitKeyW ENDP
;======================================================
   
END main

Here is the original vbs script that works.

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
Set colItems = objWMIService.ExecQuery( _
    "SELECT * FROM Win32_BIOS",,48)
For Each objItem in colItems
    If isNull(objItem.BIOSVersion) Then
        Wscript.Echo "BIOSVersion: Sorry. Unable to find that information."
    Else
        Wscript.Echo "BIOSVersion: " & Join(objItem.BIOSVersion, ",")
    End If
Next
WScript.Quit


MichaelW

In the vbs code the Join function is being passed a comma in the Delimiter parameter, so I would guess that the array consists of comma-delimited BSTRs.
eschew obfuscation

LaptoniC

Result of the function is like a internal array that I showed in my post. Maybe it is some kind of COM structure that needs to be deciphered. It is string array but I don't know how COM arrays work.

00233070  01 00 80 01 04 00 00 00 00 00 00 00 38 69 22 00  .€.......8i".
00233080  01 00 00 00 00 00 00 00 AB AB AB AB AB AB AB AB  .......««««««««
00233090  00 00 00 00 00 00 00 00 C0 5D DA 33 F3 2C 00 18  ........À]Ú3ó,.
002330A0  14 00 00 00 48 00 50 00 51 00 4F 00 45 00 4D 00  ...H.P.Q.O.E.M.
002330B0  20 00 2D 00 20 00 31 00 00 00 AD BA 0D F0 AD BA   .-. .1...­º.ð­º
002330C0  AB AB AB AB AB AB AB AB 00 00 00 00 00 00 00 00  ««««««««........
002330D0  25 58 D9 D0 FC 2C 00 00 C4 00 21 00 30 2D 23 00  %XÙÐü,..Ä.!.0-#.


akane

#3
Your var_val variable actually should be of VARIANT type, or at least 4-dword variable (you have 3 dwords allocated). The first member of a variant is VARTYPE (a word) - the VT_*** enumeration from wtypes.h. Your hex dump shows that the variant is of type VT_NULL (first two bytes are 1,0), so the BIOSVersion property is not implemented. When the variant type would be VT_ARRAY|VT_BSTR, you can go forward and access the BSTR array by locking the array itself - calling SafeArrayAccessData function with var_val.parray parameter.
Number of strings you can find in var_val.parray->rgsabound[0].cElements.
VARIANT structure is defined in oaidl.h, it is a huge union of variable types, but for this project you can define it as 4 words and 2 dwords. The first word is "VARTYPE vt", and the first dword is a pointer to SAFEARRAY (SAFEARRAY* parray)

The delimiter in Join function:
QuoteOptional. String character used to separate the substrings in the returned string. If omitted, the space character is used.

Ah I see now, the dump is a SAFEARRAY structure.struct SAFEARRAY {
   unsigned short cDims;       // Count of dimensions in this array.
   unsigned short fFeatures;   // Flags used by the SafeArray routines documented below.
   unsigned long cbElements;   // Size of an element of the array. Does not include size of pointed-to data.
   unsigned long cLocks;      // Number of times the array has been  locked without corresponding unlock.
   void* pvData;             // Pointer to the data.
   SAFEARRAYBOUND rgsabound[1];      // One bound for each dimension.
}

cDims is one - single dimension.
fFeatures is FADF_AUTO|FADF_BSTR.
cbElements is 4 - size of BSTR type. rgsabound.
pvData points to BSTR array.
rgsabound.cElements is 1, so this array has only one string.

Load pvData into esi, and rgsabound.cElements into edi. Create a loop:
while edi
   dec edi
   print_wstring [esi]
   add esi,4
endwhile

LaptoniC

Thanks a lot. It is working now. Here is the working code for anyone interested.

.586
.MODEL FLAT,STDCALL
OPTION CASEMAP:NONE

INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\ole32.inc
INCLUDE \masm32\include\msvcrt.inc
;INCLUDE \masm32\include\gel32.inc

INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\ole32.lib
INCLUDELIB \masm32\lib\msvcrt.lib
;INCLUDELIB \masm32\lib\gel32.lib

INCLUDE c:\masm32\macros\ucmacros.asm

; located in ObjIdl.h

EOAC_NONE   EQU 0

; located in RpcDce.h

RPC_C_AUTHN_LEVEL_DEFAULT   EQU 0
RPC_C_IMP_LEVEL_DEFAULT     EQU 0

RPC_C_IMP_LEVEL_IMPERSONATE EQU 3

GUID2 STRUC
     dd1 DWORD ?
     dw1 WORD ?
     dw2 WORD ?
     db1 BYTE ?
     db2 BYTE ?
     db3 BYTE ?
     db4 BYTE ?
     db5 BYTE ?
     db6 BYTE ?
     db7 BYTE ?
     db8 BYTE ?
GUID2 ENDS

IWbemLocator STRUCT
    lpVtbl DWORD   ?
IWbemLocator ENDS

IWbemLocatorVtbl STRUCT
    QueryInterface DWORD   ?
    AddRef         DWORD   ?
    Release        DWORD   ?
    ConnectServer  DWORD   ?
IWbemLocatorVtbl ENDS

IWbemServices STRUCT
    lpVtbl DWORD   ?
IWbemServices ENDS

IWbemServicesVtbl STRUCT
    QueryInterface             DWORD   ?
    AddRef                     DWORD   ?
    Release                    DWORD   ?
    OpenNamespace              DWORD   ?
    CancelAsyncCall            DWORD   ?
    QueryObjectSink            DWORD   ?
    GetObject                  DWORD   ?
    GetObjectAsync             DWORD   ?
    PutClass                   DWORD   ?
    PutClassAsync              DWORD   ?
    DeleteClass                DWORD   ?
    DeleteClassAsync           DWORD   ?
    CreateClassEnum            DWORD   ?
    CreateClassEnumAsync       DWORD   ?
    PutInstance                DWORD   ?
    PutInstanceAsync           DWORD   ?
    DeleteInstance             DWORD   ?
    DeleteInstanceAsync        DWORD   ?
    CreateInstanceEnum         DWORD   ?
    CreateInstanceEnumAsync    DWORD   ?
    ExecQuery                  DWORD   ?
    ExecQueryAsync             DWORD   ?
    ExecNotificationQuery      DWORD   ?
    ExecNotificationQueryAsync DWORD   ?
    ExecMethod                 DWORD   ?
    ExecMethodAsync            DWORD   ?
IWbemServicesVtbl ENDS

IEnumWbemClassObject STRUCT
    lpVtbl          DWORD   ?
IEnumWbemClassObject ENDS

IEnumWbemClassObjectVtbl STRUCT
    QueryInterface DWORD   ?
    AddRef         DWORD   ?
    Release        DWORD   ?
    Reset          DWORD   ?
    Next           DWORD   ?
    NextAsync      DWORD   ?
    Clone          DWORD   ?
    Skip           DWORD   ?
IEnumWbemClassObjectVtbl ENDS

IWbemClassObject STRUCT
    lpVtbl DWORD   ?
IWbemClassObject ENDS

IWbemClassObjectVtbl STRUCT
    QueryInterface          DWORD   ?
    AddRef                  DWORD   ?
    Release                 DWORD   ?
    GetQualifierSet         DWORD   ?
    Get                     DWORD   ?
    Put                     DWORD   ?
    Delete                  DWORD   ?
    GetNames                DWORD   ?
    BeginEnumeration        DWORD   ?
    Next                    DWORD   ?
    EndEnumeration          DWORD   ?
    GetPropertyQualifierSet DWORD   ?
    GetObjectText           DWORD   ?
    SpawnDerivedClass       DWORD   ?
    SpawnInstance           DWORD   ?
    CompareTo               DWORD   ?
    GetPropertyOrigin       DWORD   ?
    InheritsFrom            DWORD   ?
    GetMethod               DWORD   ?
    PutMethod               DWORD   ?
    DeleteMethod            DWORD   ?
    BeginMethodEnumeration  DWORD   ?
    NextMethod              DWORD   ?
    EndMethodEnumeration    DWORD   ?
    GetMethodQualifierSet   DWORD   ?
    GetMethodOrigin         DWORD   ?
IWbemClassObjectVtbl ENDS

SAFEARRAYBOUND struct
cElements dd ?
lLbound dd ?
SAFEARRAYBOUND ends
   
SAFEARRAY    struct
cDims dw ?
fFeatures dw ?
cbElements dd ?
cLocks dd ?
pvData dd ?
rgsabound SAFEARRAYBOUND <>
SAFEARRAY ends

.CONST
   
    wszSelect  WORD "S","E","L","E","C","T"," ","*"," ","F","R","O","M"," ",0  ; the WSTR macro can't handle the asterisk
    wszCrLf    WORD 13,10,0


    WSTR        wszClass,    "Win32_BIOS"    ;<<< Set class here
    WSTR        wszProperty, "BIOSVersion"  ;<<< Set property here

    WSTR        wszNameSpace, "root\cimv2"
    WSTR        wszQueryLanguage, "WQL"
   
    WSTR        wszMsg, "Serial Number %i: %s"
   
WaitKeyW proto :PTR WORD
.DATA

    ; located in WbemCli.h
   
    WBEM_FLAG_CONNECT_USE_MAX_WAIT  EQU     80h
    WBEM_FLAG_FORWARD_ONLY          EQU     20h
    WBEM_INFINITE                   EQU     -1
    WBEM_E_INVALID_QUERY            EQU     80041017h
    WBEM_E_INVALID_QUERY_TYPE       EQU     80041018h
   
    IID_IWbemLocator                GUID2   <0dc12a687h,0737fh,011cfh,088h,04dh,000h,0aah,000h,04bh,02eh,024h>
   
    IID_IEnumWbemClassObject        GUID2   <027947e1h,0d731h,011ceh,0a3h,057h,000h,000h,000h,000h,000h,001h>
   
    IID_IWbemClassObject            GUID2   <0dc12a681h,0737fh,011cfh,088h,04dh,000h,0aah,000h,04bh,02eh,024h>
   
    ; located in WbemProv.h
   
    CLSID_WbemAdministrativeLocator GUID2   <0cb8555cch,09128h,011d1h,0adh,09bh,000h,0c0h,04fh,0d8h,0fdh,0ffh>
   
    locator     IWbemLocator            <>
    service     IWbemServices           <>
    enumerator  IEnumWbemClassObject    <>
    processor   IWbemClassObject        <>
   
    retCount    DWORD   ?
   
    var_val     DWORD   ?
                DWORD   ?
                DWORD   ?
                DWORD   ?


    pwszResult  PWORD   ?             
               
    wszQuery   WORD 256 dup(?)               

.CODE

  main:
   
    INVOKE CoInitializeEx, NULL, COINIT_MULTITHREADED
   
    INVOKE CoInitializeSecurity, NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL
       
    INVOKE CoCreateInstance, ADDR CLSID_WbemAdministrativeLocator, NULL, CLSCTX_INPROC_SERVER, ADDR IID_IWbemLocator, ADDR locator

    INVOKE lstrcatW, ADDR wszQuery, ADDR wszSelect
    INVOKE lstrcatW, ADDR wszQuery, ADDR wszClass
   
    mov esi, locator
    lodsd
    push    OFFSET service
    push    NULL
    push    NULL
    push    WBEM_FLAG_CONNECT_USE_MAX_WAIT
    push    NULL
    push    NULL
    push    NULL
    push    OFFSET wszNameSpace
    push    DWORD PTR [locator]
    call    DWORD PTR [eax][IWbemLocatorVtbl.ConnectServer]

    mov esi, service
    lodsd
    push    OFFSET enumerator
    push    NULL
    push    WBEM_FLAG_FORWARD_ONLY
    push    OFFSET wszQuery
    push    OFFSET wszQueryLanguage
    push    DWORD PTR [service]
    call    DWORD PTR [eax][IWbemServicesVtbl.ExecQuery]

    mov esi, enumerator
    lodsd
    push    OFFSET retCount
    push    OFFSET processor
    push    TRUE
    push    WBEM_INFINITE
    push    DWORD PTR [enumerator]
    call    DWORD PTR [eax][IEnumWbemClassObjectVtbl.Next]
   
    mov esi, processor
    lodsd
    push    NULL
    push    NULL
    push    OFFSET var_val
    push    0
    push    OFFSET wszProperty
    push    DWORD PTR [processor]
    call    DWORD PTR [eax][IWbemClassObjectVtbl.Get]
   
    mov esi, [var_val]
    mov edi, [var_val + 4]
    mov ecx, [var_val + 8]
   
    mov pwszResult, ecx

mov esi,[ecx].SAFEARRAY.pvData
mov edi,[ecx].SAFEARRAY.rgsabound.cElements

    INVOKE crt_wprintf, ADDR wszCrLf
   
   

   
.while edi
  mov ecx,[esi]
  INVOKE crt_wprintf, ADDR wszMsg, edi,ecx
  INVOKE crt_wprintf, ADDR wszCrLf
dec edi
   add esi,4
.endw



    INVOKE crt_wprintf, ADDR wszCrLf

    INVOKE CoUninitialize
   
    INVOKE WaitKeyW, uni$("Press any key to exit ...")
   
    INVOKE ExitProcess, 0

WaitKeyW PROC pwszPrompt:PTR WORD
    .DATA
        IFNDEF wszCrLf
            wszCrLf WORD 13,10,0
        ENDIF
    .CODE   
    .IF pwszPrompt == NULL
        INVOKE crt_wprintf, ADDR wszCrLf
        INVOKE crt_wprintf, uni$("Press any key to continue ... ")
    .ELSE
        INVOKE crt_wprintf, ADDR wszCrLf
        INVOKE crt_wprintf, pwszPrompt
    .ENDIF   
    INVOKE crt__getch
    .IF (eax == 0) || (eax == 0E0h)
        INVOKE crt__getch
    .ENDIF
    INVOKE crt_wprintf, ADDR wszCrLf
    ret
WaitKeyW ENDP
;======================================================
   
END main