push 6e61d6ca5bcaf95ac09a664b4ba4f88238c927be
[wine/hacks.git] / programs / cmd / builtins.c
blob15d320cf2e6104a50ed7494a48e3877fc9569184
1 /*
2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
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.
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.
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
23 * NOTES:
24 * On entry to each function, global variables quals, param1, param2 contain
25 * the qualifiers (uppercased and concatenated) and parameters entered, with
26 * environment-variable and batch parameter substitution already done.
30 * FIXME:
31 * - No support for pipes, shell parameters
32 * - Lots of functionality missing from builtins
33 * - Messages etc need international support
36 #define WIN32_LEAN_AND_MEAN
38 #include "wcmd.h"
39 #include <shellapi.h>
40 #include "wine/debug.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
44 static void WCMD_part_execute(CMD_LIST **commands, WCHAR *firstcmd, WCHAR *variable,
45 WCHAR *value, BOOL isIF, BOOL conditionTRUE);
47 struct env_stack *saved_environment;
48 struct env_stack *pushd_directories;
50 extern HINSTANCE hinst;
51 extern WCHAR inbuilt[][10];
52 extern int echo_mode, verify_mode, defaultColor;
53 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
54 extern BATCH_CONTEXT *context;
55 extern DWORD errorlevel;
57 static const WCHAR dotW[] = {'.','\0'};
58 static const WCHAR dotdotW[] = {'.','.','\0'};
59 static const WCHAR slashW[] = {'\\','\0'};
60 static const WCHAR starW[] = {'*','\0'};
61 static const WCHAR equalW[] = {'=','\0'};
62 static const WCHAR fslashW[] = {'/','\0'};
63 static const WCHAR onW[] = {'O','N','\0'};
64 static const WCHAR offW[] = {'O','F','F','\0'};
65 static const WCHAR parmY[] = {'/','Y','\0'};
66 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
67 static const WCHAR nullW[] = {'\0'};
69 /**************************************************************************
70 * WCMD_ask_confirm
72 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
73 * answer.
75 * Returns True if Y (or A) answer is selected
76 * If optionAll contains a pointer, ALL is allowed, and if answered
77 * set to TRUE
80 static BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
82 WCHAR msgbuffer[MAXSTRING];
83 WCHAR Ybuffer[MAXSTRING];
84 WCHAR Nbuffer[MAXSTRING];
85 WCHAR Abuffer[MAXSTRING];
86 WCHAR answer[MAX_PATH] = {'\0'};
87 DWORD count = 0;
89 /* Load the translated 'Are you sure', plus valid answers */
90 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
91 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
92 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
93 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
95 /* Loop waiting on a Y or N */
96 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
97 static const WCHAR startBkt[] = {' ','(','\0'};
98 static const WCHAR endBkt[] = {')','?','\0'};
100 WCMD_output_asis (message);
101 if (showSureText) {
102 WCMD_output_asis (msgbuffer);
104 WCMD_output_asis (startBkt);
105 WCMD_output_asis (Ybuffer);
106 WCMD_output_asis (fslashW);
107 WCMD_output_asis (Nbuffer);
108 if (optionAll) {
109 WCMD_output_asis (fslashW);
110 WCMD_output_asis (Abuffer);
112 WCMD_output_asis (endBkt);
113 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
114 sizeof(answer)/sizeof(WCHAR), &count, NULL);
115 answer[0] = toupperW(answer[0]);
118 /* Return the answer */
119 return ((answer[0] == Ybuffer[0]) ||
120 (optionAll && (answer[0] == Abuffer[0])));
123 /****************************************************************************
124 * WCMD_clear_screen
126 * Clear the terminal screen.
129 void WCMD_clear_screen (void) {
131 /* Emulate by filling the screen from the top left to bottom right with
132 spaces, then moving the cursor to the top left afterwards */
133 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
134 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
136 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
138 COORD topLeft;
139 DWORD screenSize;
141 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
143 topLeft.X = 0;
144 topLeft.Y = 0;
145 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
146 SetConsoleCursorPosition(hStdOut, topLeft);
150 /****************************************************************************
151 * WCMD_change_tty
153 * Change the default i/o device (ie redirect STDin/STDout).
156 void WCMD_change_tty (void) {
158 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
162 /****************************************************************************
163 * WCMD_copy
165 * Copy a file or wildcarded set.
166 * FIXME: Add support for a+b+c type syntax
169 void WCMD_copy (void) {
171 WIN32_FIND_DATAW fd;
172 HANDLE hff;
173 BOOL force, status;
174 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
175 DWORD len;
176 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
177 BOOL copyToDir = FALSE;
178 BOOL copyFromDir = FALSE;
179 WCHAR srcspec[MAX_PATH];
180 DWORD attribs;
181 WCHAR drive[10];
182 WCHAR dir[MAX_PATH];
183 WCHAR fname[MAX_PATH];
184 WCHAR ext[MAX_PATH];
186 if (param1[0] == 0x00) {
187 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
188 return;
191 /* Convert source into full spec */
192 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
193 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
194 if (srcpath[strlenW(srcpath) - 1] == '\\')
195 srcpath[strlenW(srcpath) - 1] = '\0';
197 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
198 attribs = GetFileAttributesW(srcpath);
199 } else {
200 attribs = 0;
202 strcpyW(srcspec, srcpath);
204 /* If a directory, then add \* on the end when searching */
205 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
206 strcatW(srcpath, slashW);
207 copyFromDir = TRUE;
208 strcatW(srcspec, slashW);
209 strcatW(srcspec, starW);
210 } else {
211 WCMD_splitpath(srcpath, drive, dir, fname, ext);
212 strcpyW(srcpath, drive);
213 strcatW(srcpath, dir);
216 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
218 /* If no destination supplied, assume current directory */
219 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
220 if (param2[0] == 0x00) {
221 strcpyW(param2, dotW);
224 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
225 if (outpath[strlenW(outpath) - 1] == '\\')
226 outpath[strlenW(outpath) - 1] = '\0';
227 attribs = GetFileAttributesW(outpath);
228 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
229 strcatW (outpath, slashW);
230 copyToDir = TRUE;
232 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
233 wine_dbgstr_w(outpath), copyToDir);
235 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
236 if (strstrW (quals, parmNoY))
237 force = FALSE;
238 else if (strstrW (quals, parmY))
239 force = TRUE;
240 else {
241 /* By default, we will force the overwrite in batch mode and ask for
242 * confirmation in interactive mode. */
243 force = !!context;
245 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
246 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
247 * default behavior. */
248 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
249 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
250 if (!lstrcmpiW (copycmd, parmY))
251 force = TRUE;
252 else if (!lstrcmpiW (copycmd, parmNoY))
253 force = FALSE;
257 /* Loop through all source files */
258 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
259 hff = FindFirstFileW(srcspec, &fd);
260 if (hff != INVALID_HANDLE_VALUE) {
261 do {
262 WCHAR outname[MAX_PATH];
263 WCHAR srcname[MAX_PATH];
264 BOOL overwrite = force;
266 /* Destination is either supplied filename, or source name in
267 supplied destination directory */
268 strcpyW(outname, outpath);
269 if (copyToDir) strcatW(outname, fd.cFileName);
270 strcpyW(srcname, srcpath);
271 strcatW(srcname, fd.cFileName);
273 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
274 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
276 /* Skip . and .., and directories */
277 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
278 overwrite = FALSE;
279 WINE_TRACE("Skipping directories\n");
282 /* Prompt before overwriting */
283 else if (!overwrite) {
284 attribs = GetFileAttributesW(outname);
285 if (attribs != INVALID_FILE_ATTRIBUTES) {
286 WCHAR buffer[MAXSTRING];
287 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
288 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
290 else overwrite = TRUE;
293 /* Do the copy as appropriate */
294 if (overwrite) {
295 status = CopyFileW(srcname, outname, FALSE);
296 if (!status) WCMD_print_error ();
299 } while (FindNextFileW(hff, &fd) != 0);
300 FindClose (hff);
301 } else {
302 status = ERROR_FILE_NOT_FOUND;
303 WCMD_print_error ();
307 /****************************************************************************
308 * WCMD_create_dir
310 * Create a directory.
312 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
313 * they do not already exist.
316 static BOOL create_full_path(WCHAR* path)
318 int len;
319 WCHAR *new_path;
320 BOOL ret = TRUE;
322 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path)+1) * sizeof(WCHAR));
323 strcpyW(new_path,path);
325 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
326 new_path[len - 1] = 0;
328 while (!CreateDirectoryW(new_path,NULL))
330 WCHAR *slash;
331 DWORD last_error = GetLastError();
332 if (last_error == ERROR_ALREADY_EXISTS)
333 break;
335 if (last_error != ERROR_PATH_NOT_FOUND)
337 ret = FALSE;
338 break;
341 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
343 ret = FALSE;
344 break;
347 len = slash - new_path;
348 new_path[len] = 0;
349 if (!create_full_path(new_path))
351 ret = FALSE;
352 break;
354 new_path[len] = '\\';
356 HeapFree(GetProcessHeap(),0,new_path);
357 return ret;
360 void WCMD_create_dir (void) {
362 if (param1[0] == 0x00) {
363 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
364 return;
366 if (!create_full_path(param1)) WCMD_print_error ();
369 /****************************************************************************
370 * WCMD_delete
372 * Delete a file or wildcarded set.
374 * Note on /A:
375 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
376 * - Each set is a pattern, eg /ahr /as-r means
377 * readonly+hidden OR nonreadonly system files
378 * - The '-' applies to a single field, ie /a:-hr means read only
379 * non-hidden files
382 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
384 int argno = 0;
385 int argsProcessed = 0;
386 WCHAR *argN = command;
387 BOOL foundAny = FALSE;
388 static const WCHAR parmA[] = {'/','A','\0'};
389 static const WCHAR parmQ[] = {'/','Q','\0'};
390 static const WCHAR parmP[] = {'/','P','\0'};
391 static const WCHAR parmS[] = {'/','S','\0'};
392 static const WCHAR parmF[] = {'/','F','\0'};
394 /* If not recursing, clear error flag */
395 if (expectDir) errorlevel = 0;
397 /* Loop through all args */
398 while (argN) {
399 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
400 WCHAR argCopy[MAX_PATH];
402 if (argN && argN[0] != '/') {
404 WIN32_FIND_DATAW fd;
405 HANDLE hff;
406 WCHAR fpath[MAX_PATH];
407 WCHAR *p;
408 BOOL handleParm = TRUE;
409 BOOL found = FALSE;
410 static const WCHAR anyExt[]= {'.','*','\0'};
412 strcpyW(argCopy, thisArg);
413 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
414 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
415 argsProcessed++;
417 /* If filename part of parameter is * or *.*, prompt unless
418 /Q supplied. */
419 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
421 WCHAR drive[10];
422 WCHAR dir[MAX_PATH];
423 WCHAR fname[MAX_PATH];
424 WCHAR ext[MAX_PATH];
426 /* Convert path into actual directory spec */
427 GetFullPathNameW(argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
428 WCMD_splitpath(fpath, drive, dir, fname, ext);
430 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
431 if ((strcmpW(fname, starW) == 0) &&
432 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
433 BOOL ok;
434 WCHAR question[MAXSTRING];
435 static const WCHAR fmt[] = {'%','s',' ','\0'};
437 /* Note: Flag as found, to avoid file not found message */
438 found = TRUE;
440 /* Ask for confirmation */
441 wsprintfW(question, fmt, fpath);
442 ok = WCMD_ask_confirm(question, TRUE, NULL);
444 /* Abort if answer is 'N' */
445 if (!ok) continue;
449 /* First, try to delete in the current directory */
450 hff = FindFirstFileW(argCopy, &fd);
451 if (hff == INVALID_HANDLE_VALUE) {
452 handleParm = FALSE;
453 } else {
454 found = TRUE;
457 /* Support del <dirname> by just deleting all files dirname\* */
458 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
459 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
460 WCHAR modifiedParm[MAX_PATH];
461 static const WCHAR slashStar[] = {'\\','*','\0'};
463 strcpyW(modifiedParm, argCopy);
464 strcatW(modifiedParm, slashStar);
465 FindClose(hff);
466 found = TRUE;
467 WCMD_delete(modifiedParm, FALSE);
469 } else if (handleParm) {
471 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
472 strcpyW (fpath, argCopy);
473 do {
474 p = strrchrW (fpath, '\\');
475 if (p != NULL) {
476 *++p = '\0';
477 strcatW (fpath, fd.cFileName);
479 else strcpyW (fpath, fd.cFileName);
480 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
481 BOOL ok = TRUE;
482 WCHAR *nextA = strstrW (quals, parmA);
484 /* Handle attribute matching (/A) */
485 if (nextA != NULL) {
486 ok = FALSE;
487 while (nextA != NULL && !ok) {
489 WCHAR *thisA = (nextA+2);
490 BOOL stillOK = TRUE;
492 /* Skip optional : */
493 if (*thisA == ':') thisA++;
495 /* Parse each of the /A[:]xxx in turn */
496 while (*thisA && *thisA != '/') {
497 BOOL negate = FALSE;
498 BOOL attribute = FALSE;
500 /* Match negation of attribute first */
501 if (*thisA == '-') {
502 negate=TRUE;
503 thisA++;
506 /* Match attribute */
507 switch (*thisA) {
508 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
509 break;
510 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
511 break;
512 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
513 break;
514 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
515 break;
516 default:
517 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
520 /* Now check result, keeping a running boolean about whether it
521 matches all parsed attributes so far */
522 if (attribute && !negate) {
523 stillOK = stillOK;
524 } else if (!attribute && negate) {
525 stillOK = stillOK;
526 } else {
527 stillOK = FALSE;
529 thisA++;
532 /* Save the running total as the final result */
533 ok = stillOK;
535 /* Step on to next /A set */
536 nextA = strstrW (nextA+1, parmA);
540 /* /P means prompt for each file */
541 if (ok && strstrW (quals, parmP) != NULL) {
542 WCHAR question[MAXSTRING];
544 /* Ask for confirmation */
545 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
546 ok = WCMD_ask_confirm(question, FALSE, NULL);
549 /* Only proceed if ok to */
550 if (ok) {
552 /* If file is read only, and /F supplied, delete it */
553 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
554 strstrW (quals, parmF) != NULL) {
555 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
558 /* Now do the delete */
559 if (!DeleteFileW(fpath)) WCMD_print_error ();
563 } while (FindNextFileW(hff, &fd) != 0);
564 FindClose (hff);
567 /* Now recurse into all subdirectories handling the parameter in the same way */
568 if (strstrW (quals, parmS) != NULL) {
570 WCHAR thisDir[MAX_PATH];
571 int cPos;
573 WCHAR drive[10];
574 WCHAR dir[MAX_PATH];
575 WCHAR fname[MAX_PATH];
576 WCHAR ext[MAX_PATH];
578 /* Convert path into actual directory spec */
579 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
580 WCMD_splitpath(thisDir, drive, dir, fname, ext);
582 strcpyW(thisDir, drive);
583 strcatW(thisDir, dir);
584 cPos = strlenW(thisDir);
586 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
588 /* Append '*' to the directory */
589 thisDir[cPos] = '*';
590 thisDir[cPos+1] = 0x00;
592 hff = FindFirstFileW(thisDir, &fd);
594 /* Remove residual '*' */
595 thisDir[cPos] = 0x00;
597 if (hff != INVALID_HANDLE_VALUE) {
598 DIRECTORY_STACK *allDirs = NULL;
599 DIRECTORY_STACK *lastEntry = NULL;
601 do {
602 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
603 (strcmpW(fd.cFileName, dotdotW) != 0) &&
604 (strcmpW(fd.cFileName, dotW) != 0)) {
606 DIRECTORY_STACK *nextDir;
607 WCHAR subParm[MAX_PATH];
609 /* Work out search parameter in sub dir */
610 strcpyW (subParm, thisDir);
611 strcatW (subParm, fd.cFileName);
612 strcatW (subParm, slashW);
613 strcatW (subParm, fname);
614 strcatW (subParm, ext);
615 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
617 /* Allocate memory, add to list */
618 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
619 if (allDirs == NULL) allDirs = nextDir;
620 if (lastEntry != NULL) lastEntry->next = nextDir;
621 lastEntry = nextDir;
622 nextDir->next = NULL;
623 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
624 (strlenW(subParm)+1) * sizeof(WCHAR));
625 strcpyW(nextDir->dirName, subParm);
627 } while (FindNextFileW(hff, &fd) != 0);
628 FindClose (hff);
630 /* Go through each subdir doing the delete */
631 while (allDirs != NULL) {
632 DIRECTORY_STACK *tempDir;
634 tempDir = allDirs->next;
635 found |= WCMD_delete (allDirs->dirName, FALSE);
637 HeapFree(GetProcessHeap(),0,allDirs->dirName);
638 HeapFree(GetProcessHeap(),0,allDirs);
639 allDirs = tempDir;
643 /* Keep running total to see if any found, and if not recursing
644 issue error message */
645 if (expectDir) {
646 if (!found) {
647 errorlevel = 1;
648 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
651 foundAny |= found;
655 /* Handle no valid args */
656 if (argsProcessed == 0) {
657 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
660 return foundAny;
663 /****************************************************************************
664 * WCMD_echo
666 * Echo input to the screen (or not). We don't try to emulate the bugs
667 * in DOS (try typing "ECHO ON AGAIN" for an example).
670 void WCMD_echo (const WCHAR *command) {
672 int count;
674 if ((command[0] == '.') && (command[1] == 0)) {
675 WCMD_output (newline);
676 return;
678 if (command[0]==' ')
679 command++;
680 count = strlenW(command);
681 if (count == 0) {
682 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
683 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
684 return;
686 if (lstrcmpiW(command, onW) == 0) {
687 echo_mode = 1;
688 return;
690 if (lstrcmpiW(command, offW) == 0) {
691 echo_mode = 0;
692 return;
694 WCMD_output_asis (command);
695 WCMD_output (newline);
699 /**************************************************************************
700 * WCMD_for
702 * Batch file loop processing.
704 * On entry: cmdList contains the syntax up to the set
705 * next cmdList and all in that bracket contain the set data
706 * next cmdlist contains the DO cmd
707 * following that is either brackets or && entries (as per if)
711 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
713 WIN32_FIND_DATAW fd;
714 HANDLE hff;
715 int i;
716 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
717 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
718 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
719 WCHAR variable[4];
720 WCHAR *firstCmd;
721 int thisDepth;
723 WCHAR *curPos = p;
724 BOOL expandDirs = FALSE;
725 BOOL useNumbers = FALSE;
726 BOOL doRecursive = FALSE;
727 BOOL doFileset = FALSE;
728 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
729 int itemNum;
730 CMD_LIST *thisCmdStart;
733 /* Handle optional qualifiers (multiple are allowed) */
734 while (*curPos && *curPos == '/') {
735 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
736 curPos++;
737 switch (toupperW(*curPos)) {
738 case 'D': curPos++; expandDirs = TRUE; break;
739 case 'L': curPos++; useNumbers = TRUE; break;
741 /* Recursive is special case - /R can have an optional path following it */
742 /* filenamesets are another special case - /F can have an optional options following it */
743 case 'R':
744 case 'F':
746 BOOL isRecursive = (*curPos == 'R');
748 if (isRecursive) doRecursive = TRUE;
749 else doFileset = TRUE;
751 /* Skip whitespace */
752 curPos++;
753 while (*curPos && *curPos==' ') curPos++;
755 /* Next parm is either qualifier, path/options or variable -
756 only care about it if it is the path/options */
757 if (*curPos && *curPos != '/' && *curPos != '%') {
758 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
759 else WINE_FIXME("/F needs to handle options\n");
761 break;
763 default:
764 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
765 curPos++;
768 /* Skip whitespace between qualifiers */
769 while (*curPos && *curPos==' ') curPos++;
772 /* Skip whitespace before variable */
773 while (*curPos && *curPos==' ') curPos++;
775 /* Ensure line continues with variable */
776 if (!*curPos || *curPos != '%') {
777 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
778 return;
781 /* Variable should follow */
782 i = 0;
783 while (curPos[i] && curPos[i]!=' ') i++;
784 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
785 variable[i] = 0x00;
786 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
787 curPos = &curPos[i];
789 /* Skip whitespace before IN */
790 while (*curPos && *curPos==' ') curPos++;
792 /* Ensure line continues with IN */
793 if (!*curPos || lstrcmpiW (curPos, inW)) {
794 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
795 return;
798 /* Save away where the set of data starts and the variable */
799 thisDepth = (*cmdList)->bracketDepth;
800 *cmdList = (*cmdList)->nextcommand;
801 setStart = (*cmdList);
803 /* Skip until the close bracket */
804 WINE_TRACE("Searching %p as the set\n", *cmdList);
805 while (*cmdList &&
806 (*cmdList)->command != NULL &&
807 (*cmdList)->bracketDepth > thisDepth) {
808 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
809 *cmdList = (*cmdList)->nextcommand;
812 /* Skip the close bracket, if there is one */
813 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
815 /* Syntax error if missing close bracket, or nothing following it
816 and once we have the complete set, we expect a DO */
817 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
818 if ((*cmdList == NULL) ||
819 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
820 (*cmdList)->command, 3, doW, -1) != 2)) {
821 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
822 return;
825 /* Save away the starting position for the commands (and offset for the
826 first one */
827 cmdStart = *cmdList;
828 cmdEnd = *cmdList;
829 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
830 itemNum = 0;
832 thisSet = setStart;
833 /* Loop through all set entries */
834 while (thisSet &&
835 thisSet->command != NULL &&
836 thisSet->bracketDepth >= thisDepth) {
838 /* Loop through all entries on the same line */
839 WCHAR *item;
840 WCHAR *itemStart;
842 WINE_TRACE("Processing for set %p\n", thisSet);
843 i = 0;
844 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
847 * If the parameter within the set has a wildcard then search for matching files
848 * otherwise do a literal substitution.
850 static const WCHAR wildcards[] = {'*','?','\0'};
851 thisCmdStart = cmdStart;
853 itemNum++;
854 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
856 if (!useNumbers && !doFileset) {
857 if (strpbrkW (item, wildcards)) {
858 hff = FindFirstFileW(item, &fd);
859 if (hff != INVALID_HANDLE_VALUE) {
860 do {
861 BOOL isDirectory = FALSE;
863 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
865 /* Handle as files or dirs appropriately, but ignore . and .. */
866 if (isDirectory == expandDirs &&
867 (strcmpW(fd.cFileName, dotdotW) != 0) &&
868 (strcmpW(fd.cFileName, dotW) != 0))
870 thisCmdStart = cmdStart;
871 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
872 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
873 fd.cFileName, FALSE, TRUE);
876 } while (FindNextFileW(hff, &fd) != 0);
877 FindClose (hff);
879 } else {
880 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
883 } else if (useNumbers) {
884 /* Convert the first 3 numbers to signed longs and save */
885 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
886 /* else ignore them! */
888 /* Filesets - either a list of files, or a command to run and parse the output */
889 } else if (doFileset && *itemStart != '"') {
891 HANDLE input;
892 WCHAR temp_file[MAX_PATH];
894 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
895 wine_dbgstr_w(item));
897 /* If backquote or single quote, we need to launch that command
898 and parse the results - use a temporary file */
899 if (*itemStart == '`' || *itemStart == '\'') {
901 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
902 static const WCHAR redirOut[] = {'>','%','s','\0'};
903 static const WCHAR cmdW[] = {'C','M','D','\0'};
905 /* Remove trailing character */
906 itemStart[strlenW(itemStart)-1] = 0x00;
908 /* Get temp filename */
909 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
910 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
912 /* Execute program and redirect output */
913 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
914 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
916 /* Open the file, read line by line and process */
917 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
918 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
919 } else {
921 /* Open the file, read line by line and process */
922 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
923 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
926 /* Process the input file */
927 if (input == INVALID_HANDLE_VALUE) {
928 WCMD_print_error ();
929 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
930 errorlevel = 1;
931 return; /* FOR loop aborts at first failure here */
933 } else {
935 WCHAR buffer[MAXSTRING] = {'\0'};
936 WCHAR *where, *parm;
938 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
940 /* Skip blank lines*/
941 parm = WCMD_parameter (buffer, 0, &where);
942 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
943 wine_dbgstr_w(buffer));
945 if (where) {
946 /* FIXME: The following should be moved into its own routine and
947 reused for the string literal parsing below */
948 thisCmdStart = cmdStart;
949 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
950 cmdEnd = thisCmdStart;
953 buffer[0] = 0x00;
956 CloseHandle (input);
959 /* Delete the temporary file */
960 if (*itemStart == '`' || *itemStart == '\'') {
961 DeleteFileW(temp_file);
964 /* Filesets - A string literal */
965 } else if (doFileset && *itemStart == '"') {
966 WCHAR buffer[MAXSTRING] = {'\0'};
967 WCHAR *where, *parm;
969 /* Skip blank lines, and re-extract parameter now string has quotes removed */
970 strcpyW(buffer, item);
971 parm = WCMD_parameter (buffer, 0, &where);
972 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
973 wine_dbgstr_w(buffer));
975 if (where) {
976 /* FIXME: The following should be moved into its own routine and
977 reused for the string literal parsing below */
978 thisCmdStart = cmdStart;
979 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
980 cmdEnd = thisCmdStart;
984 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
985 cmdEnd = thisCmdStart;
986 i++;
989 /* Move onto the next set line */
990 thisSet = thisSet->nextcommand;
993 /* If /L is provided, now run the for loop */
994 if (useNumbers) {
995 WCHAR thisNum[20];
996 static const WCHAR fmt[] = {'%','d','\0'};
998 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
999 numbers[0], numbers[2], numbers[1]);
1000 for (i=numbers[0];
1001 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1002 i=i + numbers[1]) {
1004 sprintfW(thisNum, fmt, i);
1005 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1007 thisCmdStart = cmdStart;
1008 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1009 cmdEnd = thisCmdStart;
1013 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1014 all processing, OR it should be pointing to the end of && processing OR
1015 it should be pointing at the NULL end of bracket for the DO. The return
1016 value needs to be the NEXT command to execute, which it either is, or
1017 we need to step over the closing bracket */
1018 *cmdList = cmdEnd;
1019 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1023 /*****************************************************************************
1024 * WCMD_part_execute
1026 * Execute a command, and any && or bracketed follow on to the command. The
1027 * first command to be executed may not be at the front of the
1028 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1030 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1031 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1033 CMD_LIST *curPosition = *cmdList;
1034 int myDepth = (*cmdList)->bracketDepth;
1036 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1037 cmdList, wine_dbgstr_w(firstcmd),
1038 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1039 conditionTRUE);
1041 /* Skip leading whitespace between condition and the command */
1042 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1044 /* Process the first command, if there is one */
1045 if (conditionTRUE && firstcmd && *firstcmd) {
1046 WCHAR *command = WCMD_strdupW(firstcmd);
1047 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1048 HeapFree(GetProcessHeap(), 0, command);
1052 /* If it didn't move the position, step to next command */
1053 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1055 /* Process any other parts of the command */
1056 if (*cmdList) {
1057 BOOL processThese = TRUE;
1059 if (isIF) processThese = conditionTRUE;
1061 while (*cmdList) {
1062 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1064 /* execute all appropriate commands */
1065 curPosition = *cmdList;
1067 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1068 *cmdList,
1069 (*cmdList)->prevDelim,
1070 (*cmdList)->bracketDepth, myDepth);
1072 /* Execute any statements appended to the line */
1073 /* FIXME: Only if previous call worked for && or failed for || */
1074 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1075 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1076 if (processThese && (*cmdList)->command) {
1077 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1078 value, cmdList);
1080 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1082 /* Execute any appended to the statement with (...) */
1083 } else if ((*cmdList)->bracketDepth > myDepth) {
1084 if (processThese) {
1085 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1086 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1088 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1090 /* End of the command - does 'ELSE ' follow as the next command? */
1091 } else {
1092 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1093 NORM_IGNORECASE | SORT_STRINGSORT,
1094 (*cmdList)->command, 5, ifElse, -1) == 2) {
1096 /* Swap between if and else processing */
1097 processThese = !processThese;
1099 /* Process the ELSE part */
1100 if (processThese) {
1101 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1103 /* Skip leading whitespace between condition and the command */
1104 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1105 if (*cmd) {
1106 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1109 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1110 } else {
1111 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1112 break;
1117 return;
1120 /**************************************************************************
1121 * WCMD_give_help
1123 * Simple on-line help. Help text is stored in the resource file.
1126 void WCMD_give_help (WCHAR *command) {
1128 int i;
1130 command = WCMD_strtrim_leading_spaces(command);
1131 if (strlenW(command) == 0) {
1132 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1134 else {
1135 for (i=0; i<=WCMD_EXIT; i++) {
1136 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1137 command, -1, inbuilt[i], -1) == 2) {
1138 WCMD_output_asis (WCMD_LoadMessage(i));
1139 return;
1142 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1144 return;
1147 /****************************************************************************
1148 * WCMD_go_to
1150 * Batch file jump instruction. Not the most efficient algorithm ;-)
1151 * Prints error message if the specified label cannot be found - the file pointer is
1152 * then at EOF, effectively stopping the batch file.
1153 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1156 void WCMD_goto (CMD_LIST **cmdList) {
1158 WCHAR string[MAX_PATH];
1160 /* Do not process any more parts of a processed multipart or multilines command */
1161 if (cmdList) *cmdList = NULL;
1163 if (param1[0] == 0x00) {
1164 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1165 return;
1167 if (context != NULL) {
1168 WCHAR *paramStart = param1;
1169 static const WCHAR eofW[] = {':','e','o','f','\0'};
1171 /* Handle special :EOF label */
1172 if (lstrcmpiW (eofW, param1) == 0) {
1173 context -> skip_rest = TRUE;
1174 return;
1177 /* Support goto :label as well as goto label */
1178 if (*paramStart == ':') paramStart++;
1180 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1181 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1182 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
1184 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1186 return;
1189 /*****************************************************************************
1190 * WCMD_pushd
1192 * Push a directory onto the stack
1195 void WCMD_pushd (WCHAR *command) {
1196 struct env_stack *curdir;
1197 WCHAR *thisdir;
1198 static const WCHAR parmD[] = {'/','D','\0'};
1200 if (strchrW(command, '/') != NULL) {
1201 SetLastError(ERROR_INVALID_PARAMETER);
1202 WCMD_print_error();
1203 return;
1206 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1207 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1208 if( !curdir || !thisdir ) {
1209 LocalFree(curdir);
1210 LocalFree(thisdir);
1211 WINE_ERR ("out of memory\n");
1212 return;
1215 /* Change directory using CD code with /D parameter */
1216 strcpyW(quals, parmD);
1217 GetCurrentDirectoryW (1024, thisdir);
1218 errorlevel = 0;
1219 WCMD_setshow_default(command);
1220 if (errorlevel) {
1221 LocalFree(curdir);
1222 LocalFree(thisdir);
1223 return;
1224 } else {
1225 curdir -> next = pushd_directories;
1226 curdir -> strings = thisdir;
1227 if (pushd_directories == NULL) {
1228 curdir -> u.stackdepth = 1;
1229 } else {
1230 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1232 pushd_directories = curdir;
1237 /*****************************************************************************
1238 * WCMD_popd
1240 * Pop a directory from the stack
1243 void WCMD_popd (void) {
1244 struct env_stack *temp = pushd_directories;
1246 if (!pushd_directories)
1247 return;
1249 /* pop the old environment from the stack, and make it the current dir */
1250 pushd_directories = temp->next;
1251 SetCurrentDirectoryW(temp->strings);
1252 LocalFree (temp->strings);
1253 LocalFree (temp);
1256 /****************************************************************************
1257 * WCMD_if
1259 * Batch file conditional.
1261 * On entry, cmdlist will point to command containing the IF, and optionally
1262 * the first command to execute (if brackets not found)
1263 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1264 * If ('s were found, execute all within that bracket
1265 * Command may optionally be followed by an ELSE - need to skip instructions
1266 * in the else using the same logic
1268 * FIXME: Much more syntax checking needed!
1271 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1273 int negate = 0, test = 0;
1274 WCHAR condition[MAX_PATH], *command, *s;
1275 static const WCHAR notW[] = {'n','o','t','\0'};
1276 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1277 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1278 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1279 static const WCHAR eqeqW[] = {'=','=','\0'};
1280 static const WCHAR parmI[] = {'/','I','\0'};
1282 if (!lstrcmpiW (param1, notW)) {
1283 negate = 1;
1284 strcpyW (condition, param2);
1286 else {
1287 strcpyW (condition, param1);
1289 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1291 if (!lstrcmpiW (condition, errlvlW)) {
1292 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1293 WCMD_parameter (p, 2+negate, &command);
1295 else if (!lstrcmpiW (condition, existW)) {
1296 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1297 test = 1;
1299 WCMD_parameter (p, 2+negate, &command);
1301 else if (!lstrcmpiW (condition, defdW)) {
1302 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1303 test = 1;
1305 WCMD_parameter (p, 2+negate, &command);
1307 else if ((s = strstrW (p, eqeqW))) {
1308 s += 2;
1309 if (strstrW (quals, parmI) == NULL) {
1310 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1312 else {
1313 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1315 WCMD_parameter (s, 1, &command);
1317 else {
1318 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1319 return;
1322 /* Process rest of IF statement which is on the same line
1323 Note: This may process all or some of the cmdList (eg a GOTO) */
1324 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1327 /****************************************************************************
1328 * WCMD_move
1330 * Move a file, directory tree or wildcarded set of files.
1333 void WCMD_move (void) {
1335 int status;
1336 WIN32_FIND_DATAW fd;
1337 HANDLE hff;
1338 WCHAR input[MAX_PATH];
1339 WCHAR output[MAX_PATH];
1340 WCHAR drive[10];
1341 WCHAR dir[MAX_PATH];
1342 WCHAR fname[MAX_PATH];
1343 WCHAR ext[MAX_PATH];
1345 if (param1[0] == 0x00) {
1346 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1347 return;
1350 /* If no destination supplied, assume current directory */
1351 if (param2[0] == 0x00) {
1352 strcpyW(param2, dotW);
1355 /* If 2nd parm is directory, then use original filename */
1356 /* Convert partial path to full path */
1357 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1358 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1359 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1360 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1362 /* Split into components */
1363 WCMD_splitpath(input, drive, dir, fname, ext);
1365 hff = FindFirstFileW(input, &fd);
1366 while (hff != INVALID_HANDLE_VALUE) {
1367 WCHAR dest[MAX_PATH];
1368 WCHAR src[MAX_PATH];
1369 DWORD attribs;
1371 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1373 /* Build src & dest name */
1374 strcpyW(src, drive);
1375 strcatW(src, dir);
1377 /* See if dest is an existing directory */
1378 attribs = GetFileAttributesW(output);
1379 if (attribs != INVALID_FILE_ATTRIBUTES &&
1380 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1381 strcpyW(dest, output);
1382 strcatW(dest, slashW);
1383 strcatW(dest, fd.cFileName);
1384 } else {
1385 strcpyW(dest, output);
1388 strcatW(src, fd.cFileName);
1390 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1391 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1393 /* Check if file is read only, otherwise move it */
1394 attribs = GetFileAttributesW(src);
1395 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1396 (attribs & FILE_ATTRIBUTE_READONLY)) {
1397 SetLastError(ERROR_ACCESS_DENIED);
1398 status = 0;
1399 } else {
1400 BOOL ok = TRUE;
1402 /* If destination exists, prompt unless /Y supplied */
1403 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1404 BOOL force = FALSE;
1405 WCHAR copycmd[MAXSTRING];
1406 int len;
1408 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1409 if (strstrW (quals, parmNoY))
1410 force = FALSE;
1411 else if (strstrW (quals, parmY))
1412 force = TRUE;
1413 else {
1414 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1415 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1416 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1417 && ! lstrcmpiW (copycmd, parmY));
1420 /* Prompt if overwriting */
1421 if (!force) {
1422 WCHAR question[MAXSTRING];
1423 WCHAR yesChar[10];
1425 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1427 /* Ask for confirmation */
1428 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1429 ok = WCMD_ask_confirm(question, FALSE, NULL);
1431 /* So delete the destination prior to the move */
1432 if (ok) {
1433 if (!DeleteFileW(dest)) {
1434 WCMD_print_error ();
1435 errorlevel = 1;
1436 ok = FALSE;
1442 if (ok) {
1443 status = MoveFileW(src, dest);
1444 } else {
1445 status = 1; /* Anything other than 0 to prevent error msg below */
1449 if (!status) {
1450 WCMD_print_error ();
1451 errorlevel = 1;
1454 /* Step on to next match */
1455 if (FindNextFileW(hff, &fd) == 0) {
1456 FindClose(hff);
1457 hff = INVALID_HANDLE_VALUE;
1458 break;
1463 /****************************************************************************
1464 * WCMD_pause
1466 * Wait for keyboard input.
1469 void WCMD_pause (void) {
1471 DWORD count;
1472 WCHAR string[32];
1474 WCMD_output (anykey);
1475 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1476 sizeof(string)/sizeof(WCHAR), &count, NULL);
1479 /****************************************************************************
1480 * WCMD_remove_dir
1482 * Delete a directory.
1485 void WCMD_remove_dir (WCHAR *command) {
1487 int argno = 0;
1488 int argsProcessed = 0;
1489 WCHAR *argN = command;
1490 static const WCHAR parmS[] = {'/','S','\0'};
1491 static const WCHAR parmQ[] = {'/','Q','\0'};
1493 /* Loop through all args */
1494 while (argN) {
1495 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1496 if (argN && argN[0] != '/') {
1497 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1498 wine_dbgstr_w(quals));
1499 argsProcessed++;
1501 /* If subdirectory search not supplied, just try to remove
1502 and report error if it fails (eg if it contains a file) */
1503 if (strstrW (quals, parmS) == NULL) {
1504 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1506 /* Otherwise use ShFileOp to recursively remove a directory */
1507 } else {
1509 SHFILEOPSTRUCTW lpDir;
1511 /* Ask first */
1512 if (strstrW (quals, parmQ) == NULL) {
1513 BOOL ok;
1514 WCHAR question[MAXSTRING];
1515 static const WCHAR fmt[] = {'%','s',' ','\0'};
1517 /* Ask for confirmation */
1518 wsprintfW(question, fmt, thisArg);
1519 ok = WCMD_ask_confirm(question, TRUE, NULL);
1521 /* Abort if answer is 'N' */
1522 if (!ok) return;
1525 /* Do the delete */
1526 lpDir.hwnd = NULL;
1527 lpDir.pTo = NULL;
1528 lpDir.pFrom = thisArg;
1529 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1530 lpDir.wFunc = FO_DELETE;
1531 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1536 /* Handle no valid args */
1537 if (argsProcessed == 0) {
1538 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1539 return;
1544 /****************************************************************************
1545 * WCMD_rename
1547 * Rename a file.
1550 void WCMD_rename (void) {
1552 int status;
1553 HANDLE hff;
1554 WIN32_FIND_DATAW fd;
1555 WCHAR input[MAX_PATH];
1556 WCHAR *dotDst = NULL;
1557 WCHAR drive[10];
1558 WCHAR dir[MAX_PATH];
1559 WCHAR fname[MAX_PATH];
1560 WCHAR ext[MAX_PATH];
1561 DWORD attribs;
1563 errorlevel = 0;
1565 /* Must be at least two args */
1566 if (param1[0] == 0x00 || param2[0] == 0x00) {
1567 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1568 errorlevel = 1;
1569 return;
1572 /* Destination cannot contain a drive letter or directory separator */
1573 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1574 SetLastError(ERROR_INVALID_PARAMETER);
1575 WCMD_print_error();
1576 errorlevel = 1;
1577 return;
1580 /* Convert partial path to full path */
1581 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1582 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1583 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1584 dotDst = strchrW(param2, '.');
1586 /* Split into components */
1587 WCMD_splitpath(input, drive, dir, fname, ext);
1589 hff = FindFirstFileW(input, &fd);
1590 while (hff != INVALID_HANDLE_VALUE) {
1591 WCHAR dest[MAX_PATH];
1592 WCHAR src[MAX_PATH];
1593 WCHAR *dotSrc = NULL;
1594 int dirLen;
1596 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1598 /* FIXME: If dest name or extension is *, replace with filename/ext
1599 part otherwise use supplied name. This supports:
1600 ren *.fred *.jim
1601 ren jim.* fred.* etc
1602 However, windows has a more complex algorithm supporting eg
1603 ?'s and *'s mid name */
1604 dotSrc = strchrW(fd.cFileName, '.');
1606 /* Build src & dest name */
1607 strcpyW(src, drive);
1608 strcatW(src, dir);
1609 strcpyW(dest, src);
1610 dirLen = strlenW(src);
1611 strcatW(src, fd.cFileName);
1613 /* Build name */
1614 if (param2[0] == '*') {
1615 strcatW(dest, fd.cFileName);
1616 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1617 } else {
1618 strcatW(dest, param2);
1619 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1622 /* Build Extension */
1623 if (dotDst && (*(dotDst+1)=='*')) {
1624 if (dotSrc) strcatW(dest, dotSrc);
1625 } else if (dotDst) {
1626 if (dotDst) strcatW(dest, dotDst);
1629 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1630 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1632 /* Check if file is read only, otherwise move it */
1633 attribs = GetFileAttributesW(src);
1634 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1635 (attribs & FILE_ATTRIBUTE_READONLY)) {
1636 SetLastError(ERROR_ACCESS_DENIED);
1637 status = 0;
1638 } else {
1639 status = MoveFileW(src, dest);
1642 if (!status) {
1643 WCMD_print_error ();
1644 errorlevel = 1;
1647 /* Step on to next match */
1648 if (FindNextFileW(hff, &fd) == 0) {
1649 FindClose(hff);
1650 hff = INVALID_HANDLE_VALUE;
1651 break;
1656 /*****************************************************************************
1657 * WCMD_dupenv
1659 * Make a copy of the environment.
1661 static WCHAR *WCMD_dupenv( const WCHAR *env )
1663 WCHAR *env_copy;
1664 int len;
1666 if( !env )
1667 return NULL;
1669 len = 0;
1670 while ( env[len] )
1671 len += (strlenW(&env[len]) + 1);
1673 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1674 if (!env_copy)
1676 WINE_ERR("out of memory\n");
1677 return env_copy;
1679 memcpy (env_copy, env, len*sizeof (WCHAR));
1680 env_copy[len] = 0;
1682 return env_copy;
1685 /*****************************************************************************
1686 * WCMD_setlocal
1688 * setlocal pushes the environment onto a stack
1689 * Save the environment as unicode so we don't screw anything up.
1691 void WCMD_setlocal (const WCHAR *s) {
1692 WCHAR *env;
1693 struct env_stack *env_copy;
1694 WCHAR cwd[MAX_PATH];
1696 /* DISABLEEXTENSIONS ignored */
1698 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1699 if( !env_copy )
1701 WINE_ERR ("out of memory\n");
1702 return;
1705 env = GetEnvironmentStringsW ();
1707 env_copy->strings = WCMD_dupenv (env);
1708 if (env_copy->strings)
1710 env_copy->next = saved_environment;
1711 saved_environment = env_copy;
1713 /* Save the current drive letter */
1714 GetCurrentDirectoryW(MAX_PATH, cwd);
1715 env_copy->u.cwd = cwd[0];
1717 else
1718 LocalFree (env_copy);
1720 FreeEnvironmentStringsW (env);
1724 /*****************************************************************************
1725 * WCMD_endlocal
1727 * endlocal pops the environment off a stack
1728 * Note: When searching for '=', search from WCHAR position 1, to handle
1729 * special internal environment variables =C:, =D: etc
1731 void WCMD_endlocal (void) {
1732 WCHAR *env, *old, *p;
1733 struct env_stack *temp;
1734 int len, n;
1736 if (!saved_environment)
1737 return;
1739 /* pop the old environment from the stack */
1740 temp = saved_environment;
1741 saved_environment = temp->next;
1743 /* delete the current environment, totally */
1744 env = GetEnvironmentStringsW ();
1745 old = WCMD_dupenv (GetEnvironmentStringsW ());
1746 len = 0;
1747 while (old[len]) {
1748 n = strlenW(&old[len]) + 1;
1749 p = strchrW(&old[len] + 1, '=');
1750 if (p)
1752 *p++ = 0;
1753 SetEnvironmentVariableW (&old[len], NULL);
1755 len += n;
1757 LocalFree (old);
1758 FreeEnvironmentStringsW (env);
1760 /* restore old environment */
1761 env = temp->strings;
1762 len = 0;
1763 while (env[len]) {
1764 n = strlenW(&env[len]) + 1;
1765 p = strchrW(&env[len] + 1, '=');
1766 if (p)
1768 *p++ = 0;
1769 SetEnvironmentVariableW (&env[len], p);
1771 len += n;
1774 /* Restore current drive letter */
1775 if (IsCharAlphaW(temp->u.cwd)) {
1776 WCHAR envvar[4];
1777 WCHAR cwd[MAX_PATH];
1778 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1780 wsprintfW(envvar, fmt, temp->u.cwd);
1781 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1782 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1783 SetCurrentDirectoryW(cwd);
1787 LocalFree (env);
1788 LocalFree (temp);
1791 /*****************************************************************************
1792 * WCMD_setshow_attrib
1794 * Display and optionally sets DOS attributes on a file or directory
1798 void WCMD_setshow_attrib (void) {
1800 DWORD count;
1801 HANDLE hff;
1802 WIN32_FIND_DATAW fd;
1803 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1804 WCHAR *name = param1;
1805 DWORD attrib_set=0;
1806 DWORD attrib_clear=0;
1808 if (param1[0] == '+' || param1[0] == '-') {
1809 DWORD attrib = 0;
1810 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1811 switch (param1[1]) {
1812 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
1813 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
1814 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
1815 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
1816 default:
1817 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1818 return;
1820 switch (param1[0]) {
1821 case '+': attrib_set = attrib; break;
1822 case '-': attrib_clear = attrib; break;
1824 name = param2;
1827 if (strlenW(name) == 0) {
1828 static const WCHAR slashStarW[] = {'\\','*','\0'};
1830 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
1831 strcatW (name, slashStarW);
1834 hff = FindFirstFileW(name, &fd);
1835 if (hff == INVALID_HANDLE_VALUE) {
1836 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
1838 else {
1839 do {
1840 if (attrib_set || attrib_clear) {
1841 fd.dwFileAttributes &= ~attrib_clear;
1842 fd.dwFileAttributes |= attrib_set;
1843 if (!fd.dwFileAttributes)
1844 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
1845 SetFileAttributesW(name, fd.dwFileAttributes);
1846 } else {
1847 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1848 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1849 flags[0] = 'H';
1851 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1852 flags[1] = 'S';
1854 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1855 flags[2] = 'A';
1857 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1858 flags[3] = 'R';
1860 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1861 flags[4] = 'T';
1863 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1864 flags[5] = 'C';
1866 WCMD_output (fmt, flags, fd.cFileName);
1867 for (count=0; count < 8; count++) flags[count] = ' ';
1869 } while (FindNextFileW(hff, &fd) != 0);
1871 FindClose (hff);
1874 /*****************************************************************************
1875 * WCMD_setshow_default
1877 * Set/Show the current default directory
1880 void WCMD_setshow_default (WCHAR *command) {
1882 BOOL status;
1883 WCHAR string[1024];
1884 WCHAR cwd[1024];
1885 WCHAR *pos;
1886 WIN32_FIND_DATAW fd;
1887 HANDLE hff;
1888 static const WCHAR parmD[] = {'/','D','\0'};
1890 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1892 /* Skip /D and trailing whitespace if on the front of the command line */
1893 if (CompareStringW(LOCALE_USER_DEFAULT,
1894 NORM_IGNORECASE | SORT_STRINGSORT,
1895 command, 2, parmD, -1) == 2) {
1896 command += 2;
1897 while (*command && *command==' ') command++;
1900 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
1901 if (strlenW(command) == 0) {
1902 strcatW (cwd, newline);
1903 WCMD_output (cwd);
1905 else {
1906 /* Remove any double quotes, which may be in the
1907 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1908 pos = string;
1909 while (*command) {
1910 if (*command != '"') *pos++ = *command;
1911 command++;
1913 *pos = 0x00;
1915 /* Search for appropriate directory */
1916 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1917 hff = FindFirstFileW(string, &fd);
1918 while (hff != INVALID_HANDLE_VALUE) {
1919 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1920 WCHAR fpath[MAX_PATH];
1921 WCHAR drive[10];
1922 WCHAR dir[MAX_PATH];
1923 WCHAR fname[MAX_PATH];
1924 WCHAR ext[MAX_PATH];
1925 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1927 /* Convert path into actual directory spec */
1928 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1929 WCMD_splitpath(fpath, drive, dir, fname, ext);
1931 /* Rebuild path */
1932 wsprintfW(string, fmt, drive, dir, fd.cFileName);
1934 FindClose(hff);
1935 hff = INVALID_HANDLE_VALUE;
1936 break;
1939 /* Step on to next match */
1940 if (FindNextFileW(hff, &fd) == 0) {
1941 FindClose(hff);
1942 hff = INVALID_HANDLE_VALUE;
1943 break;
1947 /* Change to that directory */
1948 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1950 status = SetCurrentDirectoryW(string);
1951 if (!status) {
1952 errorlevel = 1;
1953 WCMD_print_error ();
1954 return;
1955 } else {
1957 /* Save away the actual new directory, to store as current location */
1958 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
1960 /* Restore old directory if drive letter would change, and
1961 CD x:\directory /D (or pushd c:\directory) not supplied */
1962 if ((strstrW(quals, parmD) == NULL) &&
1963 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1964 SetCurrentDirectoryW(cwd);
1968 /* Set special =C: type environment variable, for drive letter of
1969 change of directory, even if path was restored due to missing
1970 /D (allows changing drive letter when not resident on that
1971 drive */
1972 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
1973 WCHAR env[4];
1974 strcpyW(env, equalW);
1975 memcpy(env+1, string, 2 * sizeof(WCHAR));
1976 env[3] = 0x00;
1977 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1978 SetEnvironmentVariableW(env, string);
1982 return;
1985 /****************************************************************************
1986 * WCMD_setshow_date
1988 * Set/Show the system date
1989 * FIXME: Can't change date yet
1992 void WCMD_setshow_date (void) {
1994 WCHAR curdate[64], buffer[64];
1995 DWORD count;
1996 static const WCHAR parmT[] = {'/','T','\0'};
1998 if (strlenW(param1) == 0) {
1999 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2000 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2001 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2002 if (strstrW (quals, parmT) == NULL) {
2003 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2004 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2005 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2006 if (count > 2) {
2007 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2011 else WCMD_print_error ();
2013 else {
2014 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2018 /****************************************************************************
2019 * WCMD_compare
2021 static int WCMD_compare( const void *a, const void *b )
2023 int r;
2024 const WCHAR * const *str_a = a, * const *str_b = b;
2025 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2026 *str_a, -1, *str_b, -1 );
2027 if( r == CSTR_LESS_THAN ) return -1;
2028 if( r == CSTR_GREATER_THAN ) return 1;
2029 return 0;
2032 /****************************************************************************
2033 * WCMD_setshow_sortenv
2035 * sort variables into order for display
2036 * Optionally only display those who start with a stub
2037 * returns the count displayed
2039 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2041 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2042 const WCHAR **str;
2044 if (stub) stublen = strlenW(stub);
2046 /* count the number of strings, and the total length */
2047 while ( s[len] ) {
2048 len += (strlenW(&s[len]) + 1);
2049 count++;
2052 /* add the strings to an array */
2053 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2054 if( !str )
2055 return 0;
2056 str[0] = s;
2057 for( i=1; i<count; i++ )
2058 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2060 /* sort the array */
2061 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2063 /* print it */
2064 for( i=0; i<count; i++ ) {
2065 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2066 NORM_IGNORECASE | SORT_STRINGSORT,
2067 str[i], stublen, stub, -1) == 2) {
2068 /* Don't display special internal variables */
2069 if (str[i][0] != '=') {
2070 WCMD_output_asis(str[i]);
2071 WCMD_output_asis(newline);
2072 displayedcount++;
2077 LocalFree( str );
2078 return displayedcount;
2081 /****************************************************************************
2082 * WCMD_setshow_env
2084 * Set/Show the environment variables
2087 void WCMD_setshow_env (WCHAR *s) {
2089 LPVOID env;
2090 WCHAR *p;
2091 int status;
2092 static const WCHAR parmP[] = {'/','P','\0'};
2094 errorlevel = 0;
2095 if (param1[0] == 0x00 && quals[0] == 0x00) {
2096 env = GetEnvironmentStringsW();
2097 WCMD_setshow_sortenv( env, NULL );
2098 return;
2101 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2102 if (CompareStringW(LOCALE_USER_DEFAULT,
2103 NORM_IGNORECASE | SORT_STRINGSORT,
2104 s, 2, parmP, -1) == 2) {
2105 WCHAR string[MAXSTRING];
2106 DWORD count;
2108 s += 2;
2109 while (*s && *s==' ') s++;
2110 if (*s=='\"')
2111 WCMD_opt_s_strip_quotes(s);
2113 /* If no parameter, or no '=' sign, return an error */
2114 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2115 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2116 return;
2119 /* Output the prompt */
2120 *p++ = '\0';
2121 if (strlenW(p) != 0) WCMD_output(p);
2123 /* Read the reply */
2124 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2125 sizeof(string)/sizeof(WCHAR), &count, NULL);
2126 if (count > 1) {
2127 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2128 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2129 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2130 wine_dbgstr_w(string));
2131 status = SetEnvironmentVariableW(s, string);
2134 } else {
2135 DWORD gle;
2137 if (*s=='\"')
2138 WCMD_opt_s_strip_quotes(s);
2139 p = strchrW (s, '=');
2140 if (p == NULL) {
2141 env = GetEnvironmentStringsW();
2142 if (WCMD_setshow_sortenv( env, s ) == 0) {
2143 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2144 errorlevel = 1;
2146 return;
2148 *p++ = '\0';
2150 if (strlenW(p) == 0) p = NULL;
2151 status = SetEnvironmentVariableW(s, p);
2152 gle = GetLastError();
2153 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2154 errorlevel = 1;
2155 } else if ((!status)) WCMD_print_error();
2159 /****************************************************************************
2160 * WCMD_setshow_path
2162 * Set/Show the path environment variable
2165 void WCMD_setshow_path (WCHAR *command) {
2167 WCHAR string[1024];
2168 DWORD status;
2169 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2170 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2172 if (strlenW(param1) == 0) {
2173 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2174 if (status != 0) {
2175 WCMD_output_asis ( pathEqW);
2176 WCMD_output_asis ( string);
2177 WCMD_output_asis ( newline);
2179 else {
2180 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2183 else {
2184 if (*command == '=') command++; /* Skip leading '=' */
2185 status = SetEnvironmentVariableW(pathW, command);
2186 if (!status) WCMD_print_error();
2190 /****************************************************************************
2191 * WCMD_setshow_prompt
2193 * Set or show the command prompt.
2196 void WCMD_setshow_prompt (void) {
2198 WCHAR *s;
2199 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2201 if (strlenW(param1) == 0) {
2202 SetEnvironmentVariableW(promptW, NULL);
2204 else {
2205 s = param1;
2206 while ((*s == '=') || (*s == ' ')) s++;
2207 if (strlenW(s) == 0) {
2208 SetEnvironmentVariableW(promptW, NULL);
2210 else SetEnvironmentVariableW(promptW, s);
2214 /****************************************************************************
2215 * WCMD_setshow_time
2217 * Set/Show the system time
2218 * FIXME: Can't change time yet
2221 void WCMD_setshow_time (void) {
2223 WCHAR curtime[64], buffer[64];
2224 DWORD count;
2225 SYSTEMTIME st;
2226 static const WCHAR parmT[] = {'/','T','\0'};
2228 if (strlenW(param1) == 0) {
2229 GetLocalTime(&st);
2230 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2231 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2232 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2233 if (strstrW (quals, parmT) == NULL) {
2234 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2235 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2236 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2237 if (count > 2) {
2238 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2242 else WCMD_print_error ();
2244 else {
2245 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2249 /****************************************************************************
2250 * WCMD_shift
2252 * Shift batch parameters.
2253 * Optional /n says where to start shifting (n=0-8)
2256 void WCMD_shift (WCHAR *command) {
2257 int start;
2259 if (context != NULL) {
2260 WCHAR *pos = strchrW(command, '/');
2261 int i;
2263 if (pos == NULL) {
2264 start = 0;
2265 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2266 start = (*(pos+1) - '0');
2267 } else {
2268 SetLastError(ERROR_INVALID_PARAMETER);
2269 WCMD_print_error();
2270 return;
2273 WINE_TRACE("Shifting variables, starting at %d\n", start);
2274 for (i=start;i<=8;i++) {
2275 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2277 context -> shift_count[9] = context -> shift_count[9] + 1;
2282 /****************************************************************************
2283 * WCMD_title
2285 * Set the console title
2287 void WCMD_title (WCHAR *command) {
2288 SetConsoleTitleW(command);
2291 /****************************************************************************
2292 * WCMD_type
2294 * Copy a file to standard output.
2297 void WCMD_type (WCHAR *command) {
2299 int argno = 0;
2300 WCHAR *argN = command;
2301 BOOL writeHeaders = FALSE;
2303 if (param1[0] == 0x00) {
2304 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2305 return;
2308 if (param2[0] != 0x00) writeHeaders = TRUE;
2310 /* Loop through all args */
2311 errorlevel = 0;
2312 while (argN) {
2313 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2315 HANDLE h;
2316 WCHAR buffer[512];
2317 DWORD count;
2319 if (!argN) break;
2321 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2322 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2323 FILE_ATTRIBUTE_NORMAL, NULL);
2324 if (h == INVALID_HANDLE_VALUE) {
2325 WCMD_print_error ();
2326 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2327 errorlevel = 1;
2328 } else {
2329 if (writeHeaders) {
2330 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2331 WCMD_output(fmt, thisArg);
2333 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2334 if (count == 0) break; /* ReadFile reports success on EOF! */
2335 buffer[count] = 0;
2336 WCMD_output_asis (buffer);
2338 CloseHandle (h);
2339 if (!writeHeaders)
2340 WCMD_output_asis (newline);
2345 /****************************************************************************
2346 * WCMD_more
2348 * Output either a file or stdin to screen in pages
2351 void WCMD_more (WCHAR *command) {
2353 int argno = 0;
2354 WCHAR *argN = command;
2355 BOOL useinput = FALSE;
2356 WCHAR moreStr[100];
2357 WCHAR moreStrPage[100];
2358 WCHAR buffer[512];
2359 DWORD count;
2360 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2361 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2362 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2363 ')',' ','-','-','\n','\0'};
2364 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2366 /* Prefix the NLS more with '-- ', then load the text */
2367 errorlevel = 0;
2368 strcpyW(moreStr, moreStart);
2369 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2370 (sizeof(moreStr)/sizeof(WCHAR))-3);
2372 if (param1[0] == 0x00) {
2374 /* Wine implements pipes via temporary files, and hence stdin is
2375 effectively reading from the file. This means the prompts for
2376 more are satisfied by the next line from the input (file). To
2377 avoid this, ensure stdin is to the console */
2378 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2379 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2380 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2381 FILE_ATTRIBUTE_NORMAL, 0);
2382 WINE_TRACE("No parms - working probably in pipe mode\n");
2383 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2385 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2386 once you get in this bit unless due to a pipe, its going to end badly... */
2387 useinput = TRUE;
2388 wsprintfW(moreStrPage, moreFmt, moreStr);
2390 WCMD_enter_paged_mode(moreStrPage);
2391 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2392 if (count == 0) break; /* ReadFile reports success on EOF! */
2393 buffer[count] = 0;
2394 WCMD_output_asis (buffer);
2396 WCMD_leave_paged_mode();
2398 /* Restore stdin to what it was */
2399 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2400 CloseHandle(hConIn);
2402 return;
2403 } else {
2404 BOOL needsPause = FALSE;
2406 /* Loop through all args */
2407 WINE_TRACE("Parms supplied - working through each file\n");
2408 WCMD_enter_paged_mode(moreStrPage);
2410 while (argN) {
2411 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2412 HANDLE h;
2414 if (!argN) break;
2416 if (needsPause) {
2418 /* Wait */
2419 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2420 WCMD_leave_paged_mode();
2421 WCMD_output_asis(moreStrPage);
2422 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2423 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2424 WCMD_enter_paged_mode(moreStrPage);
2428 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2429 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2430 FILE_ATTRIBUTE_NORMAL, NULL);
2431 if (h == INVALID_HANDLE_VALUE) {
2432 WCMD_print_error ();
2433 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2434 errorlevel = 1;
2435 } else {
2436 ULONG64 curPos = 0;
2437 ULONG64 fileLen = 0;
2438 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2440 /* Get the file size */
2441 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2442 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2444 needsPause = TRUE;
2445 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2446 if (count == 0) break; /* ReadFile reports success on EOF! */
2447 buffer[count] = 0;
2448 curPos += count;
2450 /* Update % count (would be used in WCMD_output_asis as prompt) */
2451 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2453 WCMD_output_asis (buffer);
2455 CloseHandle (h);
2459 WCMD_leave_paged_mode();
2463 /****************************************************************************
2464 * WCMD_verify
2466 * Display verify flag.
2467 * FIXME: We don't actually do anything with the verify flag other than toggle
2468 * it...
2471 void WCMD_verify (WCHAR *command) {
2473 int count;
2475 count = strlenW(command);
2476 if (count == 0) {
2477 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2478 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2479 return;
2481 if (lstrcmpiW(command, onW) == 0) {
2482 verify_mode = 1;
2483 return;
2485 else if (lstrcmpiW(command, offW) == 0) {
2486 verify_mode = 0;
2487 return;
2489 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2492 /****************************************************************************
2493 * WCMD_version
2495 * Display version info.
2498 void WCMD_version (void) {
2500 WCMD_output (version_string);
2504 /****************************************************************************
2505 * WCMD_volume
2507 * Display volume info and/or set volume label. Returns 0 if error.
2510 int WCMD_volume (int mode, WCHAR *path) {
2512 DWORD count, serial;
2513 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2514 BOOL status;
2516 if (strlenW(path) == 0) {
2517 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2518 if (!status) {
2519 WCMD_print_error ();
2520 return 0;
2522 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2523 &serial, NULL, NULL, NULL, 0);
2525 else {
2526 static const WCHAR fmt[] = {'%','s','\\','\0'};
2527 if ((path[1] != ':') || (strlenW(path) != 2)) {
2528 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2529 return 0;
2531 wsprintfW (curdir, fmt, path);
2532 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2533 &serial, NULL,
2534 NULL, NULL, 0);
2536 if (!status) {
2537 WCMD_print_error ();
2538 return 0;
2540 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2541 curdir[0], label, HIWORD(serial), LOWORD(serial));
2542 if (mode) {
2543 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2544 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2545 sizeof(string)/sizeof(WCHAR), &count, NULL);
2546 if (count > 1) {
2547 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2548 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2550 if (strlenW(path) != 0) {
2551 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2553 else {
2554 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2557 return 1;
2560 /**************************************************************************
2561 * WCMD_exit
2563 * Exit either the process, or just this batch program
2567 void WCMD_exit (CMD_LIST **cmdList) {
2569 static const WCHAR parmB[] = {'/','B','\0'};
2570 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2572 if (context && lstrcmpiW(quals, parmB) == 0) {
2573 errorlevel = rc;
2574 context -> skip_rest = TRUE;
2575 *cmdList = NULL;
2576 } else {
2577 ExitProcess(rc);
2582 /*****************************************************************************
2583 * WCMD_assoc
2585 * Lists or sets file associations (assoc = TRUE)
2586 * Lists or sets file types (assoc = FALSE)
2588 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2590 HKEY key;
2591 DWORD accessOptions = KEY_READ;
2592 WCHAR *newValue;
2593 LONG rc = ERROR_SUCCESS;
2594 WCHAR keyValue[MAXSTRING];
2595 DWORD valueLen = MAXSTRING;
2596 HKEY readKey;
2597 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2598 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2600 /* See if parameter includes '=' */
2601 errorlevel = 0;
2602 newValue = strchrW(command, '=');
2603 if (newValue) accessOptions |= KEY_WRITE;
2605 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2606 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2607 accessOptions, &key) != ERROR_SUCCESS) {
2608 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2609 return;
2612 /* If no parameters then list all associations */
2613 if (*command == 0x00) {
2614 int index = 0;
2616 /* Enumerate all the keys */
2617 while (rc != ERROR_NO_MORE_ITEMS) {
2618 WCHAR keyName[MAXSTRING];
2619 DWORD nameLen;
2621 /* Find the next value */
2622 nameLen = MAXSTRING;
2623 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2625 if (rc == ERROR_SUCCESS) {
2627 /* Only interested in extension ones if assoc, or others
2628 if not assoc */
2629 if ((keyName[0] == '.' && assoc) ||
2630 (!(keyName[0] == '.') && (!assoc)))
2632 WCHAR subkey[MAXSTRING];
2633 strcpyW(subkey, keyName);
2634 if (!assoc) strcatW(subkey, shOpCmdW);
2636 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2638 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2639 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2640 WCMD_output_asis(keyName);
2641 WCMD_output_asis(equalW);
2642 /* If no default value found, leave line empty after '=' */
2643 if (rc == ERROR_SUCCESS) {
2644 WCMD_output_asis(keyValue);
2646 WCMD_output_asis(newline);
2647 RegCloseKey(readKey);
2653 } else {
2655 /* Parameter supplied - if no '=' on command line, its a query */
2656 if (newValue == NULL) {
2657 WCHAR *space;
2658 WCHAR subkey[MAXSTRING];
2660 /* Query terminates the parameter at the first space */
2661 strcpyW(keyValue, command);
2662 space = strchrW(keyValue, ' ');
2663 if (space) *space=0x00;
2665 /* Set up key name */
2666 strcpyW(subkey, keyValue);
2667 if (!assoc) strcatW(subkey, shOpCmdW);
2669 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2671 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2672 WCMD_output_asis(command);
2673 WCMD_output_asis(equalW);
2674 /* If no default value found, leave line empty after '=' */
2675 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2676 WCMD_output_asis(newline);
2677 RegCloseKey(readKey);
2679 } else {
2680 WCHAR msgbuffer[MAXSTRING];
2681 WCHAR outbuffer[MAXSTRING];
2683 /* Load the translated 'File association not found' */
2684 if (assoc) {
2685 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2686 } else {
2687 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2689 wsprintfW(outbuffer, msgbuffer, keyValue);
2690 WCMD_output_asis(outbuffer);
2691 errorlevel = 2;
2694 /* Not a query - its a set or clear of a value */
2695 } else {
2697 WCHAR subkey[MAXSTRING];
2699 /* Get pointer to new value */
2700 *newValue = 0x00;
2701 newValue++;
2703 /* Set up key name */
2704 strcpyW(subkey, command);
2705 if (!assoc) strcatW(subkey, shOpCmdW);
2707 /* If nothing after '=' then clear value - only valid for ASSOC */
2708 if (*newValue == 0x00) {
2710 if (assoc) rc = RegDeleteKeyW(key, command);
2711 if (assoc && rc == ERROR_SUCCESS) {
2712 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2714 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2715 WCMD_print_error();
2716 errorlevel = 2;
2718 } else {
2719 WCHAR msgbuffer[MAXSTRING];
2720 WCHAR outbuffer[MAXSTRING];
2722 /* Load the translated 'File association not found' */
2723 if (assoc) {
2724 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2725 sizeof(msgbuffer)/sizeof(WCHAR));
2726 } else {
2727 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2728 sizeof(msgbuffer)/sizeof(WCHAR));
2730 wsprintfW(outbuffer, msgbuffer, keyValue);
2731 WCMD_output_asis(outbuffer);
2732 errorlevel = 2;
2735 /* It really is a set value = contents */
2736 } else {
2737 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2738 accessOptions, NULL, &readKey, NULL);
2739 if (rc == ERROR_SUCCESS) {
2740 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2741 (LPBYTE)newValue, strlenW(newValue));
2742 RegCloseKey(readKey);
2745 if (rc != ERROR_SUCCESS) {
2746 WCMD_print_error();
2747 errorlevel = 2;
2748 } else {
2749 WCMD_output_asis(command);
2750 WCMD_output_asis(equalW);
2751 WCMD_output_asis(newValue);
2752 WCMD_output_asis(newline);
2758 /* Clean up */
2759 RegCloseKey(key);
2762 /****************************************************************************
2763 * WCMD_color
2765 * Clear the terminal screen.
2768 void WCMD_color (void) {
2770 /* Emulate by filling the screen from the top left to bottom right with
2771 spaces, then moving the cursor to the top left afterwards */
2772 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2773 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2775 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2776 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2777 return;
2780 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2782 COORD topLeft;
2783 DWORD screenSize;
2784 DWORD color = 0;
2786 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2788 topLeft.X = 0;
2789 topLeft.Y = 0;
2791 /* Convert the color hex digits */
2792 if (param1[0] == 0x00) {
2793 color = defaultColor;
2794 } else {
2795 color = strtoulW(param1, NULL, 16);
2798 /* Fail if fg == bg color */
2799 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2800 errorlevel = 1;
2801 return;
2804 /* Set the current screen contents and ensure all future writes
2805 remain this color */
2806 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2807 SetConsoleTextAttribute(hStdOut, color);