I am working on Windows server 2008 r2; I scheduled a task that starts each day at 10:00 and runs for 8 hours.
If the server is restarted AND time is between 10-18 THEN the task should start as soon as possible, otherwise it should not run.
If I set a trigger at startup it starts at any time.
If I set the option "run as soon as possible" and the server was not working at start time then the task is started when the server is up, if instead the task was already lauched "today" it is not restarted automatically.
So I ask your advice: is it possible to force the start of the task in the correct timeframe using just task scheduler? I'd rather avoid an approach based on "run anyway but TSKILL if time is not correct".
I don't think this ALL can be accomplished with just a scheduled task. You would need to create a scrip file and then schedule that. I added Day of week also because I think you might be looking for M-F (it can be removed if not needed)
REM skip if not Mon-Fri
for /f %%a in ('wmic path win32_localtime get dayofweek /format:list ^| findstr "="') do (set %%a)
IF %dayofweek% LSS 1 goto skip
IF %dayofweek% GTR 5 goto skip
REM skip if no 10am-6pm
IF %time:~0,2% LSS 10 goto skip
IF %time:~0,2% GTR 18 goto skip
echo pass
REM skip if already run today source:https://groups.google.com/forum/#!topic/alt.msdos.batch.nt/uIzJ-rmx3PA
set Semaphore=%temp%\Semaphore.bat
if not exist "%Semaphore%" goto notRunToday
call "%Semaphore%"
if "%lastrun%" equ "%date%" (
echo %~nx0 already ran once today.
goto skip)
:notRunToday
echo set lastrun=%date%>"%Semaphore%"
REM run the command
MyProgram.exe
:skip
Related
I have a scheduled task with a .bat file that downloads some files from a web server every day by the morning then process the data and UPDATES a database. Then it triggers another .bat file to SELECT data and EXPORT to a .xls file.
The second .bat file is like this:
set a=%date:/=-%
del /q F:\file_path\file1_%a%.xls
del /q F:\file_path\file2_%a%.xls
echo %time%_%date%
cd /D D:\oracle\product\10.2.0\db_1\BIN
sqlplus usrname/psswd#ORCL #F:\select_path\select1.sql
timeout /t 30 /nobreak > nul
ren F:\file_path\file1.xls file1_%a%.xls
sqlplus usrname/psswd#ORCL #F:\select_path\select2.sql
timeout /t 30 /nobreak > nul
ren F:\file_path\file2.xls file2_%a%.xls
cd /D F:\KMB-SP\TI\Scripts\script_select
::Command to send file1 and file2 via e-mail.
But when I arrive at the office and check the progress, only the first .xls is done. So I have to run the second .bat manually and it runs perfectly.
What could be causing this?
Notes:
I put the timeout between the two SELECTs because, in the past, the code was stopping after the INSERT and didn't trigger the second .bat . My colleague said it could be execution exception. Puting a timeout would give time to end the INSERT properly.
Before, it used to make both SELECTs and then rename both files. Doing so, sometimes it worked, sometimes not, then I tried to change the order: select1, rename1, select2, rename2.
As we download files everyday, we concatenate the data on a single file called DT-date. The first code goes like this:
rem The data is downloaded and the files are organized in their files
if exist F:\path\DT-date (
Data_consolidation.exe
timeout /t 300 /nobreak > nul
F:\path\second_bat.bat
) else (exit)
As #William Robertson said, I tried echo exit right after the first SELECT, but again, it only extracted the first file and not the second one.
As #WilliamRobertson suggested, writing echo exit | before the sqlplus commands solved the problem.
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
I want to automate the execution of several bdf-files in a folder. But, I can only have one instance of my application running at the time (due to my license for the applicetion).
I want my code to do like below:
FOR ALL *.bdf files in folder DO THIS:
CHECK IF application is running
IF running, START index.bdf (with default application)
IF NOT running, WAIT a little, then go to "CHECK IF" again
Continue For-loop
My code look like this:
#ECHO OFF
TITLE AutorunNastran
FOR %%f IN (*.bdf) DO (
:loopbody
tasklist /nh /fi "imagename eq nastran.exe" | find /i "nastran.exe" >nul && (
echo Nastran is running
TIMEOUT 5
goto loopbody
) || (
echo Nastran is not running, a new bdf-file will be executed
START %%f
)
)
PAUSE
The first .bdf file will be executed, but it seems that my variable index.bdf=%%f is forgotten the second and third time the program is running through i-statements.
I'm new to batch-files, but I've tried to find answer on google, and think maybe I need to have this command "Setlocal EnableDelayedExpansion", but it has'nt worked for me so far.
Some help would be much appreciated!
Executing a goto inside a loop cancels the loop. Try with
#echo off
setlocal enableextensions disabledelayedexpansion
set "app=nastran.exe"
for %%a in (*.bdf) do (
cmd /q /c"(for /l %%z in (0) do (tasklist /nh /fi "imagename eq %app%" | find /i "%app%" && timeout 5 || exit /b)) >nul 2>&1"
start "" "%%~a"
)
Here the loop is moved to a separate cmd instance that will keep an infinite loop until the app is not found in tasklist
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.
I'm betting that someone has already solved this and maybe I'm using the wrong search terms for google to tell me the answer, but here is my situation.
I have a script that I want to run, but I want it to run only when scheduled and only one at a time. (can't run the script simultaneously)
Now the sticky part is that say I have a table called "myhappyschedule" which has the data I need and the scheduled time. This table can have multiple scheduled times even at the same time, each one would run this script. So essentially I need a queue of each time the script fires and they all need to wait for each one before it to finish. (sometimes this can take just a minute for the script to execute sometimes its many many minutes)
What I'm thinking about doing is making a script that checks myhappyschedule every 5 min and gathers up those that are scheduled, puts them into a queue where another script can execute each 'job' or occurrence in the queue in order. Which all of this sounds messy.
To make this longer - I should say that I'm allowing users to schedule things in myhappyschedule and not edit crontab.
What can be done about this? File locks and scripts calling scripts?
add a column exec_status to myhappytable (maybe also time_started and time_finished, see pseudocode)
run the following cron script every x minutes
pseudocode of cron script:
[create/check pid lock (optional, but see "A potential pitfall" below)]
get number of rows from myhappytable where (exec_status == executing_now)
if it is > 0, exit
begin loop
get one row from myhappytable
where (exec_status == not_yet_run) and (scheduled_time <= now)
order by scheduled_time asc
if no such row, exit
set row exec_status to executing_now (maybe set time_started to now)
execute whatever command the row contains
set row exec_status to completed
(maybe also store the command output/return as well, set time_finished to now)
end loop
[delete pid lock file (complementary to the starting pid lock check)]
This way, the script first checks if none of the commands is running, then runs first not-yet run command, until there are no more commands to be run at the given moment. Also, you can see what command is executing by querying the database.
A potential pitfall: if the cron script is killed, a scheduled task will remain in "executing_now" state. That's what the pid lock at beginning and end is for: to see if the cron script terminated properly. pseudocode of create/check pidlock:
if exists pidlockfile then
check if process id given in file exists
if not exists then
update myhappytable set exec_status = error_cronscript_died_while_executing_this
where exec_status == executing_now
delete pidlockfile
else (previous instance still running)
exit
endif
endif
create pidlockfile containing cron script process id
You can use the at(1) command inside your script to schedule its next run. Before it exits, it can check myhappyschedule for the next run time. You don't need cron at all, really.
I came across this question while researching for a solution to the queuing problem. For the benefit of anyone else searching here is my solution.
Combine this with a cron that starts jobs as they are scheduled (even if they are scheduled to run at the same time) and that solves the problem you described as well.
Problem
At most one instance of the script should be running.
We want to cue up requests to process them as fast as possible.
ie. We need a pipeline to the script.
Solution:
Create a pipeline to any script. Done using a small bash script (further down).
The script can be called as
./pipeline "<any command and arguments go here>"
Example:
./pipeline sleep 10 &
./pipeline shabugabu &
./pipeline single_instance_script some arguments &
./pipeline single_instance_script some other_argumnts &
./pipeline "single_instance_script some yet_other_arguments > output.txt" &
..etc
The script creates a new named pipe for each command. So the above will create named pipes: sleep, shabugabu, and single_instance_script
In this case the initial call will start a reader and run single_instance_script with some arguments as arguments. Once the call completes, the reader will grab the next request off the pipe and execute with some other_arguments, complete, grab the next etc...
This script will block requesting processes so call it as a background job (& at the end) or as a detached process with at (at now <<< "./pipeline some_script")
#!/bin/bash -Eue
# Using command name as the pipeline name
pipeline=$(basename $(expr "$1" : '\(^[^[:space:]]*\)')).pipe
is_reader=false
function _pipeline_cleanup {
if $is_reader; then
rm -f $pipeline
fi
rm -f $pipeline.lock
exit
}
trap _pipeline_cleanup INT TERM EXIT
# Dispatch/initialization section, critical
lockfile $pipeline.lock
if [[ -p $pipeline ]]
then
echo "$*" > $pipeline
exit
fi
is_reader=true
mkfifo $pipeline
echo "$*" > $pipeline &
rm -f $pipeline.lock
# Reader section
while read command < $pipeline
do
echo "$(date) - Executing $command"
($command) &> /dev/null
done