cmd: cmd.exe /c or /k should suppress copy overwrite prompts.
[wine] / programs / cmd / builtins.c
1 /*
2  * CMD - Wine-compatible command line interface - built-in functions.
3  *
4  * Copyright (C) 1999 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  * - No support for pipes, shell parameters
25  * - Lots of functionality missing from builtins
26  * - Messages etc need international support
27  */
28
29 #define WIN32_LEAN_AND_MEAN
30
31 #include "wcmd.h"
32 #include <shellapi.h>
33 #include "wine/debug.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
36
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
40
41 struct env_stack *pushd_directories;
42 const WCHAR dotW[]    = {'.','\0'};
43 const WCHAR dotdotW[] = {'.','.','\0'};
44 const WCHAR nullW[]   = {'\0'};
45 const WCHAR starW[]   = {'*','\0'};
46 const WCHAR slashW[]  = {'\\','\0'};
47 const WCHAR equalW[]  = {'=','\0'};
48 const WCHAR inbuilt[][10] = {
49         {'C','A','L','L','\0'},
50         {'C','D','\0'},
51         {'C','H','D','I','R','\0'},
52         {'C','L','S','\0'},
53         {'C','O','P','Y','\0'},
54         {'C','T','T','Y','\0'},
55         {'D','A','T','E','\0'},
56         {'D','E','L','\0'},
57         {'D','I','R','\0'},
58         {'E','C','H','O','\0'},
59         {'E','R','A','S','E','\0'},
60         {'F','O','R','\0'},
61         {'G','O','T','O','\0'},
62         {'H','E','L','P','\0'},
63         {'I','F','\0'},
64         {'L','A','B','E','L','\0'},
65         {'M','D','\0'},
66         {'M','K','D','I','R','\0'},
67         {'M','O','V','E','\0'},
68         {'P','A','T','H','\0'},
69         {'P','A','U','S','E','\0'},
70         {'P','R','O','M','P','T','\0'},
71         {'R','E','M','\0'},
72         {'R','E','N','\0'},
73         {'R','E','N','A','M','E','\0'},
74         {'R','D','\0'},
75         {'R','M','D','I','R','\0'},
76         {'S','E','T','\0'},
77         {'S','H','I','F','T','\0'},
78         {'S','T','A','R','T','\0'},
79         {'T','I','M','E','\0'},
80         {'T','I','T','L','E','\0'},
81         {'T','Y','P','E','\0'},
82         {'V','E','R','I','F','Y','\0'},
83         {'V','E','R','\0'},
84         {'V','O','L','\0'},
85         {'E','N','D','L','O','C','A','L','\0'},
86         {'S','E','T','L','O','C','A','L','\0'},
87         {'P','U','S','H','D','\0'},
88         {'P','O','P','D','\0'},
89         {'A','S','S','O','C','\0'},
90         {'C','O','L','O','R','\0'},
91         {'F','T','Y','P','E','\0'},
92         {'M','O','R','E','\0'},
93         {'C','H','O','I','C','E','\0'},
94         {'E','X','I','T','\0'}
95 };
96 static const WCHAR externals[][10] = {
97         {'A','T','T','R','I','B','\0'},
98         {'X','C','O','P','Y','\0'}
99 };
100 static const WCHAR fslashW[] = {'/','\0'};
101 static const WCHAR onW[]  = {'O','N','\0'};
102 static const WCHAR offW[] = {'O','F','F','\0'};
103 static const WCHAR parmY[] = {'/','Y','\0'};
104 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
105
106 static HINSTANCE hinst;
107 struct env_stack *saved_environment;
108 static BOOL verify_mode = FALSE;
109
110 /**************************************************************************
111  * WCMD_ask_confirm
112  *
113  * Issue a message and ask for confirmation, waiting on a valid answer.
114  *
115  * Returns True if Y (or A) answer is selected
116  *         If optionAll contains a pointer, ALL is allowed, and if answered
117  *                   set to TRUE
118  *
119  */
120 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
121                               BOOL *optionAll) {
122
123     UINT msgid;
124     WCHAR confirm[MAXSTRING];
125     WCHAR options[MAXSTRING];
126     WCHAR Ybuffer[MAXSTRING];
127     WCHAR Nbuffer[MAXSTRING];
128     WCHAR Abuffer[MAXSTRING];
129     WCHAR answer[MAX_PATH] = {'\0'};
130     DWORD count = 0;
131
132     /* Load the translated valid answers */
133     if (showSureText)
134       LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
135     msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
136     LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
137     LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
138     LoadStringW(hinst, WCMD_NO,  Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
139     LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
140
141     /* Loop waiting on a valid answer */
142     if (optionAll)
143         *optionAll = FALSE;
144     while (1)
145     {
146       WCMD_output_asis (message);
147       if (showSureText)
148         WCMD_output_asis (confirm);
149       WCMD_output_asis (options);
150       WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
151       answer[0] = toupperW(answer[0]);
152       if (answer[0] == Ybuffer[0])
153         return TRUE;
154       if (answer[0] == Nbuffer[0])
155         return FALSE;
156       if (optionAll && answer[0] == Abuffer[0])
157       {
158         *optionAll = TRUE;
159         return TRUE;
160       }
161     }
162 }
163
164 /****************************************************************************
165  * WCMD_clear_screen
166  *
167  * Clear the terminal screen.
168  */
169
170 void WCMD_clear_screen (void) {
171
172   /* Emulate by filling the screen from the top left to bottom right with
173         spaces, then moving the cursor to the top left afterwards */
174   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
175   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
176
177   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
178   {
179       COORD topLeft;
180       DWORD screenSize;
181
182       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
183
184       topLeft.X = 0;
185       topLeft.Y = 0;
186       FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
187       SetConsoleCursorPosition(hStdOut, topLeft);
188   }
189 }
190
191 /****************************************************************************
192  * WCMD_change_tty
193  *
194  * Change the default i/o device (ie redirect STDin/STDout).
195  */
196
197 void WCMD_change_tty (void) {
198
199   WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
200
201 }
202
203 /****************************************************************************
204  * WCMD_choice
205  *
206  */
207
208 void WCMD_choice (const WCHAR * command) {
209
210     static const WCHAR bellW[] = {7,0};
211     static const WCHAR commaW[] = {',',0};
212     static const WCHAR bracket_open[] = {'[',0};
213     static const WCHAR bracket_close[] = {']','?',0};
214     WCHAR answer[16];
215     WCHAR buffer[16];
216     WCHAR *ptr = NULL;
217     WCHAR *opt_c = NULL;
218     WCHAR *my_command = NULL;
219     WCHAR opt_default = 0;
220     DWORD opt_timeout = 0;
221     DWORD count;
222     DWORD oldmode;
223     DWORD have_console;
224     BOOL opt_n = FALSE;
225     BOOL opt_s = FALSE;
226
227     have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
228     errorlevel = 0;
229
230     my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
231     if (!my_command)
232         return;
233
234     ptr = WCMD_skip_leading_spaces(my_command);
235     while (*ptr == '/') {
236         switch (toupperW(ptr[1])) {
237             case 'C':
238                 ptr += 2;
239                 /* the colon is optional */
240                 if (*ptr == ':')
241                     ptr++;
242
243                 if (!*ptr || isspaceW(*ptr)) {
244                     WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
245                     HeapFree(GetProcessHeap(), 0, my_command);
246                     return;
247                 }
248
249                 /* remember the allowed keys (overwrite previous /C option) */
250                 opt_c = ptr;
251                 while (*ptr && (!isspaceW(*ptr)))
252                     ptr++;
253
254                 if (*ptr) {
255                     /* terminate allowed chars */
256                     *ptr = 0;
257                     ptr = WCMD_skip_leading_spaces(&ptr[1]);
258                 }
259                 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
260                 break;
261
262             case 'N':
263                 opt_n = TRUE;
264                 ptr = WCMD_skip_leading_spaces(&ptr[2]);
265                 break;
266
267             case 'S':
268                 opt_s = TRUE;
269                 ptr = WCMD_skip_leading_spaces(&ptr[2]);
270                 break;
271
272             case 'T':
273                 ptr = &ptr[2];
274                 /* the colon is optional */
275                 if (*ptr == ':')
276                     ptr++;
277
278                 opt_default = *ptr++;
279
280                 if (!opt_default || (*ptr != ',')) {
281                     WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
282                     HeapFree(GetProcessHeap(), 0, my_command);
283                     return;
284                 }
285                 ptr++;
286
287                 count = 0;
288                 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
289                     count++;
290                     ptr++;
291                 }
292
293                 answer[count] = 0;
294                 opt_timeout = atoiW(answer);
295
296                 ptr = WCMD_skip_leading_spaces(ptr);
297                 break;
298
299             default:
300                 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
301                 HeapFree(GetProcessHeap(), 0, my_command);
302                 return;
303         }
304     }
305
306     if (opt_timeout)
307         WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
308
309     if (have_console)
310         SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
311
312     /* use default keys, when needed: localized versions of "Y"es and "No" */
313     if (!opt_c) {
314         LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
315         LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
316         opt_c = buffer;
317         buffer[2] = 0;
318     }
319
320     /* print the question, when needed */
321     if (*ptr)
322         WCMD_output_asis(ptr);
323
324     if (!opt_s) {
325         struprW(opt_c);
326         WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
327     }
328
329     if (!opt_n) {
330         /* print a list of all allowed answers inside brackets */
331         WCMD_output_asis(bracket_open);
332         ptr = opt_c;
333         answer[1] = 0;
334         while ((answer[0] = *ptr++)) {
335             WCMD_output_asis(answer);
336             if (*ptr)
337                 WCMD_output_asis(commaW);
338         }
339         WCMD_output_asis(bracket_close);
340     }
341
342     while (TRUE) {
343
344         /* FIXME: Add support for option /T */
345         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
346
347         if (!opt_s)
348             answer[0] = toupperW(answer[0]);
349
350         ptr = strchrW(opt_c, answer[0]);
351         if (ptr) {
352             WCMD_output_asis(answer);
353             WCMD_output_asis(newlineW);
354             if (have_console)
355                 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
356
357             errorlevel = (ptr - opt_c) + 1;
358             WINE_TRACE("answer: %d\n", errorlevel);
359             HeapFree(GetProcessHeap(), 0, my_command);
360             return;
361         }
362         else
363         {
364             /* key not allowed: play the bell */
365             WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
366             WCMD_output_asis(bellW);
367         }
368     }
369 }
370
371 /****************************************************************************
372  * WCMD_copy
373  *
374  * Copy a file or wildcarded set.
375  * FIXME: Add support for a+b+c type syntax
376  */
377
378 void WCMD_copy (void) {
379
380   WIN32_FIND_DATAW fd;
381   HANDLE hff;
382   BOOL force, status;
383   WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
384   DWORD len;
385   static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
386   BOOL copyToDir = FALSE;
387   WCHAR srcspec[MAX_PATH];
388   DWORD attribs;
389   WCHAR drive[10];
390   WCHAR dir[MAX_PATH];
391   WCHAR fname[MAX_PATH];
392   WCHAR ext[MAX_PATH];
393
394   if (param1[0] == 0x00) {
395     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
396     return;
397   }
398
399   /* Convert source into full spec */
400   WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
401   GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
402   if (srcpath[strlenW(srcpath) - 1] == '\\')
403       srcpath[strlenW(srcpath) - 1] = '\0';
404
405   if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
406     attribs = GetFileAttributesW(srcpath);
407   } else {
408     attribs = 0;
409   }
410   strcpyW(srcspec, srcpath);
411
412   /* If a directory, then add \* on the end when searching */
413   if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
414     strcatW(srcpath, slashW);
415     strcatW(srcspec, slashW);
416     strcatW(srcspec, starW);
417   } else {
418     WCMD_splitpath(srcpath, drive, dir, fname, ext);
419     strcpyW(srcpath, drive);
420     strcatW(srcpath, dir);
421   }
422
423   WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
424
425   /* If no destination supplied, assume current directory */
426   WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
427   if (param2[0] == 0x00) {
428       strcpyW(param2, dotW);
429   }
430
431   GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
432   if (outpath[strlenW(outpath) - 1] == '\\')
433       outpath[strlenW(outpath) - 1] = '\0';
434   attribs = GetFileAttributesW(outpath);
435   if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
436     strcatW (outpath, slashW);
437     copyToDir = TRUE;
438   }
439   WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
440              wine_dbgstr_w(outpath), copyToDir);
441
442   /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
443   if (strstrW (quals, parmNoY))
444     force = FALSE;
445   else if (strstrW (quals, parmY))
446     force = TRUE;
447   else {
448     /* By default, we will force the overwrite in batch mode and ask for
449      * confirmation in interactive mode. */
450     force = !interactive;
451
452     /* If COPYCMD is set, then we force the overwrite with /Y and ask for
453      * confirmation with /-Y. If COPYCMD is neither of those, then we use the
454      * default behavior. */
455     len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
456     if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
457       if (!lstrcmpiW (copycmd, parmY))
458         force = TRUE;
459       else if (!lstrcmpiW (copycmd, parmNoY))
460         force = FALSE;
461     }
462   }
463
464   /* Loop through all source files */
465   WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
466   hff = FindFirstFileW(srcspec, &fd);
467   if (hff != INVALID_HANDLE_VALUE) {
468       do {
469         WCHAR outname[MAX_PATH];
470         WCHAR srcname[MAX_PATH];
471         BOOL  overwrite = force;
472
473         /* Destination is either supplied filename, or source name in
474            supplied destination directory                             */
475         strcpyW(outname, outpath);
476         if (copyToDir) strcatW(outname, fd.cFileName);
477         strcpyW(srcname, srcpath);
478         strcatW(srcname, fd.cFileName);
479
480         WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
481         WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
482
483         /* Skip . and .., and directories */
484         if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
485           overwrite = FALSE;
486           WINE_TRACE("Skipping directories\n");
487         }
488
489         /* Prompt before overwriting */
490         else if (!overwrite) {
491           attribs = GetFileAttributesW(outname);
492           if (attribs != INVALID_FILE_ATTRIBUTES) {
493             WCHAR* question;
494             question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
495             overwrite = WCMD_ask_confirm(question, FALSE, NULL);
496             LocalFree(question);
497           }
498           else overwrite = TRUE;
499         }
500
501         /* Do the copy as appropriate */
502         if (overwrite) {
503           status = CopyFileW(srcname, outname, FALSE);
504           if (!status) WCMD_print_error ();
505         }
506
507       } while (FindNextFileW(hff, &fd) != 0);
508       FindClose (hff);
509   } else {
510       WCMD_print_error ();
511   }
512 }
513
514 /****************************************************************************
515  * WCMD_create_dir
516  *
517  * Create a directory (and, if needed, any intermediate directories).
518  *
519  * Modifies its argument by replacing slashes temporarily with nulls.
520  */
521
522 static BOOL create_full_path(WCHAR* path)
523 {
524     WCHAR *p, *start;
525
526     /* don't mess with drive letter portion of path, if any */
527     start = path;
528     if (path[1] == ':')
529         start = path+2;
530
531     /* Strip trailing slashes. */
532     for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
533         *p = 0;
534
535     /* Step through path, creating intermediate directories as needed. */
536     /* First component includes drive letter, if any. */
537     p = start;
538     for (;;) {
539         DWORD rv;
540         /* Skip to end of component */
541         while (*p == '\\') p++;
542         while (*p && *p != '\\') p++;
543         if (!*p) {
544             /* path is now the original full path */
545             return CreateDirectoryW(path, NULL);
546         }
547         /* Truncate path, create intermediate directory, and restore path */
548         *p = 0;
549         rv = CreateDirectoryW(path, NULL);
550         *p = '\\';
551         if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
552             return FALSE;
553     }
554     /* notreached */
555     return FALSE;
556 }
557
558 void WCMD_create_dir (WCHAR *command) {
559     int   argno = 0;
560     WCHAR *argN = command;
561
562     if (param1[0] == 0x00) {
563         WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
564         return;
565     }
566     /* Loop through all args */
567     while (TRUE) {
568         WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL, FALSE);
569         if (!argN) break;
570         if (!create_full_path(thisArg)) {
571             WCMD_print_error ();
572             errorlevel = 1;
573         }
574     }
575 }
576
577 /* Parse the /A options given by the user on the commandline
578  * into a bitmask of wanted attributes (*wantSet),
579  * and a bitmask of unwanted attributes (*wantClear).
580  */
581 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
582     static const WCHAR parmA[] = {'/','A','\0'};
583     WCHAR *p;
584
585     /* both are strictly 'out' parameters */
586     *wantSet=0;
587     *wantClear=0;
588
589     /* For each /A argument */
590     for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
591         /* Skip /A itself */
592         p += 2;
593
594         /* Skip optional : */
595         if (*p == ':') p++;
596
597         /* For each of the attribute specifier chars to this /A option */
598         for (; *p != 0 && *p != '/'; p++) {
599             BOOL negate = FALSE;
600             DWORD mask  = 0;
601
602             if (*p == '-') {
603                 negate=TRUE;
604                 p++;
605             }
606
607             /* Convert the attribute specifier to a bit in one of the masks */
608             switch (*p) {
609             case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
610             case 'H': mask = FILE_ATTRIBUTE_HIDDEN;   break;
611             case 'S': mask = FILE_ATTRIBUTE_SYSTEM;   break;
612             case 'A': mask = FILE_ATTRIBUTE_ARCHIVE;  break;
613             default:
614                 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
615             }
616             if (negate)
617                 *wantClear |= mask;
618             else
619                 *wantSet |= mask;
620         }
621     }
622 }
623
624 /* If filename part of parameter is * or *.*,
625  * and neither /Q nor /P options were given,
626  * prompt the user whether to proceed.
627  * Returns FALSE if user says no, TRUE otherwise.
628  * *pPrompted is set to TRUE if the user is prompted.
629  * (If /P supplied, del will prompt for individual files later.)
630  */
631 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
632     static const WCHAR parmP[] = {'/','P','\0'};
633     static const WCHAR parmQ[] = {'/','Q','\0'};
634
635     if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
636         static const WCHAR anyExt[]= {'.','*','\0'};
637         WCHAR drive[10];
638         WCHAR dir[MAX_PATH];
639         WCHAR fname[MAX_PATH];
640         WCHAR ext[MAX_PATH];
641         WCHAR fpath[MAX_PATH];
642
643         /* Convert path into actual directory spec */
644         GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
645         WCMD_splitpath(fpath, drive, dir, fname, ext);
646
647         /* Only prompt for * and *.*, not *a, a*, *.a* etc */
648         if ((strcmpW(fname, starW) == 0) &&
649             (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
650
651             WCHAR question[MAXSTRING];
652             static const WCHAR fmt[] = {'%','s',' ','\0'};
653
654             /* Caller uses this to suppress "file not found" warning later */
655             *pPrompted = TRUE;
656
657             /* Ask for confirmation */
658             wsprintfW(question, fmt, fpath);
659             return WCMD_ask_confirm(question, TRUE, NULL);
660         }
661     }
662     /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
663     return TRUE;
664 }
665
666 /* Helper function for WCMD_delete().
667  * Deletes a single file, directory, or wildcard.
668  * If /S was given, does it recursively.
669  * Returns TRUE if a file was deleted.
670  */
671 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
672
673     static const WCHAR parmP[] = {'/','P','\0'};
674     static const WCHAR parmS[] = {'/','S','\0'};
675     static const WCHAR parmF[] = {'/','F','\0'};
676     DWORD wanted_attrs;
677     DWORD unwanted_attrs;
678     BOOL found = FALSE;
679     WCHAR argCopy[MAX_PATH];
680     WIN32_FIND_DATAW fd;
681     HANDLE hff;
682     WCHAR fpath[MAX_PATH];
683     WCHAR *p;
684     BOOL handleParm = TRUE;
685
686     WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
687
688     strcpyW(argCopy, thisArg);
689     WINE_TRACE("del: Processing arg %s (quals:%s)\n",
690                wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
691
692     if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
693         /* Skip this arg if user declines to delete *.* */
694         return FALSE;
695     }
696
697     /* First, try to delete in the current directory */
698     hff = FindFirstFileW(argCopy, &fd);
699     if (hff == INVALID_HANDLE_VALUE) {
700       handleParm = FALSE;
701     } else {
702       found = TRUE;
703     }
704
705     /* Support del <dirname> by just deleting all files dirname\* */
706     if (handleParm
707         && (strchrW(argCopy,'*') == NULL)
708         && (strchrW(argCopy,'?') == NULL)
709         && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
710     {
711       WCHAR modifiedParm[MAX_PATH];
712       static const WCHAR slashStar[] = {'\\','*','\0'};
713
714       strcpyW(modifiedParm, argCopy);
715       strcatW(modifiedParm, slashStar);
716       FindClose(hff);
717       found = TRUE;
718       WCMD_delete_one(modifiedParm);
719
720     } else if (handleParm) {
721
722       /* Build the filename to delete as <supplied directory>\<findfirst filename> */
723       strcpyW (fpath, argCopy);
724       do {
725         p = strrchrW (fpath, '\\');
726         if (p != NULL) {
727           *++p = '\0';
728           strcatW (fpath, fd.cFileName);
729         }
730         else strcpyW (fpath, fd.cFileName);
731         if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
732           BOOL ok;
733
734           /* Handle attribute matching (/A) */
735           ok =  ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
736              && ((fd.dwFileAttributes & unwanted_attrs) == 0);
737
738           /* /P means prompt for each file */
739           if (ok && strstrW (quals, parmP) != NULL) {
740             WCHAR* question;
741
742             /* Ask for confirmation */
743             question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
744             ok = WCMD_ask_confirm(question, FALSE, NULL);
745             LocalFree(question);
746           }
747
748           /* Only proceed if ok to */
749           if (ok) {
750
751             /* If file is read only, and /A:r or /F supplied, delete it */
752             if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
753                 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
754                 strstrW (quals, parmF) != NULL)) {
755                 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
756             }
757
758             /* Now do the delete */
759             if (!DeleteFileW(fpath)) WCMD_print_error ();
760           }
761
762         }
763       } while (FindNextFileW(hff, &fd) != 0);
764       FindClose (hff);
765     }
766
767     /* Now recurse into all subdirectories handling the parameter in the same way */
768     if (strstrW (quals, parmS) != NULL) {
769
770       WCHAR thisDir[MAX_PATH];
771       int cPos;
772
773       WCHAR drive[10];
774       WCHAR dir[MAX_PATH];
775       WCHAR fname[MAX_PATH];
776       WCHAR ext[MAX_PATH];
777
778       /* Convert path into actual directory spec */
779       GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
780       WCMD_splitpath(thisDir, drive, dir, fname, ext);
781
782       strcpyW(thisDir, drive);
783       strcatW(thisDir, dir);
784       cPos = strlenW(thisDir);
785
786       WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
787
788       /* Append '*' to the directory */
789       thisDir[cPos] = '*';
790       thisDir[cPos+1] = 0x00;
791
792       hff = FindFirstFileW(thisDir, &fd);
793
794       /* Remove residual '*' */
795       thisDir[cPos] = 0x00;
796
797       if (hff != INVALID_HANDLE_VALUE) {
798         DIRECTORY_STACK *allDirs = NULL;
799         DIRECTORY_STACK *lastEntry = NULL;
800
801         do {
802           if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
803               (strcmpW(fd.cFileName, dotdotW) != 0) &&
804               (strcmpW(fd.cFileName, dotW) != 0)) {
805
806             DIRECTORY_STACK *nextDir;
807             WCHAR subParm[MAX_PATH];
808
809             /* Work out search parameter in sub dir */
810             strcpyW (subParm, thisDir);
811             strcatW (subParm, fd.cFileName);
812             strcatW (subParm, slashW);
813             strcatW (subParm, fname);
814             strcatW (subParm, ext);
815             WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
816
817             /* Allocate memory, add to list */
818             nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
819             if (allDirs == NULL) allDirs = nextDir;
820             if (lastEntry != NULL) lastEntry->next = nextDir;
821             lastEntry = nextDir;
822             nextDir->next = NULL;
823             nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
824          (strlenW(subParm)+1) * sizeof(WCHAR));
825             strcpyW(nextDir->dirName, subParm);
826           }
827         } while (FindNextFileW(hff, &fd) != 0);
828         FindClose (hff);
829
830         /* Go through each subdir doing the delete */
831         while (allDirs != NULL) {
832           DIRECTORY_STACK *tempDir;
833
834           tempDir = allDirs->next;
835           found |= WCMD_delete_one (allDirs->dirName);
836
837           HeapFree(GetProcessHeap(),0,allDirs->dirName);
838           HeapFree(GetProcessHeap(),0,allDirs);
839           allDirs = tempDir;
840         }
841       }
842     }
843
844     return found;
845 }
846
847 /****************************************************************************
848  * WCMD_delete
849  *
850  * Delete a file or wildcarded set.
851  *
852  * Note on /A:
853  *  - Testing shows /A is repeatable, eg. /a-r /ar matches all files
854  *  - Each set is a pattern, eg /ahr /as-r means
855  *         readonly+hidden OR nonreadonly system files
856  *  - The '-' applies to a single field, ie /a:-hr means read only
857  *         non-hidden files
858  */
859
860 BOOL WCMD_delete (WCHAR *command) {
861     int   argno;
862     WCHAR *argN;
863     BOOL  argsProcessed = FALSE;
864     BOOL  foundAny      = FALSE;
865
866     errorlevel = 0;
867
868     for (argno=0; ; argno++) {
869         BOOL found;
870         WCHAR *thisArg;
871
872         argN = NULL;
873         thisArg = WCMD_parameter (command, argno, &argN, NULL, FALSE);
874         if (!argN)
875             break;       /* no more parameters */
876         if (argN[0] == '/')
877             continue;    /* skip options */
878
879         argsProcessed = TRUE;
880         found = WCMD_delete_one(thisArg);
881         if (!found) {
882             errorlevel = 1;
883             WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
884         }
885         foundAny |= found;
886     }
887
888     /* Handle no valid args */
889     if (!argsProcessed)
890         WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
891
892     return foundAny;
893 }
894
895 /*
896  * WCMD_strtrim
897  *
898  * Returns a trimmed version of s with all leading and trailing whitespace removed
899  * Pre: s non NULL
900  *
901  */
902 static WCHAR *WCMD_strtrim(const WCHAR *s)
903 {
904     DWORD len = strlenW(s);
905     const WCHAR *start = s;
906     WCHAR* result;
907
908     if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
909         return NULL;
910
911     while (isspaceW(*start)) start++;
912     if (*start) {
913         const WCHAR *end = s + len - 1;
914         while (end > start && isspaceW(*end)) end--;
915         memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
916         result[end - start + 1] = '\0';
917     } else {
918         result[0] = '\0';
919     }
920
921     return result;
922 }
923
924 /****************************************************************************
925  * WCMD_echo
926  *
927  * Echo input to the screen (or not). We don't try to emulate the bugs
928  * in DOS (try typing "ECHO ON AGAIN" for an example).
929  */
930
931 void WCMD_echo (const WCHAR *command)
932 {
933   int count;
934   const WCHAR *origcommand = command;
935   WCHAR *trimmed;
936
937   if (   command[0]==' ' || command[0]=='\t' || command[0]=='.'
938       || command[0]==':' || command[0]==';')
939     command++;
940
941   trimmed = WCMD_strtrim(command);
942   if (!trimmed) return;
943
944   count = strlenW(trimmed);
945   if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
946                  && origcommand[0]!=';') {
947     if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
948     else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
949     return;
950   }
951
952   if (lstrcmpiW(trimmed, onW) == 0)
953     echo_mode = TRUE;
954   else if (lstrcmpiW(trimmed, offW) == 0)
955     echo_mode = FALSE;
956   else {
957     WCMD_output_asis (command);
958     WCMD_output_asis (newlineW);
959   }
960   HeapFree(GetProcessHeap(), 0, trimmed);
961 }
962
963 /*****************************************************************************
964  * WCMD_part_execute
965  *
966  * Execute a command, and any && or bracketed follow on to the command. The
967  * first command to be executed may not be at the front of the
968  * commands->thiscommand string (eg. it may point after a DO or ELSE)
969  */
970 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
971                               const WCHAR *variable, const WCHAR *value,
972                               BOOL isIF, BOOL executecmds)
973 {
974   CMD_LIST *curPosition = *cmdList;
975   int myDepth = (*cmdList)->bracketDepth;
976
977   WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
978              cmdList, wine_dbgstr_w(firstcmd),
979              wine_dbgstr_w(variable), wine_dbgstr_w(value),
980              executecmds);
981
982   /* Skip leading whitespace between condition and the command */
983   while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
984
985   /* Process the first command, if there is one */
986   if (executecmds && firstcmd && *firstcmd) {
987     WCHAR *command = WCMD_strdupW(firstcmd);
988     WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
989     HeapFree(GetProcessHeap(), 0, command);
990   }
991
992
993   /* If it didn't move the position, step to next command */
994   if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
995
996   /* Process any other parts of the command */
997   if (*cmdList) {
998     BOOL processThese = executecmds;
999
1000     while (*cmdList) {
1001       static const WCHAR ifElse[] = {'e','l','s','e'};
1002
1003       /* execute all appropriate commands */
1004       curPosition = *cmdList;
1005
1006       WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1007                  *cmdList,
1008                  (*cmdList)->prevDelim,
1009                  (*cmdList)->bracketDepth, myDepth);
1010
1011       /* Execute any statements appended to the line */
1012       /* FIXME: Only if previous call worked for && or failed for || */
1013       if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1014           (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1015         if (processThese && (*cmdList)->command) {
1016           WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1017                         value, cmdList);
1018         }
1019         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1020
1021       /* Execute any appended to the statement with (...) */
1022       } else if ((*cmdList)->bracketDepth > myDepth) {
1023         if (processThese) {
1024           *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1025           WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1026         }
1027         if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1028
1029       /* End of the command - does 'ELSE ' follow as the next command? */
1030       } else {
1031         if (isIF
1032             && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1033                                      (*cmdList)->command)) {
1034
1035           /* Swap between if and else processing */
1036           processThese = !processThese;
1037
1038           /* Process the ELSE part */
1039           if (processThese) {
1040             const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1041             WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1042
1043             /* Skip leading whitespace between condition and the command */
1044             while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1045             if (*cmd) {
1046               WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1047             }
1048           }
1049           if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1050         } else {
1051           WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1052           break;
1053         }
1054       }
1055     }
1056   }
1057   return;
1058 }
1059
1060 /**************************************************************************
1061  * WCMD_for
1062  *
1063  * Batch file loop processing.
1064  *
1065  * On entry: cmdList       contains the syntax up to the set
1066  *           next cmdList and all in that bracket contain the set data
1067  *           next cmdlist  contains the DO cmd
1068  *           following that is either brackets or && entries (as per if)
1069  *
1070  */
1071
1072 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1073
1074   WIN32_FIND_DATAW fd;
1075   HANDLE hff;
1076   int i;
1077   static const WCHAR inW[] = {'i','n'};
1078   static const WCHAR doW[] = {'d','o'};
1079   CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1080   WCHAR variable[4];
1081   WCHAR *firstCmd;
1082   int thisDepth;
1083   WCHAR optionsRoot[MAX_PATH];
1084   DIRECTORY_STACK *dirsToWalk = NULL;
1085
1086   BOOL   expandDirs  = FALSE;
1087   BOOL   useNumbers  = FALSE;
1088   BOOL   doFileset   = FALSE;
1089   BOOL   doRecurse   = FALSE;
1090   BOOL   doExecuted  = FALSE;  /* Has the 'do' part been executed */
1091   LONG   numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1092   int    itemNum;
1093   CMD_LIST *thisCmdStart;
1094   int    parameterNo = 0;
1095
1096   /* Handle optional qualifiers (multiple are allowed) */
1097   WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1098
1099   optionsRoot[0] = 0;
1100   while (thisArg && *thisArg == '/') {
1101       WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
1102       thisArg++;
1103       switch (toupperW(*thisArg)) {
1104       case 'D': expandDirs = TRUE; break;
1105       case 'L': useNumbers = TRUE; break;
1106
1107       /* Recursive is special case - /R can have an optional path following it                */
1108       /* filenamesets are another special case - /F can have an optional options following it */
1109       case 'R':
1110       case 'F':
1111           {
1112               /* When recursing directories, use current directory as the starting point unless
1113                  subsequently overridden */
1114               doRecurse = (toupperW(*thisArg) == 'R');
1115               if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
1116
1117               doFileset = (toupperW(*thisArg) == 'F');
1118
1119               /* Retrieve next parameter to see if is root/options (raw form required
1120                  with for /f, or unquoted in for /r)                                  */
1121               thisArg = WCMD_parameter(p, parameterNo, NULL, NULL, doFileset);
1122
1123               /* Next parm is either qualifier, path/options or variable -
1124                  only care about it if it is the path/options              */
1125               if (thisArg && *thisArg != '/' && *thisArg != '%') {
1126                   parameterNo++;
1127                   strcpyW(optionsRoot, thisArg);
1128                   if (!doRecurse) {
1129                       static unsigned int once;
1130                       if (!once++) WINE_FIXME("/F needs to handle options\n");
1131                   }
1132               }
1133               break;
1134           }
1135       default:
1136           WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
1137       }
1138
1139       /* Step to next token */
1140       thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1141   }
1142
1143   /* Ensure line continues with variable */
1144   if (!*thisArg || *thisArg != '%') {
1145       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1146       return;
1147   }
1148
1149   /* Set up the list of directories to recurse if we are going to */
1150   if (doRecurse) {
1151        /* Allocate memory, add to list */
1152        dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1153        dirsToWalk->next = NULL;
1154        dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
1155                                        (strlenW(optionsRoot) + 1) * sizeof(WCHAR));
1156        strcpyW(dirsToWalk->dirName, optionsRoot);
1157        WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
1158   }
1159
1160   /* Variable should follow */
1161   strcpyW(variable, thisArg);
1162   WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1163
1164   /* Ensure line continues with IN */
1165   thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1166   if (!thisArg
1167        || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1168                            thisArg, sizeof(inW)/sizeof(inW[0]), inW,
1169                            sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
1170       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1171       return;
1172   }
1173
1174   /* Save away where the set of data starts and the variable */
1175   thisDepth = (*cmdList)->bracketDepth;
1176   *cmdList = (*cmdList)->nextcommand;
1177   setStart = (*cmdList);
1178
1179   /* Skip until the close bracket */
1180   WINE_TRACE("Searching %p as the set\n", *cmdList);
1181   while (*cmdList &&
1182          (*cmdList)->command != NULL &&
1183          (*cmdList)->bracketDepth > thisDepth) {
1184     WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1185     *cmdList = (*cmdList)->nextcommand;
1186   }
1187
1188   /* Skip the close bracket, if there is one */
1189   if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1190
1191   /* Syntax error if missing close bracket, or nothing following it
1192      and once we have the complete set, we expect a DO              */
1193   WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1194   if ((*cmdList == NULL)
1195       || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1196
1197       WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1198       return;
1199   }
1200
1201   cmdEnd   = *cmdList;
1202
1203   /* Loop repeatedly per-directory we are potentially walking, when in for /r
1204      mode, or once for the rest of the time.                                  */
1205   do {
1206     WCHAR fullitem[MAX_PATH];
1207     static const WCHAR slashstarW[] = {'\\','*','\0'};
1208
1209     /* Save away the starting position for the commands (and offset for the
1210        first one)                                                           */
1211     cmdStart = *cmdList;
1212     firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1213     itemNum  = 0;
1214
1215     /* If we are recursing directories (ie /R), add all sub directories now, then
1216        prefix the root when searching for the item */
1217     if (dirsToWalk) {
1218       DIRECTORY_STACK *remainingDirs = dirsToWalk;
1219
1220       /* Build a generic search and add all directories on the list of directories
1221          still to walk                                                             */
1222       strcpyW(fullitem, dirsToWalk->dirName);
1223       strcatW(fullitem, slashstarW);
1224       hff = FindFirstFileW(fullitem, &fd);
1225       if (hff != INVALID_HANDLE_VALUE) {
1226         do {
1227           WINE_TRACE("Looking for subdirectories\n");
1228           if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1229               (strcmpW(fd.cFileName, dotdotW) != 0) &&
1230               (strcmpW(fd.cFileName, dotW) != 0))
1231           {
1232             /* Allocate memory, add to list */
1233             DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1234             WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1235             toWalk->next = remainingDirs->next;
1236             remainingDirs->next = toWalk;
1237             remainingDirs = toWalk;
1238             toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
1239                                         sizeof(WCHAR) *
1240                                         (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1241             strcpyW(toWalk->dirName, dirsToWalk->dirName);
1242             strcatW(toWalk->dirName, slashW);
1243             strcatW(toWalk->dirName, fd.cFileName);
1244             WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1245                        toWalk, toWalk->next);
1246           }
1247         } while (FindNextFileW(hff, &fd) != 0);
1248         WINE_TRACE("Finished adding all subdirectories\n");
1249         FindClose (hff);
1250       }
1251     }
1252
1253     thisSet = setStart;
1254     /* Loop through all set entries */
1255     while (thisSet &&
1256            thisSet->command != NULL &&
1257            thisSet->bracketDepth >= thisDepth) {
1258
1259       /* Loop through all entries on the same line */
1260       WCHAR *item;
1261       WCHAR *itemStart;
1262
1263       WINE_TRACE("Processing for set %p\n", thisSet);
1264       i = 0;
1265       while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL, TRUE))) {
1266
1267         /*
1268          * If the parameter within the set has a wildcard then search for matching files
1269          * otherwise do a literal substitution.
1270          */
1271         static const WCHAR wildcards[] = {'*','?','\0'};
1272         thisCmdStart = cmdStart;
1273
1274         itemNum++;
1275         WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1276
1277         if (!useNumbers && !doFileset) {
1278             WCHAR fullitem[MAX_PATH];
1279
1280             /* Now build the item to use / search for in the specified directory,
1281                as it is fully qualified in the /R case */
1282             if (dirsToWalk) {
1283               strcpyW(fullitem, dirsToWalk->dirName);
1284               strcatW(fullitem, slashW);
1285               strcatW(fullitem, item);
1286             } else {
1287               strcpyW(fullitem, item);
1288             }
1289
1290             if (strpbrkW (fullitem, wildcards)) {
1291
1292               hff = FindFirstFileW(fullitem, &fd);
1293               if (hff != INVALID_HANDLE_VALUE) {
1294                 do {
1295                   BOOL isDirectory = FALSE;
1296
1297                   if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1298
1299                   /* Handle as files or dirs appropriately, but ignore . and .. */
1300                   if (isDirectory == expandDirs &&
1301                       (strcmpW(fd.cFileName, dotdotW) != 0) &&
1302                       (strcmpW(fd.cFileName, dotW) != 0))
1303                   {
1304                       thisCmdStart = cmdStart;
1305                       WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1306
1307                       if (doRecurse) {
1308                           strcpyW(fullitem, dirsToWalk->dirName);
1309                           strcatW(fullitem, slashW);
1310                           strcatW(fullitem, fd.cFileName);
1311                       } else {
1312                           strcpyW(fullitem, fd.cFileName);
1313                       }
1314                       doExecuted = TRUE;
1315                       WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1316                                                    fullitem, FALSE, TRUE);
1317                       cmdEnd = thisCmdStart;
1318                   }
1319                 } while (FindNextFileW(hff, &fd) != 0);
1320                 FindClose (hff);
1321               }
1322             } else {
1323               doExecuted = TRUE;
1324               WCMD_part_execute(&thisCmdStart, firstCmd, variable, fullitem, FALSE, TRUE);
1325               cmdEnd = thisCmdStart;
1326             }
1327
1328         } else if (useNumbers) {
1329             /* Convert the first 3 numbers to signed longs and save */
1330             if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1331             /* else ignore them! */
1332
1333         /* Filesets - either a list of files, or a command to run and parse the output */
1334         } else if (doFileset && *itemStart != '"') {
1335
1336             HANDLE input;
1337             WCHAR temp_file[MAX_PATH];
1338
1339             WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1340                        wine_dbgstr_w(item));
1341
1342             /* If backquote or single quote, we need to launch that command
1343                and parse the results - use a temporary file                 */
1344             if (*itemStart == '`' || *itemStart == '\'') {
1345
1346                 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1347                 static const WCHAR redirOut[] = {'>','%','s','\0'};
1348                 static const WCHAR cmdW[]     = {'C','M','D','\0'};
1349
1350                 /* Remove trailing character */
1351                 itemStart[strlenW(itemStart)-1] = 0x00;
1352
1353                 /* Get temp filename */
1354                 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1355                 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1356
1357                 /* Execute program and redirect output */
1358                 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1359                 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1360
1361                 /* Open the file, read line by line and process */
1362                 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1363                                     NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1364             } else {
1365
1366                 /* Open the file, read line by line and process */
1367                 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1368                                     NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1369             }
1370
1371             /* Process the input file */
1372             if (input == INVALID_HANDLE_VALUE) {
1373               WCMD_print_error ();
1374               WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1375               errorlevel = 1;
1376               return; /* FOR loop aborts at first failure here */
1377
1378             } else {
1379
1380               WCHAR buffer[MAXSTRING];
1381               WCHAR *where, *parm;
1382
1383               while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1384
1385                 /* Skip blank lines*/
1386                 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1387                 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1388                            wine_dbgstr_w(buffer));
1389
1390                 if (where) {
1391                     /* FIXME: The following should be moved into its own routine and
1392                        reused for the string literal parsing below                  */
1393                     thisCmdStart = cmdStart;
1394                     doExecuted = TRUE;
1395                     WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1396                     cmdEnd = thisCmdStart;
1397                 }
1398
1399                 buffer[0] = 0;
1400
1401               }
1402               CloseHandle (input);
1403             }
1404
1405             /* Delete the temporary file */
1406             if (*itemStart == '`' || *itemStart == '\'') {
1407                 DeleteFileW(temp_file);
1408             }
1409
1410         /* Filesets - A string literal */
1411         } else if (doFileset && *itemStart == '"') {
1412             WCHAR buffer[MAXSTRING];
1413             WCHAR *where, *parm;
1414
1415             /* Skip blank lines, and re-extract parameter now string has quotes removed */
1416             strcpyW(buffer, item);
1417             parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1418             WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1419                          wine_dbgstr_w(buffer));
1420
1421             if (where) {
1422                 /* FIXME: The following should be moved into its own routine and
1423                    reused for the string literal parsing below                  */
1424                 thisCmdStart = cmdStart;
1425                 doExecuted = TRUE;
1426                 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1427                 cmdEnd = thisCmdStart;
1428             }
1429         }
1430
1431         WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1432         i++;
1433       }
1434
1435       /* Move onto the next set line */
1436       thisSet = thisSet->nextcommand;
1437     }
1438
1439     /* If /L is provided, now run the for loop */
1440     if (useNumbers) {
1441         WCHAR thisNum[20];
1442         static const WCHAR fmt[] = {'%','d','\0'};
1443
1444         WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1445                    numbers[0], numbers[2], numbers[1]);
1446         for (i=numbers[0];
1447              (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1448              i=i + numbers[1]) {
1449
1450             sprintfW(thisNum, fmt, i);
1451             WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1452
1453             thisCmdStart = cmdStart;
1454             doExecuted = TRUE;
1455             WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1456         }
1457         cmdEnd = thisCmdStart;
1458     }
1459
1460     /* If we are walking directories, move on to any which remain */
1461     if (dirsToWalk != NULL) {
1462       DIRECTORY_STACK *nextDir = dirsToWalk->next;
1463       HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
1464       HeapFree(GetProcessHeap(), 0, dirsToWalk);
1465       dirsToWalk = nextDir;
1466       if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
1467                                  wine_dbgstr_w(dirsToWalk->dirName));
1468       else WINE_TRACE("Finished all directories.\n");
1469     }
1470
1471   } while (dirsToWalk != NULL);
1472
1473   /* Now skip over the do part if we did not perform the for loop so far.
1474      We store in cmdEnd the next command after the do block, but we only
1475      know this if something was run. If it has not been, we need to calculate
1476      it.                                                                      */
1477   if (!doExecuted) {
1478     thisCmdStart = cmdStart;
1479     WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
1480     WCMD_part_execute(&thisCmdStart, firstCmd, NULL, NULL, FALSE, FALSE);
1481     cmdEnd = thisCmdStart;
1482   }
1483
1484   /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1485      all processing, OR it should be pointing to the end of && processing OR
1486      it should be pointing at the NULL end of bracket for the DO. The return
1487      value needs to be the NEXT command to execute, which it either is, or
1488      we need to step over the closing bracket                                  */
1489   *cmdList = cmdEnd;
1490   if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1491 }
1492
1493 /**************************************************************************
1494  * WCMD_give_help
1495  *
1496  *      Simple on-line help. Help text is stored in the resource file.
1497  */
1498
1499 void WCMD_give_help (const WCHAR *command)
1500 {
1501   size_t i;
1502
1503   command = WCMD_skip_leading_spaces((WCHAR*) command);
1504   if (strlenW(command) == 0) {
1505     WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1506   }
1507   else {
1508     /* Display help message for builtin commands */
1509     for (i=0; i<=WCMD_EXIT; i++) {
1510       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1511           command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1512         WCMD_output_asis (WCMD_LoadMessage(i));
1513         return;
1514       }
1515     }
1516     /* Launch the command with the /? option for external commands shipped with cmd.exe */
1517     for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1518       if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1519           command, -1, externals[i], -1) == CSTR_EQUAL) {
1520         WCHAR cmd[128];
1521         static const WCHAR helpW[] = {' ', '/','?','\0'};
1522         strcpyW(cmd, command);
1523         strcatW(cmd, helpW);
1524         WCMD_run_program(cmd, FALSE);
1525         return;
1526       }
1527     }
1528     WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1529   }
1530   return;
1531 }
1532
1533 /****************************************************************************
1534  * WCMD_go_to
1535  *
1536  * Batch file jump instruction. Not the most efficient algorithm ;-)
1537  * Prints error message if the specified label cannot be found - the file pointer is
1538  * then at EOF, effectively stopping the batch file.
1539  * FIXME: DOS is supposed to allow labels with spaces - we don't.
1540  */
1541
1542 void WCMD_goto (CMD_LIST **cmdList) {
1543
1544   WCHAR string[MAX_PATH];
1545   WCHAR current[MAX_PATH];
1546
1547   /* Do not process any more parts of a processed multipart or multilines command */
1548   if (cmdList) *cmdList = NULL;
1549
1550   if (context != NULL) {
1551     WCHAR *paramStart = param1, *str;
1552     static const WCHAR eofW[] = {':','e','o','f','\0'};
1553
1554     if (param1[0] == 0x00) {
1555       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1556       return;
1557     }
1558
1559     /* Handle special :EOF label */
1560     if (lstrcmpiW (eofW, param1) == 0) {
1561       context -> skip_rest = TRUE;
1562       return;
1563     }
1564
1565     /* Support goto :label as well as goto label */
1566     if (*paramStart == ':') paramStart++;
1567
1568     SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1569     while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1570       str = string;
1571       while (isspaceW (*str)) str++;
1572       if (*str == ':') {
1573         DWORD index = 0;
1574         str++;
1575         while (((current[index] = str[index])) && (!isspaceW (current[index])))
1576             index++;
1577
1578         /* ignore space at the end */
1579         current[index] = 0;
1580         if (lstrcmpiW (current, paramStart) == 0) return;
1581       }
1582     }
1583     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1584   }
1585   return;
1586 }
1587
1588 /*****************************************************************************
1589  * WCMD_pushd
1590  *
1591  *      Push a directory onto the stack
1592  */
1593
1594 void WCMD_pushd (const WCHAR *command)
1595 {
1596     struct env_stack *curdir;
1597     WCHAR *thisdir;
1598     static const WCHAR parmD[] = {'/','D','\0'};
1599
1600     if (strchrW(command, '/') != NULL) {
1601       SetLastError(ERROR_INVALID_PARAMETER);
1602       WCMD_print_error();
1603       return;
1604     }
1605
1606     curdir  = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1607     thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1608     if( !curdir || !thisdir ) {
1609       LocalFree(curdir);
1610       LocalFree(thisdir);
1611       WINE_ERR ("out of memory\n");
1612       return;
1613     }
1614
1615     /* Change directory using CD code with /D parameter */
1616     strcpyW(quals, parmD);
1617     GetCurrentDirectoryW (1024, thisdir);
1618     errorlevel = 0;
1619     WCMD_setshow_default(command);
1620     if (errorlevel) {
1621       LocalFree(curdir);
1622       LocalFree(thisdir);
1623       return;
1624     } else {
1625       curdir -> next    = pushd_directories;
1626       curdir -> strings = thisdir;
1627       if (pushd_directories == NULL) {
1628         curdir -> u.stackdepth = 1;
1629       } else {
1630         curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1631       }
1632       pushd_directories = curdir;
1633     }
1634 }
1635
1636
1637 /*****************************************************************************
1638  * WCMD_popd
1639  *
1640  *      Pop a directory from the stack
1641  */
1642
1643 void WCMD_popd (void) {
1644     struct env_stack *temp = pushd_directories;
1645
1646     if (!pushd_directories)
1647       return;
1648
1649     /* pop the old environment from the stack, and make it the current dir */
1650     pushd_directories = temp->next;
1651     SetCurrentDirectoryW(temp->strings);
1652     LocalFree (temp->strings);
1653     LocalFree (temp);
1654 }
1655
1656 /****************************************************************************
1657  * WCMD_if
1658  *
1659  * Batch file conditional.
1660  *
1661  * On entry, cmdlist will point to command containing the IF, and optionally
1662  *   the first command to execute (if brackets not found)
1663  *   If &&'s were found, this may be followed by a record flagged as isAmpersand
1664  *   If ('s were found, execute all within that bracket
1665  *   Command may optionally be followed by an ELSE - need to skip instructions
1666  *   in the else using the same logic
1667  *
1668  * FIXME: Much more syntax checking needed!
1669  */
1670
1671 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1672
1673   int negate; /* Negate condition */
1674   int test;   /* Condition evaluation result */
1675   WCHAR condition[MAX_PATH], *command, *s;
1676   static const WCHAR notW[]    = {'n','o','t','\0'};
1677   static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1678   static const WCHAR existW[]  = {'e','x','i','s','t','\0'};
1679   static const WCHAR defdW[]   = {'d','e','f','i','n','e','d','\0'};
1680   static const WCHAR eqeqW[]   = {'=','=','\0'};
1681   static const WCHAR parmI[]   = {'/','I','\0'};
1682   int caseInsensitive = (strstrW(quals, parmI) != NULL);
1683
1684   negate = !lstrcmpiW(param1,notW);
1685   strcpyW(condition, (negate ? param2 : param1));
1686   WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1687
1688   if (!lstrcmpiW (condition, errlvlW)) {
1689     WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL, FALSE);
1690     WCHAR *endptr;
1691     long int param_int = strtolW(param, &endptr, 10);
1692     if (*endptr) {
1693       WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1694       return;
1695     }
1696     test = ((long int)errorlevel >= param_int);
1697     WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1698   }
1699   else if (!lstrcmpiW (condition, existW)) {
1700     test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE))
1701              != INVALID_FILE_ATTRIBUTES);
1702     WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1703   }
1704   else if (!lstrcmpiW (condition, defdW)) {
1705     test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE),
1706                                     NULL, 0) > 0);
1707     WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1708   }
1709   else if ((s = strstrW (p, eqeqW))) {
1710     /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1711     WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1712     s += 2;
1713     WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd, FALSE);
1714     WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd, FALSE);
1715     test = caseInsensitive
1716             ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1717                               leftPart, leftPartEnd-leftPart+1,
1718                               rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1719             : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1720                               leftPart, leftPartEnd-leftPart+1,
1721                               rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1722     WCMD_parameter(s, 1, &command, NULL, FALSE);
1723   }
1724   else {
1725     WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1726     return;
1727   }
1728
1729   /* Process rest of IF statement which is on the same line
1730      Note: This may process all or some of the cmdList (eg a GOTO) */
1731   WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1732 }
1733
1734 /****************************************************************************
1735  * WCMD_move
1736  *
1737  * Move a file, directory tree or wildcarded set of files.
1738  */
1739
1740 void WCMD_move (void)
1741 {
1742   int             status;
1743   WIN32_FIND_DATAW fd;
1744   HANDLE          hff;
1745   WCHAR            input[MAX_PATH];
1746   WCHAR            output[MAX_PATH];
1747   WCHAR            drive[10];
1748   WCHAR            dir[MAX_PATH];
1749   WCHAR            fname[MAX_PATH];
1750   WCHAR            ext[MAX_PATH];
1751
1752   if (param1[0] == 0x00) {
1753     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1754     return;
1755   }
1756
1757   /* If no destination supplied, assume current directory */
1758   if (param2[0] == 0x00) {
1759       strcpyW(param2, dotW);
1760   }
1761
1762   /* If 2nd parm is directory, then use original filename */
1763   /* Convert partial path to full path */
1764   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1765   GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1766   WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1767              wine_dbgstr_w(param1), wine_dbgstr_w(output));
1768
1769   /* Split into components */
1770   WCMD_splitpath(input, drive, dir, fname, ext);
1771
1772   hff = FindFirstFileW(input, &fd);
1773   if (hff == INVALID_HANDLE_VALUE)
1774     return;
1775
1776   do {
1777     WCHAR  dest[MAX_PATH];
1778     WCHAR  src[MAX_PATH];
1779     DWORD attribs;
1780     BOOL ok = TRUE;
1781
1782     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1783
1784     /* Build src & dest name */
1785     strcpyW(src, drive);
1786     strcatW(src, dir);
1787
1788     /* See if dest is an existing directory */
1789     attribs = GetFileAttributesW(output);
1790     if (attribs != INVALID_FILE_ATTRIBUTES &&
1791        (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1792       strcpyW(dest, output);
1793       strcatW(dest, slashW);
1794       strcatW(dest, fd.cFileName);
1795     } else {
1796       strcpyW(dest, output);
1797     }
1798
1799     strcatW(src, fd.cFileName);
1800
1801     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1802     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
1803
1804     /* If destination exists, prompt unless /Y supplied */
1805     if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1806       BOOL force = FALSE;
1807       WCHAR copycmd[MAXSTRING];
1808       DWORD len;
1809
1810       /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1811       if (strstrW (quals, parmNoY))
1812         force = FALSE;
1813       else if (strstrW (quals, parmY))
1814         force = TRUE;
1815       else {
1816         static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1817         len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1818         force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1819                      && ! lstrcmpiW (copycmd, parmY));
1820       }
1821
1822       /* Prompt if overwriting */
1823       if (!force) {
1824         WCHAR* question;
1825
1826         /* Ask for confirmation */
1827         question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1828         ok = WCMD_ask_confirm(question, FALSE, NULL);
1829         LocalFree(question);
1830
1831         /* So delete the destination prior to the move */
1832         if (ok) {
1833           if (!DeleteFileW(dest)) {
1834             WCMD_print_error ();
1835             errorlevel = 1;
1836             ok = FALSE;
1837           }
1838         }
1839       }
1840     }
1841
1842     if (ok) {
1843       status = MoveFileW(src, dest);
1844     } else {
1845       status = 1; /* Anything other than 0 to prevent error msg below */
1846     }
1847
1848     if (!status) {
1849       WCMD_print_error ();
1850       errorlevel = 1;
1851     }
1852   } while (FindNextFileW(hff, &fd) != 0);
1853
1854   FindClose(hff);
1855 }
1856
1857 /****************************************************************************
1858  * WCMD_pause
1859  *
1860  * Suspend execution of a batch script until a key is typed
1861  */
1862
1863 void WCMD_pause (void)
1864 {
1865   DWORD oldmode;
1866   BOOL have_console;
1867   DWORD count;
1868   WCHAR key;
1869   HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1870
1871   have_console = GetConsoleMode(hIn, &oldmode);
1872   if (have_console)
1873       SetConsoleMode(hIn, 0);
1874
1875   WCMD_output_asis(anykey);
1876   WCMD_ReadFile(hIn, &key, 1, &count);
1877   if (have_console)
1878     SetConsoleMode(hIn, oldmode);
1879 }
1880
1881 /****************************************************************************
1882  * WCMD_remove_dir
1883  *
1884  * Delete a directory.
1885  */
1886
1887 void WCMD_remove_dir (WCHAR *command) {
1888
1889   int   argno         = 0;
1890   int   argsProcessed = 0;
1891   WCHAR *argN          = command;
1892   static const WCHAR parmS[] = {'/','S','\0'};
1893   static const WCHAR parmQ[] = {'/','Q','\0'};
1894
1895   /* Loop through all args */
1896   while (argN) {
1897     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
1898     if (argN && argN[0] != '/') {
1899       WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1900                  wine_dbgstr_w(quals));
1901       argsProcessed++;
1902
1903       /* If subdirectory search not supplied, just try to remove
1904          and report error if it fails (eg if it contains a file) */
1905       if (strstrW (quals, parmS) == NULL) {
1906         if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1907
1908       /* Otherwise use ShFileOp to recursively remove a directory */
1909       } else {
1910
1911         SHFILEOPSTRUCTW lpDir;
1912
1913         /* Ask first */
1914         if (strstrW (quals, parmQ) == NULL) {
1915           BOOL  ok;
1916           WCHAR  question[MAXSTRING];
1917           static const WCHAR fmt[] = {'%','s',' ','\0'};
1918
1919           /* Ask for confirmation */
1920           wsprintfW(question, fmt, thisArg);
1921           ok = WCMD_ask_confirm(question, TRUE, NULL);
1922
1923           /* Abort if answer is 'N' */
1924           if (!ok) return;
1925         }
1926
1927         /* Do the delete */
1928         lpDir.hwnd   = NULL;
1929         lpDir.pTo    = NULL;
1930         lpDir.pFrom  = thisArg;
1931         lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1932         lpDir.wFunc  = FO_DELETE;
1933
1934         /* SHFileOperationW needs file list with a double null termination */
1935         thisArg[lstrlenW(thisArg) + 1] = 0x00;
1936
1937         if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1938       }
1939     }
1940   }
1941
1942   /* Handle no valid args */
1943   if (argsProcessed == 0) {
1944     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1945     return;
1946   }
1947
1948 }
1949
1950 /****************************************************************************
1951  * WCMD_rename
1952  *
1953  * Rename a file.
1954  */
1955
1956 void WCMD_rename (void)
1957 {
1958   int             status;
1959   HANDLE          hff;
1960   WIN32_FIND_DATAW fd;
1961   WCHAR            input[MAX_PATH];
1962   WCHAR           *dotDst = NULL;
1963   WCHAR            drive[10];
1964   WCHAR            dir[MAX_PATH];
1965   WCHAR            fname[MAX_PATH];
1966   WCHAR            ext[MAX_PATH];
1967
1968   errorlevel = 0;
1969
1970   /* Must be at least two args */
1971   if (param1[0] == 0x00 || param2[0] == 0x00) {
1972     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1973     errorlevel = 1;
1974     return;
1975   }
1976
1977   /* Destination cannot contain a drive letter or directory separator */
1978   if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
1979       SetLastError(ERROR_INVALID_PARAMETER);
1980       WCMD_print_error();
1981       errorlevel = 1;
1982       return;
1983   }
1984
1985   /* Convert partial path to full path */
1986   GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1987   WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1988              wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1989   dotDst = strchrW(param2, '.');
1990
1991   /* Split into components */
1992   WCMD_splitpath(input, drive, dir, fname, ext);
1993
1994   hff = FindFirstFileW(input, &fd);
1995   if (hff == INVALID_HANDLE_VALUE)
1996     return;
1997
1998  do {
1999     WCHAR  dest[MAX_PATH];
2000     WCHAR  src[MAX_PATH];
2001     WCHAR *dotSrc = NULL;
2002     int   dirLen;
2003
2004     WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2005
2006     /* FIXME: If dest name or extension is *, replace with filename/ext
2007        part otherwise use supplied name. This supports:
2008           ren *.fred *.jim
2009           ren jim.* fred.* etc
2010        However, windows has a more complex algorithm supporting eg
2011           ?'s and *'s mid name                                         */
2012     dotSrc = strchrW(fd.cFileName, '.');
2013
2014     /* Build src & dest name */
2015     strcpyW(src, drive);
2016     strcatW(src, dir);
2017     strcpyW(dest, src);
2018     dirLen = strlenW(src);
2019     strcatW(src, fd.cFileName);
2020
2021     /* Build name */
2022     if (param2[0] == '*') {
2023       strcatW(dest, fd.cFileName);
2024       if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
2025     } else {
2026       strcatW(dest, param2);
2027       if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
2028     }
2029
2030     /* Build Extension */
2031     if (dotDst && (*(dotDst+1)=='*')) {
2032       if (dotSrc) strcatW(dest, dotSrc);
2033     } else if (dotDst) {
2034       if (dotDst) strcatW(dest, dotDst);
2035     }
2036
2037     WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2038     WINE_TRACE("Dest   '%s'\n", wine_dbgstr_w(dest));
2039
2040     status = MoveFileW(src, dest);
2041
2042     if (!status) {
2043       WCMD_print_error ();
2044       errorlevel = 1;
2045     }
2046   } while (FindNextFileW(hff, &fd) != 0);
2047
2048   FindClose(hff);
2049 }
2050
2051 /*****************************************************************************
2052  * WCMD_dupenv
2053  *
2054  * Make a copy of the environment.
2055  */
2056 static WCHAR *WCMD_dupenv( const WCHAR *env )
2057 {
2058   WCHAR *env_copy;
2059   int len;
2060
2061   if( !env )
2062     return NULL;
2063
2064   len = 0;
2065   while ( env[len] )
2066     len += (strlenW(&env[len]) + 1);
2067
2068   env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
2069   if (!env_copy)
2070   {
2071     WINE_ERR("out of memory\n");
2072     return env_copy;
2073   }
2074   memcpy (env_copy, env, len*sizeof (WCHAR));
2075   env_copy[len] = 0;
2076
2077   return env_copy;
2078 }
2079
2080 /*****************************************************************************
2081  * WCMD_setlocal
2082  *
2083  *  setlocal pushes the environment onto a stack
2084  *  Save the environment as unicode so we don't screw anything up.
2085  */
2086 void WCMD_setlocal (const WCHAR *s) {
2087   WCHAR *env;
2088   struct env_stack *env_copy;
2089   WCHAR cwd[MAX_PATH];
2090
2091   /* setlocal does nothing outside of batch programs */
2092   if (!context) return;
2093
2094   /* DISABLEEXTENSIONS ignored */
2095
2096   env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2097   if( !env_copy )
2098   {
2099     WINE_ERR ("out of memory\n");
2100     return;
2101   }
2102
2103   env = GetEnvironmentStringsW ();
2104   env_copy->strings = WCMD_dupenv (env);
2105   if (env_copy->strings)
2106   {
2107     env_copy->batchhandle = context->h;
2108     env_copy->next = saved_environment;
2109     saved_environment = env_copy;
2110
2111     /* Save the current drive letter */
2112     GetCurrentDirectoryW(MAX_PATH, cwd);
2113     env_copy->u.cwd = cwd[0];
2114   }
2115   else
2116     LocalFree (env_copy);
2117
2118   FreeEnvironmentStringsW (env);
2119
2120 }
2121
2122 /*****************************************************************************
2123  * WCMD_endlocal
2124  *
2125  *  endlocal pops the environment off a stack
2126  *  Note: When searching for '=', search from WCHAR position 1, to handle
2127  *        special internal environment variables =C:, =D: etc
2128  */
2129 void WCMD_endlocal (void) {
2130   WCHAR *env, *old, *p;
2131   struct env_stack *temp;
2132   int len, n;
2133
2134   /* setlocal does nothing outside of batch programs */
2135   if (!context) return;
2136
2137   /* setlocal needs a saved environment from within the same context (batch
2138      program) as it was saved in                                            */
2139   if (!saved_environment || saved_environment->batchhandle != context->h)
2140     return;
2141
2142   /* pop the old environment from the stack */
2143   temp = saved_environment;
2144   saved_environment = temp->next;
2145
2146   /* delete the current environment, totally */
2147   env = GetEnvironmentStringsW ();
2148   old = WCMD_dupenv (GetEnvironmentStringsW ());
2149   len = 0;
2150   while (old[len]) {
2151     n = strlenW(&old[len]) + 1;
2152     p = strchrW(&old[len] + 1, '=');
2153     if (p)
2154     {
2155       *p++ = 0;
2156       SetEnvironmentVariableW (&old[len], NULL);
2157     }
2158     len += n;
2159   }
2160   LocalFree (old);
2161   FreeEnvironmentStringsW (env);
2162
2163   /* restore old environment */
2164   env = temp->strings;
2165   len = 0;
2166   while (env[len]) {
2167     n = strlenW(&env[len]) + 1;
2168     p = strchrW(&env[len] + 1, '=');
2169     if (p)
2170     {
2171       *p++ = 0;
2172       SetEnvironmentVariableW (&env[len], p);
2173     }
2174     len += n;
2175   }
2176
2177   /* Restore current drive letter */
2178   if (IsCharAlphaW(temp->u.cwd)) {
2179     WCHAR envvar[4];
2180     WCHAR cwd[MAX_PATH];
2181     static const WCHAR fmt[] = {'=','%','c',':','\0'};
2182
2183     wsprintfW(envvar, fmt, temp->u.cwd);
2184     if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2185       WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2186       SetCurrentDirectoryW(cwd);
2187     }
2188   }
2189
2190   LocalFree (env);
2191   LocalFree (temp);
2192 }
2193
2194 /*****************************************************************************
2195  * WCMD_setshow_default
2196  *
2197  *      Set/Show the current default directory
2198  */
2199
2200 void WCMD_setshow_default (const WCHAR *command) {
2201
2202   BOOL status;
2203   WCHAR string[1024];
2204   WCHAR cwd[1024];
2205   WCHAR *pos;
2206   WIN32_FIND_DATAW fd;
2207   HANDLE hff;
2208   static const WCHAR parmD[] = {'/','D','\0'};
2209
2210   WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2211
2212   /* Skip /D and trailing whitespace if on the front of the command line */
2213   if (CompareStringW(LOCALE_USER_DEFAULT,
2214                      NORM_IGNORECASE | SORT_STRINGSORT,
2215                      command, 2, parmD, -1) == CSTR_EQUAL) {
2216     command += 2;
2217     while (*command && (*command==' ' || *command=='\t'))
2218       command++;
2219   }
2220
2221   GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2222   if (strlenW(command) == 0) {
2223     strcatW (cwd, newlineW);
2224     WCMD_output_asis (cwd);
2225   }
2226   else {
2227     /* Remove any double quotes, which may be in the
2228        middle, eg. cd "C:\Program Files"\Microsoft is ok */
2229     pos = string;
2230     while (*command) {
2231       if (*command != '"') *pos++ = *command;
2232       command++;
2233     }
2234     while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2235       pos--;
2236     *pos = 0x00;
2237
2238     /* Search for appropriate directory */
2239     WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2240     hff = FindFirstFileW(string, &fd);
2241     if (hff != INVALID_HANDLE_VALUE) {
2242       do {
2243         if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2244           WCHAR fpath[MAX_PATH];
2245           WCHAR drive[10];
2246           WCHAR dir[MAX_PATH];
2247           WCHAR fname[MAX_PATH];
2248           WCHAR ext[MAX_PATH];
2249           static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2250
2251           /* Convert path into actual directory spec */
2252           GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2253           WCMD_splitpath(fpath, drive, dir, fname, ext);
2254
2255           /* Rebuild path */
2256           wsprintfW(string, fmt, drive, dir, fd.cFileName);
2257           break;
2258         }
2259       } while (FindNextFileW(hff, &fd) != 0);
2260       FindClose(hff);
2261     }
2262
2263     /* Change to that directory */
2264     WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2265
2266     status = SetCurrentDirectoryW(string);
2267     if (!status) {
2268       errorlevel = 1;
2269       WCMD_print_error ();
2270       return;
2271     } else {
2272
2273       /* Save away the actual new directory, to store as current location */
2274       GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2275
2276       /* Restore old directory if drive letter would change, and
2277            CD x:\directory /D (or pushd c:\directory) not supplied */
2278       if ((strstrW(quals, parmD) == NULL) &&
2279           (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2280         SetCurrentDirectoryW(cwd);
2281       }
2282     }
2283
2284     /* Set special =C: type environment variable, for drive letter of
2285        change of directory, even if path was restored due to missing
2286        /D (allows changing drive letter when not resident on that
2287        drive                                                          */
2288     if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2289       WCHAR env[4];
2290       strcpyW(env, equalW);
2291       memcpy(env+1, string, 2 * sizeof(WCHAR));
2292       env[3] = 0x00;
2293       WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2294       SetEnvironmentVariableW(env, string);
2295     }
2296
2297    }
2298   return;
2299 }
2300
2301 /****************************************************************************
2302  * WCMD_setshow_date
2303  *
2304  * Set/Show the system date
2305  * FIXME: Can't change date yet
2306  */
2307
2308 void WCMD_setshow_date (void) {
2309
2310   WCHAR curdate[64], buffer[64];
2311   DWORD count;
2312   static const WCHAR parmT[] = {'/','T','\0'};
2313
2314   if (strlenW(param1) == 0) {
2315     if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2316                 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2317       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2318       if (strstrW (quals, parmT) == NULL) {
2319         WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2320         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2321         if (count > 2) {
2322           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2323         }
2324       }
2325     }
2326     else WCMD_print_error ();
2327   }
2328   else {
2329     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2330   }
2331 }
2332
2333 /****************************************************************************
2334  * WCMD_compare
2335  * Note: Native displays 'fred' before 'fred ', so need to only compare up to
2336  *       the equals sign.
2337  */
2338 static int WCMD_compare( const void *a, const void *b )
2339 {
2340     int r;
2341     const WCHAR * const *str_a = a, * const *str_b = b;
2342     r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2343           *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
2344     if( r == CSTR_LESS_THAN ) return -1;
2345     if( r == CSTR_GREATER_THAN ) return 1;
2346     return 0;
2347 }
2348
2349 /****************************************************************************
2350  * WCMD_setshow_sortenv
2351  *
2352  * sort variables into order for display
2353  * Optionally only display those who start with a stub
2354  * returns the count displayed
2355  */
2356 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2357 {
2358   UINT count=0, len=0, i, displayedcount=0, stublen=0;
2359   const WCHAR **str;
2360
2361   if (stub) stublen = strlenW(stub);
2362
2363   /* count the number of strings, and the total length */
2364   while ( s[len] ) {
2365     len += (strlenW(&s[len]) + 1);
2366     count++;
2367   }
2368
2369   /* add the strings to an array */
2370   str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2371   if( !str )
2372     return 0;
2373   str[0] = s;
2374   for( i=1; i<count; i++ )
2375     str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2376
2377   /* sort the array */
2378   qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2379
2380   /* print it */
2381   for( i=0; i<count; i++ ) {
2382     if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2383                                 NORM_IGNORECASE | SORT_STRINGSORT,
2384                                 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2385       /* Don't display special internal variables */
2386       if (str[i][0] != '=') {
2387         WCMD_output_asis(str[i]);
2388         WCMD_output_asis(newlineW);
2389         displayedcount++;
2390       }
2391     }
2392   }
2393
2394   LocalFree( str );
2395   return displayedcount;
2396 }
2397
2398 /****************************************************************************
2399  * WCMD_setshow_env
2400  *
2401  * Set/Show the environment variables
2402  */
2403
2404 void WCMD_setshow_env (WCHAR *s) {
2405
2406   LPVOID env;
2407   WCHAR *p;
2408   int status;
2409   static const WCHAR parmP[] = {'/','P','\0'};
2410
2411   if (param1[0] == 0x00 && quals[0] == 0x00) {
2412     env = GetEnvironmentStringsW();
2413     WCMD_setshow_sortenv( env, NULL );
2414     return;
2415   }
2416
2417   /* See if /P supplied, and if so echo the prompt, and read in a reply */
2418   if (CompareStringW(LOCALE_USER_DEFAULT,
2419                      NORM_IGNORECASE | SORT_STRINGSORT,
2420                      s, 2, parmP, -1) == CSTR_EQUAL) {
2421     WCHAR string[MAXSTRING];
2422     DWORD count;
2423
2424     s += 2;
2425     while (*s && (*s==' ' || *s=='\t')) s++;
2426     if (*s=='\"')
2427         WCMD_strip_quotes(s);
2428
2429     /* If no parameter, or no '=' sign, return an error */
2430     if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2431       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2432       return;
2433     }
2434
2435     /* Output the prompt */
2436     *p++ = '\0';
2437     if (strlenW(p) != 0) WCMD_output_asis(p);
2438
2439     /* Read the reply */
2440     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2441     if (count > 1) {
2442       string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2443       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2444       WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2445                  wine_dbgstr_w(string));
2446       status = SetEnvironmentVariableW(s, string);
2447     }
2448
2449   } else {
2450     DWORD gle;
2451
2452     if (*s=='\"')
2453         WCMD_strip_quotes(s);
2454     p = strchrW (s, '=');
2455     if (p == NULL) {
2456       env = GetEnvironmentStringsW();
2457       if (WCMD_setshow_sortenv( env, s ) == 0) {
2458         WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2459         errorlevel = 1;
2460       }
2461       return;
2462     }
2463     *p++ = '\0';
2464
2465     if (strlenW(p) == 0) p = NULL;
2466     WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2467                wine_dbgstr_w(p));
2468     status = SetEnvironmentVariableW(s, p);
2469     gle = GetLastError();
2470     if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2471       errorlevel = 1;
2472     } else if ((!status)) WCMD_print_error();
2473     else errorlevel = 0;
2474   }
2475 }
2476
2477 /****************************************************************************
2478  * WCMD_setshow_path
2479  *
2480  * Set/Show the path environment variable
2481  */
2482
2483 void WCMD_setshow_path (const WCHAR *command) {
2484
2485   WCHAR string[1024];
2486   DWORD status;
2487   static const WCHAR pathW[] = {'P','A','T','H','\0'};
2488   static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2489
2490   if (strlenW(param1) == 0 && strlenW(param2) == 0) {
2491     status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2492     if (status != 0) {
2493       WCMD_output_asis ( pathEqW);
2494       WCMD_output_asis ( string);
2495       WCMD_output_asis ( newlineW);
2496     }
2497     else {
2498       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2499     }
2500   }
2501   else {
2502     if (*command == '=') command++; /* Skip leading '=' */
2503     status = SetEnvironmentVariableW(pathW, command);
2504     if (!status) WCMD_print_error();
2505   }
2506 }
2507
2508 /****************************************************************************
2509  * WCMD_setshow_prompt
2510  *
2511  * Set or show the command prompt.
2512  */
2513
2514 void WCMD_setshow_prompt (void) {
2515
2516   WCHAR *s;
2517   static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2518
2519   if (strlenW(param1) == 0) {
2520     SetEnvironmentVariableW(promptW, NULL);
2521   }
2522   else {
2523     s = param1;
2524     while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2525     if (strlenW(s) == 0) {
2526       SetEnvironmentVariableW(promptW, NULL);
2527     }
2528     else SetEnvironmentVariableW(promptW, s);
2529   }
2530 }
2531
2532 /****************************************************************************
2533  * WCMD_setshow_time
2534  *
2535  * Set/Show the system time
2536  * FIXME: Can't change time yet
2537  */
2538
2539 void WCMD_setshow_time (void) {
2540
2541   WCHAR curtime[64], buffer[64];
2542   DWORD count;
2543   SYSTEMTIME st;
2544   static const WCHAR parmT[] = {'/','T','\0'};
2545
2546   if (strlenW(param1) == 0) {
2547     GetLocalTime(&st);
2548     if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2549                 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2550       WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2551       if (strstrW (quals, parmT) == NULL) {
2552         WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2553         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2554         if (count > 2) {
2555           WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2556         }
2557       }
2558     }
2559     else WCMD_print_error ();
2560   }
2561   else {
2562     WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2563   }
2564 }
2565
2566 /****************************************************************************
2567  * WCMD_shift
2568  *
2569  * Shift batch parameters.
2570  * Optional /n says where to start shifting (n=0-8)
2571  */
2572
2573 void WCMD_shift (const WCHAR *command) {
2574   int start;
2575
2576   if (context != NULL) {
2577     WCHAR *pos = strchrW(command, '/');
2578     int   i;
2579
2580     if (pos == NULL) {
2581       start = 0;
2582     } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2583       start = (*(pos+1) - '0');
2584     } else {
2585       SetLastError(ERROR_INVALID_PARAMETER);
2586       WCMD_print_error();
2587       return;
2588     }
2589
2590     WINE_TRACE("Shifting variables, starting at %d\n", start);
2591     for (i=start;i<=8;i++) {
2592       context -> shift_count[i] = context -> shift_count[i+1] + 1;
2593     }
2594     context -> shift_count[9] = context -> shift_count[9] + 1;
2595   }
2596
2597 }
2598
2599 /****************************************************************************
2600  * WCMD_start
2601  */
2602 void WCMD_start(const WCHAR *command)
2603 {
2604     static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2605                                  '\\','s','t','a','r','t','.','e','x','e',0};
2606     WCHAR file[MAX_PATH];
2607     WCHAR *cmdline;
2608     STARTUPINFOW st;
2609     PROCESS_INFORMATION pi;
2610
2611     GetWindowsDirectoryW( file, MAX_PATH );
2612     strcatW( file, exeW );
2613     cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2614     strcpyW( cmdline, file );
2615     strcatW( cmdline, spaceW );
2616     strcatW( cmdline, command );
2617
2618     memset( &st, 0, sizeof(STARTUPINFOW) );
2619     st.cb = sizeof(STARTUPINFOW);
2620
2621     if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2622     {
2623         WaitForSingleObject( pi.hProcess, INFINITE );
2624         GetExitCodeProcess( pi.hProcess, &errorlevel );
2625         if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2626         CloseHandle(pi.hProcess);
2627         CloseHandle(pi.hThread);
2628     }
2629     else
2630     {
2631         SetLastError(ERROR_FILE_NOT_FOUND);
2632         WCMD_print_error ();
2633         errorlevel = 9009;
2634     }
2635     HeapFree( GetProcessHeap(), 0, cmdline );
2636 }
2637
2638 /****************************************************************************
2639  * WCMD_title
2640  *
2641  * Set the console title
2642  */
2643 void WCMD_title (const WCHAR *command) {
2644   SetConsoleTitleW(command);
2645 }
2646
2647 /****************************************************************************
2648  * WCMD_type
2649  *
2650  * Copy a file to standard output.
2651  */
2652
2653 void WCMD_type (WCHAR *command) {
2654
2655   int   argno         = 0;
2656   WCHAR *argN          = command;
2657   BOOL  writeHeaders  = FALSE;
2658
2659   if (param1[0] == 0x00) {
2660     WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2661     return;
2662   }
2663
2664   if (param2[0] != 0x00) writeHeaders = TRUE;
2665
2666   /* Loop through all args */
2667   errorlevel = 0;
2668   while (argN) {
2669     WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2670
2671     HANDLE h;
2672     WCHAR buffer[512];
2673     DWORD count;
2674
2675     if (!argN) break;
2676
2677     WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2678     h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2679                 FILE_ATTRIBUTE_NORMAL, NULL);
2680     if (h == INVALID_HANDLE_VALUE) {
2681       WCMD_print_error ();
2682       WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2683       errorlevel = 1;
2684     } else {
2685       if (writeHeaders) {
2686         static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2687         WCMD_output(fmt, thisArg);
2688       }
2689       while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2690         if (count == 0) break;  /* ReadFile reports success on EOF! */
2691         buffer[count] = 0;
2692         WCMD_output_asis (buffer);
2693       }
2694       CloseHandle (h);
2695     }
2696   }
2697 }
2698
2699 /****************************************************************************
2700  * WCMD_more
2701  *
2702  * Output either a file or stdin to screen in pages
2703  */
2704
2705 void WCMD_more (WCHAR *command) {
2706
2707   int   argno         = 0;
2708   WCHAR *argN          = command;
2709   WCHAR  moreStr[100];
2710   WCHAR  moreStrPage[100];
2711   WCHAR  buffer[512];
2712   DWORD count;
2713   static const WCHAR moreStart[] = {'-','-',' ','\0'};
2714   static const WCHAR moreFmt[]   = {'%','s',' ','-','-','\n','\0'};
2715   static const WCHAR moreFmt2[]  = {'%','s',' ','(','%','2','.','2','d','%','%',
2716                                     ')',' ','-','-','\n','\0'};
2717   static const WCHAR conInW[]    = {'C','O','N','I','N','$','\0'};
2718
2719   /* Prefix the NLS more with '-- ', then load the text */
2720   errorlevel = 0;
2721   strcpyW(moreStr, moreStart);
2722   LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2723               (sizeof(moreStr)/sizeof(WCHAR))-3);
2724
2725   if (param1[0] == 0x00) {
2726
2727     /* Wine implements pipes via temporary files, and hence stdin is
2728        effectively reading from the file. This means the prompts for
2729        more are satisfied by the next line from the input (file). To
2730        avoid this, ensure stdin is to the console                    */
2731     HANDLE hstdin  = GetStdHandle(STD_INPUT_HANDLE);
2732     HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2733                          FILE_SHARE_READ, NULL, OPEN_EXISTING,
2734                          FILE_ATTRIBUTE_NORMAL, 0);
2735     WINE_TRACE("No parms - working probably in pipe mode\n");
2736     SetStdHandle(STD_INPUT_HANDLE, hConIn);
2737
2738     /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2739        once you get in this bit unless due to a pipe, its going to end badly...  */
2740     wsprintfW(moreStrPage, moreFmt, moreStr);
2741
2742     WCMD_enter_paged_mode(moreStrPage);
2743     while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2744       if (count == 0) break;    /* ReadFile reports success on EOF! */
2745       buffer[count] = 0;
2746       WCMD_output_asis (buffer);
2747     }
2748     WCMD_leave_paged_mode();
2749
2750     /* Restore stdin to what it was */
2751     SetStdHandle(STD_INPUT_HANDLE, hstdin);
2752     CloseHandle(hConIn);
2753
2754     return;
2755   } else {
2756     BOOL needsPause = FALSE;
2757
2758     /* Loop through all args */
2759     WINE_TRACE("Parms supplied - working through each file\n");
2760     WCMD_enter_paged_mode(moreStrPage);
2761
2762     while (argN) {
2763       WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2764       HANDLE h;
2765
2766       if (!argN) break;
2767
2768       if (needsPause) {
2769
2770         /* Wait */
2771         wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2772         WCMD_leave_paged_mode();
2773         WCMD_output_asis(moreStrPage);
2774         WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2775         WCMD_enter_paged_mode(moreStrPage);
2776       }
2777
2778
2779       WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2780       h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2781                 FILE_ATTRIBUTE_NORMAL, NULL);
2782       if (h == INVALID_HANDLE_VALUE) {
2783         WCMD_print_error ();
2784         WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2785         errorlevel = 1;
2786       } else {
2787         ULONG64 curPos  = 0;
2788         ULONG64 fileLen = 0;
2789         WIN32_FILE_ATTRIBUTE_DATA   fileInfo;
2790
2791         /* Get the file size */
2792         GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2793         fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2794
2795         needsPause = TRUE;
2796         while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2797           if (count == 0) break;        /* ReadFile reports success on EOF! */
2798           buffer[count] = 0;
2799           curPos += count;
2800
2801           /* Update % count (would be used in WCMD_output_asis as prompt) */
2802           wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2803
2804           WCMD_output_asis (buffer);
2805         }
2806         CloseHandle (h);
2807       }
2808     }
2809
2810     WCMD_leave_paged_mode();
2811   }
2812 }
2813
2814 /****************************************************************************
2815  * WCMD_verify
2816  *
2817  * Display verify flag.
2818  * FIXME: We don't actually do anything with the verify flag other than toggle
2819  * it...
2820  */
2821
2822 void WCMD_verify (const WCHAR *command) {
2823
2824   int count;
2825
2826   count = strlenW(command);
2827   if (count == 0) {
2828     if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2829     else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2830     return;
2831   }
2832   if (lstrcmpiW(command, onW) == 0) {
2833     verify_mode = TRUE;
2834     return;
2835   }
2836   else if (lstrcmpiW(command, offW) == 0) {
2837     verify_mode = FALSE;
2838     return;
2839   }
2840   else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2841 }
2842
2843 /****************************************************************************
2844  * WCMD_version
2845  *
2846  * Display version info.
2847  */
2848
2849 void WCMD_version (void) {
2850
2851   WCMD_output_asis (version_string);
2852
2853 }
2854
2855 /****************************************************************************
2856  * WCMD_volume
2857  *
2858  * Display volume information (set_label = FALSE)
2859  * Additionally set volume label (set_label = TRUE)
2860  * Returns 1 on success, 0 otherwise
2861  */
2862
2863 int WCMD_volume(BOOL set_label, const WCHAR *path)
2864 {
2865   DWORD count, serial;
2866   WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2867   BOOL status;
2868
2869   if (strlenW(path) == 0) {
2870     status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2871     if (!status) {
2872       WCMD_print_error ();
2873       return 0;
2874     }
2875     status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2876                                    &serial, NULL, NULL, NULL, 0);
2877   }
2878   else {
2879     static const WCHAR fmt[] = {'%','s','\\','\0'};
2880     if ((path[1] != ':') || (strlenW(path) != 2)) {
2881       WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2882       return 0;
2883     }
2884     wsprintfW (curdir, fmt, path);
2885     status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2886                                    &serial, NULL,
2887         NULL, NULL, 0);
2888   }
2889   if (!status) {
2890     WCMD_print_error ();
2891     return 0;
2892   }
2893   if (label[0] != '\0') {
2894     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2895         curdir[0], label);
2896   }
2897   else {
2898     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2899         curdir[0]);
2900   }
2901   WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2902         HIWORD(serial), LOWORD(serial));
2903   if (set_label) {
2904     WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2905     WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2906     if (count > 1) {
2907       string[count-1] = '\0';           /* ReadFile output is not null-terminated! */
2908       if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2909     }
2910     if (strlenW(path) != 0) {
2911       if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2912     }
2913     else {
2914       if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2915     }
2916   }
2917   return 1;
2918 }
2919
2920 /**************************************************************************
2921  * WCMD_exit
2922  *
2923  * Exit either the process, or just this batch program
2924  *
2925  */
2926
2927 void WCMD_exit (CMD_LIST **cmdList) {
2928
2929     static const WCHAR parmB[] = {'/','B','\0'};
2930     int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2931
2932     if (context && lstrcmpiW(quals, parmB) == 0) {
2933         errorlevel = rc;
2934         context -> skip_rest = TRUE;
2935         *cmdList = NULL;
2936     } else {
2937         ExitProcess(rc);
2938     }
2939 }
2940
2941
2942 /*****************************************************************************
2943  * WCMD_assoc
2944  *
2945  *      Lists or sets file associations  (assoc = TRUE)
2946  *      Lists or sets file types         (assoc = FALSE)
2947  */
2948 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2949
2950     HKEY    key;
2951     DWORD   accessOptions = KEY_READ;
2952     WCHAR   *newValue;
2953     LONG    rc = ERROR_SUCCESS;
2954     WCHAR    keyValue[MAXSTRING];
2955     DWORD   valueLen = MAXSTRING;
2956     HKEY    readKey;
2957     static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2958                                      'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2959
2960     /* See if parameter includes '=' */
2961     errorlevel = 0;
2962     newValue = strchrW(command, '=');
2963     if (newValue) accessOptions |= KEY_WRITE;
2964
2965     /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2966     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2967                       accessOptions, &key) != ERROR_SUCCESS) {
2968       WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2969       return;
2970     }
2971
2972     /* If no parameters then list all associations */
2973     if (*command == 0x00) {
2974       int index = 0;
2975
2976       /* Enumerate all the keys */
2977       while (rc != ERROR_NO_MORE_ITEMS) {
2978         WCHAR  keyName[MAXSTRING];
2979         DWORD nameLen;
2980
2981         /* Find the next value */
2982         nameLen = MAXSTRING;
2983         rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2984
2985         if (rc == ERROR_SUCCESS) {
2986
2987           /* Only interested in extension ones if assoc, or others
2988              if not assoc                                          */
2989           if ((keyName[0] == '.' && assoc) ||
2990               (!(keyName[0] == '.') && (!assoc)))
2991           {
2992             WCHAR subkey[MAXSTRING];
2993             strcpyW(subkey, keyName);
2994             if (!assoc) strcatW(subkey, shOpCmdW);
2995
2996             if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2997
2998               valueLen = sizeof(keyValue)/sizeof(WCHAR);
2999               rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3000               WCMD_output_asis(keyName);
3001               WCMD_output_asis(equalW);
3002               /* If no default value found, leave line empty after '=' */
3003               if (rc == ERROR_SUCCESS) {
3004                 WCMD_output_asis(keyValue);
3005               }
3006               WCMD_output_asis(newlineW);
3007               RegCloseKey(readKey);
3008             }
3009           }
3010         }
3011       }
3012
3013     } else {
3014
3015       /* Parameter supplied - if no '=' on command line, its a query */
3016       if (newValue == NULL) {
3017         WCHAR *space;
3018         WCHAR subkey[MAXSTRING];
3019
3020         /* Query terminates the parameter at the first space */
3021         strcpyW(keyValue, command);
3022         space = strchrW(keyValue, ' ');
3023         if (space) *space=0x00;
3024
3025         /* Set up key name */
3026         strcpyW(subkey, keyValue);
3027         if (!assoc) strcatW(subkey, shOpCmdW);
3028
3029         if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3030
3031           rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3032           WCMD_output_asis(command);
3033           WCMD_output_asis(equalW);
3034           /* If no default value found, leave line empty after '=' */
3035           if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
3036           WCMD_output_asis(newlineW);
3037           RegCloseKey(readKey);
3038
3039         } else {
3040           WCHAR  msgbuffer[MAXSTRING];
3041
3042           /* Load the translated 'File association not found' */
3043           if (assoc) {
3044             LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3045           } else {
3046             LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3047           }
3048           WCMD_output_stderr(msgbuffer, keyValue);
3049           errorlevel = 2;
3050         }
3051
3052       /* Not a query - its a set or clear of a value */
3053       } else {
3054
3055         WCHAR subkey[MAXSTRING];
3056
3057         /* Get pointer to new value */
3058         *newValue = 0x00;
3059         newValue++;
3060
3061         /* Set up key name */
3062         strcpyW(subkey, command);
3063         if (!assoc) strcatW(subkey, shOpCmdW);
3064
3065         /* If nothing after '=' then clear value - only valid for ASSOC */
3066         if (*newValue == 0x00) {
3067
3068           if (assoc) rc = RegDeleteKeyW(key, command);
3069           if (assoc && rc == ERROR_SUCCESS) {
3070             WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
3071
3072           } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
3073             WCMD_print_error();
3074             errorlevel = 2;
3075
3076           } else {
3077             WCHAR  msgbuffer[MAXSTRING];
3078
3079             /* Load the translated 'File association not found' */
3080             if (assoc) {
3081               LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
3082                           sizeof(msgbuffer)/sizeof(WCHAR));
3083             } else {
3084               LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
3085                           sizeof(msgbuffer)/sizeof(WCHAR));
3086             }
3087             WCMD_output_stderr(msgbuffer, keyValue);
3088             errorlevel = 2;
3089           }
3090
3091         /* It really is a set value = contents */
3092         } else {
3093           rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3094                               accessOptions, NULL, &readKey, NULL);
3095           if (rc == ERROR_SUCCESS) {
3096             rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
3097                                 (LPBYTE)newValue,
3098                                 sizeof(WCHAR) * (strlenW(newValue) + 1));
3099             RegCloseKey(readKey);
3100           }
3101
3102           if (rc != ERROR_SUCCESS) {
3103             WCMD_print_error();
3104             errorlevel = 2;
3105           } else {
3106             WCMD_output_asis(command);
3107             WCMD_output_asis(equalW);
3108             WCMD_output_asis(newValue);
3109             WCMD_output_asis(newlineW);
3110           }
3111         }
3112       }
3113     }
3114
3115     /* Clean up */
3116     RegCloseKey(key);
3117 }
3118
3119 /****************************************************************************
3120  * WCMD_color
3121  *
3122  * Colors the terminal screen.
3123  */
3124
3125 void WCMD_color (void) {
3126
3127   CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3128   HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3129
3130   if (param1[0] != 0x00 && strlenW(param1) > 2) {
3131     WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3132     return;
3133   }
3134
3135   if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3136   {
3137       COORD topLeft;
3138       DWORD screenSize;
3139       DWORD color = 0;
3140
3141       screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3142
3143       topLeft.X = 0;
3144       topLeft.Y = 0;
3145
3146       /* Convert the color hex digits */
3147       if (param1[0] == 0x00) {
3148         color = defaultColor;
3149       } else {
3150         color = strtoulW(param1, NULL, 16);
3151       }
3152
3153       /* Fail if fg == bg color */
3154       if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3155         errorlevel = 1;
3156         return;
3157       }
3158
3159       /* Set the current screen contents and ensure all future writes
3160          remain this color                                             */
3161       FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3162       SetConsoleTextAttribute(hStdOut, color);
3163   }
3164 }