Running batch file with /d option - windows

I've been working for some time on the installer for my application (using Installshield), and some time ago I came upon the problem, when batch file, that I called using LaunchApplication failed to execute (to be specific - it executed, but in the wrong directory). I decided to dig this issue up and stumbled upon this article. The problem, it turns out, lies within Autorun registry key, which is defined in following matter:
cd /d C:\Blahblah\Yadayada
So, before the batch file was actually executed, this command changed directory.
The batch file is most basic one, something like this:
:start
foo.exe %1 --bar %2 --baz %3
if errorlevel 1 goto fail
ECHO Success
goto end
fail:
ECHO Fail
:end
So, basically this batch file expects that it will be launched from the correct directory, and it's not. I'm append INSTALDIR variable to the batchname in LaunchApplication call, just to be clear, and it works perfectly fine when Autorun key is not set up.
And, well, I finally got to the question - is there any way to provide launch options for individual batch file? I know that providing /d option will render Autorun useless, but it only works only on direct call.
For instance, let's assume that I have batch file with simple 'dir' command (let's call it foo.bat), and my Autorun key is defined as shown above. I run command-prompt with /d option (CMD /d), and then run 'dir' directly I'll get content of the folder I'm currently in (e.g. user folder); BUT, if I launch foo.bat, I'll get contents of C:\Blahblah\Yadayada, because Autorun command will execute first, set default folder, and only after that 'dir' command will be called.
So, personally I see a few options here. First - removing Autorun key, and that would be the most fitting and easiest solution if it had to be applied only for one machine - I can't possibly expect every user to take care of their Autorun key for themselves. Second, which should be applicable for everyone (but which I hadn't tested yet), would be providing path to installation folder as extra parameter to batch file, and then changing directory to that:
:start
%4
cd %5
foo.exe %1 --bar %2 --baz %3
...
Where %4 would be a disk letter and %5 would be a path. This seems to be a solution, but I find it counterproductive that I have to implicitly change path to the folder, given that it works perfectly well when Autorun key is absent.
So, I was wondering if there's any workaround about this Autorun key problem. Maybe, like I've mentioned in the title, kind of /d option for batch file so that when it runs it will override global option from Autorun and will actually launch from the place it's supposed to launch, or some kind of technique like that? Also, maybe there's some kind of option in LaunchApplication() function I'm not aware of?

Related

Batch | Why can't a script running in admin access other scripts? [duplicate]

I have a batch file that is in the same directory as the file I want to xcopy. But for some reason the file is not being found.
I thought that current directory was always where the batch file was located.
I run batch file as administrator. This occurs on a Windows 7 64-bit desktop computer.
Batch file:
#ECHO OFF
XCOPY /y "File1.txt" "File2.txt"
PAUSE
Error:
File not found - File1.txt
0 File(s) copied
Which directory is current working directory on starting a batch file with context menu item Run as administrator depends on User Account Control (UAC) setting for the current user.
This can be demonstrated with following small batch file C:\Temp\Test.bat:
#echo Current directory is: %CD%
#pause
With having selected in User Account Control Settings
Default - Notify me only when programs try to make changes to my computer
Don't notify me when I make changes to Windows settings
and using Run as administrator, Windows uses registry key
HKEY_CLASSES_ROOT\batfile\shell\runasuser\command
This registry key does not contain a default string for executing the batch file. Instead there is the string value DelegateExecute with the CLSID {ea72d00e-4960-42fa-ba92-7792a7944c1d}.
The result is opening a dialog window with title User Account Control and text:
Do you want to allow the following program to make changes to this computer?
Program name: Windows Command Processor
Verified publisher: Microsoft Windows
After confirmation by the user, Windows opens temporarily a new user session like when using on command line RunAs.
In this new user session the current working directory is %SystemRoot%\System32 on executing now the command defined in Windows registry with default string of key
HKEY_CLASSES_ROOT\batfile\shell\runas\command
which is:
%SystemRoot%\System32\cmd.exe /C "%1" %*
Therefore a console window is opened with title C:\Windows\System32\cmd.exe and the 2 lines:
Current directory is: C:\Windows\System32
Press any key to continue . . .
After hitting any key, batch execution finishes which results in closing cmd.exe which results in closing the user session.
But with having selected in User Account Control Settings
Never notify me when
Programs try to install software or make changes to my computer
I make changes to Windows settings
the behavior is different as the user has already elevated privileges.
Now Windows uses directly the command
%SystemRoot%\System32\cmd.exe /C "%1" %*
according to default string of key
HKEY_CLASSES_ROOT\batfile\shell\runas\command
in current user session.
The result is opening a console window also with title C:\Windows\System32\cmd.exe, but displayed in window is:
Current directory is: C:\Temp
Press any key to continue . . .
The current working directory of the parent process (Windows Explorer as desktop) is used for executing of the batch file because no switch to a different user session was necessary in this case.
PA has posted already 2 possible solutions in his answer which I replicate here with a small improvement (pushd with directory in double quotes) and with adding a third one.
Change current directory to directory of batch file using pushd and popd:
pushd "%~dp0"
%SystemRoot%\System32\xcopy.exe "File1.txt" "File2.txt" /Y
popd
This works also for UNC paths. Run in a command prompt window pushd /? for an explanation why this also works for UNC paths.
Use directory of batch file in source and destination specifications:
%SystemRoot%\System32\xcopy.exe "%~dp0File1.txt" "%~dp0File2.txt" /Y
Change working directory to directory of batch file using cd:
cd /D "%~dp0"
%SystemRoot%\System32\xcopy.exe "File1.txt" "File2.txt" /Y
This does not work for UNC paths because command interpreter cmd does not support a UNC path as current directory by default, see for example CMD does not support UNC paths as current directories for details.
The error message is very self explanatory. The file file1.txt is not found.
Because the file name does not include an absolute path, the system tries to find it on the current directory. Your current directory does not contain this file.
Your misconception is that the current directory is not the directory that contains the bat file. Those are two unrelated concepts.
You can easily check by adding this two commands in your bat file
echo BAT directory is %~dp0
echo Current directory is %CD%
you can notice they are different, and that there is a subtle difference in the way the last backslash is appended or not.
So, there are esentially two ways to cope with this problem
either change the current directory to match the expected one
pushd %~dp0
XCOPY /y "File1.txt" "File2.txt"
popd
or specify the full path in the command
XCOPY /y "%~dp0File1.txt" "%~dp0File2.txt"
For the sake of completeness and obscurity, I add another workaround, confirmed as working under Windows 8.1 and expected to work elsewhere, as it relies on documented functionality:
You can change the runas command definition keys
HKEY_CLASSES_ROOT\batfile\shell\runas\command and
HKEY_CLASSES_ROOT\cmdfile\shell\runas\command into
%SystemRoot%\System32\cmd.exe /S /C "(for %%G in (%1) do cd /D "%%~dpG") & "%1"" %*
Which results in the bat or cmd file starting in its containing directory when started using the runas verb, respectively the "Run as Administrator" menu entry.
What the additions to the original command exactly do:
cmd /S strips away first and last (double) quote in command string after /C
for %%G in (%1) do enumerates its single entry, the %1 argument,
making it available for expansion as %%G in the loop body; the letter is arbitrary but some may be "reserved"
%%~dpG expands to the drive and path of %%G, the ~ tilde stripping away quotes if present, which is why we add them back explicitly
cd /D changes both the drive and directory to its argument, and finally
& runs the second command "%1" %* regardless of success of the first one.
You can use pushd which will even support UNC paths, but a stray popd would land any script in the system32 directory, not a behavior I would be fond of.
It should be possible to do this for the exefile entry as well, but frankly, I'd rather live with the inconsistency than to attempt this on my system, as any error there could break a lot.
Enjoy defeating the security mechanics of your operating system :)

Batch file - IF EXISTS statement "The system cannot open the device or file specified."

I have written a batch file to uninstall a faulty WiFi driver (Intel ProSet Wireless) and set up the appropriate wireless profile on a laptop. This script is intended to be run remotely through Symantec Management Agent.
The code starts by running a group policy update to pull down appropriate
network certificates from the server. Then the code checks to see if the WiFi driver is installed. If it is, the script uninstalls it. Afterwards, in either case, it will wipe the current wireless profiles and call another batch file to install the appropriate wireless profile.
My issue is, when I run the script, the console will report "The system cannot open the device or file specified." after the software is uninstalled and it will terminate. The IF EXIST statement checks to see if one of the software files is there.
Typically, I can just run the same script a second time, and the IF EXIST case will not be met, so the rest of the batch file will work properly.
I am attaching my code below -- am I using IF EXIST correctly?
gpupdate /force
IF EXIST "C:\ProgramData\Package Cache\{552523b2-40ad-46b3-94f6-2b99d0860d5c}\setup.exe" (
cd "C:\ProgramData\Package Cache\{552523b2-40ad-46b3-94f6-2b99d0860d5c}\"
start /wait setup.exe /uninstall
)
TIMEOUT /T 3 /nobreak >nul
netsh wlan delete profile name=*
cd "C:\Wireless_Settings\"
Mobile_Devices_profile.bat
I have researched other posts, and I do believe I am using the condition correctly. I don't see any other post that matches my case. It always correctly checks to see if the condition is met, however I don't understand why the program terminates after the software is uninstalled. All that I believe should happen is the case is no longer met, so the script continues on.
Possible solution : (your if statement appears to be correct)
Insert a pushd statement before the cd and a popd after the start.
This will ensure you return to your original directory after the if statement invokes setup.exe
IF EXIST "C:\ProgramData\Package Cache\{552523b2-40ad-46b3-94f6-2b99d0860d5c}\setup.exe" (
PUSHD
cd "C:\ProgramData\Package Cache\{552523b2-40ad-46b3-94f6-2b99d0860d5c}\"
start /wait setup.exe /uninstall
POPD
)
If it works, fine and good. If it doesn't, It's easy to undo.
Sure - in theory, you could change the cd to a PUSHD instead. There are many paths.
IF EXIST "C:\ProgramData\Package Cache\{552523b2-40ad-46b3-94f6-2b99d0860d5c}\setup.exe" (
PUSHD "C:\ProgramData\Package Cache\{552523b2-40ad-46b3-94f6-2b99d0860d5c}\"
start /wait setup.exe /uninstall
POPD
)
This should work.
Now that's interesting. I'm sure I've use pushd without arguments before. The documentation reads
Stores the current directory for use by the POPD command, then
changes to the specified directory.
PUSHD [path | ..]
which is not explicit when the option argument is missing.
I've also noticed that a dir list on my batch-development directory now lists a unicode-named file with spaces between the squares whereas it used to not contain those spaces. Maybe something has been silently changed... cmd version is dated 20170929
Setup.exe is trying to remove the directory from the Package Cache which happens to be your current working directory. Not a good way for a script to live.
#setlocal ENABLEEXTENSIONS
#rem #set prompt=$G
#set "_setupExe=C:\ProgramData\Package Cache\{552523b2-40ad-46b3-94f6-2b99d0860d5c}\setup.exe"
#gpupdate /force
#if exist "%_setupExe%" call %_setupExe% /uninstall
#TIMEOUT /T 3 /nobreak >nul
#netsh wlan delete profile name=*
#cd "C:\Wireless_Settings\"
#Mobile_Devices_profile.bat
I would add that debugging your script was complicated by the fact that you had a multi-line code block. You should avoid those at all costs. It's better to call a subroutine if you can fit it all on a single line.

How do I remove previous extension<->Program affiliations programatically?

I would like to write a batch file which ensures that when a user clicks on a .JNLP file, it opens using javaws.exe (located in program files/java/... you know the drill)
I have written the following batch file:
ASSOC .jnlp=JNLPFILE
IF EXIST "%ProgramFiles% (x86)" (GOTO x86) ELSE (GOTO x64)
:x86
FTYPE JNLPFILE="%ProgramFiles% (x86)\Java\jre7\bin\javaws.exe" "%1"
goto:eof
:x64
FTYPE JNLPFILE="%ProgramFiles%\Java\jre7\bin\javaws.exe" "%1"
I am testing this all in windows 7 64-bit.
This... doesn't work. Well, that's not entirely accurate. It modifies the registry correctly, and it adds .jnlp to windows list of Recommended programs to run. It does exactly what it should.
But it doesn't solve my problem. See, For testing, I went to Default Programs and associated .jnlp files with Notepad. And when I try to open .Jnlp files, IT tries to open in notepad, even after my code has run.
If I do an open with on a .jnlp, it gives me the option to open with Notepad or javaws.exe If I've run my code with the ASSOC, it adds a SECOND option of javaws.exe
I've tried ASSOC .jnlp="" and FTYPE JNLPFILE="" To try and clear notepad out, but had no luck.
How do I make my batch file blow away prior settings and assert its dominance on the machine?
EDIT: Using the answers below, I have added a single command to the beginning of my batch file, which should take care of my problem and make things work correctly.
REG DELETE HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.jnlp /f
Windows Explorer keeps its own list of file extensions for the user.
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts
To remove programs from this list, delete the program entry from
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.‌​jnlp\OpenWithList
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.jnlp\OpenWithProgIDs
and set the desired UserChoice Progid in
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.jnlp\UserChoice
Also, note that the OpenWithList and OpenWithProgids can be set at multiple levels.
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.xyz\OpenWithList
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.xyz\OpenWithProgIDs
HKCR\.xyz\OpenWithList
HKCR\.xyz\OpenWithProgIDs
HKCR\SystemFileAssociations\FileType\OpenWithList

shell:Common Startup as a parameter to XCOPY

I have a simple batch script that copies a file to the startup folder, but it appears that I can't use shell:Common Startup as a parameter to xcopy. I have tried this
xcopy hurrdurr.exe "shell:Common Startup"
and many other variations, and they don't work. As an aside, if this did work, "hurrdurr.exe" would run on every startup right, assuming I got clearance via uac to do the xcopy operation? Would using a environment variable be better? The os in question is Windows XP and proceeding.
I'm not sure why your shell command won't work, but if you need to get your program to load on startup then I would much prefer using the registry, it's cleaner and simpler, and it means you don't have to copy the file somewhere else, especially if that file is dependant on other things.
reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run /v "Hurrdurr" /d "hurrdurr.exe" /f
Just run cmd as admin and it will work :)
If you do want to use the startup folder though, on Win7 you can use
"%appdata%\Microsoft\Windows\Start Menu\Programs\Startup"
I would also use a shortcut as #David suggested instead of copying the actual file.

Pausing a batch file when double-clicked but not when run from a console window?

Is there a way for a batch file (in this case, running on Windows XP) to determine whether it was launched from a command line (i.e. inside a console window) or launched via the shell (e.g. by double-clicking)?
I have a script which I'd like to have pause at certain points when run via the shell, but not when run at a command line. I've seen a similar question on SO, but am unable to use the same solution for two reasons: first, whether or not it pauses needs to be dependent on multiple factors, only one of which is whether it was double-clicked. Second, I'll be distributing this script to others on my team and I can't realistically ask all of them to make registry changes which will affect all scripts.
Is this possible?
Found one :-) – After desperately thinking of what cmd might do when run interactively but not when launching a batch file directly ... I finally found one.
The pseudo-variable %cmdcmdline% contains the command line that was used to launch cmd. In case cmd was started normally this contains something akin to the following:
"C:\Windows\System32\cmd.exe"
However, when launching a batch file it looks like this:
cmd /c ""C:\Users\Me\test.cmd" "
Small demo:
#echo off
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" set DOUBLECLICKED=1
if defined DOUBLECLICKED pause
This way of checking might not be the most robust, though, but /c should only be present as an argument if a batch file was launched directly.
Tested here on Windows 7 x64. It may or may not work, break, do something weird, eat children (might be a good thing) or bite you in the nose.
A consolidated answer, derived from much of the information found on this page (and some other stack overflow pages with similar questions). This one does not rely on detecting /c, but actually checks for the name of the script in the command line. As a result this solution will not pause if you double-clicked on another batch and then called this one; you had to double-click on this particular batch file.
:pauseIfDoubleClicked
setlocal enabledelayedexpansion
set testl=%cmdcmdline:"=%
set testr=!testl:%~nx0=!
if not "%testl%" == "%testr%" pause
The variable "testl" gets the full line of the cmd processor call, stripping out all of the pesky double quotes.
The variable "testr" takes "testl" and further strips outs the name of the current batch file name if present (which it will be if the batch file was invoked with a double-click).
The if statement sees if "testl" and "testr" are different. If yes, batch was double-clicked, so pause; if no, batch was typed in on command line (or called from another batch file), go on.
Edit: The same can be done in a single line:
echo %cmdcmdline% | findstr /i /c:"%~nx0" && set standalone=1
In plain English, this
pipes the value of %cmdcmdline% to findstr, which then searches for the current script name
%0 contains the current script name, of course only if shift has not been called beforehand
%~nx0 extracts file name and extension from %0
>NUL 2>&1 mutes findstr by redirecting any output to NUL
findstr sets a non-zero errorlevel if it can't find the substring in question
&& only executes if the preceding command returned without error
as a consequence, standalone will not be defined if the script was started from the command line
Later in the script we can do:
if defined standalone pause
One approach might be to create an autoexec.nt file in the root of c:\ that looks something like:
#set nested=%nested%Z
In your batch file, check if %nested% is "Z" - if it is "Z" then you've been double-clicked, so pause. If it's not "Z" - its going to be "ZZ" or "ZZZ" etc as CMD inherits the environment block of the parent process.
-Oisin
A little more information...
I start with a batch-file (test.cmd) that contains:
#echo %cmdcmdline%
If I double-click the "test.cmd" batch-file from within Windows Explorer, the display of echo %cmdcmdline% is:
cmd /c ""D:\Path\test.cmd" "
When executing the "test.cmd" batch-file from within a Command Prompt window, the display of
echo %cmdcmdline% depends on how the command window was started...
If I start "cmd.exe" by clicking the "Start-Orb" and "Command Prompt" or if I click "Start-Orb" and execute "cmd.exe" from the search/run box. Then I execute the "test.cmd" batch-file, the display of echo %cmdcmdline% is:
"C:\Windows\system32\cmd.exe"
Also, for me, if I click "Command Prompt" from the desktop shortcut, then execute the "test.cmd" batch-file, the display of echo %cmdcmdline% is also:
"C:\Windows\system32\cmd.exe"
But, if I "Right-Click" inside a Windows Explorer window and select "Open Command Prompt Here", then execute the "test.cmd" batch-file, the display of echo %cmdcmdline% is:
"C:\Windows\System32\cmd.exe" /k ver
So, just be careful, if you start "cmd.exe" from a shortcut that contains a "/c" in the "Target" field (unlikely), then the test in the previous example will fail to test this case properly.

Resources