I'm working on an unattended automated install of SQL 2008, 2012 and 2014 which so far is working with my batch command. The one issue I'm encountering is that in order to use a core function found in each program (management studio) properly on Windows 10, it needs to run as an admin.
In Windows 10 this can be done manually by opening the file location of the program shortcut - right click - properties - "compatibility" tab - "change settings for all users" - check the box "run this program as an administrator". Is there a way to have a batch command check that box? This way staff won't need to manually run it as an admin each time, it'll just open Management Studio automatically as an admin.
My batch command can be found below to automate the install. The beginning finds the installation files, sets the sa password to whatever I want and pulls the custom settings for the SQL installer from the configurationfile.ini file. I need the "run as an admin" for all users to run after all that.
start "" "%~dp0SQL2008\setup.exe" /SAPWD="XXXXXXXX" /ConfigurationFile="ConfigurationFile.ini" /quiet
#echo off
setlocal
call :reg_compat "C:\User\Test\test.exe" "WINXPSP3 RUNASADMIN"
if errorlevel 1 echo Registry write failed.
exit /b
:reg_compat fullpath, reg_data
setlocal
set "reg_key=HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
set "reg_data="
:: Get current registry data.
for /f "tokens=1,2*" %%A in ('2^>nul reg query "%reg_key%" /v "%~1"') do (
if /i "%%~A" == "%~1" set "reg_data=%%~C"
)
:: Write new entry and exit.
if not defined reg_data (
>nul reg add "%reg_key%" /v "%~1" /d "%~2" /f
if errorlevel 1 exit /b 1
exit /b 0
)
:: Copy original registry data.
set "reg_data_original=%reg_data%"
:: Append new data if not in original registry data.
for %%A in (%~2) do (
set "value_exist="
for %%B in (%reg_data_original%) do (
if "%%~A" == "%%~B" set "value_exist=1"
)
if not defined value_exist (
call set "reg_data=%%reg_data%% %%~A"
)
)
:: Continue only if registry data is modified.
if "%reg_data_original%" == "%reg_data%" exit /b 0
:: Write modified entry and exit.
>nul reg add "%reg_key%" /v "%~1" /d "%reg_data%" /f
if errorlevel 1 exit /b 2
exit /b 0
The code is set to write a test entry. The test entry will add
Windows XP SP3 and Run As Admin compatibility for that filepath.
For actual use change the arguments to call the label :reg_compat
with the fullpath to the file as the 1st argument and the
reg_data compatibility arguments as the 2nd argument. The
compatibility arguments are uppercase and separated with a space.
The label :reg_compat will write new entries or update entries with
adding new compatibility arguments. The errorlevel from the called
label is set to not 0 if reg add fails.
Minimal code instead if you need just that:
#echo off
setlocal
set "reg_key=HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
reg add "%reg_key%" /v "C:\User\Test\test.exe" /d "WINXPSP3 RUNASADMIN" /f
Related
What I am trying to do is a backup by cmd commands but the problem is when I am taking the USB backup to another PC to back up the drive names are different.
For example when I do:
XCOPY G:\*.BMP X:\ /h/i/c/k/e/y/r/d
In the other computer the drives will not be G and X.
What I am seeking to do is if it is possible to make a program that I can enter with the keyboard what drive I want to backup and to what drive.
For example:
XCOPY "driver name keyboard input":\*.BMP "driver name keyboard input":/" /h/i/c/k/e/y/r/d
Yes, it can be done using a PowerShell or batch-file script (cmd tag seems to imply Windows OS).
Let's choose the latter. Next batch-file code snippet would do the same as the command in question: XCOPY G:*.BMP X:\ /h/i/c/k/e/y/r/d:
set "DriveIn=G"
set "DriveOu=X"
XCOPY %DriveIn%:*.BMP %DriveOu%:\ /h/i/c/k/e/y/r/d
Instead of hard-coded DriveIn and DriveOu, you can prompt for user input:
set /P "DriveIn=please choose SOURCE drive letter "
set /P "DriveOu=please choose TARGET drive letter "
XCOPY %DriveIn%:*.BMP %DriveOu%:\ /h/i/c/k/e/y/r/d
Hints for (necessary!) validity checks:
:dfromscratch
set "DriveIn="
set "DriveOu="
:dsource
set /P "DriveIn=please choose SOURCE drive letter "
rem basic validity check
if not defined DriveIn goto :dsource
if not exist "%DriveIn%:\" goto :dsource
:dtarget
set /P "DriveOu=please choose TARGET drive letter "
rem basic validity check
if not defined DriveOu goto :dtarget
if not exist "%DriveOu%:\" goto :dtarget
if /I "%DriveIn%"=="%DriveOu%" goto :dfromscratch
rem involve more validity check here!!!
XCOPY %DriveIn%:*.BMP %DriveOu%:\ /h/i/c/k/e/y/r/d
Some hints for (more) validity checks.
To show available disk drives:
wmic logicaldisk get Description, DeviceID, DriveType, FileSystem, VolumeName
To get a list of available disk drives, use for /F loop (note %%G in a batch script):
for /F %%G in ('
wmic logicaldisk where "DriveType != 5" get DeviceID^, DriveType^|find ":"
') do echo %%G
or next oneliner (note %G in cmd): copy&paste into an open cmd window:
for /F %G in ('wmic logicaldisk where "DriveType != 5" get DeviceID^, DriveType^|find ":"') do #echo %G
Another approach (only a draft, needs more elaboration): build a list of available drive letters %drives% and use choice command instead of set /P:
set "drives=GX"
choice /C %drives% /M "Select SOURCE drive letter"
Main_Control is the parent batch.
It calls batches named DEFAULT, SETOPTIONS AND USEROPTIONS.
DEFAULT sets a variable %def_set% to either 1 or 2. %def_set% is used in USEROPITONS to set some other variables which are used in SETOPITONS. After DEFAULT, in the MAIN_CONTROL, a test of %def_set% returns error "'1' (or '2') is not recognized as an internal or external command, program or batch" for both %def_set%=1 or 2. It doesn't seem to pass the variable correctly. What have I done wrong?
------------------------CODE-------------------------
"Main_Control" = batch1
#ECHO off
CLS
MODE CON:cols=130 lines=75
REM This particular file is the control script which CALLs the subroutines to perform the back or restore of settings.
:skipintro
REM 1. Get backup location and subroutine location/names.
REM 2. Clear screen
REM 3. Display the notes text file in the window (must be paused).
REM 4. Pause to read notes
REM 4. Get user name
REM 6. "START1" label. Start point if the user wants to rerun backup/restore in same session.
REM 7. Ask if user wants to backup or restore
REM 8. Ask what settings to backup/restore
REM 9. Ask if the user wants to use default settings
REM 10. If user wants to change default settings, provide options to change.
REM 11. Set the options to use throughout the backup/restore.
CALL "%~DP0"_BackupLocation_Subroutines.bat
CLS
TYPE %NOTES%
Timeout /t 30
CALL %GETUSERNAME%
:START1
CALL %B_R_OPTION%
CALL %MENU%
CALL %DEFAULTS%
rem test1
echo before & %def_set% & pause
rem END TEST1
CALL %USEROPTIONS%
REM TEST2
echo after & %def_set% & pause
REM END TEST2
CALL %SETOPTIONS%
Echo You are back in %~n0.
Echo You chose action: %ACTION%
Echo You chose to %ACTION%: %SELECTION%
Timeout /T 5
:: Create (for backups) or verify existence (for restores) the Main Backup and Registry Folders.
SETLOCAL ENABLEDELAYEDEXPANSION
SET FOLDERS= ^
%BACKUP_FOLDER% ^
%REGISTRY_FOLDER%
IF %ACTION%==Backup CALL %CREATE%
IF %ACTION%==Restore CALL %VERIFY%
ETC ETC ETC
%DEF_set% has to pass from DEFAULTS to USEROPTIONS and SETOPTIONS. But it is not.
DEFAULTS batch
#echo off
REM Name: DEFAULTS
REM Asks user to use defaults and have no more prompts or to be prompted for inputs.
:def_input
CLS
ECHO Would you like to use default values?
ECHO Backup location: %Backup_Folder% (cannot modify at this time)
ECHO Overwrite previous backup: No
ECHO Rename previous backup with date/time: Yes
ECHO Prompt to overwrite files: No (no option to change)
ECHO Prompt to delete files: No
ECHO Generate a log: Yes
ECHO Overwrite or append to an existing log: Overwrite
ECHO Log file located in backup location: Yes (no option to change)
ECHO.
ECHO 1. Yes
ECHO 2. No
ECHO.
Set /P Def_SET= Choose:
ECHO.
FOR %%w in (1 2) DO IF #%Def_SET%==#%%w (
ECHO You selected: %Def_SET%
Timeout /T 5
Exit /b
)
REM USER ERROR
REM If the user types something other than 1 or 2, the FOR/DO/IF statement won't run indicating user error.
REM The routine will continue here, letting the user know there is an error.
ECHO Error
ECHO Select 1 or 2 & GOTO :def_input
After this, TEST1 in Main_control batch returns this error "'1' is not recognized as an internal or external command, program or batch".
USEROPTIONS batch
#echo off
REM Name: USEROPTIONS
ECHO %~no & %def_set% & pause
IF %def_set%==1 GOTO :defopts
IF %def_set%==2 GOTO :askopts
:defopts
:: Overwrite previous backup
SET OW_BU=1
:: Generate a log
SET ONLog=1
:: Overwrite the log
SET OWLog=1
:: Prompt to delete files
SET Del_P=1
GOTO :EOF
:askopts
:: Questions to Users
:: Overwrite Backup Question
:: Rename the previous backup if user does not want it overwritten.
:OW_Backup
IF NOT EXIST %BACKUP_FOLDER%\ GOTO :skipOWQ
REM If previous backup does not exist, skip question to overwrite it.
ECHO.
ECHO Do you want to OVERWRITE the previous backup. Choose:
ECHO 1. Do not overwrite old backup,then rename it with its "created" date.
ECHO 2. Overwrite old backup.
ECHO.
SET /P OW_BU= Make a selection...
FOR %%u in (1 2) DO IF #%OW_BU%==#%%u (
ECHO You selected: %OW_BU%
GOTO SkipError1
)
REM Error
ECHO.
ECHO.
ECHO *********Error*************
ECHO Select 1 or 2 & GOTO :OW_Backup
:SkipError1
:skipOWQ
:: Generate Robocopy Log
:GenLog
ECHO.
ECHO Do you want to GENERATE a Robocopy log?
ECHO It will be located in %Backup_Folder%.
ECHO 1. Yes
ECHO 2. No
ECHO.
SET /P ONLog= Make a selection...
FOR %%v in (1 2) DO IF #%ONLog%==#%%v (
ECHO You selected: %ONLog%
GOTO SkipError2
)
REM Error
ECHO.
ECHO.
ECHO *********Error*************
ECHO Select 1 or 2 & GOTO :GenLog
:SkipError2
:: Overwrite or Append Robocopy Log
IF %ONLog%==2 GOTO :Del_Prompt
:OW_RLog
ECHO.
ECHO Do you want to OVERWRITE the Robocopy log?
ECHO.
ECHO 1. Yes - Overwrite
ECHO 2. No - Append to Log
ECHO.
SET /P ONLog= Make a selection...
FOR %%v in (1 2) DO IF #%ONLog%==#%%v (
ECHO You selected: %ONLog%
GOTO SkipError3
)
REM Error
ECHO.
ECHO.
ECHO *********Error*************
ECHO Select 1 or 2 & GOTO :OW_Rlog
:SkipError3
:: Prompt to Delete Decision
:Del_Prompt
ECHO.
ECHO.
ECHO The restoration will delete default files that are not in your backup in order to restore your VDI to the way you had it.
ECHO Do you want confirmations to delete files?
ECHO 1. No confirmations to delete files.
ECHO 2. Review and confirm to delete files.
ECHO.
SET /P Del_P= Make a selection...
ECHO.
FOR %%x in (1 2) DO IF #%Del_P%==#%%x (
ECHO You selected: %Del_P%
GOTO SkipError4
)
REM Error
ECHO.
ECHO.
ECHO *********Error*************
ECHO Select 1 or 2 & GOTO :Del_Prompt
:SkipError4
Exit /b
Back to Main_Control batch, then call SETOPTIONS
SETOPTIONS batch
#echo off
REM Name: SETOPTIONS
ECHO Currenlty running: %~nx0
pause
:: Set Options
ECHO Backup location is: %Backup_Folder%
REM Test
echo OnLog %Onlog%
echo Overwrite %Overwrite%
echo Del_P %del_P%
pause
::///////////////////////// Robocopy options /////////////////////////////
IF %ONLog%==1 (
IF %OWLog%==1 SET LOG=/LOG:%BACKUP_FOLDER%\Robocopy_Log.txt
IF %OWLog%==2 SET LOG=/LOG+:%BACKUP_FOLDER%\Robocopy_Log.txt
) else (
SET LOG=
)
SET R_OPT=/Z /MIR %LOG%
REM Removed /copyall
::///////////////////// Overwrite Backup Code ////////////////////////////////
IF NOT EXIST %BACKUP_FOLDER%\ GOTO :skipOWC
IF %Overwrite%== 1 (
ECHO.
ECHO %BACKUP_FOLDER% will be RENAMED.
TIMEOUT /T 2
Call %RENAME%
)
IF %Overwrite%== 2 (
Echo %BACKUP_FOLDER% will be OVERWRITTEN.
ECHO Close this window if you've changed your mind.
TIMEOUT /T 10
)
REM Nothing to do to overwrite old backup. It will do it on its own with /MIR option.
:skipOWC
::///////////////////// Prompt to Delete Code ////////////////////////////////
IF %Del_P%== 1 SET Del_Opt_Prompt=/Q
IF %Del_P%== 2 SET DEL_Opt_Prompt=/P
DEL_OPT=/F /S %Del_Opt_Prompt%
REM Test
echo OnLog %Onlog%
echo Overwrite %Overwrite%
echo Del_P %del_P%
pause
EXIT /b
And back to Main_Control batch.
Escape the & ampersand character (this one to be displayed) using a ^ caret as follows:
echo before ^& %def_set% & pause
rem END TEST1
CALL %USEROPTIONS%
REM TEST2
echo after ^& %def_set% & pause
The & ampersand is one of a few with special meaning in a Window CLI and/or batch.
Read also Syntax : Escape Characters, Delimiters and Quotes.
So many errors and typos.
In trying to figure out why the called batches weren't working, I added more errors in my attempts to test it.
The tests in Main_Control contained ampersands.
echo before & %def_set% & pause
USEROPTIONS crashed because of a typo - letter "o" instead of zero "0" in:
ECHO %~no & %def_set% & pause
SETOPTIONS crashed because I forgot what variables I was using. %overwrite% doesn't describe anything and doesn't exist in the line:
echo Overwrite %Overwrite%
It should have been :
echo Overwrite backup OW_BU = %OW_BU%
echo Overwrite log OWlog = %OWlog%
SET was ommitted from the beginning of the line:
DEL_OPT=/F /S %Del_Opt_Prompt%
And i think that solved all my problems. What a waste of yesterday staring at this all day. In short, the calls worked & the variables were being passed from subroutine to subroutine. The errors were all typos initially and made even worse when I introduced more typos into my attempts to tests and trap errors. I'm tired of this project.
I have this really nice line in my batch file that tells me how many lines are in a file:
find /v /c "" C:\Users\c1921\mypath\myfolder\!$Unit!.txt
This is nice and gives me 31 for the particular file I'm working with. My problem is the file looks something like this:
Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.
my_handled
219278
check
219276
control
219274
I want to be able to skip the first three lines entirely and then save the first value and then use the second value in my next command etc.
How do I save the number (e.g. 31) into a variable in my batch file?
On a hunch I tried setting a variable like so but it wasn't effective:
set "$testVar="""
echo !$testVar!
This command allows you to "save the number (e.g. 31) into a variable in my batch file":
for /F %%a in ('find /v /c "" ^< C:\Users\c1921\mypath\myfolder\!$Unit!.txt') do set numLines=%%a
This command allows you "to skip the first three lines entirely" and process the rest:
for /F "skip=3 delims=" %%a in (C:\Users\c1921\mypath\myfolder\!$Unit!.txt) do echo Processing: "%%a"
However, in my opinion this problem could be entirely avoided if the three first lines in the text file are supressed from the very beginning. I think this file is generated via a VBScript of JScript program that is executed this way:
cscript progname.vbs > C:\Users\c1921\mypath\myfolder\!$Unit!.txt
The first three lines in the text file may be avoided adding //nologo switch this way:
cscript //nologo progname.vbs > C:\Users\c1921\mypath\myfolder\!$Unit!.txt
#ECHO OFF
SETLOCAL
SET /a count=3
SET "first="
FOR /f "skip=3delims=" %%a IN (q25089468.txt) DO (
IF DEFINED first (CALL :show %%a) ELSE (SET "first=%%a")
)
ECHO count=%count%
GOTO :EOF
:show
ECHO first=%first% second=%1
SET /a count+=2
SET "first="
GOTO :eof
I used a file named q25089468.txt containing your data for my testing.
You appear to be asking two entirely different things - how to count the lines and how to skip the first 3, then deliver each succeeding pair to another process.
I am trying to modify my batch script to get the install path for a piece of software, however it needs to be version independent and the install path is stored in a version sub-key, so basically what I am looking to do is detect the greatest version sub-key and get the install path from there.
Here is what the code for getting the registry value looks like now:
FOR /F "skip=2 tokens=2,*" %%A IN ('REG.exe query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node......\6.30" /v "InstallLocation"') DO set "InstallPath=%%B"
Basically I want to not be dependent on the "6.30" part on the end of the key address, how can I do this?
Since I do not know which exact software you are looking at, I will reference Adobe Reader on Winodws 7 x64.
Answer:
The following example will output all of the sub keys within the parent.
for /f "delims=" %%A in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Adobe\Acrobat Reader"') do if not "%%~A"=="" echo.%%~nxA
Output:
9.5
10.0
11.0
Sample:
From there it would just be a matter of remembering the largest and using it in the next query for the value data.
#echo off
setlocal EnableDelayedExpansion
set "xVersion="
set "xPath="
:: Retrieve Greatest Version
for /f "delims=" %%A in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Adobe\Acrobat Reader"') do (
if not "%%~A"=="" if "%%~nxA" GTR "!xVersion!" set "xVersion=%%~nxA"
)
:: Validate Version
if "%xVersion%"=="" goto :eof
:: Retrieve Install Path
for /f "tokens=1,2,*" %%A in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Adobe\Acrobat Reader\%xVersion%\Installer" /v Path') do (
set "xPath=%%~C"
)
:: Show Results
echo.%xPath%
endlocal
Output:
C:\Program Files (x86)\Adobe\Reader 10.0\
Bonus:
If you want to validate that the %%~nxA is a number, here is a batch routine of mine.
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:IsNumber <xReturn> <xInput> [xDelims]
:: Return true if the input is a base 10 number, else return false.
:::: Does not allow any seperators unless specified by xDelims. ,.[tab][space]
setlocal
if not "%~2"=="" set "xResult=true"
for /f "tokens=1 delims=1234567890%~3" %%n in ("%~2") do set xResult=false
endlocal & if not "%~1"=="" set "%~1=%xResult%"
goto :eof
:: Usage Example.
:: The variable xResult will be set to true if %%~nxA is a decimal number.
call :IsNumber xResult "%%~nxA" "."
These are not working for me.
Any help to definitelly corret the four examples below ?
The EXAMPLE01 just echoes "continue", even if I have three CMD.exe opened.
---------- EXAMPLE 01 ------------
#echo off
wmic process where name="cmd.exe" | find "cmd.exe" /c
SET ERRORLEVEL=value if "%value%" GTR 1 (
ECHO This batch is not first
ECHO quitting ...
)
if "%value%" LSS 2 ECHO continue
I am getting the "unexpected i error" message in the EXAMPLE 02!
----------- EXAMPLE 02 -------
#echo off
FOR /F "usebackq tokens=2" %i IN (`tasklist ^| findstr /r /b "cmd.exe"`)
DO taskkill /pid %%i
I am getting the "is first" message in the EXAMPLE 03, even with three CMD.exe opened!
----------- EXAMPLE 03 -------
#echo off
wmic process where name="cmd.exe" | find "cmd.exe" /c
if "%errorlevel%" LEQ 1 echo CMD is first
if "%errorlevel%" GTR 1 echo CMD is already running
It is also possible that I will not have access to the Wmic command at work, so, another possibility is found in the EXAMPLE 04 ... but to no avail.
----------- EXAMPLE 04 -------
#echo off
Tasklist /FI "IMAGENAME eq cmd.exe" 2>NUL | find /I /N "cmd.exe">NUL
if "%ERRORLEVEL%"==0 do (goto Use) else (goto Cont)
:Cont
ECHO Only one instance running
pause
:Use
echo Application running already. Close this window
Kind regards,
Maleck
wmz identified a number of errors in the OP's code, and also has an excellent suggestion to use a lock file for concurrency control.
Below is a robust batch solution that uses a lock file to prevent multiple instances of the batch file from running at the same time. It uses a temporary lock file for the concurrency control. The mere presence of the lock file does NOT stop the script from running. The script will only fail if another process has an exclusive lock on the lock file. This is important in case the script should crash or be killed without deleting the lock file. The next process to run the script will still succeed because the file is no longer locked.
This script assumes the script is installed on a local drive. It allows only one instance for the entire machine. There are lots of variations to control the amount of concurrency allowed. For example, incorporating the %USERNAME% into the lock file name would allow one instance per user in a network environment. Incorporating %COMPUTERNAME% in the name would allow one instance per machine in a network environment.
#echo off
setlocal
:: save parameters to variables here as needed
set "param1=%~1"
:: etc.
:: Redirect an unused file handle for an entire code block to a lock file.
:: The batch file will maintain a lock on the file until the code block
:: ends or until the process is killed. The main code is called from
:: within the block. The first process to run this script will succeed.
:: Subsequent attempts will fail as long as the original is still running.
set "started="
9>"%~f0.lock" (
set "started=1"
call :start
)
if defined started (
del "%~f0.lock" >nul 2>nul
) else (
echo Process aborted: "%~f0" is already running
)
exit /b
:start
:: The main program appears below
:: All this script does is PAUSE so we can see what happens if
:: the script is run multiple times simultaneously.
pause
exit /b
EDIT
The error message "The process cannot access the file because it is being used by another process." can be suppressed by redirecting stderr output to nul in an outer block.
2>nul (
9>"%~f0.lock" (
set "started=1"
call :start
)
)
The problem with the above is that all error messages for the main program will also be suppressed. That can be fixed by 1st saving the current definition of stderr to another unused file handle, and then adding yet another inner block that redirects stderr back to the saved file handle.
8>&2 2>nul (
9>"%~f0.lock" (
2>&8 (
set "started=1"
call :start
)
)
)
You do not set value anywhere. Even if you did it would not work as variables are expanded on parse. You would need to use delayed expansion if you need to both set and test variable against what was set. Quotes on comparison are not required (see 3). help set will show you info delayed expansion.
%i should be %%i. DO taskkill /pid %%i must be on same line as rest of for command. You also use findstr in regex mode, which means it will search for cmd[any character]exe
You use string (lexical) comparison: "1" leq 1 is true (as "4" leq 1 also is...). Use errorlevel 1 which is equivalent to errorlevel equal or greater than 1. help if shows syntax
Same as 3 plus do in do (goto Use) should be removed. %errorlevel%==0 would work, but it's normally advised to use errorlevel number.
How to check if there is only 1 cmd running:
#echo off
for /f %%i in ('Tasklist /FI "IMAGENAME eq cmd.exe" 2^>NUL' ^| find /I /c "cmd.exe"') do (
if %%i leq 2 (echo only one instance) else (echo More than one instance)
)
Note: it's just an example. I do not recommend this as a real method of concurrency control. I would rather use lock file for that.