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: No wildcard support
114 void WCMD_copy (void) {
119 WCHAR outpath
[MAX_PATH
], inpath
[MAX_PATH
], *infile
, copycmd
[3];
121 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
123 if (param1
[0] == 0x00) {
124 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
128 if ((strchrW(param1
,'*') != NULL
) && (strchrW(param1
,'%') != NULL
)) {
129 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
133 /* If no destination supplied, assume current directory */
134 if (param2
[0] == 0x00) {
135 strcpyW(param2
, dotW
);
138 GetFullPathName (param2
, sizeof(outpath
)/sizeof(WCHAR
), outpath
, NULL
);
139 if (outpath
[strlenW(outpath
) - 1] == '\\')
140 outpath
[strlenW(outpath
) - 1] = '\0';
141 hff
= FindFirstFile (outpath
, &fd
);
142 if (hff
!= INVALID_HANDLE_VALUE
) {
143 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
144 GetFullPathName (param1
, sizeof(inpath
)/sizeof(WCHAR
), inpath
, &infile
);
145 strcatW (outpath
, slashW
);
146 strcatW (outpath
, infile
);
151 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
152 if (strstrW (quals
, parmNoY
))
154 else if (strstrW (quals
, parmY
))
157 len
= GetEnvironmentVariable (copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
158 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
)) && ! lstrcmpiW (copycmd
, parmY
));
162 hff
= FindFirstFile (outpath
, &fd
);
163 if (hff
!= INVALID_HANDLE_VALUE
) {
164 WCHAR buffer
[MAXSTRING
];
168 wsprintf(buffer
, WCMD_LoadMessage(WCMD_OVERWRITE
), outpath
);
169 force
= WCMD_ask_confirm(buffer
, FALSE
, NULL
);
174 status
= CopyFile (param1
, outpath
, FALSE
);
175 if (!status
) WCMD_print_error ();
179 /****************************************************************************
182 * Create a directory.
184 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
185 * they do not already exist.
188 static BOOL
create_full_path(WCHAR
* path
)
194 new_path
= HeapAlloc(GetProcessHeap(),0,(strlenW(path
) * sizeof(WCHAR
))+1);
195 strcpyW(new_path
,path
);
197 while ((len
= strlenW(new_path
)) && new_path
[len
- 1] == '\\')
198 new_path
[len
- 1] = 0;
200 while (!CreateDirectory(new_path
,NULL
))
203 DWORD last_error
= GetLastError();
204 if (last_error
== ERROR_ALREADY_EXISTS
)
207 if (last_error
!= ERROR_PATH_NOT_FOUND
)
213 if (!(slash
= strrchrW(new_path
,'\\')) && ! (slash
= strrchrW(new_path
,'/')))
219 len
= slash
- new_path
;
221 if (!create_full_path(new_path
))
226 new_path
[len
] = '\\';
228 HeapFree(GetProcessHeap(),0,new_path
);
232 void WCMD_create_dir (void) {
234 if (param1
[0] == 0x00) {
235 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
238 if (!create_full_path(param1
)) WCMD_print_error ();
241 /****************************************************************************
244 * Delete a file or wildcarded set.
247 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
248 * - Each set is a pattern, eg /ahr /as-r means
249 * readonly+hidden OR nonreadonly system files
250 * - The '-' applies to a single field, ie /a:-hr means read only
254 BOOL
WCMD_delete (WCHAR
*command
, BOOL expectDir
) {
257 int argsProcessed
= 0;
258 WCHAR
*argN
= command
;
259 BOOL foundAny
= FALSE
;
260 static const WCHAR parmA
[] = {'/','A','\0'};
261 static const WCHAR parmQ
[] = {'/','Q','\0'};
262 static const WCHAR parmP
[] = {'/','P','\0'};
263 static const WCHAR parmS
[] = {'/','S','\0'};
264 static const WCHAR parmF
[] = {'/','F','\0'};
266 /* If not recursing, clear error flag */
267 if (expectDir
) errorlevel
= 0;
269 /* Loop through all args */
271 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
272 WCHAR argCopy
[MAX_PATH
];
274 if (argN
&& argN
[0] != '/') {
278 WCHAR fpath
[MAX_PATH
];
280 BOOL handleParm
= TRUE
;
282 static const WCHAR anyExt
[]= {'.','*','\0'};
284 strcpyW(argCopy
, thisArg
);
285 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
286 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
289 /* If filename part of parameter is * or *.*, prompt unless
291 if ((strstrW (quals
, parmQ
) == NULL
) && (strstrW (quals
, parmP
) == NULL
)) {
295 WCHAR fname
[MAX_PATH
];
298 /* Convert path into actual directory spec */
299 GetFullPathName (argCopy
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
300 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
302 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
303 if ((strcmpW(fname
, starW
) == 0) &&
304 (*ext
== 0x00 || (strcmpW(ext
, anyExt
) == 0))) {
306 WCHAR question
[MAXSTRING
];
307 static const WCHAR fmt
[] = {'%','s',' ','\0'};
309 /* Note: Flag as found, to avoid file not found message */
312 /* Ask for confirmation */
313 wsprintf(question
, fmt
, fpath
);
314 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
316 /* Abort if answer is 'N' */
321 /* First, try to delete in the current directory */
322 hff
= FindFirstFile (argCopy
, &fd
);
323 if (hff
== INVALID_HANDLE_VALUE
) {
329 /* Support del <dirname> by just deleting all files dirname\* */
330 if (handleParm
&& (strchrW(argCopy
,'*') == NULL
) && (strchrW(argCopy
,'?') == NULL
)
331 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
332 WCHAR modifiedParm
[MAX_PATH
];
333 static const WCHAR slashStar
[] = {'\\','*','\0'};
335 strcpyW(modifiedParm
, argCopy
);
336 strcatW(modifiedParm
, slashStar
);
339 WCMD_delete(modifiedParm
, FALSE
);
341 } else if (handleParm
) {
343 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
344 strcpyW (fpath
, argCopy
);
346 p
= strrchrW (fpath
, '\\');
349 strcatW (fpath
, fd
.cFileName
);
351 else strcpyW (fpath
, fd
.cFileName
);
352 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
354 WCHAR
*nextA
= strstrW (quals
, parmA
);
356 /* Handle attribute matching (/A) */
359 while (nextA
!= NULL
&& !ok
) {
361 WCHAR
*thisA
= (nextA
+2);
364 /* Skip optional : */
365 if (*thisA
== ':') thisA
++;
367 /* Parse each of the /A[:]xxx in turn */
368 while (*thisA
&& *thisA
!= '/') {
370 BOOL attribute
= FALSE
;
372 /* Match negation of attribute first */
378 /* Match attribute */
380 case 'R': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
);
382 case 'H': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
);
384 case 'S': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
);
386 case 'A': attribute
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
);
389 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
392 /* Now check result, keeping a running boolean about whether it
393 matches all parsed attribues so far */
394 if (attribute
&& !negate
) {
396 } else if (!attribute
&& negate
) {
404 /* Save the running total as the final result */
407 /* Step on to next /A set */
408 nextA
= strstrW (nextA
+1, parmA
);
412 /* /P means prompt for each file */
413 if (ok
&& strstrW (quals
, parmP
) != NULL
) {
414 WCHAR question
[MAXSTRING
];
416 /* Ask for confirmation */
417 wsprintf(question
, WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
418 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
421 /* Only proceed if ok to */
424 /* If file is read only, and /F supplied, delete it */
425 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
426 strstrW (quals
, parmF
) != NULL
) {
427 SetFileAttributes(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
430 /* Now do the delete */
431 if (!DeleteFile (fpath
)) WCMD_print_error ();
435 } while (FindNextFile(hff
, &fd
) != 0);
439 /* Now recurse into all subdirectories handling the parameter in the same way */
440 if (strstrW (quals
, parmS
) != NULL
) {
442 WCHAR thisDir
[MAX_PATH
];
447 WCHAR fname
[MAX_PATH
];
450 /* Convert path into actual directory spec */
451 GetFullPathName (argCopy
, sizeof(thisDir
)/sizeof(WCHAR
), thisDir
, NULL
);
452 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
454 strcpyW(thisDir
, drive
);
455 strcatW(thisDir
, dir
);
456 cPos
= strlenW(thisDir
);
458 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
460 /* Append '*' to the directory */
462 thisDir
[cPos
+1] = 0x00;
464 hff
= FindFirstFile (thisDir
, &fd
);
466 /* Remove residual '*' */
467 thisDir
[cPos
] = 0x00;
469 if (hff
!= INVALID_HANDLE_VALUE
) {
470 DIRECTORY_STACK
*allDirs
= NULL
;
471 DIRECTORY_STACK
*lastEntry
= NULL
;
474 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
475 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
476 (strcmpW(fd
.cFileName
, dotW
) != 0)) {
478 DIRECTORY_STACK
*nextDir
;
479 WCHAR subParm
[MAX_PATH
];
481 /* Work out search parameter in sub dir */
482 strcpyW (subParm
, thisDir
);
483 strcatW (subParm
, fd
.cFileName
);
484 strcatW (subParm
, slashW
);
485 strcatW (subParm
, fname
);
486 strcatW (subParm
, ext
);
487 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
489 /* Allocate memory, add to list */
490 nextDir
= (DIRECTORY_STACK
*) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK
));
491 if (allDirs
== NULL
) allDirs
= nextDir
;
492 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
494 nextDir
->next
= NULL
;
495 nextDir
->dirName
= HeapAlloc(GetProcessHeap(),0,
496 (strlenW(subParm
)+1) * sizeof(WCHAR
));
497 strcpyW(nextDir
->dirName
, subParm
);
499 } while (FindNextFile(hff
, &fd
) != 0);
502 /* Go through each subdir doing the delete */
503 while (allDirs
!= NULL
) {
504 DIRECTORY_STACK
*tempDir
;
506 tempDir
= allDirs
->next
;
507 found
|= WCMD_delete (allDirs
->dirName
, FALSE
);
509 HeapFree(GetProcessHeap(),0,allDirs
->dirName
);
510 HeapFree(GetProcessHeap(),0,allDirs
);
515 /* Keep running total to see if any found, and if not recursing
516 issue error message */
520 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), argCopy
);
527 /* Handle no valid args */
528 if (argsProcessed
== 0) {
529 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
535 /****************************************************************************
538 * Echo input to the screen (or not). We don't try to emulate the bugs
539 * in DOS (try typing "ECHO ON AGAIN" for an example).
542 void WCMD_echo (const WCHAR
*command
) {
546 if ((command
[0] == '.') && (command
[1] == 0)) {
547 WCMD_output (newline
);
552 count
= strlenW(command
);
554 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
555 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
558 if (lstrcmpiW(command
, onW
) == 0) {
562 if (lstrcmpiW(command
, offW
) == 0) {
566 WCMD_output_asis (command
);
567 WCMD_output (newline
);
571 /**************************************************************************
574 * Batch file loop processing.
576 * On entry: cmdList contains the syntax up to the set
577 * next cmdList and all in that bracket contain the set data
578 * next cmdlist contains the DO cmd
579 * following that is either brackets or && entries (as per if)
581 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
582 * will probably work here, but the reverse is not necessarily the case...
585 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
590 const WCHAR inW
[] = {'i', 'n', '\0'};
591 const WCHAR doW
[] = {'d', 'o', ' ','\0'};
592 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
599 the first line includes the % variable name as first parm
600 we have been provided with more parts to the command
601 and there is at least some set data
602 and IN as the one after that */
603 if (lstrcmpiW (WCMD_parameter (p
, 1, NULL
), inW
)
604 || (*cmdList
) == NULL
605 || (*cmdList
)->nextcommand
== NULL
606 || (param1
[0] != '%')
607 || (strlenW(param1
) > 3)) {
608 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
612 /* Save away where the set of data starts and the variable */
613 strcpyW(variable
, param1
);
614 thisDepth
= (*cmdList
)->bracketDepth
;
615 *cmdList
= (*cmdList
)->nextcommand
;
616 setStart
= (*cmdList
);
618 /* Skip until the close bracket */
619 WINE_TRACE("Searching %p as the set\n", *cmdList
);
621 (*cmdList
)->command
!= NULL
&&
622 (*cmdList
)->bracketDepth
> thisDepth
) {
623 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
624 *cmdList
= (*cmdList
)->nextcommand
;
627 /* Skip the close bracket, if there is one */
628 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
630 /* Syntax error if missing close bracket, or nothing following it
631 and once we have the complete set, we expect a DO */
632 WINE_TRACE("Looking for 'do' in %p\n", *cmdList
);
633 if ((*cmdList
== NULL
) ||
634 (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
635 (*cmdList
)->command
, 3, doW
, -1) != 2)) {
636 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
640 /* Save away the starting position for the commands (and offset for the
644 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
647 /* Loop through all set entries */
649 thisSet
->command
!= NULL
&&
650 thisSet
->bracketDepth
>= thisDepth
) {
652 /* Loop through all entries on the same line */
655 WINE_TRACE("Processing for set %p\n", thisSet
);
657 while (*(item
= WCMD_parameter (thisSet
->command
, i
, NULL
))) {
660 * If the parameter within the set has a wildcard then search for matching files
661 * otherwise do a literal substitution.
663 static const WCHAR wildcards
[] = {'*','?','\0'};
664 CMD_LIST
*thisCmdStart
= cmdStart
;
666 WINE_TRACE("Processing for item '%s'\n", wine_dbgstr_w(item
));
667 if (strpbrkW (item
, wildcards
)) {
668 hff
= FindFirstFile (item
, &fd
);
669 if (hff
!= INVALID_HANDLE_VALUE
) {
671 BOOL isDirectory
= (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
);
672 if ((isDirs
&& isDirectory
) ||
673 (!isDirs
&& !isDirectory
))
675 thisCmdStart
= cmdStart
;
676 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
677 WCMD_part_execute (&thisCmdStart
, firstCmd
, variable
,
678 fd
.cFileName
, FALSE
, TRUE
);
681 } while (FindNextFile(hff
, &fd
) != 0);
685 WCMD_part_execute(&thisCmdStart
, firstCmd
, variable
, item
, FALSE
, TRUE
);
688 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
689 cmdEnd
= thisCmdStart
;
693 /* Move onto the next set line */
694 thisSet
= thisSet
->nextcommand
;
697 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
698 all processing, OR it should be pointing to the end of && processing OR
699 it should be pointing at the NULL end of bracket for the DO. The return
700 value needs to be the NEXT command to execute, which it either is, or
701 we need to step over the closing bracket */
703 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
707 /*****************************************************************************
710 * Execute a command, and any && or bracketed follow on to the command. The
711 * first command to be executed may not be at the front of the
712 * commands->thiscommand string (eg. it may point after a DO or ELSE
713 * Returns TRUE if something like exit or goto has aborted all processing
715 void WCMD_part_execute(CMD_LIST
**cmdList
, WCHAR
*firstcmd
, WCHAR
*variable
,
716 WCHAR
*value
, BOOL isIF
, BOOL conditionTRUE
) {
718 CMD_LIST
*curPosition
= *cmdList
;
719 int myDepth
= (*cmdList
)->bracketDepth
;
721 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
722 cmdList
, wine_dbgstr_w(firstcmd
),
723 wine_dbgstr_w(variable
), wine_dbgstr_w(value
),
726 /* Skip leading whitespace between condition and the command */
727 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
729 /* Process the first command, if there is one */
730 if (conditionTRUE
&& firstcmd
&& *firstcmd
) {
731 WCHAR
*command
= WCMD_strdupW(firstcmd
);
732 WCMD_execute (firstcmd
, variable
, value
, cmdList
);
737 /* If it didnt move the position, step to next command */
738 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
740 /* Process any other parts of the command */
742 BOOL processThese
= TRUE
;
744 if (isIF
) processThese
= conditionTRUE
;
747 const WCHAR ifElse
[] = {'e','l','s','e',' ','\0'};
749 /* execute all appropriate commands */
750 curPosition
= *cmdList
;
752 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
754 (*cmdList
)->isAmphersand
,
755 (*cmdList
)->bracketDepth
, myDepth
);
757 /* Execute any appended to the statement with &&'s */
758 if ((*cmdList
)->isAmphersand
) {
760 WCMD_execute ((*cmdList
)->command
, variable
, value
, cmdList
);
762 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
764 /* Execute any appended to the statement with (...) */
765 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
767 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, variable
, value
);
768 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList
);
770 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
772 /* End of the command - does 'ELSE ' follow as the next command? */
774 if (isIF
&& CompareString (LOCALE_USER_DEFAULT
,
775 NORM_IGNORECASE
| SORT_STRINGSORT
,
776 (*cmdList
)->command
, 5, ifElse
, -1) == 2) {
778 /* Swap between if and else processing */
779 processThese
= !processThese
;
781 /* Process the ELSE part */
783 WCHAR
*cmd
= ((*cmdList
)->command
) + strlenW(ifElse
);
785 /* Skip leading whitespace between condition and the command */
786 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
788 WCMD_execute (cmd
, variable
, value
, cmdList
);
791 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
793 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
802 /*****************************************************************************
805 * Execute a command after substituting variable text for the supplied parameter
808 void WCMD_execute (WCHAR
*orig_cmd
, WCHAR
*param
, WCHAR
*subst
, CMD_LIST
**cmdList
) {
810 WCHAR
*new_cmd
, *p
, *s
, *dup
;
814 size
= (strlenW (orig_cmd
) + 1) * sizeof(WCHAR
);
815 new_cmd
= (WCHAR
*) LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, size
);
816 dup
= s
= WCMD_strdupW(orig_cmd
);
818 while ((p
= strstrW (s
, param
))) {
820 size
+= strlenW (subst
) * sizeof(WCHAR
);
821 new_cmd
= (WCHAR
*) LocalReAlloc ((HANDLE
)new_cmd
, size
, 0);
822 strcatW (new_cmd
, s
);
823 strcatW (new_cmd
, subst
);
824 s
= p
+ strlenW (param
);
826 strcatW (new_cmd
, s
);
827 WCMD_process_command (new_cmd
, cmdList
);
829 LocalFree ((HANDLE
)new_cmd
);
831 WCMD_process_command (orig_cmd
, cmdList
);
836 /**************************************************************************
839 * Simple on-line help. Help text is stored in the resource file.
842 void WCMD_give_help (WCHAR
*command
) {
846 command
= WCMD_strtrim_leading_spaces(command
);
847 if (strlenW(command
) == 0) {
848 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
851 for (i
=0; i
<=WCMD_EXIT
; i
++) {
852 if (CompareString (LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
853 param1
, -1, inbuilt
[i
], -1) == 2) {
854 WCMD_output_asis (WCMD_LoadMessage(i
));
858 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), param1
);
863 /****************************************************************************
866 * Batch file jump instruction. Not the most efficient algorithm ;-)
867 * Prints error message if the specified label cannot be found - the file pointer is
868 * then at EOF, effectively stopping the batch file.
869 * FIXME: DOS is supposed to allow labels with spaces - we don't.
872 void WCMD_goto (CMD_LIST
**cmdList
) {
874 WCHAR string
[MAX_PATH
];
876 /* Do not process any more parts of a processed multipart or multilines command */
879 if (param1
[0] == 0x00) {
880 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
883 if (context
!= NULL
) {
884 WCHAR
*paramStart
= param1
;
885 static const WCHAR eofW
[] = {':','e','o','f','\0'};
887 /* Handle special :EOF label */
888 if (lstrcmpiW (eofW
, param1
) == 0) {
889 context
-> skip_rest
= TRUE
;
893 /* Support goto :label as well as goto label */
894 if (*paramStart
== ':') paramStart
++;
896 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
897 while (WCMD_fgets (string
, sizeof(string
)/sizeof(WCHAR
), context
-> h
)) {
898 if ((string
[0] == ':') && (lstrcmpiW (&string
[1], paramStart
) == 0)) return;
900 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET
));
905 /*****************************************************************************
908 * Push a directory onto the stack
911 void WCMD_pushd (WCHAR
*command
) {
912 struct env_stack
*curdir
;
914 static const WCHAR parmD
[] = {'/','D','\0'};
916 if (strchrW(command
, '/') != NULL
) {
917 SetLastError(ERROR_INVALID_PARAMETER
);
922 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
923 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
924 if( !curdir
|| !thisdir
) {
927 WINE_ERR ("out of memory\n");
931 /* Change directory using CD code with /D parameter */
932 strcpyW(quals
, parmD
);
933 GetCurrentDirectoryW (1024, thisdir
);
935 WCMD_setshow_default(command
);
941 curdir
-> next
= pushd_directories
;
942 curdir
-> strings
= thisdir
;
943 if (pushd_directories
== NULL
) {
944 curdir
-> u
.stackdepth
= 1;
946 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
948 pushd_directories
= curdir
;
953 /*****************************************************************************
956 * Pop a directory from the stack
959 void WCMD_popd (void) {
960 struct env_stack
*temp
= pushd_directories
;
962 if (!pushd_directories
)
965 /* pop the old environment from the stack, and make it the current dir */
966 pushd_directories
= temp
->next
;
967 SetCurrentDirectoryW(temp
->strings
);
968 LocalFree (temp
->strings
);
972 /****************************************************************************
975 * Batch file conditional.
977 * On entry, cmdlist will point to command containing the IF, and optionally
978 * the first command to execute (if brackets not found)
979 * If &&'s were found, this may be followed by a record flagged as isAmpersand
980 * If ('s were found, execute all within that bracket
981 * Command may optionally be followed by an ELSE - need to skip instructions
982 * in the else using the same logic
984 * FIXME: Much more syntax checking needed!
987 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
) {
989 int negate
= 0, test
= 0;
990 WCHAR condition
[MAX_PATH
], *command
, *s
;
991 static const WCHAR notW
[] = {'n','o','t','\0'};
992 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
993 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
994 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
995 static const WCHAR eqeqW
[] = {'=','=','\0'};
997 if (!lstrcmpiW (param1
, notW
)) {
999 strcpyW (condition
, param2
);
1002 strcpyW (condition
, param1
);
1004 if (!lstrcmpiW (condition
, errlvlW
)) {
1005 if (errorlevel
>= atoiW(WCMD_parameter (p
, 1+negate
, NULL
))) test
= 1;
1006 WCMD_parameter (p
, 2+negate
, &command
);
1008 else if (!lstrcmpiW (condition
, existW
)) {
1009 if (GetFileAttributes(WCMD_parameter (p
, 1+negate
, NULL
)) != INVALID_FILE_ATTRIBUTES
) {
1012 WCMD_parameter (p
, 2+negate
, &command
);
1014 else if (!lstrcmpiW (condition
, defdW
)) {
1015 if (GetEnvironmentVariable(WCMD_parameter (p
, 1+negate
, NULL
), NULL
, 0) > 0) {
1018 WCMD_parameter (p
, 2+negate
, &command
);
1020 else if ((s
= strstrW (p
, eqeqW
))) {
1022 if (!lstrcmpiW (condition
, WCMD_parameter (s
, 0, NULL
))) test
= 1;
1023 WCMD_parameter (s
, 1, &command
);
1026 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
1030 /* Process rest of IF statement which is on the same line
1031 Note: This may process all or some of the cmdList (eg a GOTO) */
1032 WCMD_part_execute(cmdList
, command
, NULL
, NULL
, TRUE
, (test
!= negate
));
1035 /****************************************************************************
1038 * Move a file, directory tree or wildcarded set of files.
1041 void WCMD_move (void) {
1046 WCHAR input
[MAX_PATH
];
1047 WCHAR output
[MAX_PATH
];
1049 WCHAR dir
[MAX_PATH
];
1050 WCHAR fname
[MAX_PATH
];
1051 WCHAR ext
[MAX_PATH
];
1053 if (param1
[0] == 0x00) {
1054 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1058 /* If no destination supplied, assume current directory */
1059 if (param2
[0] == 0x00) {
1060 strcpyW(param2
, dotW
);
1063 /* If 2nd parm is directory, then use original filename */
1064 /* Convert partial path to full path */
1065 GetFullPathName (param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1066 GetFullPathName (param2
, sizeof(output
)/sizeof(WCHAR
), output
, NULL
);
1067 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1068 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
1070 /* Split into components */
1071 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1073 hff
= FindFirstFile (input
, &fd
);
1074 while (hff
!= INVALID_HANDLE_VALUE
) {
1075 WCHAR dest
[MAX_PATH
];
1076 WCHAR src
[MAX_PATH
];
1079 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1081 /* Build src & dest name */
1082 strcpyW(src
, drive
);
1085 /* See if dest is an existing directory */
1086 attribs
= GetFileAttributes(output
);
1087 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
1088 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
1089 strcpyW(dest
, output
);
1090 strcatW(dest
, slashW
);
1091 strcatW(dest
, fd
.cFileName
);
1093 strcpyW(dest
, output
);
1096 strcatW(src
, fd
.cFileName
);
1098 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1099 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1101 /* Check if file is read only, otherwise move it */
1102 attribs
= GetFileAttributes(src
);
1103 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1104 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1105 SetLastError(ERROR_ACCESS_DENIED
);
1110 /* If destination exists, prompt unless /Y supplied */
1111 if (GetFileAttributes(dest
) != INVALID_FILE_ATTRIBUTES
) {
1113 WCHAR copycmd
[MAXSTRING
];
1116 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1117 if (strstrW (quals
, parmNoY
))
1119 else if (strstrW (quals
, parmY
))
1122 const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
1123 len
= GetEnvironmentVariable (copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
1124 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))
1125 && ! lstrcmpiW (copycmd
, parmY
));
1128 /* Prompt if overwriting */
1130 WCHAR question
[MAXSTRING
];
1133 strcpyW(yesChar
, WCMD_LoadMessage(WCMD_YES
));
1135 /* Ask for confirmation */
1136 wsprintf(question
, WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
1137 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1139 /* So delete the destination prior to the move */
1141 if (!DeleteFile (dest
)) {
1142 WCMD_print_error ();
1151 status
= MoveFile (src
, dest
);
1153 status
= 1; /* Anything other than 0 to prevent error msg below */
1158 WCMD_print_error ();
1162 /* Step on to next match */
1163 if (FindNextFile(hff
, &fd
) == 0) {
1165 hff
= INVALID_HANDLE_VALUE
;
1171 /****************************************************************************
1174 * Wait for keyboard input.
1177 void WCMD_pause (void) {
1182 WCMD_output (anykey
);
1183 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1184 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1187 /****************************************************************************
1190 * Delete a directory.
1193 void WCMD_remove_dir (WCHAR
*command
) {
1196 int argsProcessed
= 0;
1197 WCHAR
*argN
= command
;
1198 static const WCHAR parmS
[] = {'/','S','\0'};
1199 static const WCHAR parmQ
[] = {'/','Q','\0'};
1201 /* Loop through all args */
1203 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
1204 if (argN
&& argN
[0] != '/') {
1205 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
1206 wine_dbgstr_w(quals
));
1209 /* If subdirectory search not supplied, just try to remove
1210 and report error if it fails (eg if it contains a file) */
1211 if (strstrW (quals
, parmS
) == NULL
) {
1212 if (!RemoveDirectory (thisArg
)) WCMD_print_error ();
1214 /* Otherwise use ShFileOp to recursively remove a directory */
1217 SHFILEOPSTRUCT lpDir
;
1220 if (strstrW (quals
, parmQ
) == NULL
) {
1222 WCHAR question
[MAXSTRING
];
1223 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1225 /* Ask for confirmation */
1226 wsprintf(question
, fmt
, thisArg
);
1227 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
1229 /* Abort if answer is 'N' */
1236 lpDir
.pFrom
= thisArg
;
1237 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
1238 lpDir
.wFunc
= FO_DELETE
;
1239 if (SHFileOperation(&lpDir
)) WCMD_print_error ();
1244 /* Handle no valid args */
1245 if (argsProcessed
== 0) {
1246 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1252 /****************************************************************************
1258 void WCMD_rename (void) {
1263 WCHAR input
[MAX_PATH
];
1264 WCHAR
*dotDst
= NULL
;
1266 WCHAR dir
[MAX_PATH
];
1267 WCHAR fname
[MAX_PATH
];
1268 WCHAR ext
[MAX_PATH
];
1273 /* Must be at least two args */
1274 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
1275 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1280 /* Destination cannot contain a drive letter or directory separator */
1281 if ((strchrW(param1
,':') != NULL
) || (strchrW(param1
,'\\') != NULL
)) {
1282 SetLastError(ERROR_INVALID_PARAMETER
);
1288 /* Convert partial path to full path */
1289 GetFullPathName (param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
1290 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
1291 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
1292 dotDst
= strchrW(param2
, '.');
1294 /* Split into components */
1295 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
1297 hff
= FindFirstFile (input
, &fd
);
1298 while (hff
!= INVALID_HANDLE_VALUE
) {
1299 WCHAR dest
[MAX_PATH
];
1300 WCHAR src
[MAX_PATH
];
1301 WCHAR
*dotSrc
= NULL
;
1304 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
1306 /* FIXME: If dest name or extension is *, replace with filename/ext
1307 part otherwise use supplied name. This supports:
1309 ren jim.* fred.* etc
1310 However, windows has a more complex algorithum supporting eg
1311 ?'s and *'s mid name */
1312 dotSrc
= strchrW(fd
.cFileName
, '.');
1314 /* Build src & dest name */
1315 strcpyW(src
, drive
);
1318 dirLen
= strlenW(src
);
1319 strcatW(src
, fd
.cFileName
);
1322 if (param2
[0] == '*') {
1323 strcatW(dest
, fd
.cFileName
);
1324 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
1326 strcatW(dest
, param2
);
1327 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
1330 /* Build Extension */
1331 if (dotDst
&& (*(dotDst
+1)=='*')) {
1332 if (dotSrc
) strcatW(dest
, dotSrc
);
1333 } else if (dotDst
) {
1334 if (dotDst
) strcatW(dest
, dotDst
);
1337 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
1338 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
1340 /* Check if file is read only, otherwise move it */
1341 attribs
= GetFileAttributes(src
);
1342 if ((attribs
!= INVALID_FILE_ATTRIBUTES
) &&
1343 (attribs
& FILE_ATTRIBUTE_READONLY
)) {
1344 SetLastError(ERROR_ACCESS_DENIED
);
1347 status
= MoveFile (src
, dest
);
1351 WCMD_print_error ();
1355 /* Step on to next match */
1356 if (FindNextFile(hff
, &fd
) == 0) {
1358 hff
= INVALID_HANDLE_VALUE
;
1364 /*****************************************************************************
1367 * Make a copy of the environment.
1369 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
1379 len
+= (strlenW(&env
[len
]) + 1);
1381 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
1384 WINE_ERR("out of memory\n");
1387 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
1393 /*****************************************************************************
1396 * setlocal pushes the environment onto a stack
1397 * Save the environment as unicode so we don't screw anything up.
1399 void WCMD_setlocal (const WCHAR
*s
) {
1401 struct env_stack
*env_copy
;
1402 WCHAR cwd
[MAX_PATH
];
1404 /* DISABLEEXTENSIONS ignored */
1406 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
1409 WINE_ERR ("out of memory\n");
1413 env
= GetEnvironmentStringsW ();
1415 env_copy
->strings
= WCMD_dupenv (env
);
1416 if (env_copy
->strings
)
1418 env_copy
->next
= saved_environment
;
1419 saved_environment
= env_copy
;
1421 /* Save the current drive letter */
1422 GetCurrentDirectory (MAX_PATH
, cwd
);
1423 env_copy
->u
.cwd
= cwd
[0];
1426 LocalFree (env_copy
);
1428 FreeEnvironmentStringsW (env
);
1432 /*****************************************************************************
1435 * endlocal pops the environment off a stack
1436 * Note: When searching for '=', search from WCHAR position 1, to handle
1437 * special internal environment variables =C:, =D: etc
1439 void WCMD_endlocal (void) {
1440 WCHAR
*env
, *old
, *p
;
1441 struct env_stack
*temp
;
1444 if (!saved_environment
)
1447 /* pop the old environment from the stack */
1448 temp
= saved_environment
;
1449 saved_environment
= temp
->next
;
1451 /* delete the current environment, totally */
1452 env
= GetEnvironmentStringsW ();
1453 old
= WCMD_dupenv (GetEnvironmentStringsW ());
1456 n
= strlenW(&old
[len
]) + 1;
1457 p
= strchrW(&old
[len
] + 1, '=');
1461 SetEnvironmentVariableW (&old
[len
], NULL
);
1466 FreeEnvironmentStringsW (env
);
1468 /* restore old environment */
1469 env
= temp
->strings
;
1472 n
= strlenW(&env
[len
]) + 1;
1473 p
= strchrW(&env
[len
] + 1, '=');
1477 SetEnvironmentVariableW (&env
[len
], p
);
1482 /* Restore current drive letter */
1483 if (IsCharAlpha(temp
->u
.cwd
)) {
1485 WCHAR cwd
[MAX_PATH
];
1486 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
1488 wsprintf(envvar
, fmt
, temp
->u
.cwd
);
1489 if (GetEnvironmentVariable(envvar
, cwd
, MAX_PATH
)) {
1490 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
1491 SetCurrentDirectory(cwd
);
1499 /*****************************************************************************
1500 * WCMD_setshow_attrib
1502 * Display and optionally sets DOS attributes on a file or directory
1504 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1505 * As a result only the Readonly flag is correctly reported, the Archive bit
1506 * is always set and the rest are not implemented. We do the Right Thing anyway.
1508 * FIXME: No SET functionality.
1512 void WCMD_setshow_attrib (void) {
1517 WCHAR flags
[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1519 if (param1
[0] == '-') {
1520 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1524 if (strlenW(param1
) == 0) {
1525 static const WCHAR slashStarW
[] = {'\\','*','\0'};
1527 GetCurrentDirectory (sizeof(param1
)/sizeof(WCHAR
), param1
);
1528 strcatW (param1
, slashStarW
);
1531 hff
= FindFirstFile (param1
, &fd
);
1532 if (hff
== INVALID_HANDLE_VALUE
) {
1533 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND
), param1
);
1537 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1538 static const WCHAR fmt
[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1539 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
) {
1542 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
) {
1545 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
) {
1548 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
) {
1551 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_TEMPORARY
) {
1554 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
) {
1557 WCMD_output (fmt
, flags
, fd
.cFileName
);
1558 for (count
=0; count
< 8; count
++) flags
[count
] = ' ';
1560 } while (FindNextFile(hff
, &fd
) != 0);
1565 /*****************************************************************************
1566 * WCMD_setshow_default
1568 * Set/Show the current default directory
1571 void WCMD_setshow_default (WCHAR
*command
) {
1579 static const WCHAR parmD
[] = {'/','D','\0'};
1581 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command
));
1583 /* Skip /D and trailing whitespace if on the front of the command line */
1584 if (CompareString (LOCALE_USER_DEFAULT
,
1585 NORM_IGNORECASE
| SORT_STRINGSORT
,
1586 command
, 2, parmD
, -1) == 2) {
1588 while (*command
&& *command
==' ') command
++;
1591 GetCurrentDirectory (sizeof(cwd
)/sizeof(WCHAR
), cwd
);
1592 if (strlenW(command
) == 0) {
1593 strcatW (cwd
, newline
);
1597 /* Remove any double quotes, which may be in the
1598 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1601 if (*command
!= '"') *pos
++ = *command
;
1606 /* Search for approprate directory */
1607 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
1608 hff
= FindFirstFile (string
, &fd
);
1609 while (hff
!= INVALID_HANDLE_VALUE
) {
1610 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
1611 WCHAR fpath
[MAX_PATH
];
1613 WCHAR dir
[MAX_PATH
];
1614 WCHAR fname
[MAX_PATH
];
1615 WCHAR ext
[MAX_PATH
];
1616 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
1618 /* Convert path into actual directory spec */
1619 GetFullPathName (string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
1620 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
1623 wsprintf(string
, fmt
, drive
, dir
, fd
.cFileName
);
1626 hff
= INVALID_HANDLE_VALUE
;
1630 /* Step on to next match */
1631 if (FindNextFile(hff
, &fd
) == 0) {
1633 hff
= INVALID_HANDLE_VALUE
;
1638 /* Change to that directory */
1639 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
1641 status
= SetCurrentDirectory (string
);
1644 WCMD_print_error ();
1648 /* Restore old directory if drive letter would change, and
1649 CD x:\directory /D (or pushd c:\directory) not supplied */
1650 if ((strstrW(quals
, parmD
) == NULL
) &&
1651 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
1652 SetCurrentDirectory(cwd
);
1656 /* Set special =C: type environment variable, for drive letter of
1657 change of directory, even if path was restored due to missing
1658 /D (allows changing drive letter when not resident on that
1660 if ((string
[1] == ':') && IsCharAlpha (string
[0])) {
1662 strcpyW(env
, equalW
);
1663 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
1665 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
1666 SetEnvironmentVariable(env
, string
);
1673 /****************************************************************************
1676 * Set/Show the system date
1677 * FIXME: Can't change date yet
1680 void WCMD_setshow_date (void) {
1682 WCHAR curdate
[64], buffer
[64];
1684 static const WCHAR parmT
[] = {'/','T','\0'};
1686 if (strlenW(param1
) == 0) {
1687 if (GetDateFormat (LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
1688 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
1689 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
1690 if (strstrW (quals
, parmT
) == NULL
) {
1691 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
1692 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
),
1693 buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
1695 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1699 else WCMD_print_error ();
1702 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1706 /****************************************************************************
1709 static int WCMD_compare( const void *a
, const void *b
)
1712 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
1713 r
= CompareString( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1714 *str_a
, -1, *str_b
, -1 );
1715 if( r
== CSTR_LESS_THAN
) return -1;
1716 if( r
== CSTR_GREATER_THAN
) return 1;
1720 /****************************************************************************
1721 * WCMD_setshow_sortenv
1723 * sort variables into order for display
1724 * Optionally only display those who start with a stub
1725 * returns the count displayed
1727 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
1729 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
1732 if (stub
) stublen
= strlenW(stub
);
1734 /* count the number of strings, and the total length */
1736 len
+= (strlenW(&s
[len
]) + 1);
1740 /* add the strings to an array */
1741 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
1745 for( i
=1; i
<count
; i
++ )
1746 str
[i
] = str
[i
-1] + strlenW(str
[i
-1]) + 1;
1748 /* sort the array */
1749 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
1752 for( i
=0; i
<count
; i
++ ) {
1753 if (!stub
|| CompareString (LOCALE_USER_DEFAULT
,
1754 NORM_IGNORECASE
| SORT_STRINGSORT
,
1755 str
[i
], stublen
, stub
, -1) == 2) {
1756 /* Don't display special internal variables */
1757 if (str
[i
][0] != '=') {
1758 WCMD_output_asis(str
[i
]);
1759 WCMD_output_asis(newline
);
1766 return displayedcount
;
1769 /****************************************************************************
1772 * Set/Show the environment variables
1775 void WCMD_setshow_env (WCHAR
*s
) {
1780 static const WCHAR parmP
[] = {'/','P','\0'};
1783 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
1784 env
= GetEnvironmentStrings ();
1785 WCMD_setshow_sortenv( env
, NULL
);
1789 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1790 if (CompareString (LOCALE_USER_DEFAULT
,
1791 NORM_IGNORECASE
| SORT_STRINGSORT
,
1792 s
, 2, parmP
, -1) == 2) {
1793 WCHAR string
[MAXSTRING
];
1797 while (*s
&& *s
==' ') s
++;
1799 /* If no parameter, or no '=' sign, return an error */
1800 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
1801 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1805 /* Output the prompt */
1807 if (strlenW(p
) != 0) WCMD_output(p
);
1809 /* Read the reply */
1810 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
1811 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
1813 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
1814 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
1815 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
1816 wine_dbgstr_w(string
));
1817 status
= SetEnvironmentVariable (s
, string
);
1822 p
= strchrW (s
, '=');
1824 env
= GetEnvironmentStrings ();
1825 if (WCMD_setshow_sortenv( env
, s
) == 0) {
1826 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
1833 if (strlenW(p
) == 0) p
= NULL
;
1834 status
= SetEnvironmentVariable (s
, p
);
1835 gle
= GetLastError();
1836 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
1838 } else if ((!status
)) WCMD_print_error();
1842 /****************************************************************************
1845 * Set/Show the path environment variable
1848 void WCMD_setshow_path (WCHAR
*command
) {
1852 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
1853 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
1855 if (strlenW(param1
) == 0) {
1856 status
= GetEnvironmentVariable (pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
1858 WCMD_output_asis ( pathEqW
);
1859 WCMD_output_asis ( string
);
1860 WCMD_output_asis ( newline
);
1863 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH
));
1867 if (*command
== '=') command
++; /* Skip leading '=' */
1868 status
= SetEnvironmentVariable (pathW
, command
);
1869 if (!status
) WCMD_print_error();
1873 /****************************************************************************
1874 * WCMD_setshow_prompt
1876 * Set or show the command prompt.
1879 void WCMD_setshow_prompt (void) {
1882 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
1884 if (strlenW(param1
) == 0) {
1885 SetEnvironmentVariable (promptW
, NULL
);
1889 while ((*s
== '=') || (*s
== ' ')) s
++;
1890 if (strlenW(s
) == 0) {
1891 SetEnvironmentVariable (promptW
, NULL
);
1893 else SetEnvironmentVariable (promptW
, s
);
1897 /****************************************************************************
1900 * Set/Show the system time
1901 * FIXME: Can't change time yet
1904 void WCMD_setshow_time (void) {
1906 WCHAR curtime
[64], buffer
[64];
1909 static const WCHAR parmT
[] = {'/','T','\0'};
1911 if (strlenW(param1
) == 0) {
1913 if (GetTimeFormat (LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
1914 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
1915 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curtime
);
1916 if (strstrW (quals
, parmT
) == NULL
) {
1917 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
1918 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
1919 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
1921 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1925 else WCMD_print_error ();
1928 WCMD_output (WCMD_LoadMessage(WCMD_NYI
));
1932 /****************************************************************************
1935 * Shift batch parameters.
1936 * Optional /n says where to start shifting (n=0-8)
1939 void WCMD_shift (WCHAR
*command
) {
1942 if (context
!= NULL
) {
1943 WCHAR
*pos
= strchrW(command
, '/');
1948 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
1949 start
= (*(pos
+1) - '0');
1951 SetLastError(ERROR_INVALID_PARAMETER
);
1956 WINE_TRACE("Shifting variables, starting at %d\n", start
);
1957 for (i
=start
;i
<=8;i
++) {
1958 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
1960 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
1965 /****************************************************************************
1968 * Set the console title
1970 void WCMD_title (WCHAR
*command
) {
1971 SetConsoleTitle(command
);
1974 /****************************************************************************
1977 * Copy a file to standard output.
1980 void WCMD_type (WCHAR
*command
) {
1983 WCHAR
*argN
= command
;
1984 BOOL writeHeaders
= FALSE
;
1986 if (param1
[0] == 0x00) {
1987 WCMD_output (WCMD_LoadMessage(WCMD_NOARG
));
1991 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
1993 /* Loop through all args */
1996 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
2004 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2005 h
= CreateFile (thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2006 FILE_ATTRIBUTE_NORMAL
, NULL
);
2007 if (h
== INVALID_HANDLE_VALUE
) {
2008 WCMD_print_error ();
2009 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2013 static const WCHAR fmt
[] = {'\n','%','s','\n','\n','\0'};
2014 WCMD_output(fmt
, thisArg
);
2016 while (WCMD_ReadFile (h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
)) {
2017 if (count
== 0) break; /* ReadFile reports success on EOF! */
2019 WCMD_output_asis (buffer
);
2026 /****************************************************************************
2029 * Output either a file or stdin to screen in pages
2032 void WCMD_more (WCHAR
*command
) {
2035 WCHAR
*argN
= command
;
2036 BOOL useinput
= FALSE
;
2038 WCHAR moreStrPage
[100];
2041 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
2042 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
2043 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2044 ')',' ','-','-','\n','\0'};
2045 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
2047 /* Prefix the NLS more with '-- ', then load the text */
2049 strcpyW(moreStr
, moreStart
);
2050 LoadString (hinst
, WCMD_MORESTR
, &moreStr
[3],
2051 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
2053 if (param1
[0] == 0x00) {
2055 /* Wine implements pipes via temporary files, and hence stdin is
2056 effectively reading from the file. This means the prompts for
2057 more are satistied by the next line from the input (file). To
2058 avoid this, ensure stdin is to the console */
2059 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
2060 HANDLE hConIn
= CreateFile(conInW
, GENERIC_READ
| GENERIC_WRITE
,
2061 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2062 FILE_ATTRIBUTE_NORMAL
, 0);
2063 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
2065 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2066 once you get in this bit unless due to a pipe, its going to end badly... */
2068 wsprintf(moreStrPage
, moreFmt
, moreStr
);
2070 WCMD_enter_paged_mode(moreStrPage
);
2071 while (WCMD_ReadFile (hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2072 if (count
== 0) break; /* ReadFile reports success on EOF! */
2074 WCMD_output_asis (buffer
);
2076 WCMD_leave_paged_mode();
2078 /* Restore stdin to what it was */
2079 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
2080 CloseHandle(hConIn
);
2084 BOOL needsPause
= FALSE
;
2086 /* Loop through all args */
2087 WCMD_enter_paged_mode(moreStrPage
);
2090 WCHAR
*thisArg
= WCMD_parameter (command
, argno
++, &argN
);
2098 wsprintf(moreStrPage
, moreFmt2
, moreStr
, 100);
2099 WCMD_leave_paged_mode();
2100 WCMD_output_asis(moreStrPage
);
2101 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), buffer
,
2102 sizeof(buffer
)/sizeof(WCHAR
), &count
, NULL
);
2103 WCMD_enter_paged_mode(moreStrPage
);
2107 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
2108 h
= CreateFile (thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
2109 FILE_ATTRIBUTE_NORMAL
, NULL
);
2110 if (h
== INVALID_HANDLE_VALUE
) {
2111 WCMD_print_error ();
2112 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
2116 ULONG64 fileLen
= 0;
2117 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
2119 /* Get the file size */
2120 GetFileAttributesEx(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
2121 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
2124 while (WCMD_ReadFile (h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
, NULL
)) {
2125 if (count
== 0) break; /* ReadFile reports success on EOF! */
2129 /* Update % count (would be used in WCMD_output_asis as prompt) */
2130 wsprintf(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
2132 WCMD_output_asis (buffer
);
2138 WCMD_leave_paged_mode();
2142 /****************************************************************************
2145 * Display verify flag.
2146 * FIXME: We don't actually do anything with the verify flag other than toggle
2150 void WCMD_verify (WCHAR
*command
) {
2154 count
= strlenW(command
);
2156 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
2157 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
2160 if (lstrcmpiW(command
, onW
) == 0) {
2164 else if (lstrcmpiW(command
, offW
) == 0) {
2168 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR
));
2171 /****************************************************************************
2174 * Display version info.
2177 void WCMD_version (void) {
2179 WCMD_output (version_string
);
2183 /****************************************************************************
2186 * Display volume info and/or set volume label. Returns 0 if error.
2189 int WCMD_volume (int mode
, WCHAR
*path
) {
2191 DWORD count
, serial
;
2192 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
2195 if (strlenW(path
) == 0) {
2196 status
= GetCurrentDirectory (sizeof(curdir
)/sizeof(WCHAR
), curdir
);
2198 WCMD_print_error ();
2201 status
= GetVolumeInformation (NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
2202 &serial
, NULL
, NULL
, NULL
, 0);
2205 static const WCHAR fmt
[] = {'%','s','\\','\0'};
2206 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
2207 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2210 wsprintf (curdir
, fmt
, path
);
2211 status
= GetVolumeInformation (curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
2216 WCMD_print_error ();
2219 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL
),
2220 curdir
[0], label
, HIWORD(serial
), LOWORD(serial
));
2222 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
2223 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), string
,
2224 sizeof(string
)/sizeof(WCHAR
), &count
, NULL
);
2226 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
2227 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
2229 if (strlenW(path
) != 0) {
2230 if (!SetVolumeLabel (curdir
, string
)) WCMD_print_error ();
2233 if (!SetVolumeLabel (NULL
, string
)) WCMD_print_error ();
2239 /**************************************************************************
2242 * Exit either the process, or just this batch program
2246 void WCMD_exit (CMD_LIST
**cmdList
) {
2248 static const WCHAR parmB
[] = {'/','B','\0'};
2249 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
2251 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
2253 context
-> skip_rest
= TRUE
;
2260 /**************************************************************************
2263 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2266 * Returns True if Y (or A) answer is selected
2267 * If optionAll contains a pointer, ALL is allowed, and if answered
2271 BOOL
WCMD_ask_confirm (WCHAR
*message
, BOOL showSureText
, BOOL
*optionAll
) {
2273 WCHAR msgbuffer
[MAXSTRING
];
2274 WCHAR Ybuffer
[MAXSTRING
];
2275 WCHAR Nbuffer
[MAXSTRING
];
2276 WCHAR Abuffer
[MAXSTRING
];
2277 WCHAR answer
[MAX_PATH
] = {'\0'};
2280 /* Load the translated 'Are you sure', plus valid answers */
2281 LoadString (hinst
, WCMD_CONFIRM
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2282 LoadString (hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
)/sizeof(WCHAR
));
2283 LoadString (hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
)/sizeof(WCHAR
));
2284 LoadString (hinst
, WCMD_ALL
, Abuffer
, sizeof(Abuffer
)/sizeof(WCHAR
));
2286 /* Loop waiting on a Y or N */
2287 while (answer
[0] != Ybuffer
[0] && answer
[0] != Nbuffer
[0]) {
2288 static const WCHAR startBkt
[] = {' ','(','\0'};
2289 static const WCHAR endBkt
[] = {')','?','\0'};
2291 WCMD_output_asis (message
);
2293 WCMD_output_asis (msgbuffer
);
2295 WCMD_output_asis (startBkt
);
2296 WCMD_output_asis (Ybuffer
);
2297 WCMD_output_asis (fslashW
);
2298 WCMD_output_asis (Nbuffer
);
2300 WCMD_output_asis (fslashW
);
2301 WCMD_output_asis (Abuffer
);
2303 WCMD_output_asis (endBkt
);
2304 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE
), answer
,
2305 sizeof(answer
)/sizeof(WCHAR
), &count
, NULL
);
2306 answer
[0] = toupper(answer
[0]);
2309 /* Return the answer */
2310 return ((answer
[0] == Ybuffer
[0]) ||
2311 (optionAll
&& (answer
[0] == Abuffer
[0])));
2314 /*****************************************************************************
2317 * Lists or sets file associations (assoc = TRUE)
2318 * Lists or sets file types (assoc = FALSE)
2320 void WCMD_assoc (WCHAR
*command
, BOOL assoc
) {
2323 DWORD accessOptions
= KEY_READ
;
2325 LONG rc
= ERROR_SUCCESS
;
2326 WCHAR keyValue
[MAXSTRING
];
2327 DWORD valueLen
= MAXSTRING
;
2329 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
2330 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2332 /* See if parameter includes '=' */
2334 newValue
= strchrW(command
, '=');
2335 if (newValue
) accessOptions
|= KEY_WRITE
;
2337 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2338 if (RegOpenKeyEx(HKEY_CLASSES_ROOT
, nullW
, 0,
2339 accessOptions
, &key
) != ERROR_SUCCESS
) {
2340 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2344 /* If no parameters then list all associations */
2345 if (*command
== 0x00) {
2348 /* Enumerate all the keys */
2349 while (rc
!= ERROR_NO_MORE_ITEMS
) {
2350 WCHAR keyName
[MAXSTRING
];
2353 /* Find the next value */
2354 nameLen
= MAXSTRING
;
2355 rc
= RegEnumKeyEx(key
, index
++,
2357 NULL
, NULL
, NULL
, NULL
);
2359 if (rc
== ERROR_SUCCESS
) {
2361 /* Only interested in extension ones if assoc, or others
2363 if ((keyName
[0] == '.' && assoc
) ||
2364 (!(keyName
[0] == '.') && (!assoc
)))
2366 WCHAR subkey
[MAXSTRING
];
2367 strcpyW(subkey
, keyName
);
2368 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2370 if (RegOpenKeyEx(key
, subkey
, 0,
2371 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2373 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
2374 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
2375 (LPBYTE
)keyValue
, &valueLen
);
2376 WCMD_output_asis(keyName
);
2377 WCMD_output_asis(equalW
);
2378 /* If no default value found, leave line empty after '=' */
2379 if (rc
== ERROR_SUCCESS
) {
2380 WCMD_output_asis(keyValue
);
2382 WCMD_output_asis(newline
);
2387 RegCloseKey(readKey
);
2391 /* Parameter supplied - if no '=' on command line, its a query */
2392 if (newValue
== NULL
) {
2394 WCHAR subkey
[MAXSTRING
];
2396 /* Query terminates the parameter at the first space */
2397 strcpyW(keyValue
, command
);
2398 space
= strchrW(keyValue
, ' ');
2399 if (space
) *space
=0x00;
2401 /* Set up key name */
2402 strcpyW(subkey
, keyValue
);
2403 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2405 if (RegOpenKeyEx(key
, subkey
, 0,
2406 accessOptions
, &readKey
) == ERROR_SUCCESS
) {
2408 rc
= RegQueryValueEx(readKey
, NULL
, NULL
, NULL
,
2409 (LPBYTE
)keyValue
, &valueLen
);
2410 WCMD_output_asis(command
);
2411 WCMD_output_asis(equalW
);
2412 /* If no default value found, leave line empty after '=' */
2413 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
2414 WCMD_output_asis(newline
);
2415 RegCloseKey(readKey
);
2418 WCHAR msgbuffer
[MAXSTRING
];
2419 WCHAR outbuffer
[MAXSTRING
];
2421 /* Load the translated 'File association not found' */
2423 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2425 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
2427 wsprintf(outbuffer
, msgbuffer
, keyValue
);
2428 WCMD_output_asis(outbuffer
);
2432 /* Not a query - its a set or clear of a value */
2435 WCHAR subkey
[MAXSTRING
];
2437 /* Get pointer to new value */
2441 /* Set up key name */
2442 strcpyW(subkey
, command
);
2443 if (!assoc
) strcatW(subkey
, shOpCmdW
);
2445 /* If nothing after '=' then clear value - only valid for ASSOC */
2446 if (*newValue
== 0x00) {
2448 if (assoc
) rc
= RegDeleteKey(key
, command
);
2449 if (assoc
&& rc
== ERROR_SUCCESS
) {
2450 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command
));
2452 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
2457 WCHAR msgbuffer
[MAXSTRING
];
2458 WCHAR outbuffer
[MAXSTRING
];
2460 /* Load the translated 'File association not found' */
2462 LoadString (hinst
, WCMD_NOASSOC
, msgbuffer
,
2463 sizeof(msgbuffer
)/sizeof(WCHAR
));
2465 LoadString (hinst
, WCMD_NOFTYPE
, msgbuffer
,
2466 sizeof(msgbuffer
)/sizeof(WCHAR
));
2468 wsprintf(outbuffer
, msgbuffer
, keyValue
);
2469 WCMD_output_asis(outbuffer
);
2473 /* It really is a set value = contents */
2475 rc
= RegCreateKeyEx(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
2476 accessOptions
, NULL
, &readKey
, NULL
);
2477 if (rc
== ERROR_SUCCESS
) {
2478 rc
= RegSetValueEx(readKey
, NULL
, 0, REG_SZ
,
2479 (LPBYTE
)newValue
, strlenW(newValue
));
2480 RegCloseKey(readKey
);
2483 if (rc
!= ERROR_SUCCESS
) {
2487 WCMD_output_asis(command
);
2488 WCMD_output_asis(equalW
);
2489 WCMD_output_asis(newValue
);
2490 WCMD_output_asis(newline
);
2500 /****************************************************************************
2503 * Clear the terminal screen.
2506 void WCMD_color (void) {
2508 /* Emulate by filling the screen from the top left to bottom right with
2509 spaces, then moving the cursor to the top left afterwards */
2510 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
2511 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
2513 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
2514 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR
));
2518 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
2524 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
2529 /* Convert the color hex digits */
2530 if (param1
[0] == 0x00) {
2531 color
= defaultColor
;
2533 color
= strtoulW(param1
, NULL
, 16);
2536 /* Fail if fg == bg color */
2537 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
2542 /* Set the current screen contents and ensure all future writes
2543 remain this color */
2544 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
2545 SetConsoleTextAttribute(hStdOut
, color
);