You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by ar...@apache.org on 2011/10/05 14:40:34 UTC

svn commit: r1179199 - in /incubator/vcl/trunk/managementnode/lib/VCL/Module/OS: Windows.pm Windows/Version_6.pm

Author: arkurth
Date: Wed Oct  5 12:40:33 2011
New Revision: 1179199

URL: http://svn.apache.org/viewvc?rev=1179199&view=rev
Log:
VCL-523
Updated Windows code that sets user passwords to update scheduled task passwords for tasks configured to run as the user whose password is being changed.

VCL-30
Added enable_firewall_port, get_firewall_configuration, parse_firewall_scope subroutines to Windows.pm and Version_6.pm. These allow a firewall port to be opened by calling enable_firewall_port with protocol and port arguments.

VCL-503
Updated logoff_users subroutine to specify a timeout value when running qwinsta.exe.

Modified:
    incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm
    incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_6.pm

Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm?rev=1179199&r1=1179198&r2=1179199&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm Wed Oct  5 12:40:33 2011
@@ -55,6 +55,7 @@ use diagnostics;
 use English '-no_match_vars';
 use VCL::utils;
 use File::Basename;
+use Net::Netmask;
 
 ##############################################################################
 
@@ -601,7 +602,7 @@ sub post_load {
 	my $imagemeta_postoption = $self->data->get_imagemeta_postoption();
 	
 	notify($ERRORS{'OK'}, 0, "beginning Windows post-load tasks on $computer_node_name");
-
+	
 =item 1
 
  Wait for computer to respond to SSH
@@ -803,7 +804,7 @@ sub post_load {
 			return 0;
 		}
 	}
-
+	
 =item *
 
  Add a line to currentimage.txt indicating post_load has run
@@ -947,8 +948,8 @@ sub grant_access {
 	$remote_ip_range = 'all' if !$remote_ip_range;
 	
 	if($self->process_connect_methods('start') ){
-                notify($ERRORS{'OK'}, 0, "processed connection methods on $computer_node_name");
-        }
+		notify($ERRORS{'OK'}, 0, "processed connection methods on $computer_node_name");
+	}
 
 	# Allow RDP connections
 	if ($self->firewall_enable_rdp($remote_ip_range)) {
@@ -1447,19 +1448,19 @@ sub logoff_users {
 	my $computer_node_name   = $self->data->get_computer_node_name();
 	my $system32_path        = $self->get_system32_path() || return;
 
-	my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, "$system32_path/qwinsta.exe");
+	my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, "$system32_path/qwinsta.exe", '', '', 1, 60);
 	if ($exit_status > 0) {
-		notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe on $computer_node_name, exit status: $exit_status, output:\n@{$output}");
+		notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe on $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output));
 		return;
 	}
 	elsif (!defined($exit_status)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe SSH command on $computer_node_name");
+		notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe command on $computer_node_name");
 		return;
 	}
 	
 	# Find lines with the state = Active or Disc
 	# Disc will occur if the user disconnected the RDP session but didn't logoff
-	my @connection_lines = grep(/(Active)/, @{$output});
+	my @connection_lines = grep(/(Active)/, @$output);
 	return 1 if !@connection_lines;
 	
 	#notify($ERRORS{'OK'}, 0, "connections on $computer_node_name:\n@connection_lines");
@@ -1498,7 +1499,7 @@ sub logoff_users {
 			notify($ERRORS{'OK'}, 0, "logged off session: $session_identifier, output:\n" . join("\n", @$logoff_output));
 		}
 		else {
-			notify($ERRORS{'WARNING'}, 0, "failed to log off session: $session_identifier, exit status: $logoff_exit_status, output:\n@{$logoff_output}");
+			notify($ERRORS{'WARNING'}, 0, "failed to log off session: $session_identifier, exit status: $logoff_exit_status, output:\n" . join("\n", @$logoff_output));
 		}
 	}
 	return 1;
@@ -1900,6 +1901,7 @@ sub delete_user {
  Description : 
 
 =cut
+
 sub set_password {
 	my $self = shift;
 	if (ref($self) !~ /windows/i) {
@@ -1907,7 +1909,6 @@ sub set_password {
 		return;
 	}
 	
-	my $management_node_keys = $self->data->get_management_node_keys();
 	my $computer_node_name   = $self->data->get_computer_node_name();
 	my $system32_path        = $self->get_system32_path() || return;
 	
@@ -1931,79 +1932,40 @@ sub set_password {
 
 	# Attempt to set the password
 	notify($ERRORS{'DEBUG'}, 0, "setting password of $username to $password on $computer_node_name");
-	my ($set_password_exit_status, $set_password_output) = run_ssh_command($computer_node_name, $management_node_keys, "$system32_path/net.exe user $username '$password'");
+	my ($set_password_exit_status, $set_password_output) = $self->execute("$system32_path/net.exe user $username '$password'");
 	if ($set_password_exit_status == 0) {
 		notify($ERRORS{'OK'}, 0, "password changed to '$password' for user '$username' on $computer_node_name");
 	}
 	elsif (defined $set_password_exit_status) {
-		notify($ERRORS{'WARNING'}, 0, "failed to change password to '$password' for user '$username' on $computer_node_name, exit status: $set_password_exit_status, output:\n@{$set_password_output}");
+		notify($ERRORS{'WARNING'}, 0, "failed to change password to '$password' for user '$username' on $computer_node_name, exit status: $set_password_exit_status, output:\n" . join("\n", @$set_password_output));
 		return 0;
 	}
 	else {
 		notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to change password to '$password' for user '$username' on $computer_node_name");
 		return 0;
 	}
-
-	# Check if root user, must set sshd service password too
-	if ($username eq 'root') {
-		notify($ERRORS{'DEBUG'}, 0, "root account password changed, must also change sshd service credentials");
-		if (!$self->set_service_credentials('sshd', $username, $password)) {
-			notify($ERRORS{'WARNING'}, 0, "failed to set sshd service credentials to $username ($password)");
-			return 0;
-		}
-	}
-
-	# Attempt to change scheduled task passwords
-	notify($ERRORS{'DEBUG'}, 0, "changing passwords for scheduled tasks");
-	my ($schtasks_query_exit_status, $schtasks_query_output) = run_ssh_command($computer_node_name, $management_node_keys, "$system32_path/schtasks.exe /Query /V /FO LIST", '', '', 0);
-	if (defined($schtasks_query_exit_status) && $schtasks_query_exit_status == 0) {
-		notify($ERRORS{'DEBUG'}, 0, "queried scheduled tasks on $computer_node_name");
-	}
-	elsif (defined $schtasks_query_exit_status) {
-		notify($ERRORS{'WARNING'}, 0, "failed to query scheduled tasks on $computer_node_name, exit status: $schtasks_query_exit_status, output:\n@{$schtasks_query_output}");
-		return 0;
-	}
-	else {
-		notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to query scheduled tasks on $computer_node_name");
-		return 0;
-	}
-
-	# Find scheduled tasks configured to run as this user
-	my $task_name;
-	my @task_names_to_update;
-	for my $schtasks_output_line (@{$schtasks_query_output}) {
-		if ($schtasks_output_line =~ /TaskName:\s+(.+)/i) {
-			$task_name = $1;
-		}
-		if ($schtasks_output_line =~ /Run As User.*[\W]$username\s*$/) {
-			notify($ERRORS{'DEBUG'}, 0, "password needs to be updated for scheduled task: '$task_name'");
-			push @task_names_to_update, $task_name;
+	
+	# Get the list of services
+	my @services = $self->get_services_using_login_id($username);
+	for my $service (@services) {
+		notify($ERRORS{'DEBUG'}, 0, "$service service is configured to run as $username, updating service credentials");
+		if (!$self->set_service_credentials($service, $username, $password)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to set $service service credentials to $username ($password)");
 		}
 	}
-
-	# Loop through the scheduled tasks configured to run as the user, update the password
-	for my $task_name_to_update (@task_names_to_update) {
-		my $schtasks_command = "$system32_path/schtasks.exe /Change /RU \"$username\" /RP \"$password\" /TN \"$task_name_to_update\"";
-		my ($schtasks_change_exit_status, $schtasks_change_output) = run_ssh_command($computer_node_name, $management_node_keys, $schtasks_command, '', '', 0);
-		if (!defined($schtasks_change_output)) {
-			notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to change password for scheduled task: $task_name_to_update");
-			return;
-		}
-		elsif (grep (/^SUCCESS:/, @$schtasks_change_output)) {
-			notify($ERRORS{'OK'}, 0, "changed password for scheduled task: $task_name_to_update");
-		}
-		elsif (grep (/The parameter is incorrect/, @$schtasks_change_output)) {
-			notify($ERRORS{'WARNING'}, 0, "encountered Windows bug while attempting to change password for scheduled task: $task_name_to_update, output:\n@{$schtasks_change_output}");
-			# Don't return - There is a bug in Windows 7
-			# If a scheduled task is created using the GUI using a schedule the password cannot be set via schtasks.exe
-			# schtasks.exe displays: ERROR: The parameter is incorrect.
-			# If the same task is changed to run on an event such as logon it works
-		}
-		elsif (grep (/^ERROR:/, @$schtasks_change_output)) {
-			notify($ERRORS{'WARNING'}, 0, "failed to change password for scheduled task: $task_name_to_update, command:\n$schtasks_command\noutput:\n@{$schtasks_change_output}");
-		}
-		else {
-			notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to change password for scheduled task: $task_name_to_update, command:\n$schtasks_command\noutput:\n@{$schtasks_change_output}");
+	
+	# Get the scheduled tasks - check if any are configured to run as the user
+	my $scheduled_task_info = $self->get_scheduled_task_info();
+	for my $task_name (keys %$scheduled_task_info) {
+		my $run_as_user = $scheduled_task_info->{$task_name}{'Run As User'};
+		if ($run_as_user && $run_as_user =~ /^(.+\\)?$username$/i) {
+			notify($ERRORS{'DEBUG'}, 0, "password needs to be updated for scheduled task '$task_name' set to run as user '$run_as_user'");
+			
+			# Attempt to update the scheduled task credentials
+			# Don't return false if this fails - not extremely vital
+			if (!$self->set_scheduled_task_credentials($task_name, $username, $password)) {
+				notify($ERRORS{'WARNING'}, 0, "failed to set '$task_name' scheduled task credentials to $username ($password)");
+			}
 		}
 	}
 	
@@ -2488,16 +2450,16 @@ sub reg_query {
 		notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to query registry key: $key_argument");
 		return;
 	}
-	elsif (grep(/^Error:/, @$output)) {
+	elsif (!grep(/REG.EXE VERSION|HKEY/, @$output)) {
 		my $message = "failed to query registry:\nkey: '$key_argument'\n";
 		$message .= "value: '$value_argument'\n" if defined($value_argument);
-		$message .= "command: '$command'\noutput:\n" . join("\n", @{$output});
+		$message .= "command: '$command'\n";
+		$message .= "exit status: $exit_status\n";
+		$message .= "output:\n" . join("\n", @{$output});
 		notify($ERRORS{'WARNING'}, 0, $message);
 		return;
 	}
 	
-	notify($ERRORS{'DEBUG'}, 0, "reg.exe QUERY output:\n" . join("\n", @$output));
-	
 	# If value argument was specified, parse and return the data
 	if (defined($value_argument)) {
 		# Find the line containing the value information and parse it
@@ -2545,19 +2507,41 @@ sub reg_query {
 						 #"data: " . string_to_ascii($data)
 						 #);
 				
-				#$registry_hash{$key}{$value}{type} = $type;
-				$registry_hash{$key}{$value} = $data;
+				if (!defined($key) || !defined($value) || !defined($data) || !defined($type)) {
+					my $message = "some registry data is undefined:\n";
+					$message .= "line: '$line'\n";
+					$message .= "key: '" . ($key || 'undefined') . "'\n";
+					$message .= "value: '" . ($value || 'undefined') . "'\n";
+					$message .= "data: '" . ($data || 'undefined') . "'\n";
+					$message .= "type: '" . ($type || 'undefined') . "'";
+					notify($ERRORS{'WARNING'}, 0, $message);
+				}
+				else {
+					$registry_hash{$key}{$value}{type} = $type;
+					$registry_hash{$key}{$value} = $data;
+				}
 			}
 			elsif ($line =~ /^!/) {
 				# Ignore lines beginning with '!'
 				next;
 			}
+			elsif ($line =~ /^Error:/) {
+				# Ignore lines beginning with 'Error:' -- this is common and probably not a problem
+				# Example:
+				#    Error:  Access is denied in the key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\MRxDAV\EncryptedDirectories
+				next;
+			}
 			else {
 				notify($ERRORS{'WARNING'}, 0, "unexpected output in line: '" . string_to_ascii($line) . "'");
 			}
 		}
 		
-		notify($ERRORS{'DEBUG'}, 0, "retrieved registry data:\n" . format_data(\%registry_hash));
+		my $message = "retrieved registry data:\n";
+		$message .= "key: '$key_argument'\n";
+		$message .= "value: '$value_argument'\n" if defined($value_argument);
+		$message .= "keys found: " . scalar(keys %registry_hash);
+		notify($ERRORS{'DEBUG'}, 0, $message);
+		
 		return \%registry_hash;
 	}
 }
@@ -2724,6 +2708,10 @@ sub reg_delete {
 	if (defined($delete_registry_exit_status) && $delete_registry_exit_status == 0) {
 		notify($ERRORS{'DEBUG'}, 0, "deleted registry key: $registry_key, value: $registry_value, output:\n" . join("\n", @$delete_registry_output));
 	}
+	elsif ($delete_registry_output && grep(/unable to find/i, @$delete_registry_output)) {
+		# Error: The system was unable to find the specified registry key or value
+		notify($ERRORS{'DEBUG'}, 0, "registry key does NOT exist: $registry_key");
+	}
 	elsif ($delete_registry_exit_status) {
 		notify($ERRORS{'WARNING'}, 0, "failed to delete registry key: $registry_key, value: $registry_value, exit status: $delete_registry_exit_status, output:\n@{$delete_registry_output}");
 		return;
@@ -3076,6 +3064,60 @@ sub delete_hklm_run_registry_key {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 set_scheduled_task_credentials
+
+ Parameters  : $task_name, $username, $password
+ Returns     : boolean
+ Description : Sets the credentials under which a scheduled task runs.
+
+=cut
+
+sub set_scheduled_task_credentials {
+	my $self = shift;
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my ($task_name, $username, $password) = @_;
+	if (!defined($task_name) || !defined($username) || !defined($password)) {
+		notify($ERRORS{'WARNING'}, 0, "scheduled task name, username, and password arguments were not supplied");
+		return;
+	}
+	
+	my $computer_node_name   = $self->data->get_computer_node_name();
+	my $system32_path        = $self->get_system32_path() || return;
+	
+	my $command = "$system32_path/schtasks.exe /Change /RU \"$username\" /RP \"$password\" /TN \"$task_name\"";
+	my ($exit_status, $output) = $self->execute($command);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to change password for scheduled task: $task_name");
+		return;
+	}
+	elsif (grep (/^SUCCESS:/, @$output)) {
+		notify($ERRORS{'OK'}, 0, "changed password for scheduled task: $task_name");
+		return 1;
+	}
+	elsif (grep (/The parameter is incorrect/, @$output)) {
+		notify($ERRORS{'WARNING'}, 0, "unable to change password for scheduled task '$task_name' due to Windows bug\ncommand: '$command'\noutput:\n" . join("\n", @$output));
+		# Don't return false - There is a bug in Windows 7
+		# If a scheduled task is created using the GUI using a schedule the password cannot be set via schtasks.exe
+		# schtasks.exe displays: ERROR: The parameter is incorrect.
+		# If the same task is changed to run on an event such as logon it works
+		return 1;
+	}
+	elsif (grep (/^ERROR:/, @$output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to change password for scheduled task: $task_name, command:\n$command\noutput:\n" . join("\n", @$output));
+		return 0;
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to change password for scheduled task: $task_name, command:\n$command\noutput:\n" . join("\n", @$output));
+		return 0;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 delete_scheduled_task
 
  Parameters  :
@@ -3790,9 +3832,9 @@ sub set_service_credentials {
 
 =head2 get_service_list
 
- Parameters  : 
- Returns     : 
- Description : 
+ Parameters  : none
+ Returns     : array
+ Description : Retrieves the names of the services installed on the computer.
 
 =cut
 
@@ -3803,15 +3845,14 @@ sub get_service_list {
 		return;
 	}
 
-	my $management_node_keys = $self->data->get_management_node_keys();
 	my $computer_node_name   = $self->data->get_computer_node_name();
 	my $system32_path        = $self->get_system32_path() || return;
 	
-	# Attempt to delete the user account
-	my $sc_query_command = $system32_path . "/sc.exe query | grep SERVICE_NAME | cut --fields=2 --delimiter=' '";
-	my ($sc_query_exit_status, $sc_query_output) = run_ssh_command($computer_node_name, $management_node_keys, $sc_query_command);
+	# Call sc query
+	my $sc_query_command = $system32_path . "/sc.exe query";
+	my ($sc_query_exit_status, $sc_query_output) = $self->execute($sc_query_command);
 	if (defined($sc_query_exit_status) && $sc_query_exit_status == 0) {
-		notify($ERRORS{'OK'}, 0, "retrieved service list on $computer_node_name");
+		#notify($ERRORS{'OK'}, 0, "retrieved service list on $computer_node_name:\n" . join("\n", @$sc_query_output));
 	}
 	elsif (defined($sc_query_exit_status)) {
 		notify($ERRORS{'WARNING'}, 0, "failed to retrieve service list from $computer_node_name, exit status: $sc_query_exit_status, output:\n@{$sc_query_output}");
@@ -3821,70 +3862,131 @@ sub get_service_list {
 		notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to failed to retrieve service list from $computer_node_name");
 		return;
 	}
+	
+	my @service_names;
+	for my $line (@$sc_query_output) {
+		if ($line =~ /SERVICE_NAME: (.*)/) {
+			push @service_names, $1;
+		}
+	}
 
-	my @service_name_array = split("\n", $sc_query_output);
-	notify($ERRORS{'DEBUG'}, 0, "found " . @service_name_array . " services on $computer_node_name");
-	return @service_name_array;
+	notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@service_names) . " services on $computer_node_name");
+	return @service_names;
 } ## end sub get_service_list
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_service_login_ids
+=head2 get_service_info
 
- Parameters  : 
- Returns     : 
- Description : 
+ Parameters  : none
+ Returns     : hash reference
+ Description : Retrieves info for all services installed on the computer. A hash
+               reference is returned. The hash keys are service names.
+               Example:
+                  "sshd" => {
+                    "BINARY_PATH_NAME" => "C:\\cygwin\\bin\\cygrunsrv.exe",
+                    "DEPENDENCIES" => "tcpip",
+                    "DISPLAY_NAME" => "CYGWIN sshd",
+                    "ERROR_CONTROL" => "1   NORMAL",
+                    "LOAD_ORDER_GROUP" => "",
+                    "SERVICE_NAME" => "sshd",
+                    "SERVICE_START_NAME" => ".\\root",
+                    "START_TYPE" => "2   AUTO_START",
+                    "TAG" => 0,
+                    "TYPE" => "10  WIN32_OWN_PROCESS "
+                  },
 
 =cut
 
-sub get_services_using_login_id {
+sub get_service_info {
 	my $self = shift;
 	if (ref($self) !~ /windows/i) {
 		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
 		return;
 	}
+	
+	return $self->{service_info} if $self->{service_info};
 
-	my $management_node_keys = $self->data->get_management_node_keys();
 	my $computer_node_name   = $self->data->get_computer_node_name();
 	my $system32_path        = $self->get_system32_path() || return;
 	
-	my $login_id = shift;
-	if (!$login_id) {
-		notify($ERRORS{'WARNING'}, 0, "unable to get services using login id, login id argument was not passed correctly");
-		return;
-	}
-
-	# Get a list of the services on the node
 	my @service_list = $self->get_service_list();
-	if (!@service_list) {
-		notify($ERRORS{'WARNING'}, 0, "unable to get service logon ids, failed to retrieve service name list from $computer_node_name, service credentials cannot be changed");
-		return 0;
-	}
-
-	my @services_using_login_id;
-	for my $service_name (@service_list) {
-		# Attempt to get the service start name using sc.exe qc
-		my $sc_qc_command = $system32_path . "/sc.exe qc $service_name | grep SERVICE_START_NAME | cut --fields=2 --delimiter='\\'";
-		my ($sc_qc_exit_status, $sc_qc_output) = run_ssh_command($computer_node_name, $management_node_keys, $sc_qc_command);
-		if (defined($sc_qc_exit_status) && $sc_qc_exit_status == 0) {
-			notify($ERRORS{'OK'}, 0, "retrieved $service_name service start name from $computer_node_name");
+	
+	my $service_info;
+	for my $service (@service_list) {
+		# Call sc query
+		my $command = "$system32_path/sc.exe qc \"$service\"";
+		my ($exit_status, $output) = $self->execute($command);
+		if (defined($exit_status) && $exit_status == 0) {
+			#notify($ERRORS{'DEBUG'}, 0, "retrieved '$service' service info:\n" . join("\n", @$output));
+			
+			for my $line (@$output) {
+				if (my ($property, $value) = $line =~ /^[\s\t]*(\w+)[\s\t]*:[\s\t]*(.*)/g) {
+					$service_info->{$service}{$property} = $value;
+				}
+			}
 		}
-		elsif (defined($sc_qc_exit_status)) {
-			notify($ERRORS{'WARNING'}, 0, "failed to retrieve $service_name service start name from $computer_node_name, exit status: $sc_qc_exit_status, output:\n@{$sc_qc_output}");
-			return 0;
+		elsif (defined($exit_status)) {
+			notify($ERRORS{'WARNING'}, 0, "failed to retrieve '$service' service info from $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output));
+			next SERVICE;
 		}
 		else {
-			notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to failed to retrieve $service_name service start name from $computer_node_name");
-			return;
+			notify($ERRORS{'WARNING'}, 0, "failed to run command to retrieve '$service' service info from $computer_node_name");
+			next SERVICE;
 		}
+	}
+	
+	$self->{service_info} = $service_info;
+	#notify($ERRORS{'DEBUG'}, 0, "retrieved service info:\n" . format_data($service_info));
+	return $service_info;
+}
 
-		my $service_logon_id = @{$sc_qc_output}[0];
-		if ($service_logon_id =~ /^$login_id$/i) {
-			push @services_using_login_id, $service_logon_id;
-		}
-	} ## end for my $service_name (@service_list)
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_service_login_ids
+
+ Parameters  : $login_id
+ Returns     : array
+ Description : Enumerates the services installed on the computer and returns an
+               array containing the names of the services which are configured
+               to run using the credentials of the login ID specified as the
+               argument.
+
+=cut
 
-	return @services_using_login_id;
+sub get_services_using_login_id {
+	my $self = shift;
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $computer_node_name   = $self->data->get_computer_node_name();
+	
+	my $login_id = shift;
+	if (!$login_id) {
+		notify($ERRORS{'WARNING'}, 0, "unable to get services using login id, login id argument was not passed correctly");
+		return;
+	}
+	
+	# Get infor for all the services installed on the computer
+	my $service_info = $self->get_service_info() || return;
+	
+	my @matching_service_names;
+	for my $service_name (sort keys %$service_info) {
+		my $service_start_name = $service_info->{$service_name}{SERVICE_START_NAME};
+		
+		# The service start name may be in any of the following forms:
+		#    LocalSystem
+		#    NT AUTHORITY\LocalService
+		#    .\root
+		if ($service_start_name && $service_start_name =~ /^((NT AUTHORITY|\.)\\)?$login_id$/i) {
+			push @matching_service_names, $service_name;
+		}
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@matching_service_names) . " services using login ID '$login_id': " . join(", ", @matching_service_names));
+	return @matching_service_names;
 } ## end sub get_services_using_login_id
 
 #/////////////////////////////////////////////////////////////////////////////
@@ -3939,116 +4041,118 @@ sub disable_scheduled_task {
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_scheduled_tasks
+=head2 get_scheduled_task_info
 
  Parameters  : 
- Returns     : array reference if successful, false if failed
+ Returns     : hash reference
  Description : Queries the scheduled tasks on a computer and returns the
-               configuration for each task. An array reference is returned.
-               Each array element represents a scheduled task and contains
-               a hash reference. The hash contains the schedule task
-               configuration.  The hash keys are:
-                  $scheduled_task_hash{"HostName"},
-                  $scheduled_task_hash{"TaskName"},
-                  $scheduled_task_hash{"Next Run Time"},
-                  $scheduled_task_hash{"Status"},
-                  $scheduled_task_hash{"Last Run Time"},
-                  $scheduled_task_hash{"Last Result"},
-                  $scheduled_task_hash{"Creator"},
-                  $scheduled_task_hash{"Schedule"},
-                  $scheduled_task_hash{"Task To Run"},
-                  $scheduled_task_hash{"Start In"},
-                  $scheduled_task_hash{"Comment"},
-                  $scheduled_task_hash{"Scheduled Task State"},
-                  $scheduled_task_hash{"Scheduled Type"},
-                  $scheduled_task_hash{"Start Time"},
-                  $scheduled_task_hash{"Start Date"},
-                  $scheduled_task_hash{"End Date"},
-                  $scheduled_task_hash{"Days"},
-                  $scheduled_task_hash{"Months"},
-                  $scheduled_task_hash{"Run As User"},
-                  $scheduled_task_hash{"Delete Task If Not Rescheduled"},
-                  $scheduled_task_hash{"Stop Task If Runs X Hours and X Mins"},
-                  $scheduled_task_hash{"Repeat: Every"},
-                  $scheduled_task_hash{"Repeat: Until: Time"},
-                  $scheduled_task_hash{"Repeat: Until: Duration"},
-                  $scheduled_task_hash{"Repeat: Stop If Still Running"},
-                  $scheduled_task_hash{"Idle Time"},
-                  $scheduled_task_hash{"Power Management"}
+               configuration for each task. A hash reference is returned. The
+               hash keys are the scheduled task names.
+					Example:
+               "\\Microsoft\\Windows\\Time Synchronization\\SynchronizeTime" => {
+                    "Author" => "Microsoft Corporation",
+                    "Comment" => "Maintains date and time synchronization...",
+                    "Days" => "1/1/2005",
+                    "Delete Task If Not Rescheduled" => "Stop On Battery Mode",
+                    "End Date" => "1:00:00 AM",
+                    "HostName" => "WIN7-64BIT",
+                    "Idle Time" => " any services that explicitly depend on it will fail to start.",
+                    "Last Result" => 1056,
+                    "Last Run Time" => "9/11/2011 1:00:00 AM",
+                    "Logon Mode" => "Interactive/Background",
+                    "Months" => "N/A",
+                    "Next Run Time" => "9/18/2011 1:00:00 AM",
+                    "Power Management" => "Enabled",
+                    "Repeat: Every" => "SUN",
+                    "Repeat: Stop If Still Running" => "Disabled",
+                    "Repeat: Until: Duration" => "Disabled",
+                    "Repeat: Until: Time" => "Every 1 week(s)",
+                    "Run As User" => "Disabled",
+                    "Schedule" => "Enabled",
+                    "Schedule Type" => "72:00:00",
+                    "Scheduled Task State" => " date and time synchronization will be unavailable. If this service is disabled",
+                    "Start Date" => "Weekly",
+                    "Start In" => "N/A",
+                    "Start Time" => "Scheduling data is not available in this format.",
+                    "Status" => "Ready",
+                    "Stop Task If Runs X Hours and X Mins" => "LOCAL SERVICE",
+                    "Task To Run" => "%windir%\\system32\\sc.exe start w32time task_started"
+               },
 
 =cut
 
-sub get_scheduled_tasks {
+sub get_scheduled_task_info {
 	my $self = shift;
 	if (ref($self) !~ /windows/i) {
 		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
 		return;
 	}
+	
+	return $self->{scheduled_task_info} if $self->{scheduled_task_info};
 
-	my $management_node_keys = $self->data->get_management_node_keys();
 	my $computer_node_name   = $self->data->get_computer_node_name();
 	my $system32_path        = $self->get_system32_path() || return;
 	
 	# Attempt to retrieve scheduled task information
-	my $schtasks_command = $system32_path . '/schtasks.exe /Query /NH /V /FO CSV';
-	my ($schtasks_exit_status, $schtasks_output) = run_ssh_command($computer_node_name, $management_node_keys, $schtasks_command);
-	if (defined($schtasks_exit_status) && $schtasks_exit_status == 0) {
-		notify($ERRORS{'OK'}, 0, "retrieved scheduled task information");
-	}
-	elsif (defined($schtasks_exit_status)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to retrieve scheduled task information, exit status: $schtasks_exit_status, output:\n@{$schtasks_output}");
-		return 0;
+	my $command = $system32_path . '/schtasks.exe /Query /V /FO CSV';
+	my ($exit_status, $output) = $self->execute($command);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to run command to retrieve scheduled task information");
+		return;
+	}
+	elsif ($exit_status == 0) {
+		#notify($ERRORS{'DEBUG'}, 0, "retrieved scheduled task information, output:\n" . join("\n", @$output));
+		
+		if (grep(/no scheduled tasks/i, @$output)) {
+			notify($ERRORS{'DEBUG'}, 0, "there are no scheduled tasks on $computer_node_name, output:\n" . join("\n", @$output));
+			return {};
+		}
 	}
 	else {
-		notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to retrieve scheduled task information");
-		return;
+		notify($ERRORS{'WARNING'}, 0, "failed to retrieve scheduled task information, exit status: $exit_status, output:\n" . join("\n", @$output));
+		return 0;
 	}
 	
-	my @scheduled_task_data;
-	for my $scheduled_task_line (@{$schtasks_output}) {
-		# Remove quotes from the hash values
-		$scheduled_task_line =~ s/"//g;
-		
-		# Split the line up
-		my @scheduled_task_fields = split(/,/, $scheduled_task_line);
-		
-		# Create a hash containing the line data
-		my %scheduled_task_hash;
-		($scheduled_task_hash{"HostName"},
-		$scheduled_task_hash{"TaskName"},
-		$scheduled_task_hash{"Next Run Time"},
-		$scheduled_task_hash{"Status"},
-		$scheduled_task_hash{"Last Run Time"},
-		$scheduled_task_hash{"Last Result"},
-		$scheduled_task_hash{"Creator"},
-		$scheduled_task_hash{"Schedule"},
-		$scheduled_task_hash{"Task To Run"},
-		$scheduled_task_hash{"Start In"},
-		$scheduled_task_hash{"Comment"},
-		$scheduled_task_hash{"Scheduled Task State"},
-		$scheduled_task_hash{"Scheduled Type"},
-		$scheduled_task_hash{"Start Time"},
-		$scheduled_task_hash{"Start Date"},
-		$scheduled_task_hash{"End Date"},
-		$scheduled_task_hash{"Days"},
-		$scheduled_task_hash{"Months"},
-		$scheduled_task_hash{"Run As User"},
-		$scheduled_task_hash{"Delete Task If Not Rescheduled"},
-		$scheduled_task_hash{"Stop Task If Runs X Hours and X Mins"},
-		$scheduled_task_hash{"Repeat: Every"},
-		$scheduled_task_hash{"Repeat: Until: Time"},
-		$scheduled_task_hash{"Repeat: Until: Duration"},
-		$scheduled_task_hash{"Repeat: Stop If Still Running"},
-		$scheduled_task_hash{"Idle Time"},
-		$scheduled_task_hash{"Power Management"}) = @scheduled_task_fields;
+	my @properties;
+	my $scheduled_task_info;
+	for my $line (@$output) {
+		
+		# Split the line into an array and remove quotes from beginning and end of each value
+		my @values = split(/\",\"/, $line);
+		
+		if (grep { $_ eq 'TaskName' } @values) {
+			@properties = @values;
+			next;
+		}
+		elsif (!@properties) {
+			notify($ERRORS{'WARNING'}, 0, "unable to parse scheduled task info, column definition line containing 'TaskName' was not found before line: '$line'");
+			return;
+		}
+		
 		
-		push @scheduled_task_data, \%scheduled_task_hash;
+		if (scalar(@properties) != scalar(@values)) {
+			notify($ERRORS{'WARNING'}, 0, "property count (" . scalar(@properties) . ") does not equal value count (" . scalar(@values) . ")\nproperties line: '$line'\nvalues: '" . join(",", @values));
+			next;
+		}
+		
+		my $info;
+		for (my $i=0; $i<scalar(@values); $i++) {
+			$info->{$properties[$i]} = $values[$i];
+		}
+		
+		my $task_name = $info->{TaskName};
+		if (defined($task_name)) {
+			$scheduled_task_info->{$task_name} = $info;
+		}
+		else {
+			notify($ERRORS{'WARNING'}, 0, "failed to determine scheduled task name from line: '$line', info:\n" . format_data($info));
+		}
 	}
 	
-	notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@scheduled_task_data) . " scheduled tasks");
-	
-	return \@scheduled_task_data;
-} ## end sub disable_scheduled_task
+	$self->{scheduled_task_info} = $scheduled_task_info;
+	notify($ERRORS{'DEBUG'}, 0, "found " . scalar(keys %$scheduled_task_info) . " scheduled tasks:\n" . join("\n", sort keys(%$scheduled_task_info)));
+	return $scheduled_task_info;
+}
 
 #/////////////////////////////////////////////////////////////////////////////
 
@@ -4269,6 +4373,509 @@ sub set_my_computer_name {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 get_firewall_configuration
+
+ Parameters  : none
+ Returns     : hash reference
+ Description : Retrieves information about the open firewall ports on the
+               computer and constructs a hash. The hash keys are protocol names.
+               Each protocol key contains a hash reference. The keys are either
+               port numbers or ICMP types.
+               Example:
+               
+                  "ICMP" => {
+                    8 => {
+                      "description" => "Allow inbound echo request"
+                    }
+                  },
+                  "TCP" => {
+                    22 => {
+                      "interface_names" => [
+                        "Local Area Connection 3"
+                      ],
+                      "name" => "sshd"
+                    },
+                    3389 => {
+                      "name" => "Remote Desktop",
+                      "scope" => "192.168.53.54/255.255.255.255"
+                    },
+
+=cut
+
+sub get_firewall_configuration {
+	my $self = shift;
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	return $self->{firewall_configuration} if $self->{firewall_configuration};
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	my $system32_path = $self->get_system32_path() || return;
+	
+	my $network_configuration = $self->get_network_configuration() || return;
+	
+	my $firewall_configuration = {};
+	
+	my $port_command = "$system32_path/netsh.exe firewall show portopening verbose = ENABLE";
+	my ($port_exit_status, $port_output) = $self->execute($port_command);
+	if (!defined($port_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to run command to show open firewall ports on $computer_node_name");
+		return;
+	}
+	elsif (!grep(/Port\s+Protocol/i, @$port_output)) {
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned from command to show open firewall ports on $computer_node_name, command: '$port_command', exit status: $port_exit_status, output:\n" . join("\n", @$port_output));
+		return;
+	}
+	
+	# Execute the netsh.exe command to retrieve firewall port openings
+	# Expected output:
+	# Port configuration for Local Area Connection 4:
+	# Port   Protocol  Mode     Name
+	# -------------------------------------------------------------------
+	# 443    TCP       Disable  Secure Web Server (HTTPS)
+	# 22     TCP       Disable  Cygwin SSHD
+	
+	my $configuration;
+	my $previous_protocol;
+	my $previous_port;
+	for my $line (@$port_output) {
+		if ($line =~ /^Port configuration for (.+):/ig) {
+			$configuration = $1;
+		}
+		elsif ($line =~ /^(\d+)\s+(\w+)\s+(\w+)\s+(.*)/ig) {
+			my $port = $1;
+			my $protocol = $2;
+			my $mode = $3;
+			my $name = $4;
+			
+			$previous_protocol = $protocol;
+			$previous_port = $port;
+			
+			next if ($mode !~ /enable/i);
+			
+			$firewall_configuration->{$protocol}{$port}{name}= $name;
+			
+			if ($configuration !~ /\w+ profile/i) {
+				push @{$firewall_configuration->{$protocol}{$port}{interface_names}}, $configuration;
+			}
+		}
+		elsif (!defined($previous_protocol) ||
+				 !defined($previous_port) ||
+				 !defined($firewall_configuration->{$previous_protocol}) ||
+				 !defined($firewall_configuration->{$previous_protocol}{$previous_port})
+				 ) {
+			next;
+		}
+		elsif (my ($scope) = $line =~ /Scope:\s+(.+)/ig) {
+			$firewall_configuration->{$previous_protocol}{$previous_port}{scope} = $scope;
+		}
+	}
+	
+	# Execute the netsh.exe ICMP command
+	my $icmp_command = "$system32_path/netsh.exe firewall show icmpsetting verbose = ENABLE";
+	my ($icmp_exit_status, $icmp_output) = $self->execute($icmp_command);
+	if (!defined($icmp_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to run command to show firewall ICMP settings on $computer_node_name");
+		return;
+	}
+	elsif (!grep(/Mode\s+Type/i, @$icmp_output)) {
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned from command to show firewall ICMP settings on $computer_node_name, command: '$icmp_command', exit status: $icmp_exit_status, output:\n" . join("\n", @$icmp_output));
+		return;
+	}
+	
+	# ICMP configuration for Local Area Connection 4:
+	# Mode     Type  Description
+	# -------------------------------------------------------------------
+	# Disable  3     Allow outbound destination unreachable
+	# Disable  4     Allow outbound source quench
+
+	for my $line (@$icmp_output) {
+		if ($line =~ /^ICMP configuration for (.+):/ig) {
+			$configuration = $1;
+		}
+		elsif ($line =~ /^(\w+)\s+(\d+)\s+(.*)/ig) {
+			my $mode = $1;
+			my $type = $2;
+			my $description = $3;
+			
+			next if ($mode !~ /enable/i);
+			
+			$firewall_configuration->{ICMP}{$type}{description} = $description || '';
+			
+			if ($configuration !~ /\w+ profile/i) {
+				push @{$firewall_configuration->{ICMP}{$type}{interface_names}}, $configuration;
+			}
+		}
+	}
+	
+	$self->{firewall_configuration} = $firewall_configuration;
+	
+	notify($ERRORS{'DEBUG'}, 0, "retrieved firewall configuration from $computer_node_name:\n" . format_data($firewall_configuration));
+	return $firewall_configuration;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 parse_firewall_scope
+
+ Parameters  : @scope_strings
+ Returns     : string
+ Description : Parses an array of firewall scope strings and collpases them into
+               a simplified scope if possible. A comma-separated string is
+               returned. The scope string argument may be in the form:
+                  -192.168.53.54/255.255.255.192
+                  -192.168.53.54/24
+                  -192.168.53.54
+                  -*
+                  -Any
+                  -LocalSubnet
+
+=cut
+
+sub parse_firewall_scope {
+	my $self = shift;
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my @scope_strings = @_;
+	if (!@scope_strings) {
+		notify($ERRORS{'WARNING'}, 0, "scope array argument was not supplied");
+		return;
+	}
+	
+	my @netmask_objects;
+	
+	for my $scope_string (@scope_strings) {
+		if ($scope_string =~ /(\*|Any)/i) {
+			my $netmask_object = new Net::Netmask('any');
+			push @netmask_objects, $netmask_object;
+		}
+		
+		elsif ($scope_string =~ /LocalSubnet/i) {
+			my $network_configuration = $self->get_network_configuration() || return;
+			
+			for my $interface_name (sort keys %$network_configuration) {
+				for my $ip_address (keys %{$network_configuration->{$interface_name}{ip_address}}) {
+					my $subnet_mask = $network_configuration->{$interface_name}{ip_address}{$ip_address};
+					
+					my $netmask_object = new Net::Netmask("$ip_address/$subnet_mask");
+					if ($netmask_object) {
+						push @netmask_objects, $netmask_object;
+					}
+					else {
+						notify($ERRORS{'WARNING'}, 0, "failed to create Net::Netmask object, IP address: $ip_address, subnet mask: $subnet_mask");
+						return;
+					}
+				}
+			}
+		}
+		
+		elsif (my @scope_sections = split(/,/, $scope_string)) {
+			for my $scope_section (@scope_sections) {
+				
+				if (my ($start_address, $end_address) = $scope_section =~ /^([\d\.]+)-([\d\.]+)$/) {
+					my @netmask_range_objects = Net::Netmask::range2cidrlist($start_address, $end_address);
+					if (@netmask_range_objects) {
+						push @netmask_objects, @netmask_range_objects;
+					}
+					else {
+						notify($ERRORS{'WARNING'}, 0, "failed to call Net::Netmask::range2cidrlist to create an array of objects covering IP range: $start_address-$end_address");
+						return;
+					}
+				}
+				
+				elsif (my ($ip_address, $subnet_mask) = $scope_section =~ /^([\d\.]+)\/([\d\.]+)$/) {
+					my $netmask_object = new Net::Netmask("$ip_address/$subnet_mask");
+					if ($netmask_object) {
+						push @netmask_objects, $netmask_object;
+					}
+					else {
+						notify($ERRORS{'WARNING'}, 0, "failed to create Net::Netmask object, IP address: $ip_address, subnet mask: $subnet_mask");
+						return;
+					}
+				}
+				
+				elsif (($ip_address) = $scope_section =~ /^([\d\.]+)$/) {
+					my $netmask_object = new Net::Netmask("$ip_address");
+					if ($netmask_object) {
+						push @netmask_objects, $netmask_object;
+					}
+					else {
+						notify($ERRORS{'WARNING'}, 0, "failed to create Net::Netmask object, IP address: $ip_address");
+						return;
+					}
+				}
+				
+				else {
+					notify($ERRORS{'WARNING'}, 0, "unable to parse '$scope_section' section of scope: '$scope_string'");
+					return;
+				}
+			}
+		}
+		
+		else {
+			notify($ERRORS{'WARNING'}, 0, "unexpected scope format: '$scope_string'");
+			return
+		}
+	}
+	
+	my @netmask_objects_collapsed = cidrs2cidrs(@netmask_objects);
+	if (@netmask_objects_collapsed) {
+		my $scope_result_string;
+		my @ip_address_ranges;
+		for my $netmask_object (@netmask_objects_collapsed) {
+			
+			if ($netmask_object->first() eq $netmask_object->last()) {
+				push @ip_address_ranges, $netmask_object->first();
+				$scope_result_string .= $netmask_object->base() . ",";
+			}
+			else {
+				push @ip_address_ranges, $netmask_object->first() . "-" . $netmask_object->last();
+				$scope_result_string .= $netmask_object->base() . "/" . $netmask_object->mask() . ",";
+			}
+		}
+		
+		$scope_result_string =~ s/,+$//;
+		my $argument_string = join(",", @scope_strings);
+		if ($argument_string ne $scope_result_string) {
+			notify($ERRORS{'DEBUG'}, 0, "parsed firewall scope:\n" .
+				"argument: '$argument_string'\n" .
+				"result: '$scope_result_string'\n" .
+				"IP address ranges:\n" . join(", ", @ip_address_ranges)
+			);
+		}
+		return $scope_result_string;
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to parse firewall scope: '" . join(",", @scope_strings) . "', no Net::Netmask objects were created");
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 enable_firewall_port
+
+ Parameters  : $protocol, $port, $scope (optional), $overwrite_existing (optional), $name (optional), $description (optional)
+ Returns     : 1 if succeeded, 0 otherwise
+ Description : Enables a firewall port on the computer. The protocol and port
+               arguments are required. An optional scope argument may supplied.
+               A boolean overwrite existing may be supplied following the scope
+               argument. The default is false. If false, the existing firewall
+               configuration will be retrieved. If an exception already exists
+               for the given protocol and port, the existing and new scopes will
+               be joined. If set to true, any existing exception matching the
+               protocol and port will be removed and a new exception added.
+
+=cut
+
+sub enable_firewall_port {
+	my $self = shift;
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my ($protocol, $port, $scope_argument, $overwrite_existing, $name, $description) = @_;
+	if (!defined($protocol) || !defined($port)) {
+		notify($ERRORS{'WARNING'}, 0, "protocol and port arguments were not supplied");
+		return;
+	}
+	
+	my $computer_node_name   = $self->data->get_computer_node_name();
+	my $system32_path        = $self->get_system32_path() || return;
+	
+	# Make sure the protocol is uppercase
+	$protocol = uc($protocol);
+	
+	$scope_argument = '*' if (!defined($scope_argument));
+	
+	$name = '' if !$name;
+	$description = '' if !$description;
+	
+	my $scope;
+	
+	my $firewall_configuration = $self->get_firewall_configuration() || return;
+	my $existing_scope = $firewall_configuration->{$protocol}{$port}{scope} || '';
+	my $existing_name = $firewall_configuration->{$protocol}{$port}{name} || '';
+	my $existing_description = $firewall_configuration->{$protocol}{$port}{name} || '';
+	if ($existing_scope) {
+		
+		if ($overwrite_existing) {
+			$scope = $self->parse_firewall_scope($scope_argument);
+			if (!$scope) {
+				notify($ERRORS{'WARNING'}, 0, "failed to parse firewall scope argument: '$scope_argument'");
+				return;
+			}
+			
+			notify($ERRORS{'DEBUG'}, 0, "existing firewall opening on $computer_node_name will be replaced:\n" .
+				"name: '$existing_name'\n" .
+				"protocol: $protocol\n" .
+				"port/type: $port\n" .
+				"existing scope: '$existing_scope'\n" .
+				"new scope: $scope\n" .
+				"overwrite existing rule: " . ($overwrite_existing ? 'yes' : 'no')
+			);
+		}
+		else {
+			my $parsed_existing_scope = $self->parse_firewall_scope($existing_scope);
+			if (!$parsed_existing_scope) {
+				notify($ERRORS{'WARNING'}, 0, "failed to parse existing firewall scope: '$existing_scope'");
+				return;
+			}
+			
+			$scope = $self->parse_firewall_scope("$scope_argument,$existing_scope");
+			if (!$scope) {
+				notify($ERRORS{'WARNING'}, 0, "failed to parse firewall scope argument appended with existing scope: '$scope_argument,$existing_scope'");
+				return;
+			}
+			
+			if ($scope eq $parsed_existing_scope) {
+				notify($ERRORS{'DEBUG'}, 0, "firewall is already open on $computer_node_name, existing scope matches scope argument:\n" .
+					"name: '$existing_name'\n" .
+					"protocol: $protocol\n" .
+					"port/type: $port\n" .
+					"scope: $scope\n" .
+					"overwrite existing rule: " . ($overwrite_existing ? 'yes' : 'no')
+				);
+				return 1;
+			}
+		}
+	}
+	else {
+		$scope = $self->parse_firewall_scope($scope_argument);
+		if (!$scope) {
+			notify($ERRORS{'WARNING'}, 0, "failed to parse firewall scope argument: '$scope_argument'");
+			return;
+		}
+		
+		notify($ERRORS{'DEBUG'}, 0, "$protocol/$port firewall opening will be added to $computer_node_name, scope: $scope"
+		);
+	}
+	
+	$name = "VCL: allow $protocol/$port from $scope" if !$name;
+	$description = "VCL: allow $protocol/$port from $scope" if !$description;
+	
+	$name = substr($name, 0, 60) . "..." if length($name) > 60;
+	
+	if ($self->_enable_firewall_port_helper($protocol, $port, $scope, $overwrite_existing, $name, $description)) {
+		$firewall_configuration->{$protocol}{$port} = {
+			name => $name,
+			name => $description,
+			scope => $scope,
+		};
+		
+		return 1;
+	}
+	else {
+		return;
+	}
+}
+	
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _enable_firewall_port_helper
+
+ Parameters  : 
+ Returns     : 1 if succeeded, 0 otherwise
+ Description : 
+
+=cut
+
+sub _enable_firewall_port_helper {
+	my $self = shift;
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my ($protocol, $port, $scope, $overwrite_existing, $name, $description) = @_;
+	if (!defined($protocol) || !defined($port) || !defined($scope) || !defined($name)) {
+		notify($ERRORS{'WARNING'}, 0, "protocol and port arguments were not supplied");
+		return;
+	}
+	
+	my $computer_node_name   = $self->data->get_computer_node_name();
+	my $system32_path        = $self->get_system32_path() || return;
+	
+	my $netsh_command;
+	
+	if ($protocol =~ /icmp/i) {
+		$netsh_command .= "$system32_path/netsh.exe firewall set icmpsetting";
+		$netsh_command .= " type = $port";
+		$netsh_command .= " mode = ENABLE";
+		$netsh_command .= " profile = ALL";
+	}
+	else {
+		if ($overwrite_existing) {
+			my $firewall_configuration = $self->get_firewall_configuration() || return;
+			
+			if (defined($firewall_configuration->{$protocol}{$port}{interface_names})) {
+				for my $interface_name (@{$firewall_configuration->{$protocol}{$port}{interface_names}}) {
+					$netsh_command .= "$system32_path/netsh.exe firewall delete portopening";
+					$netsh_command .= " protocol = $protocol";
+					$netsh_command .= " port = $port";
+					$netsh_command .= " interface = \"$interface_name\"";
+					$netsh_command .= " ; ";
+				}
+			}
+		}
+		
+		$netsh_command .= "$system32_path/netsh.exe firewall delete portopening";
+		$netsh_command .= " protocol = $protocol";
+		$netsh_command .= " port = $port";
+		$netsh_command .= " ; ";
+		
+		$netsh_command .= "$system32_path/netsh.exe firewall set portopening";
+		$netsh_command .= " name = \"$name\"";
+		$netsh_command .= " protocol = $protocol";
+		$netsh_command .= " port = $port";
+		$netsh_command .= " mode = ENABLE";
+	}
+	
+	if ($scope eq '0.0.0.0/0.0.0.0') {
+		$netsh_command .= " scope = ALL";
+	}
+	else {
+		$netsh_command .= " scope = CUSTOM";
+		$netsh_command .= " addresses = $scope";
+	}
+
+	# Execute the netsh.exe command
+	my ($netsh_exit_status, $netsh_output) = $self->execute($netsh_command);
+	
+	if (!defined($netsh_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to open firewall on $computer_node_name, command: '$netsh_command'");
+		return;
+	}
+	elsif (@$netsh_output[-1] =~ /(Ok|The object already exists)/i) {
+		notify($ERRORS{'OK'}, 0, "opened firewall on $computer_node_name:\n" .
+				 "name: '$name'\n" .
+				 "protocol: $protocol\n" .
+				 "port/type: $port\n" .
+				 "scope: $scope"
+		);
+		return 1;
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to open firewall on $computer_node_name:\n" .
+			"name: '$name'\n" .
+			"protocol: $protocol\n" .
+			"port/type: $port\n" .
+			"command : '$netsh_command'\n" .
+			"exit status: $netsh_exit_status\n" .
+			"output:\n" . join("\n", @$netsh_output)
+		);
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 firewall_enable_ping
 
  Parameters  : 

Modified: incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_6.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_6.pm?rev=1179199&r1=1179198&r2=1179199&view=diff
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_6.pm (original)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows/Version_6.pm Wed Oct  5 12:40:33 2011
@@ -1435,6 +1435,271 @@ sub get_firewall_state {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 get_firewall_configuration
+
+ Parameters  : none
+ Returns     : hash reference
+ Description : Retrieves information about the open firewall ports on the
+               computer and constructs a hash. The hash keys are protocol names.
+               Each protocol key contains a hash reference. The keys are either
+               port numbers or ICMP types.
+               Example:
+               "ICMP" => {
+                 8 => {
+                   "description" => "VCL: allow ICMP/8 from 10.10.14.14",
+                   "local_ip" => "Any",
+                   "name" => "VCL: allow ICMP/8 from 10.10.14.14",
+                   "scope" => "10.10.14.14/32"
+                 }
+               },
+               "TCP" => {
+                 3389 => {
+                   "description" => "Allows incoming TCP port 3389 traffic",
+                   "local_ip" => "Any",
+                   "name" => "VCL: allow RDP port 3389",
+                   "scope" => "Any"
+                 },
+               },
+
+=cut
+
+sub get_firewall_configuration {
+	my $self = shift;
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	return $self->{firewall_configuration} if $self->{firewall_configuration};
+	
+	my $computer_node_name = $self->data->get_computer_node_name();
+	my $system32_path = $self->get_system32_path() || return;
+	
+	my $firewall_configuration;
+	
+	my $command = "$system32_path/netsh.exe advfirewall firewall show rule name=all verbose";
+	my ($exit_status, $output) = $self->execute($command);
+	if (!defined($output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to run command to show firewall rules on $computer_node_name");
+		return;
+	}
+	elsif (!grep(/Rule Name:/i, @$output)) {
+		notify($ERRORS{'WARNING'}, 0, "unexpected output returned from command to show firewall rules on $computer_node_name, command: '$command', exit status: $exit_status, output:\n" . join("\n", @$output));
+		return;
+	}
+	
+	# Execute the netsh.exe command to retrieve firewall rules
+	#   Rule Name:                            VCL: allow RDP port 3389
+	#   ----------------------------------------------------------------------
+	#   Enabled:                              Yes
+	#   Direction:                            In
+	#   Profiles:                             Domain,Private,Public
+	#   Grouping:
+	#   LocalIP:                              Any
+	#   RemoteIP:                             152.14.53.0/26,10.10.1.2-10.10.2.22
+	#   Protocol:                             TCP
+	#   LocalPort:                            3389
+	#   RemotePort:                           Any
+	#   Edge traversal:                       No
+	#   Action:                               Allow
+	#   Rule Name:                            VCL: allow ping to/from any address
+	#   ----------------------------------------------------------------------
+	#   Enabled:                              Yes
+	#   Direction:                            In
+	#   Profiles:                             Domain,Private,Public
+	#   Grouping:
+	#   LocalIP:                              Any
+	#   RemoteIP:                             Any
+	#   Protocol:                             ICMPv4
+	#                                         Type    Code
+	#                                         8       Any
+	#   Edge traversal:                       No
+	#   Action:                               Allow
+	
+	# Split the output into rule sections
+	my @rule_sections = split(/Rule Name:\s*/, join("\n", @$output));
+	
+	RULE: for my $rule_section (@rule_sections) {
+		my @lines = split(/\n+/, $rule_section);
+		
+		my $rule_name = shift(@lines);
+		
+		# The first rule section will probably be blank because of the way split works
+		next RULE if (!$rule_name);
+		
+		my $rule_info;
+		for my $line (@lines) {
+			if (my ($parameter, $value) = $line =~ /^(\w+):\s*(.*)/g) {
+				$rule_info->{$parameter} = $value;
+			}
+			elsif ($rule_info->{Protocol} && $rule_info->{Protocol} =~ /icmp/i) {
+				if (my ($icmp_type, $icmp_code) = $line =~ /^\s*(\d+)\s+(.*)/g) {
+					push @{$rule_info->{ICMPTypes}{$icmp_type}}, $icmp_code;
+				}
+			}
+		}
+		
+		if (!defined($rule_info->{Enabled}) || $rule_info->{Enabled} !~ /yes/i) {
+			#notify($ERRORS{'DEBUG'}, 0, "ignoring disabled rule: '$rule_name'");
+			next RULE;
+		}
+		if (!defined($rule_info->{Direction}) || $rule_info->{Direction} !~ /in/i) {
+			#notify($ERRORS{'DEBUG'}, 0, "ignoring outgoing rule: '$rule_name'");
+			next RULE;
+		}
+		elsif (!defined($rule_info->{Action}) || $rule_info->{Action} !~ /allow/i) {
+			#notify($ERRORS{'DEBUG'}, 0, "ignoring rule: '$rule_name', Action is NOT allow");
+			next RULE;
+		}
+		elsif (!defined($rule_info->{Protocol})) {
+			#notify($ERRORS{'DEBUG'}, 0, "ignoring rule: '$rule_name', Protocol is not defined:\n$rule_section");
+			next RULE;
+		}
+		
+		my @ports;
+		
+		if ($rule_info->{Protocol} =~ /icmp/i) {
+			if (!defined($rule_info->{ICMPTypes})) {
+				notify($ERRORS{'DEBUG'}, 0, "ignoring rule: '$rule_name', ICMP type could not be determined:\n$rule_section");
+				next RULE;
+			}
+			
+			@ports = sort keys(%{$rule_info->{ICMPTypes}})
+		}
+		else {
+			if (!defined($rule_info->{LocalPort})) {
+				notify($ERRORS{'DEBUG'}, 0, "ignoring rule: '$rule_name', LocalPort is not defined");
+				next RULE;
+			}
+			
+			@ports = split(",", $rule_info->{LocalPort});
+		}
+		
+		if (!@ports) {
+			notify($ERRORS{'WARNING'}, 0, "ignoring rule: '$rule_name', no ports defined:\n" . format_data($rule_info) . "\n$rule_section");
+			next RULE;
+		}
+		
+		for my $port (@ports) {
+			$firewall_configuration->{$rule_info->{Protocol}}{$port}{name} = $rule_name;
+			$firewall_configuration->{$rule_info->{Protocol}}{$port}{description} = $rule_info->{Description};
+			$firewall_configuration->{$rule_info->{Protocol}}{$port}{scope} = $rule_info->{RemoteIP};
+			$firewall_configuration->{$rule_info->{Protocol}}{$port}{local_ip} = $rule_info->{LocalIP};
+		}
+		
+	}
+	
+	# Copy the ICMPv4 key to one named ICMP for compatibility
+	if (defined($firewall_configuration->{ICMPv4})) {
+		$firewall_configuration->{ICMP} = $firewall_configuration->{ICMPv4};
+	}
+	
+	$self->{firewall_configuration} = $firewall_configuration;
+	
+	notify($ERRORS{'DEBUG'}, 0, "retrieved firewall info from $computer_node_name:\n" . format_data($firewall_configuration));
+	return $firewall_configuration;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _enable_firewall_port_helper
+
+ Parameters  : 
+ Returns     : boolean
+ Description : This subroutine is called by enable_firewall_port. It runs the
+               necessary 'netsh advfirewall' command to configure the firewall.
+
+=cut
+
+sub _enable_firewall_port_helper {
+	my $self = shift;
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my ($protocol, $port, $scope, $overwrite_existing, $name, $description) = @_;
+	if (!defined($protocol) || !defined($port) || !defined($scope) || !defined($name)) {
+		notify($ERRORS{'WARNING'}, 0, "protocol, port, scope, and name arguments were not supplied");
+		return;
+	}
+	
+	my $computer_node_name   = $self->data->get_computer_node_name();
+	my $system32_path        = $self->get_system32_path() || return;
+	
+	$scope = 'any' if $scope eq '0.0.0.0/0.0.0.0';
+	
+	my $netsh_command;
+	
+	if ($protocol =~ /icmp/i) {
+		$netsh_command .= "$system32_path/netsh.exe advfirewall firewall delete rule";
+		$netsh_command .= " name=all";
+		$netsh_command .= " dir=in";
+		$netsh_command .= " protocol=icmpv4:$port,any";
+		$netsh_command .= " ; ";
+		
+		$netsh_command .= " $system32_path/netsh.exe advfirewall firewall add rule";
+		$netsh_command .= " name=\"$name\"";
+		$netsh_command .= " description=\"$description\"";
+		$netsh_command .= " protocol=icmpv4:$port,any";
+		$netsh_command .= " action=allow";
+		$netsh_command .= " enable=yes";
+		$netsh_command .= " dir=in";
+		$netsh_command .= " localip=any";
+		$netsh_command .= " remoteip=$scope";
+	}
+	else {
+		$netsh_command .= "$system32_path/netsh.exe advfirewall firewall delete rule";
+		$netsh_command .= " name=all";
+		$netsh_command .= " dir=in";
+		$netsh_command .= " protocol=$protocol";
+		$netsh_command .= " localport=$port";
+		$netsh_command .= " ;";
+		
+		$netsh_command .= " $system32_path/netsh.exe advfirewall firewall add rule";
+		$netsh_command .= " name=\"$name\"";
+		$netsh_command .= " description=\"$description\"";
+		$netsh_command .= " protocol=$protocol";
+		$netsh_command .= " action=allow";
+		$netsh_command .= " enable=yes";
+		$netsh_command .= " dir=in";
+		$netsh_command .= " localip=any";
+		$netsh_command .= " localport=$port";
+		$netsh_command .= " remoteip=$scope";
+	}
+
+	# Execute the netsh.exe command
+	my ($netsh_exit_status, $netsh_output) = $self->execute($netsh_command, 1);
+	
+	if (!defined($netsh_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to open firewall on $computer_node_name, command: '$netsh_command'");
+		return;
+	}
+	elsif (@$netsh_output[-1] =~ /(Ok|The object already exists)/i) {
+		notify($ERRORS{'OK'}, 0, "opened firewall on $computer_node_name:\n" .
+				 "name: '$name'\n" .
+				 "protocol: $protocol\n" .
+				 "port/type: $port\n" .
+				 "scope: $scope"
+		);
+		return 1;
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to open firewall on $computer_node_name:\n" .
+			"name: '$name'\n" .
+			"protocol: $protocol\n" .
+			"port/type: $port\n" .
+			"scope: $scope\n" .
+			"command : '$netsh_command'" .
+			"exit status: $netsh_exit_status\n" .
+			"output:\n" . join("\n", @$netsh_output)
+		);
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 run_sysprep
 
  Parameters  : None