http://qs1969.pair.com?node_id=32210

myocom has asked for the wisdom of the Perl Monks concerning the following question:

From the "I'm probably making this harder than it has to be..." department:

I have a script that I'm running on 80ish machines (on Windows 2000), and I'd like it to run at a certain time (namely 8:00AM). Normally, I'd call it through 'AT.EXE' and call it good. Buuuut, AT.EXE (in Win2K) doesn't run as the currently-logged-in user. I know I can change that, but it doesn't appear to be programmatically changeable (and I don't want to go around to all of these machines). So I'm looking for a more Perlish solution.

Sleep would be perfectly acceptable if I could determine how long it was between "now" (whenever that is) and 8:00AM the following morning. (Note: Each machine will have its OS reinstalled every night anyway, so it only needs to run once--at 8:00AM).

God save me from having to use Date::Calc.


Ideas?

Replies are listed 'Best First'.
Re: How long 'tween now and then?
by reptile (Monk) on Sep 13, 2000 at 03:49 UTC

    Time::Local has a function called timelocal (surprise) that converts information like that returned from localtime back into epoch seconds (like that returned from time). Consider this:

    # get the epoch seconds for 8:00am the following day # NOTE if it's after midnight this doesn't work but # should be trivial to fix my $now = time(); my ($mday, $mon, $year) = (localtime($now))[3,4,5]; my $then = timelocal(0, 0, 8, $mday + 1, $mon, $year); my $difference = $then - $now; sleep($difference);

    That should sleep until 8am the following day, but if you start that after midnight and before 8am, it won't work quite right. A little logic can fix that (add 1 to $mday unless the current hour is less than 8). Also it gets even more complicated if it's the last day of the month. That will break this completely and requires a lot more work to check for. And what if it's the last day of the year too? You'll need to check for that. Ok, so you set all that up too, but it's Februrary 28th on a leap year. Oops broke again.

    In other words, it ain't easy. Maybe you should use Date::Calc.

    Update I thought of something that might work also that doesn't involve these kinds of calculations. Sleep for 1 second until it's 8am, perhaps like so:

    sleep(1) until ((localtime)[2] == 8); # it's now 8:00am.. do your thing.

    Just an idea.

    local $_ = "0A72656B636148206C72655020726568746F6E41207473754A"; while(s/..$//) { print chr(hex($&)) }

      A most interesting update, though it'd chew too much CPU time. Just as I was falling asleep last night, it occured to me to loop through sleeping for an hour or so until the day changes, then figure the seconds after that (which would get rid of the 'exceptions' like end of month/year).

      Since this will all be compiled into an EXE (for the sake of ease), the fewer big ol' modules like Date::Calc I have to include, the better. Something just bugs me about adding several hundred K to an EXE just to figure out what time it is...

RE: How long 'tween now and then?
by extremely (Priest) on Sep 13, 2000 at 06:28 UTC

    Ouch I've been mucking with time in seconds vileness for a few days myself. Try this out:

    use Time::Local; my $now=time(); my (@t)=localtime($now); # 8 am today my $next8am=timelocal(0,0,8,$t[3],$t[4],$t[5]+1900); # if after 8am today, add one day in seconds. $next8am+=86400 if ($next8am<$now); sleep ($next8am-$now);

    Honestly tho, you'll feel better in the end if you use Date::Calc =) If not $now then $later =P

    --
    $you = new YOU;
    honk() if $you->love(perl)

Re: How long 'tween now and then?
by chromatic (Archbishop) on Sep 13, 2000 at 04:02 UTC
    If you can keep a tiny little data file around, you only need to figure out the epoch seconds for one invocation of the script. Just add (60 * 60 * 24) seconds to it once a day, and generate the new sleep value with a simple subtraction statement.

    I'd do something like:

    # run operation # get timestamp from file # add 60 * 60 * 24 to it # write new timestamp to file # subtract current seconds from timestamp # sleep for result seconds
RE: How long 'tween now and then?
by Adam (Vicar) on Sep 13, 2000 at 04:18 UTC
    On Win2k you can schedule things using the scheduler (the GUI version of AT.EXE, which for some dumb reason, is not the same thing.) Open Explorer to your computer and you should see a folder called "Scheduled Tasks" at the same level as the drives. There you can click "add task" and be greated by the schedule wizard. You can browse to your script from there and specify the username to run it under.

    I know its not very Perl-ish, but its the most straight forward MS-ish way to do it.

      Ah, but that goes against one of the fundamental requirements for this: It must be automated. The Task Scheduler isn't automated (nor does it provide a scriptable interface).

      FWIW, just for the sake of completeness, AT.EXE in Win2K actually adds "special" Scheduled Tasks. If you modify them from Task Scheduler's GUI, they lose their 'AT-ness' (that is, you can no longer manipulate them from AT).

        ooops. Missed the '80 computers' part... :-)

        looking at what passes for documentation for at.exe I don't see anything specifying the user. That's no good. But I do see that you can specify the computername, which makes me wonder... how does AT verify that I have permission to run this program on that other computer? My only guess is that it uses WinAuthentication using my credentials. Which makes me think that unless you specify the process to be interactive, it will run under the username of the person who called AT. Its a big jump, but you might want to test it out... set a job for five minutes from now and then log out and log in as someone else and wait to see what happens. Just a thought.

RE: How long 'tween now and then?
by d4vis (Chaplain) on Sep 13, 2000 at 19:14 UTC
    There is a command in Win2K called 'Runas'.
    Do a 'runas /?' for the specifics, but I've used it often to call the 'at' command under a different name.
    Not a Perl solution, true, but I use it to run my Perl scripts.
    Hope that helps.

    ~d4vis
    #!/usr/bin/fnord

      Actually, I investigated going that route as well, but you can't programmatically (through redirects or echo 'n' pipe) send the account password to 'runas' anymore.

      *sigh*

RE: How long 'tween now and then?
by t0mas (Priest) on Sep 15, 2000 at 17:53 UTC
    Buuuut, AT.EXE (in Win2K) doesn't run as the currently-logged-in user. I know I can change that, but it doesn't appear to be programmatically changeable (and I don't want to go around to all of these machines).

    But yes it is programmatically changeable. The following is a snippet of a hack I did...
    #!/usr/bin/perl -w # Modules use strict; use Win32::API; # Variables use vars qw($host $user $password $success $OpenSCManager $LockService +Database); use vars qw($lock $OpenService $ChangeServiceConfig $SChandle $Shandle +); use vars qw($UnlockServiceDatabase $CloseServiceHandle); # Constants use constant SC_MANAGER_ALL_ACCESS => 0x000F0000|0x0001|0x0002 |0x0004|0x0008|0x0010|0x0020; use constant SERVICE_CHANGE_CONFIG => 0x0002; use constant SERVICE_NO_CHANGE => 0xffffffff; # Change theese... $host="SOME_COMPUTER"; $user=".\\local_user_account"; $password="password_for_user"; # Import stuff $OpenSCManager = new Win32::API( "advapi32", "OpenSCManager", ["P", "N", "N"], "I"); if (! defined $OpenSCManager) { die "Can't import OpenSCManager"; } $LockServiceDatabase = new Win32::API( "advapi32", "LockServiceDatabase", ["I"], "I"); if (! defined $LockServiceDatabase) { die "Can't import LockServiceDatabase"; } $OpenService = new Win32::API( "advapi32", "OpenService", ["I", "P", "N"], "I"); if (! defined $OpenService) { die "Can't import OpenService"; } $ChangeServiceConfig = new Win32::API( "advapi32", "ChangeServiceConfig", ["I", "N", "N", "N", "P", "P", "N", "P", "P", "P", "P"], "N"); if (! defined $ChangeServiceConfig) { die "Can't import ChangeServiceConfig"; } $UnlockServiceDatabase = new Win32::API( "advapi32", "UnlockServiceDatabase", ["I"], "I"); if (! defined $UnlockServiceDatabase) { die "Can't import UnlockServiceDatabase"; } $CloseServiceHandle = new Win32::API( "advapi32", "CloseServiceHandle", ["I"], "I"); if (! defined $CloseServiceHandle) { die "Can't import CloseServiceHandle"; } $SChandle=$OpenSCManager->Call( $host, 0, SC_MANAGER_ALL_ACCESS); die "Failed to open service manager on $host" unless $SChandle; $lock=$LockServiceDatabase->Call( $SChandle); die "Failed to lock service database on $host" unless $lock; $Shandle=$OpenService->Call( $SChandle, "Schedule", SERVICE_CHANGE_CONFIG); die "Failed to open Task Scheduler on $host" unless $Shandle; $success=$ChangeServiceConfig->Call( $Shandle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, $user, $password, "Task Scheduler"); print "Change status: "; print $success?"OK\n":"Failed\n"; $UnlockServiceDatabase->Call( $lock); $CloseServiceHandle->Call( $SChandle);


    /brother t0mas
RE: How long 'tween now and then? (Solved!)
by myocom (Deacon) on Oct 07, 2000 at 03:25 UTC

    So I ended up using Date::Manip since we wanted to easily correct for holidays, weekends, etc.

    use strict; use Date::Manip; &Date_Init("PersonalCNF=holidays.cnf","TZ=PST8PDT"); my $now = &ParseDate("now"); my $date = &ParseDate($ARGV[0]); $date = &Date_NextWorkDay(&ParseDate($date),0) unless $ARGV[1]; # Anything past the first argument acts as an "override" print &UnixDate($date,"Sleeping until %T on %F.\n"); my $delta = &DateCalc($now,$date); my $secs = &Delta_Format($delta,0,"%sh"); if ($secs < 0) { print "Hey, that's in the past--you can't fool me!\n" +; exit; } print "(that's $secs seconds...)\n"; sleep $secs;

    So now one can feed it nearly any date/time format with the following caveats: Days of the week are considered to be days of the *current* week, so you should always use "next <day of week>". Also, it will automagically skip holidays/weekends unless you specify something (anything) as other arguments (i.e., scalar @ARGV > 1).

    The reference to holidays.cnf is a customized Date::Manip configuration file with our business holidays in it.

    As a little test matrix, I ran the following (with the actual sleeping part commented out, naturally):

    C:\sleep>for /F "tokens=*" %x in (testmatrix.txt) do sleepuntil %x
    
    C:\sleep>sleepuntil "8:00 next monday"
    Sleeping until 08:00:00 on Monday, October  9, 2000.
    (that's 228992 seconds...)
    
    C:\sleep>sleepuntil "14:36:24 tomorrow"
    Sleeping until 14:36:24 on Monday, October  9, 2000.
    (that's 252775 seconds...)
    
    C:\sleep>sleepuntil "tomorrow" really
    Sleeping until 16:23:29 on Saturday, October  7, 2000.
    (that's 86400 seconds...)
    
    C:\sleep>sleepuntil "4th thursday in november"
    Sleeping until 00:00:00 on Monday, November 27, 2000.
    (that's 4433790 seconds...)
    
    C:\sleep>sleepuntil "4th thursday in november" even if it's Thanksgiving
    Sleeping until 00:00:00 on Thursday, November 23, 2000.
    (that's 4088190 seconds...)
    
    C:\sleep>sleepuntil "yesterday"
    Sleeping until 16:23:31 on Thursday, October  5, 2000.
    Hey, that's in the past--you can't fool me!
    
    C:\sleep>sleepuntil "2pm Dec 1, 2006"
    Sleeping until 14:00:00 on Friday, December  1, 2006.
    (that's 194132189 seconds...)
    

    ...and as you can see, it works beautimously. :-)

(jcwren) RE: How long 'tween now and then?
by jcwren (Prior) on Sep 13, 2000 at 23:53 UTC
    I was just looking through the MSDN library, and there's an entire chapter on the Task Scheduler. It looks plenty feasible to use Win32::API to write an interface to it (Heh. Or here's your chance to use Inline).

    It looks like you can also setup to run tasks on remote system, assuming you have sufficient priviledge levels (SetTargetComputer), priorities (ITask::SetPriority), etc.

    The advantage of using the scheduler is that a user does not have to be logged in for the task to run, merely the computer at a login screen. While I advocate both Windows and Linux alike, one area that Linux is *clearly* superior in is the cron facility. I wish Windows had a scheduler like cron...

    Some keywords for MSDN are ITask, ITaskScheduler, and IScheduledWorkItem.

    --Chris

    e-mail jcwren
Re: How long 'tween now and then?
by Fastolfe (Vicar) on Sep 14, 2000 at 20:56 UTC
    I agree with the other posters about using something already built into Windows, but Date::Manip also lets you do this fairly nicely:
    use Date::Manip; my $now = time(); my $next = &UnixDate(&ParseDate("8am"), "%s"); $next += 24 * 60 * 60 if $next < $now; sleep($next - $now);
    ParseDate() parses the "8am", and UnixDate() with the "%s" format gives us the number we want. We jump ahead 24 hours if the time parsed is before the current time. A more correct way to do this would be to re-do the ParseDate with "tomorrow at 8am" instead of "8am", since DST could muck this up. I can't find a way to tell ParseDate to use the next future occurrence of "8am" instead of assuming "today at 8am" without messing up the pre-8am case.

    ParseDate is also somewhat slow.