hidclass.sys: Use IoRegisterDeviceInterface.
[wine.git] / programs / cmd / batch.c
blob5b05d8811b28f5ad183dc69926036a9a79c2bc8e
1 /*
2 * CMD - Wine-compatible command line interface - batch interface.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 #include "wcmd.h"
23 #include "wine/debug.h"
25 extern struct env_stack *saved_environment;
27 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
29 /****************************************************************************
30 * WCMD_batch
32 * Open and execute a batch file.
33 * On entry *command includes the complete command line beginning with the name
34 * of the batch file (if a CALL command was entered the CALL has been removed).
35 * *file is the name of the file, which might not exist and may not have the
36 * .BAT suffix on. Called is 1 for a CALL, 0 otherwise.
38 * We need to handle recursion correctly, since one batch program might call another.
39 * So parameters for this batch file are held in a BATCH_CONTEXT structure.
41 * To support call within the same batch program, another input parameter is
42 * a label to goto once opened.
45 void WCMD_batch (WCHAR *file, WCHAR *command, BOOL called, WCHAR *startLabel, HANDLE pgmHandle)
47 HANDLE h = INVALID_HANDLE_VALUE;
48 BATCH_CONTEXT *prev_context;
50 if (startLabel == NULL) {
51 h = CreateFileW (file, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
52 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
53 if (h == INVALID_HANDLE_VALUE) {
54 SetLastError (ERROR_FILE_NOT_FOUND);
55 WCMD_print_error ();
56 return;
58 } else {
59 DuplicateHandle(GetCurrentProcess(), pgmHandle,
60 GetCurrentProcess(), &h,
61 0, FALSE, DUPLICATE_SAME_ACCESS);
65 * Create a context structure for this batch file.
68 prev_context = context;
69 context = LocalAlloc (LMEM_FIXED, sizeof (BATCH_CONTEXT));
70 context -> h = h;
71 context->batchfileW = heap_strdupW(file);
72 context -> command = command;
73 memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
74 context -> prev_context = prev_context;
75 context -> skip_rest = FALSE;
77 /* If processing a call :label, 'goto' the label in question */
78 if (startLabel) {
79 strcpyW(param1, startLabel);
80 WCMD_goto(NULL);
84 * Work through the file line by line. Specific batch commands are processed here,
85 * the rest are handled by the main command processor.
88 while (context -> skip_rest == FALSE) {
89 CMD_LIST *toExecute = NULL; /* Commands left to be executed */
90 if (!WCMD_ReadAndParseLine(NULL, &toExecute, h))
91 break;
92 /* Note: although this batch program itself may be called, we are not retrying
93 the command as a result of a call failing to find a program, hence the
94 retryCall parameter below is FALSE */
95 WCMD_process_commands(toExecute, FALSE, FALSE);
96 WCMD_free_commands(toExecute);
97 toExecute = NULL;
99 CloseHandle (h);
102 * If there are outstanding setlocal's to the current context, unwind them.
104 while (saved_environment && saved_environment->batchhandle == context->h) {
105 WCMD_endlocal();
109 * If invoked by a CALL, we return to the context of our caller. Otherwise return
110 * to the caller's caller.
113 heap_free(context->batchfileW);
114 LocalFree (context);
115 if ((prev_context != NULL) && (!called)) {
116 WINE_TRACE("Batch completed, but was not 'called' so skipping outer batch too\n");
117 prev_context -> skip_rest = TRUE;
118 context = prev_context;
120 context = prev_context;
123 /*******************************************************************
124 * WCMD_parameter_with_delims
126 * Extracts a delimited parameter from an input string, providing
127 * the delimiters characters to use
129 * PARAMS
130 * s [I] input string, non NULL
131 * n [I] # of the parameter to return, counted from 0
132 * start [O] Optional. Pointer to the first char of param n in s
133 * raw [I] TRUE to return the parameter in raw format (quotes maintained)
134 * FALSE to return the parameter with quotes stripped (including internal ones)
135 * wholecmdline [I] TRUE to indicate this routine is being used to parse the
136 * command line, and special logic for arg0->1 transition
137 * needs to be applied.
138 * delims[I] The delimiter characters to use
140 * RETURNS
141 * Success: The nth delimited parameter found in s
142 * if start != NULL, *start points to the start of the param (quotes maintained)
143 * Failure: An empty string if the param is not found.
144 * *start == NULL
146 * NOTES
147 * Return value is stored in static storage (i.e. overwritten after each call).
148 * By default, the parameter is returned with quotes removed, ready for use with
149 * other API calls, e.g. c:\"a b"\c is returned as c:\a b\c. However, some commands
150 * need to preserve the exact syntax (echo, for, etc) hence the raw option.
152 WCHAR *WCMD_parameter_with_delims (WCHAR *s, int n, WCHAR **start,
153 BOOL raw, BOOL wholecmdline, const WCHAR *delims)
155 int curParamNb = 0;
156 static WCHAR param[MAXSTRING];
157 WCHAR *p = s, *begin;
159 if (start != NULL) *start = NULL;
160 param[0] = '\0';
162 while (TRUE) {
164 /* Absorb repeated word delimiters until we get to the next token (or the end!) */
165 while (*p && (strchrW(delims, *p) != NULL))
166 p++;
167 if (*p == '\0') return param;
169 /* If we have reached the token number we want, remember the beginning of it */
170 if (start != NULL && curParamNb == n) *start = p;
172 /* Return the whole word up to the next delimiter, handling quotes in the middle
173 of it, e.g. a"\b c\"d is a single parameter. */
174 begin = p;
176 /* Loop character by character, but just need to special case quotes */
177 while (*p) {
178 /* Once we have found a delimiter, break */
179 if (strchrW(delims, *p) != NULL) break;
181 /* Very odd special case - Seems as if a ( acts as a delimiter which is
182 not swallowed but is effective only when it comes between the program
183 name and the parameters. Need to avoid this triggering when used
184 to walk parameters generally. */
185 if (wholecmdline && curParamNb == 0 && *p=='(') break;
187 /* If we find a quote, copy until we get the end quote */
188 if (*p == '"') {
189 p++;
190 while (*p && *p != '"') p++;
193 /* Now skip the character / quote */
194 if (*p) p++;
197 if (curParamNb == n) {
198 /* Return the parameter in static storage either as-is (raw) or
199 suitable for use with other win32 api calls (quotes stripped) */
200 if (raw) {
201 memcpy(param, begin, (p - begin) * sizeof(WCHAR));
202 param[p-begin] = '\0';
203 } else {
204 int i=0;
205 while (begin < p) {
206 if (*begin != '"') param[i++] = *begin;
207 begin++;
209 param[i] = '\0';
211 return param;
213 curParamNb++;
217 /*******************************************************************
218 * WCMD_parameter
220 * Extracts a delimited parameter from an input string, using a
221 * default set of delimiter characters. For parameters, see the main
222 * function above.
224 WCHAR *WCMD_parameter (WCHAR *s, int n, WCHAR **start, BOOL raw,
225 BOOL wholecmdline)
227 static const WCHAR defaultDelims[] = { ' ', '\t', ',', '=', ';', '\0' };
228 return WCMD_parameter_with_delims (s, n, start, raw, wholecmdline, defaultDelims);
231 /****************************************************************************
232 * WCMD_fgets
234 * Gets one line from a file/console and puts it into buffer buf
235 * Pre: buf has size noChars
236 * 1 <= noChars <= MAXSTRING
237 * Post: buf is filled with at most noChars-1 characters, and gets nul-terminated
238 buf does not include EOL terminator
239 * Returns:
240 * buf on success
241 * NULL on error or EOF
244 WCHAR *WCMD_fgets(WCHAR *buf, DWORD noChars, HANDLE h)
246 DWORD charsRead;
247 BOOL status;
248 DWORD i;
250 /* We can't use the native f* functions because of the filename syntax differences
251 between DOS and Unix. Also need to lose the LF (or CRLF) from the line. */
253 if (!WCMD_is_console_handle(h)) {
254 LARGE_INTEGER filepos;
255 char *bufA;
256 UINT cp;
257 const char *p;
259 cp = GetConsoleCP();
260 bufA = heap_alloc(noChars);
262 /* Save current file position */
263 filepos.QuadPart = 0;
264 SetFilePointerEx(h, filepos, &filepos, FILE_CURRENT);
266 status = ReadFile(h, bufA, noChars, &charsRead, NULL);
267 if (!status || charsRead == 0) {
268 heap_free(bufA);
269 return NULL;
272 /* Find first EOL */
273 for (p = bufA; p < (bufA + charsRead); p = CharNextExA(cp, p, 0)) {
274 if (*p == '\n' || *p == '\r')
275 break;
278 /* Sets file pointer to the start of the next line, if any */
279 filepos.QuadPart += p - bufA + 1 + (*p == '\r' ? 1 : 0);
280 SetFilePointerEx(h, filepos, NULL, FILE_BEGIN);
282 i = MultiByteToWideChar(cp, 0, bufA, p - bufA, buf, noChars);
283 heap_free(bufA);
285 else {
286 status = WCMD_ReadFile(h, buf, noChars, &charsRead);
287 if (!status || charsRead == 0) return NULL;
289 /* Find first EOL */
290 for (i = 0; i < charsRead; i++) {
291 if (buf[i] == '\n' || buf[i] == '\r')
292 break;
296 /* Truncate at EOL (or end of buffer) */
297 if (i == noChars)
298 i--;
300 buf[i] = '\0';
302 return buf;
305 /* WCMD_splitpath - copied from winefile as no obvious way to use it otherwise */
306 void WCMD_splitpath(const WCHAR* path, WCHAR* drv, WCHAR* dir, WCHAR* name, WCHAR* ext)
308 const WCHAR* end; /* end of processed string */
309 const WCHAR* p; /* search pointer */
310 const WCHAR* s; /* copy pointer */
312 /* extract drive name */
313 if (path[0] && path[1]==':') {
314 if (drv) {
315 *drv++ = *path++;
316 *drv++ = *path++;
317 *drv = '\0';
319 } else if (drv)
320 *drv = '\0';
322 end = path + strlenW(path);
324 /* search for begin of file extension */
325 for(p=end; p>path && *--p!='\\' && *p!='/'; )
326 if (*p == '.') {
327 end = p;
328 break;
331 if (ext)
332 for(s=end; (*ext=*s++); )
333 ext++;
335 /* search for end of directory name */
336 for(p=end; p>path; )
337 if (*--p=='\\' || *p=='/') {
338 p++;
339 break;
342 if (name) {
343 for(s=p; s<end; )
344 *name++ = *s++;
346 *name = '\0';
349 if (dir) {
350 for(s=path; s<p; )
351 *dir++ = *s++;
353 *dir = '\0';
357 /****************************************************************************
358 * WCMD_HandleTildaModifiers
360 * Handle the ~ modifiers when expanding %0-9 or (%a-z/A-Z in for command)
361 * %~xxxxxV (V=0-9 or A-Z, a-z)
362 * Where xxxx is any combination of:
363 * ~ - Removes quotes
364 * f - Fully qualified path (assumes current dir if not drive\dir)
365 * d - drive letter
366 * p - path
367 * n - filename
368 * x - file extension
369 * s - path with shortnames
370 * a - attributes
371 * t - date/time
372 * z - size
373 * $ENVVAR: - Searches ENVVAR for (contents of V) and expands to fully
374 * qualified path
376 * To work out the length of the modifier:
378 * Note: In the case of %0-9 knowing the end of the modifier is easy,
379 * but in a for loop, the for end WCHARacter may also be a modifier
380 * eg. for %a in (c:\a.a) do echo XXX
381 * where XXX = %~a (just ~)
382 * %~aa (~ and attributes)
383 * %~aaxa (~, attributes and extension)
384 * BUT %~aax (~ and attributes followed by 'x')
386 * Hence search forwards until find an invalid modifier, and then
387 * backwards until find for variable or 0-9
389 void WCMD_HandleTildaModifiers(WCHAR **start, BOOL atExecute)
392 #define NUMMODIFIERS 11
393 static const WCHAR validmodifiers[NUMMODIFIERS] = {
394 '~', 'f', 'd', 'p', 'n', 'x', 's', 'a', 't', 'z', '$'
397 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
398 WCHAR outputparam[MAX_PATH];
399 WCHAR finaloutput[MAX_PATH];
400 WCHAR fullfilename[MAX_PATH];
401 WCHAR thisoutput[MAX_PATH];
402 WCHAR *filepart = NULL;
403 WCHAR *pos = *start+1;
404 WCHAR *firstModifier = pos;
405 WCHAR *lastModifier = NULL;
406 int modifierLen = 0;
407 BOOL finished = FALSE;
408 int i = 0;
409 BOOL exists = TRUE;
410 BOOL skipFileParsing = FALSE;
411 BOOL doneModifier = FALSE;
413 /* Search forwards until find invalid character modifier */
414 while (!finished) {
416 /* Work on the previous character */
417 if (lastModifier != NULL) {
419 for (i=0; i<NUMMODIFIERS; i++) {
420 if (validmodifiers[i] == *lastModifier) {
422 /* Special case '$' to skip until : found */
423 if (*lastModifier == '$') {
424 while (*pos != ':' && *pos) pos++;
425 if (*pos == 0x00) return; /* Invalid syntax */
426 pos++; /* Skip ':' */
428 break;
432 if (i==NUMMODIFIERS) {
433 finished = TRUE;
437 /* Save this one away */
438 if (!finished) {
439 lastModifier = pos;
440 pos++;
444 while (lastModifier > firstModifier) {
445 WINE_TRACE("Looking backwards for parameter id: %s\n",
446 wine_dbgstr_w(lastModifier));
448 if (!atExecute && context && (*lastModifier >= '0' && *lastModifier <= '9')) {
449 /* Its a valid parameter identifier - OK */
450 break;
452 } else {
453 int foridx = FOR_VAR_IDX(*lastModifier);
454 /* Its a valid parameter identifier - OK */
455 if ((foridx >= 0) && (forloopcontext.variable[foridx] != NULL)) break;
457 /* Its not a valid parameter identifier - step backwards */
458 lastModifier--;
461 if (lastModifier == firstModifier) return; /* Invalid syntax */
463 /* So now, firstModifier points to beginning of modifiers, lastModifier
464 points to the variable just after the modifiers. Process modifiers
465 in a specific order, remembering there could be duplicates */
466 modifierLen = lastModifier - firstModifier;
467 finaloutput[0] = 0x00;
469 /* Extract the parameter to play with
470 Special case param 0 - With %~0 you get the batch label which was called
471 whereas if you start applying other modifiers to it, you get the filename
472 the batch label is in */
473 if (*lastModifier == '0' && modifierLen > 1) {
474 strcpyW(outputparam, context->batchfileW);
475 } else if ((*lastModifier >= '0' && *lastModifier <= '9')) {
476 strcpyW(outputparam,
477 WCMD_parameter (context -> command,
478 *lastModifier-'0' + context -> shift_count[*lastModifier-'0'],
479 NULL, FALSE, TRUE));
480 } else {
481 int foridx = FOR_VAR_IDX(*lastModifier);
482 strcpyW(outputparam, forloopcontext.variable[foridx]);
485 /* 1. Handle '~' : Strip surrounding quotes */
486 if (outputparam[0]=='"' &&
487 memchrW(firstModifier, '~', modifierLen) != NULL) {
488 int len = strlenW(outputparam);
489 if (outputparam[len-1] == '"') {
490 outputparam[len-1]=0x00;
491 len = len - 1;
493 memmove(outputparam, &outputparam[1], (len * sizeof(WCHAR))-1);
496 /* 2. Handle the special case of a $ */
497 if (memchrW(firstModifier, '$', modifierLen) != NULL) {
498 /* Special Case: Search envar specified in $[envvar] for outputparam
499 Note both $ and : are guaranteed otherwise check above would fail */
500 WCHAR *begin = strchrW(firstModifier, '$') + 1;
501 WCHAR *end = strchrW(firstModifier, ':');
502 WCHAR env[MAX_PATH];
503 DWORD size;
505 /* Extract the env var */
506 memcpy(env, begin, (end-begin) * sizeof(WCHAR));
507 env[(end-begin)] = 0x00;
509 size = GetEnvironmentVariableW(env, NULL, 0);
510 if (size > 0) {
511 WCHAR *fullpath = heap_alloc(size * sizeof(WCHAR));
512 if (!fullpath || (GetEnvironmentVariableW(env, fullpath, size) == 0) ||
513 (SearchPathW(fullpath, outputparam, NULL, MAX_PATH, outputparam, NULL) == 0))
514 size = 0;
515 heap_free(fullpath);
518 if (!size) {
519 /* If env var not found, return empty string */
520 finaloutput[0] = 0x00;
521 outputparam[0] = 0x00;
522 skipFileParsing = TRUE;
526 /* After this, we need full information on the file,
527 which is valid not to exist. */
528 if (!skipFileParsing) {
529 if (GetFullPathNameW(outputparam, MAX_PATH, fullfilename, &filepart) == 0) {
530 exists = FALSE;
531 fullfilename[0] = 0x00;
532 } else {
533 exists = GetFileAttributesExW(fullfilename, GetFileExInfoStandard,
534 &fileInfo);
537 /* 2. Handle 'a' : Output attributes (File doesn't have to exist) */
538 if (memchrW(firstModifier, 'a', modifierLen) != NULL) {
540 WCHAR defaults[] = {'-','-','-','-','-','-','-','-','-','\0'};
541 doneModifier = TRUE;
543 if (exists) {
544 strcpyW(thisoutput, defaults);
545 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
546 thisoutput[0]='d';
547 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
548 thisoutput[1]='r';
549 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
550 thisoutput[2]='a';
551 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
552 thisoutput[3]='h';
553 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
554 thisoutput[4]='s';
555 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
556 thisoutput[5]='c';
557 /* FIXME: What are 6 and 7? */
558 if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
559 thisoutput[8]='l';
560 strcatW(finaloutput, thisoutput);
564 /* 3. Handle 't' : Date+time (File doesn't have to exist) */
565 if (memchrW(firstModifier, 't', modifierLen) != NULL) {
567 SYSTEMTIME systime;
568 int datelen;
570 doneModifier = TRUE;
572 if (exists) {
573 if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
575 /* Format the time */
576 FileTimeToSystemTime(&fileInfo.ftLastWriteTime, &systime);
577 GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime,
578 NULL, thisoutput, MAX_PATH);
579 strcatW(thisoutput, spaceW);
580 datelen = strlenW(thisoutput);
581 GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &systime,
582 NULL, (thisoutput+datelen), MAX_PATH-datelen);
583 strcatW(finaloutput, thisoutput);
587 /* 4. Handle 'z' : File length (File doesn't have to exist) */
588 if (memchrW(firstModifier, 'z', modifierLen) != NULL) {
589 /* FIXME: Output full 64 bit size (sprintf does not support I64 here) */
590 ULONG/*64*/ fullsize = /*(fileInfo.nFileSizeHigh << 32) +*/
591 fileInfo.nFileSizeLow;
592 static const WCHAR fmt[] = {'%','u','\0'};
594 doneModifier = TRUE;
595 if (exists) {
596 if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
597 wsprintfW(thisoutput, fmt, fullsize);
598 strcatW(finaloutput, thisoutput);
602 /* 4. Handle 's' : Use short paths (File doesn't have to exist) */
603 if (memchrW(firstModifier, 's', modifierLen) != NULL) {
604 if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
606 /* Convert fullfilename's path to a short path - Save filename away as
607 only path is valid, name may not exist which causes GetShortPathName
608 to fail if it is provided */
609 if (filepart) {
610 strcpyW(thisoutput, filepart);
611 *filepart = 0x00;
612 GetShortPathNameW(fullfilename, fullfilename, ARRAY_SIZE(fullfilename));
613 strcatW(fullfilename, thisoutput);
617 /* 5. Handle 'f' : Fully qualified path (File doesn't have to exist) */
618 /* Note this overrides d,p,n,x */
619 if (memchrW(firstModifier, 'f', modifierLen) != NULL) {
620 doneModifier = TRUE;
621 if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
622 strcatW(finaloutput, fullfilename);
623 } else {
625 WCHAR drive[10];
626 WCHAR dir[MAX_PATH];
627 WCHAR fname[MAX_PATH];
628 WCHAR ext[MAX_PATH];
629 BOOL doneFileModifier = FALSE;
630 BOOL addSpace = (finaloutput[0] != 0x00);
632 /* Split into components */
633 WCMD_splitpath(fullfilename, drive, dir, fname, ext);
635 /* 5. Handle 'd' : Drive Letter */
636 if (memchrW(firstModifier, 'd', modifierLen) != NULL) {
637 if (addSpace) {
638 strcatW(finaloutput, spaceW);
639 addSpace = FALSE;
642 strcatW(finaloutput, drive);
643 doneModifier = TRUE;
644 doneFileModifier = TRUE;
647 /* 6. Handle 'p' : Path */
648 if (memchrW(firstModifier, 'p', modifierLen) != NULL) {
649 if (addSpace) {
650 strcatW(finaloutput, spaceW);
651 addSpace = FALSE;
654 strcatW(finaloutput, dir);
655 doneModifier = TRUE;
656 doneFileModifier = TRUE;
659 /* 7. Handle 'n' : Name */
660 if (memchrW(firstModifier, 'n', modifierLen) != NULL) {
661 if (addSpace) {
662 strcatW(finaloutput, spaceW);
663 addSpace = FALSE;
666 strcatW(finaloutput, fname);
667 doneModifier = TRUE;
668 doneFileModifier = TRUE;
671 /* 8. Handle 'x' : Ext */
672 if (memchrW(firstModifier, 'x', modifierLen) != NULL) {
673 if (addSpace) {
674 strcatW(finaloutput, spaceW);
675 addSpace = FALSE;
678 strcatW(finaloutput, ext);
679 doneModifier = TRUE;
680 doneFileModifier = TRUE;
683 /* If 's' but no other parameter, dump the whole thing */
684 if (!doneFileModifier &&
685 memchrW(firstModifier, 's', modifierLen) != NULL) {
686 doneModifier = TRUE;
687 if (finaloutput[0] != 0x00) strcatW(finaloutput, spaceW);
688 strcatW(finaloutput, fullfilename);
693 /* If No other modifier processed, just add in parameter */
694 if (!doneModifier) strcpyW(finaloutput, outputparam);
696 /* Finish by inserting the replacement into the string */
697 WCMD_strsubstW(*start, lastModifier+1, finaloutput, -1);
700 /*******************************************************************
701 * WCMD_call - processes a batch call statement
703 * If there is a leading ':', calls within this batch program
704 * otherwise launches another program.
706 void WCMD_call (WCHAR *command) {
708 /* Run other program if no leading ':' */
709 if (*command != ':') {
710 WCMD_run_program(command, TRUE);
711 /* If the thing we try to run does not exist, call returns 1 */
712 if (errorlevel) errorlevel=1;
713 } else {
715 WCHAR gotoLabel[MAX_PATH];
717 strcpyW(gotoLabel, param1);
719 if (context) {
721 LARGE_INTEGER li;
722 FOR_CONTEXT oldcontext;
724 /* Save the for variable context, then start with an empty context
725 as for loop variables do not survive a call */
726 oldcontext = forloopcontext;
727 memset(&forloopcontext, 0, sizeof(forloopcontext));
729 /* Save the current file position, call the same file,
730 restore position */
731 li.QuadPart = 0;
732 li.u.LowPart = SetFilePointer(context -> h, li.u.LowPart,
733 &li.u.HighPart, FILE_CURRENT);
734 WCMD_batch (context->batchfileW, command, TRUE, gotoLabel, context->h);
735 SetFilePointer(context -> h, li.u.LowPart,
736 &li.u.HighPart, FILE_BEGIN);
738 /* Restore the for loop context */
739 forloopcontext = oldcontext;
740 } else {
741 WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_CALLINSCRIPT));