#c:\Perl\bin\perl.exe -w #----------------------------------------------------------------------------# # sync2nas.pl # # $Id: Sync2nas.pl,v 1.38 2002/03/13 22:04:39 nic Exp $ # Copyright 2002 by Nicolas Simonds # Authors: Nicolas Simonds, Paul Sustman # # Redistribution and use in source and binary forms, with or without modi- # fication, are permitted provided that the following conditions are met: # # Redistributions of source code must retain the above copyright notice # this list of conditions and the following disclaimer. # # Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # Neither the name of Nicolas Simonds nor the names of contributors # may be used to endorse or promote products derived from this # software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Future Wish/Feature/Defect List # # - check for existence of scheduled tasks; if not available bail gracefully # - change selected folders font to same as dirtree # - verify button to verify rsync login/password/ip # - show total size of folders to be replicated as summary # - allow support for more revisions. up to 10 or maybe infinite # - allow 7 day incremental for daily replication in folders by DOW # - use event logger to record replication passes # #----------------------------------------------------------------------------# # Included Library Modules #----------------------------------------------------------------------------# use strict; use Net::Ping; use Carp::Heavy; use Socket; use Cwd qw(chdir getcwd); use File::Basename; use Tk; use Tk::Menu; use Tk::Menubutton; use Tk::Radiobutton; use Tk::Radiobutton; use Tk::DirTree; use Tk::Scrollbar; use Tk::Listbox; use Tk::Entry; use Tk::Pixmap; use Tk::DirSelect; use Tk::Canvas; use Tk::DirTree; use Tk::Label; use Tk::Radiobutton; use Tk::Button; use Tk::Optionmenu; use Tk::Event; use Tk::Icon; use Tk::Checkbutton; use Win32; use Win32::API; use Win32::Internet; use Win32::Process; use Win32::TaskScheduler; use Win32::TieRegistry; #----------------------------------------------------------------------------# # Global data #----------------------------------------------------------------------------# $main::entryTaskLogin ||= getTaskLogin() if Win32::IsWinNT(); # fatal error in Win9x $main::entryRsyncOpts ||= '-av --delete'; $main::entryRemoteDir ||= Win32::NodeName(); ($main::Version = '$Revision: 1.38 $') =~ s/^..evision: ([\d\.]*?) \$$/$1/; my ($pgm,) = fileparse($0, qr/\.[^.]*/); my $homedir = Win32::GetShortPathName(getcwd()); my $rsync = Win32::GetShortPathName("$homedir/cygwin/rsync.exe"); my @dow = qw( Sun Mon Tue Wed Thu Fri Sat ); my @timeunits = qw( hours minutes ); my $info = 'Initialized'; my $error = 'Initialized'; my ($nowButton, $hourlyButton, $dailyButton, $weeklyButton, $tempHourly, $tempDaily, $tempWeekly); my $registry = new Win32::TieRegistry('', { Delimiter => '/' }) or do { Win32::MsgBox("Error connecting to registry: $^E\n\nProgram will now exit.", MB_ICONSTOP(), 'Fatal Error'); exit; }; my $reg = $registry->TiedRef(); $reg->{"HKEY_CURRENT_USER/Software/$pgm/"} = { () } unless defined $reg->{"HKEY_CURRENT_USER/Software/$pgm/"}; #----------------------------------------------------------------------------# # MainWindow #----------------------------------------------------------------------------# my $mw = MainWindow->new(-title => "$pgm - Windows to NAS Replication"); $mw->geometry("760x580"); $mw->resizable(0, 0); $mw->setIcon(-file => Win32::GetShortPathName(canonPath($0))); $mw->Label(-textvariable => \$info, -relief => 'ridge')->pack(-side => 'bottom', -fill => 'x'); #----------------------------------------------------------------------------# # Create main canvas #----------------------------------------------------------------------------# my $c = $mw->Canvas(); $c->configure( -width => '850', -height => '680', -relief => 'sunken', -bd => 2, ); $c->pack(-expand => 'yes', -fill => 'both',-side => 'top'); my $entryframe = $c->Frame; my $c_win = create $c 'window', '0', '0', -window => $entryframe, -anchor => 'nw'; #----------------------------------------------------------------------------# # Define Entry layouts and later put them in canvas #----------------------------------------------------------------------------# my $unameEntry = $c->Entry(-textvariable => \$main::entryUserName, -state => 'disabled', -background => 'gray71', -selectbackground => "gray61", -selectforeground => "white"); my $rsyncEntry = $c->Entry(-textvariable => \$main::entryRsyncOpts, -state => 'disabled', -background => 'gray71', -selectbackground => "gray61", -selectforeground => "white"); my $tloginEntry = $c->Entry(-textvariable => \$main::entryTaskLogin); my $tpwdEntry = $c->Entry(-textvariable => \$main::entryWinPwd, -show => '*', -exportselection => 0); #----------------------------------------------------------------------------# # Put menu in window #----------------------------------------------------------------------------# my $menubar = $c->Frame(-relief => "raised", -borderwidth => 2); #----------------------------------------------------------------------------# # File Menu #----------------------------------------------------------------------------# my $filebutton = $menubar->Menubutton(-text => "File", -underline => 0); my $filemenu = $filebutton->Menu(-tearoff => 0); $filemenu->command(-command => \&clearConfiguration, -label => "Clear saved configuration", -underline => 0); $filemenu->command(-command => \&loadConfiguration, -label => "Load configuration", -underline => 0); $filemenu->command(-command => \&saveConfiguration, -label => "Save configuration", -underline => 0); $filemenu->separator; $filemenu->command(-command => \&pingHost, -label => "Ping appliance", -underline => 0); $filemenu->command(-command => \&goLookup, -label => "Lookup Tricord appliance", -underline => 7); $filemenu->command(-command => \&clearSchedTasks, -label => "Clear all scheduled tasks", -underline => 6); $filemenu->separator; $filemenu->command(-command => sub { $main::rbRunNow=1; $hourlyButton->deselect(); $dailyButton->deselect(); $weeklyButton->deselect(); $tempHourly = $main::entrySchedHourly if ($main::entrySchedHourly); $tempDaily = $main::entrySchedDaily if ($main::entrySchedDaily); $tempWeekly = $main::entrySchedWeekly if ($main::entrySchedWeekly); $main::entrySchedHourly = ''; $main::entrySchedDaily = ''; $main::entrySchedWeekly = ''; &applySched; }, -label => "Replicate once immediately", -underline => 0); $filemenu->separator; $filemenu->command(-command => \&exit, -label => "Exit", -underline => 1); $filebutton->configure(-menu => $filemenu); $mw->bind('', sub { my @window_geom = split /[+x]/, $mw->geometry(); my @canvas_geom = split /[+x]/, $c->geometry(); my @menubar_geom = split /[+x]/, $menubar->geometry(); my @filebut_geom = split /[+x]/, $filebutton->geometry(); my $x_offset = $window_geom[2] + $filebut_geom[2]; my $y_offset = $window_geom[3] + ($window_geom[1] - ($canvas_geom[1] - $menubar_geom[1]) + $filebut_geom[3]); $filebutton->menu->post($x_offset, $y_offset); }); #----------------------------------------------------------------------------# # Advanced menu. #----------------------------------------------------------------------------# my $advancedbutton = $menubar->Menubutton(-text => "Advanced", -underline => 0); my $advancedmenu = $advancedbutton->Menu(-tearoff => 0); my ($menuUserName, $menuRsyncOptions, $menuUserNameState, $menuRsyncOptionsState); $menuUserName = $advancedmenu->command( -command => sub { if ($menuUserNameState == 0) { $unameEntry->configure( -background => 'white', -state => 'normal'); $menuUserName->configure(-label => 'Disable User Name Field', -underline => 8); $menuUserNameState = 1; } else { $unameEntry->configure(-background => 'gray71', -state => 'disabled'); $menuUserName->configure(-label => 'Enable User Name Field', -underline => 7); $menuUserNameState = 0; } }, -label => 'Enable User Name Field', -underline => 7); $menuRsyncOptions = $advancedmenu->command( -command => sub { if ($menuRsyncOptionsState == 0) { $rsyncEntry->configure( -background => 'white', -state => 'normal'); $menuRsyncOptions->configure(-label => 'Disable Rsync Options Field', -underline => 8); $menuRsyncOptionsState = 1; } else { $rsyncEntry->configure( -background => 'gray71', -state => 'disabled'); $menuRsyncOptions->configure(-label => 'Enable Rsync Options Field', -underline => 7); $menuRsyncOptionsState = 0; } }, -label => 'Enable Rsync Options Field', -underline => 7); $advancedbutton->configure(-menu => $advancedmenu); $mw->bind('', sub { my @window_geom = split /[+x]/, $mw->geometry(); my @canvas_geom = split /[+x]/, $c->geometry(); my @menubar_geom = split /[+x]/, $menubar->geometry(); my @advbut_geom = split /[+x]/, $advancedbutton->geometry(); my $x_offset = $window_geom[2] + $advbut_geom[2]; my $y_offset = $window_geom[3] + ($window_geom[1] - ($canvas_geom[1] - $menubar_geom[1]) + $advbut_geom[3]); $advancedbutton->menu->post($x_offset, $y_offset); }); #----------------------------------------------------------------------------# # Help menu. #----------------------------------------------------------------------------# my $helpbutton = $menubar->Menubutton(-text => "Help", -underline => 0); my $helpmenu = $helpbutton->Menu(-tearoff => 0); $helpmenu->command(-command => sub { shellExecute("${homedir}/html/help.html") }, -label => "Replication Help", -underline => 0); $helpmenu->separator; $helpmenu->command(-command => \&helpAbout, -label => "About $pgm", -underline => 0); $helpbutton->configure(-menu => $helpmenu); $mw->bind('', sub { my @window_geom = split /[+x]/, $mw->geometry(); my @canvas_geom = split /[+x]/, $c->geometry(); my @menubar_geom = split /[+x]/, $menubar->geometry(); my @helpbut_geom = split /[+x]/, $helpbutton->geometry(); my $x_offset = $window_geom[2] + $helpbut_geom[2]; my $y_offset = $window_geom[3] + ($window_geom[1] - ($canvas_geom[1] - $menubar_geom[1]) + $helpbut_geom[3]); $helpbutton->menu->post($x_offset, $y_offset); }); # Pack most Menubuttons from the left. $filebutton->pack(-side => "left"); $advancedbutton->pack(-side => "left"); $helpbutton->pack(-side => "left"); $menubar->pack(-side => "top", -fill => "x"); #----------------------------------------------------------------------------# # Upper-Left DirSelect #----------------------------------------------------------------------------# populateDirs($c); my $srcLabel = $c->Label(-text => "Selected Folders to Replicate"); create $c 'window', '430', 109, -window => $srcLabel, -anchor => 'w'; my $lb = $c->Scrolled('Listbox', -selectmode => 'extended', -scrollbars => 'osoe', -background => 'white', -selectbackground => "gray61", -selectforeground => "white", -width => 40, -height => 7); create $c 'window', 430, 200, -window => $lb, -anchor => 'w'; #----------------------------------------------------------------------------# # Lower-Left NAS Destination Configuration #----------------------------------------------------------------------------# my $start=340; my $schedpos=430; # Vertical positioning for scheduling $c->createLine (20, ($start - 20), 350, ($start - 20)); my $dstLabel = $c->Label(-text => "Destination NAS Rsync Configuration"); create $c 'window', '20', ($start - 32), -window => $dstLabel, -anchor => 'w'; #my $statusbar = $c->Label(-text => "Status Bar"); #create $c 'window',20,497, -window => $statusbar, -anchor => 'w'; #----------------------------------------------------------------------------# # Appliance #----------------------------------------------------------------------------# my $appLabel = $c->Label(-text => "Appliance\nor Virtual IP:"); my $appEntry = $c->Entry(-textvariable => \$main::entryAppliance, -background => 'white', -selectbackground => "gray61", -selectforeground => "white"); my $appLookup = $c->Button(-text => "Ping", -width => 7, -command => sub { pingHost() }); create $c 'window', '100', $start, -window => $appLabel, -anchor => 'e'; create $c 'window', '105', $start, -window => $appEntry, -anchor => 'w'; create $c 'window', '305', $start, -window => $appLookup, -anchor => 'c'; #----------------------------------------------------------------------------# # ShareName #----------------------------------------------------------------------------# my $shareLabel = $c->Label(-text => "Share Name:"); my $shareEntry = $c->Entry(-textvariable => \$main::entryShareName, -background => 'white', -selectbackground => "gray61", -selectforeground => "white"); create $c 'window', '100', ($start + 25), -window => $shareLabel, -anchor => 'e'; create $c 'window', '105', ($start + 25), -window => $shareEntry, -anchor => 'w'; $shareEntry->bind('', sub { $main::entryUserName = $main::entryShareName if ($unameEntry->cget(-state) eq 'disabled'); $unameEntry->update();}); #----------------------------------------------------------------------------# # DestinationDir #----------------------------------------------------------------------------# my $remotedirLabel = $c->Label(-text => "Remote Folder"); my $remotedirEntry = $c->Entry(-textvariable => \$main::entryRemoteDir, -state => 'normal', -background => 'white', -selectbackground => "gray61", -selectforeground => "white"); create $c 'window', '100', ($start + 50), -window => $remotedirLabel, -anchor => 'e'; create $c 'window', '105', ($start + 50), -window => $remotedirEntry, -anchor => 'w'; #----------------------------------------------------------------------------# # UserName #----------------------------------------------------------------------------# my $unameLabel = $c->Label(-text => "User Name:"); create $c 'window', '100', ($start + 75), -window => $unameLabel, -anchor => 'e'; create $c 'window', '105', ($start + 75), -window => $unameEntry, -anchor => 'w'; #----------------------------------------------------------------------------# # Password #----------------------------------------------------------------------------# my $passwdLabel = $c->Label(-text => "Password:"); my $passwdEntry = $c->Entry(-textvariable => \$ENV{'RSYNC_PASSWORD'}, -show => '*', -exportselection => 0, -background => 'white', -selectbackground => "gray61", -selectforeground => "white"); create $c 'window', '100', ($start + 100), -window => $passwdLabel, -anchor => 'e'; create $c 'window', '105', ($start + 100), -window => $passwdEntry, -anchor => 'w'; $passwdEntry->configure(-background => 'gray71', -state => 'disabled') unless Win32::IsWinNT(); #----------------------------------------------------------------------------# # Rsync Options #----------------------------------------------------------------------------# my $rsyncLabel = $c->Label(-text => "Rsync Options:"); create $c 'window', '100', ($start + 125), -window => $rsyncLabel, -anchor => 'e'; create $c 'window', '105', ($start + 125), -window => $rsyncEntry, -anchor => 'w'; #----------------------------------------------------------------------------# # More Rsync Options #----------------------------------------------------------------------------# my $rsyncOptButton1 = $c->Checkbutton(-text => "Save one extra revision of your files on destination NAS", -variable => \$main::cbRsyncButton1, -command => sub { if ($main::cbRsyncButton1) { $main::entryRsyncOpts .= " -b --suffix='.1'" if ($main::entryRsyncOpts !~ m/--suffix/); } else { $main::entryRsyncOpts =~ s/ -b --suffix='.1'// if ($main::entryRsyncOpts =~ m/--suffix/); }}); create $c 'window','20',$start+155, -window => $rsyncOptButton1, -anchor => 'w'; my $rsyncOptButton2 = $c->Checkbutton(-text => "Compress transferred data. Useful only over WANs.", -variable => \$main::cbRsyncButton2, -command => sub { if ($main::cbRsyncButton2) { $main::entryRsyncOpts .= " -z" if ($main::entryRsyncOpts !~ m/-z/); } else { $main::entryRsyncOpts =~ s/ -z// if ($main::entryRsyncOpts =~ m/-z/); }}); create $c 'window','20',$start+180, -window => $rsyncOptButton2, -anchor => 'w'; #----------------------------------------------------------------------------# # Lower-Right Task Schedule Section #----------------------------------------------------------------------------# $c->createLine ($schedpos, ($start - 20), 740, ($start - 20)); my $srctaskLabel = $c->Label(-text => "Source Replication Task Schedule"); create $c 'window', $schedpos, ($start - 32), -window => $srctaskLabel, -anchor => 'w'; #----------------------------------------------------------------------------# # Schedule Now #----------------------------------------------------------------------------# $nowButton = $c->Radiobutton(-text => "Replicate Once Immediately", -variable => \$main::rbRunNow, -value => "1", -command => sub { $hourlyButton->deselect(); $dailyButton->deselect(); $weeklyButton->deselect(); $tempHourly = $main::entrySchedHourly if ($main::entrySchedHourly); $tempDaily = $main::entrySchedDaily if ($main::entrySchedDaily); $tempWeekly = $main::entrySchedWeekly if ($main::entrySchedWeekly); $main::entrySchedHourly = ''; $main::entrySchedDaily = ''; $main::entrySchedWeekly = ''; $info="Select Apply to start replication of selected Source Folders."; }); create $c 'window', $schedpos, $start, -window => $nowButton, -anchor => 'w'; #----------------------------------------------------------------------------# # Schedule Hourly #----------------------------------------------------------------------------# $hourlyButton = $c->Radiobutton(-text => "Replicate every", -variable => \$main::rbRunHourly, -value => "2", -command => sub { $nowButton->deselect(); $dailyButton->deselect(); $weeklyButton->deselect(); if ($tempHourly) { $main::entrySchedHourly = $tempHourly; } else { $tempHourly = $main::entrySchedHourly; $main::entrySchedHourly = ''; } $tempDaily = $main::entrySchedDaily if ($main::entrySchedDaily); $tempWeekly = $main::entrySchedWeekly if ($main::entrySchedWeekly); $main::entrySchedDaily = ''; $main::entrySchedWeekly = ''; $info="Selected: Schedule Hourly Replication"; }); create $c 'window', $schedpos, ($start + 25), -window => $hourlyButton, -anchor => 'w'; my $hourlyEntry = $c->Entry(-textvariable => \$main::entrySchedHourly, -width => 2); create $c 'window', ($schedpos + 130), ($start + 25), -window => $hourlyEntry, -anchor => 'w'; my $timeunitsmenu = $c->Optionmenu (-options => \@timeunits, -textvariable => \$main::optionMenuUnits); create $c 'window',$schedpos+163,$start+25, -window => $timeunitsmenu, -width=> 90, -height=>25, -anchor => 'w'; #----------------------------------------------------------------------------# # Schedule Daily #----------------------------------------------------------------------------# $dailyButton = $c->Radiobutton(-text => "Replicate Daily at", -variable => \$main::rbRunDaily, -value => "3", -command => sub { $nowButton->deselect(); $hourlyButton->deselect(); $weeklyButton->deselect(); if ($tempDaily) { $main::entrySchedDaily = $tempDaily if ($tempDaily); } else { $tempDaily = $main::entrySchedDaily; $main::entrySchedDaily = ''; } $tempHourly = $main::entrySchedHourly if ($main::entrySchedHourly); $tempWeekly = $main::entrySchedWeekly if ($main::entrySchedWeekly); $main::entrySchedHourly = ''; $main::entrySchedWeekly = ''; }); create $c 'window', $schedpos, ($start + 50), -window => $dailyButton, -anchor => 'w'; my $dailyEntry = $c->Entry(-textvariable => \$main::entrySchedDaily, -width => 5); create $c 'window', ($schedpos + 140), ($start + 50), -window => $dailyEntry, -anchor => 'w'; #----------------------------------------------------------------------------# # Schedule Weekly #----------------------------------------------------------------------------# $weeklyButton = $c->Radiobutton(-text => "Replicate Weekly at", -variable => \$main::rbRunWeekly, -value => "4", -command => sub { $nowButton->deselect(); $hourlyButton->deselect(); $dailyButton->deselect(); if ($tempWeekly) { $main::entrySchedWeekly = $tempWeekly if ($tempWeekly); } else { $tempWeekly = $main::entrySchedWeekly; $main::entrySchedWeekly = ''; } $tempHourly = $main::entrySchedHourly if ($main::entrySchedHourly); $tempDaily = $main::entrySchedDaily if ($main::entrySchedDaily); $main::entrySchedHourly = ''; $main::entrySchedDaily = ''; }); create $c 'window', $schedpos, ($start + 75), -window => $weeklyButton, -anchor => 'w'; my $weeklyEntry = $c->Entry(-textvariable => \$main::entrySchedWeekly, -width => 5); create $c 'window', ($schedpos + 155), ($start + 75), -window => $weeklyEntry, -anchor => 'w'; my $weeklyLabel2 = $c->Label(-text => "on"); create $c 'window', ($schedpos + 200), ($start + 75), -window => $weeklyLabel2, -anchor => 'w'; my $dowmenu = $c->Optionmenu (-options => \@dow, -textvariable => \$main::optionMenuDOW); create $c 'window', ($schedpos + 225), ($start + 75), -window => $dowmenu, -height=>25, -anchor => 'w'; #----------------------------------------------------------------------------# # Task Scheduler Login #----------------------------------------------------------------------------# my $taskLabel = $c->Label(-text => "Windows Scheduled Tasks"); my $tloginLabel = $c->Label(-text => "Task Login:"); create $c 'window', $schedpos, ($start + 110), -window => $taskLabel, -anchor => 'w'; create $c 'window', ($schedpos + 20), ($start + 135), -window => $tloginLabel, -anchor => 'w'; create $c 'window', ($schedpos + 125), ($start + 135), -window => $tloginEntry, -anchor => 'w'; $tloginEntry->configure(-background => 'gray71', -state => 'disabled') unless Win32::IsWinNT(); #----------------------------------------------------------------------------# # Task Scheduler Password #----------------------------------------------------------------------------# my $tpwdLabel = $c->Label(-text => "Task Password:"); create $c 'window', ($schedpos + 20), ($start + 160), -window => $tpwdLabel, -anchor => 'w'; create $c 'window', ($schedpos + 125), ($start + 160), -window => $tpwdEntry, -anchor => 'w'; $tpwdEntry->configure(-background => 'gray71', -state => 'disabled') unless Win32::IsWinNT(); #----------------------------------------------------------------------------# # Schedule Apply #----------------------------------------------------------------------------# my $applyButton = $c->Button(-text => "Apply", -width => 7, -command => sub { applySched(); }); $info="Selected: Schedule Daily Replication"; my $clearButton = $c->Button( -text => "Clear", -width => 7, -command => sub { $nowButton->deselect(); $hourlyButton->deselect(); $dailyButton->deselect(); $weeklyButton->deselect(); $main::entrySchedWeekly = ''; $main::entrySchedDaily = ''; $main::entrySchedHourly = ''; $tempDaily = ''; $tempWeekly = ''; $tempHourly = ''; }); create $c 'window', $schedpos, ($start + 195), -window => $applyButton, -anchor => 'w'; create $c 'window', ($schedpos + 150), ($start + 195), -window => $clearButton, -anchor => 'w'; #----------------------------------------------------------------------------# # MainLoop #----------------------------------------------------------------------------# loadConfiguration(); MainLoop; #----------------------------------------------------------------------------# # clearConfiguration #----------------------------------------------------------------------------# sub clearConfiguration { my $key = $reg->{"HKEY_CURRENT_USER/Software/$pgm/"} || die "Umm, $^E\n"; # Out with the old... foreach my $x (keys %{ $key }) { delete $key->{$x} } $info = "Cleared $pgm registry keys"; } #----------------------------------------------------------------------------# # saveConfiguration #----------------------------------------------------------------------------# sub saveConfiguration { my $key = $reg->{"HKEY_CURRENT_USER/Software/$pgm/"} || die "Umm, $^E\n"; # Out with the old... foreach my $x (keys %{ $key }) { delete $key->{$x} } # Let's sort and uniq the Selected Source Folders before saving $main::entrySrcDirs = ""; if ($lb->size) { my %found; $main::entrySrcDirs = join ";", (sort (grep {not $found{$_}++ } ($lb->get(0, 'end')))); } # In with the new... $key->{'/entrySrcDirs'} = $main::entrySrcDirs if $main::entrySrcDirs; $key->{'/entryShareName'} = $main::entryShareName if $main::entryShareName; $key->{'/entryRemoteDir'} = $main::entryRemoteDir if $main::entryRemoteDir; $key->{'/entryAppliance'} = $main::entryAppliance if $main::entryAppliance; $key->{'/entryUserName'} = $main::entryUserName if $main::entryUserName; $key->{'/rbRunNow'} = $main::rbRunNow if $main::rbRunNow; $key->{'/rbRunDaily'} = $main::rbRunDaily if $main::rbRunDaily; $key->{'/rbRunHourly'} = $main::rbRunHourly if $main::rbRunHourly; $key->{'/rbRunWeekly'} = $main::rbRunWeekly if $main::rbRunWeekly; $key->{'/entrySchedDaily'} = $main::entrySchedDaily if $main::entrySchedDaily; $key->{'/entrySchedHourly'} = $main::entrySchedHourly if $main::entrySchedHourly; $key->{'/entrySchedWeekly'} = $main::entrySchedWeekly if $main::entrySchedWeekly; $key->{'/optionMenuDOW'} = $main::optionMenuDOW if $main::optionMenuDOW; $key->{'/optionMenuUnits'} = $main::optionMenuUnits if $main::optionMenuUnits; $key->{'/entryRsyncOpts'} = $main::entryRsyncOpts if $main::entryRsyncOpts; $key->{'/entryTaskLogin'} = $main::entryTaskLogin if $main::entryTaskLogin; $key->{'/cbRsyncButton1'} = $main::cbRsyncButton1 if $main::cbRsyncButton1; $key->{'/cbRsyncButton2'} = $main::cbRsyncButton2 if $main::cbRsyncButton2; # Save out environment variables we need if (Win32::IsWinNT()) { $reg->{'HKEY_CURRENT_USER/Environment/'}->{'/Rsync_Password'} = $ENV{'RSYNC_PASSWORD'} if $ENV{'RSYNC_PASSWORD'}; my $dir = Win32::GetFullPathName("$homedir/cygwin"); unless (grep { uc($_) eq uc($dir) } (split /;/, $ENV{'PATH'})) { $reg->{'HKEY_CURRENT_USER/Environment/'}->{'/PATH'} .= ";$dir"; $ENV{'PATH'} .= ";$dir"; } $reg->{'HKEY_CURRENT_USER/Environment/'}->Flush(); } $info = 'Saved configuration to registry'; } #----------------------------------------------------------------------------# # loadConfiguration #----------------------------------------------------------------------------# sub loadConfiguration { # Initialize variables from registry my $key = $reg->{"HKEY_CURRENT_USER/Software/$pgm/"} || die "Umm, $^E\n"; $main::entrySrcDirs = $key->{'/entrySrcDirs'} if $key->{'/entrySrcDirs'}; $main::entryShareName = $key->{'/entryShareName'} if $key->{'/entryShareName'}; $main::entryRemoteDir = $key->{'/entryRemoteDir'} if $key->{'/entryRemoteDir'}; $main::entryAppliance = $key->{'/entryAppliance'} if $key->{'/entryAppliance'}; $main::entryUserName = $key->{'/entryUserName'} if $key->{'/entryUserName'}; $main::rbRunNow = $key->{'/rbRunNow'} if $key->{'/rbRunNow'}; $main::rbRunDaily = $key->{'/rbRunDaily'} if $key->{'/rbRunDaily'}; $main::rbRunHourly = $key->{'/rbRunHourly'} if $key->{'/rbRunHourly'}; $main::rbRunWeekly = $key->{'/rbRunWeekly'} if $key->{'/rbRunWeekly'}; $main::entrySchedDaily = $key->{'/entrySchedDaily'} if $key->{'/entrySchedDaily'}; $main::entrySchedHourly = $key->{'/entrySchedHourly'} if $key->{'/entrySchedHourly'}; $main::entrySchedWeekly = $key->{'/entrySchedWeekly'} if $key->{'/entrySchedWeekly'}; $main::optionMenuDOW = $key->{'/optionMenuDOW'} if $key->{'/optionMenuDOW'}; $main::optionMenuUnits = $key->{'/optionMenuUnits'} if $key->{'/optionMenuUnits'}; $main::entryRsyncOpts = $key->{'/entryRsyncOpts'} if $key->{'/entryRsyncOpts'}; $main::entryTaskLogin = $key->{'/entryTaskLogin'} if $key->{'/entryTaskLogin'}; $main::cbRsyncButton1 = $key->{'/cbRsyncButton1'} if $key->{'/cbRsyncButton1'}; $main::cbRsyncButton2 = $key->{'/cbRsyncButton2'} if $key->{'/cbRsyncButton2'}; # Load Source Folders into Listbox $lb $lb->delete(0, 'end'); foreach my $dir (split ';', $main::entrySrcDirs) { $lb->insert('end', $dir); } # Load optionmenu selection $dowmenu->configure(-textvariable => \$main::optionMenuDOW) if ($main::optionMenuDOW); $timeunitsmenu->configure(-textvariable => \$main::optionMenuUnits) if ($main::optionMenuUnits); $main::entryUserName = $main::entryShareName if ($main::entryShareName && !$main::entryUserName); $info = 'Loaded configuration from registry'; } #----------------------------------------------------------------------------# # pingHost #----------------------------------------------------------------------------# sub pingHost { my $host = $main::entryAppliance || return; my $p = Net::Ping->new("icmp"); $info = qq/Remote server "$host" is / . (($p->ping($host)) ? 'alive' : 'unavailable'); $p->close(); } #----------------------------------------------------------------------------# # genRsyncCmdLine #----------------------------------------------------------------------------# sub genRsyncCmdLine { my $dirs = ''; my $opts = ($main::entryRsyncOpts) ? $main::entryRsyncOpts : '-av'; my $stuff2sync = $main::entrySrcDirs; # # On Tricord NAS, sharename = username. Also, prevent saving # entryUserName and not allowing user to modify it. # my $user = ($main::entryUserName) ? $main::entryUserName : $main::entryShareName; foreach my $dir (split ';', $stuff2sync) { $dir =~ s#^(.+?):#/cygdrive/$1#; $dir =~ s#\\#/#g; $dir = qq#"$dir"#; $dirs .= "$dir "; } return qq#$opts $dirs "$user\@${main::entryAppliance}::${main::entryShareName}/${main::entryRemoteDir}"#; } #----------------------------------------------------------------------------# # clearSchedTasks #----------------------------------------------------------------------------# sub clearSchedTasks { my $scheduler = Win32::TaskScheduler->New(); my $nukedTask = 0; my @jobs = $scheduler->Enum(); foreach my $job (@jobs) { next if !($job =~ m/$pgm/); if (!$scheduler->Delete($job)) { $info = qq#Deletion of "$job" scheduled task failed!#; Win32::MsgBox($info, MB_ICONEXCLAMATION(), 'Error'); } else { $nukedTask++; } } if ($nukedTask) { $info = "Cleared $nukedTask Scheduled Tasks created by $pgm"; } else { $info = "No Scheduled Tasks to clear"; } } #----------------------------------------------------------------------------# # checkTime #----------------------------------------------------------------------------# sub checkTime { my $time = shift(@_); if ($time =~ m/:/) { my ($starthour, $startmin) = split ':', $time; if (!($starthour > 0 && $starthour < 24)) { $info = qq#Illegal hour "$starthour". Hours in range 0-23 only.#; return (0); } if (!($startmin >= 0 && $startmin < 60)) { $info = qq#Illegal minutes "$startmin". Minutes in range 0-59 only.#; return (0); } } else { $info = "Illegal time format. Use HH:MM"; return(0); } return(1); } #----------------------------------------------------------------------------# # applySched #----------------------------------------------------------------------------# sub applySched { $main::entrySrcDirs = join ';', $lb->get(0, 'end'); if (! -r $rsync ) { $info = qq#Corrupt installation. "$rsync" not found.#; Win32::MsgBox($info, MB_ICONEXCLAMATION(), 'Corrupt Installation'); return; } if (($main::rbRunNow || $main::rbRunHourly || $main::rbRunDaily || $main::rbRunWeekly) && (!($main::entrySrcDirs))) { $error = "First select a list of Source Folders to\nReplicate from the Directory Chooser."; Win32::MsgBox($error, MB_ICONEXCLAMATION(), 'Error'); return; } if (($main::rbRunNow || $main::rbRunHourly || $main::rbRunDaily || $main::rbRunWeekly) && (!($main::entryTaskLogin && $main::entryWinPwd)) && Win32::IsWinNT()) { $error = "Enter a Task Login and Task Password\nto create a Windows Scheduled Task.\nReplication Skipped."; Win32::MsgBox($error, MB_ICONEXCLAMATION(), 'Error'); return; } if (($main::rbRunHourly) && ($main::entrySchedHourly < 1 || $main::entrySchedHourly > 99)) { $error = qq#Illegal replication interval "$main::entrySchedHourly".\nValue must be in range 1-99#; Win32::MsgBox($error, MB_ICONEXCLAMATION(), 'Error'); return; } if (!($main::entryAppliance)) { $error = "You must enter a qualified appliance name or IP,\nor a valid Virtual Cluster IP for the destination."; Win32::MsgBox($error, MB_ICONEXCLAMATION(), 'Error'); return; } if (!($main::entryShareName)) { $error = "You must enter a valid name of a share on the destination."; Win32::MsgBox($error, MB_ICONEXCLAMATION(), 'Error'); return; } if ($main::entryRemoteDir =~ m#[/\\]#) { $error = "The destination Remote Folder\nmay not contain a slash or blackslash."; Win32::MsgBox($error, MB_ICONEXCLAMATION(), 'Error'); return; } if (!($ENV{'RSYNC_PASSWORD'})) { if (Win32::IsWinNT()) { $error = "You must enter a password to a share\non the destination NAS cluster."; Win32::MsgBox($error, MB_ICONEXCLAMATION(), 'Error'); return; } else { $error = "You must set the environment variable %RSYNC_PASSWORD% for replication to occur unattended.\n\nYou will be prompted for a password otherwise."; Win32::MsgBox($error, MB_ICONEXCLAMATION(), 'Information'); } } saveConfiguration(); my ($sec, $min, $hour, $mday, $mon, $year, ) = localtime(time()); $year += 1900; $mon++; my $scheduler = Win32::TaskScheduler->New(); my %day = ( 'Sun' => $scheduler->TASK_SUNDAY(), 'Mon' => $scheduler->TASK_MONDAY(), 'Tue' => $scheduler->TASK_TUESDAY(), 'Wed' => $scheduler->TASK_WEDNESDAY(), 'Thu' => $scheduler->TASK_THURSDAY(), 'Fri' => $scheduler->TASK_FRIDAY(), 'Sat' => $scheduler->TASK_SATURDAY() ); my %trig = ( 'BeginYear' => $year, 'BeginMonth' => $mon, 'BeginDay' => $mday, 'StartHour' => $hour, 'StartMinute' => $min, ); my %units = ( 'hours' => 60, 'minutes' => 1, ); my $task = "Periodic $pgm"; if ($main::rbRunNow) { $trig{'StartMinute'} = ((localtime)[1] + 1); $trig{'TriggerType'} = $scheduler->TASK_TIME_TRIGGER_ONCE; $task = "One-time $pgm"; } elsif ($main::rbRunDaily) { return if (!checkTime($main::entrySchedDaily)); my ($starthour, $startmin) = split ':', $main::entrySchedDaily, 2; $trig{'StartHour'} = $starthour; $trig{'StartMinute'} = $startmin; $trig{'TriggerType'} = $scheduler->TASK_TIME_TRIGGER_DAILY; $trig{'Type'} = { 'DaysInterval' => 1 }; } elsif ($main::rbRunWeekly) { return if (!checkTime($main::entrySchedWeekly)); my ($starthour, $startmin) = split ':', $main::entrySchedWeekly, 2; $trig{'StartHour'} = $starthour; $trig{'StartMinute'} = $startmin; $trig{'TriggerType'} = $scheduler->TASK_TIME_TRIGGER_WEEKLY; $trig{'Type'} = {'WeeksInterval' => 1, 'DaysOfTheWeek' => $day{$main::optionMenuDOW} }; } elsif ($main::rbRunHourly) { $trig{'StartHour'} = 0; $trig{'StartMinute'} = 0; $trig{'MinutesInterval'} = int($units{$main::optionMenuUnits} * $main::entrySchedHourly); $trig{'MinutesDuration'} = 1440; $trig{'TriggerType'} = $scheduler->TASK_TIME_TRIGGER_DAILY; $trig{'Type'} = { 'DaysInterval' => 1 }; } else { # Delicious undefined error, bail out.. $info = 'i HaF t0ObZ eN MaH eErZ!!1'; return; } if (!$scheduler->NewWorkItem($task, \%trig)) { $scheduler->Delete($task); $scheduler->NewWorkItem($task, \%trig) || return; } $scheduler->SetApplicationName($rsync); $scheduler->SetParameters(genRsyncCmdLine()); $scheduler->SetComment($task); $scheduler->SetWorkingDirectory($homedir); $scheduler->SetAccountInformation($main::entryTaskLogin, $main::entryWinPwd) if Win32::IsWinNT(); if ($main::rbRunNow) { $scheduler->SetFlags($scheduler->TASK_FLAG_DELETE_WHEN_DONE()); } $scheduler->Save(); $info = qq#Created Scheduled Task named "$task".# } #----------------------------------------------------------------------------# # helpAbout #----------------------------------------------------------------------------# sub helpAbout { my $about = "$pgm\nVersion $main::Version\n\nAuthors: Nicolas Simonds, Paul Sustman\nCopyright \251 2002 Nicolas Simonds\n\nWindows to Tricord NAS Replication"; Win32::MsgBox($about, MB_ICONINFORMATION(), 'About'); } #----------------------------------------------------------------------------# # populateDirs # Adapted from example by Kristi Thompson, kristi@kristi.ca #----------------------------------------------------------------------------# sub populateDirs { my $width = 50; my $directory; my $top = $c->Frame(-relief => 'groove', -bd => 2); create $c 'window', '20', '75', -window => $top, -anchor => 'w'; my $mid = $c->Frame; create $c 'window', '20', '190', -window => $mid, -anchor => 'w'; my $bottom = $c->Frame; create $c 'window', '20', '350', -window => $bottom, -anchor => 'w'; my $ab = $c->Button(-text => ">>", -width => 5, -command => sub { my $file = $mid->packSlaves->selectionGet(); foreach my $x ($lb->get(0, 'end')) { $x =~ s#\\#/#g; if ($file =~ m/^$x/i) { my $msg; if ($file eq $x) { $file =~ s#/#\\#g; $msg = "$file is already specified -- skipping"; } else { $file =~ s#/#\\#g; $msg = "A parent directory of $file is already specified -- skipping"; } Win32::MsgBox($msg, MB_ICONEXCLAMATION(), 'Error'); return; } } $file =~ s#/#\\#g; $file .= "\\" if ($file =~ m/^[A-Z]:$/); $lb->insert('end', ${file}); }); create $c 'window', '365', '170', -window => $ab, -anchor => 'w'; my $ab = $c->Button(-text => "<<", -width => 5, -command => sub{ if ($lb->size) { my @array = $lb->curselection(); for (my $j = $#array+1; $j-- ; $j>=0) { my $del = $lb->delete($array[$j]); } }}); create $c 'window', '365', '200', -window => $ab, -anchor => 'w'; my ($string, $major, $minor, $build, $id) = Win32::GetOSVersion(); my @os = qw/Win32s Win95 WinNT/; if ($os[$id] !~ /win/i) { $top->packForget; my $d = $directory; $d = '/' if (!$d); defineDirTree($mid, $d, $width); } else { require Win32API::File; my @drives = Win32API::File::getLogicalDrives(); my $startdir, my $startdrive; if ($directory) { $startdrive = convDrive($directory); $startdir = $directory; } else { $startdrive = convDrive(getcwd); $startdir = convDrive(getcwd); } my $selcolor = $top->cget(-background); foreach my $d (@drives) { my $drive = convDrive($d); my $b = $top->Radiobutton( -selectcolor => $selcolor, -indicatoron => 0, -text => $drive, -width => 3, -command => [ \&browseDrives, $mid, $d, $width ], -value => $d, )->pack(-side => 'left', -padx => 4, -pady => 6); if (lc $startdrive eq lc $drive){ $b->invoke; browseDrives($mid, $startdir, $width); } } } } #----------------------------------------------------------------------------# # browseDrives #----------------------------------------------------------------------------# sub browseDrives { my($f, $d, $w) = @_; foreach ($f->packSlaves()) { $_->packForget() } my %drives = ( 0 => 'Unknown', 1 => 'No root drive', 2 => 'Removable disk drive', 3 => 'Local disk drive', 4 => 'Network drive', 5 => 'CD-Rom drive', 6 => 'RAM Disk' ); my $drive = convDrive($d); if (chdir($drive)) { my $volumelabel; Win32API::File::GetVolumeInformation($drive, $volumelabel, [], [], [], [], [], []); my $drivetype = Win32API::File::GetDriveType($drive); driveLabel($f, "$volumelabel ($drive) $drives{$drivetype}"); defineDirTree($f, $d, $w); } else { driveLabel($f, "$drive is not available."); } } #----------------------------------------------------------------------------# # defineDirTree #----------------------------------------------------------------------------# sub defineDirTree { my($f, $d, $w) = @_; chdir $d; my $dt = $f->Scrolled('DirTree', -scrollbars => 'osoe', -directory => $d, -selectmode => 'browse', -background => 'white', -selectbackground => "gray61", -selectforeground => "white", -width => $w )->pack(-fill => 'both', -expand => 1, -pady => 4,); $dt->configure(-command => sub { my $file = $_[0]; foreach my $x ($lb->get(0, 'end')) { $x =~ s#\\#/#g; if ($file =~ m/^$x/i) { my $msg; if ($file eq $x) { $file =~ s#/#\\#g; $msg = "$file is already specified -- skipping"; } else { $file =~ s#/#\\#g; $msg = "A parent directory of $file is already specified -- skipping"; } Win32::MsgBox($msg, MB_ICONEXCLAMATION(), 'Error'); return; } } $file =~ s#/#\\#g; $file .= "\\" if ($file =~ m/^[A-Z]:$/); $lb->insert('end',${file}); }); $dt->configure(-browsecmd => sub { $dt->anchorClear }); } #----------------------------------------------------------------------------# # driveLabel #----------------------------------------------------------------------------# sub driveLabel { my($f, $msg) = @_; $f->Label(-text => " $msg", -relief => 'sunken', -bd => 1, -anchor => 'w' )->pack(-padx => 2, -fill => 'x', -ipady => 2,); } #----------------------------------------------------------------------------# # convDrive #----------------------------------------------------------------------------# sub convDrive { shift =~ /^(.*:)/; return($1); } #----------------------------------------------------------------------------# # goLookup #----------------------------------------------------------------------------# sub goLookup { my $INET = new Win32::Internet(); my ($resp, $name); $resp=$INET->FetchURL("http://$main::entryAppliance/cgi-bin/Tools/diagnostics.sh"); ($name) = ($resp =~ m/^Group\[0\] = (.*)$/mi); if (!($name)) { $info = qq#Unable to determine Tricord cluster that contains appliance "$main::entryAppliance".#; } else { $info = qq#Appliance "$main::entryAppliance" belongs to Tricord cluster "$name".#; } $INET->Close(); } #----------------------------------------------------------------------------# # canonPath #----------------------------------------------------------------------------# sub canonPath { my $arg = shift || return getcwd(); my ($file, $dir,) = fileparse($arg); my $cur = getcwd(); chdir $dir; my $canon = getcwd() . "/$file"; chdir $cur; return $canon; } #----------------------------------------------------------------------------# # launchProgram #----------------------------------------------------------------------------# sub launchProgram { my $appname = shift || return; my $cmdline = shift; # Anonymous coderefs are fun. Break these out to actual subs # if anything else ever needs them.. my $which = sub { my $file = shift || return; foreach my $x ((split /;/, $ENV{'PATH'}), $ENV{'SYSTEMROOT'}) { if (-r Win32::GetShortPathName("$x/$file")) { # Make the pathname super-compatible. # This refuses to work inline for some reason.. my $x = Win32::GetShortPathName("$x/$file"); return Win32::GetFullPathName($x); } } return; }; $appname = $which->($appname); Win32::Process::Create($_, $appname, "$appname $cmdline", 0, NORMAL_PRIORITY_CLASS() | CREATE_NEW_PROCESS_GROUP(), '.', ) || Win32::MsgBox(errorReport($appname), MB_ICONSTOP(), 'Error'); } #----------------------------------------------------------------------------# # shellExecute #----------------------------------------------------------------------------# sub shellExecute { my $shellexecute = new Win32::API "shell32", "ShellExecuteA", [qw(N P P P P N)], 'N'; my $hwnd; my $file = shift || return; my $operation = shift || 'Open'; my $parameters = 0; my $directory = 0; my $showcmd = 1; # 1 = normal, 3 = maximized $shellexecute->Call($hwnd, $operation, $file, $parameters, $directory, $showcmd); Win32::MsgBox(errorReport($file), MB_ICONSTOP(), 'Error') if (Win32::GetLastError()); } #----------------------------------------------------------------------------# # errorReport #----------------------------------------------------------------------------# sub errorReport { my $appname = shift || 'program'; return "Error launching $appname: " . Win32::FormatMessage(Win32::GetLastError()); } #----------------------------------------------------------------------------# # getTaskLogin #----------------------------------------------------------------------------# sub getTaskLogin { my $system = Win32::NodeName(); my $domain = Win32::DomainName(); my $user = Win32::LoginName(); my $dc; # Win32::DomainName() will return the workgroup name as a domain, which confuses # facilities like the task scheduler. This is an attempt to validate the domain. # If we have a valid domain controller, we're in domain mode. If not, we're # logged in locally. require Win32::NetAdmin; import Win32::NetAdmin; Win32::NetAdmin::GetDomainController($system, $domain, $dc); return ($dc) ? "$domain\\$user" : "$system\\$user"; }