gdi32: Implement SelectFont as a standard driver entry point.
[wine] / programs / cmd / wcmdmain.c
1 /*
2  * CMD - Wine-compatible command line interface.
3  *
4  * Copyright (C) 1999 - 2001 D A Pickles
5  * Copyright (C) 2007 J Edmeades
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 /*
23  * FIXME:
24  * - Cannot handle parameters in quotes
25  * - Lots of functionality missing from builtins
26  */
27
28 #include "config.h"
29 #include "wcmd.h"
30 #include "wine/debug.h"
31
32 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
33
34 const WCHAR inbuilt[][10] = {
35         {'C','A','L','L','\0'},
36         {'C','D','\0'},
37         {'C','H','D','I','R','\0'},
38         {'C','L','S','\0'},
39         {'C','O','P','Y','\0'},
40         {'C','T','T','Y','\0'},
41         {'D','A','T','E','\0'},
42         {'D','E','L','\0'},
43         {'D','I','R','\0'},
44         {'E','C','H','O','\0'},
45         {'E','R','A','S','E','\0'},
46         {'F','O','R','\0'},
47         {'G','O','T','O','\0'},
48         {'H','E','L','P','\0'},
49         {'I','F','\0'},
50         {'L','A','B','E','L','\0'},
51         {'M','D','\0'},
52         {'M','K','D','I','R','\0'},
53         {'M','O','V','E','\0'},
54         {'P','A','T','H','\0'},
55         {'P','A','U','S','E','\0'},
56         {'P','R','O','M','P','T','\0'},
57         {'R','E','M','\0'},
58         {'R','E','N','\0'},
59         {'R','E','N','A','M','E','\0'},
60         {'R','D','\0'},
61         {'R','M','D','I','R','\0'},
62         {'S','E','T','\0'},
63         {'S','H','I','F','T','\0'},
64         {'T','I','M','E','\0'},
65         {'T','I','T','L','E','\0'},
66         {'T','Y','P','E','\0'},
67         {'V','E','R','I','F','Y','\0'},
68         {'V','E','R','\0'},
69         {'V','O','L','\0'},
70         {'E','N','D','L','O','C','A','L','\0'},
71         {'S','E','T','L','O','C','A','L','\0'},
72         {'P','U','S','H','D','\0'},
73         {'P','O','P','D','\0'},
74         {'A','S','S','O','C','\0'},
75         {'C','O','L','O','R','\0'},
76         {'F','T','Y','P','E','\0'},
77         {'M','O','R','E','\0'},
78         {'C','H','O','I','C','E','\0'},
79         {'E','X','I','T','\0'}
80 };
81
82 const WCHAR externals[NUM_EXTERNALS][10] = {
83         {'A','T','T','R','I','B','\0'},
84         {'X','C','O','P','Y','\0'}
85 };
86
87 HINSTANCE hinst;
88 DWORD errorlevel;
89 int defaultColor = 7;
90 BOOL echo_mode = TRUE;
91 static int opt_c, opt_k, opt_s;
92 const WCHAR newline[] = {'\r','\n','\0'};
93 static const WCHAR equalsW[] = {'=','\0'};
94 static const WCHAR closeBW[] = {')','\0'};
95 WCHAR anykey[100];
96 WCHAR version_string[100];
97 WCHAR quals[MAX_PATH], param1[MAXSTRING], param2[MAXSTRING];
98 BATCH_CONTEXT *context = NULL;
99 extern struct env_stack *pushd_directories;
100 static const WCHAR *pagedMessage = NULL;
101 static char  *output_bufA = NULL;
102 #define MAX_WRITECONSOLE_SIZE 65535
103 static BOOL unicodePipes = FALSE;
104
105 /*
106  * Returns a buffer for reading from/writing to file
107  * Never freed
108  */
109 static char *get_file_buffer(void)
110 {
111     if (!output_bufA) {
112         output_bufA = HeapAlloc(GetProcessHeap(), 0, MAX_WRITECONSOLE_SIZE);
113         if (!output_bufA)
114             WINE_FIXME("Out of memory - could not allocate ansi 64K buffer\n");
115     }
116     return output_bufA;
117 }
118
119 /*******************************************************************
120  * WCMD_output_asis_len - send output to current standard output
121  *
122  * Output a formatted unicode string. Ideally this will go to the console
123  *  and hence required WriteConsoleW to output it, however if file i/o is
124  *  redirected, it needs to be WriteFile'd using OEM (not ANSI) format
125  */
126 static void WCMD_output_asis_len(const WCHAR *message, int len, HANDLE device) {
127
128     DWORD   nOut= 0;
129     DWORD   res = 0;
130
131     /* If nothing to write, return (MORE does this sometimes) */
132     if (!len) return;
133
134     /* Try to write as unicode assuming it is to a console */
135     res = WriteConsoleW(device, message, len, &nOut, NULL);
136
137     /* If writing to console fails, assume its file
138        i/o so convert to OEM codepage and output                  */
139     if (!res) {
140       BOOL usedDefaultChar = FALSE;
141       DWORD convertedChars;
142       char *buffer;
143
144       if (!unicodePipes) {
145
146         if (!(buffer = get_file_buffer()))
147             return;
148
149         /* Convert to OEM, then output */
150         convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
151                             len, buffer, MAX_WRITECONSOLE_SIZE,
152                             "?", &usedDefaultChar);
153         WriteFile(device, buffer, convertedChars,
154                   &nOut, FALSE);
155       } else {
156         WriteFile(device, message, len*sizeof(WCHAR),
157                   &nOut, FALSE);
158       }
159     }
160     return;
161 }
162
163 /*******************************************************************
164  * WCMD_output - send output to current standard output device.
165  *
166  */
167
168 void WCMD_output (const WCHAR *format, ...) {
169
170   va_list ap;
171   WCHAR string[1024];
172   int ret;
173
174   va_start(ap,format);
175   ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
176   if( ret >= (sizeof(string)/sizeof(WCHAR))) {
177        WINE_ERR("Output truncated\n" );
178        ret = (sizeof(string)/sizeof(WCHAR)) - 1;
179        string[ret] = '\0';
180   }
181   va_end(ap);
182   WCMD_output_asis_len(string, ret, GetStdHandle(STD_OUTPUT_HANDLE));
183 }
184
185 /*******************************************************************
186  * WCMD_output_stderr - send output to current standard error device.
187  *
188  */
189
190 void WCMD_output_stderr (const WCHAR *format, ...) {
191
192   va_list ap;
193   WCHAR string[1024];
194   int ret;
195
196   va_start(ap,format);
197   ret = vsnprintfW(string, sizeof(string)/sizeof(WCHAR), format, ap);
198   if( ret >= (sizeof(string)/sizeof(WCHAR))) {
199        WINE_ERR("Output truncated\n" );
200        ret = (sizeof(string)/sizeof(WCHAR)) - 1;
201        string[ret] = '\0';
202   }
203   va_end(ap);
204   WCMD_output_asis_len(string, ret, GetStdHandle(STD_ERROR_HANDLE));
205 }
206
207 static int line_count;
208 static int max_height;
209 static int max_width;
210 static BOOL paged_mode;
211 static int numChars;
212
213 void WCMD_enter_paged_mode(const WCHAR *msg)
214 {
215   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
216
217   if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
218     max_height = consoleInfo.dwSize.Y;
219     max_width  = consoleInfo.dwSize.X;
220   } else {
221     max_height = 25;
222     max_width  = 80;
223   }
224   paged_mode = TRUE;
225   line_count = 0;
226   numChars   = 0;
227   pagedMessage = (msg==NULL)? anykey : msg;
228 }
229
230 void WCMD_leave_paged_mode(void)
231 {
232   paged_mode = FALSE;
233   pagedMessage = NULL;
234 }
235
236 /***************************************************************************
237  * WCMD_Readfile
238  *
239  *      Read characters in from a console/file, returning result in Unicode
240  */
241 BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
242 {
243     DWORD numRead;
244     char *buffer;
245
246     if (WCMD_is_console_handle(hIn))
247         /* Try to read from console as Unicode */
248         return ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL);
249
250     /* We assume it's a file handle and read then convert from assumed OEM codepage */
251     if (!(buffer = get_file_buffer()))
252         return FALSE;
253
254     if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
255         return FALSE;
256
257     *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
258
259     return TRUE;
260 }
261
262 /*******************************************************************
263  * WCMD_output_asis_handle
264  *
265  * Send output to specified handle without formatting e.g. when message contains '%'
266  */
267 static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
268   DWORD count;
269   const WCHAR* ptr;
270   WCHAR string[1024];
271   HANDLE handle = GetStdHandle(std_handle);
272
273   if (paged_mode) {
274     do {
275       ptr = message;
276       while (*ptr && *ptr!='\n' && (numChars < max_width)) {
277         numChars++;
278         ptr++;
279       };
280       if (*ptr == '\n') ptr++;
281       WCMD_output_asis_len(message, (ptr) ? ptr - message : strlenW(message), handle);
282       if (ptr) {
283         numChars = 0;
284         if (++line_count >= max_height - 1) {
285           line_count = 0;
286           WCMD_output_asis_len(pagedMessage, strlenW(pagedMessage), handle);
287           WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
288         }
289       }
290     } while (((message = ptr) != NULL) && (*ptr));
291   } else {
292     WCMD_output_asis_len(message, lstrlenW(message), handle);
293   }
294 }
295
296 /*******************************************************************
297  * WCMD_output_asis
298  *
299  * Send output to current standard output device, without formatting
300  * e.g. when message contains '%'
301  */
302 void WCMD_output_asis (const WCHAR *message) {
303     WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
304 }
305
306 /*******************************************************************
307  * WCMD_output_asis_stderr
308  *
309  * Send output to current standard error device, without formatting
310  * e.g. when message contains '%'
311  */
312 void WCMD_output_asis_stderr (const WCHAR *message) {
313     WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
314 }
315
316 /****************************************************************************
317  * WCMD_print_error
318  *
319  * Print the message for GetLastError
320  */
321
322 void WCMD_print_error (void) {
323   LPVOID lpMsgBuf;
324   DWORD error_code;
325   int status;
326
327   error_code = GetLastError ();
328   status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
329                           NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
330   if (!status) {
331     WINE_FIXME ("Cannot display message for error %d, status %d\n",
332                         error_code, GetLastError());
333     return;
334   }
335
336   WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
337                        GetStdHandle(STD_ERROR_HANDLE));
338   LocalFree (lpMsgBuf);
339   WCMD_output_asis_len (newline, lstrlenW(newline),
340                         GetStdHandle(STD_ERROR_HANDLE));
341   return;
342 }
343
344 /******************************************************************************
345  * WCMD_show_prompt
346  *
347  *      Display the prompt on STDout
348  *
349  */
350
351 static void WCMD_show_prompt (void) {
352
353   int status;
354   WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
355   WCHAR *p, *q;
356   DWORD len;
357   static const WCHAR envPrompt[] = {'P','R','O','M','P','T','\0'};
358
359   len = GetEnvironmentVariableW(envPrompt, prompt_string,
360                                 sizeof(prompt_string)/sizeof(WCHAR));
361   if ((len == 0) || (len >= (sizeof(prompt_string)/sizeof(WCHAR)))) {
362     static const WCHAR dfltPrompt[] = {'$','P','$','G','\0'};
363     strcpyW (prompt_string, dfltPrompt);
364   }
365   p = prompt_string;
366   q = out_string;
367   *q++ = '\r';
368   *q++ = '\n';
369   *q = '\0';
370   while (*p != '\0') {
371     if (*p != '$') {
372       *q++ = *p++;
373       *q = '\0';
374     }
375     else {
376       p++;
377       switch (toupper(*p)) {
378         case '$':
379           *q++ = '$';
380           break;
381         case 'A':
382           *q++ = '&';
383           break;
384         case 'B':
385           *q++ = '|';
386           break;
387         case 'C':
388           *q++ = '(';
389           break;
390         case 'D':
391           GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH);
392           while (*q) q++;
393           break;
394         case 'E':
395           *q++ = '\E';
396           break;
397         case 'F':
398           *q++ = ')';
399           break;
400         case 'G':
401           *q++ = '>';
402           break;
403         case 'H':
404           *q++ = '\b';
405           break;
406         case 'L':
407           *q++ = '<';
408           break;
409         case 'N':
410           status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
411           if (status) {
412             *q++ = curdir[0];
413           }
414           break;
415         case 'P':
416           status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
417           if (status) {
418             strcatW (q, curdir);
419             while (*q) q++;
420           }
421           break;
422         case 'Q':
423           *q++ = '=';
424           break;
425         case 'S':
426           *q++ = ' ';
427           break;
428         case 'T':
429           GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
430           while (*q) q++;
431           break;
432         case 'V':
433           strcatW (q, version_string);
434           while (*q) q++;
435           break;
436         case '_':
437           *q++ = '\n';
438           break;
439         case '+':
440           if (pushd_directories) {
441             memset(q, '+', pushd_directories->u.stackdepth);
442             q = q + pushd_directories->u.stackdepth;
443           }
444           break;
445       }
446       p++;
447       *q = '\0';
448     }
449   }
450   WCMD_output_asis (out_string);
451 }
452
453
454 /*************************************************************************
455  * WCMD_strdupW
456  *    A wide version of strdup as its missing from unicode.h
457  */
458 WCHAR *WCMD_strdupW(const WCHAR *input) {
459    int len=strlenW(input)+1;
460    WCHAR *result = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
461    memcpy(result, input, len * sizeof(WCHAR));
462    return result;
463 }
464
465 /*************************************************************************
466  * WCMD_strsubstW
467  *    Replaces a portion of a Unicode string with the specified string.
468  *    It's up to the caller to ensure there is enough space in the
469  *    destination buffer.
470  */
471 void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
472
473    if (len < 0)
474       len=insert ? lstrlenW(insert) : 0;
475    if (start+len != next)
476        memmove(start+len, next, (strlenW(next) + 1) * sizeof(*next));
477    if (insert)
478        memcpy(start, insert, len * sizeof(*insert));
479 }
480
481 /***************************************************************************
482  * WCMD_skip_leading_spaces
483  *
484  *  Return a pointer to the first non-whitespace character of string.
485  *  Does not modify the input string.
486  */
487 WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
488
489   WCHAR *ptr;
490
491   ptr = string;
492   while (*ptr == ' ' || *ptr == '\t') ptr++;
493   return ptr;
494 }
495
496 /***************************************************************************
497  * WCMD_keyword_ws_found
498  *
499  *  Checks if the string located at ptr matches a keyword (of length len)
500  *  followed by a whitespace character (space or tab)
501  */
502 BOOL WCMD_keyword_ws_found(const WCHAR *keyword, int len, const WCHAR *ptr) {
503     return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
504                            ptr, len, keyword, len) == CSTR_EQUAL)
505             && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
506 }
507
508 /*************************************************************************
509  * WCMD_strip_quotes
510  *
511  *      Remove first and last quote WCHARacters, preserving all other text
512  */
513 void WCMD_strip_quotes(WCHAR *cmd) {
514   WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL;
515   while((*dest=*src) != '\0') {
516       if (*src=='\"')
517           lastq=dest;
518       dest++, src++;
519   }
520   if (lastq) {
521       dest=lastq++;
522       while ((*dest++=*lastq++) != 0)
523           ;
524   }
525 }
526
527
528 /*************************************************************************
529  * WCMD_is_magic_envvar
530  * Return TRUE if s is '%'magicvar'%'
531  * and is not masked by a real environment variable.
532  */
533
534 static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
535 {
536     int len;
537
538     if (s[0] != '%')
539         return FALSE;         /* Didn't begin with % */
540     len = strlenW(s);
541     if (len < 2 || s[len-1] != '%')
542         return FALSE;         /* Didn't end with another % */
543
544     if (CompareStringW(LOCALE_USER_DEFAULT,
545                        NORM_IGNORECASE | SORT_STRINGSORT,
546                        s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
547         /* Name doesn't match. */
548         return FALSE;
549     }
550
551     if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
552         /* Masked by real environment variable. */
553         return FALSE;
554     }
555
556     return TRUE;
557 }
558
559 /*************************************************************************
560  * WCMD_expand_envvar
561  *
562  *      Expands environment variables, allowing for WCHARacter substitution
563  */
564 static WCHAR *WCMD_expand_envvar(WCHAR *start,
565                                  const WCHAR *forVar, const WCHAR *forVal) {
566     WCHAR *endOfVar = NULL, *s;
567     WCHAR *colonpos = NULL;
568     WCHAR thisVar[MAXSTRING];
569     WCHAR thisVarContents[MAXSTRING];
570     WCHAR savedchar = 0x00;
571     int len;
572
573     static const WCHAR ErrorLvl[]  = {'E','R','R','O','R','L','E','V','E','L','\0'};
574     static const WCHAR Date[]      = {'D','A','T','E','\0'};
575     static const WCHAR Time[]      = {'T','I','M','E','\0'};
576     static const WCHAR Cd[]        = {'C','D','\0'};
577     static const WCHAR Random[]    = {'R','A','N','D','O','M','\0'};
578     static const WCHAR Delims[]    = {'%',' ',':','\0'};
579
580     WINE_TRACE("Expanding: %s (%s,%s)\n", wine_dbgstr_w(start),
581                wine_dbgstr_w(forVal), wine_dbgstr_w(forVar));
582
583     /* Find the end of the environment variable, and extract name */
584     endOfVar = strpbrkW(start+1, Delims);
585
586     if (endOfVar == NULL || *endOfVar==' ') {
587
588       /* In batch program, missing terminator for % and no following
589          ':' just removes the '%'                                   */
590       if (context) {
591         WCMD_strsubstW(start, start + 1, NULL, 0);
592         return start;
593       } else {
594
595         /* In command processing, just ignore it - allows command line
596            syntax like: for %i in (a.a) do echo %i                     */
597         return start+1;
598       }
599     }
600
601     /* If ':' found, process remaining up until '%' (or stop at ':' if
602        a missing '%' */
603     if (*endOfVar==':') {
604         WCHAR *endOfVar2 = strchrW(endOfVar+1, '%');
605         if (endOfVar2 != NULL) endOfVar = endOfVar2;
606     }
607
608     memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
609     thisVar[(endOfVar - start)+1] = 0x00;
610     colonpos = strchrW(thisVar+1, ':');
611
612     /* If there's complex substitution, just need %var% for now
613        to get the expanded data to play with                    */
614     if (colonpos) {
615         *colonpos = '%';
616         savedchar = *(colonpos+1);
617         *(colonpos+1) = 0x00;
618     }
619
620     WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
621
622     /* Expand to contents, if unchanged, return */
623     /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
624     /* override if existing env var called that name              */
625     if (WCMD_is_magic_envvar(thisVar, ErrorLvl)) {
626       static const WCHAR fmt[] = {'%','d','\0'};
627       wsprintfW(thisVarContents, fmt, errorlevel);
628       len = strlenW(thisVarContents);
629     } else if (WCMD_is_magic_envvar(thisVar, Date)) {
630       GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
631                     NULL, thisVarContents, MAXSTRING);
632       len = strlenW(thisVarContents);
633     } else if (WCMD_is_magic_envvar(thisVar, Time)) {
634       GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
635                         NULL, thisVarContents, MAXSTRING);
636       len = strlenW(thisVarContents);
637     } else if (WCMD_is_magic_envvar(thisVar, Cd)) {
638       GetCurrentDirectoryW(MAXSTRING, thisVarContents);
639       len = strlenW(thisVarContents);
640     } else if (WCMD_is_magic_envvar(thisVar, Random)) {
641       static const WCHAR fmt[] = {'%','d','\0'};
642       wsprintfW(thisVarContents, fmt, rand() % 32768);
643       len = strlenW(thisVarContents);
644
645     /* Look for a matching 'for' variable */
646     } else if (forVar &&
647                (CompareStringW(LOCALE_USER_DEFAULT,
648                                SORT_STRINGSORT,
649                                thisVar,
650                                (colonpos - thisVar) - 1,
651                                forVar, -1) == CSTR_EQUAL)) {
652       strcpyW(thisVarContents, forVal);
653       len = strlenW(thisVarContents);
654
655     } else {
656
657       len = ExpandEnvironmentStringsW(thisVar, thisVarContents,
658                                sizeof(thisVarContents)/sizeof(WCHAR));
659     }
660
661     if (len == 0)
662       return endOfVar+1;
663
664     /* In a batch program, unknown env vars are replaced with nothing,
665          note syntax %garbage:1,3% results in anything after the ':'
666          except the %
667        From the command line, you just get back what you entered      */
668     if (lstrcmpiW(thisVar, thisVarContents) == 0) {
669
670       /* Restore the complex part after the compare */
671       if (colonpos) {
672         *colonpos = ':';
673         *(colonpos+1) = savedchar;
674       }
675
676       /* Command line - just ignore this */
677       if (context == NULL) return endOfVar+1;
678
679
680       /* Batch - replace unknown env var with nothing */
681       if (colonpos == NULL) {
682         WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
683       } else {
684         len = strlenW(thisVar);
685         thisVar[len-1] = 0x00;
686         /* If %:...% supplied, : is retained */
687         if (colonpos == thisVar+1) {
688           WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
689         } else {
690           WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
691         }
692       }
693       return start;
694
695     }
696
697     /* See if we need to do complex substitution (any ':'s), if not
698        then our work here is done                                  */
699     if (colonpos == NULL) {
700       WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
701       return start;
702     }
703
704     /* Restore complex bit */
705     *colonpos = ':';
706     *(colonpos+1) = savedchar;
707
708     /*
709         Handle complex substitutions:
710            xxx=yyy    (replace xxx with yyy)
711            *xxx=yyy   (replace up to and including xxx with yyy)
712            ~x         (from x WCHARs in)
713            ~-x        (from x WCHARs from the end)
714            ~x,y       (from x WCHARs in for y WCHARacters)
715            ~x,-y      (from x WCHARs in until y WCHARacters from the end)
716      */
717
718     /* ~ is substring manipulation */
719     if (savedchar == '~') {
720
721       int   substrposition, substrlength = 0;
722       WCHAR *commapos = strchrW(colonpos+2, ',');
723       WCHAR *startCopy;
724
725       substrposition = atolW(colonpos+2);
726       if (commapos) substrlength = atolW(commapos+1);
727
728       /* Check bounds */
729       if (substrposition >= 0) {
730         startCopy = &thisVarContents[min(substrposition, len)];
731       } else {
732         startCopy = &thisVarContents[max(0, len+substrposition-1)];
733       }
734
735       if (commapos == NULL) {
736         /* Copy the lot */
737         WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
738       } else if (substrlength < 0) {
739
740         int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
741         if (copybytes > len) copybytes = len;
742         else if (copybytes < 0) copybytes = 0;
743         WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
744       } else {
745         WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
746       }
747
748       return start;
749
750     /* search and replace manipulation */
751     } else {
752       WCHAR *equalspos = strstrW(colonpos, equalsW);
753       WCHAR *replacewith = equalspos+1;
754       WCHAR *found       = NULL;
755       WCHAR *searchIn;
756       WCHAR *searchFor;
757
758       if (equalspos == NULL) return start+1;
759       s = WCMD_strdupW(endOfVar + 1);
760
761       /* Null terminate both strings */
762       thisVar[strlenW(thisVar)-1] = 0x00;
763       *equalspos = 0x00;
764
765       /* Since we need to be case insensitive, copy the 2 buffers */
766       searchIn  = WCMD_strdupW(thisVarContents);
767       CharUpperBuffW(searchIn, strlenW(thisVarContents));
768       searchFor = WCMD_strdupW(colonpos+1);
769       CharUpperBuffW(searchFor, strlenW(colonpos+1));
770
771       /* Handle wildcard case */
772       if (*(colonpos+1) == '*') {
773         /* Search for string to replace */
774         found = strstrW(searchIn, searchFor+1);
775
776         if (found) {
777           /* Do replacement */
778           strcpyW(start, replacewith);
779           strcatW(start, thisVarContents + (found-searchIn) + strlenW(searchFor+1));
780           strcatW(start, s);
781         } else {
782           /* Copy as is */
783           strcpyW(start, thisVarContents);
784           strcatW(start, s);
785         }
786
787       } else {
788         /* Loop replacing all instances */
789         WCHAR *lastFound = searchIn;
790         WCHAR *outputposn = start;
791
792         *start = 0x00;
793         while ((found = strstrW(lastFound, searchFor))) {
794             lstrcpynW(outputposn,
795                     thisVarContents + (lastFound-searchIn),
796                     (found - lastFound)+1);
797             outputposn  = outputposn + (found - lastFound);
798             strcatW(outputposn, replacewith);
799             outputposn = outputposn + strlenW(replacewith);
800             lastFound = found + strlenW(searchFor);
801         }
802         strcatW(outputposn,
803                 thisVarContents + (lastFound-searchIn));
804         strcatW(outputposn, s);
805       }
806       HeapFree(GetProcessHeap(), 0, s);
807       HeapFree(GetProcessHeap(), 0, searchIn);
808       HeapFree(GetProcessHeap(), 0, searchFor);
809       return start;
810     }
811     return start+1;
812 }
813
814 /*****************************************************************************
815  * Expand the command. Native expands lines from batch programs as they are
816  * read in and not again, except for 'for' variable substitution.
817  * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
818  */
819 static void handleExpansion(WCHAR *cmd, BOOL justFors,
820                             const WCHAR *forVariable, const WCHAR *forValue) {
821
822   /* For commands in a context (batch program):                  */
823   /*   Expand environment variables in a batch file %{0-9} first */
824   /*     including support for any ~ modifiers                   */
825   /* Additionally:                                               */
826   /*   Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special  */
827   /*     names allowing environment variable overrides           */
828   /* NOTE: To support the %PATH:xxx% syntax, also perform        */
829   /*   manual expansion of environment variables here            */
830
831   WCHAR *p = cmd;
832   WCHAR *t;
833   int   i;
834
835   while ((p = strchrW(p, '%'))) {
836
837     WINE_TRACE("Translate command:%s %d (at: %s)\n",
838                    wine_dbgstr_w(cmd), justFors, wine_dbgstr_w(p));
839     i = *(p+1) - '0';
840
841     /* Don't touch %% unless its in Batch */
842     if (!justFors && *(p+1) == '%') {
843       if (context) {
844         WCMD_strsubstW(p, p+1, NULL, 0);
845       }
846       p+=1;
847
848     /* Replace %~ modifications if in batch program */
849     } else if (*(p+1) == '~') {
850       WCMD_HandleTildaModifiers(&p, forVariable, forValue, justFors);
851       p++;
852
853     /* Replace use of %0...%9 if in batch program*/
854     } else if (!justFors && context && (i >= 0) && (i <= 9)) {
855       t = WCMD_parameter(context -> command, i + context -> shift_count[i], NULL, NULL);
856       WCMD_strsubstW(p, p+2, t, -1);
857
858     /* Replace use of %* if in batch program*/
859     } else if (!justFors && context && *(p+1)=='*') {
860       WCHAR *startOfParms = NULL;
861       t = WCMD_parameter(context -> command, 1, &startOfParms, NULL);
862       if (startOfParms != NULL)
863         WCMD_strsubstW(p, p+2, startOfParms, -1);
864       else
865         WCMD_strsubstW(p, p+2, NULL, 0);
866
867     } else if (forVariable &&
868                (CompareStringW(LOCALE_USER_DEFAULT,
869                                SORT_STRINGSORT,
870                                p,
871                                strlenW(forVariable),
872                                forVariable, -1) == CSTR_EQUAL)) {
873       WCMD_strsubstW(p, p + strlenW(forVariable), forValue, -1);
874
875     } else if (!justFors) {
876       p = WCMD_expand_envvar(p, forVariable, forValue);
877
878     /* In a FOR loop, see if this is the variable to replace */
879     } else { /* Ignore %'s on second pass of batch program */
880       p++;
881     }
882   }
883
884   return;
885 }
886
887
888 /*******************************************************************
889  * WCMD_parse - parse a command into parameters and qualifiers.
890  *
891  *      On exit, all qualifiers are concatenated into q, the first string
892  *      not beginning with "/" is in p1 and the
893  *      second in p2. Any subsequent non-qualifier strings are lost.
894  *      Parameters in quotes are handled.
895  */
896 static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
897 {
898   int p = 0;
899
900   *q = *p1 = *p2 = '\0';
901   while (TRUE) {
902     switch (*s) {
903       case '/':
904         *q++ = *s++;
905         while ((*s != '\0') && (*s != ' ') && *s != '/') {
906           *q++ = toupperW (*s++);
907         }
908         *q = '\0';
909         break;
910       case ' ':
911       case '\t':
912         s++;
913         break;
914       case '"':
915         s++;
916         while ((*s != '\0') && (*s != '"')) {
917           if (p == 0) *p1++ = *s++;
918           else if (p == 1) *p2++ = *s++;
919           else s++;
920         }
921         if (p == 0) *p1 = '\0';
922         if (p == 1) *p2 = '\0';
923         p++;
924         if (*s == '"') s++;
925         break;
926       case '\0':
927         return;
928       default:
929         while ((*s != '\0') && (*s != ' ') && (*s != '\t')
930                && (*s != '=')  && (*s != ',') ) {
931           if (p == 0) *p1++ = *s++;
932           else if (p == 1) *p2++ = *s++;
933           else s++;
934         }
935         /* Skip concurrent parms */
936         while ((*s == ' ') || (*s == '\t') || (*s == '=')  || (*s == ',') ) s++;
937
938         if (p == 0) *p1 = '\0';
939         if (p == 1) *p2 = '\0';
940         p++;
941     }
942   }
943 }
944
945 static void init_msvcrt_io_block(STARTUPINFOW* st)
946 {
947     STARTUPINFOW st_p;
948     /* fetch the parent MSVCRT info block if any, so that the child can use the
949      * same handles as its grand-father
950      */
951     st_p.cb = sizeof(STARTUPINFOW);
952     GetStartupInfoW(&st_p);
953     st->cbReserved2 = st_p.cbReserved2;
954     st->lpReserved2 = st_p.lpReserved2;
955     if (st_p.cbReserved2 && st_p.lpReserved2)
956     {
957         /* Override the entries for fd 0,1,2 if we happened
958          * to change those std handles (this depends on the way cmd sets
959          * its new input & output handles)
960          */
961         size_t sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
962         BYTE* ptr = HeapAlloc(GetProcessHeap(), 0, sz);
963         if (ptr)
964         {
965             unsigned num = *(unsigned*)st_p.lpReserved2;
966             char* flags = (char*)(ptr + sizeof(unsigned));
967             HANDLE* handles = (HANDLE*)(flags + num * sizeof(char));
968
969             memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
970             st->cbReserved2 = sz;
971             st->lpReserved2 = ptr;
972
973 #define WX_OPEN 0x01    /* see dlls/msvcrt/file.c */
974             if (num <= 0 || (flags[0] & WX_OPEN))
975             {
976                 handles[0] = GetStdHandle(STD_INPUT_HANDLE);
977                 flags[0] |= WX_OPEN;
978             }
979             if (num <= 1 || (flags[1] & WX_OPEN))
980             {
981                 handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
982                 flags[1] |= WX_OPEN;
983             }
984             if (num <= 2 || (flags[2] & WX_OPEN))
985             {
986                 handles[2] = GetStdHandle(STD_ERROR_HANDLE);
987                 flags[2] |= WX_OPEN;
988             }
989 #undef WX_OPEN
990         }
991     }
992 }
993
994 /******************************************************************************
995  * WCMD_run_program
996  *
997  *      Execute a command line as an external program. Must allow recursion.
998  *
999  *      Precedence:
1000  *        Manual testing under windows shows PATHEXT plays a key part in this,
1001  *      and the search algorithm and precedence appears to be as follows.
1002  *
1003  *      Search locations:
1004  *        If directory supplied on command, just use that directory
1005  *        If extension supplied on command, look for that explicit name first
1006  *        Otherwise, search in each directory on the path
1007  *      Precedence:
1008  *        If extension supplied on command, look for that explicit name first
1009  *        Then look for supplied name .* (even if extension supplied, so
1010  *          'garbage.exe' will match 'garbage.exe.cmd')
1011  *        If any found, cycle through PATHEXT looking for name.exe one by one
1012  *      Launching
1013  *        Once a match has been found, it is launched - Code currently uses
1014  *          findexecutable to achieve this which is left untouched.
1015  */
1016
1017 void WCMD_run_program (WCHAR *command, int called) {
1018
1019   WCHAR  temp[MAX_PATH];
1020   WCHAR  pathtosearch[MAXSTRING];
1021   WCHAR *pathposn;
1022   WCHAR  stemofsearch[MAX_PATH];    /* maximum allowed executable name is
1023                                        MAX_PATH, including null character */
1024   WCHAR *lastSlash;
1025   WCHAR  pathext[MAXSTRING];
1026   BOOL  extensionsupplied = FALSE;
1027   BOOL  launched = FALSE;
1028   BOOL  status;
1029   BOOL  assumeInternal = FALSE;
1030   DWORD len;
1031   static const WCHAR envPath[] = {'P','A','T','H','\0'};
1032   static const WCHAR envPathExt[] = {'P','A','T','H','E','X','T','\0'};
1033   static const WCHAR delims[] = {'/','\\',':','\0'};
1034
1035   WCMD_parse (command, quals, param1, param2);  /* Quick way to get the filename */
1036   if (!(*param1) && !(*param2))
1037     return;
1038
1039   /* Calculate the search path and stem to search for */
1040   if (strpbrkW (param1, delims) == NULL) {  /* No explicit path given, search path */
1041     static const WCHAR curDir[] = {'.',';','\0'};
1042     strcpyW(pathtosearch, curDir);
1043     len = GetEnvironmentVariableW(envPath, &pathtosearch[2], (sizeof(pathtosearch)/sizeof(WCHAR))-2);
1044     if ((len == 0) || (len >= (sizeof(pathtosearch)/sizeof(WCHAR)) - 2)) {
1045       static const WCHAR curDir[] = {'.','\0'};
1046       strcpyW (pathtosearch, curDir);
1047     }
1048     if (strchrW(param1, '.') != NULL) extensionsupplied = TRUE;
1049     if (strlenW(param1) >= MAX_PATH)
1050     {
1051         WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
1052         return;
1053     }
1054
1055     strcpyW(stemofsearch, param1);
1056
1057   } else {
1058
1059     /* Convert eg. ..\fred to include a directory by removing file part */
1060     GetFullPathNameW(param1, sizeof(pathtosearch)/sizeof(WCHAR), pathtosearch, NULL);
1061     lastSlash = strrchrW(pathtosearch, '\\');
1062     if (lastSlash && strchrW(lastSlash, '.') != NULL) extensionsupplied = TRUE;
1063     strcpyW(stemofsearch, lastSlash+1);
1064
1065     /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
1066        c:\windows\a.bat syntax                                                 */
1067     if (lastSlash) *(lastSlash + 1) = 0x00;
1068   }
1069
1070   /* Now extract PATHEXT */
1071   len = GetEnvironmentVariableW(envPathExt, pathext, sizeof(pathext)/sizeof(WCHAR));
1072   if ((len == 0) || (len >= (sizeof(pathext)/sizeof(WCHAR)))) {
1073     static const WCHAR dfltPathExt[] = {'.','b','a','t',';',
1074                                         '.','c','o','m',';',
1075                                         '.','c','m','d',';',
1076                                         '.','e','x','e','\0'};
1077     strcpyW (pathext, dfltPathExt);
1078   }
1079
1080   /* Loop through the search path, dir by dir */
1081   pathposn = pathtosearch;
1082   WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
1083              wine_dbgstr_w(stemofsearch));
1084   while (!launched && pathposn) {
1085
1086     WCHAR  thisDir[MAX_PATH] = {'\0'};
1087     WCHAR *pos               = NULL;
1088     BOOL  found             = FALSE;
1089     static const WCHAR slashW[] = {'\\','\0'};
1090
1091     /* Work on the first directory on the search path */
1092     pos = strchrW(pathposn, ';');
1093     if (pos) {
1094       memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
1095       thisDir[(pos-pathposn)] = 0x00;
1096       pathposn = pos+1;
1097
1098     } else {
1099       strcpyW(thisDir, pathposn);
1100       pathposn = NULL;
1101     }
1102
1103     /* Since you can have eg. ..\.. on the path, need to expand
1104        to full information                                      */
1105     strcpyW(temp, thisDir);
1106     GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
1107
1108     /* 1. If extension supplied, see if that file exists */
1109     strcatW(thisDir, slashW);
1110     strcatW(thisDir, stemofsearch);
1111     pos = &thisDir[strlenW(thisDir)]; /* Pos = end of name */
1112
1113     /* 1. If extension supplied, see if that file exists */
1114     if (extensionsupplied) {
1115       if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1116         found = TRUE;
1117       }
1118     }
1119
1120     /* 2. Any .* matches? */
1121     if (!found) {
1122       HANDLE          h;
1123       WIN32_FIND_DATAW finddata;
1124       static const WCHAR allFiles[] = {'.','*','\0'};
1125
1126       strcatW(thisDir,allFiles);
1127       h = FindFirstFileW(thisDir, &finddata);
1128       FindClose(h);
1129       if (h != INVALID_HANDLE_VALUE) {
1130
1131         WCHAR *thisExt = pathext;
1132
1133         /* 3. Yes - Try each path ext */
1134         while (thisExt) {
1135           WCHAR *nextExt = strchrW(thisExt, ';');
1136
1137           if (nextExt) {
1138             memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
1139             pos[(nextExt-thisExt)] = 0x00;
1140             thisExt = nextExt+1;
1141           } else {
1142             strcpyW(pos, thisExt);
1143             thisExt = NULL;
1144           }
1145
1146           if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
1147             found = TRUE;
1148             thisExt = NULL;
1149           }
1150         }
1151       }
1152     }
1153
1154    /* Internal programs won't be picked up by this search, so even
1155       though not found, try one last createprocess and wait for it
1156       to complete.
1157       Note: Ideally we could tell between a console app (wait) and a
1158       windows app, but the API's for it fail in this case           */
1159     if (!found && pathposn == NULL) {
1160         WINE_TRACE("ASSUMING INTERNAL\n");
1161         assumeInternal = TRUE;
1162     } else {
1163         WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
1164     }
1165
1166     /* Once found, launch it */
1167     if (found || assumeInternal) {
1168       STARTUPINFOW st;
1169       PROCESS_INFORMATION pe;
1170       SHFILEINFOW psfi;
1171       DWORD console;
1172       HINSTANCE hinst;
1173       WCHAR *ext = strrchrW( thisDir, '.' );
1174       static const WCHAR batExt[] = {'.','b','a','t','\0'};
1175       static const WCHAR cmdExt[] = {'.','c','m','d','\0'};
1176
1177       launched = TRUE;
1178
1179       /* Special case BAT and CMD */
1180       if (ext && !strcmpiW(ext, batExt)) {
1181         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1182         return;
1183       } else if (ext && !strcmpiW(ext, cmdExt)) {
1184         WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
1185         return;
1186       } else {
1187
1188         /* thisDir contains the file to be launched, but with what?
1189            eg. a.exe will require a.exe to be launched, a.html may be iexplore */
1190         hinst = FindExecutableW (thisDir, NULL, temp);
1191         if ((INT_PTR)hinst < 32)
1192           console = 0;
1193         else
1194           console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
1195
1196         ZeroMemory (&st, sizeof(STARTUPINFOW));
1197         st.cb = sizeof(STARTUPINFOW);
1198         init_msvcrt_io_block(&st);
1199
1200         /* Launch the process and if a CUI wait on it to complete
1201            Note: Launching internal wine processes cannot specify a full path to exe */
1202         status = CreateProcessW(assumeInternal?NULL : thisDir,
1203                                 command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
1204         if ((opt_c || opt_k) && !opt_s && !status
1205             && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
1206           /* strip first and last quote WCHARacters and try again */
1207           WCMD_strip_quotes(command);
1208           opt_s=1;
1209           WCMD_run_program(command, called);
1210           return;
1211         }
1212
1213         if (!status)
1214           break;
1215
1216         if (!assumeInternal && !console) errorlevel = 0;
1217         else
1218         {
1219             /* Always wait when called in a batch program context */
1220             if (assumeInternal || context || !HIWORD(console)) WaitForSingleObject (pe.hProcess, INFINITE);
1221             GetExitCodeProcess (pe.hProcess, &errorlevel);
1222             if (errorlevel == STILL_ACTIVE) errorlevel = 0;
1223         }
1224         CloseHandle(pe.hProcess);
1225         CloseHandle(pe.hThread);
1226         return;
1227       }
1228     }
1229   }
1230
1231   /* Not found anywhere - give up */
1232   SetLastError(ERROR_FILE_NOT_FOUND);
1233   WCMD_print_error ();
1234
1235   /* If a command fails to launch, it sets errorlevel 9009 - which
1236      does not seem to have any associated constant definition     */
1237   errorlevel = 9009;
1238   return;
1239
1240 }
1241
1242 /*****************************************************************************
1243  * Process one command. If the command is EXIT this routine does not return.
1244  * We will recurse through here executing batch files.
1245  */
1246 void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
1247                    const WCHAR *forVariable, const WCHAR *forValue,
1248                    CMD_LIST **cmdList)
1249 {
1250     WCHAR *cmd, *p, *redir;
1251     int status, i;
1252     DWORD count, creationDisposition;
1253     HANDLE h;
1254     WCHAR *whichcmd;
1255     SECURITY_ATTRIBUTES sa;
1256     WCHAR *new_cmd = NULL;
1257     WCHAR *new_redir = NULL;
1258     HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
1259                                 GetStdHandle (STD_OUTPUT_HANDLE),
1260                                 GetStdHandle (STD_ERROR_HANDLE)};
1261     DWORD  idx_stdhandles[3] = {STD_INPUT_HANDLE,
1262                                 STD_OUTPUT_HANDLE,
1263                                 STD_ERROR_HANDLE};
1264     BOOL prev_echo_mode, piped = FALSE;
1265
1266     WINE_TRACE("command on entry:%s (%p), with forVariable '%s'='%s'\n",
1267                wine_dbgstr_w(command), cmdList,
1268                wine_dbgstr_w(forVariable), wine_dbgstr_w(forValue));
1269
1270     /* If the next command is a pipe then we implement pipes by redirecting
1271        the output from this command to a temp file and input into the
1272        next command from that temp file.
1273        FIXME: Use of named pipes would make more sense here as currently this
1274        process has to finish before the next one can start but this requires
1275        a change to not wait for the first app to finish but rather the pipe  */
1276     if (cmdList && (*cmdList)->nextcommand &&
1277         (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
1278
1279         WCHAR temp_path[MAX_PATH];
1280         static const WCHAR cmdW[]     = {'C','M','D','\0'};
1281
1282         /* Remember piping is in action */
1283         WINE_TRACE("Output needs to be piped\n");
1284         piped = TRUE;
1285
1286         /* Generate a unique temporary filename */
1287         GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1288         GetTempFileNameW(temp_path, cmdW, 0, (*cmdList)->nextcommand->pipeFile);
1289         WINE_TRACE("Using temporary file of %s\n",
1290                    wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
1291     }
1292
1293     /* Move copy of the command onto the heap so it can be expanded */
1294     new_cmd = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1295     if (!new_cmd)
1296     {
1297         WINE_ERR("Could not allocate memory for new_cmd\n");
1298         return;
1299     }
1300     strcpyW(new_cmd, command);
1301
1302     /* Move copy of the redirects onto the heap so it can be expanded */
1303     new_redir = HeapAlloc( GetProcessHeap(), 0, MAXSTRING * sizeof(WCHAR));
1304     if (!new_redir)
1305     {
1306         WINE_ERR("Could not allocate memory for new_redir\n");
1307         HeapFree( GetProcessHeap(), 0, new_cmd );
1308         return;
1309     }
1310
1311     /* If piped output, send stdout to the pipe by appending >filename to redirects */
1312     if (piped) {
1313         static const WCHAR redirOut[] = {'%','s',' ','>',' ','%','s','\0'};
1314         wsprintfW (new_redir, redirOut, redirects, (*cmdList)->nextcommand->pipeFile);
1315         WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
1316     } else {
1317         strcpyW(new_redir, redirects);
1318     }
1319
1320     /* Expand variables in command line mode only (batch mode will
1321        be expanded as the line is read in, except for 'for' loops) */
1322     handleExpansion(new_cmd, (context != NULL), forVariable, forValue);
1323     handleExpansion(new_redir, (context != NULL), forVariable, forValue);
1324     cmd = new_cmd;
1325
1326 /*
1327  *      Changing default drive has to be handled as a special case.
1328  */
1329
1330     if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) && (strlenW(cmd) == 2)) {
1331       WCHAR envvar[5];
1332       WCHAR dir[MAX_PATH];
1333
1334       /* According to MSDN CreateProcess docs, special env vars record
1335          the current directory on each drive, in the form =C:
1336          so see if one specified, and if so go back to it             */
1337       strcpyW(envvar, equalsW);
1338       strcatW(envvar, cmd);
1339       if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
1340         static const WCHAR fmt[] = {'%','s','\\','\0'};
1341         wsprintfW(cmd, fmt, cmd);
1342         WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
1343       }
1344       WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
1345       status = SetCurrentDirectoryW(cmd);
1346       if (!status) WCMD_print_error ();
1347       HeapFree( GetProcessHeap(), 0, cmd );
1348       HeapFree( GetProcessHeap(), 0, new_redir );
1349       return;
1350     }
1351
1352     sa.nLength = sizeof(sa);
1353     sa.lpSecurityDescriptor = NULL;
1354     sa.bInheritHandle = TRUE;
1355
1356 /*
1357  *      Redirect stdin, stdout and/or stderr if required.
1358  */
1359
1360     /* STDIN could come from a preceding pipe, so delete on close if it does */
1361     if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
1362         WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
1363         h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
1364                   FILE_SHARE_READ, &sa, OPEN_EXISTING,
1365                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1366         if (h == INVALID_HANDLE_VALUE) {
1367           WCMD_print_error ();
1368           HeapFree( GetProcessHeap(), 0, cmd );
1369           HeapFree( GetProcessHeap(), 0, new_redir );
1370           return;
1371         }
1372         SetStdHandle (STD_INPUT_HANDLE, h);
1373
1374         /* No need to remember the temporary name any longer once opened */
1375         (*cmdList)->pipeFile[0] = 0x00;
1376
1377     /* Otherwise STDIN could come from a '<' redirect */
1378     } else if ((p = strchrW(new_redir,'<')) != NULL) {
1379       h = CreateFileW(WCMD_parameter(++p, 0, NULL, NULL), GENERIC_READ, FILE_SHARE_READ,
1380                       &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1381       if (h == INVALID_HANDLE_VALUE) {
1382         WCMD_print_error ();
1383         HeapFree( GetProcessHeap(), 0, cmd );
1384         HeapFree( GetProcessHeap(), 0, new_redir );
1385         return;
1386       }
1387       SetStdHandle (STD_INPUT_HANDLE, h);
1388     }
1389
1390     /* Scan the whole command looking for > and 2> */
1391     redir = new_redir;
1392     while (redir != NULL && ((p = strchrW(redir,'>')) != NULL)) {
1393       int handle = 0;
1394
1395       if (p > redir && (*(p-1)=='2'))
1396         handle = 2;
1397       else
1398         handle = 1;
1399
1400       p++;
1401       if ('>' == *p) {
1402         creationDisposition = OPEN_ALWAYS;
1403         p++;
1404       }
1405       else {
1406         creationDisposition = CREATE_ALWAYS;
1407       }
1408
1409       /* Add support for 2>&1 */
1410       redir = p;
1411       if (*p == '&') {
1412         int idx = *(p+1) - '0';
1413
1414         if (DuplicateHandle(GetCurrentProcess(),
1415                         GetStdHandle(idx_stdhandles[idx]),
1416                         GetCurrentProcess(),
1417                         &h,
1418                         0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
1419           WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
1420         }
1421         WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
1422
1423       } else {
1424         WCHAR *param = WCMD_parameter(p, 0, NULL, NULL);
1425         h = CreateFileW(param, GENERIC_WRITE, 0, &sa, creationDisposition,
1426                         FILE_ATTRIBUTE_NORMAL, NULL);
1427         if (h == INVALID_HANDLE_VALUE) {
1428           WCMD_print_error ();
1429           HeapFree( GetProcessHeap(), 0, cmd );
1430           HeapFree( GetProcessHeap(), 0, new_redir );
1431           return;
1432         }
1433         if (SetFilePointer (h, 0, NULL, FILE_END) ==
1434               INVALID_SET_FILE_POINTER) {
1435           WCMD_print_error ();
1436         }
1437         WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
1438       }
1439
1440       SetStdHandle (idx_stdhandles[handle], h);
1441     }
1442
1443 /*
1444  * Strip leading whitespaces, and a '@' if supplied
1445  */
1446     whichcmd = WCMD_skip_leading_spaces(cmd);
1447     WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
1448     if (whichcmd[0] == '@') whichcmd++;
1449
1450 /*
1451  *      Check if the command entered is internal. If it is, pass the rest of the
1452  *      line down to the command. If not try to run a program.
1453  */
1454
1455     count = 0;
1456     while (IsCharAlphaNumericW(whichcmd[count])) {
1457       count++;
1458     }
1459     for (i=0; i<=WCMD_EXIT; i++) {
1460       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1461         whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
1462     }
1463     p = WCMD_skip_leading_spaces (&whichcmd[count]);
1464     WCMD_parse (p, quals, param1, param2);
1465     WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1466
1467     if (i <= WCMD_EXIT && (p[0] == '/') && (p[1] == '?')) {
1468       /* this is a help request for a builtin program */
1469       i = WCMD_HELP;
1470       memcpy(p, whichcmd, count * sizeof(WCHAR));
1471       p[count] = '\0';
1472
1473     }
1474
1475     switch (i) {
1476
1477       case WCMD_CALL:
1478         WCMD_call (p);
1479         break;
1480       case WCMD_CD:
1481       case WCMD_CHDIR:
1482         WCMD_setshow_default (p);
1483         break;
1484       case WCMD_CLS:
1485         WCMD_clear_screen ();
1486         break;
1487       case WCMD_COPY:
1488         WCMD_copy ();
1489         break;
1490       case WCMD_CTTY:
1491         WCMD_change_tty ();
1492         break;
1493       case WCMD_DATE:
1494         WCMD_setshow_date ();
1495         break;
1496       case WCMD_DEL:
1497       case WCMD_ERASE:
1498         WCMD_delete (p);
1499         break;
1500       case WCMD_DIR:
1501         WCMD_directory (p);
1502         break;
1503       case WCMD_ECHO:
1504         WCMD_echo(&whichcmd[count]);
1505         break;
1506       case WCMD_FOR:
1507         WCMD_for (p, cmdList);
1508         break;
1509       case WCMD_GOTO:
1510         WCMD_goto (cmdList);
1511         break;
1512       case WCMD_HELP:
1513         WCMD_give_help (p);
1514         break;
1515       case WCMD_IF:
1516         WCMD_if (p, cmdList);
1517         break;
1518       case WCMD_LABEL:
1519         WCMD_volume (TRUE, p);
1520         break;
1521       case WCMD_MD:
1522       case WCMD_MKDIR:
1523         WCMD_create_dir (p);
1524         break;
1525       case WCMD_MOVE:
1526         WCMD_move ();
1527         break;
1528       case WCMD_PATH:
1529         WCMD_setshow_path (p);
1530         break;
1531       case WCMD_PAUSE:
1532         WCMD_pause ();
1533         break;
1534       case WCMD_PROMPT:
1535         WCMD_setshow_prompt ();
1536         break;
1537       case WCMD_REM:
1538         break;
1539       case WCMD_REN:
1540       case WCMD_RENAME:
1541         WCMD_rename ();
1542         break;
1543       case WCMD_RD:
1544       case WCMD_RMDIR:
1545         WCMD_remove_dir (p);
1546         break;
1547       case WCMD_SETLOCAL:
1548         WCMD_setlocal(p);
1549         break;
1550       case WCMD_ENDLOCAL:
1551         WCMD_endlocal();
1552         break;
1553       case WCMD_SET:
1554         WCMD_setshow_env (p);
1555         break;
1556       case WCMD_SHIFT:
1557         WCMD_shift (p);
1558         break;
1559       case WCMD_TIME:
1560         WCMD_setshow_time ();
1561         break;
1562       case WCMD_TITLE:
1563         if (strlenW(&whichcmd[count]) > 0)
1564           WCMD_title(&whichcmd[count+1]);
1565         break;
1566       case WCMD_TYPE:
1567         WCMD_type (p);
1568         break;
1569       case WCMD_VER:
1570         WCMD_output(newline);
1571         WCMD_version ();
1572         break;
1573       case WCMD_VERIFY:
1574         WCMD_verify (p);
1575         break;
1576       case WCMD_VOL:
1577         WCMD_volume (FALSE, p);
1578         break;
1579       case WCMD_PUSHD:
1580         WCMD_pushd(p);
1581         break;
1582       case WCMD_POPD:
1583         WCMD_popd();
1584         break;
1585       case WCMD_ASSOC:
1586         WCMD_assoc(p, TRUE);
1587         break;
1588       case WCMD_COLOR:
1589         WCMD_color();
1590         break;
1591       case WCMD_FTYPE:
1592         WCMD_assoc(p, FALSE);
1593         break;
1594       case WCMD_MORE:
1595         WCMD_more(p);
1596         break;
1597       case WCMD_CHOICE:
1598         WCMD_choice(p);
1599         break;
1600       case WCMD_EXIT:
1601         WCMD_exit (cmdList);
1602         break;
1603       default:
1604         prev_echo_mode = echo_mode;
1605         WCMD_run_program (whichcmd, 0);
1606         echo_mode = prev_echo_mode;
1607     }
1608     HeapFree( GetProcessHeap(), 0, cmd );
1609     HeapFree( GetProcessHeap(), 0, new_redir );
1610
1611     /* Restore old handles */
1612     for (i=0; i<3; i++) {
1613       if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
1614         CloseHandle (GetStdHandle (idx_stdhandles[i]));
1615         SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
1616       }
1617     }
1618 }
1619
1620 /*************************************************************************
1621  * WCMD_LoadMessage
1622  *    Load a string from the resource file, handling any error
1623  *    Returns string retrieved from resource file
1624  */
1625 WCHAR *WCMD_LoadMessage(UINT id) {
1626     static WCHAR msg[2048];
1627     static const WCHAR failedMsg[]  = {'F','a','i','l','e','d','!','\0'};
1628
1629     if (!LoadStringW(GetModuleHandleW(NULL), id, msg, sizeof(msg)/sizeof(WCHAR))) {
1630        WINE_FIXME("LoadString failed with %d\n", GetLastError());
1631        strcpyW(msg, failedMsg);
1632     }
1633     return msg;
1634 }
1635
1636 /***************************************************************************
1637  * WCMD_DumpCommands
1638  *
1639  *      Dumps out the parsed command line to ensure syntax is correct
1640  */
1641 static void WCMD_DumpCommands(CMD_LIST *commands) {
1642     CMD_LIST *thisCmd = commands;
1643
1644     WINE_TRACE("Parsed line:\n");
1645     while (thisCmd != NULL) {
1646       WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
1647                thisCmd,
1648                thisCmd->prevDelim,
1649                thisCmd->bracketDepth,
1650                thisCmd->nextcommand,
1651                wine_dbgstr_w(thisCmd->command),
1652                wine_dbgstr_w(thisCmd->redirects));
1653       thisCmd = thisCmd->nextcommand;
1654     }
1655 }
1656
1657 /***************************************************************************
1658  * WCMD_addCommand
1659  *
1660  *   Adds a command to the current command list
1661  */
1662 static void WCMD_addCommand(WCHAR *command, int *commandLen,
1663                      WCHAR *redirs,  int *redirLen,
1664                      WCHAR **copyTo, int **copyToLen,
1665                      CMD_DELIMITERS prevDelim, int curDepth,
1666                      CMD_LIST **lastEntry, CMD_LIST **output) {
1667
1668     CMD_LIST *thisEntry = NULL;
1669
1670     /* Allocate storage for command */
1671     thisEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LIST));
1672
1673     /* Copy in the command */
1674     if (command) {
1675         thisEntry->command = HeapAlloc(GetProcessHeap(), 0,
1676                                       (*commandLen+1) * sizeof(WCHAR));
1677         memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
1678         thisEntry->command[*commandLen] = 0x00;
1679
1680         /* Copy in the redirects */
1681         thisEntry->redirects = HeapAlloc(GetProcessHeap(), 0,
1682                                          (*redirLen+1) * sizeof(WCHAR));
1683         memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
1684         thisEntry->redirects[*redirLen] = 0x00;
1685         thisEntry->pipeFile[0] = 0x00;
1686
1687         /* Reset the lengths */
1688         *commandLen   = 0;
1689         *redirLen     = 0;
1690         *copyToLen    = commandLen;
1691         *copyTo       = command;
1692
1693     } else {
1694         thisEntry->command = NULL;
1695         thisEntry->redirects = NULL;
1696         thisEntry->pipeFile[0] = 0x00;
1697     }
1698
1699     /* Fill in other fields */
1700     thisEntry->nextcommand = NULL;
1701     thisEntry->prevDelim = prevDelim;
1702     thisEntry->bracketDepth = curDepth;
1703     if (*lastEntry) {
1704         (*lastEntry)->nextcommand = thisEntry;
1705     } else {
1706         *output = thisEntry;
1707     }
1708     *lastEntry = thisEntry;
1709 }
1710
1711
1712 /***************************************************************************
1713  * WCMD_IsEndQuote
1714  *
1715  *   Checks if the quote pointed to is the end-quote.
1716  *
1717  *   Quotes end if:
1718  *
1719  *   1) The current parameter ends at EOL or at the beginning
1720  *      of a redirection or pipe and not in a quote section.
1721  *
1722  *   2) If the next character is a space and not in a quote section.
1723  *
1724  *   Returns TRUE if this is an end quote, and FALSE if it is not.
1725  *
1726  */
1727 static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
1728 {
1729     int quoteCount = quoteIndex;
1730     int i;
1731
1732     /* If we are not in a quoted section, then we are not an end-quote */
1733     if(quoteIndex == 0)
1734     {
1735         return FALSE;
1736     }
1737
1738     /* Check how many quotes are left for this parameter */
1739     for(i=0;quote[i];i++)
1740     {
1741         if(quote[i] == '"')
1742         {
1743             quoteCount++;
1744         }
1745
1746         /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
1747         else if(((quoteCount % 2) == 0)
1748             && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ')))
1749         {
1750             break;
1751         }
1752     }
1753
1754     /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
1755        be an end-quote */
1756     if(quoteIndex >= (quoteCount / 2))
1757     {
1758         return TRUE;
1759     }
1760
1761     /* No cigar */
1762     return FALSE;
1763 }
1764
1765 /***************************************************************************
1766  * WCMD_ReadAndParseLine
1767  *
1768  *   Either uses supplied input or
1769  *     Reads a file from the handle, and then...
1770  *   Parse the text buffer, splitting into separate commands
1771  *     - unquoted && strings split 2 commands but the 2nd is flagged as
1772  *            following an &&
1773  *     - ( as the first character just ups the bracket depth
1774  *     - unquoted ) when bracket depth > 0 terminates a bracket and
1775  *            adds a CMD_LIST structure with null command
1776  *     - Anything else gets put into the command string (including
1777  *            redirects)
1778  */
1779 WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
1780 {
1781     WCHAR    *curPos;
1782     int       inQuotes = 0;
1783     WCHAR     curString[MAXSTRING];
1784     int       curStringLen = 0;
1785     WCHAR     curRedirs[MAXSTRING];
1786     int       curRedirsLen = 0;
1787     WCHAR    *curCopyTo;
1788     int      *curLen;
1789     int       curDepth = 0;
1790     CMD_LIST *lastEntry = NULL;
1791     CMD_DELIMITERS prevDelim = CMD_NONE;
1792     static WCHAR    *extraSpace = NULL;  /* Deliberately never freed */
1793     static const WCHAR remCmd[] = {'r','e','m'};
1794     static const WCHAR forCmd[] = {'f','o','r'};
1795     static const WCHAR ifCmd[]  = {'i','f'};
1796     static const WCHAR ifElse[] = {'e','l','s','e'};
1797     BOOL      inRem = FALSE;
1798     BOOL      inFor = FALSE;
1799     BOOL      inIn  = FALSE;
1800     BOOL      inIf  = FALSE;
1801     BOOL      inElse= FALSE;
1802     BOOL      onlyWhiteSpace = FALSE;
1803     BOOL      lastWasWhiteSpace = FALSE;
1804     BOOL      lastWasDo   = FALSE;
1805     BOOL      lastWasIn   = FALSE;
1806     BOOL      lastWasElse = FALSE;
1807     BOOL      lastWasRedirect = TRUE;
1808
1809     /* Allocate working space for a command read from keyboard, file etc */
1810     if (!extraSpace)
1811       extraSpace = HeapAlloc(GetProcessHeap(), 0, (MAXSTRING+1) * sizeof(WCHAR));
1812     if (!extraSpace)
1813     {
1814         WINE_ERR("Could not allocate memory for extraSpace\n");
1815         return NULL;
1816     }
1817
1818     /* If initial command read in, use that, otherwise get input from handle */
1819     if (optionalcmd != NULL) {
1820         strcpyW(extraSpace, optionalcmd);
1821     } else if (readFrom == INVALID_HANDLE_VALUE) {
1822         WINE_FIXME("No command nor handle supplied\n");
1823     } else {
1824         if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
1825           return NULL;
1826     }
1827     curPos = extraSpace;
1828
1829     /* Handle truncated input - issue warning */
1830     if (strlenW(extraSpace) == MAXSTRING -1) {
1831         WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
1832         WCMD_output_asis_stderr(extraSpace);
1833         WCMD_output_asis_stderr(newline);
1834     }
1835
1836     /* Replace env vars if in a batch context */
1837     if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
1838     /* Show prompt before batch line IF echo is on and in batch program */
1839     if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
1840       static const WCHAR spc[]={' ','\0'};
1841       static const WCHAR echoDot[] = {'e','c','h','o','.'};
1842       static const WCHAR echoCol[] = {'e','c','h','o',':'};
1843       const DWORD len = sizeof(echoDot)/sizeof(echoDot[0]);
1844       DWORD curr_size = strlenW(extraSpace);
1845       DWORD min_len = (curr_size < len ? curr_size : len);
1846       WCMD_show_prompt();
1847       WCMD_output_asis(extraSpace);
1848       /* I don't know why Windows puts a space here but it does */
1849       /* Except for lines starting with 'echo.' or 'echo:'. Ask MS why */
1850       if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1851                          extraSpace, min_len, echoDot, len) != CSTR_EQUAL
1852           && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1853                          extraSpace, min_len, echoCol, len) != CSTR_EQUAL)
1854       {
1855           WCMD_output_asis(spc);
1856       }
1857       WCMD_output_asis(newline);
1858     }
1859
1860     /* Start with an empty string, copying to the command string */
1861     curStringLen = 0;
1862     curRedirsLen = 0;
1863     curCopyTo    = curString;
1864     curLen       = &curStringLen;
1865     lastWasRedirect = FALSE;  /* Required for eg spaces between > and filename */
1866
1867     /* Parse every character on the line being processed */
1868     while (*curPos != 0x00) {
1869
1870       WCHAR thisChar;
1871
1872       /* Debugging AID:
1873       WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
1874                  lastWasWhiteSpace, onlyWhiteSpace);
1875       */
1876
1877       /* Certain commands need special handling */
1878       if (curStringLen == 0 && curCopyTo == curString) {
1879         static const WCHAR forDO[] = {'d','o'};
1880
1881         /* If command starts with 'rem ', ignore any &&, ( etc. */
1882         if (WCMD_keyword_ws_found(remCmd, sizeof(remCmd)/sizeof(remCmd[0]), curPos)) {
1883           inRem = TRUE;
1884
1885         } else if (WCMD_keyword_ws_found(forCmd, sizeof(forCmd)/sizeof(forCmd[0]), curPos)) {
1886           inFor = TRUE;
1887
1888         /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
1889            is only true in the command portion of the IF statement, but this
1890            should suffice for now
1891             FIXME: Silly syntax like "if 1(==1( (
1892                                         echo they equal
1893                                       )" will be parsed wrong */
1894         } else if (WCMD_keyword_ws_found(ifCmd, sizeof(ifCmd)/sizeof(ifCmd[0]), curPos)) {
1895           inIf = TRUE;
1896
1897         } else if (WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]), curPos)) {
1898           const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1899           inElse = TRUE;
1900           lastWasElse = TRUE;
1901           onlyWhiteSpace = TRUE;
1902           memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1903           (*curLen)+=keyw_len;
1904           curPos+=keyw_len;
1905           continue;
1906
1907         /* In a for loop, the DO command will follow a close bracket followed by
1908            whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
1909            is then 0, and all whitespace is skipped                                */
1910         } else if (inFor &&
1911                    WCMD_keyword_ws_found(forDO, sizeof(forDO)/sizeof(forDO[0]), curPos)) {
1912           const int keyw_len = sizeof(forDO)/sizeof(forDO[0]) + 1;
1913           WINE_TRACE("Found 'DO '\n");
1914           lastWasDo = TRUE;
1915           onlyWhiteSpace = TRUE;
1916           memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1917           (*curLen)+=keyw_len;
1918           curPos+=keyw_len;
1919           continue;
1920         }
1921       } else if (curCopyTo == curString) {
1922
1923         /* Special handling for the 'FOR' command */
1924         if (inFor && lastWasWhiteSpace) {
1925           static const WCHAR forIN[] = {'i','n'};
1926
1927           WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
1928
1929           if (WCMD_keyword_ws_found(forIN, sizeof(forIN)/sizeof(forIN[0]), curPos)) {
1930             const int keyw_len = sizeof(forIN)/sizeof(forIN[0]) + 1;
1931             WINE_TRACE("Found 'IN '\n");
1932             lastWasIn = TRUE;
1933             onlyWhiteSpace = TRUE;
1934             memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
1935             (*curLen)+=keyw_len;
1936             curPos+=keyw_len;
1937             continue;
1938           }
1939         }
1940       }
1941
1942       /* Nothing 'ends' a REM statement and &&, quotes etc are ineffective,
1943          so just use the default processing ie skip character specific
1944          matching below                                                    */
1945       if (!inRem) thisChar = *curPos;
1946       else        thisChar = 'X';  /* Character with no special processing */
1947
1948       lastWasWhiteSpace = FALSE; /* Will be reset below */
1949
1950       switch (thisChar) {
1951
1952       case '=': /* drop through - ignore token delimiters at the start of a command */
1953       case ',': /* drop through - ignore token delimiters at the start of a command */
1954       case '\t':/* drop through - ignore token delimiters at the start of a command */
1955       case ' ':
1956                 /* If a redirect in place, it ends here */
1957                 if (!inQuotes && !lastWasRedirect) {
1958
1959                   /* If finishing off a redirect, add a whitespace delimiter */
1960                   if (curCopyTo == curRedirs) {
1961                       curCopyTo[(*curLen)++] = ' ';
1962                   }
1963                   curCopyTo = curString;
1964                   curLen = &curStringLen;
1965                 }
1966                 if (*curLen > 0) {
1967                   curCopyTo[(*curLen)++] = *curPos;
1968                 }
1969
1970                 /* Remember just processed whitespace */
1971                 lastWasWhiteSpace = TRUE;
1972
1973                 break;
1974
1975       case '>': /* drop through - handle redirect chars the same */
1976       case '<':
1977                 /* Make a redirect start here */
1978                 if (!inQuotes) {
1979                   curCopyTo = curRedirs;
1980                   curLen = &curRedirsLen;
1981                   lastWasRedirect = TRUE;
1982                 }
1983
1984                 /* See if 1>, 2> etc, in which case we have some patching up
1985                    to do (provided there's a preceding whitespace, and enough
1986                    chars read so far) */
1987                 if (curStringLen > 2
1988                         && (*(curPos-1)>='1') && (*(curPos-1)<='9')
1989                         && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
1990                     curStringLen--;
1991                     curString[curStringLen] = 0x00;
1992                     curCopyTo[(*curLen)++] = *(curPos-1);
1993                 }
1994
1995                 curCopyTo[(*curLen)++] = *curPos;
1996
1997                 /* If a redirect is immediately followed by '&' (ie. 2>&1) then
1998                     do not process that ampersand as an AND operator */
1999                 if (thisChar == '>' && *(curPos+1) == '&') {
2000                     curCopyTo[(*curLen)++] = *(curPos+1);
2001                     curPos++;
2002                 }
2003                 break;
2004
2005       case '|': /* Pipe character only if not || */
2006                 if (!inQuotes) {
2007                   lastWasRedirect = FALSE;
2008
2009                   /* Add an entry to the command list */
2010                   if (curStringLen > 0) {
2011
2012                     /* Add the current command */
2013                     WCMD_addCommand(curString, &curStringLen,
2014                           curRedirs, &curRedirsLen,
2015                           &curCopyTo, &curLen,
2016                           prevDelim, curDepth,
2017                           &lastEntry, output);
2018
2019                   }
2020
2021                   if (*(curPos+1) == '|') {
2022                     curPos++; /* Skip other | */
2023                     prevDelim = CMD_ONFAILURE;
2024                   } else {
2025                     prevDelim = CMD_PIPE;
2026                   }
2027                 } else {
2028                   curCopyTo[(*curLen)++] = *curPos;
2029                 }
2030                 break;
2031
2032       case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
2033                     inQuotes--;
2034                 } else {
2035                     inQuotes++; /* Quotes within quotes are fun! */
2036                 }
2037                 curCopyTo[(*curLen)++] = *curPos;
2038                 lastWasRedirect = FALSE;
2039                 break;
2040
2041       case '(': /* If a '(' is the first non whitespace in a command portion
2042                    ie start of line or just after &&, then we read until an
2043                    unquoted ) is found                                       */
2044                 WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
2045                            ", for(%d, In:%d, Do:%d)"
2046                            ", if(%d, else:%d, lwe:%d)\n",
2047                            *curLen, inQuotes,
2048                            onlyWhiteSpace,
2049                            inFor, lastWasIn, lastWasDo,
2050                            inIf, inElse, lastWasElse);
2051                 lastWasRedirect = FALSE;
2052
2053                 /* Ignore open brackets inside the for set */
2054                 if (*curLen == 0 && !inIn) {
2055                   curDepth++;
2056
2057                 /* If in quotes, ignore brackets */
2058                 } else if (inQuotes) {
2059                   curCopyTo[(*curLen)++] = *curPos;
2060
2061                 /* In a FOR loop, an unquoted '(' may occur straight after
2062                       IN or DO
2063                    In an IF statement just handle it regardless as we don't
2064                       parse the operands
2065                    In an ELSE statement, only allow it straight away after
2066                       the ELSE and whitespace
2067                  */
2068                 } else if (inIf ||
2069                            (inElse && lastWasElse && onlyWhiteSpace) ||
2070                            (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
2071
2072                    /* If entering into an 'IN', set inIn */
2073                   if (inFor && lastWasIn && onlyWhiteSpace) {
2074                     WINE_TRACE("Inside an IN\n");
2075                     inIn = TRUE;
2076                   }
2077
2078                   /* Add the current command */
2079                   WCMD_addCommand(curString, &curStringLen,
2080                                   curRedirs, &curRedirsLen,
2081                                   &curCopyTo, &curLen,
2082                                   prevDelim, curDepth,
2083                                   &lastEntry, output);
2084
2085                   curDepth++;
2086                 } else {
2087                   curCopyTo[(*curLen)++] = *curPos;
2088                 }
2089                 break;
2090
2091       case '&': if (!inQuotes) {
2092                   lastWasRedirect = FALSE;
2093
2094                   /* Add an entry to the command list */
2095                   if (curStringLen > 0) {
2096
2097                     /* Add the current command */
2098                     WCMD_addCommand(curString, &curStringLen,
2099                           curRedirs, &curRedirsLen,
2100                           &curCopyTo, &curLen,
2101                           prevDelim, curDepth,
2102                           &lastEntry, output);
2103
2104                   }
2105
2106                   if (*(curPos+1) == '&') {
2107                     curPos++; /* Skip other & */
2108                     prevDelim = CMD_ONSUCCESS;
2109                   } else {
2110                     prevDelim = CMD_NONE;
2111                   }
2112                 } else {
2113                   curCopyTo[(*curLen)++] = *curPos;
2114                 }
2115                 break;
2116
2117       case ')': if (!inQuotes && curDepth > 0) {
2118                   lastWasRedirect = FALSE;
2119
2120                   /* Add the current command if there is one */
2121                   if (curStringLen) {
2122
2123                       /* Add the current command */
2124                       WCMD_addCommand(curString, &curStringLen,
2125                             curRedirs, &curRedirsLen,
2126                             &curCopyTo, &curLen,
2127                             prevDelim, curDepth,
2128                             &lastEntry, output);
2129                   }
2130
2131                   /* Add an empty entry to the command list */
2132                   prevDelim = CMD_NONE;
2133                   WCMD_addCommand(NULL, &curStringLen,
2134                         curRedirs, &curRedirsLen,
2135                         &curCopyTo, &curLen,
2136                         prevDelim, curDepth,
2137                         &lastEntry, output);
2138                   curDepth--;
2139
2140                   /* Leave inIn if necessary */
2141                   if (inIn) inIn =  FALSE;
2142                 } else {
2143                   curCopyTo[(*curLen)++] = *curPos;
2144                 }
2145                 break;
2146       default:
2147                 lastWasRedirect = FALSE;
2148                 curCopyTo[(*curLen)++] = *curPos;
2149       }
2150
2151       curPos++;
2152
2153       /* At various times we need to know if we have only skipped whitespace,
2154          so reset this variable and then it will remain true until a non
2155          whitespace is found                                               */
2156       if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
2157         onlyWhiteSpace = FALSE;
2158
2159       /* Flag end of interest in FOR DO and IN parms once something has been processed */
2160       if (!lastWasWhiteSpace) {
2161         lastWasIn = lastWasDo = FALSE;
2162       }
2163
2164       /* If we have reached the end, add this command into the list */
2165       if (*curPos == 0x00 && *curLen > 0) {
2166
2167           /* Add an entry to the command list */
2168           WCMD_addCommand(curString, &curStringLen,
2169                 curRedirs, &curRedirsLen,
2170                 &curCopyTo, &curLen,
2171                 prevDelim, curDepth,
2172                 &lastEntry, output);
2173       }
2174
2175       /* If we have reached the end of the string, see if bracketing outstanding */
2176       if (*curPos == 0x00 && curDepth > 0 && readFrom != INVALID_HANDLE_VALUE) {
2177         inRem = FALSE;
2178         prevDelim = CMD_NONE;
2179         inQuotes = 0;
2180         memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
2181
2182         /* Read more, skipping any blank lines */
2183         while (*extraSpace == 0x00) {
2184           if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
2185           if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
2186             break;
2187         }
2188         curPos = extraSpace;
2189         if (context) handleExpansion(extraSpace, FALSE, NULL, NULL);
2190         /* Continue to echo commands IF echo is on and in batch program */
2191         if (context && echo_mode && extraSpace[0] && (extraSpace[0] != '@')) {
2192           WCMD_output_asis(extraSpace);
2193           WCMD_output_asis(newline);
2194         }
2195       }
2196     }
2197
2198     /* Dump out the parsed output */
2199     WCMD_DumpCommands(*output);
2200
2201     return extraSpace;
2202 }
2203
2204 /***************************************************************************
2205  * WCMD_process_commands
2206  *
2207  * Process all the commands read in so far
2208  */
2209 CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
2210                                 const WCHAR *var, const WCHAR *val) {
2211
2212     int bdepth = -1;
2213
2214     if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
2215
2216     /* Loop through the commands, processing them one by one */
2217     while (thisCmd) {
2218
2219       CMD_LIST *origCmd = thisCmd;
2220
2221       /* If processing one bracket only, and we find the end bracket
2222          entry (or less), return                                    */
2223       if (oneBracket && !thisCmd->command &&
2224           bdepth <= thisCmd->bracketDepth) {
2225         WINE_TRACE("Finished bracket @ %p, next command is %p\n",
2226                    thisCmd, thisCmd->nextcommand);
2227         return thisCmd->nextcommand;
2228       }
2229
2230       /* Ignore the NULL entries a ')' inserts (Only 'if' cares
2231          about them and it will be handled in there)
2232          Also, skip over any batch labels (eg. :fred)          */
2233       if (thisCmd->command && thisCmd->command[0] != ':') {
2234         WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
2235         WCMD_execute (thisCmd->command, thisCmd->redirects, var, val, &thisCmd);
2236       }
2237
2238       /* Step on unless the command itself already stepped on */
2239       if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
2240     }
2241     return NULL;
2242 }
2243
2244 /***************************************************************************
2245  * WCMD_free_commands
2246  *
2247  * Frees the storage held for a parsed command line
2248  * - This is not done in the process_commands, as eventually the current
2249  *   pointer will be modified within the commands, and hence a single free
2250  *   routine is simpler
2251  */
2252 void WCMD_free_commands(CMD_LIST *cmds) {
2253
2254     /* Loop through the commands, freeing them one by one */
2255     while (cmds) {
2256       CMD_LIST *thisCmd = cmds;
2257       cmds = cmds->nextcommand;
2258       HeapFree(GetProcessHeap(), 0, thisCmd->command);
2259       HeapFree(GetProcessHeap(), 0, thisCmd->redirects);
2260       HeapFree(GetProcessHeap(), 0, thisCmd);
2261     }
2262 }
2263
2264
2265 /*****************************************************************************
2266  * Main entry point. This is a console application so we have a main() not a
2267  * winmain().
2268  */
2269
2270 int wmain (int argc, WCHAR *argvW[])
2271 {
2272   int     args;
2273   WCHAR  *cmd   = NULL;
2274   WCHAR string[1024];
2275   WCHAR envvar[4];
2276   int opt_q;
2277   int opt_t = 0;
2278   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2279   static const WCHAR defaultpromptW[] = {'$','P','$','G','\0'};
2280   char ansiVersion[100];
2281   CMD_LIST *toExecute = NULL;         /* Commands left to be executed */
2282
2283   srand(time(NULL));
2284
2285   /* Pre initialize some messages */
2286   strcpy(ansiVersion, PACKAGE_VERSION);
2287   MultiByteToWideChar(CP_ACP, 0, ansiVersion, -1, string, 1024);
2288   wsprintfW(version_string, WCMD_LoadMessage(WCMD_VERSION), string);
2289   strcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
2290
2291   args  = argc;
2292   opt_c=opt_k=opt_q=opt_s=0;
2293   while (args > 0)
2294   {
2295       WCHAR c;
2296       WINE_TRACE("Command line parm: '%s'\n", wine_dbgstr_w(*argvW));
2297       if ((*argvW)[0]!='/' || (*argvW)[1]=='\0') {
2298           argvW++;
2299           args--;
2300           continue;
2301       }
2302
2303       c=(*argvW)[1];
2304       if (tolowerW(c)=='c') {
2305           opt_c=1;
2306       } else if (tolowerW(c)=='q') {
2307           opt_q=1;
2308       } else if (tolowerW(c)=='k') {
2309           opt_k=1;
2310       } else if (tolowerW(c)=='s') {
2311           opt_s=1;
2312       } else if (tolowerW(c)=='a') {
2313           unicodePipes=FALSE;
2314       } else if (tolowerW(c)=='u') {
2315           unicodePipes=TRUE;
2316       } else if (tolowerW(c)=='t' && (*argvW)[2]==':') {
2317           opt_t=strtoulW(&(*argvW)[3], NULL, 16);
2318       } else if (tolowerW(c)=='x' || tolowerW(c)=='y') {
2319           /* Ignored for compatibility with Windows */
2320       }
2321
2322       if ((*argvW)[2]==0) {
2323           argvW++;
2324           args--;
2325       }
2326       else /* handle `cmd /cnotepad.exe` and `cmd /x/c ...` */
2327       {
2328           *argvW+=2;
2329       }
2330
2331       if (opt_c || opt_k) /* break out of parsing immediately after c or k */
2332           break;
2333   }
2334
2335   if (opt_q) {
2336     static const WCHAR eoff[] = {'O','F','F','\0'};
2337     WCMD_echo(eoff);
2338   }
2339
2340   if (opt_c || opt_k) {
2341       int     len,qcount;
2342       WCHAR** arg;
2343       int     argsLeft;
2344       WCHAR*  p;
2345
2346       /* opt_s left unflagged if the command starts with and contains exactly
2347        * one quoted string (exactly two quote characters). The quoted string
2348        * must be an executable name that has whitespace and must not have the
2349        * following characters: &<>()@^| */
2350
2351       /* Build the command to execute */
2352       len = 0;
2353       qcount = 0;
2354       argsLeft = args;
2355       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2356       {
2357           int has_space,bcount;
2358           WCHAR* a;
2359
2360           has_space=0;
2361           bcount=0;
2362           a=*arg;
2363           if( !*a ) has_space=1;
2364           while (*a!='\0') {
2365               if (*a=='\\') {
2366                   bcount++;
2367               } else {
2368                   if (*a==' ' || *a=='\t') {
2369                       has_space=1;
2370                   } else if (*a=='"') {
2371                       /* doubling of '\' preceding a '"',
2372                        * plus escaping of said '"'
2373                        */
2374                       len+=2*bcount+1;
2375                       qcount++;
2376                   }
2377                   bcount=0;
2378               }
2379               a++;
2380           }
2381           len+=(a-*arg) + 1; /* for the separating space */
2382           if (has_space)
2383           {
2384               len+=2; /* for the quotes */
2385               qcount+=2;
2386           }
2387       }
2388
2389       if (qcount!=2)
2390           opt_s=1;
2391
2392       /* check argvW[0] for a space and invalid characters */
2393       if (!opt_s) {
2394           opt_s=1;
2395           p=*argvW;
2396           while (*p!='\0') {
2397               if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
2398                   || *p=='@' || *p=='^' || *p=='|') {
2399                   opt_s=1;
2400                   break;
2401               }
2402               if (*p==' ')
2403                   opt_s=0;
2404               p++;
2405           }
2406       }
2407
2408       cmd = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
2409       if (!cmd)
2410           exit(1);
2411
2412       p = cmd;
2413       argsLeft = args;
2414       for (arg = argvW; argsLeft>0; arg++,argsLeft--)
2415       {
2416           int has_space,has_quote;
2417           WCHAR* a;
2418
2419           /* Check for quotes and spaces in this argument */
2420           has_space=has_quote=0;
2421           a=*arg;
2422           if( !*a ) has_space=1;
2423           while (*a!='\0') {
2424               if (*a==' ' || *a=='\t') {
2425                   has_space=1;
2426                   if (has_quote)
2427                       break;
2428               } else if (*a=='"') {
2429                   has_quote=1;
2430                   if (has_space)
2431                       break;
2432               }
2433               a++;
2434           }
2435
2436           /* Now transfer it to the command line */
2437           if (has_space)
2438               *p++='"';
2439           if (has_quote) {
2440               int bcount;
2441               WCHAR* a;
2442
2443               bcount=0;
2444               a=*arg;
2445               while (*a!='\0') {
2446                   if (*a=='\\') {
2447                       *p++=*a;
2448                       bcount++;
2449                   } else {
2450                       if (*a=='"') {
2451                           int i;
2452
2453                           /* Double all the '\\' preceding this '"', plus one */
2454                           for (i=0;i<=bcount;i++)
2455                               *p++='\\';
2456                           *p++='"';
2457                       } else {
2458                           *p++=*a;
2459                       }
2460                       bcount=0;
2461                   }
2462                   a++;
2463               }
2464           } else {
2465               strcpyW(p,*arg);
2466               p+=strlenW(*arg);
2467           }
2468           if (has_space)
2469               *p++='"';
2470           *p++=' ';
2471       }
2472       if (p > cmd)
2473           p--;  /* remove last space */
2474       *p = '\0';
2475
2476       WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
2477
2478       /* strip first and last quote characters if opt_s; check for invalid
2479        * executable is done later */
2480       if (opt_s && *cmd=='\"')
2481           WCMD_strip_quotes(cmd);
2482   }
2483
2484   if (opt_c) {
2485       /* If we do a "cmd /c command", we don't want to allocate a new
2486        * console since the command returns immediately. Rather, we use
2487        * the currently allocated input and output handles. This allows
2488        * us to pipe to and read from the command interpreter.
2489        */
2490
2491       /* Parse the command string, without reading any more input */
2492       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2493       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2494       WCMD_free_commands(toExecute);
2495       toExecute = NULL;
2496
2497       HeapFree(GetProcessHeap(), 0, cmd);
2498       return errorlevel;
2499   }
2500
2501   SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_LINE_INPUT |
2502                  ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2503   SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
2504
2505   /* Note: cmd.exe /c dir does not get a new color, /k dir does */
2506   if (opt_t) {
2507       if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
2508           defaultColor = opt_t & 0xFF;
2509           param1[0] = 0x00;
2510           WCMD_color();
2511       }
2512   } else {
2513       /* Check HKCU\Software\Microsoft\Command Processor
2514          Then  HKLM\Software\Microsoft\Command Processor
2515            for defaultcolour value
2516            Note  Can be supplied as DWORD or REG_SZ
2517            Note2 When supplied as REG_SZ it's in decimal!!! */
2518       HKEY key;
2519       DWORD type;
2520       DWORD value=0, size=4;
2521       static const WCHAR regKeyW[] = {'S','o','f','t','w','a','r','e','\\',
2522                                       'M','i','c','r','o','s','o','f','t','\\',
2523                                       'C','o','m','m','a','n','d',' ','P','r','o','c','e','s','s','o','r','\0'};
2524       static const WCHAR dfltColorW[] = {'D','e','f','a','u','l','t','C','o','l','o','r','\0'};
2525
2526       if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
2527                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2528           WCHAR  strvalue[4];
2529
2530           /* See if DWORD or REG_SZ */
2531           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2532                      NULL, NULL) == ERROR_SUCCESS) {
2533               if (type == REG_DWORD) {
2534                   size = sizeof(DWORD);
2535                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2536                                   (LPBYTE)&value, &size);
2537               } else if (type == REG_SZ) {
2538                   size = sizeof(strvalue)/sizeof(WCHAR);
2539                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2540                                   (LPBYTE)strvalue, &size);
2541                   value = strtoulW(strvalue, NULL, 10);
2542               }
2543           }
2544           RegCloseKey(key);
2545       }
2546
2547       if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
2548                        0, KEY_READ, &key) == ERROR_SUCCESS) {
2549           WCHAR  strvalue[4];
2550
2551           /* See if DWORD or REG_SZ */
2552           if (RegQueryValueExW(key, dfltColorW, NULL, &type,
2553                      NULL, NULL) == ERROR_SUCCESS) {
2554               if (type == REG_DWORD) {
2555                   size = sizeof(DWORD);
2556                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2557                                   (LPBYTE)&value, &size);
2558               } else if (type == REG_SZ) {
2559                   size = sizeof(strvalue)/sizeof(WCHAR);
2560                   RegQueryValueExW(key, dfltColorW, NULL, NULL,
2561                                   (LPBYTE)strvalue, &size);
2562                   value = strtoulW(strvalue, NULL, 10);
2563               }
2564           }
2565           RegCloseKey(key);
2566       }
2567
2568       /* If one found, set the screen to that colour */
2569       if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
2570           defaultColor = value & 0xFF;
2571           param1[0] = 0x00;
2572           WCMD_color();
2573       }
2574
2575   }
2576
2577   /* Save cwd into appropriate env var */
2578   GetCurrentDirectoryW(1024, string);
2579   if (IsCharAlphaW(string[0]) && string[1] == ':') {
2580     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2581     wsprintfW(envvar, fmt, string[0]);
2582     SetEnvironmentVariableW(envvar, string);
2583     WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
2584   }
2585
2586   if (opt_k) {
2587       /* Parse the command string, without reading any more input */
2588       WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
2589       WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2590       WCMD_free_commands(toExecute);
2591       toExecute = NULL;
2592       HeapFree(GetProcessHeap(), 0, cmd);
2593   }
2594
2595 /*
2596  *      Loop forever getting commands and executing them.
2597  */
2598
2599   SetEnvironmentVariableW(promptW, defaultpromptW);
2600   WCMD_version ();
2601   while (TRUE) {
2602
2603     /* Read until EOF (which for std input is never, but if redirect
2604        in place, may occur                                          */
2605     if (echo_mode) WCMD_show_prompt();
2606     if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
2607       break;
2608     WCMD_process_commands(toExecute, FALSE, NULL, NULL);
2609     WCMD_free_commands(toExecute);
2610     toExecute = NULL;
2611   }
2612   return 0;
2613 }