gdiplus/tests: Use Tahoma instead of Courier New for testing font metrics.
[wine/hacks.git] / programs / cmd / builtins.c
blob7a4f9a1d3cbe849ed7f65f0b01abd4fe326a5ab8
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 WCHAR srcspec[MAX_PATH];
179 DWORD attribs;
180 WCHAR drive[10];
181 WCHAR dir[MAX_PATH];
182 WCHAR fname[MAX_PATH];
183 WCHAR ext[MAX_PATH];
185 if (param1[0] == 0x00) {
186 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
187 return;
190 /* Convert source into full spec */
191 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
192 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
193 if (srcpath[strlenW(srcpath) - 1] == '\\')
194 srcpath[strlenW(srcpath) - 1] = '\0';
196 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
197 attribs = GetFileAttributesW(srcpath);
198 } else {
199 attribs = 0;
201 strcpyW(srcspec, srcpath);
203 /* If a directory, then add \* on the end when searching */
204 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
205 strcatW(srcpath, slashW);
206 strcatW(srcspec, slashW);
207 strcatW(srcspec, starW);
208 } else {
209 WCMD_splitpath(srcpath, drive, dir, fname, ext);
210 strcpyW(srcpath, drive);
211 strcatW(srcpath, dir);
214 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
216 /* If no destination supplied, assume current directory */
217 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
218 if (param2[0] == 0x00) {
219 strcpyW(param2, dotW);
222 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
223 if (outpath[strlenW(outpath) - 1] == '\\')
224 outpath[strlenW(outpath) - 1] = '\0';
225 attribs = GetFileAttributesW(outpath);
226 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
227 strcatW (outpath, slashW);
228 copyToDir = TRUE;
230 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
231 wine_dbgstr_w(outpath), copyToDir);
233 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
234 if (strstrW (quals, parmNoY))
235 force = FALSE;
236 else if (strstrW (quals, parmY))
237 force = TRUE;
238 else {
239 /* By default, we will force the overwrite in batch mode and ask for
240 * confirmation in interactive mode. */
241 force = !!context;
243 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
244 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
245 * default behavior. */
246 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
247 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
248 if (!lstrcmpiW (copycmd, parmY))
249 force = TRUE;
250 else if (!lstrcmpiW (copycmd, parmNoY))
251 force = FALSE;
255 /* Loop through all source files */
256 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
257 hff = FindFirstFileW(srcspec, &fd);
258 if (hff != INVALID_HANDLE_VALUE) {
259 do {
260 WCHAR outname[MAX_PATH];
261 WCHAR srcname[MAX_PATH];
262 BOOL overwrite = force;
264 /* Destination is either supplied filename, or source name in
265 supplied destination directory */
266 strcpyW(outname, outpath);
267 if (copyToDir) strcatW(outname, fd.cFileName);
268 strcpyW(srcname, srcpath);
269 strcatW(srcname, fd.cFileName);
271 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
272 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
274 /* Skip . and .., and directories */
275 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
276 overwrite = FALSE;
277 WINE_TRACE("Skipping directories\n");
280 /* Prompt before overwriting */
281 else if (!overwrite) {
282 attribs = GetFileAttributesW(outname);
283 if (attribs != INVALID_FILE_ATTRIBUTES) {
284 WCHAR buffer[MAXSTRING];
285 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
286 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
288 else overwrite = TRUE;
291 /* Do the copy as appropriate */
292 if (overwrite) {
293 status = CopyFileW(srcname, outname, FALSE);
294 if (!status) WCMD_print_error ();
297 } while (FindNextFileW(hff, &fd) != 0);
298 FindClose (hff);
299 } else {
300 status = ERROR_FILE_NOT_FOUND;
301 WCMD_print_error ();
305 /****************************************************************************
306 * WCMD_create_dir
308 * Create a directory.
310 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
311 * they do not already exist.
314 static BOOL create_full_path(WCHAR* path)
316 int len;
317 WCHAR *new_path;
318 BOOL ret = TRUE;
320 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path)+1) * sizeof(WCHAR));
321 strcpyW(new_path,path);
323 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
324 new_path[len - 1] = 0;
326 while (!CreateDirectoryW(new_path,NULL))
328 WCHAR *slash;
329 DWORD last_error = GetLastError();
330 if (last_error == ERROR_ALREADY_EXISTS)
331 break;
333 if (last_error != ERROR_PATH_NOT_FOUND)
335 ret = FALSE;
336 break;
339 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
341 ret = FALSE;
342 break;
345 len = slash - new_path;
346 new_path[len] = 0;
347 if (!create_full_path(new_path))
349 ret = FALSE;
350 break;
352 new_path[len] = '\\';
354 HeapFree(GetProcessHeap(),0,new_path);
355 return ret;
358 void WCMD_create_dir (void) {
360 if (param1[0] == 0x00) {
361 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
362 return;
364 if (!create_full_path(param1)) WCMD_print_error ();
367 /****************************************************************************
368 * WCMD_delete
370 * Delete a file or wildcarded set.
372 * Note on /A:
373 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
374 * - Each set is a pattern, eg /ahr /as-r means
375 * readonly+hidden OR nonreadonly system files
376 * - The '-' applies to a single field, ie /a:-hr means read only
377 * non-hidden files
380 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
382 int argno = 0;
383 int argsProcessed = 0;
384 WCHAR *argN = command;
385 BOOL foundAny = FALSE;
386 static const WCHAR parmA[] = {'/','A','\0'};
387 static const WCHAR parmQ[] = {'/','Q','\0'};
388 static const WCHAR parmP[] = {'/','P','\0'};
389 static const WCHAR parmS[] = {'/','S','\0'};
390 static const WCHAR parmF[] = {'/','F','\0'};
392 /* If not recursing, clear error flag */
393 if (expectDir) errorlevel = 0;
395 /* Loop through all args */
396 while (argN) {
397 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
398 WCHAR argCopy[MAX_PATH];
400 if (argN && argN[0] != '/') {
402 WIN32_FIND_DATAW fd;
403 HANDLE hff;
404 WCHAR fpath[MAX_PATH];
405 WCHAR *p;
406 BOOL handleParm = TRUE;
407 BOOL found = FALSE;
408 static const WCHAR anyExt[]= {'.','*','\0'};
410 strcpyW(argCopy, thisArg);
411 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
412 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
413 argsProcessed++;
415 /* If filename part of parameter is * or *.*, prompt unless
416 /Q supplied. */
417 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
419 WCHAR drive[10];
420 WCHAR dir[MAX_PATH];
421 WCHAR fname[MAX_PATH];
422 WCHAR ext[MAX_PATH];
424 /* Convert path into actual directory spec */
425 GetFullPathNameW(argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
426 WCMD_splitpath(fpath, drive, dir, fname, ext);
428 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
429 if ((strcmpW(fname, starW) == 0) &&
430 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
431 BOOL ok;
432 WCHAR question[MAXSTRING];
433 static const WCHAR fmt[] = {'%','s',' ','\0'};
435 /* Note: Flag as found, to avoid file not found message */
436 found = TRUE;
438 /* Ask for confirmation */
439 wsprintfW(question, fmt, fpath);
440 ok = WCMD_ask_confirm(question, TRUE, NULL);
442 /* Abort if answer is 'N' */
443 if (!ok) continue;
447 /* First, try to delete in the current directory */
448 hff = FindFirstFileW(argCopy, &fd);
449 if (hff == INVALID_HANDLE_VALUE) {
450 handleParm = FALSE;
451 } else {
452 found = TRUE;
455 /* Support del <dirname> by just deleting all files dirname\* */
456 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
457 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
458 WCHAR modifiedParm[MAX_PATH];
459 static const WCHAR slashStar[] = {'\\','*','\0'};
461 strcpyW(modifiedParm, argCopy);
462 strcatW(modifiedParm, slashStar);
463 FindClose(hff);
464 found = TRUE;
465 WCMD_delete(modifiedParm, FALSE);
467 } else if (handleParm) {
469 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
470 strcpyW (fpath, argCopy);
471 do {
472 p = strrchrW (fpath, '\\');
473 if (p != NULL) {
474 *++p = '\0';
475 strcatW (fpath, fd.cFileName);
477 else strcpyW (fpath, fd.cFileName);
478 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
479 BOOL ok = TRUE;
480 WCHAR *nextA = strstrW (quals, parmA);
482 /* Handle attribute matching (/A) */
483 if (nextA != NULL) {
484 ok = FALSE;
485 while (nextA != NULL && !ok) {
487 WCHAR *thisA = (nextA+2);
488 BOOL stillOK = TRUE;
490 /* Skip optional : */
491 if (*thisA == ':') thisA++;
493 /* Parse each of the /A[:]xxx in turn */
494 while (*thisA && *thisA != '/') {
495 BOOL negate = FALSE;
496 BOOL attribute = FALSE;
498 /* Match negation of attribute first */
499 if (*thisA == '-') {
500 negate=TRUE;
501 thisA++;
504 /* Match attribute */
505 switch (*thisA) {
506 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
507 break;
508 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
509 break;
510 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
511 break;
512 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
513 break;
514 default:
515 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
518 /* Now check result, keeping a running boolean about whether it
519 matches all parsed attributes so far */
520 if (attribute && !negate) {
521 stillOK = stillOK;
522 } else if (!attribute && negate) {
523 stillOK = stillOK;
524 } else {
525 stillOK = FALSE;
527 thisA++;
530 /* Save the running total as the final result */
531 ok = stillOK;
533 /* Step on to next /A set */
534 nextA = strstrW (nextA+1, parmA);
538 /* /P means prompt for each file */
539 if (ok && strstrW (quals, parmP) != NULL) {
540 WCHAR question[MAXSTRING];
542 /* Ask for confirmation */
543 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
544 ok = WCMD_ask_confirm(question, FALSE, NULL);
547 /* Only proceed if ok to */
548 if (ok) {
550 /* If file is read only, and /F supplied, delete it */
551 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
552 strstrW (quals, parmF) != NULL) {
553 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
556 /* Now do the delete */
557 if (!DeleteFileW(fpath)) WCMD_print_error ();
561 } while (FindNextFileW(hff, &fd) != 0);
562 FindClose (hff);
565 /* Now recurse into all subdirectories handling the parameter in the same way */
566 if (strstrW (quals, parmS) != NULL) {
568 WCHAR thisDir[MAX_PATH];
569 int cPos;
571 WCHAR drive[10];
572 WCHAR dir[MAX_PATH];
573 WCHAR fname[MAX_PATH];
574 WCHAR ext[MAX_PATH];
576 /* Convert path into actual directory spec */
577 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
578 WCMD_splitpath(thisDir, drive, dir, fname, ext);
580 strcpyW(thisDir, drive);
581 strcatW(thisDir, dir);
582 cPos = strlenW(thisDir);
584 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
586 /* Append '*' to the directory */
587 thisDir[cPos] = '*';
588 thisDir[cPos+1] = 0x00;
590 hff = FindFirstFileW(thisDir, &fd);
592 /* Remove residual '*' */
593 thisDir[cPos] = 0x00;
595 if (hff != INVALID_HANDLE_VALUE) {
596 DIRECTORY_STACK *allDirs = NULL;
597 DIRECTORY_STACK *lastEntry = NULL;
599 do {
600 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
601 (strcmpW(fd.cFileName, dotdotW) != 0) &&
602 (strcmpW(fd.cFileName, dotW) != 0)) {
604 DIRECTORY_STACK *nextDir;
605 WCHAR subParm[MAX_PATH];
607 /* Work out search parameter in sub dir */
608 strcpyW (subParm, thisDir);
609 strcatW (subParm, fd.cFileName);
610 strcatW (subParm, slashW);
611 strcatW (subParm, fname);
612 strcatW (subParm, ext);
613 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
615 /* Allocate memory, add to list */
616 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
617 if (allDirs == NULL) allDirs = nextDir;
618 if (lastEntry != NULL) lastEntry->next = nextDir;
619 lastEntry = nextDir;
620 nextDir->next = NULL;
621 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
622 (strlenW(subParm)+1) * sizeof(WCHAR));
623 strcpyW(nextDir->dirName, subParm);
625 } while (FindNextFileW(hff, &fd) != 0);
626 FindClose (hff);
628 /* Go through each subdir doing the delete */
629 while (allDirs != NULL) {
630 DIRECTORY_STACK *tempDir;
632 tempDir = allDirs->next;
633 found |= WCMD_delete (allDirs->dirName, FALSE);
635 HeapFree(GetProcessHeap(),0,allDirs->dirName);
636 HeapFree(GetProcessHeap(),0,allDirs);
637 allDirs = tempDir;
641 /* Keep running total to see if any found, and if not recursing
642 issue error message */
643 if (expectDir) {
644 if (!found) {
645 errorlevel = 1;
646 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
649 foundAny |= found;
653 /* Handle no valid args */
654 if (argsProcessed == 0) {
655 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
658 return foundAny;
661 /****************************************************************************
662 * WCMD_echo
664 * Echo input to the screen (or not). We don't try to emulate the bugs
665 * in DOS (try typing "ECHO ON AGAIN" for an example).
668 void WCMD_echo (const WCHAR *command) {
670 int count;
672 if ((command[0] == '.') && (command[1] == 0)) {
673 WCMD_output (newline);
674 return;
676 if (command[0]==' ')
677 command++;
678 count = strlenW(command);
679 if (count == 0) {
680 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
681 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
682 return;
684 if (lstrcmpiW(command, onW) == 0) {
685 echo_mode = 1;
686 return;
688 if (lstrcmpiW(command, offW) == 0) {
689 echo_mode = 0;
690 return;
692 WCMD_output_asis (command);
693 WCMD_output (newline);
697 /**************************************************************************
698 * WCMD_for
700 * Batch file loop processing.
702 * On entry: cmdList contains the syntax up to the set
703 * next cmdList and all in that bracket contain the set data
704 * next cmdlist contains the DO cmd
705 * following that is either brackets or && entries (as per if)
709 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
711 WIN32_FIND_DATAW fd;
712 HANDLE hff;
713 int i;
714 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
715 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
716 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
717 WCHAR variable[4];
718 WCHAR *firstCmd;
719 int thisDepth;
721 WCHAR *curPos = p;
722 BOOL expandDirs = FALSE;
723 BOOL useNumbers = FALSE;
724 BOOL doFileset = FALSE;
725 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
726 int itemNum;
727 CMD_LIST *thisCmdStart;
730 /* Handle optional qualifiers (multiple are allowed) */
731 while (*curPos && *curPos == '/') {
732 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
733 curPos++;
734 switch (toupperW(*curPos)) {
735 case 'D': curPos++; expandDirs = TRUE; break;
736 case 'L': curPos++; useNumbers = TRUE; break;
738 /* Recursive is special case - /R can have an optional path following it */
739 /* filenamesets are another special case - /F can have an optional options following it */
740 case 'R':
741 case 'F':
743 BOOL isRecursive = (*curPos == 'R');
745 if (!isRecursive)
746 doFileset = TRUE;
748 /* Skip whitespace */
749 curPos++;
750 while (*curPos && *curPos==' ') curPos++;
752 /* Next parm is either qualifier, path/options or variable -
753 only care about it if it is the path/options */
754 if (*curPos && *curPos != '/' && *curPos != '%') {
755 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
756 else WINE_FIXME("/F needs to handle options\n");
758 break;
760 default:
761 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
762 curPos++;
765 /* Skip whitespace between qualifiers */
766 while (*curPos && *curPos==' ') curPos++;
769 /* Skip whitespace before variable */
770 while (*curPos && *curPos==' ') curPos++;
772 /* Ensure line continues with variable */
773 if (!*curPos || *curPos != '%') {
774 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
775 return;
778 /* Variable should follow */
779 i = 0;
780 while (curPos[i] && curPos[i]!=' ') i++;
781 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
782 variable[i] = 0x00;
783 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
784 curPos = &curPos[i];
786 /* Skip whitespace before IN */
787 while (*curPos && *curPos==' ') curPos++;
789 /* Ensure line continues with IN */
790 if (!*curPos || lstrcmpiW (curPos, inW)) {
791 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
792 return;
795 /* Save away where the set of data starts and the variable */
796 thisDepth = (*cmdList)->bracketDepth;
797 *cmdList = (*cmdList)->nextcommand;
798 setStart = (*cmdList);
800 /* Skip until the close bracket */
801 WINE_TRACE("Searching %p as the set\n", *cmdList);
802 while (*cmdList &&
803 (*cmdList)->command != NULL &&
804 (*cmdList)->bracketDepth > thisDepth) {
805 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
806 *cmdList = (*cmdList)->nextcommand;
809 /* Skip the close bracket, if there is one */
810 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
812 /* Syntax error if missing close bracket, or nothing following it
813 and once we have the complete set, we expect a DO */
814 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
815 if ((*cmdList == NULL) ||
816 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
817 (*cmdList)->command, 3, doW, -1) != 2)) {
818 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
819 return;
822 /* Save away the starting position for the commands (and offset for the
823 first one */
824 cmdStart = *cmdList;
825 cmdEnd = *cmdList;
826 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
827 itemNum = 0;
829 thisSet = setStart;
830 /* Loop through all set entries */
831 while (thisSet &&
832 thisSet->command != NULL &&
833 thisSet->bracketDepth >= thisDepth) {
835 /* Loop through all entries on the same line */
836 WCHAR *item;
837 WCHAR *itemStart;
839 WINE_TRACE("Processing for set %p\n", thisSet);
840 i = 0;
841 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
844 * If the parameter within the set has a wildcard then search for matching files
845 * otherwise do a literal substitution.
847 static const WCHAR wildcards[] = {'*','?','\0'};
848 thisCmdStart = cmdStart;
850 itemNum++;
851 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
853 if (!useNumbers && !doFileset) {
854 if (strpbrkW (item, wildcards)) {
855 hff = FindFirstFileW(item, &fd);
856 if (hff != INVALID_HANDLE_VALUE) {
857 do {
858 BOOL isDirectory = FALSE;
860 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
862 /* Handle as files or dirs appropriately, but ignore . and .. */
863 if (isDirectory == expandDirs &&
864 (strcmpW(fd.cFileName, dotdotW) != 0) &&
865 (strcmpW(fd.cFileName, dotW) != 0))
867 thisCmdStart = cmdStart;
868 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
869 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
870 fd.cFileName, FALSE, TRUE);
873 } while (FindNextFileW(hff, &fd) != 0);
874 FindClose (hff);
876 } else {
877 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
880 } else if (useNumbers) {
881 /* Convert the first 3 numbers to signed longs and save */
882 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
883 /* else ignore them! */
885 /* Filesets - either a list of files, or a command to run and parse the output */
886 } else if (doFileset && *itemStart != '"') {
888 HANDLE input;
889 WCHAR temp_file[MAX_PATH];
891 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
892 wine_dbgstr_w(item));
894 /* If backquote or single quote, we need to launch that command
895 and parse the results - use a temporary file */
896 if (*itemStart == '`' || *itemStart == '\'') {
898 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
899 static const WCHAR redirOut[] = {'>','%','s','\0'};
900 static const WCHAR cmdW[] = {'C','M','D','\0'};
902 /* Remove trailing character */
903 itemStart[strlenW(itemStart)-1] = 0x00;
905 /* Get temp filename */
906 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
907 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
909 /* Execute program and redirect output */
910 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
911 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
913 /* Open the file, read line by line and process */
914 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
915 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
916 } else {
918 /* Open the file, read line by line and process */
919 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
920 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
923 /* Process the input file */
924 if (input == INVALID_HANDLE_VALUE) {
925 WCMD_print_error ();
926 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
927 errorlevel = 1;
928 return; /* FOR loop aborts at first failure here */
930 } else {
932 WCHAR buffer[MAXSTRING] = {'\0'};
933 WCHAR *where, *parm;
935 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
937 /* Skip blank lines*/
938 parm = WCMD_parameter (buffer, 0, &where);
939 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
940 wine_dbgstr_w(buffer));
942 if (where) {
943 /* FIXME: The following should be moved into its own routine and
944 reused for the string literal parsing below */
945 thisCmdStart = cmdStart;
946 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
947 cmdEnd = thisCmdStart;
950 buffer[0] = 0x00;
953 CloseHandle (input);
956 /* Delete the temporary file */
957 if (*itemStart == '`' || *itemStart == '\'') {
958 DeleteFileW(temp_file);
961 /* Filesets - A string literal */
962 } else if (doFileset && *itemStart == '"') {
963 WCHAR buffer[MAXSTRING] = {'\0'};
964 WCHAR *where, *parm;
966 /* Skip blank lines, and re-extract parameter now string has quotes removed */
967 strcpyW(buffer, item);
968 parm = WCMD_parameter (buffer, 0, &where);
969 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
970 wine_dbgstr_w(buffer));
972 if (where) {
973 /* FIXME: The following should be moved into its own routine and
974 reused for the string literal parsing below */
975 thisCmdStart = cmdStart;
976 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
977 cmdEnd = thisCmdStart;
981 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
982 cmdEnd = thisCmdStart;
983 i++;
986 /* Move onto the next set line */
987 thisSet = thisSet->nextcommand;
990 /* If /L is provided, now run the for loop */
991 if (useNumbers) {
992 WCHAR thisNum[20];
993 static const WCHAR fmt[] = {'%','d','\0'};
995 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
996 numbers[0], numbers[2], numbers[1]);
997 for (i=numbers[0];
998 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
999 i=i + numbers[1]) {
1001 sprintfW(thisNum, fmt, i);
1002 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1004 thisCmdStart = cmdStart;
1005 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1006 cmdEnd = thisCmdStart;
1010 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1011 all processing, OR it should be pointing to the end of && processing OR
1012 it should be pointing at the NULL end of bracket for the DO. The return
1013 value needs to be the NEXT command to execute, which it either is, or
1014 we need to step over the closing bracket */
1015 *cmdList = cmdEnd;
1016 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1020 /*****************************************************************************
1021 * WCMD_part_execute
1023 * Execute a command, and any && or bracketed follow on to the command. The
1024 * first command to be executed may not be at the front of the
1025 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1027 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1028 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1030 CMD_LIST *curPosition = *cmdList;
1031 int myDepth = (*cmdList)->bracketDepth;
1033 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1034 cmdList, wine_dbgstr_w(firstcmd),
1035 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1036 conditionTRUE);
1038 /* Skip leading whitespace between condition and the command */
1039 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1041 /* Process the first command, if there is one */
1042 if (conditionTRUE && firstcmd && *firstcmd) {
1043 WCHAR *command = WCMD_strdupW(firstcmd);
1044 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1045 HeapFree(GetProcessHeap(), 0, command);
1049 /* If it didn't move the position, step to next command */
1050 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1052 /* Process any other parts of the command */
1053 if (*cmdList) {
1054 BOOL processThese = TRUE;
1056 if (isIF) processThese = conditionTRUE;
1058 while (*cmdList) {
1059 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1061 /* execute all appropriate commands */
1062 curPosition = *cmdList;
1064 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1065 *cmdList,
1066 (*cmdList)->prevDelim,
1067 (*cmdList)->bracketDepth, myDepth);
1069 /* Execute any statements appended to the line */
1070 /* FIXME: Only if previous call worked for && or failed for || */
1071 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1072 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1073 if (processThese && (*cmdList)->command) {
1074 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1075 value, cmdList);
1077 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1079 /* Execute any appended to the statement with (...) */
1080 } else if ((*cmdList)->bracketDepth > myDepth) {
1081 if (processThese) {
1082 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1083 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1085 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1087 /* End of the command - does 'ELSE ' follow as the next command? */
1088 } else {
1089 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1090 NORM_IGNORECASE | SORT_STRINGSORT,
1091 (*cmdList)->command, 5, ifElse, -1) == 2) {
1093 /* Swap between if and else processing */
1094 processThese = !processThese;
1096 /* Process the ELSE part */
1097 if (processThese) {
1098 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1100 /* Skip leading whitespace between condition and the command */
1101 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1102 if (*cmd) {
1103 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1106 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1107 } else {
1108 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1109 break;
1114 return;
1117 /**************************************************************************
1118 * WCMD_give_help
1120 * Simple on-line help. Help text is stored in the resource file.
1123 void WCMD_give_help (WCHAR *command) {
1125 int i;
1127 command = WCMD_strtrim_leading_spaces(command);
1128 if (strlenW(command) == 0) {
1129 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1131 else {
1132 for (i=0; i<=WCMD_EXIT; i++) {
1133 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1134 command, -1, inbuilt[i], -1) == 2) {
1135 WCMD_output_asis (WCMD_LoadMessage(i));
1136 return;
1139 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1141 return;
1144 /****************************************************************************
1145 * WCMD_go_to
1147 * Batch file jump instruction. Not the most efficient algorithm ;-)
1148 * Prints error message if the specified label cannot be found - the file pointer is
1149 * then at EOF, effectively stopping the batch file.
1150 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1153 void WCMD_goto (CMD_LIST **cmdList) {
1155 WCHAR string[MAX_PATH];
1157 /* Do not process any more parts of a processed multipart or multilines command */
1158 if (cmdList) *cmdList = NULL;
1160 if (param1[0] == 0x00) {
1161 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1162 return;
1164 if (context != NULL) {
1165 WCHAR *paramStart = param1, *str;
1166 static const WCHAR eofW[] = {':','e','o','f','\0'};
1168 /* Handle special :EOF label */
1169 if (lstrcmpiW (eofW, param1) == 0) {
1170 context -> skip_rest = TRUE;
1171 return;
1174 /* Support goto :label as well as goto label */
1175 if (*paramStart == ':') paramStart++;
1177 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1178 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1179 str = string;
1180 while (isspaceW(*str)) str++;
1181 if ((*str == ':') && (lstrcmpiW (++str, paramStart) == 0)) return;
1183 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1185 return;
1188 /*****************************************************************************
1189 * WCMD_pushd
1191 * Push a directory onto the stack
1194 void WCMD_pushd (WCHAR *command) {
1195 struct env_stack *curdir;
1196 WCHAR *thisdir;
1197 static const WCHAR parmD[] = {'/','D','\0'};
1199 if (strchrW(command, '/') != NULL) {
1200 SetLastError(ERROR_INVALID_PARAMETER);
1201 WCMD_print_error();
1202 return;
1205 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1206 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1207 if( !curdir || !thisdir ) {
1208 LocalFree(curdir);
1209 LocalFree(thisdir);
1210 WINE_ERR ("out of memory\n");
1211 return;
1214 /* Change directory using CD code with /D parameter */
1215 strcpyW(quals, parmD);
1216 GetCurrentDirectoryW (1024, thisdir);
1217 errorlevel = 0;
1218 WCMD_setshow_default(command);
1219 if (errorlevel) {
1220 LocalFree(curdir);
1221 LocalFree(thisdir);
1222 return;
1223 } else {
1224 curdir -> next = pushd_directories;
1225 curdir -> strings = thisdir;
1226 if (pushd_directories == NULL) {
1227 curdir -> u.stackdepth = 1;
1228 } else {
1229 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1231 pushd_directories = curdir;
1236 /*****************************************************************************
1237 * WCMD_popd
1239 * Pop a directory from the stack
1242 void WCMD_popd (void) {
1243 struct env_stack *temp = pushd_directories;
1245 if (!pushd_directories)
1246 return;
1248 /* pop the old environment from the stack, and make it the current dir */
1249 pushd_directories = temp->next;
1250 SetCurrentDirectoryW(temp->strings);
1251 LocalFree (temp->strings);
1252 LocalFree (temp);
1255 /****************************************************************************
1256 * WCMD_if
1258 * Batch file conditional.
1260 * On entry, cmdlist will point to command containing the IF, and optionally
1261 * the first command to execute (if brackets not found)
1262 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1263 * If ('s were found, execute all within that bracket
1264 * Command may optionally be followed by an ELSE - need to skip instructions
1265 * in the else using the same logic
1267 * FIXME: Much more syntax checking needed!
1270 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1272 int negate = 0, test = 0;
1273 WCHAR condition[MAX_PATH], *command, *s;
1274 static const WCHAR notW[] = {'n','o','t','\0'};
1275 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1276 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1277 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1278 static const WCHAR eqeqW[] = {'=','=','\0'};
1279 static const WCHAR parmI[] = {'/','I','\0'};
1281 if (!lstrcmpiW (param1, notW)) {
1282 negate = 1;
1283 strcpyW (condition, param2);
1285 else {
1286 strcpyW (condition, param1);
1288 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1290 if (!lstrcmpiW (condition, errlvlW)) {
1291 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1292 WCMD_parameter (p, 2+negate, &command);
1294 else if (!lstrcmpiW (condition, existW)) {
1295 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1296 test = 1;
1298 WCMD_parameter (p, 2+negate, &command);
1300 else if (!lstrcmpiW (condition, defdW)) {
1301 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1302 test = 1;
1304 WCMD_parameter (p, 2+negate, &command);
1306 else if ((s = strstrW (p, eqeqW))) {
1307 s += 2;
1308 if (strstrW (quals, parmI) == NULL) {
1309 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1311 else {
1312 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1314 WCMD_parameter (s, 1, &command);
1316 else {
1317 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1318 return;
1321 /* Process rest of IF statement which is on the same line
1322 Note: This may process all or some of the cmdList (eg a GOTO) */
1323 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1326 /****************************************************************************
1327 * WCMD_move
1329 * Move a file, directory tree or wildcarded set of files.
1332 void WCMD_move (void) {
1334 int status;
1335 WIN32_FIND_DATAW fd;
1336 HANDLE hff;
1337 WCHAR input[MAX_PATH];
1338 WCHAR output[MAX_PATH];
1339 WCHAR drive[10];
1340 WCHAR dir[MAX_PATH];
1341 WCHAR fname[MAX_PATH];
1342 WCHAR ext[MAX_PATH];
1344 if (param1[0] == 0x00) {
1345 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1346 return;
1349 /* If no destination supplied, assume current directory */
1350 if (param2[0] == 0x00) {
1351 strcpyW(param2, dotW);
1354 /* If 2nd parm is directory, then use original filename */
1355 /* Convert partial path to full path */
1356 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1357 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1358 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1359 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1361 /* Split into components */
1362 WCMD_splitpath(input, drive, dir, fname, ext);
1364 hff = FindFirstFileW(input, &fd);
1365 while (hff != INVALID_HANDLE_VALUE) {
1366 WCHAR dest[MAX_PATH];
1367 WCHAR src[MAX_PATH];
1368 DWORD attribs;
1370 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1372 /* Build src & dest name */
1373 strcpyW(src, drive);
1374 strcatW(src, dir);
1376 /* See if dest is an existing directory */
1377 attribs = GetFileAttributesW(output);
1378 if (attribs != INVALID_FILE_ATTRIBUTES &&
1379 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1380 strcpyW(dest, output);
1381 strcatW(dest, slashW);
1382 strcatW(dest, fd.cFileName);
1383 } else {
1384 strcpyW(dest, output);
1387 strcatW(src, fd.cFileName);
1389 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1390 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1392 /* Check if file is read only, otherwise move it */
1393 attribs = GetFileAttributesW(src);
1394 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1395 (attribs & FILE_ATTRIBUTE_READONLY)) {
1396 SetLastError(ERROR_ACCESS_DENIED);
1397 status = 0;
1398 } else {
1399 BOOL ok = TRUE;
1401 /* If destination exists, prompt unless /Y supplied */
1402 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1403 BOOL force = FALSE;
1404 WCHAR copycmd[MAXSTRING];
1405 int len;
1407 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1408 if (strstrW (quals, parmNoY))
1409 force = FALSE;
1410 else if (strstrW (quals, parmY))
1411 force = TRUE;
1412 else {
1413 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1414 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1415 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1416 && ! lstrcmpiW (copycmd, parmY));
1419 /* Prompt if overwriting */
1420 if (!force) {
1421 WCHAR question[MAXSTRING];
1422 WCHAR yesChar[10];
1424 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1426 /* Ask for confirmation */
1427 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1428 ok = WCMD_ask_confirm(question, FALSE, NULL);
1430 /* So delete the destination prior to the move */
1431 if (ok) {
1432 if (!DeleteFileW(dest)) {
1433 WCMD_print_error ();
1434 errorlevel = 1;
1435 ok = FALSE;
1441 if (ok) {
1442 status = MoveFileW(src, dest);
1443 } else {
1444 status = 1; /* Anything other than 0 to prevent error msg below */
1448 if (!status) {
1449 WCMD_print_error ();
1450 errorlevel = 1;
1453 /* Step on to next match */
1454 if (FindNextFileW(hff, &fd) == 0) {
1455 FindClose(hff);
1456 hff = INVALID_HANDLE_VALUE;
1457 break;
1462 /****************************************************************************
1463 * WCMD_pause
1465 * Wait for keyboard input.
1468 void WCMD_pause (void) {
1470 DWORD count;
1471 WCHAR string[32];
1473 WCMD_output (anykey);
1474 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1475 sizeof(string)/sizeof(WCHAR), &count, NULL);
1478 /****************************************************************************
1479 * WCMD_remove_dir
1481 * Delete a directory.
1484 void WCMD_remove_dir (WCHAR *command) {
1486 int argno = 0;
1487 int argsProcessed = 0;
1488 WCHAR *argN = command;
1489 static const WCHAR parmS[] = {'/','S','\0'};
1490 static const WCHAR parmQ[] = {'/','Q','\0'};
1492 /* Loop through all args */
1493 while (argN) {
1494 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1495 if (argN && argN[0] != '/') {
1496 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1497 wine_dbgstr_w(quals));
1498 argsProcessed++;
1500 /* If subdirectory search not supplied, just try to remove
1501 and report error if it fails (eg if it contains a file) */
1502 if (strstrW (quals, parmS) == NULL) {
1503 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1505 /* Otherwise use ShFileOp to recursively remove a directory */
1506 } else {
1508 SHFILEOPSTRUCTW lpDir;
1510 /* Ask first */
1511 if (strstrW (quals, parmQ) == NULL) {
1512 BOOL ok;
1513 WCHAR question[MAXSTRING];
1514 static const WCHAR fmt[] = {'%','s',' ','\0'};
1516 /* Ask for confirmation */
1517 wsprintfW(question, fmt, thisArg);
1518 ok = WCMD_ask_confirm(question, TRUE, NULL);
1520 /* Abort if answer is 'N' */
1521 if (!ok) return;
1524 /* Do the delete */
1525 lpDir.hwnd = NULL;
1526 lpDir.pTo = NULL;
1527 lpDir.pFrom = thisArg;
1528 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1529 lpDir.wFunc = FO_DELETE;
1530 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1535 /* Handle no valid args */
1536 if (argsProcessed == 0) {
1537 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1538 return;
1543 /****************************************************************************
1544 * WCMD_rename
1546 * Rename a file.
1549 void WCMD_rename (void) {
1551 int status;
1552 HANDLE hff;
1553 WIN32_FIND_DATAW fd;
1554 WCHAR input[MAX_PATH];
1555 WCHAR *dotDst = NULL;
1556 WCHAR drive[10];
1557 WCHAR dir[MAX_PATH];
1558 WCHAR fname[MAX_PATH];
1559 WCHAR ext[MAX_PATH];
1560 DWORD attribs;
1562 errorlevel = 0;
1564 /* Must be at least two args */
1565 if (param1[0] == 0x00 || param2[0] == 0x00) {
1566 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1567 errorlevel = 1;
1568 return;
1571 /* Destination cannot contain a drive letter or directory separator */
1572 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1573 SetLastError(ERROR_INVALID_PARAMETER);
1574 WCMD_print_error();
1575 errorlevel = 1;
1576 return;
1579 /* Convert partial path to full path */
1580 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1581 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1582 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1583 dotDst = strchrW(param2, '.');
1585 /* Split into components */
1586 WCMD_splitpath(input, drive, dir, fname, ext);
1588 hff = FindFirstFileW(input, &fd);
1589 while (hff != INVALID_HANDLE_VALUE) {
1590 WCHAR dest[MAX_PATH];
1591 WCHAR src[MAX_PATH];
1592 WCHAR *dotSrc = NULL;
1593 int dirLen;
1595 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1597 /* FIXME: If dest name or extension is *, replace with filename/ext
1598 part otherwise use supplied name. This supports:
1599 ren *.fred *.jim
1600 ren jim.* fred.* etc
1601 However, windows has a more complex algorithm supporting eg
1602 ?'s and *'s mid name */
1603 dotSrc = strchrW(fd.cFileName, '.');
1605 /* Build src & dest name */
1606 strcpyW(src, drive);
1607 strcatW(src, dir);
1608 strcpyW(dest, src);
1609 dirLen = strlenW(src);
1610 strcatW(src, fd.cFileName);
1612 /* Build name */
1613 if (param2[0] == '*') {
1614 strcatW(dest, fd.cFileName);
1615 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1616 } else {
1617 strcatW(dest, param2);
1618 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1621 /* Build Extension */
1622 if (dotDst && (*(dotDst+1)=='*')) {
1623 if (dotSrc) strcatW(dest, dotSrc);
1624 } else if (dotDst) {
1625 if (dotDst) strcatW(dest, dotDst);
1628 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1629 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1631 /* Check if file is read only, otherwise move it */
1632 attribs = GetFileAttributesW(src);
1633 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1634 (attribs & FILE_ATTRIBUTE_READONLY)) {
1635 SetLastError(ERROR_ACCESS_DENIED);
1636 status = 0;
1637 } else {
1638 status = MoveFileW(src, dest);
1641 if (!status) {
1642 WCMD_print_error ();
1643 errorlevel = 1;
1646 /* Step on to next match */
1647 if (FindNextFileW(hff, &fd) == 0) {
1648 FindClose(hff);
1649 hff = INVALID_HANDLE_VALUE;
1650 break;
1655 /*****************************************************************************
1656 * WCMD_dupenv
1658 * Make a copy of the environment.
1660 static WCHAR *WCMD_dupenv( const WCHAR *env )
1662 WCHAR *env_copy;
1663 int len;
1665 if( !env )
1666 return NULL;
1668 len = 0;
1669 while ( env[len] )
1670 len += (strlenW(&env[len]) + 1);
1672 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1673 if (!env_copy)
1675 WINE_ERR("out of memory\n");
1676 return env_copy;
1678 memcpy (env_copy, env, len*sizeof (WCHAR));
1679 env_copy[len] = 0;
1681 return env_copy;
1684 /*****************************************************************************
1685 * WCMD_setlocal
1687 * setlocal pushes the environment onto a stack
1688 * Save the environment as unicode so we don't screw anything up.
1690 void WCMD_setlocal (const WCHAR *s) {
1691 WCHAR *env;
1692 struct env_stack *env_copy;
1693 WCHAR cwd[MAX_PATH];
1695 /* DISABLEEXTENSIONS ignored */
1697 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1698 if( !env_copy )
1700 WINE_ERR ("out of memory\n");
1701 return;
1704 env = GetEnvironmentStringsW ();
1706 env_copy->strings = WCMD_dupenv (env);
1707 if (env_copy->strings)
1709 env_copy->next = saved_environment;
1710 saved_environment = env_copy;
1712 /* Save the current drive letter */
1713 GetCurrentDirectoryW(MAX_PATH, cwd);
1714 env_copy->u.cwd = cwd[0];
1716 else
1717 LocalFree (env_copy);
1719 FreeEnvironmentStringsW (env);
1723 /*****************************************************************************
1724 * WCMD_endlocal
1726 * endlocal pops the environment off a stack
1727 * Note: When searching for '=', search from WCHAR position 1, to handle
1728 * special internal environment variables =C:, =D: etc
1730 void WCMD_endlocal (void) {
1731 WCHAR *env, *old, *p;
1732 struct env_stack *temp;
1733 int len, n;
1735 if (!saved_environment)
1736 return;
1738 /* pop the old environment from the stack */
1739 temp = saved_environment;
1740 saved_environment = temp->next;
1742 /* delete the current environment, totally */
1743 env = GetEnvironmentStringsW ();
1744 old = WCMD_dupenv (GetEnvironmentStringsW ());
1745 len = 0;
1746 while (old[len]) {
1747 n = strlenW(&old[len]) + 1;
1748 p = strchrW(&old[len] + 1, '=');
1749 if (p)
1751 *p++ = 0;
1752 SetEnvironmentVariableW (&old[len], NULL);
1754 len += n;
1756 LocalFree (old);
1757 FreeEnvironmentStringsW (env);
1759 /* restore old environment */
1760 env = temp->strings;
1761 len = 0;
1762 while (env[len]) {
1763 n = strlenW(&env[len]) + 1;
1764 p = strchrW(&env[len] + 1, '=');
1765 if (p)
1767 *p++ = 0;
1768 SetEnvironmentVariableW (&env[len], p);
1770 len += n;
1773 /* Restore current drive letter */
1774 if (IsCharAlphaW(temp->u.cwd)) {
1775 WCHAR envvar[4];
1776 WCHAR cwd[MAX_PATH];
1777 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1779 wsprintfW(envvar, fmt, temp->u.cwd);
1780 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1781 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1782 SetCurrentDirectoryW(cwd);
1786 LocalFree (env);
1787 LocalFree (temp);
1790 /*****************************************************************************
1791 * WCMD_setshow_attrib
1793 * Display and optionally sets DOS attributes on a file or directory
1797 void WCMD_setshow_attrib (void) {
1799 DWORD count;
1800 HANDLE hff;
1801 WIN32_FIND_DATAW fd;
1802 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1803 WCHAR *name = param1;
1804 DWORD attrib_set=0;
1805 DWORD attrib_clear=0;
1807 if (param1[0] == '+' || param1[0] == '-') {
1808 DWORD attrib = 0;
1809 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1810 switch (param1[1]) {
1811 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
1812 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
1813 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
1814 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
1815 default:
1816 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1817 return;
1819 switch (param1[0]) {
1820 case '+': attrib_set = attrib; break;
1821 case '-': attrib_clear = attrib; break;
1823 name = param2;
1826 if (strlenW(name) == 0) {
1827 static const WCHAR slashStarW[] = {'\\','*','\0'};
1829 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
1830 strcatW (name, slashStarW);
1833 hff = FindFirstFileW(name, &fd);
1834 if (hff == INVALID_HANDLE_VALUE) {
1835 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
1837 else {
1838 do {
1839 if (attrib_set || attrib_clear) {
1840 fd.dwFileAttributes &= ~attrib_clear;
1841 fd.dwFileAttributes |= attrib_set;
1842 if (!fd.dwFileAttributes)
1843 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
1844 SetFileAttributesW(name, fd.dwFileAttributes);
1845 } else {
1846 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1847 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1848 flags[0] = 'H';
1850 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1851 flags[1] = 'S';
1853 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1854 flags[2] = 'A';
1856 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1857 flags[3] = 'R';
1859 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1860 flags[4] = 'T';
1862 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1863 flags[5] = 'C';
1865 WCMD_output (fmt, flags, fd.cFileName);
1866 for (count=0; count < 8; count++) flags[count] = ' ';
1868 } while (FindNextFileW(hff, &fd) != 0);
1870 FindClose (hff);
1873 /*****************************************************************************
1874 * WCMD_setshow_default
1876 * Set/Show the current default directory
1879 void WCMD_setshow_default (WCHAR *command) {
1881 BOOL status;
1882 WCHAR string[1024];
1883 WCHAR cwd[1024];
1884 WCHAR *pos;
1885 WIN32_FIND_DATAW fd;
1886 HANDLE hff;
1887 static const WCHAR parmD[] = {'/','D','\0'};
1889 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1891 /* Skip /D and trailing whitespace if on the front of the command line */
1892 if (CompareStringW(LOCALE_USER_DEFAULT,
1893 NORM_IGNORECASE | SORT_STRINGSORT,
1894 command, 2, parmD, -1) == 2) {
1895 command += 2;
1896 while (*command && *command==' ') command++;
1899 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
1900 if (strlenW(command) == 0) {
1901 strcatW (cwd, newline);
1902 WCMD_output (cwd);
1904 else {
1905 /* Remove any double quotes, which may be in the
1906 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1907 pos = string;
1908 while (*command) {
1909 if (*command != '"') *pos++ = *command;
1910 command++;
1912 *pos = 0x00;
1914 /* Search for appropriate directory */
1915 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1916 hff = FindFirstFileW(string, &fd);
1917 while (hff != INVALID_HANDLE_VALUE) {
1918 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1919 WCHAR fpath[MAX_PATH];
1920 WCHAR drive[10];
1921 WCHAR dir[MAX_PATH];
1922 WCHAR fname[MAX_PATH];
1923 WCHAR ext[MAX_PATH];
1924 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1926 /* Convert path into actual directory spec */
1927 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1928 WCMD_splitpath(fpath, drive, dir, fname, ext);
1930 /* Rebuild path */
1931 wsprintfW(string, fmt, drive, dir, fd.cFileName);
1933 FindClose(hff);
1934 hff = INVALID_HANDLE_VALUE;
1935 break;
1938 /* Step on to next match */
1939 if (FindNextFileW(hff, &fd) == 0) {
1940 FindClose(hff);
1941 hff = INVALID_HANDLE_VALUE;
1942 break;
1946 /* Change to that directory */
1947 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1949 status = SetCurrentDirectoryW(string);
1950 if (!status) {
1951 errorlevel = 1;
1952 WCMD_print_error ();
1953 return;
1954 } else {
1956 /* Save away the actual new directory, to store as current location */
1957 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
1959 /* Restore old directory if drive letter would change, and
1960 CD x:\directory /D (or pushd c:\directory) not supplied */
1961 if ((strstrW(quals, parmD) == NULL) &&
1962 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1963 SetCurrentDirectoryW(cwd);
1967 /* Set special =C: type environment variable, for drive letter of
1968 change of directory, even if path was restored due to missing
1969 /D (allows changing drive letter when not resident on that
1970 drive */
1971 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
1972 WCHAR env[4];
1973 strcpyW(env, equalW);
1974 memcpy(env+1, string, 2 * sizeof(WCHAR));
1975 env[3] = 0x00;
1976 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1977 SetEnvironmentVariableW(env, string);
1981 return;
1984 /****************************************************************************
1985 * WCMD_setshow_date
1987 * Set/Show the system date
1988 * FIXME: Can't change date yet
1991 void WCMD_setshow_date (void) {
1993 WCHAR curdate[64], buffer[64];
1994 DWORD count;
1995 static const WCHAR parmT[] = {'/','T','\0'};
1997 if (strlenW(param1) == 0) {
1998 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
1999 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2000 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2001 if (strstrW (quals, parmT) == NULL) {
2002 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2003 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2004 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2005 if (count > 2) {
2006 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2010 else WCMD_print_error ();
2012 else {
2013 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2017 /****************************************************************************
2018 * WCMD_compare
2020 static int WCMD_compare( const void *a, const void *b )
2022 int r;
2023 const WCHAR * const *str_a = a, * const *str_b = b;
2024 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2025 *str_a, -1, *str_b, -1 );
2026 if( r == CSTR_LESS_THAN ) return -1;
2027 if( r == CSTR_GREATER_THAN ) return 1;
2028 return 0;
2031 /****************************************************************************
2032 * WCMD_setshow_sortenv
2034 * sort variables into order for display
2035 * Optionally only display those who start with a stub
2036 * returns the count displayed
2038 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2040 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2041 const WCHAR **str;
2043 if (stub) stublen = strlenW(stub);
2045 /* count the number of strings, and the total length */
2046 while ( s[len] ) {
2047 len += (strlenW(&s[len]) + 1);
2048 count++;
2051 /* add the strings to an array */
2052 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2053 if( !str )
2054 return 0;
2055 str[0] = s;
2056 for( i=1; i<count; i++ )
2057 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2059 /* sort the array */
2060 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2062 /* print it */
2063 for( i=0; i<count; i++ ) {
2064 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2065 NORM_IGNORECASE | SORT_STRINGSORT,
2066 str[i], stublen, stub, -1) == 2) {
2067 /* Don't display special internal variables */
2068 if (str[i][0] != '=') {
2069 WCMD_output_asis(str[i]);
2070 WCMD_output_asis(newline);
2071 displayedcount++;
2076 LocalFree( str );
2077 return displayedcount;
2080 /****************************************************************************
2081 * WCMD_setshow_env
2083 * Set/Show the environment variables
2086 void WCMD_setshow_env (WCHAR *s) {
2088 LPVOID env;
2089 WCHAR *p;
2090 int status;
2091 static const WCHAR parmP[] = {'/','P','\0'};
2093 errorlevel = 0;
2094 if (param1[0] == 0x00 && quals[0] == 0x00) {
2095 env = GetEnvironmentStringsW();
2096 WCMD_setshow_sortenv( env, NULL );
2097 return;
2100 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2101 if (CompareStringW(LOCALE_USER_DEFAULT,
2102 NORM_IGNORECASE | SORT_STRINGSORT,
2103 s, 2, parmP, -1) == 2) {
2104 WCHAR string[MAXSTRING];
2105 DWORD count;
2107 s += 2;
2108 while (*s && *s==' ') s++;
2109 if (*s=='\"')
2110 WCMD_opt_s_strip_quotes(s);
2112 /* If no parameter, or no '=' sign, return an error */
2113 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2114 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2115 return;
2118 /* Output the prompt */
2119 *p++ = '\0';
2120 if (strlenW(p) != 0) WCMD_output(p);
2122 /* Read the reply */
2123 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2124 sizeof(string)/sizeof(WCHAR), &count, NULL);
2125 if (count > 1) {
2126 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2127 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2128 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2129 wine_dbgstr_w(string));
2130 status = SetEnvironmentVariableW(s, string);
2133 } else {
2134 DWORD gle;
2136 if (*s=='\"')
2137 WCMD_opt_s_strip_quotes(s);
2138 p = strchrW (s, '=');
2139 if (p == NULL) {
2140 env = GetEnvironmentStringsW();
2141 if (WCMD_setshow_sortenv( env, s ) == 0) {
2142 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2143 errorlevel = 1;
2145 return;
2147 *p++ = '\0';
2149 if (strlenW(p) == 0) p = NULL;
2150 status = SetEnvironmentVariableW(s, p);
2151 gle = GetLastError();
2152 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2153 errorlevel = 1;
2154 } else if ((!status)) WCMD_print_error();
2158 /****************************************************************************
2159 * WCMD_setshow_path
2161 * Set/Show the path environment variable
2164 void WCMD_setshow_path (WCHAR *command) {
2166 WCHAR string[1024];
2167 DWORD status;
2168 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2169 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2171 if (strlenW(param1) == 0) {
2172 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2173 if (status != 0) {
2174 WCMD_output_asis ( pathEqW);
2175 WCMD_output_asis ( string);
2176 WCMD_output_asis ( newline);
2178 else {
2179 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2182 else {
2183 if (*command == '=') command++; /* Skip leading '=' */
2184 status = SetEnvironmentVariableW(pathW, command);
2185 if (!status) WCMD_print_error();
2189 /****************************************************************************
2190 * WCMD_setshow_prompt
2192 * Set or show the command prompt.
2195 void WCMD_setshow_prompt (void) {
2197 WCHAR *s;
2198 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2200 if (strlenW(param1) == 0) {
2201 SetEnvironmentVariableW(promptW, NULL);
2203 else {
2204 s = param1;
2205 while ((*s == '=') || (*s == ' ')) s++;
2206 if (strlenW(s) == 0) {
2207 SetEnvironmentVariableW(promptW, NULL);
2209 else SetEnvironmentVariableW(promptW, s);
2213 /****************************************************************************
2214 * WCMD_setshow_time
2216 * Set/Show the system time
2217 * FIXME: Can't change time yet
2220 void WCMD_setshow_time (void) {
2222 WCHAR curtime[64], buffer[64];
2223 DWORD count;
2224 SYSTEMTIME st;
2225 static const WCHAR parmT[] = {'/','T','\0'};
2227 if (strlenW(param1) == 0) {
2228 GetLocalTime(&st);
2229 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2230 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2231 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2232 if (strstrW (quals, parmT) == NULL) {
2233 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2234 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2235 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2236 if (count > 2) {
2237 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2241 else WCMD_print_error ();
2243 else {
2244 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2248 /****************************************************************************
2249 * WCMD_shift
2251 * Shift batch parameters.
2252 * Optional /n says where to start shifting (n=0-8)
2255 void WCMD_shift (WCHAR *command) {
2256 int start;
2258 if (context != NULL) {
2259 WCHAR *pos = strchrW(command, '/');
2260 int i;
2262 if (pos == NULL) {
2263 start = 0;
2264 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2265 start = (*(pos+1) - '0');
2266 } else {
2267 SetLastError(ERROR_INVALID_PARAMETER);
2268 WCMD_print_error();
2269 return;
2272 WINE_TRACE("Shifting variables, starting at %d\n", start);
2273 for (i=start;i<=8;i++) {
2274 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2276 context -> shift_count[9] = context -> shift_count[9] + 1;
2281 /****************************************************************************
2282 * WCMD_title
2284 * Set the console title
2286 void WCMD_title (WCHAR *command) {
2287 SetConsoleTitleW(command);
2290 /****************************************************************************
2291 * WCMD_type
2293 * Copy a file to standard output.
2296 void WCMD_type (WCHAR *command) {
2298 int argno = 0;
2299 WCHAR *argN = command;
2300 BOOL writeHeaders = FALSE;
2302 if (param1[0] == 0x00) {
2303 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2304 return;
2307 if (param2[0] != 0x00) writeHeaders = TRUE;
2309 /* Loop through all args */
2310 errorlevel = 0;
2311 while (argN) {
2312 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2314 HANDLE h;
2315 WCHAR buffer[512];
2316 DWORD count;
2318 if (!argN) break;
2320 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2321 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2322 FILE_ATTRIBUTE_NORMAL, NULL);
2323 if (h == INVALID_HANDLE_VALUE) {
2324 WCMD_print_error ();
2325 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2326 errorlevel = 1;
2327 } else {
2328 if (writeHeaders) {
2329 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2330 WCMD_output(fmt, thisArg);
2332 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2333 if (count == 0) break; /* ReadFile reports success on EOF! */
2334 buffer[count] = 0;
2335 WCMD_output_asis (buffer);
2337 CloseHandle (h);
2338 if (!writeHeaders)
2339 WCMD_output_asis (newline);
2344 /****************************************************************************
2345 * WCMD_more
2347 * Output either a file or stdin to screen in pages
2350 void WCMD_more (WCHAR *command) {
2352 int argno = 0;
2353 WCHAR *argN = command;
2354 WCHAR moreStr[100];
2355 WCHAR moreStrPage[100];
2356 WCHAR buffer[512];
2357 DWORD count;
2358 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2359 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2360 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2361 ')',' ','-','-','\n','\0'};
2362 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2364 /* Prefix the NLS more with '-- ', then load the text */
2365 errorlevel = 0;
2366 strcpyW(moreStr, moreStart);
2367 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2368 (sizeof(moreStr)/sizeof(WCHAR))-3);
2370 if (param1[0] == 0x00) {
2372 /* Wine implements pipes via temporary files, and hence stdin is
2373 effectively reading from the file. This means the prompts for
2374 more are satisfied by the next line from the input (file). To
2375 avoid this, ensure stdin is to the console */
2376 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2377 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2378 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2379 FILE_ATTRIBUTE_NORMAL, 0);
2380 WINE_TRACE("No parms - working probably in pipe mode\n");
2381 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2383 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2384 once you get in this bit unless due to a pipe, its going to end badly... */
2385 wsprintfW(moreStrPage, moreFmt, moreStr);
2387 WCMD_enter_paged_mode(moreStrPage);
2388 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2389 if (count == 0) break; /* ReadFile reports success on EOF! */
2390 buffer[count] = 0;
2391 WCMD_output_asis (buffer);
2393 WCMD_leave_paged_mode();
2395 /* Restore stdin to what it was */
2396 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2397 CloseHandle(hConIn);
2399 return;
2400 } else {
2401 BOOL needsPause = FALSE;
2403 /* Loop through all args */
2404 WINE_TRACE("Parms supplied - working through each file\n");
2405 WCMD_enter_paged_mode(moreStrPage);
2407 while (argN) {
2408 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2409 HANDLE h;
2411 if (!argN) break;
2413 if (needsPause) {
2415 /* Wait */
2416 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2417 WCMD_leave_paged_mode();
2418 WCMD_output_asis(moreStrPage);
2419 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2420 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2421 WCMD_enter_paged_mode(moreStrPage);
2425 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2426 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2427 FILE_ATTRIBUTE_NORMAL, NULL);
2428 if (h == INVALID_HANDLE_VALUE) {
2429 WCMD_print_error ();
2430 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2431 errorlevel = 1;
2432 } else {
2433 ULONG64 curPos = 0;
2434 ULONG64 fileLen = 0;
2435 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2437 /* Get the file size */
2438 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2439 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2441 needsPause = TRUE;
2442 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2443 if (count == 0) break; /* ReadFile reports success on EOF! */
2444 buffer[count] = 0;
2445 curPos += count;
2447 /* Update % count (would be used in WCMD_output_asis as prompt) */
2448 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2450 WCMD_output_asis (buffer);
2452 CloseHandle (h);
2456 WCMD_leave_paged_mode();
2460 /****************************************************************************
2461 * WCMD_verify
2463 * Display verify flag.
2464 * FIXME: We don't actually do anything with the verify flag other than toggle
2465 * it...
2468 void WCMD_verify (WCHAR *command) {
2470 int count;
2472 count = strlenW(command);
2473 if (count == 0) {
2474 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2475 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2476 return;
2478 if (lstrcmpiW(command, onW) == 0) {
2479 verify_mode = 1;
2480 return;
2482 else if (lstrcmpiW(command, offW) == 0) {
2483 verify_mode = 0;
2484 return;
2486 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2489 /****************************************************************************
2490 * WCMD_version
2492 * Display version info.
2495 void WCMD_version (void) {
2497 WCMD_output (version_string);
2501 /****************************************************************************
2502 * WCMD_volume
2504 * Display volume info and/or set volume label. Returns 0 if error.
2507 int WCMD_volume (int mode, WCHAR *path) {
2509 DWORD count, serial;
2510 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2511 BOOL status;
2513 if (strlenW(path) == 0) {
2514 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2515 if (!status) {
2516 WCMD_print_error ();
2517 return 0;
2519 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2520 &serial, NULL, NULL, NULL, 0);
2522 else {
2523 static const WCHAR fmt[] = {'%','s','\\','\0'};
2524 if ((path[1] != ':') || (strlenW(path) != 2)) {
2525 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2526 return 0;
2528 wsprintfW (curdir, fmt, path);
2529 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2530 &serial, NULL,
2531 NULL, NULL, 0);
2533 if (!status) {
2534 WCMD_print_error ();
2535 return 0;
2537 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2538 curdir[0], label, HIWORD(serial), LOWORD(serial));
2539 if (mode) {
2540 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2541 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2542 sizeof(string)/sizeof(WCHAR), &count, NULL);
2543 if (count > 1) {
2544 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2545 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2547 if (strlenW(path) != 0) {
2548 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2550 else {
2551 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2554 return 1;
2557 /**************************************************************************
2558 * WCMD_exit
2560 * Exit either the process, or just this batch program
2564 void WCMD_exit (CMD_LIST **cmdList) {
2566 static const WCHAR parmB[] = {'/','B','\0'};
2567 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2569 if (context && lstrcmpiW(quals, parmB) == 0) {
2570 errorlevel = rc;
2571 context -> skip_rest = TRUE;
2572 *cmdList = NULL;
2573 } else {
2574 ExitProcess(rc);
2579 /*****************************************************************************
2580 * WCMD_assoc
2582 * Lists or sets file associations (assoc = TRUE)
2583 * Lists or sets file types (assoc = FALSE)
2585 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2587 HKEY key;
2588 DWORD accessOptions = KEY_READ;
2589 WCHAR *newValue;
2590 LONG rc = ERROR_SUCCESS;
2591 WCHAR keyValue[MAXSTRING];
2592 DWORD valueLen = MAXSTRING;
2593 HKEY readKey;
2594 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2595 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2597 /* See if parameter includes '=' */
2598 errorlevel = 0;
2599 newValue = strchrW(command, '=');
2600 if (newValue) accessOptions |= KEY_WRITE;
2602 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2603 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2604 accessOptions, &key) != ERROR_SUCCESS) {
2605 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2606 return;
2609 /* If no parameters then list all associations */
2610 if (*command == 0x00) {
2611 int index = 0;
2613 /* Enumerate all the keys */
2614 while (rc != ERROR_NO_MORE_ITEMS) {
2615 WCHAR keyName[MAXSTRING];
2616 DWORD nameLen;
2618 /* Find the next value */
2619 nameLen = MAXSTRING;
2620 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2622 if (rc == ERROR_SUCCESS) {
2624 /* Only interested in extension ones if assoc, or others
2625 if not assoc */
2626 if ((keyName[0] == '.' && assoc) ||
2627 (!(keyName[0] == '.') && (!assoc)))
2629 WCHAR subkey[MAXSTRING];
2630 strcpyW(subkey, keyName);
2631 if (!assoc) strcatW(subkey, shOpCmdW);
2633 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2635 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2636 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2637 WCMD_output_asis(keyName);
2638 WCMD_output_asis(equalW);
2639 /* If no default value found, leave line empty after '=' */
2640 if (rc == ERROR_SUCCESS) {
2641 WCMD_output_asis(keyValue);
2643 WCMD_output_asis(newline);
2644 RegCloseKey(readKey);
2650 } else {
2652 /* Parameter supplied - if no '=' on command line, its a query */
2653 if (newValue == NULL) {
2654 WCHAR *space;
2655 WCHAR subkey[MAXSTRING];
2657 /* Query terminates the parameter at the first space */
2658 strcpyW(keyValue, command);
2659 space = strchrW(keyValue, ' ');
2660 if (space) *space=0x00;
2662 /* Set up key name */
2663 strcpyW(subkey, keyValue);
2664 if (!assoc) strcatW(subkey, shOpCmdW);
2666 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2668 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2669 WCMD_output_asis(command);
2670 WCMD_output_asis(equalW);
2671 /* If no default value found, leave line empty after '=' */
2672 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2673 WCMD_output_asis(newline);
2674 RegCloseKey(readKey);
2676 } else {
2677 WCHAR msgbuffer[MAXSTRING];
2678 WCHAR outbuffer[MAXSTRING];
2680 /* Load the translated 'File association not found' */
2681 if (assoc) {
2682 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2683 } else {
2684 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2686 wsprintfW(outbuffer, msgbuffer, keyValue);
2687 WCMD_output_asis(outbuffer);
2688 errorlevel = 2;
2691 /* Not a query - its a set or clear of a value */
2692 } else {
2694 WCHAR subkey[MAXSTRING];
2696 /* Get pointer to new value */
2697 *newValue = 0x00;
2698 newValue++;
2700 /* Set up key name */
2701 strcpyW(subkey, command);
2702 if (!assoc) strcatW(subkey, shOpCmdW);
2704 /* If nothing after '=' then clear value - only valid for ASSOC */
2705 if (*newValue == 0x00) {
2707 if (assoc) rc = RegDeleteKeyW(key, command);
2708 if (assoc && rc == ERROR_SUCCESS) {
2709 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2711 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2712 WCMD_print_error();
2713 errorlevel = 2;
2715 } else {
2716 WCHAR msgbuffer[MAXSTRING];
2717 WCHAR outbuffer[MAXSTRING];
2719 /* Load the translated 'File association not found' */
2720 if (assoc) {
2721 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2722 sizeof(msgbuffer)/sizeof(WCHAR));
2723 } else {
2724 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2725 sizeof(msgbuffer)/sizeof(WCHAR));
2727 wsprintfW(outbuffer, msgbuffer, keyValue);
2728 WCMD_output_asis(outbuffer);
2729 errorlevel = 2;
2732 /* It really is a set value = contents */
2733 } else {
2734 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2735 accessOptions, NULL, &readKey, NULL);
2736 if (rc == ERROR_SUCCESS) {
2737 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2738 (LPBYTE)newValue, strlenW(newValue));
2739 RegCloseKey(readKey);
2742 if (rc != ERROR_SUCCESS) {
2743 WCMD_print_error();
2744 errorlevel = 2;
2745 } else {
2746 WCMD_output_asis(command);
2747 WCMD_output_asis(equalW);
2748 WCMD_output_asis(newValue);
2749 WCMD_output_asis(newline);
2755 /* Clean up */
2756 RegCloseKey(key);
2759 /****************************************************************************
2760 * WCMD_color
2762 * Clear the terminal screen.
2765 void WCMD_color (void) {
2767 /* Emulate by filling the screen from the top left to bottom right with
2768 spaces, then moving the cursor to the top left afterwards */
2769 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2770 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2772 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2773 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2774 return;
2777 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2779 COORD topLeft;
2780 DWORD screenSize;
2781 DWORD color = 0;
2783 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2785 topLeft.X = 0;
2786 topLeft.Y = 0;
2788 /* Convert the color hex digits */
2789 if (param1[0] == 0x00) {
2790 color = defaultColor;
2791 } else {
2792 color = strtoulW(param1, NULL, 16);
2795 /* Fail if fg == bg color */
2796 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2797 errorlevel = 1;
2798 return;
2801 /* Set the current screen contents and ensure all future writes
2802 remain this color */
2803 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2804 SetConsoleTextAttribute(hStdOut, color);