2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * On entry to each function, global variables quals, param1, param2 contain
24 * the qualifiers (uppercased and concatenated) and parameters entered, with
25 * environment-variable and batch parameter substitution already done.
30 * - No support for pipes, shell parameters
31 * - Lots of functionality missing from builtins
32 * - Messages etc need international support
35 #define WIN32_LEAN_AND_MEAN
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
43 static void WCMD_part_execute(CMD_LIST **commands, WCHAR *firstcmd, WCHAR *variable,
44 WCHAR *value, BOOL isIF, BOOL conditionTRUE);
46 struct env_stack *saved_environment;
47 struct env_stack *pushd_directories;
49 extern HINSTANCE hinst;
50 extern WCHAR inbuilt[][10];
51 extern int echo_mode, verify_mode, defaultColor;
52 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
53 extern BATCH_CONTEXT *context;
54 extern DWORD errorlevel;
56 static const WCHAR dotW[] = {'.','\0'};
57 static const WCHAR dotdotW[] = {'.','.','\0'};
58 static const WCHAR slashW[] = {'\\','\0'};
59 static const WCHAR starW[] = {'*','\0'};
60 static const WCHAR equalW[] = {'=','\0'};
61 static const WCHAR fslashW[] = {'/','\0'};
62 static const WCHAR onW[] = {'O','N','\0'};
63 static const WCHAR offW[] = {'O','F','F','\0'};
64 static const WCHAR parmY[] = {'/','Y','\0'};
65 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
66 static const WCHAR nullW[] = {'\0'};
68 /****************************************************************************
71 * Clear the terminal screen.
74 void WCMD_clear_screen (void) {
76 /* Emulate by filling the screen from the top left to bottom right with
77 spaces, then moving the cursor to the top left afterwards */
78 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
79 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
81 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
86 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
90 FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
91 SetConsoleCursorPosition(hStdOut, topLeft);
95 /****************************************************************************
98 * Change the default i/o device (ie redirect STDin/STDout).
101 void WCMD_change_tty (void) {
103 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
107 /****************************************************************************
110 * Copy a file or wildcarded set.
111 * FIXME: Add support for a+b+c type syntax
114 void WCMD_copy (void) {
119 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[3];
121 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
122 BOOL copyToDir = FALSE;
123 BOOL copyFromDir = FALSE;
124 WCHAR srcspec[MAX_PATH];
128 WCHAR fname[MAX_PATH];
131 if (param1[0] == 0x00) {
132 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
136 /* Convert source into full spec */
137 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
138 GetFullPathName (param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
139 if (srcpath[strlenW(srcpath) - 1] == '\\')
140 srcpath[strlenW(srcpath) - 1] = '\0';
142 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
143 attribs = GetFileAttributes(srcpath);
147 strcpyW(srcspec, srcpath);
149 /* If a directory, then add \* on the end when searching */
150 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
151 strcatW(srcpath, slashW);
153 strcatW(srcspec, slashW);
154 strcatW(srcspec, starW);
156 WCMD_splitpath(srcpath, drive, dir, fname, ext);
157 strcpyW(srcpath, drive);
158 strcatW(srcpath, dir);
161 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
163 /* If no destination supplied, assume current directory */
164 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
165 if (param2[0] == 0x00) {
166 strcpyW(param2, dotW);
169 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
170 if (outpath[strlenW(outpath) - 1] == '\\')
171 outpath[strlenW(outpath) - 1] = '\0';
172 attribs = GetFileAttributes(outpath);
173 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
174 strcatW (outpath, slashW);
177 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
178 wine_dbgstr_w(outpath), copyToDir);
180 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
181 if (strstrW (quals, parmNoY))
183 else if (strstrW (quals, parmY))
186 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
187 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
190 /* Loop through all source files */
191 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
192 hff = FindFirstFile (srcspec, &fd);
193 if (hff != INVALID_HANDLE_VALUE) {
195 WCHAR outname[MAX_PATH];
196 WCHAR srcname[MAX_PATH];
197 BOOL overwrite = force;
199 /* Destination is either supplied filename, or source name in
200 supplied destination directory */
201 strcpyW(outname, outpath);
202 if (copyToDir) strcatW(outname, fd.cFileName);
203 strcpyW(srcname, srcpath);
204 strcatW(srcname, fd.cFileName);
206 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
207 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
209 /* Skip . and .., and directories */
210 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
212 WINE_TRACE("Skipping directories\n");
215 /* Prompt before overwriting */
216 else if (!overwrite) {
217 attribs = GetFileAttributes(outname);
218 if (attribs != INVALID_FILE_ATTRIBUTES) {
219 WCHAR buffer[MAXSTRING];
220 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
221 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
223 else overwrite = TRUE;
226 /* Do the copy as appropriate */
228 status = CopyFile (srcname, outname, FALSE);
229 if (!status) WCMD_print_error ();
232 } while (FindNextFile(hff, &fd) != 0);
235 status = ERROR_FILE_NOT_FOUND;
240 /****************************************************************************
243 * Create a directory.
245 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
246 * they do not already exist.
249 static BOOL create_full_path(WCHAR* path)
255 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
256 strcpyW(new_path,path);
258 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
259 new_path[len - 1] = 0;
261 while (!CreateDirectory(new_path,NULL))
264 DWORD last_error = GetLastError();
265 if (last_error == ERROR_ALREADY_EXISTS)
268 if (last_error != ERROR_PATH_NOT_FOUND)
274 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
280 len = slash - new_path;
282 if (!create_full_path(new_path))
287 new_path[len] = '\\';
289 HeapFree(GetProcessHeap(),0,new_path);
293 void WCMD_create_dir (void) {
295 if (param1[0] == 0x00) {
296 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
299 if (!create_full_path(param1)) WCMD_print_error ();
302 /****************************************************************************
305 * Delete a file or wildcarded set.
308 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
309 * - Each set is a pattern, eg /ahr /as-r means
310 * readonly+hidden OR nonreadonly system files
311 * - The '-' applies to a single field, ie /a:-hr means read only
315 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
318 int argsProcessed = 0;
319 WCHAR *argN = command;
320 BOOL foundAny = FALSE;
321 static const WCHAR parmA[] = {'/','A','\0'};
322 static const WCHAR parmQ[] = {'/','Q','\0'};
323 static const WCHAR parmP[] = {'/','P','\0'};
324 static const WCHAR parmS[] = {'/','S','\0'};
325 static const WCHAR parmF[] = {'/','F','\0'};
327 /* If not recursing, clear error flag */
328 if (expectDir) errorlevel = 0;
330 /* Loop through all args */
332 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
333 WCHAR argCopy[MAX_PATH];
335 if (argN && argN[0] != '/') {
339 WCHAR fpath[MAX_PATH];
341 BOOL handleParm = TRUE;
343 static const WCHAR anyExt[]= {'.','*','\0'};
345 strcpyW(argCopy, thisArg);
346 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
347 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
350 /* If filename part of parameter is * or *.*, prompt unless
352 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
356 WCHAR fname[MAX_PATH];
359 /* Convert path into actual directory spec */
360 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
361 WCMD_splitpath(fpath, drive, dir, fname, ext);
363 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
364 if ((strcmpW(fname, starW) == 0) &&
365 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
367 WCHAR question[MAXSTRING];
368 static const WCHAR fmt[] = {'%','s',' ','\0'};
370 /* Note: Flag as found, to avoid file not found message */
373 /* Ask for confirmation */
374 wsprintf(question, fmt, fpath);
375 ok = WCMD_ask_confirm(question, TRUE, NULL);
377 /* Abort if answer is 'N' */
382 /* First, try to delete in the current directory */
383 hff = FindFirstFile (argCopy, &fd);
384 if (hff == INVALID_HANDLE_VALUE) {
390 /* Support del <dirname> by just deleting all files dirname\* */
391 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
392 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
393 WCHAR modifiedParm[MAX_PATH];
394 static const WCHAR slashStar[] = {'\\','*','\0'};
396 strcpyW(modifiedParm, argCopy);
397 strcatW(modifiedParm, slashStar);
400 WCMD_delete(modifiedParm, FALSE);
402 } else if (handleParm) {
404 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
405 strcpyW (fpath, argCopy);
407 p = strrchrW (fpath, '\\');
410 strcatW (fpath, fd.cFileName);
412 else strcpyW (fpath, fd.cFileName);
413 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
415 WCHAR *nextA = strstrW (quals, parmA);
417 /* Handle attribute matching (/A) */
420 while (nextA != NULL && !ok) {
422 WCHAR *thisA = (nextA+2);
425 /* Skip optional : */
426 if (*thisA == ':') thisA++;
428 /* Parse each of the /A[:]xxx in turn */
429 while (*thisA && *thisA != '/') {
431 BOOL attribute = FALSE;
433 /* Match negation of attribute first */
439 /* Match attribute */
441 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
443 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
445 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
447 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
450 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
453 /* Now check result, keeping a running boolean about whether it
454 matches all parsed attribues so far */
455 if (attribute && !negate) {
457 } else if (!attribute && negate) {
465 /* Save the running total as the final result */
468 /* Step on to next /A set */
469 nextA = strstrW (nextA+1, parmA);
473 /* /P means prompt for each file */
474 if (ok && strstrW (quals, parmP) != NULL) {
475 WCHAR question[MAXSTRING];
477 /* Ask for confirmation */
478 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
479 ok = WCMD_ask_confirm(question, FALSE, NULL);
482 /* Only proceed if ok to */
485 /* If file is read only, and /F supplied, delete it */
486 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
487 strstrW (quals, parmF) != NULL) {
488 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
491 /* Now do the delete */
492 if (!DeleteFile (fpath)) WCMD_print_error ();
496 } while (FindNextFile(hff, &fd) != 0);
500 /* Now recurse into all subdirectories handling the parameter in the same way */
501 if (strstrW (quals, parmS) != NULL) {
503 WCHAR thisDir[MAX_PATH];
508 WCHAR fname[MAX_PATH];
511 /* Convert path into actual directory spec */
512 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
513 WCMD_splitpath(thisDir, drive, dir, fname, ext);
515 strcpyW(thisDir, drive);
516 strcatW(thisDir, dir);
517 cPos = strlenW(thisDir);
519 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
521 /* Append '*' to the directory */
523 thisDir[cPos+1] = 0x00;
525 hff = FindFirstFile (thisDir, &fd);
527 /* Remove residual '*' */
528 thisDir[cPos] = 0x00;
530 if (hff != INVALID_HANDLE_VALUE) {
531 DIRECTORY_STACK *allDirs = NULL;
532 DIRECTORY_STACK *lastEntry = NULL;
535 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
536 (strcmpW(fd.cFileName, dotdotW) != 0) &&
537 (strcmpW(fd.cFileName, dotW) != 0)) {
539 DIRECTORY_STACK *nextDir;
540 WCHAR subParm[MAX_PATH];
542 /* Work out search parameter in sub dir */
543 strcpyW (subParm, thisDir);
544 strcatW (subParm, fd.cFileName);
545 strcatW (subParm, slashW);
546 strcatW (subParm, fname);
547 strcatW (subParm, ext);
548 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
550 /* Allocate memory, add to list */
551 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
552 if (allDirs == NULL) allDirs = nextDir;
553 if (lastEntry != NULL) lastEntry->next = nextDir;
555 nextDir->next = NULL;
556 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
557 (strlenW(subParm)+1) * sizeof(WCHAR));
558 strcpyW(nextDir->dirName, subParm);
560 } while (FindNextFile(hff, &fd) != 0);
563 /* Go through each subdir doing the delete */
564 while (allDirs != NULL) {
565 DIRECTORY_STACK *tempDir;
567 tempDir = allDirs->next;
568 found |= WCMD_delete (allDirs->dirName, FALSE);
570 HeapFree(GetProcessHeap(),0,allDirs->dirName);
571 HeapFree(GetProcessHeap(),0,allDirs);
576 /* Keep running total to see if any found, and if not recursing
577 issue error message */
581 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
588 /* Handle no valid args */
589 if (argsProcessed == 0) {
590 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
596 /****************************************************************************
599 * Echo input to the screen (or not). We don't try to emulate the bugs
600 * in DOS (try typing "ECHO ON AGAIN" for an example).
603 void WCMD_echo (const WCHAR *command) {
607 if ((command[0] == '.') && (command[1] == 0)) {
608 WCMD_output (newline);
613 count = strlenW(command);
615 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
616 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
619 if (lstrcmpiW(command, onW) == 0) {
623 if (lstrcmpiW(command, offW) == 0) {
627 WCMD_output_asis (command);
628 WCMD_output (newline);
632 /**************************************************************************
635 * Batch file loop processing.
637 * On entry: cmdList contains the syntax up to the set
638 * next cmdList and all in that bracket contain the set data
639 * next cmdlist contains the DO cmd
640 * following that is either brackets or && entries (as per if)
642 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
643 * will probably work here, but the reverse is not necessarily the case...
646 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
651 const WCHAR inW[] = {'i', 'n', '\0'};
652 const WCHAR doW[] = {'d', 'o', ' ','\0'};
653 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
659 the first line includes the % variable name as first parm
660 we have been provided with more parts to the command
661 and there is at least some set data
662 and IN as the one after that */
663 if (lstrcmpiW (WCMD_parameter (p, 1, NULL), inW)
664 || (*cmdList) == NULL
665 || (*cmdList)->nextcommand == NULL
666 || (param1[0] != '%')
667 || (strlenW(param1) > 3)) {
668 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
672 /* Save away where the set of data starts and the variable */
673 strcpyW(variable, param1);
674 thisDepth = (*cmdList)->bracketDepth;
675 *cmdList = (*cmdList)->nextcommand;
676 setStart = (*cmdList);
678 /* Skip until the close bracket */
679 WINE_TRACE("Searching %p as the set\n", *cmdList);
681 (*cmdList)->command != NULL &&
682 (*cmdList)->bracketDepth > thisDepth) {
683 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
684 *cmdList = (*cmdList)->nextcommand;
687 /* Skip the close bracket, if there is one */
688 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
690 /* Syntax error if missing close bracket, or nothing following it
691 and once we have the complete set, we expect a DO */
692 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
693 if ((*cmdList == NULL) ||
694 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
695 (*cmdList)->command, 3, doW, -1) != 2)) {
696 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
700 /* Save away the starting position for the commands (and offset for the
704 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
707 /* Loop through all set entries */
709 thisSet->command != NULL &&
710 thisSet->bracketDepth >= thisDepth) {
712 /* Loop through all entries on the same line */
715 WINE_TRACE("Processing for set %p\n", thisSet);
717 while (*(item = WCMD_parameter (thisSet->command, i, NULL))) {
720 * If the parameter within the set has a wildcard then search for matching files
721 * otherwise do a literal substitution.
723 static const WCHAR wildcards[] = {'*','?','\0'};
724 CMD_LIST *thisCmdStart = cmdStart;
726 WINE_TRACE("Processing for item '%s'\n", wine_dbgstr_w(item));
727 if (strpbrkW (item, wildcards)) {
728 hff = FindFirstFile (item, &fd);
729 if (hff != INVALID_HANDLE_VALUE) {
731 BOOL isDirectory = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
734 thisCmdStart = cmdStart;
735 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
736 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
737 fd.cFileName, FALSE, TRUE);
740 } while (FindNextFile(hff, &fd) != 0);
744 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
747 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
748 cmdEnd = thisCmdStart;
752 /* Move onto the next set line */
753 thisSet = thisSet->nextcommand;
756 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
757 all processing, OR it should be pointing to the end of && processing OR
758 it should be pointing at the NULL end of bracket for the DO. The return
759 value needs to be the NEXT command to execute, which it either is, or
760 we need to step over the closing bracket */
762 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
766 /*****************************************************************************
769 * Execute a command, and any && or bracketed follow on to the command. The
770 * first command to be executed may not be at the front of the
771 * commands->thiscommand string (eg. it may point after a DO or ELSE)
773 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
774 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
776 CMD_LIST *curPosition = *cmdList;
777 int myDepth = (*cmdList)->bracketDepth;
779 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
780 cmdList, wine_dbgstr_w(firstcmd),
781 wine_dbgstr_w(variable), wine_dbgstr_w(value),
784 /* Skip leading whitespace between condition and the command */
785 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
787 /* Process the first command, if there is one */
788 if (conditionTRUE && firstcmd && *firstcmd) {
789 WCHAR *command = WCMD_strdupW(firstcmd);
790 WCMD_execute (firstcmd, variable, value, cmdList);
795 /* If it didn't move the position, step to next command */
796 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
798 /* Process any other parts of the command */
800 BOOL processThese = TRUE;
802 if (isIF) processThese = conditionTRUE;
805 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
807 /* execute all appropriate commands */
808 curPosition = *cmdList;
810 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
812 (*cmdList)->isAmphersand,
813 (*cmdList)->bracketDepth, myDepth);
815 /* Execute any appended to the statement with &&'s */
816 if ((*cmdList)->isAmphersand) {
818 WCMD_execute ((*cmdList)->command, variable, value, cmdList);
820 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
822 /* Execute any appended to the statement with (...) */
823 } else if ((*cmdList)->bracketDepth > myDepth) {
825 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
826 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
828 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
830 /* End of the command - does 'ELSE ' follow as the next command? */
832 if (isIF && CompareString (LOCALE_USER_DEFAULT,
833 NORM_IGNORECASE | SORT_STRINGSORT,
834 (*cmdList)->command, 5, ifElse, -1) == 2) {
836 /* Swap between if and else processing */
837 processThese = !processThese;
839 /* Process the ELSE part */
841 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
843 /* Skip leading whitespace between condition and the command */
844 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
846 WCMD_execute (cmd, variable, value, cmdList);
849 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
851 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
860 /*****************************************************************************
863 * Execute a command after substituting variable text for the supplied parameter
866 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
868 WCHAR *new_cmd, *p, *s, *dup;
872 size = (strlenW (orig_cmd) + 1) * sizeof(WCHAR);
873 new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
874 dup = s = WCMD_strdupW(orig_cmd);
876 while ((p = strstrW (s, param))) {
878 size += strlenW (subst) * sizeof(WCHAR);
879 new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
880 strcatW (new_cmd, s);
881 strcatW (new_cmd, subst);
882 s = p + strlenW (param);
884 strcatW (new_cmd, s);
885 WCMD_process_command (new_cmd, cmdList);
887 LocalFree ((HANDLE)new_cmd);
889 WCMD_process_command (orig_cmd, cmdList);
894 /**************************************************************************
897 * Simple on-line help. Help text is stored in the resource file.
900 void WCMD_give_help (WCHAR *command) {
904 command = WCMD_strtrim_leading_spaces(command);
905 if (strlenW(command) == 0) {
906 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
909 for (i=0; i<=WCMD_EXIT; i++) {
910 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
911 param1, -1, inbuilt[i], -1) == 2) {
912 WCMD_output_asis (WCMD_LoadMessage(i));
916 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
921 /****************************************************************************
924 * Batch file jump instruction. Not the most efficient algorithm ;-)
925 * Prints error message if the specified label cannot be found - the file pointer is
926 * then at EOF, effectively stopping the batch file.
927 * FIXME: DOS is supposed to allow labels with spaces - we don't.
930 void WCMD_goto (CMD_LIST **cmdList) {
932 WCHAR string[MAX_PATH];
934 /* Do not process any more parts of a processed multipart or multilines command */
937 if (param1[0] == 0x00) {
938 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
941 if (context != NULL) {
942 WCHAR *paramStart = param1;
943 static const WCHAR eofW[] = {':','e','o','f','\0'};
945 /* Handle special :EOF label */
946 if (lstrcmpiW (eofW, param1) == 0) {
947 context -> skip_rest = TRUE;
951 /* Support goto :label as well as goto label */
952 if (*paramStart == ':') paramStart++;
954 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
955 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
956 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
958 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
963 /*****************************************************************************
966 * Push a directory onto the stack
969 void WCMD_pushd (WCHAR *command) {
970 struct env_stack *curdir;
972 static const WCHAR parmD[] = {'/','D','\0'};
974 if (strchrW(command, '/') != NULL) {
975 SetLastError(ERROR_INVALID_PARAMETER);
980 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
981 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
982 if( !curdir || !thisdir ) {
985 WINE_ERR ("out of memory\n");
989 /* Change directory using CD code with /D parameter */
990 strcpyW(quals, parmD);
991 GetCurrentDirectoryW (1024, thisdir);
993 WCMD_setshow_default(command);
999 curdir -> next = pushd_directories;
1000 curdir -> strings = thisdir;
1001 if (pushd_directories == NULL) {
1002 curdir -> u.stackdepth = 1;
1004 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1006 pushd_directories = curdir;
1011 /*****************************************************************************
1014 * Pop a directory from the stack
1017 void WCMD_popd (void) {
1018 struct env_stack *temp = pushd_directories;
1020 if (!pushd_directories)
1023 /* pop the old environment from the stack, and make it the current dir */
1024 pushd_directories = temp->next;
1025 SetCurrentDirectoryW(temp->strings);
1026 LocalFree (temp->strings);
1030 /****************************************************************************
1033 * Batch file conditional.
1035 * On entry, cmdlist will point to command containing the IF, and optionally
1036 * the first command to execute (if brackets not found)
1037 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1038 * If ('s were found, execute all within that bracket
1039 * Command may optionally be followed by an ELSE - need to skip instructions
1040 * in the else using the same logic
1042 * FIXME: Much more syntax checking needed!
1045 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1047 int negate = 0, test = 0;
1048 WCHAR condition[MAX_PATH], *command, *s;
1049 static const WCHAR notW[] = {'n','o','t','\0'};
1050 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1051 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1052 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1053 static const WCHAR eqeqW[] = {'=','=','\0'};
1055 if (!lstrcmpiW (param1, notW)) {
1057 strcpyW (condition, param2);
1060 strcpyW (condition, param1);
1062 if (!lstrcmpiW (condition, errlvlW)) {
1063 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1064 WCMD_parameter (p, 2+negate, &command);
1066 else if (!lstrcmpiW (condition, existW)) {
1067 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1070 WCMD_parameter (p, 2+negate, &command);
1072 else if (!lstrcmpiW (condition, defdW)) {
1073 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1076 WCMD_parameter (p, 2+negate, &command);
1078 else if ((s = strstrW (p, eqeqW))) {
1080 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1081 WCMD_parameter (s, 1, &command);
1084 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1088 /* Process rest of IF statement which is on the same line
1089 Note: This may process all or some of the cmdList (eg a GOTO) */
1090 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1093 /****************************************************************************
1096 * Move a file, directory tree or wildcarded set of files.
1099 void WCMD_move (void) {
1104 WCHAR input[MAX_PATH];
1105 WCHAR output[MAX_PATH];
1107 WCHAR dir[MAX_PATH];
1108 WCHAR fname[MAX_PATH];
1109 WCHAR ext[MAX_PATH];
1111 if (param1[0] == 0x00) {
1112 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1116 /* If no destination supplied, assume current directory */
1117 if (param2[0] == 0x00) {
1118 strcpyW(param2, dotW);
1121 /* If 2nd parm is directory, then use original filename */
1122 /* Convert partial path to full path */
1123 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1124 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1125 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1126 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1128 /* Split into components */
1129 WCMD_splitpath(input, drive, dir, fname, ext);
1131 hff = FindFirstFile (input, &fd);
1132 while (hff != INVALID_HANDLE_VALUE) {
1133 WCHAR dest[MAX_PATH];
1134 WCHAR src[MAX_PATH];
1137 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1139 /* Build src & dest name */
1140 strcpyW(src, drive);
1143 /* See if dest is an existing directory */
1144 attribs = GetFileAttributes(output);
1145 if (attribs != INVALID_FILE_ATTRIBUTES &&
1146 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1147 strcpyW(dest, output);
1148 strcatW(dest, slashW);
1149 strcatW(dest, fd.cFileName);
1151 strcpyW(dest, output);
1154 strcatW(src, fd.cFileName);
1156 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1157 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1159 /* Check if file is read only, otherwise move it */
1160 attribs = GetFileAttributes(src);
1161 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1162 (attribs & FILE_ATTRIBUTE_READONLY)) {
1163 SetLastError(ERROR_ACCESS_DENIED);
1168 /* If destination exists, prompt unless /Y supplied */
1169 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1171 WCHAR copycmd[MAXSTRING];
1174 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1175 if (strstrW (quals, parmNoY))
1177 else if (strstrW (quals, parmY))
1180 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1181 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1182 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1183 && ! lstrcmpiW (copycmd, parmY));
1186 /* Prompt if overwriting */
1188 WCHAR question[MAXSTRING];
1191 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1193 /* Ask for confirmation */
1194 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1195 ok = WCMD_ask_confirm(question, FALSE, NULL);
1197 /* So delete the destination prior to the move */
1199 if (!DeleteFile (dest)) {
1200 WCMD_print_error ();
1209 status = MoveFile (src, dest);
1211 status = 1; /* Anything other than 0 to prevent error msg below */
1216 WCMD_print_error ();
1220 /* Step on to next match */
1221 if (FindNextFile(hff, &fd) == 0) {
1223 hff = INVALID_HANDLE_VALUE;
1229 /****************************************************************************
1232 * Wait for keyboard input.
1235 void WCMD_pause (void) {
1240 WCMD_output (anykey);
1241 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1242 sizeof(string)/sizeof(WCHAR), &count, NULL);
1245 /****************************************************************************
1248 * Delete a directory.
1251 void WCMD_remove_dir (WCHAR *command) {
1254 int argsProcessed = 0;
1255 WCHAR *argN = command;
1256 static const WCHAR parmS[] = {'/','S','\0'};
1257 static const WCHAR parmQ[] = {'/','Q','\0'};
1259 /* Loop through all args */
1261 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1262 if (argN && argN[0] != '/') {
1263 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1264 wine_dbgstr_w(quals));
1267 /* If subdirectory search not supplied, just try to remove
1268 and report error if it fails (eg if it contains a file) */
1269 if (strstrW (quals, parmS) == NULL) {
1270 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1272 /* Otherwise use ShFileOp to recursively remove a directory */
1275 SHFILEOPSTRUCT lpDir;
1278 if (strstrW (quals, parmQ) == NULL) {
1280 WCHAR question[MAXSTRING];
1281 static const WCHAR fmt[] = {'%','s',' ','\0'};
1283 /* Ask for confirmation */
1284 wsprintf(question, fmt, thisArg);
1285 ok = WCMD_ask_confirm(question, TRUE, NULL);
1287 /* Abort if answer is 'N' */
1294 lpDir.pFrom = thisArg;
1295 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1296 lpDir.wFunc = FO_DELETE;
1297 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1302 /* Handle no valid args */
1303 if (argsProcessed == 0) {
1304 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1310 /****************************************************************************
1316 void WCMD_rename (void) {
1321 WCHAR input[MAX_PATH];
1322 WCHAR *dotDst = NULL;
1324 WCHAR dir[MAX_PATH];
1325 WCHAR fname[MAX_PATH];
1326 WCHAR ext[MAX_PATH];
1331 /* Must be at least two args */
1332 if (param1[0] == 0x00 || param2[0] == 0x00) {
1333 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1338 /* Destination cannot contain a drive letter or directory separator */
1339 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1340 SetLastError(ERROR_INVALID_PARAMETER);
1346 /* Convert partial path to full path */
1347 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1348 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1349 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1350 dotDst = strchrW(param2, '.');
1352 /* Split into components */
1353 WCMD_splitpath(input, drive, dir, fname, ext);
1355 hff = FindFirstFile (input, &fd);
1356 while (hff != INVALID_HANDLE_VALUE) {
1357 WCHAR dest[MAX_PATH];
1358 WCHAR src[MAX_PATH];
1359 WCHAR *dotSrc = NULL;
1362 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1364 /* FIXME: If dest name or extension is *, replace with filename/ext
1365 part otherwise use supplied name. This supports:
1367 ren jim.* fred.* etc
1368 However, windows has a more complex algorithum supporting eg
1369 ?'s and *'s mid name */
1370 dotSrc = strchrW(fd.cFileName, '.');
1372 /* Build src & dest name */
1373 strcpyW(src, drive);
1376 dirLen = strlenW(src);
1377 strcatW(src, fd.cFileName);
1380 if (param2[0] == '*') {
1381 strcatW(dest, fd.cFileName);
1382 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1384 strcatW(dest, param2);
1385 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1388 /* Build Extension */
1389 if (dotDst && (*(dotDst+1)=='*')) {
1390 if (dotSrc) strcatW(dest, dotSrc);
1391 } else if (dotDst) {
1392 if (dotDst) strcatW(dest, dotDst);
1395 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1396 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1398 /* Check if file is read only, otherwise move it */
1399 attribs = GetFileAttributes(src);
1400 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1401 (attribs & FILE_ATTRIBUTE_READONLY)) {
1402 SetLastError(ERROR_ACCESS_DENIED);
1405 status = MoveFile (src, dest);
1409 WCMD_print_error ();
1413 /* Step on to next match */
1414 if (FindNextFile(hff, &fd) == 0) {
1416 hff = INVALID_HANDLE_VALUE;
1422 /*****************************************************************************
1425 * Make a copy of the environment.
1427 static WCHAR *WCMD_dupenv( const WCHAR *env )
1437 len += (strlenW(&env[len]) + 1);
1439 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1442 WINE_ERR("out of memory\n");
1445 memcpy (env_copy, env, len*sizeof (WCHAR));
1451 /*****************************************************************************
1454 * setlocal pushes the environment onto a stack
1455 * Save the environment as unicode so we don't screw anything up.
1457 void WCMD_setlocal (const WCHAR *s) {
1459 struct env_stack *env_copy;
1460 WCHAR cwd[MAX_PATH];
1462 /* DISABLEEXTENSIONS ignored */
1464 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1467 WINE_ERR ("out of memory\n");
1471 env = GetEnvironmentStringsW ();
1473 env_copy->strings = WCMD_dupenv (env);
1474 if (env_copy->strings)
1476 env_copy->next = saved_environment;
1477 saved_environment = env_copy;
1479 /* Save the current drive letter */
1480 GetCurrentDirectory (MAX_PATH, cwd);
1481 env_copy->u.cwd = cwd[0];
1484 LocalFree (env_copy);
1486 FreeEnvironmentStringsW (env);
1490 /*****************************************************************************
1493 * endlocal pops the environment off a stack
1494 * Note: When searching for '=', search from WCHAR position 1, to handle
1495 * special internal environment variables =C:, =D: etc
1497 void WCMD_endlocal (void) {
1498 WCHAR *env, *old, *p;
1499 struct env_stack *temp;
1502 if (!saved_environment)
1505 /* pop the old environment from the stack */
1506 temp = saved_environment;
1507 saved_environment = temp->next;
1509 /* delete the current environment, totally */
1510 env = GetEnvironmentStringsW ();
1511 old = WCMD_dupenv (GetEnvironmentStringsW ());
1514 n = strlenW(&old[len]) + 1;
1515 p = strchrW(&old[len] + 1, '=');
1519 SetEnvironmentVariableW (&old[len], NULL);
1524 FreeEnvironmentStringsW (env);
1526 /* restore old environment */
1527 env = temp->strings;
1530 n = strlenW(&env[len]) + 1;
1531 p = strchrW(&env[len] + 1, '=');
1535 SetEnvironmentVariableW (&env[len], p);
1540 /* Restore current drive letter */
1541 if (IsCharAlpha(temp->u.cwd)) {
1543 WCHAR cwd[MAX_PATH];
1544 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1546 wsprintf(envvar, fmt, temp->u.cwd);
1547 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1548 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1549 SetCurrentDirectory(cwd);
1557 /*****************************************************************************
1558 * WCMD_setshow_attrib
1560 * Display and optionally sets DOS attributes on a file or directory
1562 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1563 * As a result only the Readonly flag is correctly reported, the Archive bit
1564 * is always set and the rest are not implemented. We do the Right Thing anyway.
1566 * FIXME: No SET functionality.
1570 void WCMD_setshow_attrib (void) {
1575 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1577 if (param1[0] == '-') {
1578 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1582 if (strlenW(param1) == 0) {
1583 static const WCHAR slashStarW[] = {'\\','*','\0'};
1585 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1586 strcatW (param1, slashStarW);
1589 hff = FindFirstFile (param1, &fd);
1590 if (hff == INVALID_HANDLE_VALUE) {
1591 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1595 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1596 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1597 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1600 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1603 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1606 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1609 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1612 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1615 WCMD_output (fmt, flags, fd.cFileName);
1616 for (count=0; count < 8; count++) flags[count] = ' ';
1618 } while (FindNextFile(hff, &fd) != 0);
1623 /*****************************************************************************
1624 * WCMD_setshow_default
1626 * Set/Show the current default directory
1629 void WCMD_setshow_default (WCHAR *command) {
1637 static const WCHAR parmD[] = {'/','D','\0'};
1639 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1641 /* Skip /D and trailing whitespace if on the front of the command line */
1642 if (CompareString (LOCALE_USER_DEFAULT,
1643 NORM_IGNORECASE | SORT_STRINGSORT,
1644 command, 2, parmD, -1) == 2) {
1646 while (*command && *command==' ') command++;
1649 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1650 if (strlenW(command) == 0) {
1651 strcatW (cwd, newline);
1655 /* Remove any double quotes, which may be in the
1656 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1659 if (*command != '"') *pos++ = *command;
1664 /* Search for approprate directory */
1665 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1666 hff = FindFirstFile (string, &fd);
1667 while (hff != INVALID_HANDLE_VALUE) {
1668 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1669 WCHAR fpath[MAX_PATH];
1671 WCHAR dir[MAX_PATH];
1672 WCHAR fname[MAX_PATH];
1673 WCHAR ext[MAX_PATH];
1674 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1676 /* Convert path into actual directory spec */
1677 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1678 WCMD_splitpath(fpath, drive, dir, fname, ext);
1681 wsprintf(string, fmt, drive, dir, fd.cFileName);
1684 hff = INVALID_HANDLE_VALUE;
1688 /* Step on to next match */
1689 if (FindNextFile(hff, &fd) == 0) {
1691 hff = INVALID_HANDLE_VALUE;
1696 /* Change to that directory */
1697 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1699 status = SetCurrentDirectory (string);
1702 WCMD_print_error ();
1706 /* Restore old directory if drive letter would change, and
1707 CD x:\directory /D (or pushd c:\directory) not supplied */
1708 if ((strstrW(quals, parmD) == NULL) &&
1709 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1710 SetCurrentDirectory(cwd);
1714 /* Set special =C: type environment variable, for drive letter of
1715 change of directory, even if path was restored due to missing
1716 /D (allows changing drive letter when not resident on that
1718 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1720 strcpyW(env, equalW);
1721 memcpy(env+1, string, 2 * sizeof(WCHAR));
1723 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1724 SetEnvironmentVariable(env, string);
1731 /****************************************************************************
1734 * Set/Show the system date
1735 * FIXME: Can't change date yet
1738 void WCMD_setshow_date (void) {
1740 WCHAR curdate[64], buffer[64];
1742 static const WCHAR parmT[] = {'/','T','\0'};
1744 if (strlenW(param1) == 0) {
1745 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1746 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1747 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1748 if (strstrW (quals, parmT) == NULL) {
1749 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1750 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1751 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1753 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1757 else WCMD_print_error ();
1760 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1764 /****************************************************************************
1767 static int WCMD_compare( const void *a, const void *b )
1770 const WCHAR * const *str_a = a, * const *str_b = b;
1771 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1772 *str_a, -1, *str_b, -1 );
1773 if( r == CSTR_LESS_THAN ) return -1;
1774 if( r == CSTR_GREATER_THAN ) return 1;
1778 /****************************************************************************
1779 * WCMD_setshow_sortenv
1781 * sort variables into order for display
1782 * Optionally only display those who start with a stub
1783 * returns the count displayed
1785 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1787 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1790 if (stub) stublen = strlenW(stub);
1792 /* count the number of strings, and the total length */
1794 len += (strlenW(&s[len]) + 1);
1798 /* add the strings to an array */
1799 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1803 for( i=1; i<count; i++ )
1804 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1806 /* sort the array */
1807 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1810 for( i=0; i<count; i++ ) {
1811 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1812 NORM_IGNORECASE | SORT_STRINGSORT,
1813 str[i], stublen, stub, -1) == 2) {
1814 /* Don't display special internal variables */
1815 if (str[i][0] != '=') {
1816 WCMD_output_asis(str[i]);
1817 WCMD_output_asis(newline);
1824 return displayedcount;
1827 /****************************************************************************
1830 * Set/Show the environment variables
1833 void WCMD_setshow_env (WCHAR *s) {
1838 static const WCHAR parmP[] = {'/','P','\0'};
1841 if (param1[0] == 0x00 && quals[0] == 0x00) {
1842 env = GetEnvironmentStrings ();
1843 WCMD_setshow_sortenv( env, NULL );
1847 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1848 if (CompareString (LOCALE_USER_DEFAULT,
1849 NORM_IGNORECASE | SORT_STRINGSORT,
1850 s, 2, parmP, -1) == 2) {
1851 WCHAR string[MAXSTRING];
1855 while (*s && *s==' ') s++;
1857 /* If no parameter, or no '=' sign, return an error */
1858 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1859 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1863 /* Output the prompt */
1865 if (strlenW(p) != 0) WCMD_output(p);
1867 /* Read the reply */
1868 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1869 sizeof(string)/sizeof(WCHAR), &count, NULL);
1871 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1872 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1873 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1874 wine_dbgstr_w(string));
1875 status = SetEnvironmentVariable (s, string);
1880 p = strchrW (s, '=');
1882 env = GetEnvironmentStrings ();
1883 if (WCMD_setshow_sortenv( env, s ) == 0) {
1884 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1891 if (strlenW(p) == 0) p = NULL;
1892 status = SetEnvironmentVariable (s, p);
1893 gle = GetLastError();
1894 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1896 } else if ((!status)) WCMD_print_error();
1900 /****************************************************************************
1903 * Set/Show the path environment variable
1906 void WCMD_setshow_path (WCHAR *command) {
1910 static const WCHAR pathW[] = {'P','A','T','H','\0'};
1911 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1913 if (strlenW(param1) == 0) {
1914 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1916 WCMD_output_asis ( pathEqW);
1917 WCMD_output_asis ( string);
1918 WCMD_output_asis ( newline);
1921 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1925 if (*command == '=') command++; /* Skip leading '=' */
1926 status = SetEnvironmentVariable (pathW, command);
1927 if (!status) WCMD_print_error();
1931 /****************************************************************************
1932 * WCMD_setshow_prompt
1934 * Set or show the command prompt.
1937 void WCMD_setshow_prompt (void) {
1940 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1942 if (strlenW(param1) == 0) {
1943 SetEnvironmentVariable (promptW, NULL);
1947 while ((*s == '=') || (*s == ' ')) s++;
1948 if (strlenW(s) == 0) {
1949 SetEnvironmentVariable (promptW, NULL);
1951 else SetEnvironmentVariable (promptW, s);
1955 /****************************************************************************
1958 * Set/Show the system time
1959 * FIXME: Can't change time yet
1962 void WCMD_setshow_time (void) {
1964 WCHAR curtime[64], buffer[64];
1967 static const WCHAR parmT[] = {'/','T','\0'};
1969 if (strlenW(param1) == 0) {
1971 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1972 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1973 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1974 if (strstrW (quals, parmT) == NULL) {
1975 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1976 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1977 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1979 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1983 else WCMD_print_error ();
1986 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1990 /****************************************************************************
1993 * Shift batch parameters.
1994 * Optional /n says where to start shifting (n=0-8)
1997 void WCMD_shift (WCHAR *command) {
2000 if (context != NULL) {
2001 WCHAR *pos = strchrW(command, '/');
2006 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2007 start = (*(pos+1) - '0');
2009 SetLastError(ERROR_INVALID_PARAMETER);
2014 WINE_TRACE("Shifting variables, starting at %d\n", start);
2015 for (i=start;i<=8;i++) {
2016 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2018 context -> shift_count[9] = context -> shift_count[9] + 1;
2023 /****************************************************************************
2026 * Set the console title
2028 void WCMD_title (WCHAR *command) {
2029 SetConsoleTitle(command);
2032 /****************************************************************************
2035 * Copy a file to standard output.
2038 void WCMD_type (WCHAR *command) {
2041 WCHAR *argN = command;
2042 BOOL writeHeaders = FALSE;
2044 if (param1[0] == 0x00) {
2045 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2049 if (param2[0] != 0x00) writeHeaders = TRUE;
2051 /* Loop through all args */
2054 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2062 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2063 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2064 FILE_ATTRIBUTE_NORMAL, NULL);
2065 if (h == INVALID_HANDLE_VALUE) {
2066 WCMD_print_error ();
2067 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2071 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2072 WCMD_output(fmt, thisArg);
2074 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2075 if (count == 0) break; /* ReadFile reports success on EOF! */
2077 WCMD_output_asis (buffer);
2084 /****************************************************************************
2087 * Output either a file or stdin to screen in pages
2090 void WCMD_more (WCHAR *command) {
2093 WCHAR *argN = command;
2094 BOOL useinput = FALSE;
2096 WCHAR moreStrPage[100];
2099 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2100 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2101 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2102 ')',' ','-','-','\n','\0'};
2103 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2105 /* Prefix the NLS more with '-- ', then load the text */
2107 strcpyW(moreStr, moreStart);
2108 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2109 (sizeof(moreStr)/sizeof(WCHAR))-3);
2111 if (param1[0] == 0x00) {
2113 /* Wine implements pipes via temporary files, and hence stdin is
2114 effectively reading from the file. This means the prompts for
2115 more are satistied by the next line from the input (file). To
2116 avoid this, ensure stdin is to the console */
2117 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2118 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2119 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2120 FILE_ATTRIBUTE_NORMAL, 0);
2121 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2123 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2124 once you get in this bit unless due to a pipe, its going to end badly... */
2126 wsprintf(moreStrPage, moreFmt, moreStr);
2128 WCMD_enter_paged_mode(moreStrPage);
2129 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2130 if (count == 0) break; /* ReadFile reports success on EOF! */
2132 WCMD_output_asis (buffer);
2134 WCMD_leave_paged_mode();
2136 /* Restore stdin to what it was */
2137 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2138 CloseHandle(hConIn);
2142 BOOL needsPause = FALSE;
2144 /* Loop through all args */
2145 WCMD_enter_paged_mode(moreStrPage);
2148 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2156 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2157 WCMD_leave_paged_mode();
2158 WCMD_output_asis(moreStrPage);
2159 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2160 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2161 WCMD_enter_paged_mode(moreStrPage);
2165 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2166 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2167 FILE_ATTRIBUTE_NORMAL, NULL);
2168 if (h == INVALID_HANDLE_VALUE) {
2169 WCMD_print_error ();
2170 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2174 ULONG64 fileLen = 0;
2175 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2177 /* Get the file size */
2178 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2179 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2182 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2183 if (count == 0) break; /* ReadFile reports success on EOF! */
2187 /* Update % count (would be used in WCMD_output_asis as prompt) */
2188 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2190 WCMD_output_asis (buffer);
2196 WCMD_leave_paged_mode();
2200 /****************************************************************************
2203 * Display verify flag.
2204 * FIXME: We don't actually do anything with the verify flag other than toggle
2208 void WCMD_verify (WCHAR *command) {
2212 count = strlenW(command);
2214 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2215 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2218 if (lstrcmpiW(command, onW) == 0) {
2222 else if (lstrcmpiW(command, offW) == 0) {
2226 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2229 /****************************************************************************
2232 * Display version info.
2235 void WCMD_version (void) {
2237 WCMD_output (version_string);
2241 /****************************************************************************
2244 * Display volume info and/or set volume label. Returns 0 if error.
2247 int WCMD_volume (int mode, WCHAR *path) {
2249 DWORD count, serial;
2250 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2253 if (strlenW(path) == 0) {
2254 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2256 WCMD_print_error ();
2259 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2260 &serial, NULL, NULL, NULL, 0);
2263 static const WCHAR fmt[] = {'%','s','\\','\0'};
2264 if ((path[1] != ':') || (strlenW(path) != 2)) {
2265 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2268 wsprintf (curdir, fmt, path);
2269 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2274 WCMD_print_error ();
2277 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2278 curdir[0], label, HIWORD(serial), LOWORD(serial));
2280 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2281 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2282 sizeof(string)/sizeof(WCHAR), &count, NULL);
2284 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2285 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2287 if (strlenW(path) != 0) {
2288 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2291 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2297 /**************************************************************************
2300 * Exit either the process, or just this batch program
2304 void WCMD_exit (CMD_LIST **cmdList) {
2306 static const WCHAR parmB[] = {'/','B','\0'};
2307 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2309 if (context && lstrcmpiW(quals, parmB) == 0) {
2311 context -> skip_rest = TRUE;
2318 /**************************************************************************
2321 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2324 * Returns True if Y (or A) answer is selected
2325 * If optionAll contains a pointer, ALL is allowed, and if answered
2329 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2331 WCHAR msgbuffer[MAXSTRING];
2332 WCHAR Ybuffer[MAXSTRING];
2333 WCHAR Nbuffer[MAXSTRING];
2334 WCHAR Abuffer[MAXSTRING];
2335 WCHAR answer[MAX_PATH] = {'\0'};
2338 /* Load the translated 'Are you sure', plus valid answers */
2339 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2340 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2341 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2342 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2344 /* Loop waiting on a Y or N */
2345 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2346 static const WCHAR startBkt[] = {' ','(','\0'};
2347 static const WCHAR endBkt[] = {')','?','\0'};
2349 WCMD_output_asis (message);
2351 WCMD_output_asis (msgbuffer);
2353 WCMD_output_asis (startBkt);
2354 WCMD_output_asis (Ybuffer);
2355 WCMD_output_asis (fslashW);
2356 WCMD_output_asis (Nbuffer);
2358 WCMD_output_asis (fslashW);
2359 WCMD_output_asis (Abuffer);
2361 WCMD_output_asis (endBkt);
2362 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2363 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2364 answer[0] = toupperW(answer[0]);
2367 /* Return the answer */
2368 return ((answer[0] == Ybuffer[0]) ||
2369 (optionAll && (answer[0] == Abuffer[0])));
2372 /*****************************************************************************
2375 * Lists or sets file associations (assoc = TRUE)
2376 * Lists or sets file types (assoc = FALSE)
2378 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2381 DWORD accessOptions = KEY_READ;
2383 LONG rc = ERROR_SUCCESS;
2384 WCHAR keyValue[MAXSTRING];
2385 DWORD valueLen = MAXSTRING;
2387 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2388 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2390 /* See if parameter includes '=' */
2392 newValue = strchrW(command, '=');
2393 if (newValue) accessOptions |= KEY_WRITE;
2395 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2396 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2397 accessOptions, &key) != ERROR_SUCCESS) {
2398 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2402 /* If no parameters then list all associations */
2403 if (*command == 0x00) {
2406 /* Enumerate all the keys */
2407 while (rc != ERROR_NO_MORE_ITEMS) {
2408 WCHAR keyName[MAXSTRING];
2411 /* Find the next value */
2412 nameLen = MAXSTRING;
2413 rc = RegEnumKeyEx(key, index++,
2415 NULL, NULL, NULL, NULL);
2417 if (rc == ERROR_SUCCESS) {
2419 /* Only interested in extension ones if assoc, or others
2421 if ((keyName[0] == '.' && assoc) ||
2422 (!(keyName[0] == '.') && (!assoc)))
2424 WCHAR subkey[MAXSTRING];
2425 strcpyW(subkey, keyName);
2426 if (!assoc) strcatW(subkey, shOpCmdW);
2428 if (RegOpenKeyEx(key, subkey, 0,
2429 accessOptions, &readKey) == ERROR_SUCCESS) {
2431 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2432 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2433 (LPBYTE)keyValue, &valueLen);
2434 WCMD_output_asis(keyName);
2435 WCMD_output_asis(equalW);
2436 /* If no default value found, leave line empty after '=' */
2437 if (rc == ERROR_SUCCESS) {
2438 WCMD_output_asis(keyValue);
2440 WCMD_output_asis(newline);
2445 RegCloseKey(readKey);
2449 /* Parameter supplied - if no '=' on command line, its a query */
2450 if (newValue == NULL) {
2452 WCHAR subkey[MAXSTRING];
2454 /* Query terminates the parameter at the first space */
2455 strcpyW(keyValue, command);
2456 space = strchrW(keyValue, ' ');
2457 if (space) *space=0x00;
2459 /* Set up key name */
2460 strcpyW(subkey, keyValue);
2461 if (!assoc) strcatW(subkey, shOpCmdW);
2463 if (RegOpenKeyEx(key, subkey, 0,
2464 accessOptions, &readKey) == ERROR_SUCCESS) {
2466 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2467 (LPBYTE)keyValue, &valueLen);
2468 WCMD_output_asis(command);
2469 WCMD_output_asis(equalW);
2470 /* If no default value found, leave line empty after '=' */
2471 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2472 WCMD_output_asis(newline);
2473 RegCloseKey(readKey);
2476 WCHAR msgbuffer[MAXSTRING];
2477 WCHAR outbuffer[MAXSTRING];
2479 /* Load the translated 'File association not found' */
2481 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2483 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2485 wsprintf(outbuffer, msgbuffer, keyValue);
2486 WCMD_output_asis(outbuffer);
2490 /* Not a query - its a set or clear of a value */
2493 WCHAR subkey[MAXSTRING];
2495 /* Get pointer to new value */
2499 /* Set up key name */
2500 strcpyW(subkey, command);
2501 if (!assoc) strcatW(subkey, shOpCmdW);
2503 /* If nothing after '=' then clear value - only valid for ASSOC */
2504 if (*newValue == 0x00) {
2506 if (assoc) rc = RegDeleteKey(key, command);
2507 if (assoc && rc == ERROR_SUCCESS) {
2508 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2510 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2515 WCHAR msgbuffer[MAXSTRING];
2516 WCHAR outbuffer[MAXSTRING];
2518 /* Load the translated 'File association not found' */
2520 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2521 sizeof(msgbuffer)/sizeof(WCHAR));
2523 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2524 sizeof(msgbuffer)/sizeof(WCHAR));
2526 wsprintf(outbuffer, msgbuffer, keyValue);
2527 WCMD_output_asis(outbuffer);
2531 /* It really is a set value = contents */
2533 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2534 accessOptions, NULL, &readKey, NULL);
2535 if (rc == ERROR_SUCCESS) {
2536 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2537 (LPBYTE)newValue, strlenW(newValue));
2538 RegCloseKey(readKey);
2541 if (rc != ERROR_SUCCESS) {
2545 WCMD_output_asis(command);
2546 WCMD_output_asis(equalW);
2547 WCMD_output_asis(newValue);
2548 WCMD_output_asis(newline);
2558 /****************************************************************************
2561 * Clear the terminal screen.
2564 void WCMD_color (void) {
2566 /* Emulate by filling the screen from the top left to bottom right with
2567 spaces, then moving the cursor to the top left afterwards */
2568 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2569 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2571 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2572 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2576 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2582 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2587 /* Convert the color hex digits */
2588 if (param1[0] == 0x00) {
2589 color = defaultColor;
2591 color = strtoulW(param1, NULL, 16);
2594 /* Fail if fg == bg color */
2595 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2600 /* Set the current screen contents and ensure all future writes
2601 remain this color */
2602 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2603 SetConsoleTextAttribute(hStdOut, color);