package Win32::RcmdRun;

# rcmdrun.pm
# written by Will Roden
# wroden@cisco.com

require Exporter;

our @ISA = qw(Exporter);
our @EXPORT_OK = qw(rcmdrun);
our $VERSION = 0.1.0;

=head1 NAME

Win32::RcmdRun - Run commands on remote WINNT machines via rcmd

=head1 SYNOPSIS

use Win32::RcmdRun;

=head1 DESCRIPTION

This module installs and starts the Rcmd service on a remote WINNT system.  Runs whatever
command lines you specify and returns the Rcmd service to the state it was found in.

=head1 DEPENDENCIES

Win32::Service
File::Copy
Winn32::TieRegistry

The following files from Microsoft's NT or 2000 Resource Kit must be in the path.

sc.exe
rcmd.exe

rcmdsvc.exe must be in the path, the current directory or a directory specified in \$dir.


=head1 FUNCTIONS

=item rcmdrun($hostname, [\$dir], @commandlines)

Checks whether rcmdservice is installed and running on the remote host, installs and starts
it if necessary, runs the command lines specified, stops and uninstalls rcmdservice if
if it was not already running/installed and returns a list containing the ouput of each 
command line.  Returns zero in case of a failure.

You must have admin access on the remote machine to use this function.

=cut

sub rcmdrun {
#install rcmd and run a command on a remote server
#must have admin access on the remote server
#call is rcmdrun($servername, $commandline)
#returns the output of the command in scalar context if successful
#returns 0 if unsuccessful, 
#if it returns 0 it may have left rcmd running on the remote server
#rcmd.exe and sc.exe (both from the resource kit) must be in the path
#rcmdsvc.exe must be in the current directory


	use Win32::Service;
	use File::Copy;
	my $server = shift;
	my $rcmdstatus;
	my $ntdrive;
	my $ntpath;
	my $sys32;
	my $serverunc = "\\\\$server";
	my $rcmdsvcexists = 0;
	my $startrcmd;
	my $origstate;
	my $stoprcmd;
	my $deletercmd;
	my @commandline;
	my @output;
	my $rcmdsvcdir;
	my $caller = caller;

	foreach (".", (split /;/, $ENV{PATH})) { ($rcmdsvcdir = $_ and last) if (-e "$_\\rcmdsvc.exe")	}

	if ($_[0] =~ /^\$/) { 
		my $ref = shift;
		$ref =~ s/\$/$caller\:\:/;
		$rcmdsvcdir = $$ref;
	}

	@commandline = @_;
	
	#check rcmd service status on remote server
	$rcmdstatus = checkrcmd($server);
	$origstate = $rcmdstatus;
	
	#install rcmdsvc if it isn't already there
	$rcmdstatus = instrcmdsvc($server, $rcmdsvcdir) if $rcmdstatus == 0;
	
	#die if rcmd isn't installed by now
	$rcmdstatus > 0 or return 0;
	
	#start rcmd on the remote server
	$startrcmd = `sc \\\\$server start RemoteCmd` unless $origstate == 2;
	$rcmdstatus = 2 if $startrcmd =~ /STATE.*[4|2]/;
	
	#die if rcmd isn't started by now
	$rcmdstatus == 2 or return 0;
	
	foreach (@commandline) {
		my $out = `rcmd \\\\$server $_`;
		@output = (@output, $out);
	}
	
	#stop rcmd if it wasn't already started
	$stoprcmd = `sc \\\\$server stop RemoteCmd` if $origstate < 2;
	$rcmdstatus = 1 if $stoprcmd =~ /STATE.*1/;
	
	#delete rcmd service if it wasn't already installed
	$deletercmd = `sc \\\\$server delete RemoteCmd` if $origstate < 1;
	$rcmdstatus = 0 if $deletercmd =~ /SUCCESS/;

	return @output;

} #rcmdrun

sub checkrcmd {
#checks if rcmd is installed and running on target server
#call is checkrcmd($servername);
#returns 0 if rcmd isn't installed
#returns 1 if rcmd is installed but not running
#returns 2 if rcmd is installed and running

	use Win32::Service;
	my $rcmdstatus = 0;
	my %services;
	my %status;
	my $server = $_[0];

	Win32::Service::GetServices("$server", \%services) or return 0;
	$rcmdstatus++ if defined($services{"Remote Command Server"});
	if ($rcmdstatus == 1) {
		Win32::Service::GetStatus( $server, "RemoteCmd", \%status ) or return 0;
		$rcmdstatus++ if $status{CurrentState} == 4;
	}

	return $rcmdstatus;	

} #checkrcmd

sub getsysroot {
#gets path to systemroot for a remote server by reading from remote server's registry
#call is getsysroot($servername).  no \\ on servername.
#returns 0 if unsuccessful
#returns a list if successful
#list[0] is the drive letter (no :)
#list[1] is the path, including leading but not trailing backslash and no drive letter


	use Win32::TieRegistry;
	$Registry->Delimiter("/");
	my $server = $_[0];
	my @return;

	my($srckey) = $Registry->Connect($server, "LMachine/software/microsoft/windows nt/currentversion") or return 0;
	my($sysroot, $trash) = $srckey->GetValue("SystemRoot");

	@return = split/\:/,$sysroot;
	return @return;	


} #getsysroot

sub instrcmdsvc {

	my $server = $_[0];
	my $rcmdsvcdir = $_[1];
	my $serverunc = "\\\\$server";

	#get path to system32
	($ntdrive, $ntpath) = getsysroot($server);
	$sys32 = $serverunc."\\$ntdrive\$$ntpath\\system32";
	return 0 if $sys32 eq 0;
	my $rcmdsvcexists = 0;

	return 0 unless (-e "$rcmdsvcdir\\rcmdsvc.exe");

	$rcmdsvcexists = 1 if (-e "$sys32\\rcmdsvc.exe");
	
	#put rcmdsvc.exe in sys32 if it isn't already there
	(copy("$rcmdsvcdir\\rcmdsvc.exe", "$sys32"."\\rcmdsvc.exe") or return 0) if $rcmdsvcexists == 0;

	my $svcinst = `sc \\\\$server create RemoteCmd binpath= $ntdrive:$ntpath\\system32\\Rcmdsvc.exe type= own start= auto DisplayName= \"Remote Command Server\"`;
	return 0 unless $svcinst =~ /SUCCESS/;

	return 1;

} #instrcmdsvc