/*++ Copyright (c) 1993 Microsoft Corporation Module Name: MEMMON.c Abstract: This module contains the NT/Win32 Pool Monitor Author: Lou Perazzoli (loup) 13-Sep-1993 Revision History: --*/ #include "perfmtrp.h" #include #include #include #include #define BUFFER_SIZE 64*1024 #define MAX_BUFFER_SIZE 10*1024*1024 PCHAR buffer; ULONG CurrentBufferSize = BUFFER_SIZE; #define CPU_USAGE 0 #define QUOTAS 1 #define TAG 0 #define ALLOC 1 #define FREE 2 #define DIFF 3 #define BYTES 4 #define EACH 5 #define LIGHT 6 #define NONPAGED 0 #define PAGED 1 #define BOTH 2 CHAR *PoolType[] = { "Nonp ", "Paged" }; CHAR LargeBuffer1[BUFFER_SIZE]; CHAR LargeBuffer2[BUFFER_SIZE]; #define NONAME_STRING_SIZE 14 CHAR NoName[] = {"No Name Found\0"}; ULONG TotalNoNameFound; #define NOFILE_STRING_SIZE 13 CHAR NoFileName[] = {"No File Name\0"}; ULONG TotalNoFileFound; #define META_FILE_STRING_SIZE 13 CHAR MetaFile[] = {"Fs Meta File\0"}; ULONG TotalFsMetaFile; #define OUT_STRING_SIZE 60 typedef struct _MEMMON_OUT { ULONG Valid; ULONG Standby; ULONG Modified; ULONG PageTable; CHAR String[OUT_STRING_SIZE]; WCHAR Null; } MEMMON_OUT, *PMEMMON_OUT; MEMMON_OUT OutBuffer[2000]; ULONG DisplayType = BOTH; ULONG SortBy = TAG; ULONG Paren; ULONG DelayTimeMsec = 5000; BOOLEAN Interactive; ULONG NumberOfInputRecords; INPUT_RECORD InputRecord; HANDLE InputHandle; HANDLE OriginalOutputHandle; HANDLE OutputHandle; DWORD OriginalInputMode; WORD NormalAttribute; WORD HighlightAttribute; ULONG NumberOfCols; ULONG NumberOfRows; ULONG NumberOfDetailLines; ULONG FirstDetailLine; CONSOLE_SCREEN_BUFFER_INFO OriginalConsoleInfo; ULONG NoHighlight; BOOLEAN DisplayTotals = FALSE; MEMMON_OUT Totals[2]; typedef struct _FILTER { union { UCHAR Tag[4]; ULONG TagUlong; }; BOOLEAN Exclude; } FILTER, *PFILTER; #define MAX_FILTER 64 FILTER Filter[MAX_FILTER]; ULONG FilterCount = 0; VOID ShowHelpPopup( VOID ); int __cdecl ulcomp(const void *e1,const void *e2); int __cdecl ulcomp(const void *e1,const void *e2) { ULONG u1; switch (SortBy) { case TAG: u1 = (strcmp (((PMEMMON_OUT)e1)->String, ((PMEMMON_OUT)e2)->String)); return u1; break; case ALLOC: u1 = ((PMEMMON_OUT)e2)->Valid - ((PMEMMON_OUT)e1)->Valid; return (u1); break; case FREE: u1 = ((PMEMMON_OUT)e2)->Standby - ((PMEMMON_OUT)e1)->Standby; return (u1); break; case BYTES: u1 = ((PMEMMON_OUT)e2)->Modified - ((PMEMMON_OUT)e1)->Modified; return (u1); break; case DIFF: u1 = ((PMEMMON_OUT)e2)->PageTable - ((PMEMMON_OUT)e1)->PageTable; return (u1); break; case EACH: return (0); break; default: return(0); break; } } BOOLEAN CheckSingleFilter ( PCHAR Tag, PCHAR Filter ) { ULONG i; CHAR tc; CHAR fc; for ( i = 0; i < 4; i++ ) { tc = *Tag++; fc = *Filter++; if ( fc == '*' ) return TRUE; if ( fc == '?' ) continue; if ( tc != fc ) return FALSE; } return TRUE; } BOOLEAN CheckFilters ( PSYSTEM_POOLTAG TagInfo ) { BOOLEAN pass; ULONG i; PCHAR tag; // // If there are no filters, all tags pass. // if ( FilterCount == 0 ) { return TRUE; } // // There are filters. If the first filter excludes tags, then any // tag not explicitly mentioned passes. If the first filter includes // tags, then any tag not explicitly mentioned fails. // if ( Filter[0].Exclude ) { pass = TRUE; } else { pass = FALSE; } tag = TagInfo->Tag; for ( i = 0; i < FilterCount; i++ ) { if ( CheckSingleFilter( tag, (PCHAR)&Filter[i].Tag ) ) { pass = !Filter[i].Exclude; } } return pass; } VOID AddFilter ( BOOLEAN Exclude, PCHAR FilterString ) { PFILTER f; PCHAR p; ULONG i; if ( FilterCount == MAX_FILTER ) { printf( "Too many filters specified. Limit is %d\n", MAX_FILTER ); return; } f = &Filter[FilterCount]; p = f->Tag; for ( i = 0; i < 4; i++ ) { if ( *FilterString == 0 ) break; *p++ = *FilterString++; } for ( ; i < 4; i++ ) { *p++ = ' '; } f->Exclude = Exclude; FilterCount++; return; } VOID ParseArgs ( int argc, char *argv[] ) { char *p; BOOLEAN exclude; argc--; argv++; while ( argc-- > 0 ) { p = *argv++; if ( *p == '-' || *p == '/' ) { p++; exclude = TRUE; switch ( tolower(*p) ) { case 'i': exclude = FALSE; case 'x': p++; if ( strlen(p) == 0 ) { printf( "missing filter string\n" ); ExitProcess( 1 ); } else if ( strlen(p) <= sizeof(ULONG) ) { AddFilter( exclude, p ); } else { printf( "filter string too long: %s\n", p ); ExitProcess( 1 ); } break; case 'e': DisplayTotals = TRUE; break; case 't': SortBy = TAG; break; case 'a': SortBy = ALLOC; break; case 'u': case 'b': SortBy = BYTES; break; case 'f': SortBy = FREE; break; case 'd': SortBy = DIFF; break; case 'm': SortBy = EACH; case 'l': NoHighlight = 1 - NoHighlight; break; case 'p': DisplayType += 1; if (DisplayType > BOTH) { DisplayType = NONPAGED; } break; case '(': case ')': Paren += 1; break; default: printf( "unknown switch: %s\n", p ); ExitProcess( 2 ); } } else { printf( "unknown switch: %s\n", p ); ExitProcess( 2 ); } } return; } BOOL WriteConsoleLine( HANDLE OutputHandle, WORD LineNumber, LPSTR Text, BOOL Highlight ) { COORD WriteCoord; DWORD NumberWritten; DWORD TextLength; WriteCoord.X = 0; WriteCoord.Y = LineNumber; if (!FillConsoleOutputCharacter( OutputHandle, ' ', NumberOfCols, WriteCoord, &NumberWritten ) ) { return FALSE; } if (!FillConsoleOutputAttribute( OutputHandle, (WORD)((Highlight && !NoHighlight) ? HighlightAttribute : NormalAttribute), NumberOfCols, WriteCoord, &NumberWritten ) ) { return FALSE; } if (Text == NULL || (TextLength = strlen( Text )) == 0) { return TRUE; } else { return WriteConsoleOutputCharacter( OutputHandle, Text, TextLength, WriteCoord, &NumberWritten ); } } int __cdecl main( argc, argv ) int argc; char *argv[]; { NTSTATUS Status; ULONG LastCount = 0; SYSTEM_BASIC_INFORMATION BasicInfo; SYSTEM_PERFORMANCE_INFORMATION PerfInfo; PSYSTEM_POOLTAG_INFORMATION PoolInfo; PSYSTEM_POOLTAG_INFORMATION PoolInfoOld; PUCHAR PreviousBuffer; PUCHAR CurrentBuffer; PUCHAR TempBuffer; BOOLEAN DoHelp; BOOLEAN DoQuit; int NumberOfPoolTags; int i; UCHAR LastKey; PMEMMON_OUT Out; LONG ScrollDelta; WORD DisplayLine, LastDetailRow; CHAR OutputBuffer[ 512 ]; NTSTATUS status; PSYSTEM_MEMORY_INFORMATION MemInfo; PSYSTEM_MEMORY_INFO Info; PSYSTEM_MEMORY_INFO InfoEnd; PUCHAR String; ULONG TotalValid; ULONG TotalPageTable; ULONG TotalModified; ULONG TotalStandby; SYSTEMTIME Time; ULONG PageKb; DoHelp = FALSE; DoQuit = FALSE; Interactive = TRUE; buffer = VirtualAlloc (NULL, MAX_BUFFER_SIZE, MEM_RESERVE, PAGE_READWRITE); if (buffer == NULL) { printf("Memory allocation failed\n"); return 0; } buffer = VirtualAlloc (buffer, BUFFER_SIZE, MEM_COMMIT, PAGE_READWRITE); if (buffer == NULL) { printf("Memory commit failed\n"); return 0; } CurrentBufferSize = BUFFER_SIZE; ParseArgs( argc, argv ); InputHandle = GetStdHandle( STD_INPUT_HANDLE ); OriginalOutputHandle = GetStdHandle( STD_OUTPUT_HANDLE ); if (Interactive) { if (InputHandle == NULL || OriginalOutputHandle == NULL || !GetConsoleMode( InputHandle, &OriginalInputMode ) ) { Interactive = FALSE; } else { OutputHandle = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, CONSOLE_TEXTMODE_BUFFER, NULL ); if (OutputHandle == NULL || !GetConsoleScreenBufferInfo( OriginalOutputHandle, &OriginalConsoleInfo ) || !SetConsoleScreenBufferSize( OutputHandle, OriginalConsoleInfo.dwSize ) || !SetConsoleActiveScreenBuffer( OutputHandle ) || !SetConsoleMode( InputHandle, 0 ) ) { if (OutputHandle != NULL) { CloseHandle( OutputHandle ); OutputHandle = NULL; } Interactive = FALSE; } else { NormalAttribute = 0x1F; HighlightAttribute = 0x71; NumberOfCols = OriginalConsoleInfo.dwSize.X; NumberOfRows = OriginalConsoleInfo.dwSize.Y; NumberOfDetailLines = NumberOfRows; } } } NtQuerySystemInformation( SystemBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL ); if (GetPriorityClass(GetCurrentProcess()) == NORMAL_PRIORITY_CLASS) { SetPriorityClass(GetCurrentProcess(),HIGH_PRIORITY_CLASS); } PageKb = BasicInfo.PageSize / 1024; PreviousBuffer = NULL; CurrentBuffer = LargeBuffer1; while(TRUE) { Status = NtQuerySystemInformation( SystemPerformanceInformation, &PerfInfo, sizeof(PerfInfo), NULL ); if ( !NT_SUCCESS(Status) ) { printf("Query perf Failed %lx\n",Status); break; } retry01: status = NtQuerySystemInformation (SystemFullMemoryInformation, buffer, CurrentBufferSize, NULL); if ((status == STATUS_INFO_LENGTH_MISMATCH) || (status == STATUS_DATA_OVERRUN)) { // // Increase buffer size. // CurrentBufferSize += 8192; buffer = VirtualAlloc (buffer, CurrentBufferSize, MEM_COMMIT, PAGE_READWRITE); if (buffer == NULL) { printf("Memory commit failed\n"); ExitProcess(0); } goto retry01; } if (!NT_SUCCESS (status)) { printf("query system information failed %lx\n",status); return 1; } TotalValid = 0; TotalPageTable = 0; TotalStandby = 0; TotalModified = 0; MemInfo = (PSYSTEM_MEMORY_INFORMATION)buffer; Info = &MemInfo->Memory[0]; InfoEnd = (PSYSTEM_MEMORY_INFO)MemInfo->StringStart; // // Calculate pool tags and display information. // PoolInfo = (PSYSTEM_POOLTAG_INFORMATION)CurrentBuffer; i = PoolInfo->Count; PoolInfoOld = (PSYSTEM_POOLTAG_INFORMATION)PreviousBuffer; DisplayLine = 0; sprintf( OutputBuffer, " Memory:%8ldK Avail:%8ldK PageFlts:%6ld InRam Krnl:%5ldK P:%5ldK", BasicInfo.NumberOfPhysicalPages*(BasicInfo.PageSize/1024), PerfInfo.AvailablePages*(BasicInfo.PageSize/1024), PerfInfo.PageFaultCount - LastCount, (PerfInfo.ResidentSystemCodePage + PerfInfo.ResidentSystemDriverPage)*(BasicInfo.PageSize/1024), (PerfInfo.ResidentPagedPoolPage)*(BasicInfo.PageSize/1024) ); WriteConsoleLine( OutputHandle, DisplayLine++, OutputBuffer, FALSE ); LastCount = PerfInfo.PageFaultCount; sprintf( OutputBuffer, " Commit:%7ldK Limit:%7ldK Peak:%7ldK Pool N:%5ldK P:%5ldK", PerfInfo.CommittedPages*(BasicInfo.PageSize/1024), PerfInfo.CommitLimit*(BasicInfo.PageSize/1024), PerfInfo.PeakCommitment*(BasicInfo.PageSize/1024), PerfInfo.NonPagedPoolPages*(BasicInfo.PageSize/1024), PerfInfo.PagedPoolPages*(BasicInfo.PageSize/1024) ); WriteConsoleLine( OutputHandle, DisplayLine++, OutputBuffer, FALSE ); WriteConsoleLine( OutputHandle, DisplayLine++, " Valid Transition Modified PageTables Name ", FALSE ); WriteConsoleLine( OutputHandle, DisplayLine++, NULL, FALSE ); Out = &OutBuffer[3]; if (DisplayTotals) { RtlZeroMemory( Totals, sizeof(MEMMON_OUT)*2 ); } TotalNoNameFound = 0; TotalFsMetaFile = 0; Out[0].Valid = 0; Out[0].PageTable = 0; Out[0].Standby = 0; Out[0].Modified = 0; RtlCopyMemory (Out[0].String, NoName, NONAME_STRING_SIZE); Out[1].Valid = 0; Out[1].PageTable = 0; Out[1].Standby = 0; Out[1].Modified = 0; RtlCopyMemory (Out[1].String, MetaFile, META_FILE_STRING_SIZE); Out[2].Valid = 0; Out[2].PageTable = 0; Out[2].Standby = 0; Out[2].Modified = 0; RtlCopyMemory (Out[2].String, NoFileName, NOFILE_STRING_SIZE); while (Info < InfoEnd) { // if ( !CheckFilters(&PoolInfo->TagInfo[i]) ) { // continue; // } Out->Valid = Info->ValidCount*PageKb * PageKb; Out->Modified = Info->PageTableCount*PageKb; Out->Standby = Info->TransitionCount*PageKb; Out->PageTable = Info->ModifiedCount*PageKb; TotalValid += Info->ValidCount; TotalPageTable += Info->PageTableCount; TotalStandby += Info->TransitionCount; TotalModified += Info->ModifiedCount; RtlZeroMemory (Out->String, OUT_STRING_SIZE); if (Info->StringOffset != 0) { if (*(PUCHAR)(Info->StringOffset + 1) == 0) { WideCharToMultiByte (CP_ACP, 0, (LPCWSTR)Info->StringOffset, -1, (LPSTR)Out->String, OUT_STRING_SIZE, NULL, NULL); } else { if (!strncmp (Info->StringOffset, MetaFile, META_FILE_STRING_SIZE)) { TotalNoNameFound += 1; Out[1].Valid += Info->ValidCount*PageKb * PageKb; Out[1].PageTable += Info->PageTableCount*PageKb; Out[1].Standby += Info->TransitionCount*PageKb; Out[1].Modified += Info->ModifiedCount*PageKb; Out -= 1; } else if (!strncmp (Info->StringOffset, NoFileName, NOFILE_STRING_SIZE)) { TotalNoNameFound += 1; Out[2].Valid += Info->ValidCount*PageKb * PageKb; Out[2].PageTable += Info->PageTableCount*PageKb; Out[2].Standby += Info->TransitionCount*PageKb; Out[2].Modified += Info->ModifiedCount*PageKb; Out -= 1; } else { RtlCopyMemory (Out->String, Info->StringOffset, OUT_STRING_SIZE); } } } else { TotalNoNameFound += 1; Out[0].Valid += Info->ValidCount*PageKb * PageKb; Out[0].PageTable += Info->PageTableCount*PageKb; Out[0].Standby += Info->TransitionCount*PageKb; Out[0].Modified += Info->ModifiedCount*PageKb; Out -= 1; } Out += 1; Info += 1; i++; } //end for // // Sort the running working set buffer // NumberOfPoolTags = Out - &OutBuffer[0]; qsort((void *)&OutBuffer, (size_t)NumberOfPoolTags, (size_t)sizeof(MEMMON_OUT), ulcomp); LastDetailRow = (WORD)(NumberOfRows - (DisplayTotals ? (DisplayType == BOTH ? 3 : 2) : 0)); for (i = FirstDetailLine; i < NumberOfPoolTags; i++) { if (DisplayLine >= LastDetailRow) { break; } sprintf( OutputBuffer, " %8ld %8ld %8ld %8ld %s", OutBuffer[i].Valid, OutBuffer[i].Standby, OutBuffer[i].Modified, OutBuffer[i].PageTable, OutBuffer[i].String ); WriteConsoleLine( OutputHandle, DisplayLine++, OutputBuffer, FALSE ); } if (DisplayTotals) { WriteConsoleLine( OutputHandle, DisplayLine++, NULL, FALSE ); for (i = 0; i < 2; i++) { if ( (int)DisplayType == i || DisplayType == BOTH ) { sprintf( OutputBuffer, "Total %9ld %9ld %8ld %7ld", TotalValid, TotalStandby, TotalModified, TotalPageTable ); WriteConsoleLine( OutputHandle, DisplayLine++, OutputBuffer, FALSE ); } } } if (PreviousBuffer == NULL) { PreviousBuffer = LargeBuffer2; } TempBuffer = PreviousBuffer; PreviousBuffer = CurrentBuffer; CurrentBuffer = TempBuffer; while (WaitForSingleObject( InputHandle, DelayTimeMsec ) == STATUS_WAIT_0) { // // Check for input record // if (ReadConsoleInput( InputHandle, &InputRecord, 1, &NumberOfInputRecords ) && InputRecord.EventType == KEY_EVENT && InputRecord.Event.KeyEvent.bKeyDown ) { LastKey = InputRecord.Event.KeyEvent.uChar.AsciiChar; if (LastKey < ' ') { ScrollDelta = 0; if (LastKey == 'C'-'A'+1) { DoQuit = TRUE; } else switch (InputRecord.Event.KeyEvent.wVirtualKeyCode) { case VK_ESCAPE: DoQuit = TRUE; break; case VK_PRIOR: ScrollDelta = -(LONG)(InputRecord.Event.KeyEvent.wRepeatCount * NumberOfDetailLines); break; case VK_NEXT: ScrollDelta = InputRecord.Event.KeyEvent.wRepeatCount * NumberOfDetailLines; break; case VK_UP: ScrollDelta = -InputRecord.Event.KeyEvent.wRepeatCount; break; case VK_DOWN: ScrollDelta = InputRecord.Event.KeyEvent.wRepeatCount; break; case VK_HOME: FirstDetailLine = 0; break; case VK_END: FirstDetailLine = NumberOfPoolTags - NumberOfDetailLines; break; } if (ScrollDelta != 0) { if (ScrollDelta < 0) { if (FirstDetailLine <= (ULONG)-ScrollDelta) { FirstDetailLine = 0; } else { FirstDetailLine += ScrollDelta; } } else { FirstDetailLine += ScrollDelta; if (FirstDetailLine >= (NumberOfPoolTags - NumberOfDetailLines)) { FirstDetailLine = NumberOfPoolTags - NumberOfDetailLines; } } } } else { switch (toupper( LastKey )) { case 'Q': // // Go to the bottom of the current screen when // we quit. // DoQuit = TRUE; break; case 'T': SortBy = TAG; FirstDetailLine = 0; break; case 'A': SortBy = ALLOC; FirstDetailLine = 0; break; case 'U': case 'B': SortBy = BYTES; FirstDetailLine = 0; break; case 'F': SortBy = FREE; FirstDetailLine = 0; break; case 'D': SortBy = DIFF; FirstDetailLine = 0; break; case 'M': SortBy = EACH; FirstDetailLine = 0; break; case 'L': NoHighlight = 1 - NoHighlight; break; case 'P': DisplayType += 1; if (DisplayType > BOTH) { DisplayType = NONPAGED; } FirstDetailLine = 0; break; case 'X': case '(': case ')': Paren += 1; break; case 'E': DisplayTotals = !DisplayTotals; FirstDetailLine = 0; break; case 'H': case '?': DoHelp = TRUE; break; } } break; } } if (DoQuit) { break; } if (DoHelp) { DoHelp = FALSE; ShowHelpPopup(); } } if (Interactive) { SetConsoleActiveScreenBuffer( OriginalOutputHandle ); SetConsoleMode( InputHandle, OriginalInputMode ); CloseHandle( OutputHandle ); } ExitProcess( 0 ); return 0; } VOID ShowHelpPopup( VOID ) { HANDLE PopupHandle; WORD n; PopupHandle = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, CONSOLE_TEXTMODE_BUFFER, NULL ); if (PopupHandle == NULL) { return; } SetConsoleActiveScreenBuffer( PopupHandle ); n = 0; WriteConsoleLine( PopupHandle, n++, NULL, FALSE ); WriteConsoleLine( PopupHandle, n++, " Poolmon Help", FALSE ); WriteConsoleLine( PopupHandle, n++, NULL, FALSE ); WriteConsoleLine( PopupHandle, n++, " columns:", FALSE ); WriteConsoleLine( PopupHandle, n++, " Tag is the 4 byte tag given to the pool allocation", FALSE ); WriteConsoleLine( PopupHandle, n++, " Type is paged or nonp(aged)", FALSE ); WriteConsoleLine( PopupHandle, n++, " Allocs is count of all alloctions", FALSE ); WriteConsoleLine( PopupHandle, n++, " ( ) is difference in Allocs column from last update", FALSE ); WriteConsoleLine( PopupHandle, n++, " Frees is count of all frees", FALSE ); WriteConsoleLine( PopupHandle, n++, " ( ) difference in Frees column from last update", FALSE ); WriteConsoleLine( PopupHandle, n++, " Diff is (Allocs - Frees)", FALSE ); WriteConsoleLine( PopupHandle, n++, " Bytes is the total bytes consumed in pool", FALSE ); WriteConsoleLine( PopupHandle, n++, " ( ) difference in Bytes column from last update", FALSE ); WriteConsoleLine( PopupHandle, n++, " Per Alloc is (Bytes / Diff)", FALSE ); WriteConsoleLine( PopupHandle, n++, NULL, FALSE ); WriteConsoleLine( PopupHandle, n++, " switches: ", FALSE ); WriteConsoleLine( PopupHandle, n++, " ? or h - gives this help", FALSE ); WriteConsoleLine( PopupHandle, n++, " q - quits", FALSE ); WriteConsoleLine( PopupHandle, n++, " p - toggles default pool display between both, paged, and nonpaged", FALSE ); WriteConsoleLine( PopupHandle, n++, " e - toggles totals lines on and off", FALSE ); WriteConsoleLine( PopupHandle, n++, " l - toggles highlighting of changed lines on and off", FALSE ); WriteConsoleLine( PopupHandle, n++, NULL, FALSE ); WriteConsoleLine( PopupHandle, n++, " sorting switches:", FALSE ); WriteConsoleLine( PopupHandle, n++, " t - tag a - allocations", FALSE ); WriteConsoleLine( PopupHandle, n++, " f - frees d - difference", FALSE ); WriteConsoleLine( PopupHandle, n++, " b - bytes m - per alloc", FALSE ); WriteConsoleLine( PopupHandle, n++, " (u is the same as b)", FALSE ); WriteConsoleLine( PopupHandle, n++, NULL, FALSE ); WriteConsoleLine( PopupHandle, n++, " ) - toggles sort between primary tag and value in ( )", FALSE ); WriteConsoleLine( PopupHandle, n++, NULL, FALSE ); WriteConsoleLine( PopupHandle, n++, " command line switches", FALSE ); WriteConsoleLine( PopupHandle, n++, " -i - list only matching tags", FALSE ); WriteConsoleLine( PopupHandle, n++, " -x - list everything except matching tags", FALSE ); WriteConsoleLine( PopupHandle, n++, " can include * and ?", FALSE ); WriteConsoleLine( PopupHandle, n++, " -peltafdbum) - as listed above", FALSE ); WriteConsoleLine( PopupHandle, n++, NULL, FALSE ); WriteConsoleLine( PopupHandle, n++, NULL, FALSE ); while (TRUE) { if (WaitForSingleObject( InputHandle, DelayTimeMsec ) == STATUS_WAIT_0 && ReadConsoleInput( InputHandle, &InputRecord, 1, &NumberOfInputRecords ) && InputRecord.EventType == KEY_EVENT && InputRecord.Event.KeyEvent.bKeyDown && InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE ) { break; } } SetConsoleActiveScreenBuffer( OutputHandle ); CloseHandle( PopupHandle ); return; }