├── LICENSE.txt
├── README.md
├── args.cpp
├── bin
└── Release
│ └── mtee.exe
├── handler.cpp
├── header.h
├── help.cpp
├── helpers.cpp
├── icon1.ico
├── main.cpp
├── mtee-screenshot1.png
├── mtee.cbp
├── output.cpp
└── perr.cpp
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Ritchie Lawrence
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Table of Contents
2 |
3 | * [Synopsis](#synopsis)
4 | * [Usage](#usage)
5 | * [Examples](#examples)
6 | * [FAQs](#faqs)
7 | * [Screenshots](#screenshots)
8 | * [Revisions](#revisions)
9 | * [Copyright and License](#copyright-and-license)
10 |
11 | ## Synopsis
12 |
13 | Mtee is a Win32 console application that sends any data it receives to stdout and to any number of files. Useful if you want to watch and record the output from a batch file or program. It can also prefix each line of output with a timestamp.
14 |
15 | Mtee is a 19kb standalone executable. It does not create any temporary files or write to the registry. There is no installation procedure, just run it. To remove all traces of Mtee from your system, just delete it.
16 |
17 | Mtee is simple to use and only has several options. To list them, type:-
18 |
19 | ```batch
20 | mtee /?
21 | ```
22 |
23 | ## Usage
24 |
25 |
26 | MTEE [/A | /U] [/C] [/D] [/T] [/E] [[/+] file] [...]
27 |
28 | /A Convert output to ANSI. Default output is same as input.
29 | /C Continue if errors occur opening/writing to file (advanced users only).
30 | /D Prefix each line of output with local date in YYYY-MM-DD format.
31 | /T Prefix each line of output with local time in HH:MM:SS.MSS format.
32 | /U Convert output to UNICODE. Default output is same as input.
33 | /E Exit with exit code of piped process.
34 | /ET Calculate and display elapsed time.
35 | /+ Append to existing file. If omitted, existing file is overwritten.
36 | file File to receive the output. File is overwritten if /+ not specified.
37 | ... Any number of additional files. Use /+ before each file to append.
38 |
39 | Example usage:-
40 |
41 | 1) script.cmd | mtee result.txt
42 | 2) ftp -n -s:ftp.scr | mtee local.log /+ \\server\logs$\remote.log
43 | 3) update.cmd 2>&1 | mtee/d/t/+ log.txt
44 |
45 | 1) Sends the output of script.cmd to the console and to result.log. If
46 | result.txt already exists, it will be overwritten.
47 | 2) Sends output of automated ftp session to the console and two log files,
48 | local.log is overwritten if it already exists, remote.log is appended to.
49 | 3) Redirects stdout and stderr from update.cmd to console and appends to
50 | log.txt. Each line is prefixed with local date and time.
51 |
52 |
53 | ## Examples
54 |
55 | View Mtee help screen:-
56 |
57 | ```batch
58 | mtee/?
59 | ````
60 |
61 | Send the output of script.cmd to the console and to RESULT.LOG. If RESULT.LOG already exists, it will be overwritten:-
62 |
63 | ```batch
64 | script.cmd | mtee result.log
65 | ```
66 |
67 | Send the output of the automated ftp session to the console and to two log files, LOCAL.LOG is overwritten if it already exists. REMOTE.LOG is appended to if it exists, otherwise it is created:-
68 |
69 | ```batch
70 | ftp -n -s:ftp.scr | mtee local.log /+ \\server\logs\remote.log
71 | ```
72 |
73 | Make two copies of LOG whilst viewing LOG on the screen. If NEW1 and NEW2 already exist, they are overwritten:-
74 |
75 | ```batch
76 | mtee < log new1 new2
77 | ```
78 |
79 | Redirect stdout and stderr from UPDATE.CMD to the console and appends to LOG.TXT. Each line is prefixed with local date and time:-
80 |
81 | ```batch
82 | update.cmd 2>&1 | mtee/d/t/+ log.txt
83 | ```
84 |
85 | Send the output from BACKUP.CMD to the console and two remote log files. If there is an error opening any of the log files (server offline for instance) MTEE will continue. If the destination files already exist, they are appended to:-
86 |
87 | ```batch
88 | backup.cmd | mtee /c/+ \\svr1\log$\bu.log /+ \\svr2\logs$\bu.log
89 | ```
90 |
91 | Make multiple carbon copies of patch.exe:-
92 |
93 | ```batch
94 | type patch.exe|mtee \\pc1\c$\patch.exe \\pc2\c$\patch.exe \\pc3\c$\patch.exe
95 | ```
96 |
97 | Make a unicode log of HFNETCHK:-
98 |
99 | ```batch
100 | hfnetchk|mtee/u log
101 | ```
102 |
103 | Display stdout on the console, and stderr on the console and also to a log file with each line of stderr prefixed with local date and time:-
104 |
105 | ```batch
106 | batch.cmd 2>&1 1>&3 3>&1 |mtee/t/d log
107 | ```
108 |
109 | ## FAQs
110 |
111 | How can I determine the exit code of the process piped into Mtee?
112 |
113 | > Update Mtee to at least version v2.21 and use the /E option.
114 |
115 | ## Screenshots
116 |
117 | 
118 |
119 | ## Revisions
120 |
121 | Revision | Date | Changes
122 | ---|---|---
123 | 2.3 | 2018-06-21 | Added /ET option (calculate and display elapsed time).
124 | 2.21 | 2016-08-07 | Added /E option (exit with exit code of process piped into Mtee). Cleaned up code - Mtee compiles without errors or warnings using a default install of the [CodeBlocks IDE](http://www.codeblocks.org/).
125 | 2.2 | 2016-06-10 | Credit to Jari Kulmala for implementing workaround to avoid possible bug in Windows 10 where program takes 30 seconds to exit.
126 | 2.1 | 2013-03-01 | Mtee is now open source software released under the MIT License. Credit to Jari Kulmala for addressing the following:-
mtee is now Windows 8 compatible
mtee assumed all files < 4GB
echo "t013\|mtee /u con" entered a continuous loop
"echo x x x x \| mtee" caused mtee to guess input was unicode
redirection to console and con device as output file was not supported
127 | 2.0 | 2003-08-27 | The following features are new to Mtee v2.0:-
Read and output unicode
Convert ANSI to unicode (and vice-versa)
Reads text and binary data without performing any character translations
Support for unicode filenames of ~32,000 characters
Smaller than ever. Mtee is now just 11kb (and no, it's not compressed!)
128 |
129 | ## Copyright and License
130 |
131 | Code and documentation copyright 2001-2016 Ritchie Lawrence. Code released under [MIT License](https://github.com/ritchielawrence/mtee/blob/master/LICENSE.txt)
132 |
--------------------------------------------------------------------------------
/args.cpp:
--------------------------------------------------------------------------------
1 | #include "header.h"
2 |
3 | BOOL ParseCommandlineW(LPARGS args)
4 | {
5 | PWCHAR lpToken;
6 | PFILEINFO fi;
7 | //
8 | // init defaults
9 | //
10 | args->bHelp = FALSE; // /?
11 | args->bAnsi = FALSE; // /A
12 | args->bContinue = FALSE; // /C
13 | args->bAddDate = FALSE; // /D
14 | args->bIntermediate = FALSE; // /I
15 | args->bAddTime = FALSE; // /T
16 | args->bUnicode = FALSE; // /U
17 | args->bFwdExitCode = FALSE; // /E
18 | args->bElapsedTime = FALSE; // /ET
19 | args->dwBufSize = 0x4000;
20 | args->dwPeekTimeout = 50;
21 | //
22 | // point to first fileinfo and init. This will contain the entry for the output file that is the console, unless redirected.
23 | //
24 | fi = &args->fi;
25 | fi->hFile = NULL;
26 | fi->lpFileName = NULL;
27 | fi->fiNext = NULL;
28 | fi->bAppend = FALSE;
29 |
30 | while(GetCommandLineTokenW(&lpToken))
31 | {
32 | if(!lstrcmpiW(lpToken, L"/?")) // help
33 | args->bHelp = TRUE;
34 | else if(!lstrcmpiW(lpToken, L"/A")) // ansi
35 | args->bAnsi = TRUE;
36 | else if(!lstrcmpiW(lpToken, L"/C")) // continue
37 | args->bContinue = TRUE;
38 | else if(!lstrcmpiW(lpToken, L"/D")) // add date
39 | args->bAddDate = TRUE;
40 | else if(!lstrcmpiW(lpToken, L"/I")) // create intermediate dirs
41 | args->bIntermediate = TRUE;
42 | else if(!lstrcmpiW(lpToken, L"/E")) // forward exit code
43 | args->bFwdExitCode = TRUE;
44 | else if(!lstrcmpiW(lpToken, L"/T")) // add time
45 | args->bAddTime = TRUE;
46 | else if(!lstrcmpiW(lpToken, L"/U")) // unicode
47 | args->bUnicode = TRUE;
48 | else if(!lstrcmpiW(lpToken, L"/ET")) // elapsed time
49 | args->bElapsedTime = TRUE;
50 | else if(!lstrcmpiW(lpToken, L"/+")) // append
51 | {
52 | //
53 | // next token should be a filename, load it and create a new fileinfo struct
54 | //
55 | if(!GetCommandLineTokenW(&lpToken))
56 | {
57 | Verbose(TEXT("The /+ switch must be followed by a filename.\r\n"));
58 | return FALSE;
59 | }
60 | if(!CreateFileInfoStruct(&fi)) ExitProcess(Perror((DWORD)NULL));
61 | fi->bAppend = TRUE;
62 |
63 | if(!CheckFileName(lpToken)) ExitProcess(Perror(ERROR_INVALID_PARAMETER));
64 | if(!StringAllocW(&fi->lpFileName, lpToken)) ExitProcess(Perror((DWORD)NULL));
65 | }
66 | else
67 | {
68 | // unknown token so assume a filename
69 | if(!CreateFileInfoStruct(&fi)) ExitProcess(Perror((DWORD)NULL));
70 | if(!CheckFileName(lpToken)) ExitProcess(Perror(ERROR_INVALID_PARAMETER));
71 | if(!StringAllocW(&fi->lpFileName, lpToken)) ExitProcess(Perror((DWORD)NULL));
72 | }
73 | }
74 |
75 | //
76 | // validate the options
77 | //
78 | if((args->bAnsi + args->bUnicode) > 1)
79 | {
80 | Verbose(TEXT("The /A and /U switches cannot be used together.\r\n"));
81 | return FALSE;
82 | }
83 |
84 | return TRUE;
85 |
86 | }
87 |
88 | BOOL CheckFileName(PWCHAR lpToken)
89 | {
90 | PWCHAR p = lpToken;
91 | WCHAR strBadChars[] = L"<>|\"/";
92 | PWCHAR lpBadChars;
93 | if(!lstrcmpW(lpToken, L"")) return FALSE;
94 | while(*p)
95 | {
96 | lpBadChars = strBadChars;
97 | while(*lpBadChars) if(*p == *lpBadChars++) return FALSE;
98 | p++;
99 | }
100 | return TRUE;
101 | }
102 |
103 | PFILEINFO CreateFileInfoStruct(PFILEINFO *fi)
104 | {
105 |
106 | (*fi)->fiNext = (PFILEINFO) HeapAlloc(GetProcessHeap(), 0, sizeof(FILEINFO));
107 | if(!(*fi)->fiNext) return NULL;
108 | //
109 | // init and point to new struct
110 | //
111 | (*fi) = (*fi)->fiNext;
112 | (*fi)->hFile = NULL;
113 | (*fi)->lpFileName = NULL;
114 | (*fi)->bAppend = FALSE;
115 | (*fi)->fiNext = NULL;
116 | return *fi;
117 | }
118 |
119 | VOID FreeFileInfoStructs(PFILEINFO fi)
120 | {
121 | while(fi)
122 | {
123 | HeapFree(GetProcessHeap(), 0, fi->lpFileName);
124 | HeapFree(GetProcessHeap(), 0, fi = fi->fiNext);
125 | }
126 | }
127 |
128 | LPWSTR StringAllocW(PWCHAR *dest, LPCWSTR src)
129 | {
130 | *dest = (WCHAR *) HeapAlloc(GetProcessHeap(), 0, (lstrlenW(src) * sizeof(WCHAR) + sizeof(WCHAR)));
131 | if(!*dest) return NULL;
132 | else return lstrcpyW(*dest, src);
133 | }
134 |
135 | //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
136 | // Function: GetCommandLineToken()
137 | //
138 | // Synopsis: Parses a commandline, returning one token at a time
139 | //
140 | // Arguments: [lpToken] Pointer to a pointer. Points to the found token
141 | //
142 | // Returns: TRUE if a token is found, otherwise FALSE (end of commandline
143 | // reached
144 | //
145 | // Notes: Each call sets lpToken to point to each token in turn until no
146 | // more found
147 | //
148 | //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
149 | BOOL GetCommandLineTokenW(PWCHAR *lpToken)
150 | {
151 | static PWCHAR p = NULL; // keeps track of where we've got to on windows cmdline
152 | static PWCHAR lpTokenBuf = NULL; // buffer to hold each token in turn
153 | PWCHAR t; // keeps track of current token
154 | DWORD cQuote = 0; // keeps tracks of quotes
155 | BOOL bOutQuotes = TRUE; // true if outside of quotes
156 | BOOL bInToken = FALSE; // true if inside a token
157 |
158 | //
159 | // if p is NULL, then I'm being called for first time
160 | //
161 | if(!p)
162 | {
163 | //
164 | // get a pointer to windows commandline
165 | //
166 | p = GetCommandLineW();
167 |
168 | //
169 | // allocate enough memory to hold any tokens in the commandline
170 | //
171 | lpTokenBuf = (PWCHAR) HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * (lstrlenW(p) + sizeof(WCHAR)));
172 |
173 | if(!lpTokenBuf) ExitProcess(Perror((DWORD)NULL));
174 |
175 | //
176 | // skip over name of exe to get to start arguments
177 | // windows always removes leading whitespace and adds a trailing space
178 | // if not already present. [; ; prog/s] --> [prog /s]
179 | //
180 | if(*p == L'"')
181 | {
182 | //
183 | // if exe name begins with a quote(s), then skip over all beginning quotes
184 | //
185 | while(*p == L'"') p++;
186 | //
187 | // now skip to the ending quote(s)
188 | //
189 | while(*p != L'"') p++;
190 | //
191 | // finally skip any further trailing quotes
192 | //
193 | while(*p == L'"') p++;
194 | }
195 | //
196 | // didn't start with a quote, so skip to first whitespace
197 | //
198 | else
199 | {
200 | while((*p) && (*p != L' ') && (*p != L'\t')) p++;
201 | }
202 |
203 | //
204 | // skip over any whitespace to first token or end of commandline
205 | //
206 | while(*p && (*p == L' ' || *p == L'\t')) p++;
207 |
208 | }
209 |
210 | //
211 | // initialise token pointers and token buffer
212 | //
213 | *lpToken = t = lstrcpyW(lpTokenBuf, L"");
214 |
215 | //
216 | // now start to copy characters from windows commandline to token buffer
217 | //
218 | if(*p)
219 | {
220 | while(*p)
221 | {
222 | //
223 | // skip over whitespace if not inside a quoted token
224 | //
225 | if((*p == L' ' || *p == L'\t') && bOutQuotes)
226 | {
227 | break;
228 | }
229 | else if(*p == L'"')
230 | {
231 | bOutQuotes = !bOutQuotes;
232 | p++;
233 |
234 | //
235 | // three consecutive quotes make one 'real' quote
236 | //
237 | if(++cQuote == 3)
238 | {
239 | *t++ = L'"';
240 | cQuote = 0;
241 | bOutQuotes = !bOutQuotes;
242 | }
243 | continue;
244 | }
245 | //
246 | // if forward slash found then reset cQuote and toggle bInToken flag
247 | //
248 | else if(*p == L'/')
249 | {
250 | cQuote = 0;
251 | bInToken = !bInToken;
252 |
253 | //
254 | // if not a token and not in quotes then break and l
255 | //
256 | if(!bInToken && bOutQuotes) break;
257 | }
258 |
259 | cQuote = 0;
260 | bInToken = TRUE;
261 |
262 | //
263 | // add the character to the token
264 | //
265 | *t++ = *p++;
266 |
267 | } // while(*p)
268 |
269 |
270 | //
271 | // terminate the token
272 | //
273 | *t = L'\0';
274 |
275 | //
276 | // set p to point to next argument ready for next call
277 | //
278 | while(*p && (*p == L' ' || *p == L'\t')) p++;
279 |
280 | return TRUE;
281 | }
282 |
283 | //
284 | // reached the end of windows commandline
285 | //
286 | else return FALSE;
287 | }
288 |
--------------------------------------------------------------------------------
/bin/Release/mtee.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ritchielawrence/mtee/c849b52344c8e851448f3933521a9d36f97eb777/bin/Release/mtee.exe
--------------------------------------------------------------------------------
/handler.cpp:
--------------------------------------------------------------------------------
1 | #include "header.h"
2 |
3 | extern DWORD dwCtrlEvent;
4 |
5 | BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
6 | {
7 | dwCtrlEvent = dwCtrlType;
8 | switch(dwCtrlType)
9 | {
10 | case CTRL_C_EVENT:
11 | case CTRL_BREAK_EVENT:
12 | return TRUE;
13 | default:
14 | return FALSE;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/header.h:
--------------------------------------------------------------------------------
1 | #define _WIN32_WINNT 0x0502
2 | #define WIN32_LEAN_AND_MEAN
3 | #include
4 | #include
5 |
6 | #define OP_IN_OUT_SHIFT (16) // out codes are 16 bits from in codes
7 | #define OP_ANSI_IN (0x00000001)
8 | #define OP_UNICODE_IN (0x00000002)
9 | #define OP_ANSI_OUT (0x00010000)
10 | #define OP_UNICODE_OUT (0x00020000)
11 | #define OP_HEX_OUT (0x00040000)
12 |
13 | // MTEE Operations
14 | #define OP_ANSI_IN_ANSI_OUT (OP_ANSI_IN | OP_ANSI_OUT)
15 | #define OP_ANSI_IN_UNICODE_OUT (OP_ANSI_IN | OP_UNICODE_OUT)
16 | #define OP_UNICODE_IN_ANSI_OUT (OP_UNICODE_IN | OP_ANSI_OUT)
17 | #define OP_UNICODE_IN_UNICODE_OUT (OP_UNICODE_IN | OP_UNICODE_OUT)
18 | #define OP_ANSI_IN_HEX_OUT (OP_ANSI_IN | OP_HEX_OUT)
19 | #define OP_UNICODE_IN_HEX_OUT (OP_UNICODE_IN | OP_HEX_OUT)
20 |
21 |
22 | // defines for GetWinVer()
23 | #define VER_PRE_NT4 (0L)
24 | #define VER_NT4 (1L)
25 | #define VER_2000 (2L)
26 | #define VER_XP (3L)
27 | #define VER_2003 (4L)
28 |
29 | #define _MERGE_RDATA_
30 |
31 |
32 | #if !defined (INVALID_SET_FILE_POINTER)
33 | #define INVALID_SET_FILE_POINTER ((DWORD)-1)
34 | #endif
35 |
36 | //#include
37 |
38 | #define CTRL_CLEAR_EVENT (0xFFFF)
39 |
40 | #define MAX_CONSOLE_PID_LIST (128L)
41 |
42 | //
43 | // Stores details of the files to be written to
44 | //
45 | typedef struct _FILEINFO
46 | {
47 | HANDLE hFile; // handle of open file
48 | PWCHAR lpFileName; // pointer to dynamically assigned wide buffer holding filename
49 | BOOL bAppend; // if true, append I/O to file instead of overwrite
50 | BOOL bIsConsole; // true if it is a console device
51 | _FILEINFO *fiNext; // pointer to next FILEINFO structure
52 | } FILEINFO, *PFILEINFO;
53 |
54 | //
55 | // Stores the commandline arguments/options
56 | //
57 | typedef struct _ARGS
58 | {
59 | BOOL bAddDate; // if true, prefix each newline with local date
60 | BOOL bAddTime; // if true, prefix each newline with local time
61 | BOOL bContinue; // if true, continue on I/O errors
62 | BOOL bHelp; // if true, show helpscreen
63 | BOOL bUnicode; // if true, output will be unicode (converted if required)
64 | BOOL bFwdExitCode; // if true, exit with exit code of piped process
65 | BOOL bAnsi; // if true, output will be ansi (converted if required)
66 | BOOL bIntermediate; // if true, create intermediate directories if required
67 | BOOL bElapsedTime; // if true, calculate and display elapsed time
68 | FILEINFO fi; // first FILEINFO structure in linked list
69 | DWORD dwBufSize; // max size of buffer for read operations
70 | DWORD dwPeekTimeout; // max ms to peek for input
71 | } ARGS, *LPARGS;
72 |
73 | // args.cpp
74 | BOOL CheckFileName(PWCHAR lpToken);
75 | BOOL GetCommandLineTokenW(PWCHAR *tokenPtr); // process ANSI commandline args
76 | BOOL ParseCommandlineW(LPARGS args);
77 | LPWSTR StringAllocW(PWCHAR *dest, LPCWSTR src);
78 | VOID FreeFileInfoStructs(PFILEINFO fi);
79 | PFILEINFO CreateFileInfoStruct(PFILEINFO *fi);
80 |
81 |
82 | //VOID MsgBox(LPCTSTR);
83 |
84 | BOOL WINAPI HandlerRoutine(DWORD);
85 | HWND GetConsoleHandle(VOID);
86 | VOID ShowFileType(HANDLE);
87 | int ShowHelp(VOID);
88 | VOID ShowPipeInfo(HANDLE h);
89 | VOID ConfigStdIn(HANDLE h);
90 | PWSTR CreateFullPathW(PWSTR szPath);
91 |
92 | //DWORD GetWinVer(VOID);
93 | DWORD GetFormattedDateTimeA(PCHAR lpBuf, BOOL bDate, BOOL bTime);
94 | DWORD GetFormattedDateTimeW(PWCHAR lpBuf, BOOL bDate, BOOL bTime);
95 | BOOL IsAnOutputConsoleDevice(HANDLE h);
96 | DWORD GetParentProcessId(VOID);
97 | HANDLE GetPipedProcessHandle(VOID);
98 |
99 | int FormatElapsedTime( LARGE_INTEGER* elapsedTime, PCHAR outBuf,
100 | const int outBufSize );
101 |
102 | // perr.cpp
103 | DWORD Perror(DWORD dwErrNum);
104 | VOID Verbose(LPCTSTR szMsg);
105 |
106 | // output.cpp
107 | BOOL WriteBufferToConsoleAndFilesA(LPARGS args, PCHAR lpBuf, DWORD dwCharsRead, BOOL AddDate, BOOL AddTime);
108 | BOOL WriteBufferToConsoleAndFilesW(LPARGS args, PWCHAR lpBuf, DWORD dwCharsRead, BOOL AddDate, BOOL AddTime);
109 | BOOL AnsiToUnicode(PWCHAR *lpDest, PCHAR lpSrc, LPDWORD lpSize);
110 | BOOL UnicodeToAnsi(PCHAR *lpDest, PWCHAR lpSrc, LPDWORD lpSize);
111 | BOOL WriteBom(PFILEINFO fi, BOOL bContinue);
112 |
--------------------------------------------------------------------------------
/help.cpp:
--------------------------------------------------------------------------------
1 | #include "header.h"
2 |
3 | int ShowHelp(VOID)
4 | {
5 | DWORD cBytes;
6 | PCHAR lpHelpMsg = (PCHAR)
7 |
8 | ("\r\nMTEE v2.21 Win32 Commandline Standard Stream Splitter for Windows XP .. 10.\r\n"
9 | "Copyright (c) 2001-2016 Ritchie Lawrence, http://www.commandline.co.uk.\r\n\r\n"
10 | // ----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
11 | " MTEE [/A | /U] [/C] [/D] [/T] [/E] [[/+] file] [...]\r\n\r\n"
12 | // ----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
13 | " /A Convert output to ANSI. Default output is same as input.\r\n"
14 | " /C Continue if errors occur opening/writing to file (advanced users only).\r\n"
15 | " /D Prefix each line of output with local date in YYYY-MM-DD format.\r\n"
16 | // " /I Create intermediate directories if required (advanced users only).\r\n"
17 | " /T Prefix each line of output with local time in HH:MM:SS.MSS format.\r\n"
18 | " /U Convert output to UNICODE. Default output is same as input.\r\n"
19 | " /E Exit with exit code of piped process.\r\n"
20 | " /ET Calculate and display elapsed time.\r\n"
21 | " /+ Append to existing file. If omitted, existing file is overwritten.\r\n"
22 | " file File to receive the output. File is overwritten if /+ not specified.\r\n"
23 | " ... Any number of additional files. Use /+ before each file to append.\r\n\r\n"
24 | // ----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
25 | " Example usage:-\r\n\r\n"
26 | // ----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
27 | " 1) script.cmd | mtee result.txt\r\n"
28 | " 2) ftp -n -s:ftp.scr | mtee local.log /+ \\\\server\\logs$\\remote.log\r\n"
29 | " 3) update.cmd 2>&1 | mtee/d/t/+ log.txt\r\n\r\n"
30 | // ----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
31 | " 1) Sends the output of script.cmd to the console and to result.log. If\r\n"
32 | " result.txt already exists, it will be overwritten.\r\n"
33 | " 2) Sends output of automated ftp session to the console and two log files,\r\n"
34 | " local.log is overwritten if it already exists, remote.log is appended to.\r\n"
35 | " 3) Redirects stdout and stderr from update.cmd to console and appends to\r\n"
36 | " log.txt. Each line is prefixed with local date and time.\r\n");
37 |
38 | WriteFile(
39 | GetStdHandle(STD_OUTPUT_HANDLE),
40 | lpHelpMsg,
41 | lstrlenA(lpHelpMsg),
42 | &cBytes,
43 | NULL
44 | );
45 |
46 | return 0;
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/helpers.cpp:
--------------------------------------------------------------------------------
1 | #include "header.h"
2 | #include
3 |
4 | //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
5 | // CreateFullPathW creates the directory structure pointed to by szPath
6 | // It creates everything upto the last backslash. For example:-
7 | // c:\dir1\dir2\ <-- creates dir1 and dir2
8 | // c:\dir1\file.dat <-- creates only dir1. This means you can pass this
9 | // function the path to a file, and it will just create the required
10 | // directories.
11 | // Absolute or relative paths can be used, as can path length of 32,767
12 | // characters, composed of components up to 255 characters in length. To
13 | // specify such a path, use the "\\?\" prefix. For example, "\\?\D:\".
14 | // To specify such a UNC path, use the "\\?\UNC\" prefix. For example,
15 | // "\\?\UNC\\".
16 | //
17 | //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
18 |
19 | PWSTR CreateFullPathW(PWSTR szPath)
20 | {
21 | PWCHAR p = szPath;
22 |
23 | while(*p++)
24 | {
25 | if(*p == L'\\')
26 | {
27 | *p = L'\0';
28 | CreateDirectoryW(szPath, NULL);
29 | *p = L'\\';
30 | }
31 | }
32 | return szPath;
33 | }
34 |
35 | /*
36 | VOID MsgBox(LPCTSTR msg)
37 | {
38 | MessageBox
39 | (
40 | NULL,
41 | msg,
42 | TEXT("MTEE BETA"),
43 | MB_OK | MB_SETFOREGROUND
44 | );
45 | }
46 | */
47 |
48 | /*ID ShowFileType(HANDLE h)
49 | {
50 | DWORD dwHndType;
51 | dwHndType = GetFileType(h);
52 |
53 | switch(dwHndType)
54 | {
55 | case FILE_TYPE_DISK: // stdin/stdout is redirected from/to disk: app.exeout
56 | MsgBox(TEXT("FILE_TYPE_DISK"));
57 | break;
58 | case FILE_TYPE_CHAR: // stdin from keyboard or nul/con/aux/comx, stdout to console or nul
59 | MsgBox(TEXT("FILE_TYPE_CHAR"));
60 | break;
61 | case FILE_TYPE_PIPE: // stdin is from pipe prn/lptx, stdout piped to anything
62 | MsgBox(TEXT("FILE_TYPE_PIPE"));
63 | break;
64 | default:
65 | MsgBox(TEXT("FILE_TYPE_UNKNOWN"));
66 | break;
67 | }
68 | }
69 |
70 | */
71 | /*DWORD GetWinVer(VOID)
72 | {
73 | OSVERSIONINFO os;
74 | os.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
75 | DWORD myVersion;
76 |
77 | if(!GetVersionEx(&os)) return 0;
78 |
79 | if(os.dwPlatformId != VER_PLATFORM_WIN32_NT) myVersion = VER_PRE_NT4;
80 | else
81 | {
82 | switch(os.dwMajorVersion)
83 | {
84 | case 3: myVersion = VER_PRE_NT4; break;
85 | case 4: myVersion = VER_NT4; break;
86 | default: myVersion = VER_2000;
87 | }
88 | }
89 |
90 | if(myVersion == VER_2000)
91 | {
92 | switch(os.dwMinorVersion)
93 | {
94 | case 0: myVersion = VER_2000; break;
95 | case 1: myVersion = VER_XP; break;
96 | default: myVersion = VER_2003;
97 | }
98 | }
99 |
100 | return myVersion;
101 | }
102 | */
103 | /*
104 | dwPlatFormID dwMajorVersion dwMinorVersion dwBuildNumber GetWinVer()
105 | 95 1 4 0 950 0
106 | 95 SP1 1 4 0 >950 && <=1080 0
107 | 95 OSR2 1 4 <10 >1080 0
108 | 98 1 4 10 1998 0
109 | 98 SP1 1 4 10 >1998 && <2183 0
110 | 98 SE 1 4 10 >=2183 0
111 | ME 1 4 90 3000 0
112 |
113 | NT 3.51 2 3 51 0
114 | NT 4 2 4 0 1381 1
115 |
116 | 2000 2 5 0 2195 2
117 | XP 2 5 1 xxxx 3
118 | 2003* 2 5 2 xxxx 4
119 |
120 | CE 3 0
121 | */
122 |
123 | //----------------------------------------------------------------------------
124 | DWORD GetFormattedDateTimeA(PCHAR lpBuf, BOOL bDate, BOOL bTime)
125 | {
126 | SYSTEMTIME st;
127 | DWORD dwSize = 0;
128 |
129 | GetLocalTime(&st);
130 |
131 | if(bDate)
132 | {
133 | dwSize += wsprintfA(lpBuf, "%04u-%02u-%02u ", st.wYear, st.wMonth, st.wDay);
134 | }
135 | if(bTime)
136 | {
137 | dwSize += wsprintfA(lpBuf + dwSize, "%02u:%02u:%02u.%03u ", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
138 | }
139 | return dwSize;
140 | }
141 |
142 | //----------------------------------------------------------------------------
143 | DWORD GetFormattedDateTimeW(PWCHAR lpBuf, BOOL bDate, BOOL bTime)
144 | {
145 | SYSTEMTIME st;
146 | DWORD dwSize = 0;
147 |
148 | GetLocalTime(&st);
149 |
150 | if(bDate)
151 | {
152 | dwSize += wsprintfW(lpBuf, L"%04u-%02u-%02u ", st.wYear, st.wMonth, st.wDay);
153 | }
154 | if(bTime)
155 | {
156 | dwSize += wsprintfW(lpBuf + dwSize, L"%02u:%02u:%02u.%03u ", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
157 | }
158 | return dwSize; // return characters written, not bytes
159 | }
160 |
161 | // determine whether the output is a console
162 | // this is hard. I first tried to use GetConsoleMode but it returns FALSE in case: mtee > con
163 | BOOL IsAnOutputConsoleDevice(HANDLE h)
164 | {
165 | if (GetFileType(h) == FILE_TYPE_CHAR)
166 | {
167 | // CON, NUL, ...
168 | DWORD dwBytesWritten;
169 | if (WriteConsoleA(h, "", 0, &dwBytesWritten, NULL))
170 | return TRUE;
171 | }
172 | return FALSE;
173 | }
174 |
175 | DWORD GetParentProcessId(VOID)
176 | {
177 | DWORD ppid = 0, pid = GetCurrentProcessId();
178 |
179 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
180 | if (hSnapshot == INVALID_HANDLE_VALUE) Perror((DWORD)NULL);
181 |
182 | PROCESSENTRY32 pe = {0};
183 | pe.dwSize = sizeof(PROCESSENTRY32);
184 |
185 | //
186 | // find our PID in the process snapshot then lookup parent PID
187 | //
188 | if(Process32First(hSnapshot, &pe)) {
189 | do {
190 | if (pe.th32ProcessID == pid) {
191 | ppid = pe.th32ParentProcessID;
192 | break;
193 | }
194 | } while(Process32Next(hSnapshot, &pe));
195 | }
196 | CloseHandle(hSnapshot);
197 | return ppid;
198 | }
199 |
200 | HANDLE GetPipedProcessHandle(VOID)
201 | {
202 | //
203 | // returns a handle to the process piped into mtee
204 | //
205 | DWORD dwProcCount, lpdwProcessList[MAX_CONSOLE_PID_LIST];
206 | HANDLE hPipedProcess = NULL;
207 | //
208 | // get an array of PIDs attached to this console
209 | //
210 | dwProcCount = GetConsoleProcessList(lpdwProcessList, MAX_CONSOLE_PID_LIST);
211 | for (DWORD dw=0; dw lpdwProcessList[mtee][A][B][C][cmd]
217 | //
218 | // find the first PID that is not this PID and not parent PID.
219 | //
220 | DWORD ppid = GetParentProcessId();
221 | DWORD cpid = GetCurrentProcessId();
222 | for (DWORD dw = 0; dw < dwProcCount; dw++)
223 | {
224 | HANDLE Handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, lpdwProcessList[dw]);
225 | if ((cpid != lpdwProcessList[dw]) && (ppid != lpdwProcessList[dw]))
226 | {
227 | hPipedProcess = Handle;
228 | break;
229 | }
230 | CloseHandle(Handle);
231 | }
232 | return hPipedProcess;
233 | }
234 |
235 | int FormatElapsedTime( LARGE_INTEGER* elapsedTime, PCHAR outBuf,
236 | const int outBufSize )
237 | {
238 | int h = 0;
239 | int m = 0;
240 | int len = 0;
241 |
242 | float s = float(elapsedTime->QuadPart / 1000000);
243 | m = (int)(s / 60.0);
244 | s = s - 60*m;
245 |
246 | h = (int)((float)m/60.0);
247 | m = m - 60*h;
248 |
249 | len = snprintf( outBuf, outBufSize, "Elapsed time: %02dh%02dm%06.3fs", h, m, s);
250 |
251 | return len;
252 | }
253 |
254 |
255 |
256 |
--------------------------------------------------------------------------------
/icon1.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ritchielawrence/mtee/c849b52344c8e851448f3933521a9d36f97eb777/icon1.ico
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | History:
3 | 01-MAR-2013, MTEE 2.1
4 | Bug: In Windows 8, mtee resulted to "Incorrect function" if output was to pipe.
5 | Fixed to not not rely on undocumented error codes from WriteConsole.
6 | At the same time, got rid of separate functions for console and disk files,
7 | and combined them to WriteBufferToConsoleAndFilesA and WriteBufferToConsoleAndFilesW and separating
8 | the console case with fi->bIsConsole. Some re-org to the idea of args.fi : the first item
9 | is always the std output, and the rest of the files are the given output files. The last item
10 | in the linked list no longer a dummy item.
11 |
12 | Bug: echo x x x x | mtee guessed that the input was Unicode.
13 | Fixed to use IS_TEXT_UNICODE_NULL_BYTES instead of IS_TEXT_UNICODE_ASCII16 | IS_TEXT_UNICODE_STATISTICS.
14 |
15 | Bug: echo t013|mtee /u con entered a forever loop.
16 | Fixed the bug in WriteBufferToDiskW loop.
17 |
18 | Bug: assumed that all files are less than 4 GB.
19 | Fixed by using also dwFileSizeHigh in GetFileSize.
20 |
21 | Bug: redir to console and con as output file was not supported.
22 | Fixed by not trying to truncate the result file with SetEndOfFile if it is a console.
23 | (redir to con may be wanted if std output is already redirected to file)
24 | 27-APR-2016, MTEE 2.2
25 | Workaround: Using ExitProcess at end to workaround an issue in Windows 10
26 | Ref: https://connect.microsoft.com/VisualStudio/feedback/details/2640071/30-s-delay-after-returning-from-main-program
27 | */
28 | #include "header.h"
29 | #include
30 |
31 | #define PEEK_BUF_SIZE (0x400) // 1024
32 | #define PEEK_WAIT_INT (10)
33 |
34 | DWORD dwCtrlEvent; // set by ctrl handler
35 |
36 | int main(VOID)
37 | {
38 | PCHAR lpBuf = NULL; // pointer to main input buffer
39 | PCHAR lpAnsiBuf = NULL; // pointer to buffer for converting unicode to ansi
40 | PWCHAR lpUnicodeBuf = NULL; // pointer to buffer for converting ansi to unicode
41 | DWORD dwBytesRead = 0L; // bytes read from input
42 | HANDLE hOut = NULL; // handle to stdout
43 | HANDLE hIn = NULL; // handle to stdin
44 | DWORD dwStdInType = (DWORD)NULL; // stdin's filetype (file/pipe/console)
45 | PFILEINFO fi = NULL; // pointer for indexing FILEINFO records
46 | BOOL bBomFound = FALSE;// true if BOM found
47 | BOOL bCtrlHandler = FALSE;
48 | ARGS args; // holds commandline arguments/options
49 | DWORD dwPeekBytesRead = 0L;
50 | DWORD dwPeekBytesAvailable = 0L;
51 | DWORD dwPeekBytesUnavailable= 0L;
52 | DWORD cPeekTimeout = 0L;
53 | BYTE byPeekBuf[PEEK_BUF_SIZE]; // holds peeked input for ansi/unicode test
54 | DWORD dwInFormat = OP_ANSI_IN;
55 | DWORD dwOperation;
56 | int iFlags;
57 | #ifdef _DEBUG
58 | MessageBox(0,"start", "mtee", MB_OK);
59 | #endif
60 | /*
61 | if(!GetWinVer())
62 | {
63 | Verbose(TEXT("This program requires Windows NT4, 2000, XP or 2003.\r\n"));
64 | ExitProcess(1);
65 | }
66 | */
67 | //
68 | // install ctrl handler to trap ctrl-c and ctrl-break
69 | //
70 | dwCtrlEvent = CTRL_CLEAR_EVENT;
71 | bCtrlHandler = SetConsoleCtrlHandler(HandlerRoutine, TRUE);
72 |
73 | //
74 | // parse the commandline
75 | //
76 | if(!ParseCommandlineW(&args)) ExitProcess(1);
77 |
78 | //
79 | // did user want to display helpscreen?
80 | //
81 | if(args.bHelp) ExitProcess(ShowHelp());
82 |
83 | //
84 | // get handles to stdout/stdin
85 | //
86 | if((hIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) ExitProcess(Perror((DWORD)NULL));
87 | if((hOut = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE) ExitProcess(Perror((DWORD)NULL));
88 |
89 | args.fi.hFile = hOut;
90 |
91 | //
92 | // determine whether the output is a console
93 | //
94 | args.fi.bIsConsole = IsAnOutputConsoleDevice(hOut);
95 |
96 | //
97 | // determine the type of input file then peek at content to ascertain if it's unicode
98 | //
99 | dwStdInType = GetFileType(hIn);
100 |
101 | //
102 | // if requested by user, get handle to piped process
103 | //
104 | HANDLE hPipedProcess = NULL;
105 | if (args.bFwdExitCode) hPipedProcess = GetPipedProcessHandle();
106 |
107 |
108 | switch(dwStdInType)
109 | {
110 | case FILE_TYPE_DISK: // stdin is from a file: mtee.exe < infile
111 | {
112 | DWORD dwFileSizeAtLeast;
113 | DWORD dwFileSizeHigh = 0;
114 |
115 | //
116 | // try and determine if input file is unicode or ansi. first check the filesize
117 | // if its zero bytes then don't use readfile as this will block
118 | //
119 | dwFileSizeAtLeast = GetFileSize(hIn, &dwFileSizeHigh);
120 | if (dwFileSizeHigh != 0)
121 | dwFileSizeAtLeast = 0xFFFFFFFE;
122 | if(dwFileSizeAtLeast == 0xFFFFFFFF && GetLastError() != NO_ERROR)
123 | {
124 | if(!args.bContinue) ExitProcess(Perror((DWORD)NULL));
125 | else dwFileSizeAtLeast = 0L;
126 | }
127 | //
128 | // only try and peek if there's at least a wchar available otherwise a test for
129 | // unicode is meaningless
130 | //
131 | if(dwFileSizeAtLeast >= sizeof(WCHAR))
132 | {
133 | if(!ReadFile(hIn,
134 | byPeekBuf,
135 | dwFileSizeAtLeast < sizeof(byPeekBuf) ? dwFileSizeAtLeast : sizeof(byPeekBuf),
136 | &dwPeekBytesRead,
137 | NULL))
138 | {
139 | //
140 | // if failed and if i/o errors not being ignored then quit
141 | //
142 | if(!args.bContinue) ExitProcess(Perror((DWORD)NULL));
143 | else break;
144 | }
145 | //
146 | // reset the filepointer to beginning
147 | //
148 | if(SetFilePointer(hIn, (LONG)NULL, NULL, FILE_BEGIN) && (!args.bContinue))
149 | ExitProcess(Perror((DWORD)NULL));
150 | }
151 | }
152 | break;
153 | case FILE_TYPE_CHAR:
154 | // stdin from NUL, CON, CONIN$, AUX or COMx
155 | // if AUX or COMx, then quit without creating any files (not even zero byte files)
156 | args.dwBufSize = 1;
157 | {
158 | DWORD dwInMode;
159 | if(!GetConsoleMode(hIn, &dwInMode)) // fails (err 6) if NUL, COMx, AUX
160 | {
161 | COMMTIMEOUTS CommTimeouts;
162 | // suceeds if AUX or COMx so quit (allow NUL)
163 | if(GetCommTimeouts(hIn, &CommTimeouts)) ExitProcess(ERROR_SUCCESS);
164 | }
165 | }
166 | break;
167 | case FILE_TYPE_PIPE: // stdin is from pipe, prn or lpt1
168 | while((!dwPeekBytesRead) && (cPeekTimeout < args.dwPeekTimeout) && (dwCtrlEvent == CTRL_CLEAR_EVENT))
169 | {
170 | if(!PeekNamedPipe(
171 | hIn, // handle to pipe to copy from
172 | byPeekBuf, // pointer to data buffer
173 | PEEK_BUF_SIZE, // size, in bytes, of data buffer
174 | &dwPeekBytesRead, // pointer to number of bytes read
175 | &dwPeekBytesAvailable, // pointer to total number of bytes available
176 | &dwPeekBytesUnavailable)) // pointer to unread bytes in this message
177 | {
178 | if(GetLastError() != ERROR_BROKEN_PIPE) ExitProcess(Perror((DWORD)NULL));
179 | }
180 | Sleep(PEEK_WAIT_INT);
181 | cPeekTimeout += PEEK_WAIT_INT;
182 | }
183 | break;
184 | }
185 |
186 | //
187 | // open/create all the files after checking stdin, that way if there was an error then
188 | // zero byte files are not created
189 | //
190 | fi = args.fi.fiNext;
191 | while(fi)
192 | {
193 | fi->hFile = CreateFileW
194 | (
195 | args.bIntermediate ? CreateFullPathW(fi->lpFileName) : fi->lpFileName,
196 | GENERIC_WRITE, // we definitely need write access
197 | FILE_SHARE_READ, // allow others to open file for read
198 | NULL, // security attr - no thanks
199 | OPEN_ALWAYS, // creation disposition - we always want to open or append
200 | 0, // flags & attributes - gulp! have you seen the documentation?
201 | NULL // handle to a template? yer right
202 | );
203 | if((fi->hFile == INVALID_HANDLE_VALUE) && !args.bContinue) ExitProcess(Perror((DWORD)NULL));
204 | //
205 | // if appending set filepointer to eof
206 | //
207 | if(fi->bAppend)
208 | {
209 | if((SetFilePointer(fi->hFile, (LONG)NULL, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
210 | && !args.bContinue)
211 | {
212 | ExitProcess(Perror((DWORD)NULL));
213 | }
214 | }
215 |
216 | //
217 | // Check if it happens to be CON or CONOUT$
218 | //
219 | fi->bIsConsole = IsAnOutputConsoleDevice(fi->hFile);
220 |
221 | //
222 | // Truncate the possibly existing file to zero size
223 | //
224 | if(!fi->bIsConsole && !SetEndOfFile(fi->hFile))
225 | {
226 | switch(GetLastError())
227 | {
228 | case ERROR_INVALID_HANDLE: // CON, CONOUT$, CONIN$ device, so close the record
229 | //Yes, this is OK also. fi->hFile = INVALID_HANDLE_VALUE;
230 | break;
231 | case ERROR_INVALID_FUNCTION: // NUL device
232 | case ERROR_INVALID_PARAMETER: // PRN device
233 | break;
234 | default:
235 | if(!args.bContinue) ExitProcess(Perror((DWORD)NULL));
236 | }
237 | }
238 |
239 | fi = fi->fiNext;
240 | }
241 |
242 |
243 | //
244 | // if enough bytes read for a meaningful unicode test...
245 | //
246 | if(dwPeekBytesRead >= sizeof(WCHAR) * 2)
247 | {
248 | //Verbose(TEXT("dwPeekBytesRead >= 4\r\n"));
249 | //
250 | // first look for BOM
251 | // TO DO. if BOM found then do not write it to the console
252 | // maybe write to files and then advance input pointer two bytes
253 | //
254 | if((byPeekBuf[0] == 0xFF) && (byPeekBuf[1] == 0xFE)) // notepad and wordpad's unicode format
255 | {
256 | bBomFound = TRUE;
257 | dwInFormat = OP_UNICODE_IN;
258 | }
259 | else
260 | {
261 | iFlags = IS_TEXT_UNICODE_NULL_BYTES;
262 | IsTextUnicode(byPeekBuf, dwPeekBytesRead, &iFlags);
263 | if(iFlags & IS_TEXT_UNICODE_NULL_BYTES)
264 | {
265 | dwInFormat = OP_UNICODE_IN;
266 | }
267 | }
268 | }
269 |
270 | // if(dwInFormat & OP_UNICODE_IN) Verbose("Unicode in...\r\n");
271 | // else Verbose("ANSI in...\r\n");
272 | //
273 | // allocate the main I/O buffer
274 | //
275 | lpBuf = (PCHAR) HeapAlloc(GetProcessHeap(), 0, args.dwBufSize * sizeof(CHAR));
276 | if(!lpBuf) ExitProcess(Perror((DWORD)NULL));
277 |
278 | if(args.bAnsi) dwOperation = (dwInFormat | OP_ANSI_OUT);
279 | else if(args.bUnicode) dwOperation = (dwInFormat | OP_UNICODE_OUT);
280 | else dwOperation = dwInFormat | (dwInFormat << OP_IN_OUT_SHIFT);
281 |
282 | //
283 | // if input starts with a BOM and output is unicode skip over BOM
284 | //
285 | if(bBomFound)
286 | {
287 | if(!ReadFile(hIn, lpBuf, sizeof(WCHAR), &dwBytesRead, NULL))
288 | {
289 | if(GetLastError() != ERROR_BROKEN_PIPE) ExitProcess(Perror((DWORD)NULL));
290 | }
291 | }
292 | //
293 | // if output is unicode and user specified unicode conversion, write BOM to files (but not to the std output)
294 | //
295 | if((dwOperation & OP_UNICODE_OUT) && args.bUnicode)
296 | {
297 | if(!WriteBom(args.fi.fiNext, args.bContinue))
298 | {
299 | if(!args.bContinue) ExitProcess(Perror((DWORD)NULL));
300 | }
301 | }
302 |
303 | LARGE_INTEGER startTimestamp, endTimestamp, elapsedTime;
304 | LARGE_INTEGER frequency;
305 |
306 | if( args.bElapsedTime )
307 | {
308 | (void)QueryPerformanceFrequency(&frequency);
309 | (void)QueryPerformanceCounter(&startTimestamp);
310 | }
311 |
312 | for(;;)
313 | {
314 | if(!ReadFile(hIn, lpBuf, args.dwBufSize * sizeof(CHAR), &dwBytesRead, NULL))
315 | {
316 | if(GetLastError() != ERROR_BROKEN_PIPE)
317 | {
318 | Perror((DWORD)NULL);
319 | break;
320 | }
321 | }
322 | //
323 | // if nothing read or user entered EOF then break (ctrl event also causes nothing to be read )
324 | //
325 | if( (!dwBytesRead) || ((dwStdInType == FILE_TYPE_CHAR) && (*lpBuf == '\x1A')) ) break;
326 | if(dwOperation == OP_ANSI_IN_ANSI_OUT)
327 | {
328 | if(!WriteBufferToConsoleAndFilesA(&args, lpBuf, dwBytesRead, args.bAddDate, args.bAddTime))
329 | {
330 | Perror((DWORD)NULL);
331 | break;
332 | }
333 | }
334 | else if(dwOperation == OP_UNICODE_IN_UNICODE_OUT)
335 | {
336 | if(!WriteBufferToConsoleAndFilesW(&args, (PWCHAR) lpBuf, dwBytesRead / sizeof(WCHAR), args.bAddDate, args.bAddTime))
337 | {
338 | Perror((DWORD)NULL);
339 | break;
340 | }
341 | }
342 | else if(dwOperation == OP_ANSI_IN_UNICODE_OUT)
343 | {
344 | AnsiToUnicode(&lpUnicodeBuf, lpBuf, &dwBytesRead);
345 | if(!WriteBufferToConsoleAndFilesW(&args, (PWCHAR) lpUnicodeBuf, dwBytesRead, args.bAddDate, args.bAddTime))
346 | {
347 | Perror((DWORD)NULL);
348 | break;
349 | }
350 | }
351 | else if(dwOperation == OP_UNICODE_IN_ANSI_OUT)
352 | {
353 | UnicodeToAnsi(&lpAnsiBuf, (PWCHAR) lpBuf, &dwBytesRead);
354 | if(!WriteBufferToConsoleAndFilesA(&args, lpAnsiBuf, dwBytesRead / sizeof(WCHAR), args.bAddDate, args.bAddTime))
355 | {
356 | Perror((DWORD)NULL);
357 | break;
358 | }
359 | }
360 | }
361 |
362 | if( args.bElapsedTime )
363 | {
364 | char strElapsedTime[128];
365 | int strLen = 0;
366 | memset( strElapsedTime, 0x00, sizeof(strElapsedTime) );
367 |
368 | (void)QueryPerformanceCounter(&endTimestamp);
369 |
370 | elapsedTime.QuadPart = endTimestamp.QuadPart - startTimestamp.QuadPart;
371 | elapsedTime.QuadPart *= 1000000L;
372 | elapsedTime.QuadPart /= frequency.QuadPart;
373 |
374 | strLen = FormatElapsedTime( &elapsedTime, strElapsedTime,
375 | sizeof(strElapsedTime) );
376 | WriteBufferToConsoleAndFilesA(&args, strElapsedTime, strLen, FALSE,
377 | FALSE);
378 | }
379 |
380 | //
381 | // close all open files (not the first entry that contains the std output)
382 | //
383 | fi = args.fi.fiNext;
384 | while(fi)
385 | {
386 | if(fi->hFile != INVALID_HANDLE_VALUE) CloseHandle(fi->hFile);
387 | fi = fi->fiNext;
388 | }
389 | if(bCtrlHandler) SetConsoleCtrlHandler(HandlerRoutine, FALSE);
390 | if(dwStdInType == FILE_TYPE_CHAR) FlushFileBuffers(hIn);
391 |
392 | DWORD dwExitCode = 0;
393 | //
394 | // if requested by user, get exit code of piped process
395 | //
396 | if(args.bFwdExitCode) {
397 | GetExitCodeProcess(hPipedProcess, &dwExitCode);
398 | CloseHandle(hPipedProcess);
399 | }
400 | //
401 | // Use ExitProcess (instead of return) to workaround an issue in Windows 10
402 | //
403 | ExitProcess(dwExitCode);
404 | }
405 |
--------------------------------------------------------------------------------
/mtee-screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ritchielawrence/mtee/c849b52344c8e851448f3933521a9d36f97eb777/mtee-screenshot1.png
--------------------------------------------------------------------------------
/mtee.cbp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/output.cpp:
--------------------------------------------------------------------------------
1 | #include "header.h"
2 | #define MAX_DATE_TIME_LEN (25)
3 |
4 | BOOL WriteBufferToConsoleAndFilesA(LPARGS args, PCHAR lpBuf, DWORD dwCharsRead, BOOL AddDate, BOOL AddTime)
5 | {
6 | DWORD dwBytesWritten;
7 | PFILEINFO fi;
8 | static BOOL bNewLine = TRUE;
9 | PCHAR pHead, pTail;
10 |
11 | if(AddDate | AddTime)
12 | {
13 | CHAR szDT[MAX_DATE_TIME_LEN];
14 | pHead = pTail = lpBuf;
15 | while(pTail < (lpBuf + dwCharsRead))
16 | {
17 | if(bNewLine)
18 | {
19 | DWORD dwBR = GetFormattedDateTimeA(szDT, AddDate, AddTime);
20 | if(!WriteBufferToConsoleAndFilesA(args, szDT, dwBR, FALSE, FALSE)) return FALSE;
21 | bNewLine = FALSE;
22 | }
23 | if(*pTail++ == 0x0A)
24 | {
25 | bNewLine = TRUE;
26 | if(!WriteBufferToConsoleAndFilesA(args, pHead, pTail - pHead, FALSE, FALSE)) return FALSE;
27 | pHead = pTail;
28 | }
29 | }
30 | if(!WriteBufferToConsoleAndFilesA(args, pHead, pTail - pHead, FALSE, FALSE)) return FALSE;
31 | }
32 | else
33 | {
34 | fi = &args->fi;
35 | while(fi)
36 | {
37 | if(fi->hFile != INVALID_HANDLE_VALUE)
38 | {
39 | BOOL ok;
40 | if(fi->bIsConsole)
41 | ok = WriteConsoleA(fi->hFile, lpBuf, dwCharsRead, &dwBytesWritten, NULL);
42 | else
43 | ok = WriteFile(fi->hFile, lpBuf, dwCharsRead , &dwBytesWritten, NULL);
44 | if (!ok && !args->bContinue) return FALSE;
45 | }
46 | fi = fi->fiNext;
47 | }
48 | }
49 | return TRUE;
50 | }
51 |
52 | BOOL WriteBufferToConsoleAndFilesW(LPARGS args, PWCHAR lpBuf, DWORD dwCharsRead, BOOL AddDate, BOOL AddTime)
53 | {
54 | DWORD dwBytesWritten;
55 | PFILEINFO fi;
56 | static BOOL bNewLine = TRUE;
57 | PWCHAR pHead, pTail;
58 |
59 | if(AddDate | AddTime)
60 | {
61 | WCHAR szDT[MAX_DATE_TIME_LEN];
62 | pHead = pTail = (PWCHAR) lpBuf;
63 | while(pTail < (lpBuf + dwCharsRead))
64 | {
65 | if(bNewLine)
66 | {
67 | DWORD dwBR = GetFormattedDateTimeW(szDT, AddDate, AddTime);
68 | if(!WriteBufferToConsoleAndFilesW(args, szDT, dwBR, FALSE, FALSE)) return FALSE;
69 | bNewLine = FALSE;
70 | }
71 | if(*pTail++ == L'\n')
72 | {
73 | bNewLine = TRUE;
74 | if(!WriteBufferToConsoleAndFilesW(args, pHead, pTail - pHead, FALSE, FALSE)) return FALSE;
75 | pHead = pTail;
76 | }
77 | }
78 | if(!WriteBufferToConsoleAndFilesW(args, pHead, pTail - pHead, FALSE, FALSE)) return FALSE;
79 | }
80 | else
81 | {
82 | fi = &args->fi;
83 | while(fi)
84 | {
85 | if(fi->hFile != INVALID_HANDLE_VALUE)
86 | {
87 | BOOL ok;
88 | if(fi->bIsConsole)
89 | // The ANSI version perhaps works identically to WriteFile, however in UNICODE version
90 | // WriteConsoleW is definitely needed so that it understands that we are outputting UNICODE characters
91 | ok = WriteConsoleW(fi->hFile, lpBuf, dwCharsRead, &dwBytesWritten, NULL);
92 | else
93 | ok = WriteFile(fi->hFile, lpBuf, dwCharsRead * sizeof(WCHAR), &dwBytesWritten, NULL);
94 | if (!ok && !args->bContinue) return FALSE;
95 | }
96 | fi = fi->fiNext;
97 | }
98 | }
99 | return TRUE;
100 | }
101 |
102 |
103 | BOOL AnsiToUnicode(PWCHAR *lpDest, PCHAR lpSrc, LPDWORD lpSize)
104 | {
105 | int iWideCharLen;
106 | if(*lpDest)
107 | {
108 | if(!HeapFree(GetProcessHeap(), 0, *lpDest)) return FALSE;
109 | }
110 |
111 |
112 | iWideCharLen = MultiByteToWideChar(GetConsoleCP(), 0, lpSrc, *lpSize, NULL, 0);
113 |
114 | // temp change RE lienhart post
115 | //iWideCharLen = MultiByteToWideChar(CP_ACP, 0, lpSrc, *lpSize, NULL, 0);
116 |
117 | *lpDest = (PWCHAR) HeapAlloc(GetProcessHeap(), 0, iWideCharLen * sizeof(WCHAR));
118 | if(lpDest == NULL) return FALSE;
119 | //
120 | // set lpsize to number of chars
121 | //
122 |
123 | *lpSize = MultiByteToWideChar(GetConsoleCP(), 0, lpSrc, *lpSize, *lpDest, iWideCharLen);
124 |
125 | // temp change RE lienhart post
126 | //*lpSize = MultiByteToWideChar(CP_ACP, 0, lpSrc, *lpSize, *lpDest, iWideCharLen);
127 |
128 | return TRUE;
129 | }
130 |
131 | BOOL UnicodeToAnsi(PCHAR *lpDest, PWCHAR lpSrc, LPDWORD lpSize)
132 | {
133 | int iAnsiCharLen;
134 | if(*lpDest)
135 | {
136 | if(!HeapFree(GetProcessHeap(), 0, *lpDest)) return FALSE;
137 | }
138 | iAnsiCharLen = WideCharToMultiByte(GetConsoleCP(), 0, lpSrc, *lpSize, NULL, 0, NULL, NULL);
139 | *lpDest = (PCHAR) HeapAlloc(GetProcessHeap(), 0, iAnsiCharLen * sizeof(WCHAR));
140 | if(lpDest == NULL) return FALSE;
141 |
142 | *lpSize = WideCharToMultiByte(GetConsoleCP(), 0, lpSrc, *lpSize, *lpDest, iAnsiCharLen, NULL, NULL);
143 |
144 | // temp change RE lienhart post CP_ACP
145 | //*lpSize = WideCharToMultiByte(CP_ACP, 0, lpSrc, *lpSize, *lpDest, iAnsiCharLen, NULL, NULL);
146 |
147 | return TRUE;
148 | }
149 |
150 | BOOL WriteBom(PFILEINFO fi, BOOL bContinue)
151 | {
152 | DWORD dwBytesWritten;
153 | WCHAR wcBom = 0xFEFF;
154 | while(fi)
155 | {
156 | DWORD dwFileSizeHigh = 0;
157 | DWORD siz = GetFileSize(fi->hFile, &dwFileSizeHigh);
158 | if((fi->hFile != INVALID_HANDLE_VALUE) && (siz == 0 && dwFileSizeHigh == 0))
159 | {
160 | if(!WriteFile(fi->hFile, &wcBom, sizeof(WCHAR), &dwBytesWritten, NULL))
161 | {
162 | if(!bContinue) return FALSE;
163 | }
164 | }
165 | fi = fi->fiNext;
166 | }
167 |
168 | return TRUE;
169 | }
--------------------------------------------------------------------------------
/perr.cpp:
--------------------------------------------------------------------------------
1 | #include "header.h"
2 |
3 | #define MAX_MSG_BUF_SIZE (1024)
4 |
5 | DWORD Perror(DWORD dwErrNum)
6 | {
7 | DWORD cMsgLen;
8 | LPVOID pErrBuf;
9 | DWORD dwLastErr;
10 |
11 | dwErrNum ? dwLastErr = dwErrNum : dwLastErr = GetLastError();
12 |
13 | //
14 | // get the text description for that error number from the system
15 | //
16 | cMsgLen = FormatMessage
17 | (
18 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
19 | NULL,
20 | dwLastErr,
21 | MAKELANGID(0, SUBLANG_ENGLISH_US),
22 | (LPTSTR) &pErrBuf,
23 | MAX_MSG_BUF_SIZE,
24 | NULL
25 | );
26 |
27 | if(cMsgLen)
28 | {
29 | Verbose((LPCTSTR) pErrBuf);
30 | LocalFree((HLOCAL) pErrBuf);
31 | }
32 | return dwLastErr;
33 | }
34 |
35 | VOID Verbose(LPCTSTR szMsg)
36 | {
37 | DWORD cBytes;
38 |
39 | WriteFile
40 | (
41 | GetStdHandle(STD_ERROR_HANDLE),
42 | szMsg,
43 | lstrlen(szMsg),
44 | &cBytes,
45 | NULL
46 | );
47 | }
48 |
49 |
50 |
51 |
52 |
53 |
54 | /*
55 | VOID ShowPipeInfo(HANDLE h)
56 | {
57 |
58 |
59 | TCHAR z[1024];
60 | DWORD lpFlags;
61 | DWORD lpOutBufferSize;
62 | DWORD lpInBufferSize;
63 | DWORD lpMaxInstances;
64 |
65 | DWORD lpState;
66 | DWORD lpCurInstances;
67 | //LPTSTR lpUserName;
68 | //DWORD nMaxUserNameSize;
69 |
70 | if(!GetNamedPipeInfo(
71 | h,
72 | &lpFlags,
73 | &lpOutBufferSize,
74 | &lpInBufferSize,
75 | &lpMaxInstances))
76 | {
77 | perr(TEXT("GetNamedPipeInfo()"));
78 | }
79 |
80 | if(!GetNamedPipeHandleState(
81 | h,
82 | &lpState,
83 | &lpCurInstances,
84 | NULL,
85 | NULL,
86 | NULL,
87 | NULL))
88 | {
89 | perr(TEXT("GetNamedPipeHandleState"));
90 | }
91 |
92 | wsprintf(
93 | z,
94 | TEXT("Handle:\t\t0x%08X\n")
95 | TEXT("Flags:\t\t0x%08X\n")
96 | TEXT("OutBufferSize:\t0x%08X\n")
97 | TEXT("InBufferSize:\t0x%08X\n")
98 | TEXT("MaxInstances:\t0x%08X\n")
99 | TEXT("State:\t\t0x%08X\n")
100 | TEXT("CurInstances:\t0x%08X\n\n"),
101 | h,
102 | lpFlags,
103 | lpOutBufferSize,
104 | lpInBufferSize,
105 | lpMaxInstances,
106 | lpState,
107 | lpCurInstances);
108 |
109 | DWORD nBytes;
110 |
111 | WriteFile(
112 | GetStdHandle(STD_ERROR_HANDLE),
113 | z,
114 | lstrlen(z) * sizeof(TCHAR),
115 | &nBytes,
116 | NULL);
117 | }
118 |
119 | VOID ConfigStdIn(HANDLE h)
120 | {
121 | DWORD lpMode;
122 |
123 | lpMode = (PIPE_READMODE_BYTE | PIPE_NOWAIT);
124 |
125 | if(!SetNamedPipeHandleState(
126 | h,
127 | &lpMode,
128 | NULL,
129 | NULL))
130 | {
131 | perr(TEXT("SetNamedPipeHandleState()"));
132 | }
133 |
134 | }
135 | */
--------------------------------------------------------------------------------