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