| #!/usr/bin/perl -w |
| ############################################################################### |
| # $Id$ |
| ############################################################################### |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| ############################################################################### |
| |
| =head1 NAME |
| |
| VCL::Module::OS::Windows - Windows OS support module |
| |
| =head1 SYNOPSIS |
| |
| Needs to be written |
| |
| =head1 DESCRIPTION |
| |
| This module provides... |
| |
| =cut |
| |
| ############################################################################## |
| package VCL::Module::OS::Windows; |
| |
| # Specify the lib path using FindBin |
| use FindBin; |
| use lib "$FindBin::Bin/../../.."; |
| |
| # Configure inheritance |
| #use base qw(VCL::Module::OS::Windows); |
| use base qw(VCL::Module::OS); |
| |
| # Specify the version of this module |
| our $VERSION = '2.2.2'; |
| |
| # Specify the version of Perl to use |
| use 5.008000; |
| |
| use strict; |
| use warnings; |
| use diagnostics; |
| use English '-no_match_vars'; |
| use VCL::utils; |
| use File::Basename; |
| |
| ############################################################################## |
| |
| =head1 CLASS VARIABLES |
| |
| =cut |
| |
| =head2 $SOURCE_CONFIGURATION_DIRECTORY |
| |
| Data type : String |
| Description : Location on the management node of the files specific to this OS |
| module which are needed to configure the loaded OS on a computer. |
| This is normally the directory under 'tools' named after this OS |
| module. |
| |
| Example: |
| /usr/local/vcl/tools/Windows |
| |
| =cut |
| |
| our $SOURCE_CONFIGURATION_DIRECTORY = "$TOOLS/Windows"; |
| |
| |
| =head2 $NODE_CONFIGURATION_DIRECTORY |
| |
| Data type : String |
| Description : Location on computer on which an image has been loaded where |
| configuration files reside. The files residing on the managment |
| node in the directory specified by $NODE_CONFIGURATION_DIRECTORY |
| are copied to this directory. |
| |
| Example: |
| C:\Cygwin\home\root\VCL |
| |
| =cut |
| |
| our $NODE_CONFIGURATION_DIRECTORY = 'C:/Cygwin/home/root/VCL'; |
| |
| |
| =head2 %TIME_ZONE_INFO |
| |
| Data type : Hash |
| Description : Windows time zone code information. The hash keys are the |
| numerical Windows time zone codes used for things such as |
| Sysprep. |
| |
| =cut |
| |
| our %TIME_ZONE_INFO = ( |
| 'Afghanistan Standard Time' => {'abbreviation' => 'KAB', 'offset' => '+04:30', 'code' => '175'}, |
| 'Alaskan Standard Time' => {'abbreviation' => 'ALA', 'offset' => '-09:00', 'code' => '3'}, |
| 'Arab Standard Time' => {'abbreviation' => 'BKR', 'offset' => '+03:00', 'code' => '150'}, |
| 'Arabian Standard Time' => {'abbreviation' => 'ABT', 'offset' => '+04:00', 'code' => '165'}, |
| 'Arabic Standard Time' => {'abbreviation' => 'BKR', 'offset' => '+03:00', 'code' => '158'}, |
| 'Atlantic Standard Time' => {'abbreviation' => 'AST', 'offset' => '-04:00', 'code' => '50'}, |
| 'AUS Central Standard Time' => {'abbreviation' => 'ADA', 'offset' => '+09:30', 'code' => '245'}, |
| 'AUS Eastern Standard Time' => {'abbreviation' => 'CMS', 'offset' => '+10:00', 'code' => '255'}, |
| 'Azerbaijan Standard Time' => {'abbreviation' => undef, 'offset' => '+04:00', 'code' => undef}, |
| 'Azores Standard Time' => {'abbreviation' => 'AZO', 'offset' => '-01:00', 'code' => '80'}, |
| 'Canada Central Standard Time' => {'abbreviation' => 'CST', 'offset' => '-06:00', 'code' => '25'}, |
| 'Cape Verde Standard Time' => {'abbreviation' => 'AZO', 'offset' => '-01:00', 'code' => '83'}, |
| 'Caucasus Standard Time' => {'abbreviation' => 'ABT', 'offset' => '+04:00', 'code' => '170'}, |
| 'Cen. Australia Standard Time' => {'abbreviation' => 'ADA', 'offset' => '+09:30', 'code' => '250'}, |
| 'Central America Standard Time' => {'abbreviation' => 'CST', 'offset' => '-06:00', 'code' => '33'}, |
| 'Central Asia Standard Time' => {'abbreviation' => 'ADC', 'offset' => '+06:00', 'code' => '195'}, |
| 'Central Brazilian Standard Time' => {'abbreviation' => undef, 'offset' => '-04:00', 'code' => undef}, |
| 'Central Europe Standard Time' => {'abbreviation' => 'AMS', 'offset' => '+01:00', 'code' => '95'}, |
| 'Central European Standard Time' => {'abbreviation' => 'AMS', 'offset' => '+01:00', 'code' => '100'}, |
| 'Central Pacific Standard Time' => {'abbreviation' => 'MSN', 'offset' => '+11:00', 'code' => '280'}, |
| 'Central Standard Time' => {'abbreviation' => 'CST', 'offset' => '-06:00', 'code' => '20'}, |
| 'Central Standard Time (Mexico)' => {'abbreviation' => 'CST', 'offset' => '-06:00', 'code' => '30'}, |
| 'China Standard Time' => {'abbreviation' => 'SST', 'offset' => '+08:00', 'code' => '210'}, |
| 'Dateline Standard Time' => {'abbreviation' => 'IDLE', 'offset' => '-12:00', 'code' => '0'}, |
| 'E. Africa Standard Time' => {'abbreviation' => 'BKR', 'offset' => '+03:00', 'code' => '155'}, |
| 'E. Australia Standard Time' => {'abbreviation' => 'BGP', 'offset' => '+10:00', 'code' => '260'}, |
| 'E. Europe Standard Time' => {'abbreviation' => 'BCP', 'offset' => '+02:00', 'code' => '115'}, |
| 'E. South America Standard Time' => {'abbreviation' => 'BBA', 'offset' => '-03:00', 'code' => '65'}, |
| 'Eastern Standard Time' => {'abbreviation' => 'EST', 'offset' => '-05:00', 'code' => '35'}, |
| 'Egypt Standard Time' => {'abbreviation' => 'BCP', 'offset' => '+02:00', 'code' => '120'}, |
| 'Ekaterinburg Standard Time' => {'abbreviation' => 'EIK', 'offset' => '+05:00', 'code' => '180'}, |
| 'Fiji Standard Time' => {'abbreviation' => 'FKM', 'offset' => '+12:00', 'code' => '285'}, |
| 'FLE Standard Time' => {'abbreviation' => 'HRI', 'offset' => '+02:00', 'code' => '125'}, |
| 'Georgian Standard Time' => {'abbreviation' => undef, 'offset' => '+04:00', 'code' => undef}, |
| 'GMT Standard Time' => {'abbreviation' => 'GMT', 'offset' => '+00:00', 'code' => '85'}, |
| 'Greenland Standard Time' => {'abbreviation' => 'BBA', 'offset' => '-03:00', 'code' => '73'}, |
| 'Greenwich Standard Time' => {'abbreviation' => 'GMT', 'offset' => '+00:00', 'code' => '90'}, |
| 'GTB Standard Time' => {'abbreviation' => 'AIM', 'offset' => '+02:00', 'code' => '130'}, |
| 'Hawaiian Standard Time' => {'abbreviation' => 'HAW', 'offset' => '-10:00', 'code' => '2'}, |
| 'India Standard Time' => {'abbreviation' => 'BCD', 'offset' => '+05:30', 'code' => '190'}, |
| 'Iran Standard Time' => {'abbreviation' => 'THE', 'offset' => '+03:30', 'code' => '160'}, |
| 'Israel Standard Time' => {'abbreviation' => 'BCP', 'offset' => '+02:00', 'code' => '135'}, |
| 'Korea Standard Time' => {'abbreviation' => 'SYA', 'offset' => '+09:00', 'code' => '230'}, |
| 'Mid-Atlantic Standard Time' => {'abbreviation' => 'MAT', 'offset' => '-02:00', 'code' => '75'}, |
| 'Mountain Standard Time' => {'abbreviation' => 'MST', 'offset' => '-07:00', 'code' => '10'}, |
| 'Mountain Standard Time (Mexico)' => {'abbreviation' => 'MST', 'offset' => '-07:00', 'code' => '13'}, |
| 'Myanmar Standard Time' => {'abbreviation' => 'MMT', 'offset' => '+06:30', 'code' => '203'}, |
| 'N. Central Asia Standard Time' => {'abbreviation' => 'ADC', 'offset' => '+06:00', 'code' => '201'}, |
| 'Namibia Standard Time' => {'abbreviation' => undef, 'offset' => '+02:00', 'code' => undef}, |
| 'Nepal Standard Time' => {'abbreviation' => 'NPT', 'offset' => '+05:45', 'code' => '193'}, |
| 'New Zealand Standard Time' => {'abbreviation' => 'AWE', 'offset' => '+12:00', 'code' => '290'}, |
| 'Newfoundland Standard Time' => {'abbreviation' => 'NWF', 'offset' => '-03:30', 'code' => '60'}, |
| 'North Asia East Standard Time' => {'abbreviation' => 'SST', 'offset' => '+08:00', 'code' => '227'}, |
| 'North Asia Standard Time' => {'abbreviation' => 'BHJ', 'offset' => '+07:00', 'code' => '207'}, |
| 'Pacific SA Standard Time' => {'abbreviation' => 'AST', 'offset' => '-04:00', 'code' => '56'}, |
| 'Pacific Standard Time' => {'abbreviation' => 'PST', 'offset' => '-08:00', 'code' => '4'}, |
| 'Romance Standard Time' => {'abbreviation' => 'AMS', 'offset' => '+01:00', 'code' => '105'}, |
| 'Russian Standard Time' => {'abbreviation' => 'MSV', 'offset' => '+03:00', 'code' => '145'}, |
| 'SA Eastern Standard Time' => {'abbreviation' => 'BBA', 'offset' => '-03:00', 'code' => '70'}, |
| 'SA Pacific Standard Time' => {'abbreviation' => 'EST', 'offset' => '-05:00', 'code' => '45'}, |
| 'SA Western Standard Time' => {'abbreviation' => 'AST', 'offset' => '-04:00', 'code' => '55'}, |
| 'Samoa Standard Time' => {'abbreviation' => 'MIS', 'offset' => '-11:00', 'code' => '1'}, |
| 'SE Asia Standard Time' => {'abbreviation' => 'BHJ', 'offset' => '+07:00', 'code' => '205'}, |
| 'Singapore Standard Time' => {'abbreviation' => 'SST', 'offset' => '+08:00', 'code' => '215'}, |
| 'South Africa Standard Time' => {'abbreviation' => 'BCP', 'offset' => '+02:00', 'code' => '140'}, |
| 'Sri Lanka Standard Time' => {'abbreviation' => 'ADC', 'offset' => '+06:00', 'code' => '200'}, |
| 'Taipei Standard Time' => {'abbreviation' => 'SST', 'offset' => '+08:00', 'code' => '220'}, |
| 'Tasmania Standard Time' => {'abbreviation' => 'HVL', 'offset' => '+10:00', 'code' => '265'}, |
| 'Tokyo Standard Time' => {'abbreviation' => 'OST', 'offset' => '+09:00', 'code' => '235'}, |
| 'Tonga Standard Time' => {'abbreviation' => 'TOT', 'offset' => '+13:00', 'code' => '300'}, |
| 'US Eastern Standard Time' => {'abbreviation' => 'EST', 'offset' => '-05:00', 'code' => '40'}, |
| 'US Mountain Standard Time' => {'abbreviation' => 'MST', 'offset' => '-07:00', 'code' => '15'}, |
| 'Vladivostok Standard Time' => {'abbreviation' => 'HVL', 'offset' => '+10:00', 'code' => '270'}, |
| 'W. Australia Standard Time' => {'abbreviation' => 'SST', 'offset' => '+08:00', 'code' => '225'}, |
| 'W. Central Africa Standard Time' => {'abbreviation' => 'AMS', 'offset' => '+01:00', 'code' => '113'}, |
| 'W. Europe Standard Time' => {'abbreviation' => 'AMS', 'offset' => '+01:00', 'code' => '110'}, |
| 'West Asia Standard Time' => {'abbreviation' => 'EIK', 'offset' => '+05:00', 'code' => '185'}, |
| 'West Pacific Standard Time' => {'abbreviation' => 'BGP', 'offset' => '+10:00', 'code' => '275'}, |
| 'Yakutsk Standard Time' => {'abbreviation' => 'SYA', 'offset' => '+09:00', 'code' => '240'}, |
| ); |
| |
| ############################################################################## |
| |
| =head1 INTERFACE OBJECT METHODS |
| |
| =cut |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 pre_capture |
| |
| Parameters : Hash containing 'end_state' key |
| Returns : If successful: true |
| If failed: false |
| Description : Performs the steps necessary to prepare a Windows OS before an |
| image is captured. |
| This subroutine is called by a provisioning module's capture() |
| subroutine. |
| |
| The steps performed are: |
| |
| =over 3 |
| |
| =cut |
| |
| sub pre_capture { |
| my $self = shift; |
| my $args = 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; |
| } |
| |
| # Check if end_state argument was passed |
| if (defined $args->{end_state}) { |
| $self->{end_state} = $args->{end_state}; |
| } |
| else { |
| $self->{end_state} = 'off'; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| my $image_os_install_type = $self->data->get_image_os_install_type(); |
| |
| notify($ERRORS{'OK'}, 0, "beginning Windows image capture preparation tasks on $computer_node_name"); |
| |
| =item 1 |
| |
| Log off all currently logged in users |
| |
| =cut |
| |
| if (!$self->logoff_users()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to log off all currently logged in users on $computer_node_name"); |
| return 0; |
| } |
| |
| =item * |
| |
| Set root account password to known value |
| |
| =cut |
| |
| if (!$self->set_password('root', $WINDOWS_ROOT_PASSWORD)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set root password"); |
| return 0; |
| } |
| |
| =item * |
| |
| Delete the users assigned to this reservation |
| |
| =cut |
| |
| my $deleted_users = $self->delete_users(); |
| if (!$deleted_users) { |
| notify($ERRORS{'WARNING'}, 0, "unable to delete users, will try again after reboot"); |
| } |
| |
| =item * |
| |
| Set root as the owner of /home/root |
| |
| =cut |
| |
| if (!$self->set_file_owner('/home/root', 'root')) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set root as the owner of /home/root"); |
| return 0; |
| } |
| |
| =item * |
| |
| Copy the capture configuration files to the computer (scripts, utilities, drivers...) |
| |
| =cut |
| |
| if (!$self->copy_capture_configuration_files()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to copy general Windows capture configuration files to $computer_node_name"); |
| return 0; |
| } |
| |
| =item * |
| |
| Apply Windows security templates |
| |
| =cut |
| |
| # This find any .inf security template files configured for the OS and run secedit.exe to apply them |
| if (!$self->apply_security_templates()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to apply security templates"); |
| return 0; |
| } |
| |
| =item * |
| |
| Disable autoadminlogon before disabling the pagefile and rebooting |
| |
| =cut |
| |
| if (!$self->disable_autoadminlogon()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable autoadminlogon"); |
| return 0; |
| } |
| |
| =item * |
| |
| Disable ntsyslog service if it exists on the computer - it can prevent Cygwin sshd from working |
| |
| =cut |
| |
| if ($self->service_exists('ntsyslog') && !$self->set_service_startup_mode('ntsyslog', 'disabled')) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set ntsyslog service startup mode to disabled"); |
| return 0; |
| } |
| |
| =item * |
| |
| Disable dynamic DNS |
| |
| =cut |
| |
| if (!$self->disable_dynamic_dns()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable dynamic dns"); |
| } |
| |
| =item * |
| |
| Disable Shutdown Event Tracker |
| |
| =cut |
| |
| if (!$self->disable_shutdown_event_tracker()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable shutdown event tracker"); |
| } |
| |
| =item * |
| |
| Disable System Restore |
| |
| =cut |
| |
| if (!$self->disable_system_restore()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable system restore"); |
| } |
| |
| =item * |
| |
| Disable hibernation |
| |
| =cut |
| |
| if (!$self->disable_hibernation()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable hibernation"); |
| } |
| |
| =item * |
| |
| Disable sleep |
| |
| =cut |
| |
| if (!$self->disable_sleep()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable sleep"); |
| } |
| |
| =item * |
| |
| Disable Windows Customer Experience Improvement program |
| |
| =cut |
| |
| if (!$self->disable_ceip()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable Windows Customer Experience Improvement program"); |
| } |
| |
| =item * |
| |
| Disable Internet Explorer configuration page |
| |
| =cut |
| |
| if (!$self->disable_ie_configuration_page()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable IE configuration"); |
| } |
| |
| =item * |
| |
| Disable Windows Defender |
| |
| =cut |
| |
| if (!$self->disable_windows_defender()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable Windows Defender"); |
| } |
| |
| =item * |
| |
| Disable Automatic Updates |
| |
| =cut |
| |
| if (!$self->disable_automatic_updates()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable automatic updates"); |
| } |
| |
| =item * |
| |
| Disable Security Center notifications |
| |
| =cut |
| |
| if (!$self->disable_security_center_notifications()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable Security Center notifications"); |
| } |
| |
| =item * |
| |
| Disable login screensaver if computer is a VM |
| |
| =cut |
| |
| if ($image_os_install_type =~ /vm/i) { |
| if (!$self->disable_security_center_notifications()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable Security Center notifications"); |
| } |
| } |
| |
| =item * |
| |
| Enable audio redirection for RDP sessions |
| |
| =cut |
| |
| if (!$self->enable_rdp_audio()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to enable RDP audio redirection"); |
| } |
| |
| =item * |
| |
| Clean up the hard drive |
| |
| =cut |
| |
| if (!$self->clean_hard_drive()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to clean unnecessary files the hard drive"); |
| } |
| |
| =item * |
| |
| Defragment hard drive |
| |
| =cut |
| |
| if (!$self->defragment_hard_drive()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to defragment the hard drive"); |
| } |
| |
| =item * |
| |
| Disable the pagefile, reboot, and delete pagefile.sys |
| |
| ********* node reboots ********* |
| |
| =cut |
| |
| # This will set the registry key to disable the pagefile, reboot, then delete pagefile.sys |
| # Calls the reboot() subroutine, which makes sure ssh service is set to auto and firewall is open for ssh |
| if (!$self->disable_pagefile()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable pagefile"); |
| return 0; |
| } |
| |
| =item * |
| |
| Delete the users assigned to this reservation if attempt before reboot failed |
| |
| =cut |
| |
| if (!$deleted_users && !$self->delete_users()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to delete users after reboot"); |
| return 0; |
| } |
| |
| =item * |
| |
| Disable RDP access from any IP address |
| |
| =cut |
| |
| if (!$self->firewall_disable_rdp()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable RDP from all addresses"); |
| return 0; |
| } |
| |
| =item * |
| |
| Enable SSH access from any IP address |
| |
| =cut |
| |
| if (!$self->firewall_enable_ssh()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to enable SSH from any IP address"); |
| return 0; |
| } |
| |
| =item * |
| |
| Enable ping from any IP address |
| |
| =cut |
| |
| if (!$self->firewall_enable_ping()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to enable ping from any IP address"); |
| return 0; |
| } |
| |
| =item * |
| |
| Reenable the pagefile |
| |
| =cut |
| |
| if (!$self->enable_pagefile()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to reenable pagefile"); |
| return 0; |
| } |
| |
| =item * |
| |
| Set the Cygwin SSHD service startup mode to manual |
| |
| =cut |
| |
| if (!$self->set_service_startup_mode('sshd', 'manual')) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set sshd service startup mode to manual"); |
| return 0; |
| } |
| |
| =back |
| |
| =cut |
| |
| notify($ERRORS{'OK'}, 0, "returning 1"); |
| return 1; |
| |
| } ## end sub pre_capture |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 post_load |
| |
| Parameters : None. |
| Returns : If successful: true |
| If failed: false |
| Description : Performs the steps necessary to configure a Windows OS after an |
| image has been loaded. |
| |
| This subroutine is called by a provisioning module's load() |
| subroutine. |
| |
| The steps performed are: |
| |
| =over 3 |
| |
| =cut |
| |
| sub post_load { |
| 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 $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 |
| |
| =cut |
| |
| if (!$self->wait_for_response(15, 900, 8)) { |
| notify($ERRORS{'WARNING'}, 0, "$computer_node_name never responded to SSH"); |
| return 0; |
| } |
| |
| =item * |
| |
| Wait for root to log off |
| |
| =cut |
| |
| if (!$self->wait_for_logoff('root', 2)) { |
| notify($ERRORS{'WARNING'}, 0, "root account never logged off"); |
| } |
| |
| =item * |
| |
| Log off all currently logged on users |
| |
| Do this in case autoadminlogon was enabled during the load process and the user |
| account was not properly logged off. |
| |
| =cut |
| |
| if (!$self->logoff_users()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to log off all currently logged in users"); |
| } |
| |
| =item * |
| |
| Set root as the owner of /home/root |
| |
| =cut |
| |
| if (!$self->set_file_owner('/home/root', 'root')) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set root as the owner of /home/root"); |
| } |
| |
| =item * |
| |
| Set the Cygwin SSHD service startup mode to automatic |
| |
| The Cygwin SSHD service startup mode should be set to automatic after an image |
| has been loaded and is ready to be reserved. Access will be lost if the service |
| is not set to automatic and the computer is rebooted. |
| |
| =cut |
| |
| if (!$self->set_service_startup_mode('sshd', 'auto')) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set sshd service startup mode to auto"); |
| return 0; |
| } |
| |
| =item * |
| |
| Update the SSH known_hosts file on the management node |
| |
| =cut |
| |
| if (!$self->update_ssh_known_hosts()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to update the SSH known_hosts file on the management node"); |
| } |
| |
| =item * |
| |
| Enable RDP access on the private network interface |
| |
| =cut |
| |
| if (!$self->firewall_enable_rdp_private()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to enable RDP on private network"); |
| return 0; |
| } |
| |
| =item * |
| |
| Enable SSH access on the private network interface |
| |
| =cut |
| |
| if (!$self->firewall_enable_ssh_private()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to enable SSH from private IP address"); |
| return 0; |
| } |
| |
| =item * |
| |
| Enable ping on the private network interface |
| |
| =cut |
| |
| if (!$self->firewall_enable_ping_private()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to enable ping from private IP address"); |
| return 0; |
| } |
| |
| =item * |
| |
| Set persistent public default route |
| |
| =cut |
| |
| if (!$self->set_public_default_route()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set persistent public default route"); |
| } |
| |
| =item * |
| |
| Configure and synchronize time |
| |
| =cut |
| |
| if (!$self->configure_time_synchronization()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to configure and synchronize time"); |
| } |
| |
| =item * |
| |
| Set the "My Computer" description to the image pretty name |
| |
| =cut |
| |
| if (!$self->set_my_computer_name()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to rename My Computer"); |
| } |
| |
| #=item * |
| # |
| #Disable NetBIOS |
| # |
| #=cut |
| # |
| # if (!$self->disable_netbios()) { |
| # notify($ERRORS{'WARNING'}, 0, "failed to disable NetBIOS"); |
| # } |
| |
| #=item * |
| # |
| #Disable dynamic DNS |
| # |
| #=cut |
| # |
| # if (!$self->disable_dynamic_dns()) { |
| # notify($ERRORS{'WARNING'}, 0, "failed to disable dynamic DNS"); |
| # } |
| |
| =item * |
| |
| Remove the Windows root password and other private information from the VCL configuration files |
| |
| =cut |
| |
| if (!$self->sanitize_files()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to sanitize the files on the computer"); |
| return; |
| } |
| |
| =item * |
| |
| Randomize the root account password |
| |
| =cut |
| |
| my $root_random_password = getpw(); |
| if (!$self->set_password('root', $root_random_password)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set random root password"); |
| return 0; |
| } |
| |
| =item * |
| |
| Randomize the Administrator account password |
| |
| =cut |
| |
| my $administrator_random_password = getpw(); |
| if (!$self->set_password('Administrator', $administrator_random_password)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set random Administrator password"); |
| return 0; |
| } |
| |
| =item * |
| |
| Disable sleep |
| |
| =cut |
| |
| if (!$self->disable_sleep()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable sleep"); |
| } |
| |
| =item * |
| |
| Check if the imagemeta postoption is set to reboot, reboot if necessary |
| |
| =cut |
| |
| if ($imagemeta_postoption =~ /reboot/i) { |
| notify($ERRORS{'OK'}, 0, "imagemeta postoption reboot is set for image, rebooting computer"); |
| if (!$self->reboot()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to reboot the computer"); |
| return 0; |
| } |
| } |
| |
| =item * |
| |
| Add a line to currentimage.txt indicating post_load has run |
| |
| =cut |
| |
| $self->set_vcld_post_load_status(); |
| |
| =back |
| |
| =cut |
| |
| notify($ERRORS{'OK'}, 0, "returning 1"); |
| return 1; |
| } ## end sub post_load |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reserve |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub reserve { |
| 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 $request_forimaging = $self->data->get_request_forimaging(); |
| my $reservation_password = $self->data->get_reservation_password(); |
| |
| notify($ERRORS{'OK'}, 0, "beginning Windows reserve tasks"); |
| |
| # Check if this is an imaging request or not |
| if ($request_forimaging) { |
| # Imaging request, don't create account, set the Administrator password |
| if (!$self->set_password('Administrator', $reservation_password)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set password for Administrator account"); |
| return 0; |
| } |
| } |
| else { |
| # Add the users to the computer |
| # The add_users() subroutine will add the primary reservation user and any imagemeta group users |
| if (!$self->add_users()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to add users"); |
| return 0; |
| } |
| } |
| |
| notify($ERRORS{'OK'}, 0, "returning 1"); |
| return 1; |
| } ## end sub reserve |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 sanitize |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub sanitize { |
| 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(); |
| |
| # Revoke access |
| if (!$self->revoke_access()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to revoke access to $computer_node_name"); |
| return 0; |
| } |
| |
| # Delete all users associated with the reservation |
| # This includes the primary reservation user and users listed in imagemeta group if it's configured |
| if ($self->delete_users()) { |
| notify($ERRORS{'OK'}, 0, "users have been deleted from $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete users from $computer_node_name"); |
| return 0; |
| } |
| |
| notify($ERRORS{'OK'}, 0, "$computer_node_name has been sanitized"); |
| return 1; |
| } ## end sub sanitize |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 grant_access |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub grant_access { |
| 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 $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(); |
| my $remote_ip = $self->data->get_reservation_remote_ip(); |
| my $multiple_users = $self->data->get_imagemeta_usergroupmembercount(); |
| my $request_forimaging = $self->data->get_request_forimaging(); |
| |
| # Check to make sure remote IP is defined |
| my $remote_ip_range; |
| if (!$remote_ip) { |
| notify($ERRORS{'WARNING'}, 0, "reservation remote IP address is not set in the data structure, opening RDP to any address"); |
| } |
| elsif ($multiple_users) { |
| notify($ERRORS{'OK'}, 0, "reservation has multiple users, opening RDP to any address"); |
| } |
| elsif ($remote_ip !~ /^(\d{1,3}\.?){4}$/) { |
| notify($ERRORS{'WARNING'}, 0, "reservation remote IP address format is invalid: $remote_ip, opening RDP to any address"); |
| } |
| else { |
| # Assemble the IP range string in CIDR notation |
| $remote_ip_range = "$remote_ip/16"; |
| notify($ERRORS{'OK'}, 0, "RDP will be allowed from $remote_ip_range on $computer_node_name"); |
| } |
| |
| # Set the $remote_ip_range variable to the string 'all' if it isn't already set (for display purposes) |
| $remote_ip_range = 'all' if !$remote_ip_range; |
| |
| # Allow RDP connections |
| if ($self->firewall_enable_rdp($remote_ip_range)) { |
| notify($ERRORS{'OK'}, 0, "firewall was configured to allow RDP access from $remote_ip_range on $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "firewall could not be configured to grant RDP access from $remote_ip_range on $computer_node_name"); |
| return 0; |
| } |
| |
| # If this is an imaging request, make sure the Administrator account is enabled |
| if ($request_forimaging) { |
| notify($ERRORS{'DEBUG'}, 0, "imaging request, making sure Administrator account is enabled"); |
| if ($self->enable_user('Administrator')) { |
| notify($ERRORS{'OK'}, 0, "Administrator account is enabled for imaging request"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to enable Administrator account for imaging request"); |
| return 0; |
| } |
| } ## end if ($request_forimaging) |
| |
| # Delete legacy VCL logon/logoff scripts |
| if (!$self->delete_files_by_pattern("$system32_path/GroupPolicy/User/Scripts", ".*VCL.*cmd", 2)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete legacy VCL logon and logoff scripts"); |
| } |
| |
| notify($ERRORS{'OK'}, 0, "access has been granted for reservation on $computer_node_name"); |
| return 1; |
| } ## end sub grant_access |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 revoke_access |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub revoke_access { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Disallow RDP connections |
| if ($self->firewall_disable_rdp()) { |
| notify($ERRORS{'OK'}, 0, "firewall was configured to deny RDP access on $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "firewall could not be configured to deny RDP access on $computer_node_name"); |
| return 0; |
| } |
| |
| notify($ERRORS{'OK'}, 0, "access has been revoked to $computer_node_name"); |
| return 1; |
| } ## end sub revoke_access |
| |
| ############################################################################## |
| |
| =head1 AUXILIARY OBJECT METHODS |
| |
| =cut |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 create_directory |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub create_directory { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $path = shift; |
| if (!$path) { |
| notify($ERRORS{'WARNING'}, 0, "directory path argument was not specified"); |
| return; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to create directory: '$path'"); |
| |
| # Assemble the Windows shell mkdir command and execute it |
| my $mkdir_command = "cmd.exe /c \"mkdir \\\"$path\\\"\""; |
| my ($mkdir_exit_status, $mkdir_output) = run_ssh_command($computer_node_name, $management_node_keys, $mkdir_command, '', '', 1); |
| |
| if (!defined($mkdir_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to create directory on $computer_node_name: $path"); |
| return; |
| } |
| elsif ($mkdir_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "created directory on $computer_node_name: '$path'"); |
| } |
| elsif (grep(/already exists/i, @$mkdir_output)) { |
| notify($ERRORS{'OK'}, 0, "directory already exists on $computer_node_name: '$path'"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to create directory on $computer_node_name: '$path', exit status: $mkdir_exit_status, output:\n" . join("\n", @$mkdir_output)); |
| } |
| |
| # Make sure directory was created |
| if (!$self->file_exists($path)) { |
| notify($ERRORS{'WARNING'}, 0, "directory does not exist on $computer_node_name: '$path'"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "verified directory exists on $computer_node_name: '$path'"); |
| return 1; |
| } |
| } ## end sub create_directory |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_file |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub delete_file { |
| 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 $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; |
| |
| # Get file path subroutine argument |
| my $path_argument = shift; |
| if (!$path_argument) { |
| notify($ERRORS{'WARNING'}, 0, "file path was not specified as an argument"); |
| return; |
| } |
| |
| # Check if file exists before attempting to delete it |
| if (!$self->file_exists($path_argument)) { |
| notify($ERRORS{'OK'}, 0, "failed not deleted because it does not exist: '$path_argument'"); |
| return 1; |
| } |
| |
| my $path_unix = $self->format_path_unix($path_argument); |
| my $path_dos = $self->format_path_dos($path_argument); |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to delete file: '$path_argument'"); |
| |
| # Assemble a set of commands concatenated together |
| # Try to take ownership, set the permissions, then delete the file using both Cygwin bash and Windows commands |
| # This should allow files to be deleted with restrictive ownership, permissions, and attributes |
| |
| my $command; |
| $command .= "echo ---"; |
| $command .= " ; echo Calling chown.exe to change owner to root..."; |
| $command .= " ; /usr/bin/chown.exe -Rv root $path_unix 2>&1"; |
| |
| $command .= " ; echo ---"; |
| $command .= " ; echo Calling chmod.exe to change permissions to 777..."; |
| $command .= " ; /usr/bin/chmod.exe -Rv 777 $path_unix 2>&1"; |
| |
| $command .= " ; echo ---"; |
| $command .= " ; echo Calling \\\"rm.exe -rfv $path_unix\\\" to to delete file..."; |
| $command .= " ; /usr/bin/rm.exe -rfv $path_unix 2>&1"; |
| |
| # Add call to rmdir if the path does not contain a wildcard |
| # rmdir does not accept wildcards |
| if ($path_dos !~ /\*/) { |
| $command .= " ; echo ---"; |
| $command .= " ; echo Calling \\\"cmd.exe /c rmdir $path_dos\\\" to to delete directory..."; |
| $command .= " ; cmd.exe /c \"rmdir /s /q \\\"$path_dos\\\"\" 2>&1"; |
| } |
| |
| $command .= " ; echo ---"; |
| $command .= " ; echo Calling \\\"cmd.exe /c del $path_dos\\\" to to delete file..."; |
| $command .= " ; cmd.exe /c \"del /s /q /f /a \\\"$path_dos\\\" 2>&1\" 2>&1"; |
| |
| $command .= " ; echo ---"; |
| $command .= " ; echo Calling \\\"cmd.exe /c dir $path_dos\\\" to to list remaining files..."; |
| $command .= " ; cmd.exe /c \"dir /a /w \\\"$path_dos\\\"\" 2>&1"; |
| |
| $command .= " ; echo ---"; |
| $command .= " ; date +%r"; |
| |
| |
| # Run the command |
| my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 0); |
| if (!defined($exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to delete file: '$path_argument'"); |
| return; |
| } |
| |
| ## Sleep 1 second before checking if file was deleted |
| #sleep 1; |
| |
| # Check if file was deleted |
| if ($self->file_exists($path_argument)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete file, it still exists: '$path_argument', command:\n$command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "deleted file: '$path_argument'"); |
| return 1; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 move_file |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub move_file { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Get file path subroutine arguments |
| my $source_path = shift; |
| my $destination_path = shift; |
| if (!$source_path) { |
| notify($ERRORS{'WARNING'}, 0, "file source path was not specified as an argument"); |
| return; |
| } |
| if (!$destination_path) { |
| notify($ERRORS{'WARNING'}, 0, "file destination path was not specified as an argument"); |
| return; |
| } |
| |
| # Replace backslashes with forward slashes |
| $source_path =~ s/\\+/\//gs; |
| $destination_path =~ s/\\+/\//gs; |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to move file: $source_path --> $destination_path"); |
| |
| # Assemble the Windows shell move command and execute it |
| my $move_command = "mv -fv \"$source_path\" \"$destination_path\""; |
| my ($move_exit_status, $move_output) = run_ssh_command($computer_node_name, $management_node_keys, $move_command, '', '', 1); |
| if (defined($move_exit_status) && $move_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "file moved: $source_path --> $destination_path, output:\n@{$move_output}"); |
| } |
| elsif ($move_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to move file: $source_path --> $destination_path, exit status: $move_exit_status, output:\n@{$move_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to move file: $source_path --> $destination_path"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_files_by_pattern |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub delete_files_by_pattern { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $base_directory = shift; |
| my $pattern = shift; |
| my $max_depth = shift || '5'; |
| |
| # Make sure base directory and pattern were specified |
| if (!($base_directory && $pattern)) { |
| notify($ERRORS{'WARNING'}, 0, "base directory and pattern must be specified as arguments"); |
| return; |
| } |
| |
| # Check if the path begins with an environment variable and extract it |
| my ($base_directory_variable) = $base_directory =~ /(\$[^\/\\]*)/g; |
| |
| # Remove trailing slashes from base directory |
| $base_directory =~ s/[\/\\]*$/\//; |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to delete files under $base_directory matching pattern $pattern, max depth: $max_depth"); |
| |
| # Assemble command |
| # Use find to locate all the files under the base directory matching the pattern specified |
| my $command = "/bin/find.exe \"$base_directory\" -mindepth 1 -maxdepth $max_depth -iregex \"$pattern\""; |
| $command .= " -exec chown -R root {} \\;"; |
| $command .= " -exec chmod -R 777 {} \\;"; |
| $command .= " -exec rm -rvf {} \\;"; |
| |
| # If the path begins with an environment variable, check if the variable is defined by passing it to cygpath.exe |
| # Unintended files will be deleted if the environment variable is not defined because the base directory would change from "$TEMP/" to "/" |
| if ($base_directory_variable) { |
| $command = "/bin/cygpath.exe \"$base_directory_variable\" && $command"; |
| } |
| |
| my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 1); |
| |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to delete files under $base_directory matching pattern $pattern, command: $command"); |
| return; |
| } |
| elsif (grep(/cygpath:/i, @$output)) { |
| notify($ERRORS{'OK'}, 0, "files not deleted because environment variable is not set: $base_directory_variable"); |
| return; |
| } |
| elsif (grep(/find:.*no such file/i, @$output)) { |
| notify($ERRORS{'OK'}, 0, "files not deleted because base directory does not exist: $base_directory"); |
| return 1; |
| } |
| elsif (grep(/(^Usage:)/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete files under $base_directory matching pattern $pattern\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| my @deleted = grep(/removed /, @$output); |
| my @not_deleted = grep(/cannot remove/, @$output); |
| notify($ERRORS{'OK'}, 0, scalar @deleted . "/" . scalar @not_deleted . " files deleted deleted under '$base_directory' matching '$pattern'"); |
| notify($ERRORS{'DEBUG'}, 0, "files/directories which were deleted:\n" . join("\n", @deleted)) if @deleted; |
| notify($ERRORS{'DEBUG'}, 0, "files/directories which were NOT deleted:\n" . join("\n", @not_deleted)) if @not_deleted; |
| } |
| |
| return 1; |
| } ## end sub delete_files_by_pattern |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 file_exists |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub file_exists { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Get the path from the subroutine arguments and make sure it was passed |
| my $path = shift; |
| if (!$path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to detmine if file exists, path was not specified as an argument"); |
| return; |
| } |
| |
| my $path_dos = $self->format_path_dos($path); |
| |
| # Assemble the dir command and execute it |
| my $dir_command = "cmd.exe /c \"dir /a /b \\\"$path_dos\\\"\""; |
| my ($dir_exit_status, $dir_output) = run_ssh_command($computer_node_name, $management_node_keys, $dir_command, '', '', 0); |
| if (!defined($dir_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to determine if file exists on $computer_node_name: $path"); |
| return; |
| } |
| |
| if ($dir_exit_status == 1 || grep(/(file not found|cannot find)/i, @$dir_output)) { |
| notify($ERRORS{'DEBUG'}, 0, "file does NOT exist on $computer_node_name: '$path'"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "file exists on $computer_node_name: '$path'"); |
| return 1; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_file_owner |
| |
| Parameters : file path, owner |
| Returns : If successful: true |
| If failed: false |
| Description : Recursively sets the owner of the file path. The file path can |
| be a file or directory. The owner must be a valid user account. A |
| group can optionally be specified by appending a semicolon and |
| the group name to the owner. |
| Examples: |
| set_file_owner('/home/root', 'root') |
| set_file_owner('/home/root', 'root:Administrators') |
| |
| =cut |
| |
| sub set_file_owner { |
| 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; |
| } |
| |
| # Get the file path argument |
| my $file_path = shift; |
| if (!$file_path) { |
| notify($ERRORS{'WARNING'}, 0, "file path argument was not specified"); |
| return; |
| } |
| |
| # Get the owner argument |
| my $owner = shift; |
| if (!$owner) { |
| notify($ERRORS{'WARNING'}, 0, "owner argument was not specified"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Run chown |
| my ($chown_exit_status, $chown_output) = run_ssh_command($computer_node_name, $management_node_keys, "/usr/bin/chown.exe -vR \"$owner\" \"$file_path\"", '', '', 0); |
| |
| # Check if exit status is defined - if not, SSH command failed |
| if (!defined($chown_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to set $owner as the owner of $file_path"); |
| return; |
| } |
| |
| # Check if any known error lines exist in the chown output |
| my @chown_error_lines = grep(/(chown:|cannot access|no such file|failed to)/ig, @$chown_output); |
| if (@chown_error_lines) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred setting $owner as the owner of $file_path, error output:\n" . join("\n", @chown_error_lines)); |
| return; |
| } |
| |
| # Make sure an "ownership of" line exists in the chown output |
| my @chown_success_lines = grep(/(ownership of)/ig, @$chown_output); |
| if (@chown_success_lines) { |
| notify($ERRORS{'OK'}, 0, "set $owner as the owner of $file_path, files and directories modified: " . scalar(@chown_success_lines)); |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "$owner is already the owner of $file_path"); |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 logoff_users |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub logoff_users { |
| 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 $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 ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, "$system32_path/qwinsta.exe"); |
| if ($exit_status > 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe on $computer_node_name, exit status: $exit_status, output:\n@{$output}"); |
| return; |
| } |
| elsif (!defined($exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe SSH 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}); |
| return 1 if !@connection_lines; |
| |
| #notify($ERRORS{'OK'}, 0, "connections on $computer_node_name:\n@connection_lines"); |
| # SESSIONNAME USERNAME ID STATE TYPE DEVICE |
| # '> root 0 Disc rdpwd ' |
| # '>rdp-tcp#24 root 0 Active rdpwd ' |
| foreach my $connection_line (@connection_lines) { |
| my ($session_id) = $connection_line =~ /(\d+)\s+(?:Active|Listen|Conn|Disc)/g; |
| my ($session_name) = $connection_line =~ /^\s?>?([^ ]+)/g; |
| |
| # Determine if the session ID or name will be used to kill the session |
| # logoff.exe has trouble killing sessions with ID=0 |
| # Use the ID if it's > 0, otherwise use the session name |
| my $session_identifier; |
| if ($session_id) { |
| $session_identifier = $session_id; |
| } |
| elsif ($session_name) { |
| $session_identifier = $session_name; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "session ID or name could not be determined from line:\n$connection_line"); |
| next; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "attempting to kill connection:\nline: '$connection_line'\nsession identifier: $session_identifier"); |
| |
| #LOGOFF [sessionname | sessionid] [/SERVER:servername] [/V] |
| # sessionname The name of the session. |
| # sessionid The ID of the session. |
| # /SERVER:servername Specifies the Terminal server containing the user |
| # session to log off (default is current). |
| # /V Displays information about the actions performed. |
| # Call logoff.exe, pass it the session |
| my ($logoff_exit_status, $logoff_output) = run_ssh_command($computer_node_name, $management_node_keys, "$system32_path/logoff.exe $session_identifier /V"); |
| if ($logoff_exit_status == 0) { |
| 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}"); |
| } |
| } |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 add_users |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub add_users { |
| 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(); |
| |
| # Attempt to get the user array from the arguments |
| # If no argument was supplied, use the users specified in the DataStructure |
| my $user_array_ref = shift; |
| my @users; |
| if ($user_array_ref) { |
| $user_array_ref = $self->data->get_imagemeta_usergroupmembers(); |
| @users = @{$user_array_ref}; |
| } |
| else { |
| # User list was not specified as an argument |
| # Use the imagemeta group members and the primary reservation user |
| my $user_login_id = $self->data->get_user_login_id(); |
| my $user_group_members = $self->data->get_imagemeta_usergroupmembers(); |
| |
| push @users, $user_login_id; |
| |
| foreach my $user_group_member_uid (keys(%{$user_group_members})) { |
| my $user_group_member_login_id = $user_group_members->{$user_group_member_uid}; |
| push @users, $user_group_member_login_id; |
| } |
| |
| # Remove duplicate users |
| @users = keys %{{map {$_, 1} @users}}; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to add " . scalar @users . " users to $computer_node_name: " . join(", ", @users)); |
| |
| # Attempt to get the password from the arguments |
| # If no argument was supplied, use the password specified in the DataStructure |
| my $password = shift; |
| if (!$password) { |
| $password = $self->data->get_reservation_password(); |
| } |
| |
| # Loop through the users in the imagemeta group and attempt to add them |
| for my $username (@users) { |
| if (!$self->create_user($username, $password)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to add users to $computer_node_name"); |
| return 0; |
| } |
| } |
| |
| notify($ERRORS{'OK'}, 0, "added " . scalar @users . " users to $computer_node_name"); |
| return 1; |
| } ## end sub add_users |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_users |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub delete_users { |
| 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(); |
| |
| # Attempt to get the user array from the arguments |
| # If no argument was supplied, use the users specified in the DataStructure |
| my $user_array_ref = shift; |
| my @users; |
| if ($user_array_ref) { |
| $user_array_ref = $self->data->get_imagemeta_usergroupmembers(); |
| @users = @{$user_array_ref}; |
| } |
| else { |
| # User list was not specified as an argument |
| # Use the imagemeta group members and the primary reservation user |
| my $user_login_id = $self->data->get_user_login_id(); |
| my $user_group_members = $self->data->get_imagemeta_usergroupmembers(); |
| |
| push @users, $user_login_id; |
| |
| foreach my $user_group_member_uid (keys(%{$user_group_members})) { |
| my $user_group_member_login_id = $user_group_members->{$user_group_member_uid}; |
| push @users, $user_group_member_login_id; |
| } |
| |
| # Remove duplicate users |
| @users = keys %{{map {$_, 1} @users}}; |
| } ## end else [ if ($user_array_ref) |
| |
| # Loop through the users and attempt to delete them |
| for my $username (@users) { |
| if (!$self->delete_user($username)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete user $username from $computer_node_name"); |
| return 0; |
| } |
| } |
| |
| notify($ERRORS{'OK'}, 0, "deleted " . scalar @users . " users from $computer_node_name"); |
| return 1; |
| } ## end sub delete_users |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 user_exists |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub user_exists { |
| 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 $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 get the username from the arguments |
| # If no argument was supplied, use the user specified in the DataStructure |
| my $username = shift; |
| if (!$username) { |
| $username = $self->data->get_user_login_id(); |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "checking if user $username exists on $computer_node_name"); |
| |
| # Attempt to query the user account |
| my $query_user_command = "$system32_path/net.exe user \"$username\""; |
| my ($query_user_exit_status, $query_user_output) = run_ssh_command($computer_node_name, $management_node_keys, $query_user_command, '', '', '1'); |
| if (defined($query_user_exit_status) && $query_user_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "user $username exists on $computer_node_name"); |
| return 1; |
| } |
| elsif (defined($query_user_exit_status) && $query_user_exit_status == 2) { |
| notify($ERRORS{'DEBUG'}, 0, "user $username does not exist on $computer_node_name"); |
| return 0; |
| } |
| elsif (defined($query_user_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine if user $username exists on $computer_node_name, exit status: $query_user_exit_status, output:\n@{$query_user_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to determine if user $username exists on $computer_node_name"); |
| return; |
| } |
| } ## end sub user_exists |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 create_user |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub create_user { |
| 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 $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 $imagemeta_rootaccess = $self->data->get_imagemeta_rootaccess(); |
| |
| # Attempt to get the username from the arguments |
| # If no argument was supplied, use the user specified in the DataStructure |
| my $username = shift; |
| my $password = shift; |
| if (!$username) { |
| $username = $self->data->get_user_login_id(); |
| } |
| if (!$password) { |
| $password = $self->data->get_reservation_password(); |
| } |
| |
| # Check if user already exists |
| if ($self->user_exists($username)) { |
| notify($ERRORS{'OK'}, 0, "user $username already exists on $computer_node_name, attempting to delete user"); |
| |
| # Attempt to delete the user |
| if (!$self->delete_user($username)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to add user $username to $computer_node_name, user already exists and could not be deleted"); |
| return 0; |
| } |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to add user $username to $computer_node_name ($password)"); |
| |
| # Attempt to add the user account |
| my $add_user_command = "$system32_path/net.exe user \"$username\" \"$password\" /ADD /EXPIRES:NEVER /COMMENT:\"Account created by VCL\""; |
| $add_user_command .= " && $system32_path/net.exe localgroup \"Remote Desktop Users\" \"$username\" /ADD"; |
| |
| # Add the user to the Administrators group if imagemeta.rootaccess isn't 0 |
| if (defined($imagemeta_rootaccess) && $imagemeta_rootaccess eq '0') { |
| notify($ERRORS{'DEBUG'}, 0, "user will NOT be added to the Administrators group"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "user will be added to the Administrators group"); |
| $add_user_command .= " && $system32_path/net.exe localgroup \"Administrators\" \"$username\" /ADD"; |
| } |
| |
| my ($add_user_exit_status, $add_user_output) = run_ssh_command($computer_node_name, $management_node_keys, $add_user_command, '', '', '1'); |
| if (defined($add_user_exit_status) && $add_user_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "added user $username ($password) to $computer_node_name"); |
| } |
| elsif (defined($add_user_exit_status) && $add_user_exit_status == 2) { |
| notify($ERRORS{'OK'}, 0, "user $username was not added, user already exists"); |
| return 1; |
| } |
| elsif (defined($add_user_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to add user $username to $computer_node_name, exit status: $add_user_exit_status, output:\n@{$add_user_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command add user $username to $computer_node_name"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub create_user |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 add_user_to_group |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub add_user_to_group { |
| 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 $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 get the username from the arguments |
| # If no argument was supplied, use the user specified in the DataStructure |
| my $username = shift; |
| my $group = shift; |
| if (!$username || !$group) { |
| notify($ERRORS{'WARNING'}, 0, "unable to add user to group, arguments were not passed correctly"); |
| return; |
| } |
| |
| # Attempt to add the user to the group using net.exe localgroup |
| my $localgroup_user_command = "$system32_path/net.exe localgroup \"$group\" $username /ADD"; |
| my ($localgroup_user_exit_status, $localgroup_user_output) = run_ssh_command($computer_node_name, $management_node_keys, $localgroup_user_command); |
| if (defined($localgroup_user_exit_status) && $localgroup_user_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "added user $username to \"$group\" group on $computer_node_name"); |
| } |
| elsif (defined($localgroup_user_exit_status) && $localgroup_user_exit_status == 2) { |
| # Exit status is 2, this could mean the user is already a member or that the group doesn't exist |
| # Check the output to determine what happened |
| if (grep(/error 1378/, @{$localgroup_user_output})) { |
| # System error 1378 has occurred. |
| # The specified account name is already a member of the group. |
| notify($ERRORS{'OK'}, 0, "user $username was not added to $group group because user already a member"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to add user $username to $group group on $computer_node_name, exit status: $localgroup_user_exit_status, output:\n@{$localgroup_user_output}"); |
| return 0; |
| } |
| } ## end elsif (defined($localgroup_user_exit_status) ... [ if (defined($localgroup_user_exit_status) ... |
| elsif (defined($localgroup_user_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to add user $username to $group group on $computer_node_name, exit status: $localgroup_user_exit_status, output:\n@{$localgroup_user_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to add user $username to $group group on $computer_node_name"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub add_user_to_group |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_user |
| |
| Parameters : $node, $user, $type, $osname |
| Returns : 1 success 0 failure |
| Description : removes user account and profile directory from specificed node |
| |
| =cut |
| |
| sub delete_user { |
| 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 $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 get the username from the arguments |
| # If no argument was supplied, use the user specified in the DataStructure |
| my $username = shift; |
| if (!(defined($username))) { |
| $username = $self->data->get_user_login_id(); |
| } |
| |
| notify($ERRORS{'OK'}, 0, "attempting to delete user $username from $computer_node_name"); |
| |
| # Attempt to delete the user account |
| my $delete_user_command = "$system32_path/net.exe user $username /DELETE"; |
| my ($delete_user_exit_status, $delete_user_output) = run_ssh_command($computer_node_name, $management_node_keys, $delete_user_command); |
| if (defined($delete_user_exit_status) && $delete_user_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "deleted user $username from $computer_node_name"); |
| } |
| elsif (defined($delete_user_exit_status) && $delete_user_exit_status == 2) { |
| notify($ERRORS{'OK'}, 0, "user $username was not deleted because user does not exist"); |
| } |
| elsif (defined($delete_user_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete user $username from $computer_node_name, exit status: $delete_user_exit_status, output:\n@{$delete_user_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command delete user $username from $computer_node_name"); |
| return; |
| } |
| |
| # Delete the user's home directory |
| if ($self->delete_file("C:/Documents and Settings/$username")) { |
| notify($ERRORS{'OK'}, 0, "deleted profile for user $username from $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete profile for user $username from $computer_node_name"); |
| return 0; |
| } |
| |
| return 1; |
| } ## end sub delete_user |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_password |
| |
| Parameters : $username, $password |
| Returns : |
| Description : |
| |
| =cut |
| sub set_password { |
| 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 $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 get the username from the arguments |
| my $username = shift; |
| my $password = shift; |
| |
| # If no argument was supplied, use the user specified in the DataStructure |
| if (!defined($username)) { |
| $username = $self->data->get_user_logon_id(); |
| } |
| if (!defined($password)) { |
| $password = $self->data->get_reservation_password(); |
| } |
| |
| # Make sure both the username and password were determined |
| if (!defined($username) || !defined($password)) { |
| notify($ERRORS{'WARNING'}, 0, "username and password could not be determined"); |
| return 0; |
| } |
| |
| # 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'"); |
| 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}"); |
| 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; |
| } |
| } |
| |
| # 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}"); |
| } |
| } |
| |
| notify($ERRORS{'OK'}, 0, "changed password for user: $username"); |
| return 1; |
| } ## end sub set_password |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 enable_user |
| |
| Parameters : $username (optional |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub enable_user { |
| 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 $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 get the username from the arguments |
| my $username = shift; |
| |
| # If no argument was supplied, use the user specified in the DataStructure |
| if (!defined($username)) { |
| $username = $self->data->get_user_logon_id(); |
| } |
| |
| # Make sure the username was determined |
| if (!defined($username)) { |
| notify($ERRORS{'WARNING'}, 0, "username could not be determined"); |
| return 0; |
| } |
| |
| # Attempt to enable the user account (set ACTIVE=YES) |
| notify($ERRORS{'DEBUG'}, 0, "enabling user $username on $computer_node_name"); |
| my ($enable_exit_status, $enable_output) = run_ssh_command($computer_node_name, $management_node_keys, "$system32_path/net.exe user $username /ACTIVE:YES"); |
| if ($enable_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "user $username enabled on $computer_node_name"); |
| } |
| elsif ($enable_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to enable user $username on $computer_node_name, exit status: $enable_exit_status, output:\n@{$enable_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to enable user $username on $computer_node_name"); |
| return 0; |
| } |
| |
| return 1; |
| } ## end sub enable_user |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_pagefile |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub disable_pagefile { |
| 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 $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; |
| |
| # Set the registry key to blank |
| my $memory_management_key = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management'; |
| my $reg_add_command = $system32_path . '/reg.exe add "' . $memory_management_key . '" /v PagingFiles /d "" /t REG_MULTI_SZ /f'; |
| my ($reg_add_exit_status, $reg_add_output) = run_ssh_command($computer_node_name, $management_node_keys, $reg_add_command, '', '', 1); |
| if (defined($reg_add_exit_status) && $reg_add_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "set registry key to disable pagefile"); |
| } |
| elsif (defined($reg_add_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set registry key to disable pagefile, exit status: $reg_add_exit_status, output:\n@{$reg_add_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to set registry key to disable pagefile"); |
| return; |
| } |
| |
| # Attempt to reboot the computer in order to delete the pagefile |
| if ($self->reboot()) { |
| notify($ERRORS{'DEBUG'}, 0, "computer was rebooted after disabling pagefile in the registry"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to reboot computer after disabling pagefile"); |
| return; |
| } |
| |
| # Attempt to delete the pagefile from all drives |
| # A pagefile may reside on drives other than C: if additional volumes are configured in the image |
| my @volume_list = $self->get_volume_list(); |
| if (!@volume_list || !(grep(/c/, @volume_list))) { |
| @volume_list = ('c'); |
| } |
| |
| # Loop through the drive letters and attempt to delete pagefile.sys on each drive |
| for my $drive_letter (@volume_list) { |
| if ($self->delete_file("$drive_letter:/pagefile.sys")) { |
| notify($ERRORS{'DEBUG'}, 0, "deleted pagefile.sys on all $drive_letter:"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete pagefile.sys on all $drive_letter:"); |
| return; |
| } |
| } |
| |
| return 1; |
| } ## end sub disable_pagefile |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 enable_pagefile |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub enable_pagefile { |
| 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 $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 $memory_management_key = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management'; |
| |
| my $reg_add_command = $system32_path . '/reg.exe add "' . $memory_management_key . '" /v PagingFiles /d "$SYSTEMDRIVE\\pagefile.sys 0 0" /t REG_MULTI_SZ /f'; |
| my ($reg_add_exit_status, $reg_add_output) = run_ssh_command($computer_node_name, $management_node_keys, $reg_add_command, '', '', 1); |
| if (defined($reg_add_exit_status) && $reg_add_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "set registry key to enable pagefile"); |
| } |
| elsif (defined($reg_add_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set registry key to enable pagefile, exit status: $reg_add_exit_status, output:\n@{$reg_add_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to set registry key to enable pagefile"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub enable_pagefile |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 enable_ipv6 |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub enable_ipv6 { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| ; This registry file contains the entries to disable all IPv6 components |
| ; http://support.microsoft.com/kb/929852 |
| |
| [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters] |
| "DisabledComponents"=dword:00000000 |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "set registry keys to enable IPv6"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the registry keys to enable IPv6"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_ipv6 |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub disable_ipv6 { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| ; This registry file contains the entries to disable all IPv6 components |
| ; http://support.microsoft.com/kb/929852 |
| |
| [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters] |
| "DisabledComponents"=dword:ffffffff |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "set registry keys to disable IPv6"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the registry keys to disable IPv6"); |
| return 0; |
| } |
| |
| return 1; |
| } ## end sub disable_ipv6 |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 import_registry_file |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub import_registry_file { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_file_path = shift; |
| if (!defined($registry_file_path) || !$registry_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "registry file path was not passed correctly as an argument"); |
| return; |
| } |
| |
| my $registry_file_contents = `cat $registry_file_path`; |
| notify($ERRORS{'DEBUG'}, 0, "registry file '$registry_file_path' contents:\n$registry_file_contents"); |
| |
| $registry_file_contents =~ s/([\"])/\\$1/gs; |
| $registry_file_contents =~ s/\\+"/\\"/gs; |
| |
| # Specify where on the node the temporary registry file will reside |
| my $temp_registry_file_path = 'C:/Cygwin/tmp/vcl_import.reg'; |
| |
| # Echo the registry string to a file on the node |
| my $echo_registry_command = "/usr/bin/echo.exe -E \"$registry_file_contents\" > " . $temp_registry_file_path; |
| my ($echo_registry_exit_status, $echo_registry_output) = run_ssh_command($computer_node_name, $management_node_keys, $echo_registry_command, '', '', 1); |
| if (defined($echo_registry_exit_status) && $echo_registry_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "registry file contents echoed to $temp_registry_file_path"); |
| } |
| elsif ($echo_registry_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to echo registry file contents to $temp_registry_file_path, exit status: $echo_registry_exit_status, output:\n@{$echo_registry_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to echo registry file contents to $temp_registry_file_path"); |
| return; |
| } |
| |
| # Run reg.exe IMPORT |
| if (!$self->reg_import($temp_registry_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to import registry string contents from $temp_registry_file_path"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub import_registry_file |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 import_registry_string |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub import_registry_string { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string = shift; |
| if (!defined($registry_string) || !$registry_string) { |
| notify($ERRORS{'WARNING'}, 0, "registry file path was not passed correctly as an argument"); |
| return; |
| } |
| |
| #notify($ERRORS{'DEBUG'}, 0, "registry string:\n" . $registry_string); |
| |
| # Escape special characters with a backslash: |
| # \ |
| # " |
| #notify($ERRORS{'DEBUG'}, 0, "registry string:\n$registry_string"); |
| #$registry_string =~ s/\\+/\\\\\\\\/gs; |
| $registry_string =~ s/\\/\\\\/gs; |
| $registry_string =~ s/"/\\"/gs; |
| |
| # Replace \\" with \" |
| #$registry_string =~ s/\\+(")/\\\\$1/gs; |
| |
| # Replace regular newlines with Windows newlines |
| $registry_string =~ s/\r?\n/\r\n/gs; |
| |
| # Remove spaces from end of file |
| $registry_string =~ s/\s+$//; |
| |
| # Assemble a temporary registry file path |
| # Name the file after the sub which called this so you can tell where the .reg file was generated from |
| my @caller = caller(1); |
| my ($calling_sub) = $caller[3] =~ /([^:]+)$/; |
| my $calling_line = $caller[2]; |
| my $temp_registry_file_path = "C:/Cygwin/tmp/$calling_sub\_$calling_line.reg"; |
| |
| # Echo the registry string to a file on the node |
| my $echo_registry_command = "rm -f $temp_registry_file_path; /usr/bin/echo.exe -E \"$registry_string\" > " . $temp_registry_file_path; |
| my ($echo_registry_exit_status, $echo_registry_output) = run_ssh_command($computer_node_name, $management_node_keys, $echo_registry_command, '', '', 0); |
| if (defined($echo_registry_exit_status) && $echo_registry_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "registry string contents echoed to $temp_registry_file_path"); |
| } |
| elsif ($echo_registry_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to echo registry string contents to $temp_registry_file_path, exit status: $echo_registry_exit_status, output:\n@{$echo_registry_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to echo registry string contents to $temp_registry_file_path"); |
| return; |
| } |
| |
| # Run reg.exe IMPORT |
| if (!$self->reg_import($temp_registry_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to import registry string contents from $temp_registry_file_path"); |
| return; |
| } |
| |
| # Delete the temporary .reg file |
| if (!$self->delete_file($temp_registry_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete the temporary registry file: $temp_registry_file_path"); |
| } |
| |
| return 1; |
| } ## end sub import_registry_string |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reg_query |
| |
| Parameters : $registry_key, $registry_value (optional) |
| Returns : If $registry_value argument is specified: scalar |
| If $registry_value argument is specified: hash reference |
| Description : Queries the registry on the Windows computer. The $registry_key |
| argument is required. The $registry_value argument is optional. |
| |
| If $registry_value is specified, a scalar containing the value's |
| data is returned. The '(Default)' value's data is returned if the |
| $registry_value is either an empty string or exactly matches the |
| string '(Default)'. |
| |
| If $registry_value is NOT specified, a hash reference containing |
| the keys's subkey names, values, and each value's data is |
| returned. The hash has 2 keys: 'subkeys', 'values'. |
| |
| The 'subkeys' key contains an array reference. This array contains |
| the names of the key arguments subkeys. |
| |
| The 'values' key contain a hash reference. The keys of this hash |
| are the names of the values that are set for the key argument. |
| Each of theses contains a 'type' and 'data' key containing the |
| registry value type and value data. |
| |
| Example: |
| my $registry_data = $self->os->reg_query('HKLM/SYSTEM/CurrentControlSet/Services/NetBT/Parameters'); |
| @{$registry_data->{subkeys}}[0] = 'Interfaces' |
| my @value_names = @{$registry_data->{values}}; |
| $registry_data->{values}{$value_names[0]}{type} = 'REG_DWORD' |
| $registry_data->{values}{$value_names[0]}{data} = '123' |
| |
| =cut |
| |
| sub reg_query { |
| 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 $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; |
| |
| # Get the arguments |
| my $key_argument = shift; |
| if (!defined($key_argument) || !$key_argument) { |
| notify($ERRORS{'WARNING'}, 0, "registry key was not passed correctly as an argument"); |
| return; |
| } |
| my $value_argument = shift; |
| |
| # Replace forward slashes and double backslashes with a single backslashes |
| $key_argument =~ s/[\\\/]+/\\/g; |
| |
| # Removing trailing slashes |
| $key_argument =~ s/\\+$//g; |
| |
| # Replace abbreviated key names so argument matches reg.exe output |
| $key_argument =~ s/^HKLM/HKEY_LOCAL_MACHINE/; |
| $key_argument =~ s/^HKCU/HKEY_CURRENT_USER/; |
| $key_argument =~ s/^HKCR/HKEY_CLASSES_ROOT/; |
| $key_argument =~ s/^HKU/HKEY_USERS/; |
| $key_argument =~ s/^HKCC/HKEY_CURRENT_CONFIG/; |
| |
| # Assemble the reg.exe QUERY command |
| my $command .= "$system32_path/reg.exe QUERY \"$key_argument\" "; |
| |
| if (!defined($value_argument)) { |
| # Do not add any switches |
| $command .= "/s"; |
| } |
| elsif ($value_argument eq '(Default)') { |
| # Add /ve switch to query the default value |
| $command .= "/ve"; |
| } |
| else { |
| # Escape slashes and double-quotes in the value argument |
| (my $value_argument_escaped = $value_argument) =~ s/([\\\"])/\\$1/g; |
| |
| # Add /v switch to query a specific value |
| $command .= "/v \"$value_argument_escaped\""; |
| } |
| |
| # Run reg.exe QUERY |
| my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to query registry key: $key_argument"); |
| return; |
| } |
| elsif (grep(/^Error:/, @$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}); |
| 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 |
| my ($value, $type, $data) = map { $_ =~ /^\s*(.*)\s+(REG_\w+)\s+(.*)/ } @$output; |
| $value =~ s/(^\s+|\s+$)//g; |
| $type =~ s/(^\s+|\s+$)//g; |
| $data =~ s/(^\s+|\s+$)//g; |
| |
| $value = '(Default)' if $value =~ /NO NAME/; |
| |
| if ($type && defined($data)) { |
| $data = $self->reg_query_convert_data($type, $data); |
| notify($ERRORS{'DEBUG'}, 0, "retrieved registry data:\nkey: '$key_argument'\nvalue: '$value'\ntype: $type\ndata: '$data'"); |
| return $data; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve registry data:\nkey: '$key_argument'\nvalue: '$value'\ncommand: '$command'\noutput:\n" . string_to_ascii(join("\n", @$output))); |
| return; |
| } |
| } |
| else { |
| # Value argument was not specified, construct a hash containing the contents of the key |
| my %registry_hash; |
| |
| my $key; |
| for my $line (@$output) { |
| |
| if ($line =~ /^HKEY/) { |
| $key = $line; |
| $registry_hash{$key} = {}; |
| next; |
| } |
| elsif ($line =~ /^\s*(.*)\s+(REG_\w+)\s+(.*)/) { |
| my ($value, $type, $data) = ($1, $2, $3); |
| $value =~ s/(^\s+|\s+$)//g; |
| $type =~ s/(^\s+|\s+$)//g; |
| $data =~ s/(^\s+|\s+$)//g; |
| |
| $value = '(Default)' if $value =~ /NO NAME/; |
| |
| $data = $self->reg_query_convert_data($type, $data); |
| |
| #notify($ERRORS{'DEBUG'}, 0, "line: " . string_to_ascii($line) . "\n" . |
| #"value: " . string_to_ascii($value) . "\n" . |
| #"data: " . string_to_ascii($data) |
| #); |
| |
| #$registry_hash{$key}{$value}{type} = $type; |
| $registry_hash{$key}{$value} = $data; |
| } |
| elsif ($line =~ /^!/) { |
| # Ignore lines beginning with '!' |
| 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)); |
| return \%registry_hash; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reg_query_convert_data |
| |
| Parameters : $type, $data |
| Returns : scalar |
| Description : |
| |
| =cut |
| |
| sub reg_query_convert_data { |
| 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 ($type, $data) = @_; |
| if (!$type || !defined($data)) { |
| notify($ERRORS{'WARNING'}, 0, "registry data type and data value arguments were not specified"); |
| return; |
| } |
| |
| if ($type eq 'REG_DWORD') { |
| # Convert the hex value to decimal |
| $data = hex($data); |
| } |
| if ($type eq 'REG_MULTI_SZ') { |
| # Split data into an array, data values are separated in the output by '\0' |
| my @data_values = split(/\\0/, $data); |
| $data = \@data_values; |
| } |
| |
| return $data; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reg_add |
| |
| Parameters : key, value, type, data |
| Returns : If successful: true |
| If failed: false |
| Description : Adds or sets a registry key. |
| |
| =cut |
| |
| sub reg_add { |
| 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 $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; |
| |
| # Get the arguments |
| my $registry_key = shift; |
| if (!defined($registry_key) || !$registry_key) { |
| notify($ERRORS{'WARNING'}, 0, "registry key was not passed correctly as an argument"); |
| return; |
| } |
| |
| my $registry_value = shift; |
| if (!defined($registry_value) || !$registry_value) { |
| notify($ERRORS{'WARNING'}, 0, "registry value was not passed correctly as an argument"); |
| return; |
| } |
| |
| my $registry_type = shift; |
| if (!defined($registry_type) || !$registry_type) { |
| notify($ERRORS{'WARNING'}, 0, "registry type was not passed correctly as an argument"); |
| return; |
| } |
| if ($registry_type !~ /^(REG_SZ|REG_MULTI_SZ|REG_DWORD_BIG_ENDIAN|REG_DWORD|REG_BINARY|REG_DWORD_LITTLE_ENDIAN|REG_NONE|REG_EXPAND_SZ)$/) { |
| notify($ERRORS{'WARNING'}, 0, "invalid registry type was specified: $registry_type"); |
| return; |
| } |
| |
| my $registry_data = shift; |
| if (!defined($registry_data)) { |
| notify($ERRORS{'WARNING'}, 0, "registry data was not passed correctly as an argument"); |
| return; |
| } |
| |
| # Fix the value parameter to allow 'default' to be specified |
| my $value_parameter; |
| if ($registry_value =~ /^default$/i) { |
| $value_parameter = '/ve'; |
| } |
| else { |
| $value_parameter = "/v \"$registry_value\""; |
| } |
| |
| # Replace forward slashes with backslashes in registry key |
| $registry_key =~ s/\//\\\\/g; |
| |
| # Run reg.exe ADD |
| my $add_registry_command = $system32_path . "/reg.exe ADD \"$registry_key\" $value_parameter /t $registry_type /d \"$registry_data\" /f"; |
| my ($add_registry_exit_status, $add_registry_output) = run_ssh_command($computer_node_name, $management_node_keys, $add_registry_command, '', '', 1); |
| if (defined($add_registry_exit_status) && $add_registry_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "added registry key: $registry_key, output:\n" . join("\n", @$add_registry_output)); |
| } |
| elsif ($add_registry_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to add registry key: $registry_key, value: $registry_value, exit status: $add_registry_exit_status, output:\n@{$add_registry_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to add registry key: $registry_key, value: $registry_value"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reg_delete |
| |
| Parameters : registry key, registry value |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub reg_delete { |
| 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 $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; |
| |
| # Get the arguments |
| my $registry_key = shift; |
| if (!defined($registry_key) || !$registry_key) { |
| notify($ERRORS{'WARNING'}, 0, "registry key was not passed correctly as an argument"); |
| return; |
| } |
| my $registry_value = shift; |
| |
| # Replace forward slashes with backslashes in registry key |
| $registry_key =~ s/\//\\\\/g; |
| |
| # Run reg.exe DELETE |
| my $delete_registry_command; |
| if ($registry_value) { |
| $delete_registry_command = $system32_path . "/reg.exe DELETE \"$registry_key\" /v \"$registry_value\" /f"; |
| } |
| else { |
| $delete_registry_command = $system32_path . "/reg.exe DELETE \"$registry_key\" /f"; |
| $registry_value = '*'; |
| } |
| my ($delete_registry_exit_status, $delete_registry_output) = run_ssh_command($computer_node_name, $management_node_keys, $delete_registry_command, '', '', 1); |
| 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_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; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to delete registry key: $registry_key, value: $registry_value"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reg_import |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub reg_import { |
| 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 $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; |
| |
| # Get the registry file path argument |
| my $registry_file_path = shift; |
| if (!defined($registry_file_path) || !$registry_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "registry file path was not passed correctly as an argument"); |
| return; |
| } |
| |
| # Run reg.exe IMPORT |
| my $command .= $system32_path . "/reg.exe IMPORT $registry_file_path"; |
| my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to import registry file: $registry_file_path"); |
| return; |
| } |
| elsif (grep(/completed successfully/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "imported registry file: $registry_file_path"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to import registry file: $registry_file_path, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reg_export |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub reg_export { |
| 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 $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; |
| |
| # Get the arguments |
| my $root_key = shift; |
| if (!$root_key) { |
| notify($ERRORS{'WARNING'}, 0, "registry root key was not passed correctly as an argument"); |
| return; |
| } |
| |
| # Get the registry file path argument |
| my $registry_file_path = shift; |
| if (!defined($registry_file_path) || !$registry_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "registry file path was not passed correctly as an argument"); |
| return; |
| } |
| $registry_file_path = $self->format_path_unix($registry_file_path); |
| |
| # Escape backslashes in the root key |
| $root_key =~ s/\\+/\\\\/; |
| |
| # Run reg.exe EXPORT |
| my $command .= $system32_path . "/reg.exe EXPORT $root_key $registry_file_path /y"; |
| my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 1); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to export registry key $root_key to file: $registry_file_path"); |
| return; |
| } |
| elsif (grep(/completed successfully/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "exported registry key $root_key to file: $registry_file_path"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to export registry key $root_key to file: $registry_file_path, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reg_load |
| |
| Parameters : $root_key, $hive_file_path |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub reg_load { |
| 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 $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; |
| |
| # Get the arguments |
| my $root_key = shift; |
| if (!$root_key) { |
| notify($ERRORS{'WARNING'}, 0, "registry root key was not passed correctly as an argument"); |
| return; |
| } |
| my $hive_file_path = shift; |
| if (!$hive_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "registry hive file path was not passed correctly as an argument"); |
| return; |
| } |
| $hive_file_path = $self->format_path_unix($hive_file_path); |
| |
| # Escape backslashes in the root key |
| $root_key =~ s/\\+/\\\\/; |
| |
| # Run reg.exe LOAD |
| my $command .= "$system32_path/reg.exe LOAD $root_key $hive_file_path"; |
| my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to load registry hive file '$hive_file_path' into key $root_key"); |
| return; |
| } |
| elsif (grep(/completed successfully/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "loaded registry hive file '$hive_file_path' into key $root_key"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to load registry hive file '$hive_file_path' into key $root_key, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reg_unload |
| |
| Parameters : $root_key |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub reg_unload { |
| 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 $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; |
| |
| # Get the arguments |
| my $root_key = shift; |
| if (!$root_key) { |
| notify($ERRORS{'WARNING'}, 0, "registry root key was not passed correctly as an argument"); |
| return; |
| } |
| |
| # Escape backslashes in the root key |
| $root_key =~ s/\\+/\\\\/; |
| |
| # Run reg.exe UNLOAD |
| my $command .= "$system32_path/reg.exe UNLOAD $root_key"; |
| my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to unload registry hive: $root_key"); |
| return; |
| } |
| elsif (grep(/completed successfully/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "unloaded registry hive key: $root_key"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to unload registry hive: $root_key, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 add_hklm_run_registry_key |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub add_hklm_run_registry_key { |
| 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 $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 $command_name = shift; |
| my $command = shift; |
| |
| notify($ERRORS{'DEBUG'}, 0, "command name: " . $command_name); |
| notify($ERRORS{'DEBUG'}, 0, "command: " . $command); |
| |
| # Replace forward slashes with backslashes, unless a space precedes the forward slash |
| $command =~ s/([^ ])\//$1\\/g; |
| notify($ERRORS{'DEBUG'}, 0, "forward to backslash: " . $command); |
| |
| # Escape backslashes, can never have enough... |
| $command =~ s/\\/\\\\/g; |
| notify($ERRORS{'DEBUG'}, 0, "escape backslashes: " . $command); |
| |
| # Escape quotes |
| $command =~ s/"/\\"/g; |
| notify($ERRORS{'DEBUG'}, 0, "escaped quotes: " . $command); |
| |
| # Make sure arguments were supplied |
| if (!defined($command_name) && !defined($command)) { |
| notify($ERRORS{'WARNING'}, 0, "HKLM run registry key not added, arguments were not passed correctly"); |
| return 0; |
| } |
| |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run] |
| "$command_name"="$command" |
| EOF |
| |
| notify($ERRORS{'DEBUG'}, 0, "registry string:\n" . $registry_string); |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "added HKLM run registry value, name: $command_name, command: $command"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to add HKLM run registry value, name: $command_name, command: $command"); |
| return 0; |
| } |
| |
| # Attempt to query the registry key to make sure it was added |
| my $reg_query_command = $system32_path . '/reg.exe query "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"'; |
| my ($reg_query_exit_status, $reg_query_output) = run_ssh_command($computer_node_name, $management_node_keys, $reg_query_command, '', '', 1); |
| if (defined($reg_query_exit_status) && $reg_query_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "queried '$command_name' registry key:\n" . join("\n", @{$reg_query_output})); |
| } |
| elsif (defined($reg_query_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to query '$command_name' registry key, exit status: $reg_query_exit_status, output:\n@{$reg_query_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to query '$command_name' registry key"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub add_hklm_run_registry_key |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_hklm_run_registry_value |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub delete_hklm_run_registry_key { |
| 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 $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 $key_name = shift; |
| |
| # Make sure argument was supplied |
| if (!defined($key_name) && !defined($key_name)) { |
| notify($ERRORS{'WARNING'}, 0, "HKLM run registry key not deleted, argument was not passed correctly"); |
| return 0; |
| } |
| |
| # Attempt to query the registry key to make sure it was added |
| my $reg_delete_command = $system32_path . '/reg.exe delete "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run" /v "' . $key_name . '" /F'; |
| my ($reg_delete_exit_status, $reg_delete_output) = run_ssh_command($computer_node_name, $management_node_keys, $reg_delete_command, '', '', 1); |
| if (defined($reg_delete_exit_status) && $reg_delete_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "deleted '$key_name' run registry key:\n" . join("\n", @{$reg_delete_output})); |
| } |
| elsif (defined($reg_delete_output) && grep(/unable to find/i, @{$reg_delete_output})) { |
| notify($ERRORS{'OK'}, 0, "'$key_name' run registry key was not deleted, it does not exist"); |
| } |
| elsif (defined($reg_delete_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete '$key_name' run registry key, exit status: $reg_delete_exit_status, output:\n@{$reg_delete_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to delete '$key_name' run registry key"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_scheduled_task |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub delete_scheduled_task { |
| 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 $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 $task_name = shift; |
| |
| # Run schtasks.exe to delete any existing task |
| my $delete_task_command = "$system32_path/schtasks.exe /Delete /F /TN \"$task_name\""; |
| my ($delete_task_exit_status, $delete_task_output) = run_ssh_command($computer_node_name, $management_node_keys, $delete_task_command); |
| if (defined($delete_task_exit_status) && $delete_task_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "deleted existing scheduled task '$task_name' on $computer_node_name"); |
| } |
| elsif (defined($delete_task_output) && grep(/(task.*does not exist|cannot find the file specified)/i, @{$delete_task_output})) { |
| notify($ERRORS{'DEBUG'}, 0, "scheduled task '$task_name' does not exist on $computer_node_name"); |
| } |
| elsif (defined($delete_task_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete existing scheduled task '$task_name' on $computer_node_name, exit status: $delete_task_exit_status, output:\n@{$delete_task_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute ssh command to delete existing scheduled task '$task_name' on $computer_node_name"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 create_startup_scheduled_task |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub create_startup_scheduled_task { |
| 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 $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 $task_name = shift; |
| my $task_command = shift; |
| my $task_user = shift; |
| my $task_password = shift; |
| |
| # Escape backslashes, can never have enough... |
| $task_command =~ s/\\/\\\\/g; |
| |
| # Replace forward slashes with backslashes |
| $task_command =~ s/([^\s])\//$1\\\\/g; |
| |
| # Escape quote characters |
| $task_command =~ s/"/\\"/g; |
| |
| # Make sure arguments were supplied |
| if (!defined($task_name) || !defined($task_command) || !defined($task_user) || !defined($task_password)) { |
| notify($ERRORS{'WARNING'}, 0, "startup scheduled task not added, arguments were not passed correctly"); |
| return; |
| } |
| |
| # You cannot create a task if one with the same name already exists |
| # Windows 6.x schtasks.exe has a /F which forces a new task to be created if one with the same name already exists |
| # This option isn't supported with XP and other older versions of Windows |
| if (!$self->delete_scheduled_task($task_name)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to delete existing scheduled task '$task_name' on $computer_node_name"); |
| } |
| |
| # Run schtasks.exe to add the task |
| # Occasionally see this error even though it schtasks.exe returns exit status 0: |
| # WARNING: The Scheduled task "System Startup Script" has been created, but may not run because the account information could not be set. |
| my $create_task_command = "$system32_path/schtasks.exe /Create /RU \"$task_user\" /RP \"$task_password\" /SC ONSTART /TN \"$task_name\" /TR \"$task_command\""; |
| my ($create_task_exit_status, $create_task_output) = run_ssh_command($computer_node_name, $management_node_keys, $create_task_command); |
| if (defined($create_task_output) && grep(/could not be set/i, @{$create_task_output})) { |
| notify($ERRORS{'WARNING'}, 0, "created scheduled task '$task_name' on $computer_node_name but error occurred: " . join("\n", @{$create_task_output})); |
| return 0; |
| } |
| elsif (defined($create_task_exit_status) && $create_task_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "created scheduled task '$task_name' on $computer_node_name"); |
| } |
| elsif (defined($create_task_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create scheduled task '$task_name' on $computer_node_name, exit status: $create_task_exit_status, output:\n@{$create_task_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute ssh command created scheduled task '$task_name' on $computer_node_name"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub create_startup_scheduled_task |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 enable_autoadminlogon |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub enable_autoadminlogon { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| ; This file enables autoadminlogon for the root account |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon] |
| "AutoAdminLogon"="1" |
| "DefaultUserName"="root" |
| "DefaultPassword"= "$WINDOWS_ROOT_PASSWORD" |
| |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "enabled autoadminlogon"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to enable autoadminlogon"); |
| return 0; |
| } |
| } ## end sub enable_autoadminlogon |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_autoadminlogon |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub disable_autoadminlogon { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<EOF; |
| Windows Registry Editor Version 5.00 |
| |
| ; This file disables autoadminlogon for the root account |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon] |
| "AutoAdminLogon"="0" |
| "AutoLogonCount"="0" |
| "DefaultPassword"= "" |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "disabled autoadminlogon"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to disable autoadminlogon"); |
| return 0; |
| } |
| } ## end sub disable_autoadminlogon |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 create_eventlog_entry |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub create_eventlog_entry { |
| 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 $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 $message = shift; |
| |
| # Make sure the message was passed as an argument |
| if (!defined($message)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create eventlog entry, message was passed as an argument"); |
| return 0; |
| } |
| |
| # Run eventcreate.exe to create an event log entry |
| my $eventcreate_command = $system32_path . '/eventcreate.exe /T INFORMATION /L APPLICATION /SO VCL /ID 555 /D "' . $message . '"'; |
| my ($eventcreate_exit_status, $eventcreate_output) = run_ssh_command($computer_node_name, $management_node_keys, $eventcreate_command); |
| if (defined($eventcreate_exit_status) && $eventcreate_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "created event log entry on $computer_node_name: $message"); |
| } |
| elsif (defined($eventcreate_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create event log entry on $computer_node_name: $message, exit status: $eventcreate_exit_status, output:\n@{$eventcreate_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to create event log entry on $computer_node_name: $message"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub create_eventlog_entry |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reboot |
| |
| Parameters : $wait_for_reboot |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub reboot { |
| 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 $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; |
| |
| # Check if an argument was supplied |
| my $wait_for_reboot = shift; |
| if (!defined($wait_for_reboot) || $wait_for_reboot !~ /0/) { |
| notify($ERRORS{'DEBUG'}, 0, "rebooting $computer_node_name and waiting for ssh to become active"); |
| $wait_for_reboot = 1; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "rebooting $computer_node_name and NOT waiting"); |
| $wait_for_reboot = 0; |
| } |
| |
| my $reboot_start_time = time(); |
| notify($ERRORS{'DEBUG'}, 0, "reboot will be attempted on $computer_node_name"); |
| |
| # Check if computer responds to ssh before preparing for reboot |
| if ($self->wait_for_ssh(0)) { |
| # Make sure SSH access is enabled from private IP addresses |
| if (!$self->firewall_enable_ssh_private()) { |
| notify($ERRORS{'WARNING'}, 0, "reboot not attempted, failed to enable ssh from private IP addresses"); |
| return 0; |
| } |
| |
| # Set sshd service startup mode to auto |
| if (!$self->set_service_startup_mode('sshd', 'auto')) { |
| notify($ERRORS{'WARNING'}, 0, "reboot not attempted, unable to set sshd service startup mode to auto"); |
| return 0; |
| } |
| |
| # Make sure ping access is enabled from private IP addresses |
| if (!$self->firewall_enable_ping_private()) { |
| notify($ERRORS{'WARNING'}, 0, "reboot not attempted, failed to enable ping from private IP addresses"); |
| return 0; |
| } |
| |
| # Kill the screen saver process, it occasionally prevents reboots and shutdowns from working |
| $self->kill_process('logon.scr'); |
| |
| # Check if tsshutdn.exe exists on the computer |
| # tsshutdn.exe is the preferred utility, shutdown.exe often fails on Windows Server 2003 |
| my $reboot_command; |
| my $windows_product_name = $self->get_product_name() || ''; |
| if ($windows_product_name =~ /2003/ && $self->file_exists("$system32_path/tsshutdn.exe")) { |
| $reboot_command = "$system32_path/tsshutdn.exe 0 /REBOOT /DELAY:0 /V"; |
| } |
| else { |
| $reboot_command = "$system32_path/shutdown.exe /r /t 0 /f"; |
| } |
| |
| my ($reboot_exit_status, $reboot_output) = run_ssh_command($computer_node_name, $management_node_keys, $reboot_command); |
| if (!defined($reboot_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute ssh command to reboot $computer_node_name"); |
| return; |
| } |
| |
| if ($reboot_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "executed reboot command on $computer_node_name"); |
| } |
| else { |
| # The following message may be displayed causing the reboot to fail: |
| # The computer is processing another action and thus cannot be shut down. Wait until the computer has finished its action, and then try again.(21) |
| notify($ERRORS{'WARNING'}, 0, "failed to reboot $computer_node_name, attempting power reset, output:\n" . join("\n", @$reboot_output)); |
| |
| # Call provisioning module's power_reset() subroutine |
| if ($self->provisioner->power_reset()) { |
| notify($ERRORS{'OK'}, 0, "initiated power reset on $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "reboot failed, failed to initiate power reset on $computer_node_name"); |
| return; |
| } |
| } |
| } |
| else { |
| # Computer did not respond to ssh |
| notify($ERRORS{'WARNING'}, 0, "$computer_node_name did not respond to ssh, graceful reboot cannot be performed, attempting hard reset"); |
| |
| # Call provisioning module's power_reset() subroutine |
| if ($self->provisioner->power_reset()) { |
| notify($ERRORS{'OK'}, 0, "initiated power reset on $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "reboot failed, failed to initiate power reset on $computer_node_name"); |
| return 0; |
| } |
| } ## end else [ if ($self->wait_for_ssh(0)) |
| |
| # Check if wait for reboot is set |
| if (!$wait_for_reboot) { |
| return 1; |
| } |
| |
| # Make multiple attempts to wait for the reboot to complete |
| my $wait_attempt_limit = 2; |
| WAIT_ATTEMPT: |
| for (my $wait_attempt = 1; $wait_attempt <= $wait_attempt_limit; $wait_attempt++) { |
| if ($wait_attempt > 1) { |
| # Computer did not become fully responsive on previous wait attempt |
| notify($ERRORS{'OK'}, 0, "$computer_node_name reboot failed to complete on previous attempt, attempting hard power reset"); |
| |
| # Call provisioning module's power_reset() subroutine |
| if ($self->provisioner->power_reset()) { |
| notify($ERRORS{'OK'}, 0, "reboot attempt $wait_attempt/$wait_attempt_limit: initiated power reset on $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "reboot failed, failed to initiate power reset on $computer_node_name"); |
| return 0; |
| } |
| } ## end if ($wait_attempt > 1) |
| |
| # Wait maximum of 3 minutes for the computer to become unresponsive |
| if (!$self->wait_for_no_ping(180, 3)) { |
| # Computer never stopped responding to ping |
| notify($ERRORS{'WARNING'}, 0, "$computer_node_name never became unresponsive to ping"); |
| next WAIT_ATTEMPT; |
| } |
| |
| # Computer is unresponsive, reboot has begun |
| # Wait for 5 seconds before beginning to check if computer is back online |
| notify($ERRORS{'DEBUG'}, 0, "$computer_node_name reboot has begun, sleeping for 5 seconds"); |
| sleep 5; |
| |
| # Wait maximum of 6 minutes for the computer to come back up |
| if (!$self->wait_for_ping(360, 5)) { |
| # Check if the computer was ever offline, it should have been or else reboot never happened |
| notify($ERRORS{'WARNING'}, 0, "$computer_node_name never responded to ping"); |
| next WAIT_ATTEMPT; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is pingable, waiting for ssh to respond"); |
| |
| # Wait maximum of 3 minutes for ssh to respond |
| if (!$self->wait_for_ssh(180, 5)) { |
| notify($ERRORS{'WARNING'}, 0, "ssh never responded on $computer_node_name"); |
| next WAIT_ATTEMPT; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "$computer_node_name responded to ssh"); |
| |
| ## Wait then check ssh again in case initialization scripts are running |
| ## ssh may be available when the computer first boots, then network configuration scripts may automatically run |
| ## Make sure ssh is available a short time after it's first available |
| #notify($ERRORS{'DEBUG'}, 0, "sleeping for 20 seconds then checking ssh again"); |
| #sleep 20; |
| # |
| ## Wait maximum of 2 minutes for ssh to respond |
| #if (!$self->wait_for_ssh(120)) { |
| # notify($ERRORS{'WARNING'}, 0, "ssh responded then stopped responding on $computer_node_name"); |
| # next WAIT_ATTEMPT; |
| #} |
| |
| # Reboot was successful, calculate how long reboot took |
| my $reboot_end_time = time(); |
| my $reboot_duration = ($reboot_end_time - $reboot_start_time); |
| notify($ERRORS{'OK'}, 0, "reboot complete on $computer_node_name, took $reboot_duration seconds"); |
| return 1; |
| } ## end for (my $wait_attempt = 1; $wait_attempt <=... |
| |
| # If loop completed, maximum number of reboot attempts was reached |
| notify($ERRORS{'WARNING'}, 0, "reboot failed on $computer_node_name, made $wait_attempt_limit attempts"); |
| return 0; |
| } ## end sub reboot |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 shutdown |
| |
| Parameters : $enable_dhcp |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub shutdown { |
| 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; |
| } |
| |
| # Get the argument that determines whether or not to disable DHCP before shutting down computer |
| my $disable_dhcp = shift; |
| |
| 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; |
| |
| # Kill the screen saver process, it occasionally prevents reboots and shutdowns from working |
| $self->kill_process('logon.scr'); |
| |
| # Clear the event log before shutting down |
| $self->clear_event_log(); |
| |
| my $shutdown_command = "/bin/cygstart.exe \$SYSTEMROOT/system32/cmd.exe /c \""; |
| |
| if ($disable_dhcp) { |
| notify($ERRORS{'DEBUG'}, 0, "enabling DHCP and shutting down $computer_node_name"); |
| |
| my $private_interface_name = $self->get_private_interface_name(); |
| my $public_interface_name = $self->get_public_interface_name(); |
| if (!$private_interface_name || !$public_interface_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine private and public interface names, failed to enable DHCP and shut down $computer_node_name"); |
| return; |
| } |
| |
| $shutdown_command .= "$system32_path/netsh.exe interface ip set address name=\\\"$private_interface_name\\\" source=dhcp & "; |
| $shutdown_command .= "$system32_path/netsh.exe interface ip set dnsservers name=\\\"$private_interface_name\\\" source=dhcp & "; |
| $shutdown_command .= "$system32_path/netsh.exe interface ip set address name=\\\"$public_interface_name\\\" source=dhcp & "; |
| $shutdown_command .= "$system32_path/netsh.exe interface ip set dnsservers name=\\\"$public_interface_name\\\" source=dhcp & "; |
| $shutdown_command .= "$system32_path/netsh.exe interface ip reset & "; |
| $shutdown_command .= "$system32_path/ipconfig.exe /release & "; |
| $shutdown_command .= "$system32_path/ipconfig.exe /flushdns & "; |
| $shutdown_command .= "$system32_path/arp.exe -d * & "; |
| $shutdown_command .= "$system32_path/route.exe DELETE 0.0.0.0 MASK 0.0.0.0 & "; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "shutting down $computer_node_name"); |
| } |
| |
| # Check if tsshutdn.exe exists on the computer |
| # tsshutdn.exe is the preferred utility for Windows 2003, shutdown.exe often fails |
| my $windows_product_name = $self->get_product_name() || ''; |
| if ($windows_product_name =~ /2003/ && $self->file_exists("$system32_path/tsshutdn.exe")) { |
| $shutdown_command .= "$system32_path/tsshutdn.exe 0 /POWERDOWN /DELAY:0 /V"; |
| } |
| else { |
| $shutdown_command .= "$system32_path/shutdown.exe /s /t 0 /f"; |
| } |
| |
| $shutdown_command .= "\""; |
| |
| my $attempt_count = 0; |
| my $attempt_limit = 12; |
| while ($attempt_count < $attempt_limit) { |
| $attempt_count++; |
| if ($attempt_count > 1) { |
| notify($ERRORS{'DEBUG'}, 0, "sleeping for 10 seconds before making next shutdown attempt"); |
| sleep 10; |
| } |
| |
| my ($shutdown_exit_status, $shutdown_output) = run_ssh_command($computer_node_name, $management_node_keys, $shutdown_command); |
| if (!defined($shutdown_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute ssh command to shutdown $computer_node_name"); |
| last; |
| } |
| elsif (grep(/(processing another action)/i, @$shutdown_output)) { |
| notify($ERRORS{'WARNING'}, 0, "attempt $attempt_count/$attempt_limit: failed to execute shutdown command on $computer_node_name, exit status: $shutdown_exit_status, output:\n@{$shutdown_output}"); |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "attempt $attempt_count/$attempt_limit: executed shutdown command on $computer_node_name"); |
| last; |
| } |
| } |
| |
| # Wait maximum of 3 minutes for the computer to become unresponsive |
| if (!$self->wait_for_no_ping(180)) { |
| # Computer never stopped responding to ping |
| notify($ERRORS{'WARNING'}, 0, "$computer_node_name never became unresponsive to ping after shutdown command was issued"); |
| return; |
| } |
| |
| # Wait maximum of 5 minutes for computer to power off |
| my $power_off = $self->provisioner->wait_for_power_off(300); |
| if (!defined($power_off)) { |
| # wait_for_power_off result will be undefined if the provisioning module doesn't implement a power_status subroutine |
| notify($ERRORS{'OK'}, 0, "unable to determine power status of $computer_node_name from provisioning module, sleeping 1 minute to allow computer time to shutdown"); |
| sleep 60; |
| } |
| elsif (!$power_off) { |
| notify($ERRORS{'WARNING'}, 0, "$computer_node_name never powered off"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_service_startup_mode |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub set_service_startup_mode { |
| 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 $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 $service_name = shift; |
| my $startup_mode = shift; |
| |
| # Make sure both arguments were supplied |
| if (!defined($service_name) && !defined($startup_mode)) { |
| notify($ERRORS{'WARNING'}, 0, "set service startup mode failed, service name and startup mode arguments were not passed correctly"); |
| return 0; |
| } |
| |
| # Make sure the startup mode is valid |
| if ($startup_mode !~ /boot|system|auto|demand|disabled|delayed-auto|manual/i) { |
| notify($ERRORS{'WARNING'}, 0, "set service startup mode failed, invalid startup mode: $startup_mode"); |
| return 0; |
| } |
| |
| # Set the mode to demand if manual was specified, specific to sc command |
| $startup_mode = "demand" if ($startup_mode eq "manual"); |
| |
| # Use sc.exe to change the start mode |
| my $service_startup_command = $system32_path . '/sc.exe config ' . "$service_name start= $startup_mode"; |
| my ($service_startup_exit_status, $service_startup_output) = run_ssh_command($computer_node_name, $management_node_keys, $service_startup_command); |
| if (defined($service_startup_output) && grep(/service does not exist/, @$service_startup_output)) { |
| notify($ERRORS{'WARNING'}, 0, "$service_name service startup mode not set because service does not exist"); |
| return; |
| } |
| elsif (defined($service_startup_exit_status) && $service_startup_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "$service_name service startup mode set to $startup_mode"); |
| } |
| elsif ($service_startup_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set $service_name service startup mode to $startup_mode, exit status: $service_startup_exit_status, output:\n@{$service_startup_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to set $service_name service startup mode to $startup_mode"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub set_service_startup_mode |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 defragment_hard_drive |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub defragment_hard_drive { |
| 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 $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; |
| |
| # Defragment the hard drive |
| notify($ERRORS{'OK'}, 0, "beginning to defragment the hard drive on $computer_node_name"); |
| my ($defrag_exit_status, $defrag_output) = run_ssh_command($computer_node_name, $management_node_keys, $system32_path . '/defrag.exe $SYSTEMDRIVE -v'); |
| if (defined($defrag_exit_status) && $defrag_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "hard drive defragmentation complete on $computer_node_name"); |
| return 1; |
| } |
| elsif (defined($defrag_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to defragment the hard drive, exit status: $defrag_exit_status, output:\n@{$defrag_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run the SSH command to defragment the hard drive"); |
| return; |
| } |
| } ## end sub defragment_hard_drive |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 prepare_post_load |
| |
| Parameters : None. |
| Returns : If successful: true |
| If failed: false |
| Description : This subroutine should be called as the last step before an image |
| is captured if Sysprep is not is used. It enables autoadminlogon |
| so that root automatically logs on the next time the computer is |
| booted and creates a registry key under |
| HKLM\Software\Microsoft\Windows\CurrentVersion\Run. |
| |
| This key causes the post_load.cmd script after the image is |
| loaded when root automatically logs on. This script needs to run |
| in order to configure networking and the Cygwin SSH service. |
| |
| =cut |
| |
| sub prepare_post_load { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| my $end_state = $self->{end_state} || 'off'; |
| |
| # Set the DevicePath registry key |
| # This is used to locate device drivers |
| if (!$self->set_device_path_key()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the DevicePath registry key"); |
| return; |
| } |
| |
| # Get the node configuration directory |
| my $node_configuration_directory = $self->get_node_configuration_directory(); |
| unless ($node_configuration_directory) { |
| notify($ERRORS{'WARNING'}, 0, "node configuration directory could not be determined"); |
| return; |
| } |
| |
| # Add HKLM run key to call post_load.cmd after the image comes up |
| if (!$self->add_hklm_run_registry_key('post_load.cmd', $node_configuration_directory . '/Scripts/post_load.cmd >> ' . $node_configuration_directory . '/Logs/post_load.log')) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create run key to call post_load.cmd"); |
| return; |
| } |
| |
| # Enable autoadminlogon |
| if (!$self->enable_autoadminlogon()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to enable autoadminlogon"); |
| return 0; |
| } |
| |
| # Shut down computer unless end_state argument was passed with a value other than 'off' |
| if ($end_state eq 'off') { |
| if (!$self->shutdown(1)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to shut down computer"); |
| return; |
| } |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_service_credentials |
| |
| Parameters : $service_name, $username, $password |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub set_service_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 $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 get the username from the arguments |
| my $service_name = shift; |
| my $username = shift; |
| my $password = shift; |
| |
| # Make sure arguments were supplied |
| if (!$service_name || !$username || !$password) { |
| notify($ERRORS{'WARNING'}, 0, "set service logon failed, service name, username, and password arguments were not passed correctly"); |
| return 0; |
| } |
| |
| # Attempt to set the service logon user name and password |
| my $service_logon_command = $system32_path . '/sc.exe config ' . $service_name . ' obj= ".\\' . $username . '" password= "' . $password . '"'; |
| my ($service_logon_exit_status, $service_logon_output) = run_ssh_command($computer_node_name, $management_node_keys, $service_logon_command); |
| if (defined($service_logon_exit_status) && $service_logon_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "changed logon credentials for '$service_name' service to $username ($password) on $computer_node_name"); |
| } |
| elsif (defined($service_logon_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to change $service_name service logon credentials to $username ($password) on $computer_node_name, exit status: $service_logon_exit_status, output:\n@{$service_logon_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to change $service_name service logon credentials to $username ($password) on $computer_node_name"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub set_service_credentials |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_service_list |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub get_service_list { |
| 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 $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); |
| if (defined($sc_query_exit_status) && $sc_query_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "retrieved service list on $computer_node_name"); |
| } |
| 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}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to failed to retrieve service list from $computer_node_name"); |
| return; |
| } |
| |
| 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; |
| } ## end sub get_service_list |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_service_login_ids |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| 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 $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"); |
| } |
| 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; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to failed to retrieve $service_name service start name from $computer_node_name"); |
| return; |
| } |
| |
| 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) |
| |
| return @services_using_login_id; |
| } ## end sub get_services_using_login_id |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_scheduled_task |
| |
| Parameters : |
| Returns : 1 success 0 failure |
| Description : |
| |
| =cut |
| |
| sub disable_scheduled_task { |
| 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 $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 get the task name from the arguments |
| my $task_name = shift; |
| if (!$task_name) { |
| notify($ERRORS{'OK'}, 0, "failed to disable scheduled task, task name argument was not correctly passed"); |
| return; |
| } |
| |
| # Attempt to delete the user account |
| my $schtasks_command = $system32_path . '/schtasks.exe /Change /DISABLE /TN "' . $task_name . '"'; |
| my ($schtasks_exit_status, $schtasks_output) = run_ssh_command($computer_node_name, $management_node_keys, $schtasks_command, '', '', 1); |
| if (!defined($schtasks_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to disable $task_name scheduled task on $computer_node_name"); |
| return; |
| } |
| elsif (grep(/have been changed/, @$schtasks_output)) { |
| notify($ERRORS{'OK'}, 0, "$task_name scheduled task disabled on $computer_node_name"); |
| } |
| elsif (grep(/does not exist/, @$schtasks_output)) { |
| notify($ERRORS{'OK'}, 0, "$task_name was not disabled on $computer_node_name because it does not exist"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to disable $task_name scheduled task on $computer_node_name, exit status: $schtasks_exit_status, output:\n@{$schtasks_output}"); |
| return 0; |
| } |
| |
| |
| return 1; |
| } ## end sub disable_scheduled_task |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_scheduled_tasks |
| |
| Parameters : |
| Returns : array reference if successful, false if failed |
| 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"} |
| |
| =cut |
| |
| sub get_scheduled_tasks { |
| 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 $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; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to retrieve scheduled task information"); |
| return; |
| } |
| |
| 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; |
| |
| push @scheduled_task_data, \%scheduled_task_hash; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@scheduled_task_data) . " scheduled tasks"); |
| |
| return \@scheduled_task_data; |
| } ## end sub disable_scheduled_task |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_dynamic_dns |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub disable_dynamic_dns { |
| 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 $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 $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| ; This file disables dynamic DNS updates |
| |
| [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters] |
| "DisableDynamicUpdate"=dword:00000001 |
| |
| [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters] |
| "DisableReverseAddressRegistrations"=dword:00000001 |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "disabled dynamic dns"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to disable dynamic dns"); |
| return; |
| } |
| |
| # Get the network configuration |
| my $network_configuration = $self->get_network_configuration(); |
| if (!$network_configuration) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve network configuration"); |
| return; |
| } |
| |
| # Get the public and private interface names |
| my $public_interface_name = $self->get_public_interface_name(); |
| my $private_interface_name = $self->get_private_interface_name(); |
| |
| # Assemble netsh.exe commands to disable DNS registration |
| my $netsh_command; |
| $netsh_command .= "$system32_path/netsh.exe interface ip set dns"; |
| $netsh_command .= " name = \"$public_interface_name\""; |
| $netsh_command .= " source = dhcp"; |
| $netsh_command .= " register = none"; |
| $netsh_command .= " ;"; |
| |
| $netsh_command .= "$system32_path/netsh.exe interface ip set dns"; |
| $netsh_command .= " name = \"$private_interface_name\""; |
| $netsh_command .= " source = dhcp"; |
| $netsh_command .= " register = none"; |
| $netsh_command .= " ;"; |
| |
| # Execute the netsh.exe command |
| my ($netsh_exit_status, $netsh_output) = run_ssh_command($computer_node_name, $management_node_keys, $netsh_command); |
| if (defined($netsh_exit_status) && $netsh_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "disabled dynamic DNS registration on public and private adapters"); |
| } |
| elsif (defined($netsh_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to disable dynamic DNS registration on public and private adapters, exit status: $netsh_exit_status, output:\n@{$netsh_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to disable dynamic DNS registration on public and private adapters"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub disable_dynamic_dns |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_netbios |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub disable_netbios { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Attempt to query the registry for the NetBT service parameters |
| my $interface_registry_data = $self->reg_query('HKLM/SYSTEM/CurrentControlSet/Services/NetBT/Parameters/Interfaces'); |
| if (!$interface_registry_data) { |
| notify($ERRORS{'WARNING'}, 0, "failed to query registry to determine NetBT network interface strings"); |
| return; |
| } |
| |
| my @interface_keys = grep(/Tcpip_/i, keys %{$interface_registry_data}); |
| notify($ERRORS{'DEBUG'}, 0, "retrieved NetBT interface keys:\n" . join("\n", @interface_keys)); |
| |
| for my $interface_key (@interface_keys) { |
| my $netbios_options = $interface_registry_data->{$interface_key}{NetbiosOptions}; |
| |
| if ($self->reg_add($interface_key, 'NetbiosOptions', 'REG_DWORD', 2)) { |
| notify($ERRORS{'OK'}, 0, "disabled Netbios for interface: $interface_key"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to disabled Netbios for interface: $interface_key"); |
| return; |
| } |
| } |
| |
| return 1; |
| } ## end sub disable_netbios |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_computer_description |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub set_computer_description { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Attempt to get the description from the arguments |
| my $description = shift; |
| if (!$description) { |
| my $image_name = $self->data->get_image_name(); |
| my $image_prettyname = $self->data->get_image_prettyname(); |
| $description = "$image_prettyname ($image_name)"; |
| } |
| |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\LanmanServer\\Parameters] |
| "srvcomment"="$description" |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "set computer description to '$description'"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set computer description to '$description'"); |
| return 0; |
| } |
| } ## end sub set_computer_description |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_my_computer_name |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub set_my_computer_name { |
| 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 $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 $image_prettyname = $self->data->get_image_prettyname(); |
| |
| my $value = shift; |
| $value = $image_prettyname if !$value; |
| |
| my $add_registry_command .= $system32_path . "/reg.exe add \"HKCR\\CLSID\\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\" /v LocalizedString /t REG_EXPAND_SZ /d \"$value\" /f"; |
| my ($add_registry_exit_status, $add_registry_output) = run_ssh_command($computer_node_name, $management_node_keys, $add_registry_command, '', '', 1); |
| if (defined($add_registry_exit_status) && $add_registry_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "my computer name changed to '$value'"); |
| } |
| elsif ($add_registry_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to change my computer name to '$value', exit status: $add_registry_exit_status, output:\n@{$add_registry_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to change my computer name to '$value'"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub set_my_computer_name |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_enable_ping |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub firewall_enable_ping { |
| 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 $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 $netsh_command; |
| $netsh_command .= "$system32_path/netsh.exe firewall set icmpsetting"; |
| $netsh_command .= " type = 8"; |
| $netsh_command .= " mode = ENABLE"; |
| $netsh_command .= " profile = ALL"; |
| |
| # Execute the netsh.exe command |
| my ($netsh_exit_status, $netsh_output) = run_ssh_command($computer_node_name, $management_node_keys, $netsh_command); |
| |
| if (defined($netsh_output) && @$netsh_output[-1] =~ /(Ok|The object already exists)/i) { |
| notify($ERRORS{'OK'}, 0, "configured firewall to allow ping"); |
| } |
| elsif (defined($netsh_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure firewall to allow ping, exit status: $netsh_exit_status, output:\n@{$netsh_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to configure firewall to allow ping"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_enable_ping_private |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub firewall_enable_ping_private { |
| 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 $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 $netsh_command; |
| |
| # Get the public interface name |
| # Add command to disable ping on public interface if its name is found |
| my $public_interface_name = $self->get_public_interface_name(); |
| if ($public_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "ping will be disabled on public interface: $public_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set icmpsetting"; |
| $netsh_command .= " type = 8"; |
| $netsh_command .= " mode = DISABLE"; |
| $netsh_command .= " interface = \"$public_interface_name\""; |
| $netsh_command .= ' ;'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "ping will not be disabled on public interface because public interface name could not be determined"); |
| } |
| |
| # Get the private interface name |
| # Add command to ensable ping on private interface if its name is found |
| my $private_interface_name = $self->get_private_interface_name(); |
| if ($private_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "ping will be enabled on private interface: $private_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set icmpsetting"; |
| $netsh_command .= " type = 8"; |
| $netsh_command .= " mode = DISABLE"; |
| $netsh_command .= " profile = ALL"; |
| $netsh_command .= ' ;'; |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set icmpsetting"; |
| $netsh_command .= " type = 8"; |
| $netsh_command .= " mode = ENABLE"; |
| $netsh_command .= " interface = \"$private_interface_name\""; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "private interface name could not be determined, ping will be enabled for all profiles"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set icmpsetting"; |
| $netsh_command .= " type = 8"; |
| $netsh_command .= " mode = ENABLE"; |
| $netsh_command .= " profile = ALL"; |
| $netsh_command .= ' ;'; |
| } |
| |
| # Execute the netsh.exe command |
| my ($netsh_exit_status, $netsh_output) = run_ssh_command($computer_node_name, $management_node_keys, $netsh_command); |
| |
| if (defined($netsh_output) && @$netsh_output[-1] =~ /(Ok|The object already exists)/i) { |
| notify($ERRORS{'OK'}, 0, "configured firewall to allow ping on private interface"); |
| } |
| elsif (defined($netsh_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure firewall to allow ping on private interface, exit status: $netsh_exit_status, output:\n@{$netsh_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to configure firewall to allow ping on private interface"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub firewall_enable_ping_private |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_disable_ping |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub firewall_disable_ping { |
| 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 $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 $netsh_command; |
| |
| # Get the private interface name |
| # Add command to disable ping on private interface if its name is found |
| my $private_interface_name = $self->get_private_interface_name(); |
| if ($private_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved private interface name: $private_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set icmpsetting"; |
| $netsh_command .= " type = 8"; |
| $netsh_command .= " mode = DISABLE"; |
| $netsh_command .= " interface = \"$private_interface_name\""; |
| $netsh_command .= ' ;'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "private interface name could not be determined"); |
| } |
| |
| # Get the public interface name |
| # Add command to disable ping on public interface if its name is found |
| my $public_interface_name = $self->get_public_interface_name(); |
| if ($public_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved public interface name: $public_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set icmpsetting"; |
| $netsh_command .= " type = 8"; |
| $netsh_command .= " mode = DISABLE"; |
| $netsh_command .= " interface = \"$public_interface_name\""; |
| $netsh_command .= ' ;'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "public interface name could not be determined"); |
| } |
| |
| # Add command to disable ping for all profiles |
| $netsh_command .= "$system32_path/netsh.exe firewall set icmpsetting"; |
| $netsh_command .= " type = 8"; |
| $netsh_command .= " mode = DISABLE"; |
| $netsh_command .= " profile = ALL"; |
| |
| # Execute the netsh.exe command |
| my ($netsh_exit_status, $netsh_output) = run_ssh_command($computer_node_name, $management_node_keys, $netsh_command); |
| |
| if (defined($netsh_output) && @$netsh_output[-1] =~ /(Ok|The object already exists)/i) { |
| notify($ERRORS{'OK'}, 0, "configured firewall to disallow ping"); |
| } |
| elsif (defined($netsh_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure firewall to disallow ping, exit status: $netsh_exit_status, output:\n@{$netsh_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to configure firewall to disallow ping"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_enable_ssh |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub firewall_enable_ssh { |
| 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; |
| } |
| |
| # Check if the remote IP was passed correctly as an argument |
| my $remote_ip = shift; |
| |
| 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 $netsh_command; |
| |
| # Get the public interface name |
| # Add command to disable SSH on public interface if its name is found |
| my $public_interface_name = $self->get_public_interface_name(); |
| if ($public_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "SSH will be disabled on public interface: $public_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall delete portopening"; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 22"; |
| $netsh_command .= " interface = \"$public_interface_name\""; |
| $netsh_command .= ' ;'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "SSH will not be disabled on public interface because public interface name could not be determined"); |
| } |
| |
| # Get the private interface name |
| # Add command to disable SSH on private interface if its name is found |
| my $private_interface_name = $self->get_private_interface_name(); |
| if ($private_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "SSH will be disabled on private interface: $private_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall delete portopening"; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 22"; |
| $netsh_command .= " interface = \"$private_interface_name\""; |
| $netsh_command .= ' ;'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "SSH will not be disabled on private interface because private interface name could not be determined"); |
| } |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set portopening"; |
| $netsh_command .= " name = \"Cygwin SSHD\""; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 22"; |
| $netsh_command .= " mode = ENABLE"; |
| |
| if (!defined($remote_ip) || $remote_ip !~ /[\d\.\/]/) { |
| $remote_ip = 'all addresses'; # Set only to display in output |
| $netsh_command .= " scope = ALL"; |
| } |
| else { |
| $netsh_command .= " scope = CUSTOM"; |
| $netsh_command .= " addresses = $remote_ip"; |
| } |
| |
| # Execute the netsh.exe command |
| my ($netsh_exit_status, $netsh_output) = run_ssh_command($computer_node_name, $management_node_keys, $netsh_command); |
| |
| if (defined($netsh_output) && @$netsh_output[-1] =~ /(Ok|The object already exists)/i) { |
| notify($ERRORS{'OK'}, 0, "configured firewall to allow SSH from $remote_ip"); |
| } |
| elsif (defined($netsh_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure firewall to allow SSH from $remote_ip, exit status: $netsh_exit_status, output:\n@{$netsh_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to configure firewall to allow SSH from $remote_ip"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub firewall_enable_ssh_private |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_enable_ssh_private |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub firewall_enable_ssh_private { |
| 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 $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 $netsh_command; |
| |
| # Get the public interface name |
| # Add command to disable SSH on public interface if its name is found |
| my $public_interface_name = $self->get_public_interface_name(); |
| if ($public_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "SSH will be disabled on public interface: $public_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall delete portopening"; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 22"; |
| $netsh_command .= " interface = \"$public_interface_name\""; |
| $netsh_command .= ' ;'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "SSH will not be disabled on public interface because public interface name could not be determined"); |
| } |
| |
| # Get the private interface name |
| # Add command to ensable SSH on private interface if its name is found |
| my $private_interface_name = $self->get_private_interface_name(); |
| if ($private_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "SSH will be enabled on private interface: $private_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall delete portopening"; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 22"; |
| $netsh_command .= " profile = ALL"; |
| $netsh_command .= ' ;'; |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set portopening"; |
| $netsh_command .= " name = \"Cygwin SSHD\""; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 22"; |
| $netsh_command .= " mode = ENABLE"; |
| $netsh_command .= " interface = \"$private_interface_name\""; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "private interface name could not be determined, SSH will be enabled for all profiles"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set portopening"; |
| $netsh_command .= " name = \"Cygwin SSHD\""; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 22"; |
| $netsh_command .= " profile = ALL"; |
| } |
| |
| # Execute the netsh.exe command |
| my ($netsh_exit_status, $netsh_output) = run_ssh_command($computer_node_name, $management_node_keys, $netsh_command); |
| |
| if (defined($netsh_output) && @$netsh_output[-1] =~ /(Ok|The object already exists)/i) { |
| notify($ERRORS{'OK'}, 0, "configured firewall to allow SSH on private interface"); |
| } |
| elsif (defined($netsh_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure firewall to allow SSH on private interface, exit status: $netsh_exit_status, output:\n@{$netsh_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to configure firewall to allow SSH on private interface"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub firewall_enable_ssh_private |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_enable_rdp |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub firewall_enable_rdp { |
| 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; |
| } |
| |
| # Check if the remote IP was passed correctly as an argument |
| my $remote_ip = shift; |
| |
| 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 $netsh_command; |
| |
| # Set the key to allow remote connections whenever enabling RDP |
| # Include this in the SSH command along with the netsh.exe commands rather than calling it separately for faster execution |
| $netsh_command .= $system32_path . '/reg.exe ADD "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server" /t REG_DWORD /v fDenyTSConnections /d 0 /f ; '; |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set portopening"; |
| $netsh_command .= " name = \"Remote Desktop\""; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 3389"; |
| $netsh_command .= " mode = ENABLE"; |
| |
| if (!defined($remote_ip) || $remote_ip !~ /[\d\.\/]/) { |
| $remote_ip = 'all addresses'; # Set only to display in output |
| $netsh_command .= " scope = ALL"; |
| } |
| else { |
| $netsh_command .= " scope = CUSTOM"; |
| $netsh_command .= " addresses = $remote_ip"; |
| } |
| |
| # Execute the netsh.exe command |
| my ($netsh_exit_status, $netsh_output) = run_ssh_command($computer_node_name, $management_node_keys, $netsh_command); |
| |
| if (defined($netsh_output) && @$netsh_output[-1] =~ /(Ok|The object already exists)/i) { |
| notify($ERRORS{'OK'}, 0, "configured firewall to allow RDP from $remote_ip"); |
| } |
| elsif (defined($netsh_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure firewall to allow RDP from $remote_ip, exit status: $netsh_exit_status, output:\n@{$netsh_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to configure firewall to allow RDP from $remote_ip"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub firewall_enable_rdp |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_enable_rdp_private |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub firewall_enable_rdp_private { |
| 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 $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 $netsh_command; |
| |
| # Set the key to allow remote connections whenever enabling RDP |
| # Include this in the SSH command along with the netsh.exe commands rather than calling it separately for faster execution |
| $netsh_command .= $system32_path . '/reg.exe ADD "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server" /t REG_DWORD /v fDenyTSConnections /d 0 /f ; '; |
| |
| # Get the public interface name |
| # Add command to disable RDP on public interface if its name is found |
| my $public_interface_name = $self->get_public_interface_name(); |
| if ($public_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "RDP will be disabled on public interface: $public_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall delete portopening"; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 3389"; |
| $netsh_command .= " interface = \"$public_interface_name\""; |
| $netsh_command .= ' ;'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "RDP will not be disabled on public interface because public interface name could not be determined"); |
| } |
| |
| # Get the private interface name |
| # Add command to ensable RDP on private interface if its name is found |
| my $private_interface_name = $self->get_private_interface_name(); |
| if ($private_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "RDP will be enabled on private interface: $private_interface_name"); |
| |
| $netsh_command .= "netsh.exe firewall delete portopening"; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 3389"; |
| $netsh_command .= " profile = ALL"; |
| $netsh_command .= ' ;'; |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set portopening"; |
| $netsh_command .= " name = \"Remote Desktop\""; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 3389"; |
| $netsh_command .= " mode = ENABLE"; |
| $netsh_command .= " interface = \"$private_interface_name\""; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "private interface name could not be determined, RDP will be enabled for all profiles"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall set portopening"; |
| $netsh_command .= " name = \"Remote Desktop\""; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 3389"; |
| $netsh_command .= " profile = ALL"; |
| } |
| |
| # Execute the netsh.exe command |
| my ($netsh_exit_status, $netsh_output) = run_ssh_command($computer_node_name, $management_node_keys, $netsh_command); |
| |
| if (defined($netsh_output) && @$netsh_output[-1] =~ /(Ok|The object already exists)/i) { |
| notify($ERRORS{'OK'}, 0, "configured firewall to allow RDP on private interface"); |
| } |
| elsif (defined($netsh_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure firewall to allow RDP on private interface, exit status: $netsh_exit_status, output:\n@{$netsh_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to configure firewall to allow RDP on private interface"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub firewall_enable_ssh_private |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall_disable_rdp |
| |
| Parameters : |
| Returns : 1 if succeeded, 0 otherwise |
| Description : |
| |
| =cut |
| |
| sub firewall_disable_rdp { |
| 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 $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 $netsh_command; |
| |
| # Get the private interface name |
| # Add command to disable RDP on private interface if its name is found |
| my $private_interface_name = $self->get_private_interface_name(); |
| if ($private_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "RDP will be disabled on private interface: $private_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall delete portopening"; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 3389"; |
| $netsh_command .= " interface = \"$private_interface_name\""; |
| $netsh_command .= ' ;'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "private interface name could not be determined"); |
| } |
| |
| # Get the public interface name |
| # Add command to disable RDP on public interface if its name is found |
| my $public_interface_name = $self->get_public_interface_name(); |
| if ($public_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "RDP will be disabled on public interface: $public_interface_name"); |
| |
| $netsh_command .= "$system32_path/netsh.exe firewall delete portopening"; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 3389"; |
| $netsh_command .= " interface = \"$public_interface_name\""; |
| $netsh_command .= ' ;'; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "public interface name could not be determined"); |
| } |
| |
| # Add command to disable RDP for all profiles |
| $netsh_command .= "$system32_path/netsh.exe firewall delete portopening"; |
| $netsh_command .= " protocol = TCP"; |
| $netsh_command .= " port = 3389"; |
| $netsh_command .= " profile = ALL"; |
| |
| # Execute the netsh.exe command |
| my ($netsh_exit_status, $netsh_output) = run_ssh_command($computer_node_name, $management_node_keys, $netsh_command); |
| |
| if (defined($netsh_output) && @$netsh_output[-1] =~ /(Ok|The object already exists)/i) { |
| notify($ERRORS{'OK'}, 0, "configured firewall to disallow RDP"); |
| } |
| elsif (defined($netsh_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure firewall to disallow RDP, exit status: $netsh_exit_status, output:\n@{$netsh_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to configure firewall to disallow RDP"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub firewall_disable_rdp |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_network_configuration |
| |
| Parameters : |
| Returns : |
| Description : Retrieves the network configuration from the computer. Returns |
| a hash. The hash keys are the interface names: |
| $hash{<interface name>}{dhcp_enabled} |
| $hash{<interface name>}{description} |
| $hash{<interface name>}{ip_address} |
| $hash{<interface name>}{subnet_mask} |
| $hash{<interface name>}{default_gateway} |
| |
| The hash also contains 2 keys containing the names of the |
| public and private interfaces: |
| $hash{public_name} |
| $hash{private_name} |
| |
| =cut |
| |
| sub get_network_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; |
| } |
| |
| 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; |
| |
| # Check if a 'public' or 'private' network type argument was specified |
| my $network_type = lc(shift()); |
| if ($network_type && $network_type !~ /(public|private)/i) { |
| notify($ERRORS{'WARNING'}, 0, "network type argument can only be 'public' or 'private'"); |
| return; |
| } |
| |
| my %network_configuration; |
| if (!$self->{network_configuration}) { |
| notify($ERRORS{'DEBUG'}, 0, "attempting to retrieve network configuration information from $computer_node_name"); |
| |
| # Run ipconfig /all, try twice in case it fails the first time |
| my $ipconfig_attempt = 0; |
| my $ipconfig_attempt_limit = 2; |
| my ($ipconfig_exit_status, $ipconfig_output); |
| while (++$ipconfig_attempt) { |
| ($ipconfig_exit_status, $ipconfig_output) = run_ssh_command($computer_node_name, $management_node_keys, $system32_path . '/ipconfig.exe /all', '', '', 1); |
| if (defined($ipconfig_exit_status) && $ipconfig_exit_status == 0) { |
| last; |
| } |
| elsif (defined($ipconfig_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "attempt $ipconfig_attempt: failed to run ipconfig, exit status: $ipconfig_exit_status, output:\n@{$ipconfig_output}"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "attempt $ipconfig_attempt: failed to run the SSH command to run ipconfig"); |
| } |
| |
| if ($ipconfig_attempt >= $ipconfig_attempt_limit) { |
| notify($ERRORS{'WARNING'}, 0, "failed to get network configuration, made $ipconfig_attempt attempts to run ipconfig"); |
| return; |
| } |
| |
| sleep 2; |
| } |
| |
| my $interface_name; |
| my $previous_ip = 0; |
| my $setting; |
| |
| for my $line (@{$ipconfig_output}) { |
| # Find beginning of interface section |
| if ($line =~ /\A[^\s].*adapter (.*):\s*\Z/i) { |
| # Get the interface name |
| $interface_name = $1; |
| notify($ERRORS{'DEBUG'}, 0, "found interface: $interface_name"); |
| next; |
| } |
| |
| # Skip line if interface hasn't been found yet |
| next if !$interface_name; |
| |
| # Check if the interface should be ignored based on the name or description |
| if ($interface_name =~ /loopback|vmnet|afs|tunnel|6to4|isatap|teredo/i) { |
| next; |
| } |
| |
| # Take apart the line finding the setting name and value with a hideous regex |
| my ($line_setting, $value) = $line =~ /^[ ]{1,8}(\w[^\.]*\w)?[ \.:]+([^\r\n]*)/i; |
| |
| # If the setting was found in the line, use it |
| # Otherwise, use the last found setting |
| $setting = $line_setting if $line_setting; |
| |
| # Skip line if value wasn't found |
| next if !$value; |
| |
| # Normalize the setting format, make it lowercase, convert dashes and spaces to underscores |
| $setting = lc($setting); |
| $setting =~ s/[ -]/_/g; |
| |
| # Windows 6.x includes a version indicator in IP address lines such as IPv4, remove this |
| $setting =~ s/ip(v\d)?_address/ip_address/; |
| |
| # Autoconfiguration ip address will be displayed as "Autoconfiguration IP Address. . . : 169.x.x.x" |
| $setting =~ s/autoconfiguration_ip/ip/; |
| |
| # Remove the trailing s from dns_servers |
| $setting =~ s/dns_servers/dns_server/; |
| |
| # Check which setting was found and add to hash |
| if ($setting =~ /dns_servers/) { |
| push(@{$network_configuration{$interface_name}{$setting}}, $value); |
| #notify($ERRORS{'OK'}, 0, "$interface_name:$setting = @{$network_configuration{$interface_name}{$setting}}"); |
| } |
| elsif ($setting =~ /ip_address/) { |
| $value =~ s/[^\.\d]//g; |
| $network_configuration{$interface_name}{$setting}{$value} = ''; |
| $previous_ip = $value; |
| } |
| elsif ($setting =~ /subnet_mask/) { |
| $network_configuration{$interface_name}{ip_address}{$previous_ip} = $value; |
| } |
| elsif ($setting) { |
| $network_configuration{$interface_name}{$setting} = $value; |
| } |
| } |
| notify($ERRORS{'DEBUG'}, 0, 'saving network configuration in $self->{network_configuration}'); |
| $self->{network_configuration} = \%network_configuration; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "network configuration has already been retrieved"); |
| %network_configuration = %{$self->{network_configuration}}; |
| } |
| |
| # 'public' or 'private' wasn't specified, return all network interface information |
| if (!$network_type) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved network configuration:\n" . format_data(\%network_configuration)); |
| return \%network_configuration; |
| } |
| |
| # Get the computer private IP address |
| my $computer_private_ip_address = $self->data->get_computer_private_ip_address(); |
| if (!$computer_private_ip_address) { |
| notify($ERRORS{'DEBUG'}, 0, "unable to retrieve computer private IP address from reservation data"); |
| return; |
| } |
| |
| my $public_interface_name; |
| my $private_interface_name; |
| |
| # Loop through all of the network interfaces found |
| INTERFACE: foreach my $interface_name (sort keys %network_configuration) { |
| my @ip_addresses = keys %{$network_configuration{$interface_name}{ip_address}}; |
| my $description = $network_configuration{$interface_name}{description}; |
| $description = '' if !$description; |
| |
| # Make sure an IP address was found |
| if (!@ip_addresses) { |
| notify($ERRORS{'DEBUG'}, 0, "interface does not have an ip address: $interface_name"); |
| next; |
| } |
| |
| # Check if interface has private IP address assigned to it |
| if (grep { $_ eq $computer_private_ip_address } @ip_addresses) { |
| # If private interface information was requested, return a hash containing only this interface |
| notify($ERRORS{'DEBUG'}, 0, "private interface found: $interface_name, description: $description, address(es): " . join (", ", @ip_addresses)); |
| $private_interface_name = $interface_name; |
| if ($network_type =~ /private/i) { |
| last INTERFACE; |
| } |
| else { |
| next INTERFACE; |
| } |
| } |
| elsif ($network_type =~ /private/i) { |
| notify($ERRORS{'DEBUG'}, 0, "interface is not assigned the private IP address: $interface_name (" . join (", ", @ip_addresses) . ")"); |
| next INTERFACE; |
| } |
| |
| # Check if the interface should be ignored based on the name or description |
| if ($interface_name =~ /(loopback|vmnet|afs|tunnel|6to4|isatap|teredo)/i) { |
| notify($ERRORS{'DEBUG'}, 0, "interface '$interface_name' ignored because name contains '$1', address(es): " . join (", ", @ip_addresses)); |
| next INTERFACE; |
| } |
| elsif ($description =~ /(loopback|virtual|afs|tunnel|pseudo|6to4|isatap)/i) { |
| notify($ERRORS{'DEBUG'}, 0, "interface '$interface_name' ignored because description contains '$1': '$description', address(es): " . join (", ", @ip_addresses)); |
| next INTERFACE; |
| } |
| |
| # Loop through the IP addresses for the interface |
| # Once a public address is found, return the data for that interface |
| IP_ADDRESS: for my $ip_address (@ip_addresses) { |
| my $is_public = is_public_ip_address($ip_address); |
| my $default_gateway = $network_configuration{$interface_name}{default_gateway}; |
| |
| if ($is_public) { |
| if ($default_gateway) { |
| notify($ERRORS{'DEBUG'}, 0, "public interface found with default gateway: $interface_name, address(es): " . join (", ", @ip_addresses) . ", default gateway: $default_gateway"); |
| $public_interface_name = $interface_name; |
| last INTERFACE; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "interface found with public address but default gateway is not set: $interface_name, address(es): " . join (", ", @ip_addresses)); |
| $public_interface_name = $interface_name; |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "interface found with non-public address not matching private address for reservation: $interface_name, address(es): " . join (", ", @ip_addresses)); |
| |
| if ($public_interface_name) { |
| notify($ERRORS{'DEBUG'}, 0, "already found another interface with a non-public address not matching private address for reservation, the one previously found will be used if a public address isn't found"); |
| next; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "interface will be returned if another with a public address isn't found"); |
| $public_interface_name = $interface_name; |
| } |
| } |
| |
| } |
| } |
| |
| if ($network_type =~ /private/i) { |
| if ($private_interface_name) { |
| return {$private_interface_name => $network_configuration{$private_interface_name}}; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "did not find an interface using the private IP address for the reservation: $computer_private_ip_address\n" . format_data(\%network_configuration)); |
| return; |
| } |
| } |
| else { |
| if ($public_interface_name) { |
| return {$public_interface_name => $network_configuration{$public_interface_name}}; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to find a public interface:\n" . format_data(\%network_configuration)); |
| return; |
| } |
| } |
| } ## end sub get_network_configuration |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_private_interface_name |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub get_private_interface_name { |
| 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; |
| } |
| |
| # Make sure network configuration was retrieved |
| my $network_configuration = $self->get_network_configuration('private'); |
| if (!$network_configuration) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve network configuration"); |
| return; |
| } |
| |
| my $interface_name = (keys(%{$network_configuration}))[0]; |
| notify($ERRORS{'DEBUG'}, 0, "returning private interface name: $interface_name"); |
| |
| return $interface_name; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_public_interface_name |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub get_public_interface_name { |
| 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; |
| } |
| |
| # Make sure network configuration was retrieved |
| my $network_configuration = $self->get_network_configuration('public'); |
| if (!$network_configuration) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve network configuration"); |
| return; |
| } |
| |
| my $interface_name = (keys(%{$network_configuration}))[0]; |
| notify($ERRORS{'DEBUG'}, 0, "returning public interface name: $interface_name"); |
| |
| return $interface_name; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_private_mac_address |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub get_private_mac_address { |
| 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; |
| } |
| |
| # Make sure network configuration was retrieved |
| my $network_configuration = $self->get_network_configuration('private'); |
| if (!$network_configuration) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve network configuration"); |
| return; |
| } |
| |
| my $interface_name = (keys(%{$network_configuration}))[0]; |
| my $mac_address = $network_configuration->{$interface_name}{physical_address}; |
| notify($ERRORS{'DEBUG'}, 0, "returning private MAC address: $mac_address"); |
| return $mac_address; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_public_mac_address |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub get_public_mac_address { |
| 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; |
| } |
| |
| # Make sure network configuration was retrieved |
| my $network_configuration = $self->get_network_configuration('public'); |
| if (!$network_configuration) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve network configuration"); |
| return; |
| } |
| |
| my $interface_name = (keys(%{$network_configuration}))[0]; |
| my $mac_address = $network_configuration->{$interface_name}{physical_address}; |
| notify($ERRORS{'DEBUG'}, 0, "returning public MAC address: $mac_address"); |
| return $mac_address; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_private_ip_address |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub get_private_ip_address { |
| 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; |
| } |
| |
| # Make sure network configuration was retrieved |
| my $network_configuration = $self->get_network_configuration('private'); |
| if (!$network_configuration) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve network configuration"); |
| return; |
| } |
| |
| my $interface_name = (keys(%{$network_configuration}))[0]; |
| my $ip_address_config = $network_configuration->{$interface_name}{ip_address}; |
| my $ip_address = (keys(%$ip_address_config))[0]; |
| notify($ERRORS{'DEBUG'}, 0, "returning private IP address: $ip_address"); |
| return $ip_address; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_public_ip_address |
| |
| Parameters : none |
| Returns : string |
| Description : Retrieves the public IP address assigned to the computer. If an |
| auto-generated public IP address is detected (169.254.x.x or |
| 0.0.0.0), the public interface is using DHCP, and the management |
| node is configured to use DHCP, an attempt will be made to call |
| 'ipconfig /renew' to obtain a valid public IP address. |
| |
| =cut |
| |
| sub get_public_ip_address { |
| 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 $argument = shift; |
| |
| # Make sure network configuration was retrieved |
| my $network_configuration = $self->get_network_configuration('public'); |
| if (!$network_configuration) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve network configuration"); |
| return; |
| } |
| |
| my $public_ip_configuration = $self->data->get_management_node_public_ip_configuration(); |
| |
| my $interface_name = (keys(%{$network_configuration}))[0]; |
| |
| my $ip_address_config = $network_configuration->{$interface_name}{ip_address}; |
| my $ip_address = (keys(%$ip_address_config))[0]; |
| |
| my $dhcp_enabled = $network_configuration->{$interface_name}{dhcp_enabled}; |
| |
| # Check if DHCP is to be used and an auto-generated IP address is detected |
| if ($public_ip_configuration =~ /dhcp/i && $dhcp_enabled =~ /yes/i && $ip_address =~ /^(169\.254\.|0\.0\.0\.0)/) { |
| # Check if this is the 2nd attempt to retrieve the public IP address |
| # This subroutine calls itself with a 'renewed' argument if an auto-generated IP address was detected and 'ipconfig /renew' was called |
| if ($argument && $argument eq 'renewed') { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve public IP address, public interface '$interface_name' is still assigned an auto-generated IP address after attempting 'ipconfig /renew \"$interface_name\"'"); |
| return; |
| } |
| |
| notify($ERRORS{'WARNING'}, 0, "public interface '$interface_name' has DHCP enabled and is assigned an auto-generated IP address: $ip_address, management node DHCP configuration: '$public_ip_configuration'"); |
| |
| # Attempt to renew the IP address |
| if (!$self->ipconfig_renew($interface_name)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve public IP address, failed to renew the IP address for the public interface '$interface_name'"); |
| return; |
| } |
| |
| # Call this subroutine again, pass the 'renewed' argument so that 'ipconfig /renew' isn't called again (prevent infinite loop) |
| return $self->get_public_ip_address('renewed'); |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "returning public IP address: $ip_address"); |
| return $ip_address; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_public_default_gateway |
| |
| Parameters : None |
| Returns : If successful: string containing IP address |
| If failed: false |
| Description : Returns the default gateway currently configured for the |
| computer's public interface. |
| |
| =cut |
| |
| sub get_public_default_gateway { |
| 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_nodename = $self->data->get_computer_node_name() || 'node'; |
| |
| my $default_gateway; |
| |
| # Check the management node's DHCP IP configuration mode (static or dynamic) |
| my $ip_configuration = $self->data->get_management_node_public_ip_configuration(); |
| notify($ERRORS{'DEBUG'}, 0, "IP configuration mode in use: $ip_configuration"); |
| if ($ip_configuration !~ /static/i) { |
| # Management node is using dynamic public IP addresses |
| # Retrieve public network configuration currently in use on computer |
| my $network_configuration = $self->get_network_configuration('public'); |
| if ($network_configuration) { |
| # Get the default gateway out of the network configuration currently being used |
| my $interface_name = (keys(%{$network_configuration}))[0]; |
| $default_gateway = $network_configuration->{$interface_name}{default_gateway}; |
| if ($default_gateway) { |
| notify($ERRORS{'DEBUG'}, 0, "returning default gateway currently in use on $computer_nodename: $default_gateway"); |
| return $default_gateway; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine default gateway currently in use on $computer_nodename"); |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve public network configuration currently in use on $computer_nodename"); |
| } |
| } |
| |
| # Static addresses used, get default gateway address configured for management node |
| $default_gateway = $self->data->get_management_node_public_default_gateway(); |
| |
| # Make sure default gateway was retrieved |
| if ($default_gateway) { |
| notify($ERRORS{'DEBUG'}, 0, "returning management node's default gateway address: $default_gateway"); |
| return $default_gateway; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve management node's default gateway address"); |
| return; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_private_subnet_mask |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub get_private_subnet_mask { |
| 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; |
| } |
| |
| # Get private network configuration |
| my $network_configuration = $self->get_network_configuration('private'); |
| if (!$network_configuration) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve network configuration"); |
| return; |
| } |
| |
| my $interface_name = (keys(%{$network_configuration}))[0]; |
| my $ip_addresses = $network_configuration->{$interface_name}{ip_address}; |
| my $ip_address = (keys(%$ip_addresses))[0]; |
| my $subnet_mask = $ip_addresses->{$ip_address}; |
| |
| if (!$subnet_mask) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine private subnet mask, network configuration:\n" . format_data($network_configuration)); |
| return; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "returning private subnet mask: $subnet_mask"); |
| return $subnet_mask; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 enable_dhcp |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub enable_dhcp { |
| 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 $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 $interface_name_argument = shift; |
| my @interface_names; |
| if (!$interface_name_argument) { |
| push(@interface_names, $self->get_public_interface_name()); |
| push(@interface_names, $self->get_private_interface_name()); |
| } |
| elsif ($interface_name_argument =~ /public/i) { |
| push(@interface_names, $self->get_public_interface_name()); |
| } |
| elsif ($interface_name_argument =~ /private/i) { |
| push(@interface_names, $self->get_private_interface_name()); |
| } |
| else { |
| push(@interface_names, $interface_name_argument); |
| } |
| |
| for my $interface_name (@interface_names) { |
| # Use netsh.exe to set the NIC to use DHCP |
| my $set_dhcp_command = $system32_path . '/netsh.exe interface ip set address name="' . $interface_name . '" source=dhcp'; |
| my ($set_dhcp_status, $set_dhcp_output) = run_ssh_command($computer_node_name, $management_node_keys, $set_dhcp_command); |
| if (defined($set_dhcp_status) && $set_dhcp_status == 0) { |
| notify($ERRORS{'OK'}, 0, "set interface '$interface_name' to use dhcp"); |
| } |
| elsif (defined($set_dhcp_output) && grep(/dhcp is already enabled/i, @{$set_dhcp_output})) { |
| notify($ERRORS{'OK'}, 0, "dhcp is already enabled on interface '$interface_name'"); |
| } |
| elsif (defined($set_dhcp_status)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set interface '$interface_name' to use dhcp, exit status: $set_dhcp_status, output:\n@{$set_dhcp_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to set interface '$interface_name' to use dhcp"); |
| return 0; |
| } |
| } ## end for my $interface_name (@interface_names) |
| |
| # Run ipconfig /renew after setting the adapters to use DHCP |
| # The default gateway gets lost otherwise |
| return $self->ipconfig_renew(); |
| } ## end sub enable_dhcp |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 ipconfig_renew |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub ipconfig_renew { |
| 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 $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 $interface_name_argument = shift; |
| |
| # Assemble the ipconfig command, include the interface name if argument was specified |
| my $ipconfig_command = $system32_path . '/ipconfig.exe /renew'; |
| if ($interface_name_argument) { |
| $ipconfig_command .= " \"$interface_name_argument\""; |
| } |
| |
| # Run ipconfig |
| my ($ipconfig_status, $ipconfig_output) = run_ssh_command($computer_node_name, $management_node_keys, $ipconfig_command); |
| if (defined($ipconfig_status) && $ipconfig_status == 0) { |
| notify($ERRORS{'OK'}, 0, "ran ipconfig /renew"); |
| |
| # Undefined previously retrieved network configuration so that it is retrieved again |
| $self->{network_configuration} = undef; |
| } |
| elsif (defined($ipconfig_status)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ipconfig /renew, exit status: $ipconfig_status, output:\n@{$ipconfig_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to to run ipconfig /renew"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_capture_configuration_files |
| |
| Parameters : |
| Returns : |
| Description : Deletes the capture configuration directory. |
| |
| =cut |
| |
| sub delete_capture_configuration_files { |
| 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 $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; |
| |
| # Remove old logon and logoff scripts |
| $self->delete_files_by_pattern($system32_path . '/GroupPolicy/User/Scripts', '.*\(Prepare\|prepare\|Cleanup\|cleanup\|post_load\).*'); |
| |
| # Remove old scripts and utilities |
| $self->delete_files_by_pattern('C:/Cygwin/home/root', '.*\(vbs\|exe\|cmd\|bat\|log\)'); |
| |
| # Remove old C:\VCL directory if it exists |
| $self->delete_file('C:/VCL'); |
| |
| # Delete VCL scheduled task if it exists |
| $self->delete_scheduled_task('VCL Startup Configuration'); |
| |
| # Remove VCLprepare.cmd and VCLcleanup.cmd lines from scripts.ini file |
| $self->remove_group_policy_script('logon', 'VCLprepare.cmd'); |
| $self->remove_group_policy_script('logoff', 'VCLcleanup.cmd'); |
| |
| # Remove old root Application Data/VCL directory |
| $self->delete_file('$SYSTEMDRIVE/Documents and Settings/root/Application Data/VCL'); |
| |
| # Remove existing configuration files if they exist |
| notify($ERRORS{'OK'}, 0, "attempting to remove old configuration directory if it exists: $NODE_CONFIGURATION_DIRECTORY"); |
| if (!$self->delete_file($NODE_CONFIGURATION_DIRECTORY)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to remove existing configuration directory: $NODE_CONFIGURATION_DIRECTORY"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 add_group_policy_script |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub add_group_policy_script { |
| 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 $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; |
| |
| # Get the arguments |
| my $stage_argument = shift; |
| my $cmdline_argument = shift; |
| my $parameters_argument = shift; |
| if (!$stage_argument || $stage_argument !~ /^(logon|logoff)$/i) { |
| notify($ERRORS{'WARNING'}, 0, "stage (logon/logoff) argument was not specified"); |
| return; |
| } |
| if (!$cmdline_argument) { |
| notify($ERRORS{'WARNING'}, 0, "CmdLine argument was not specified"); |
| return; |
| } |
| if (!$parameters_argument) { |
| $parameters_argument = ''; |
| } |
| |
| # Capitalize the first letter of logon/logoff |
| $stage_argument = lc($stage_argument); |
| $stage_argument = "L" . substr($stage_argument, 1); |
| |
| # Store the stage name (logon/logoff) not being modified |
| my $opposite_stage_argument; |
| if ($stage_argument =~ /logon/i) { |
| $opposite_stage_argument = 'Logoff'; |
| } |
| else { |
| $opposite_stage_argument = 'Logon'; |
| } |
| |
| # Path to scripts.ini file |
| my $scripts_ini = $system32_path . '/GroupPolicy/User/Scripts/scripts.ini'; |
| |
| # Set the owner of scripts.ini to root |
| my $chown_command = "touch $scripts_ini && chown root $scripts_ini"; |
| my ($chown_status, $chown_output) = run_ssh_command($computer_node_name, $management_node_keys, $chown_command); |
| if (defined($chown_status) && $chown_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "set root as owner of scripts.ini"); |
| } |
| elsif (defined($chown_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set root as owner of scripts.ini, exit status: $chown_status, output:\n@{$chown_output}"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to set root as owner of scripts.ini"); |
| } |
| |
| # Set the permissions of scripts.ini to 664 |
| my $chmod_command = "chmod 664 $scripts_ini"; |
| my ($chmod_status, $chmod_output) = run_ssh_command($computer_node_name, $management_node_keys, $chmod_command); |
| if (defined($chmod_status) && $chmod_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "ran chmod on scripts.ini"); |
| } |
| elsif (defined($chmod_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run chmod 664 on scripts.ini, exit status: $chmod_status, output:\n@{$chmod_output}"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to run chmod 664 on scripts.ini"); |
| } |
| |
| # Clear hidden, system, and readonly flags on scripts.ini |
| my $attrib_command = "attrib -H -S -R $scripts_ini"; |
| my ($attrib_status, $attrib_output) = run_ssh_command($computer_node_name, $management_node_keys, $attrib_command); |
| if (defined($attrib_status) && $attrib_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "ran attrib -H -S -R on scripts.ini"); |
| } |
| elsif (defined($attrib_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run attrib -H -S -R on scripts.ini, exit status: $attrib_status, output:\n@{$attrib_output}"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to run attrib -H -S -R on scripts.ini"); |
| } |
| |
| # Get the contents of scripts.ini |
| my $cat_command = "cat $scripts_ini"; |
| my ($cat_status, $cat_output) = run_ssh_command($computer_node_name, $management_node_keys, $cat_command, '', '', 1); |
| if (defined($cat_status) && $cat_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved scripts.ini contents:\n" . join("\n", @{$cat_output})); |
| } |
| elsif (defined($cat_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to cat scripts.ini contents"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to scripts.ini contents"); |
| } |
| |
| # Create a string containing all of the lines in scripts.ini |
| my $scripts_ini_string = join("\n", @{$cat_output}) || ''; |
| |
| # Remove any carriage returns to make pattern matching easier |
| $scripts_ini_string =~ s/\r//gs; |
| |
| # Get a string containing just the section being modified (logon/logoff) |
| my ($section_string) = $scripts_ini_string =~ /(\[$stage_argument\][^\[\]]*)/is; |
| $section_string = "[$stage_argument]" if !$section_string; |
| notify($ERRORS{'DEBUG'}, 0, "scripts.ini $stage_argument section:\n" . string_to_ascii($section_string)); |
| |
| my ($opposite_section_string) = $scripts_ini_string =~ /(\[$opposite_stage_argument\][^\[\]]*)/is; |
| $opposite_section_string = "[$opposite_stage_argument]" if !$opposite_section_string; |
| notify($ERRORS{'DEBUG'}, 0, "scripts.ini $opposite_stage_argument section:\n" . string_to_ascii($opposite_section_string)); |
| |
| my @section_lines = split(/[\r\n]+/, $section_string); |
| notify($ERRORS{'DEBUG'}, 0, "scripts.ini $stage_argument section line count: " . scalar @section_lines); |
| |
| my %scripts_original; |
| for my $section_line (@section_lines) { |
| if ($section_line =~ /(\d+)Parameters\s*=(.*)/i) { |
| my $index = $1; |
| my $parameters = $2; |
| if (!defined $scripts_original{$index}{Parameters}) { |
| $scripts_original{$index}{Parameters} = $parameters; |
| #notify($ERRORS{'DEBUG'}, 0, "found $stage_argument parameters:\nline: '$section_line'\nparameters: '$parameters'\nindex: $index"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "found duplicate $stage_argument parameters line for index $index"); |
| } |
| } |
| elsif ($section_line =~ /(\d+)CmdLine\s*=(.*)/i) { |
| my $index = $1; |
| my $cmdline = $2; |
| if (!defined $scripts_original{$index}{CmdLine}) { |
| $scripts_original{$index}{CmdLine} = $cmdline; |
| #notify($ERRORS{'DEBUG'}, 0, "found $stage_argument cmdline:\nline: '$section_line'\ncmdline: '$cmdline'\nindex: $index"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "found duplicate $stage_argument CmdLine line for index $index"); |
| } |
| } |
| elsif ($section_line =~ /\[$stage_argument\]/i) { |
| #notify($ERRORS{'DEBUG'}, 0, "found $stage_argument heading:\nline: '$section_line'"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "found unexpected line: '$section_line'"); |
| } |
| } |
| |
| my %scripts_modified; |
| my $index_modified = 0; |
| foreach my $index (sort keys %scripts_original) { |
| if (!defined $scripts_original{$index}{CmdLine}) { |
| notify($ERRORS{'WARNING'}, 0, "CmdLine not specified for index $index"); |
| next; |
| } |
| elsif ($scripts_original{$index}{CmdLine} =~ /^\s*$/) { |
| notify($ERRORS{'WARNING'}, 0, "CmdLine blank for index $index"); |
| next; |
| } |
| if (!defined $scripts_original{$index}{Parameters}) { |
| notify($ERRORS{'WARNING'}, 0, "Parameters not specified for index $index"); |
| $scripts_original{$index}{Parameters} = ''; |
| } |
| |
| if ($scripts_original{$index}{CmdLine} =~ /$cmdline_argument/i && $scripts_original{$index}{Parameters} =~ /$parameters_argument/i) { |
| notify($ERRORS{'DEBUG'}, 0, "replacing existing $stage_argument script at index $index:\ncmdline: $scripts_original{$index}{CmdLine}\nparameters: $scripts_original{$index}{Parameters}"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "retaining existing $stage_argument script at index $index:\ncmdline: $scripts_original{$index}{CmdLine}\nparameters: $scripts_original{$index}{Parameters}"); |
| $scripts_modified{$index_modified}{CmdLine} = $scripts_original{$index}{CmdLine}; |
| $scripts_modified{$index_modified}{Parameters} = $scripts_original{$index}{Parameters}; |
| $index_modified++; |
| } |
| } |
| |
| # Add the argument script to the hash |
| $scripts_modified{$index_modified}{CmdLine} = $cmdline_argument; |
| $scripts_modified{$index_modified}{Parameters} = $parameters_argument; |
| $index_modified++; |
| |
| #notify($ERRORS{'DEBUG'}, 0, "arguments:\ncmdline: $cmdline_argument\nparameters: $parameters_argument"); |
| #notify($ERRORS{'DEBUG'}, 0, "original $stage_argument scripts data:\n" . format_data(\%scripts_original)); |
| #notify($ERRORS{'DEBUG'}, 0, "modified $stage_argument scripts data:\n" . format_data(\%scripts_modified)); |
| |
| my $section_string_new = "[$stage_argument]\n"; |
| foreach my $index_new (sort keys(%scripts_modified)) { |
| $section_string_new .= $index_new . "CmdLine=$scripts_modified{$index_new}{CmdLine}\n"; |
| $section_string_new .= $index_new . "Parameters=$scripts_modified{$index_new}{Parameters}\n"; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "original $stage_argument scripts section:\n$section_string"); |
| notify($ERRORS{'DEBUG'}, 0, "modified $stage_argument scripts section:\n$section_string_new"); |
| |
| my $scripts_ini_modified; |
| if ($stage_argument =~ /logon/i) { |
| $scripts_ini_modified = "$section_string_new\n$opposite_section_string"; |
| } |
| else { |
| $scripts_ini_modified = "$opposite_section_string\n$section_string_new"; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "modified scripts.ini contents:\n$scripts_ini_modified"); |
| |
| # Escape quote characters |
| $scripts_ini_modified =~ s/"/\\"/gs; |
| |
| # Echo the modified contents to scripts.ini |
| my $echo_command = "echo \"$scripts_ini_modified\" > $scripts_ini"; |
| my ($echo_status, $echo_output) = run_ssh_command($computer_node_name, $management_node_keys, $echo_command); |
| if (defined($echo_status) && $echo_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "echo'd modified contents to scripts.ini"); |
| } |
| elsif (defined($echo_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to echo modified contents to scripts.ini, exit status: $echo_status, output:\n@{$echo_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to echo modified contents to scripts.ini"); |
| return; |
| } |
| |
| # Run unix2dos on scripts.ini |
| $self->run_unix2dos($scripts_ini); |
| |
| # Get the modified contents of scripts.ini |
| my $cat_modified_command = "cat $scripts_ini"; |
| my ($cat_modified_status, $cat_modified_output) = run_ssh_command($computer_node_name, $management_node_keys, $cat_modified_command, '', '', 1); |
| if (defined($cat_modified_status) && $cat_modified_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved modified scripts.ini contents"); |
| } |
| elsif (defined($cat_modified_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to cat scripts.ini contents"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to scripts.ini contents"); |
| } |
| |
| ## Run gpupdate so the new settings take effect immediately |
| #$self->run_gpupdate(); |
| |
| notify($ERRORS{'OK'}, 0, "added '$cmdline_argument' $stage_argument script to scripts.ini\noriginal contents:\n$scripts_ini_string\n-----\nnew contents:\n" . join("\n", @{$cat_modified_output})); |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 remove_group_policy_script |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub remove_group_policy_script { |
| 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 $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; |
| |
| # Get the arguments |
| my $stage_argument = shift; |
| my $cmdline_argument = shift; |
| if (!$stage_argument || $stage_argument !~ /^(logon|logoff)$/i) { |
| notify($ERRORS{'WARNING'}, 0, "stage (logon/logoff) argument was not specified"); |
| return; |
| } |
| if (!$cmdline_argument) { |
| notify($ERRORS{'WARNING'}, 0, "CmdLine argument was not specified"); |
| return; |
| } |
| |
| # Capitalize the first letter of logon/logoff |
| $stage_argument = lc($stage_argument); |
| $stage_argument = "L" . substr($stage_argument, 1); |
| |
| # Store the stage name (logon/logoff) not being modified |
| my $opposite_stage_argument; |
| if ($stage_argument =~ /logon/i) { |
| $opposite_stage_argument = 'Logoff'; |
| } |
| else { |
| $opposite_stage_argument = 'Logon'; |
| } |
| |
| # Attempt to delete batch or script files specified by the argument |
| $self->delete_files_by_pattern("$system32_path/GroupPolicy/User/Scripts", ".*$cmdline_argument.*", 2); |
| |
| # Path to scripts.ini file |
| my $scripts_ini = $system32_path . '/GroupPolicy/User/Scripts/scripts.ini'; |
| |
| # Set the owner of scripts.ini to root |
| my $chown_command = "touch $scripts_ini && chown root $scripts_ini"; |
| my ($chown_status, $chown_output) = run_ssh_command($computer_node_name, $management_node_keys, $chown_command); |
| if (defined($chown_output) && grep(/no such file/i, @$chown_output)) { |
| notify($ERRORS{'DEBUG'}, 0, "scripts.ini file does not exist, nothing to remove"); |
| return 1; |
| } |
| elsif (defined($chown_status) && $chown_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "set root as owner of scripts.ini"); |
| } |
| elsif (defined($chown_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set root as owner of scripts.ini, exit status: $chown_status, output:\n@{$chown_output}"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to set root as owner of scripts.ini"); |
| } |
| |
| # Set the permissions of scripts.ini to 664 |
| my $chmod_command = "chmod 664 $scripts_ini"; |
| my ($chmod_status, $chmod_output) = run_ssh_command($computer_node_name, $management_node_keys, $chmod_command); |
| if (defined($chmod_status) && $chmod_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "ran chmod on scripts.ini"); |
| } |
| elsif (defined($chmod_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run chmod 664 on scripts.ini, exit status: $chmod_status, output:\n@{$chmod_output}"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to run chmod 664 on scripts.ini"); |
| } |
| |
| # Clear hidden, system, and readonly flags on scripts.ini |
| my $attrib_command = "attrib -H -S -R $scripts_ini"; |
| my ($attrib_status, $attrib_output) = run_ssh_command($computer_node_name, $management_node_keys, $attrib_command); |
| if (defined($attrib_status) && $attrib_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "ran attrib -H -S -R on scripts.ini"); |
| } |
| elsif (defined($attrib_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run attrib -H -S -R on scripts.ini, exit status: $attrib_status, output:\n@{$attrib_output}"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to run attrib -H -S -R on scripts.ini"); |
| } |
| |
| # Get the contents of scripts.ini |
| my $cat_command = "cat $scripts_ini"; |
| my ($cat_status, $cat_output) = run_ssh_command($computer_node_name, $management_node_keys, $cat_command, '', '', 1); |
| if (defined($cat_status) && $cat_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved scripts.ini contents:\n" . join("\n", @{$cat_output})); |
| } |
| elsif (defined($cat_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to cat scripts.ini contents"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to scripts.ini contents"); |
| } |
| |
| # Create a string containing all of the lines in scripts.ini |
| my $scripts_ini_string = join("\n", @{$cat_output}) || ''; |
| |
| # Remove any carriage returns to make pattern matching easier |
| $scripts_ini_string =~ s/\r//gs; |
| |
| # Get a string containing just the section being modified (logon/logoff) |
| my ($section_string) = $scripts_ini_string =~ /(\[$stage_argument\][^\[\]]*)/is; |
| $section_string = "[$stage_argument]" if !$section_string; |
| notify($ERRORS{'DEBUG'}, 0, "scripts.ini $stage_argument section:\n" . string_to_ascii($section_string)); |
| |
| my ($opposite_section_string) = $scripts_ini_string =~ /(\[$opposite_stage_argument\][^\[\]]*)/is; |
| $opposite_section_string = "[$opposite_stage_argument]" if !$opposite_section_string; |
| notify($ERRORS{'DEBUG'}, 0, "scripts.ini $opposite_stage_argument section:\n" . string_to_ascii($opposite_section_string)); |
| |
| my @section_lines = split(/[\r\n]+/, $section_string); |
| notify($ERRORS{'DEBUG'}, 0, "scripts.ini $stage_argument section line count: " . scalar @section_lines); |
| |
| my %scripts_original; |
| for my $section_line (@section_lines) { |
| if ($section_line =~ /(\d+)Parameters\s*=(.*)/i) { |
| my $index = $1; |
| my $parameters = $2; |
| if (!defined $scripts_original{$index}{Parameters}) { |
| $scripts_original{$index}{Parameters} = $parameters; |
| #notify($ERRORS{'DEBUG'}, 0, "found $stage_argument parameters:\nline: '$section_line'\nparameters: '$parameters'\nindex: $index"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "found duplicate $stage_argument parameters line for index $index"); |
| } |
| } |
| elsif ($section_line =~ /(\d+)CmdLine\s*=(.*)/i) { |
| my $index = $1; |
| my $cmdline = $2; |
| if (!defined $scripts_original{$index}{CmdLine}) { |
| $scripts_original{$index}{CmdLine} = $cmdline; |
| #notify($ERRORS{'DEBUG'}, 0, "found $stage_argument cmdline:\nline: '$section_line'\ncmdline: '$cmdline'\nindex: $index"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "found duplicate $stage_argument CmdLine line for index $index"); |
| } |
| } |
| elsif ($section_line =~ /\[$stage_argument\]/i) { |
| #notify($ERRORS{'DEBUG'}, 0, "found $stage_argument heading:\nline: '$section_line'"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "found unexpected line: '$section_line'"); |
| } |
| } |
| |
| my %scripts_modified; |
| my $index_modified = 0; |
| foreach my $index (sort keys %scripts_original) { |
| if (!defined $scripts_original{$index}{CmdLine}) { |
| notify($ERRORS{'WARNING'}, 0, "CmdLine not specified for index $index"); |
| next; |
| } |
| elsif ($scripts_original{$index}{CmdLine} =~ /^\s*$/) { |
| notify($ERRORS{'WARNING'}, 0, "CmdLine blank for index $index"); |
| next; |
| } |
| if (!defined $scripts_original{$index}{Parameters}) { |
| notify($ERRORS{'WARNING'}, 0, "Parameters not specified for index $index"); |
| $scripts_original{$index}{Parameters} = ''; |
| } |
| |
| if ($scripts_original{$index}{CmdLine} =~ /$cmdline_argument/i) { |
| notify($ERRORS{'DEBUG'}, 0, "removing $stage_argument script at index $index:\ncmdline: $scripts_original{$index}{CmdLine}\nparameters: $scripts_original{$index}{Parameters}"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "retaining existing $stage_argument script at index $index:\ncmdline: $scripts_original{$index}{CmdLine}\nparameters: $scripts_original{$index}{Parameters}"); |
| $scripts_modified{$index_modified}{CmdLine} = $scripts_original{$index}{CmdLine}; |
| $scripts_modified{$index_modified}{Parameters} = $scripts_original{$index}{Parameters}; |
| $index_modified++; |
| } |
| } |
| |
| my $section_string_new = "[$stage_argument]\n"; |
| foreach my $index_new (sort keys(%scripts_modified)) { |
| $section_string_new .= $index_new . "CmdLine=$scripts_modified{$index_new}{CmdLine}\n"; |
| $section_string_new .= $index_new . "Parameters=$scripts_modified{$index_new}{Parameters}\n"; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "original $stage_argument scripts section:\n$section_string"); |
| notify($ERRORS{'DEBUG'}, 0, "modified $stage_argument scripts section:\n$section_string_new"); |
| |
| my $scripts_ini_modified; |
| if ($stage_argument =~ /logon/i) { |
| $scripts_ini_modified = "$section_string_new\n$opposite_section_string"; |
| } |
| else { |
| $scripts_ini_modified = "$opposite_section_string\n$section_string_new"; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "modified scripts.ini contents:\n$scripts_ini_modified"); |
| |
| $scripts_ini_modified =~ s/"/\\"/gs; |
| |
| # Echo the modified contents to scripts.ini |
| my $echo_command = "echo \"$scripts_ini_modified\" > $scripts_ini"; |
| my ($echo_status, $echo_output) = run_ssh_command($computer_node_name, $management_node_keys, $echo_command); |
| if (defined($echo_status) && $echo_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "echo'd modified contents to scripts.ini"); |
| } |
| elsif (defined($echo_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to echo modified contents to scripts.ini, exit status: $echo_status, output:\n@{$echo_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to echo modified contents to scripts.ini"); |
| return; |
| } |
| |
| # Run unix2dos on scripts.ini |
| $self->run_unix2dos($scripts_ini); |
| |
| # Get the modified contents of scripts.ini |
| my $cat_modified_command = "cat $scripts_ini"; |
| my ($cat_modified_status, $cat_modified_output) = run_ssh_command($computer_node_name, $management_node_keys, $cat_modified_command, '', '', 1); |
| if (defined($cat_modified_status) && $cat_modified_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved modified scripts.ini contents"); |
| } |
| elsif (defined($cat_modified_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to cat scripts.ini contents"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to scripts.ini contents"); |
| } |
| |
| notify($ERRORS{'OK'}, 0, "removed '$cmdline_argument' $stage_argument script from scripts.ini\noriginal contents:\n$scripts_ini_string\n-----\nnew contents:\n" . join("\n", @{$cat_modified_output})); |
| |
| ## Run gpupdate so the new settings take effect immediately |
| #$self->run_gpupdate(); |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 run_gpupdate |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub run_gpupdate { |
| 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 $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 $gpupdate_command = "cmd.exe /c $system32_path/gpupdate.exe /Force"; |
| my ($gpupdate_status, $gpupdate_output) = run_ssh_command($computer_node_name, $management_node_keys, $gpupdate_command); |
| if (defined($gpupdate_output) && !grep(/error/i, @{$gpupdate_output})) { |
| notify($ERRORS{'OK'}, 0, "ran gpupdate /force"); |
| } |
| elsif (defined($gpupdate_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run gpupdate /force, exit status: $gpupdate_status, output:\n@{$gpupdate_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to run gpupdate /force"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 run_unix2dos |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub run_unix2dos { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Get the arguments |
| my $file_path = shift; |
| if (!$file_path) { |
| notify($ERRORS{'WARNING'}, 0, "file path was not specified as an argument"); |
| return; |
| } |
| |
| # Run unix2dos on scripts.ini |
| my $unix2dos_command = "unix2dos $file_path"; |
| my ($unix2dos_status, $unix2dos_output) = run_ssh_command($computer_node_name, $management_node_keys, $unix2dos_command); |
| if (defined($unix2dos_status) && $unix2dos_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "ran unix2dos on $file_path"); |
| } |
| elsif (defined($unix2dos_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run unix2dos on $file_path, exit status: $unix2dos_status, output:\n@{$unix2dos_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to run unix2dos on $file_path"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 search_and_replace_in_files |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub search_and_replace_in_files { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Get the arguments |
| my $base_directory = shift; |
| my $search_pattern = shift; |
| my $replace_string = shift; |
| if (!$base_directory) { |
| notify($ERRORS{'WARNING'}, 0, "base directory was not specified as an argument"); |
| return; |
| } |
| if (!$search_pattern) { |
| notify($ERRORS{'WARNING'}, 0, "search pattern was not specified as an argument"); |
| return; |
| } |
| if (!$replace_string) { |
| notify($ERRORS{'WARNING'}, 0, "replace string was not specified as an argument"); |
| return; |
| } |
| |
| # Replace backslashes with a forward slash in the base directory path |
| $base_directory =~ s/\\+/\//g; |
| |
| # Escape forward slashes in the search pattern and replace string |
| $search_pattern =~ s/\//\\\//g; |
| $replace_string =~ s/\//\\\//g; |
| |
| # Escape special characters in the search pattern |
| $search_pattern =~ s/([!-])/\\$1/g; |
| |
| # Run grep to find files matching pattern |
| my $grep_command = "/bin/grep -ilr \"$search_pattern\" \"$base_directory\" 2>&1 | grep -Ev \"\.(exe|dll)\""; |
| my ($grep_status, $grep_output) = run_ssh_command($computer_node_name, $management_node_keys, $grep_command, '', '', 0); |
| if (!defined($grep_status)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to run grep on directory: $base_directory, pattern: $search_pattern"); |
| return; |
| } |
| elsif ("@$grep_output" =~ /No such file/i) { |
| notify($ERRORS{'DEBUG'}, 0, "no files to process, base directory does not exist: $base_directory"); |
| return 1; |
| } |
| elsif ("@$grep_output" =~ /(grep|bash):/i) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred running command '$grep_command':\n" . join("\n", @$grep_output)); |
| return; |
| } |
| elsif ($grep_status == 1) { |
| notify($ERRORS{'OK'}, 0, "no files were found matching pattern '$search_pattern' in: $base_directory"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "found files matching pattern '$search_pattern' in $base_directory:\n" . join("\n", @$grep_output)); |
| } |
| |
| # Run sed on each matching file to replace string |
| my $sed_error_count = 0; |
| for my $matching_file (@$grep_output) { |
| $matching_file =~ s/\\+/\//g; |
| # Run grep to find files matching pattern |
| my $sed_command = "/bin/sed -i -e \"s/$search_pattern/$replace_string/\" \"$matching_file\""; |
| my ($sed_status, $sed_output) = run_ssh_command($computer_node_name, $management_node_keys, $sed_command, '', '', 0); |
| if (!defined($sed_status)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to run sed on file: $matching_file"); |
| $sed_error_count++; |
| } |
| elsif ("@$sed_output" =~ /No such file/i) { |
| notify($ERRORS{'WARNING'}, 0, "file was not found: $matching_file, sed output:\n" . join("\n", @$sed_output)); |
| $sed_error_count++; |
| } |
| elsif ("@$grep_output" =~ /(grep|sed):/i) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred running command '$sed_command':\n" . join("\n", @$sed_output)); |
| $sed_error_count++; |
| } |
| elsif ($sed_status != 0) { |
| notify($ERRORS{'WARNING'}, 0, "sed exit status is $sed_status, output:\n" . join("\n", @$sed_output)); |
| $sed_error_count++; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "replaced '$search_pattern' with '$replace_string' in $matching_file"); |
| |
| # sed replaces Windows newlines with \n |
| # There is a sed -b option which prevents this but it is not available on all versions of sed |
| $self->run_unix2dos($matching_file); |
| } |
| } |
| |
| # Return false if any errors occurred |
| if ($sed_error_count) { |
| return; |
| } |
| |
| return 1; |
| |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 copy_capture_configuration_files |
| |
| Parameters : $source_configuration_directory |
| Returns : |
| Description : Copies all required configuration files to the computer, |
| including scripts, utilities, drivers needed to capture an |
| image. |
| |
| =cut |
| |
| sub copy_capture_configuration_files { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL module object method"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Get an array containing the configuration directory paths on the management node |
| # This is made up of all the the $SOURCE_CONFIGURATION_DIRECTORY values for the OS class and it's parent classes |
| # The first array element is the value from the top-most class the OS object inherits from |
| my @source_configuration_directories = $self->get_source_configuration_directories(); |
| if (!@source_configuration_directories) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve source configuration directories"); |
| return; |
| } |
| |
| # Delete existing configuration directory if it exists |
| if (!$self->delete_capture_configuration_files()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to delete existing capture configuration files"); |
| return; |
| } |
| |
| # Attempt to create the configuration directory if it doesn't already exist |
| if (!$self->create_directory($NODE_CONFIGURATION_DIRECTORY)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create directory on $computer_node_name: $NODE_CONFIGURATION_DIRECTORY"); |
| return; |
| } |
| |
| # Copy configuration files |
| for my $source_configuration_directory (@source_configuration_directories) { |
| # Check if source configuration directory exists on this management node |
| unless (-d "$source_configuration_directory") { |
| notify($ERRORS{'OK'}, 0, "source directory does not exist on this management node: $source_configuration_directory"); |
| next; |
| } |
| |
| notify($ERRORS{'OK'}, 0, "copying image capture configuration files from $source_configuration_directory to $computer_node_name"); |
| if (run_scp_command("$source_configuration_directory/*", "$computer_node_name:$NODE_CONFIGURATION_DIRECTORY", $management_node_keys)) { |
| notify($ERRORS{'OK'}, 0, "copied $source_configuration_directory directory to $computer_node_name:$NODE_CONFIGURATION_DIRECTORY"); |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to set permissions on $computer_node_name:$NODE_CONFIGURATION_DIRECTORY"); |
| if (run_ssh_command($computer_node_name, $management_node_keys, "/usr/bin/chmod.exe -R 777 $NODE_CONFIGURATION_DIRECTORY")) { |
| notify($ERRORS{'OK'}, 0, "chmoded -R 777 $computer_node_name:$NODE_CONFIGURATION_DIRECTORY"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "could not chmod -R 777 $computer_node_name:$NODE_CONFIGURATION_DIRECTORY"); |
| return; |
| } |
| } ## end if (run_scp_command("$source_configuration_directory/*"... |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy $source_configuration_directory to $computer_node_name"); |
| return; |
| } |
| } |
| |
| # Delete any Subversion files which may have been copied |
| if (!$self->delete_files_by_pattern($NODE_CONFIGURATION_DIRECTORY, '.*\.svn.*')) { |
| notify($ERRORS{'WARNING'}, 0, "unable to delete Subversion files under: $NODE_CONFIGURATION_DIRECTORY"); |
| } |
| |
| # Find any files containing a 'WINDOWS_ROOT_PASSWORD' string and replace it with the root password |
| if ($self->search_and_replace_in_files($NODE_CONFIGURATION_DIRECTORY, 'WINDOWS_ROOT_PASSWORD', $WINDOWS_ROOT_PASSWORD)) { |
| notify($ERRORS{'DEBUG'}, 0, "set the Windows root password in configuration files"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the Windows root password in configuration files"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub copy_capture_configuration_files |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 clean_hard_drive |
| |
| Parameters : |
| Returns : |
| Description : Removed unnecessary files from the hard drive. This is done |
| before capturing an image. Examples of unnecessary files: |
| -temp files and temp directories |
| -cache files |
| -downloaded patch files |
| |
| =cut |
| |
| sub clean_hard_drive { |
| 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 $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; |
| |
| # Note: attempt to delete everything under C:\RECYCLER before running cleanmgr.exe |
| # The Recycle Bin occasionally becomes corrupted |
| # cleanmgr.exe will hang with an "OK/Cancel" box on the screen if this happens |
| my @patterns_to_delete = ( |
| '$SYSTEMDRIVE/RECYCLER,.*', |
| '$TEMP,.*', |
| '$TMP,.*', |
| '$SYSTEMDRIVE/cygwin/tmp,.*', |
| '$SYSTEMDRIVE/Temp,.*', |
| '$SYSTEMROOT/Temp,.*', |
| '$SYSTEMROOT/ie7updates,.*', |
| '$SYSTEMROOT/ServicePackFiles,.*', |
| '$SYSTEMROOT/SoftwareDistribution/Download,.*', |
| '$SYSTEMROOT/Minidump,.*', |
| '$ALLUSERSPROFILE/Application Data/Microsoft/Dr Watson,.*', |
| '$SYSTEMROOT,.*\\.tmp,1', |
| '$SYSTEMROOT,.*\\$hf_mig\\$.*,1', |
| '$SYSTEMROOT,.*\\$NtUninstall.*,1', |
| '$SYSTEMROOT,.*\\$NtServicePackUninstall.*,1', |
| '$SYSTEMROOT,.*\\$MSI.*Uninstall.*,1', |
| '$SYSTEMROOT,.*AFSCache,1', |
| '$SYSTEMROOT,.*afsd_init\\.log,1', |
| '$SYSTEMDRIVE/Documents and Settings,.*Recent\\/.*,10', |
| '$SYSTEMDRIVE/Documents and Settings,.*Cookies\\/.*,10', |
| '$SYSTEMDRIVE/Documents and Settings,.*Temp\\/.*,10', |
| '$SYSTEMDRIVE/Documents and Settings,.*Temporary Internet Files\\/Content.*\\/.*,10', |
| '$SYSTEMDRIVE,.*pagefile\\.sys,1', |
| '$SYSTEMDRIVE/cygwin/home/root,.*%USERPROFILE%,1', |
| "$system32_path/GroupPolicy/User/Scripts,.*VCL.*cmd" |
| ); |
| |
| # Attempt to stop the AFS service, needed to delete AFS files |
| if ($self->service_exists('TransarcAFSDaemon')) { |
| $self->stop_service('TransarcAFSDaemon'); |
| } |
| |
| # Loop through the directories to empty |
| # Don't care if they aren't emptied |
| for my $base_pattern (@patterns_to_delete) { |
| my ($base_directory, $pattern, $max_depth) = split(',', $base_pattern); |
| notify($ERRORS{'DEBUG'}, 0, "attempting to delete files under $base_directory matching pattern $pattern"); |
| $self->delete_files_by_pattern($base_directory, $pattern, $max_depth); |
| } |
| |
| # Add the cleanmgr.exe settings to the registry |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| ; This registry file contains the entries to turn on all cleanmgr options |
| ; The state flags below are set to 1, so use the command: 'CLEANMGR /sagerun:1' |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches] |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Active Setup Temp Folders] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Content Indexer Cleaner] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Downloaded Program Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Hibernation File] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Internet Cache Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Memory Dump Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Offline Pages Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Old ChkDsk Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Previous Installations] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Recycle Bin] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Setup Log Files] |
| "StateFlags0001"=dword:00000000 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\System error memory dump files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\System error minidump files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Temporary Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Temporary Setup Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Temporary Sync Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Thumbnail Cache] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Upgrade Discarded Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Windows Error Reporting Archive Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Windows Error Reporting Queue Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Windows Error Reporting System Archive Files] |
| "StateFlags0001"=dword:00000002 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\explorer\\VolumeCaches\\Windows Error Reporting System Queue Files] |
| "StateFlags0001"=dword:00000002 |
| |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'DEBUG'}, 0, "set registry settings to configure the disk cleanup utility"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set registry settings to configure the disk cleanup utility"); |
| } |
| |
| # Run cleanmgr.exe |
| # The cleanmgr.exe file may not be present - it is not installed by default on Windows Server 2008 and possibly others |
| my $cleanmgr_command = "/bin/cygstart.exe $system32_path/cleanmgr.exe /SAGERUN:01"; |
| my ($cleanmgr_exit_status, $cleanmgr_output) = run_ssh_command($computer_node_name, $management_node_keys, $cleanmgr_command); |
| if (!defined($cleanmgr_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to run cleanmgr.exe"); |
| return; |
| } |
| elsif (grep(/not found/i, @$cleanmgr_output)) { |
| notify($ERRORS{'OK'}, 0, "cleanmgr.exe is not present on $computer_node_name, this is usually because the Desktop Experience feature is not installed"); |
| } |
| else { |
| # Wait for cleanmgr.exe to finish |
| my $message = 'waiting for cleanmgr.exe to finish'; |
| my $total_wait_seconds = 120; |
| notify($ERRORS{'OK'}, 0, "started cleanmgr.exe, waiting up to $total_wait_seconds seconds for it to finish"); |
| |
| if ($self->code_loop_timeout(sub{!$self->is_process_running(@_)}, ['cleanmgr.exe'], $message, $total_wait_seconds, 5)) { |
| notify($ERRORS{'DEBUG'}, 0, "cleanmgr.exe has finished"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "cleanmgr.exe has not finished after waiting $total_wait_seconds seconds, the Recycle Bin may be corrupt"); |
| } |
| } |
| |
| return 1; |
| } ## end sub clean_hard_drive |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 is_process_running |
| |
| Parameters : $process_identifier |
| Returns : boolean |
| Description : Determines if a process is running identified by the argument. |
| The argument should be the name of an executable. Wildcards (*) |
| are allowed. |
| |
| =cut |
| |
| sub is_process_running { |
| 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, arguments:\n" . format_data(\@_)); |
| 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; |
| |
| my $process_identifier = shift; |
| if (!defined($process_identifier)) { |
| notify($ERRORS{'WARNING'}, 0, "process identifier argument was not supplied"); |
| return; |
| } |
| |
| my $command = "$system32_path/tasklist.exe /FI \"IMAGENAME eq $process_identifier\""; |
| my ($status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to determine if process is running: $process_identifier"); |
| return; |
| } |
| elsif (grep(/No tasks/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "process is NOT running: $process_identifier"); |
| return 0; |
| } |
| elsif (grep(/PID/, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "process is running: $process_identifier"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned from command to determine if process is running: '$command', output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 start_service |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub start_service { |
| 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 $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 $service_name = shift; |
| if (!$service_name) { |
| notify($ERRORS{'WARNING'}, 0, "service name was not passed as an argument"); |
| return; |
| } |
| |
| my $command = $system32_path . '/net.exe start "' . $service_name . '"'; |
| my ($status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command); |
| if (defined($status) && $status == 0) { |
| notify($ERRORS{'OK'}, 0, "started service: $service_name"); |
| } |
| elsif (defined($output) && grep(/already been started/i, @{$output})) { |
| notify($ERRORS{'OK'}, 0, "service has already been started: $service_name"); |
| } |
| elsif (defined($status)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to start service: $service_name, exit status: $status, output:\n@{$output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to to start service: $service_name"); |
| return 0; |
| } |
| |
| return 1; |
| } ## end sub start_service |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 stop_service |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub stop_service { |
| 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 $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 $service_name = shift; |
| if (!$service_name) { |
| notify($ERRORS{'WARNING'}, 0, "service name was not passed as an argument"); |
| return; |
| } |
| |
| my $command = $system32_path . '/net.exe stop "' . $service_name . '"'; |
| my ($status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command); |
| if (defined($status) && $status == 0) { |
| notify($ERRORS{'OK'}, 0, "stopped service: $service_name"); |
| } |
| elsif (defined($output) && grep(/is not started/i, @{$output})) { |
| notify($ERRORS{'OK'}, 0, "service is not started: $service_name"); |
| } |
| elsif (defined($output) && grep(/does not exist/i, @{$output})) { |
| notify($ERRORS{'WARNING'}, 0, "service was not stopped because it does not exist: $service_name"); |
| return; |
| } |
| elsif (defined($status)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to stop service: $service_name, exit status: $status, output:\n@{$output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to to stop service: $service_name"); |
| return 0; |
| } |
| |
| return 1; |
| } ## end sub stop_service |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 service_exists |
| |
| Parameters : $service_name |
| Returns : If service exists: 1 |
| If service does not exist: 0 |
| If error occurred: undefined |
| Description : Runs sc.exe query to determine if a service exists. |
| |
| =cut |
| |
| sub service_exists { |
| 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 $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 $service_name = shift; |
| if (!$service_name) { |
| notify($ERRORS{'WARNING'}, 0, "service name was not passed as an argument"); |
| return; |
| } |
| |
| my $command = $system32_path . '/sc.exe query "' . $service_name . '"'; |
| my ($status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command, '', '', 1); |
| if (defined($output) && grep(/service does not exist/i, @{$output})) { |
| notify($ERRORS{'DEBUG'}, 0, "service does not exist: $service_name"); |
| return 0; |
| } |
| elsif (defined($status) && $status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "service exists: $service_name"); |
| } |
| elsif (defined($status)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if service exists: $service_name, exit status: $status, output:\n@{$output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to run ssh command to determine if service exists"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_installed_applications |
| |
| Parameters : |
| Returns : |
| Description : Queries the registry for applications that are installed on the computer. |
| Subkeys under the following key contain this information: |
| HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall |
| |
| A reference to a hash is returned. The keys of this hash are the names of the subkeys under the Uninstall key. |
| Each subkey contains additional data formatted as follows: |
| my $installed_applications = $self->os->get_installed_applications(); |
| $installed_applications->{pdfFactory Pro}{DisplayName} = 'pdfFactory Pro' |
| $installed_applications->{pdfFactory Pro}{UninstallString} = 'C:\WINDOWS\System32\spool\DRIVERS\W32X86\3\fppinst2.exe /uninstall' |
| |
| =cut |
| |
| sub get_installed_applications { |
| my $self = shift; |
| if (!ref($self)) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| 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; |
| |
| # Get an optional regex filter string |
| my $regex_filter = shift; |
| if ($regex_filter) { |
| notify($ERRORS{'DEBUG'}, 0, "attempting to retrieve applications installed on $computer_node_name matching filter: $regex_filter"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "attempting to retrieve all applications installed on $computer_node_name"); |
| } |
| |
| # Attempt to query the registry for installed applications |
| my $reg_query_command = $system32_path . '/reg.exe QUERY "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" /s'; |
| my ($reg_query_exit_status, $reg_query_output) = run_ssh_command($computer_node_name, $management_node_keys, $reg_query_command, '', '', 1); |
| if (defined($reg_query_exit_status) && $reg_query_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "queried Uninstall registry keys"); |
| } |
| elsif (defined($reg_query_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to query Uninstall registry keys, exit status: $reg_query_exit_status, output:\n@{$reg_query_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to query Uninstall registry keys"); |
| return; |
| } |
| |
| # Make sure output was retrieved |
| if (!$reg_query_output || scalar @{$reg_query_output} == 0) { |
| notify($ERRORS{'WARNING'}, 0, "registry query did not product any output"); |
| return; |
| } |
| |
| #notify($ERRORS{'DEBUG'}, 0, "reg.exe query output: " . join("\n", @{$reg_query_output})); |
| |
| # Loop through the lines of output |
| my $product_key; |
| my %installed_products; |
| for my $query_output_line (@{$reg_query_output}) { |
| #notify($ERRORS{'DEBUG'}, 0, "reg.exe query output line: '" . string_to_ascii($query_output_line) . "'"); |
| |
| # Remove spaces from beginning and end of line |
| $query_output_line =~ s/(^\s+)|(\s+$)//g; |
| |
| # Skip lines which don't contain a word character and lines starting with ! like this one: |
| # ! REG.EXE VERSION 3.0 |
| if ($query_output_line =~ /^!/ || $query_output_line !~ /\w/) { |
| next; |
| } |
| |
| # Check if line starts with HKEY, as in: |
| # HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\ATI Display Driver |
| if ($query_output_line =~ /^HKEY.*\\(.*)\s*/) { |
| # Skip first line showing the base key that was searched for: |
| # HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall |
| next if ($1 eq 'Uninstall'); |
| |
| $product_key = $1; |
| #notify($ERRORS{'DEBUG'}, 0, "found product key: '" . string_to_ascii($product_key) . "'"); |
| next; |
| } |
| |
| # Line is a child key of one of the products, take apart the line, looks like this: |
| # <NO NAME> REG_SZ |
| # DisplayName REG_SZ F-Secure SSH Client |
| my ($info_key, $info_value) = ($query_output_line =~ /\s*([^\t]+)\s+\w+\s*([^\r\n]*)/); |
| |
| # Make sure the regex found the registry key name, if not, regex needs improvement |
| if (!$info_key) { |
| notify($ERRORS{'WARNING'}, 0, "regex didn't work correctly finding key name and value, line:\n" . string_to_ascii($query_output_line)); |
| next; |
| } |
| |
| # Make sure the product key was found by this point, it should have been |
| if (!$product_key) { |
| notify($ERRORS{'WARNING'}, 0, "product key was not determined by the time the following line was processed, line:\n$query_output_line\nreg.exe query output: @{$reg_query_output}"); |
| next; |
| } |
| |
| # Add the key and value to the hash |
| $installed_products{$product_key}{$info_key} = $info_value; |
| } ## end for my $query_output_line (@{$reg_query_output... |
| |
| # If filter was specified, remove keys not matching filter |
| if ($regex_filter) { |
| notify($ERRORS{'DEBUG'}, 0, "finding applications matching filter: $regex_filter"); |
| my %matching_products; |
| foreach my $product_key (sort keys %installed_products) { |
| #notify($ERRORS{'DEBUG'}, 0, "checking product key: $product_key"); |
| if (eval "\$product_key =~ $regex_filter") { |
| notify($ERRORS{'DEBUG'}, 0, "found matching product key:\n$product_key"); |
| $matching_products{$product_key} = $installed_products{$product_key}; |
| next; |
| } |
| |
| foreach my $info_key (sort keys %{$installed_products{$product_key}}) { |
| my $info_value = $installed_products{$product_key}{$info_key}; |
| #notify($ERRORS{'DEBUG'}, 0, "checking value of {$info_key}: $info_value"); |
| if (eval "\$info_value =~ $regex_filter") { |
| notify($ERRORS{'DEBUG'}, 0, "found matching value:\n{$product_key}{$info_key} = '$info_value'"); |
| $matching_products{$product_key} = $installed_products{$product_key}; |
| last; |
| } |
| else { |
| next; |
| } |
| } ## end foreach my $info_key (sort keys %{$installed_products... |
| } ## end foreach my $product_key (sort keys %installed_products) |
| %installed_products = %matching_products; |
| } ## end if ($regex_filter) |
| |
| if (%installed_products && $regex_filter) { |
| notify($ERRORS{'DEBUG'}, 0, "found the following installed applications matching filter:\n$regex_filter\n" . format_data(\%installed_products)); |
| return \%installed_products; |
| } |
| elsif (%installed_products && !$regex_filter) { |
| notify($ERRORS{'DEBUG'}, 0, "found the following installed applications:\n" . format_data(\%installed_products)); |
| return \%installed_products; |
| } |
| if (!%installed_products && $regex_filter) { |
| notify($ERRORS{'DEBUG'}, 0, "did not find any installed applications matching filter:\n$regex_filter"); |
| return 0; |
| } |
| elsif (!%installed_products && !$regex_filter) { |
| notify($ERRORS{'DEBUG'}, 0, "did not find any installed applications"); |
| return 0; |
| } |
| } ## end sub get_installed_applications |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_task_list |
| |
| Parameters : None, must be called as an object method ($self->os->get_task_list()) |
| Returns : If successful: Reference to an array containing the lines of output generated by tasklist.exe |
| If failed: false |
| Description : Runs tasklist.exe and returns its output. Tasklist.exe displays a list of applications and associated tasks running on the computer. |
| The following switches are used when tasklist.exe is executed: |
| /NH - specifies the column header should not be displayed in the output |
| /V - specifies that verbose information should be displayed |
| The output is formatted as follows (column header is not included): |
| Image Name PID Session Name Session# Mem Usage Status User Name CPU Time Window Title |
| System Idle Process 0 Console 0 16 K Running NT AUTHORITY\SYSTEM |
| |
| =cut |
| |
| sub get_task_list { |
| my $self = shift; |
| if (!ref($self)) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| 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 run tasklist.exe with /NH for no header |
| my $tasklist_command = $system32_path . '/tasklist.exe /NH /V'; |
| my ($tasklist_exit_status, $tasklist_output) = run_ssh_command($computer_node_name, $management_node_keys, $tasklist_command, '', '', 1); |
| if (defined($tasklist_exit_status) && $tasklist_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "ran tasklist.exe"); |
| } |
| elsif (defined($tasklist_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run tasklist.exe, exit status: $tasklist_exit_status, output:\n@{$tasklist_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to run tasklist.exe"); |
| return; |
| } |
| |
| return $tasklist_output; |
| } ## end sub get_task_list |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 apply_security_templates |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Runs secedit.exe to apply the security template files configured |
| for the OS. Windows security template files use the .inf |
| extension. |
| |
| Security templates are always copied from the management node |
| rather than using a copy stored locally on the computer. This |
| allows templates updated centrally to always be applied to the |
| computer. Template files residing locally on the computer are not |
| processed. |
| |
| The template files should reside in a directory named "Security" |
| under the OS source configuration directory. An example would be: |
| |
| /usr/local/vcl/tools/Windows_XP/Security/xp_security.inf |
| |
| This subroutine supports OS module inheritence meaning that if an |
| OS module inherits from another OS module, the security templates |
| of both will be applied. The order is from the highest parent |
| class down to any template files configured specifically for the |
| OS module which was instantiated. |
| |
| This allows any Windows OS module to inherit from another class |
| which has security templates defined and override any settings |
| from above. |
| |
| Multiple .inf security template files may be configured for each |
| OS. They will be applied in alphabetical order. |
| |
| Example: Inheritence is configured as follows, with the XP module |
| being the instantiated (lowest) class: |
| |
| VCL::Module |
| ^ |
| VCL::Module::OS |
| ^ |
| VCL::Module::OS::Windows |
| ^ |
| VCL::Module::OS::Windows::Version_5 |
| ^ |
| VCL::Module::OS::Windows::Version_5::XP |
| |
| The XP and Windows classes each have 2 security template files |
| configured in their respective Security directories: |
| |
| /usr/local/vcl/tools/Windows/Security/eventlog_512.inf |
| /usr/local/vcl/tools/Windows/Security/windows_security.inf |
| /usr/local/vcl/tools/Windows_XP/Security/xp_eventlog_4096.inf |
| /usr/local/vcl/tools/Windows_XP/Security/xp_security.inf |
| |
| The templates will be applied in the order shown above. The |
| Windows templates are applied first because it is a parent class |
| of XP. For each class being processed, the files are applied in |
| alphabetical order. |
| |
| Assume in the example above that the Windows module's |
| eventlog_512.inf file configures the event log to be a maximum of |
| 512 KB and that it is desirable under Windows XP to configure a |
| larger maximum event log size. In order to achieve this, |
| xp_eventlog_4096.inf was placed in XP's Security directory which |
| contains settings to set the maximum size to 4,096 KB. The |
| xp_eventlog_4096.inf file is applied after the eventlog_512.inf |
| file, thus overridding the setting configured in the |
| eventlog_512.inf file. The resultant maximum event log size will |
| be set to 4,096 KB. |
| |
| =cut |
| |
| sub apply_security_templates { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module:: module object method"); |
| 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; |
| |
| # Get an array containing the configuration directory paths on the management node |
| # This is made up of all the the $SOURCE_CONFIGURATION_DIRECTORY values for the OS class and it's parent classes |
| # The first array element is the value from the top-most class the OS object inherits from |
| my @source_configuration_directories = $self->get_source_configuration_directories(); |
| if (!@source_configuration_directories) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve source configuration directories"); |
| return; |
| } |
| |
| # Loop through the configuration directories for each OS class on the management node |
| # Find any .inf files residing under Security |
| my @inf_file_paths; |
| for my $source_configuration_directory (@source_configuration_directories) { |
| notify($ERRORS{'OK'}, 0, "checking if any security templates exist in: $source_configuration_directory/Security"); |
| |
| # Check each source configuration directory for .inf files under a Security subdirectory |
| my $find_command = "find $source_configuration_directory/Security -name \"*.inf\" 2>&1 | sort -f"; |
| my ($find_exit_status, $find_output) = run_command($find_command); |
| if (defined($find_output) && grep(/No such file/i, @$find_output)) { |
| notify($ERRORS{'DEBUG'}, 0, "path does not exist: $source_configuration_directory/Security"); |
| } |
| elsif (defined($find_exit_status) && $find_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "ran find, output:\n" . join("\n", @$find_output)); |
| push @inf_file_paths, @$find_output; |
| } |
| elsif (defined($find_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run find, exit status: $find_exit_status, output:\n@{$find_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run local command to run find"); |
| return; |
| } |
| } |
| |
| # Remove any newlines from the file paths in the array |
| chomp(@inf_file_paths); |
| notify($ERRORS{'DEBUG'}, 0, "security templates will be applied in this order:\n" . join("\n", @inf_file_paths)); |
| |
| # Make sure the Security directory exists before attempting to copy files or SCP will fail |
| if (!$self->create_directory("$NODE_CONFIGURATION_DIRECTORY/Security")) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create directory: $NODE_CONFIGURATION_DIRECTORY/Security"); |
| } |
| |
| # Loop through the .inf files and apply them to the node using secedit.exe |
| my $inf_count = 0; |
| my $error_occurred = 0; |
| for my $inf_file_path (@inf_file_paths) { |
| $inf_count++; |
| |
| # Get the name of the file |
| my ($inf_file_name) = $inf_file_path =~ /.*[\\\/](.*)/g; |
| my ($inf_file_root) = $inf_file_path =~ /.*[\\\/](.*).inf/gi; |
| |
| # Construct the target path, prepend a number to indicate the order the files were processed |
| my $inf_target_path = "$NODE_CONFIGURATION_DIRECTORY/Security/$inf_count\_$inf_file_name"; |
| |
| # Copy the file to the node and set the permissions to 644 |
| notify($ERRORS{'DEBUG'}, 0, "attempting to copy file to: $inf_target_path"); |
| if (run_scp_command($inf_file_path, "$computer_node_name:$inf_target_path", $management_node_keys)) { |
| notify($ERRORS{'DEBUG'}, 0, "copied file: $computer_node_name:$inf_target_path"); |
| |
| # Set permission on the copied file |
| if (!run_ssh_command($computer_node_name, $management_node_keys, "/usr/bin/chmod.exe -R 644 $inf_target_path", '', '', 0)) { |
| notify($ERRORS{'WARNING'}, 0, "could not set permissions on $inf_target_path"); |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy $inf_file_path to $inf_target_path"); |
| next; |
| } |
| |
| # Assemble the paths secedit needs |
| my $secedit_exe = $system32_path . '/secedit.exe'; |
| my $secedit_db = '$SYSTEMROOT/security/Database/' . "$inf_count\_$inf_file_root.sdb"; |
| my $secedit_log = '$SYSTEMROOT/security/Logs/' . "$inf_count\_$inf_file_root.log"; |
| |
| # Attempt to delete an existing log file |
| $self->delete_file($secedit_log); |
| |
| # The inf path must use backslashes or secedit.exe will fail |
| $inf_target_path =~ s/\//\\\\/g; |
| |
| # Run secedit.exe |
| # Note: secedit.exe returns exit status 3 if a warning occurs, this will appear in the log file: |
| # Task is completed. Warnings occurred for some attributes during this operation. It's ok to ignore. |
| my $secedit_command = "$secedit_exe /configure /cfg \"$inf_target_path\" /db $secedit_db /log $secedit_log /overwrite /quiet"; |
| my ($secedit_exit_status, $secedit_output) = run_ssh_command($computer_node_name, $management_node_keys, $secedit_command, '', '', 0); |
| if (defined($secedit_exit_status) && ($secedit_exit_status == 0 || $secedit_exit_status == 3)) { |
| notify($ERRORS{'OK'}, 0, "ran secedit.exe to apply $inf_file_name"); |
| } |
| elsif (defined($secedit_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run secedit.exe to apply $inf_target_path, exit status: $secedit_exit_status, output:\n" . join("\n", @$secedit_output)); |
| $error_occurred++; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to run secedit.exe to apply $inf_target_path"); |
| $error_occurred++; |
| } |
| } |
| |
| if ($error_occurred) { |
| return 0; |
| } |
| else { |
| return 1; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 kill_process |
| |
| Parameters : String containing task name pattern |
| Returns : If successful: true |
| If failed: false |
| Description : Runs taskkill.exe to kill processes with names matching a |
| pattern. Wildcards can be specified using *, but task name |
| patterns cannot begin with a *. |
| |
| Example pattern: notepad* |
| |
| =cut |
| |
| sub kill_process { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| # Get the task name pattern argument |
| my $task_pattern = shift; |
| unless ($task_pattern) { |
| notify($ERRORS{'WARNING'}, 0, "task name pattern argument was not specified"); |
| 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; |
| |
| # Typical output: |
| # Task was killed, exit status = 0: |
| # SUCCESS: The process with PID 3476 child of PID 5876 has been terminated. |
| |
| # No tasks match pattern, exit status = 0: |
| # INFO: No tasks running with the specified criteria. |
| |
| # Bad search filter, exit status = 1: |
| # ERROR: The search filter cannot be recognized. |
| |
| # Attempt to kill task |
| my $taskkill_command = $system32_path . "/taskkill.exe /F /T /FI \"IMAGENAME eq $task_pattern\""; |
| my ($taskkill_exit_status, $taskkill_output) = run_ssh_command($computer_node_name, $management_node_keys, $taskkill_command, '', '', '1'); |
| if (defined($taskkill_exit_status) && $taskkill_exit_status == 0 && (my @killed = grep(/SUCCESS/, @$taskkill_output))) { |
| notify($ERRORS{'OK'}, 0, scalar @killed . "processe(s) killed matching pattern: $task_pattern\n" . join("\n", @killed)); |
| } |
| elsif (defined($taskkill_exit_status) && $taskkill_exit_status == 0 && grep(/No tasks running/i, @{$taskkill_output})) { |
| notify($ERRORS{'DEBUG'}, 0, "process does not exist matching pattern: $task_pattern"); |
| } |
| elsif (defined($taskkill_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to kill process matching pattern: $task_pattern\n" . join("\n", @{$taskkill_output})); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to kill process matching pattern: $task_pattern"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_ie_configuration_page |
| |
| Parameters : None. |
| Returns : If successful: true |
| If failed: false |
| Description : Sets registry keys which prevent Internet Explorer's |
| configuration page from appearing the first time a user launches |
| it. This subroutine also enables the Internet Explorer Phishing |
| Filter and sets it to not display a balloon message. |
| |
| =cut |
| |
| sub disable_ie_configuration_page { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| [HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Internet Explorer\\Main] |
| "DisableFirstRunCustomize"=dword:00000001 |
| "RunOnceHasShown"=dword:00000001 |
| "RunOnceComplete"=dword:00000001 |
| |
| [HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Internet Explorer\\PhishingFilter] |
| "Enabled"=dword:00000002 |
| "ShownVerifyBalloon"=dword:00000001 |
| |
| [HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\Internet Explorer\\Main] |
| "DisableFirstRunCustomize"=dword:00000001 |
| |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "set the registry keys to disable IE runonce"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the registry key to disable IE runonce"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 enable_rdp_audio |
| |
| Parameters : None. |
| Returns : If successful: true |
| If failed: false |
| Description : Sets the registry keys to allow audio redirection via RDP |
| sessions. This is disabled by default under Windows Server 2008 |
| and possibly other versions of Windows. Also sets the Windows |
| Audio service to start automatically. |
| |
| =cut |
| |
| sub enable_rdp_audio { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp] |
| "fDisableCam"=dword:00000000 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Terminal Services] |
| "fDisableCam"=dword:00000000 |
| |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "set the registry keys to enable RDP audio"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the registry key to enable RDP audio"); |
| return 0; |
| } |
| |
| # Configure the Windows Audio service to start automatically |
| if ($self->set_service_startup_mode('AudioSrv', 'auto')) { |
| notify($ERRORS{'DEBUG'}, 0, "set the Windows Audio service startup mode to auto"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the Windows Audio service startup mode to auto"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_node_configuration_directory |
| |
| Parameters : None. |
| Returns : String containing filesystem path |
| Description : Retrieves the $NODE_CONFIGURATION_DIRECTORY variable value the |
| OS. This is the path on the computer's hard drive where image |
| configuration files and scripts are copied. |
| |
| =cut |
| |
| sub get_node_configuration_directory { |
| return $NODE_CONFIGURATION_DIRECTORY; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_computer_name |
| |
| Parameters : $computer_name (optional) |
| Returns : If successful: true |
| If failed: false |
| Description : Sets the registry keys to set the computer name. This subroutine |
| does not attempt to reboot the computer. |
| The computer name argument is optional. If not supplied, the |
| computer's short name stored in the database will be used, |
| followed by a hyphen and the image ID that is loaded. |
| |
| =cut |
| |
| sub set_computer_name { |
| 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; |
| } |
| |
| # Get the computer name |
| my $new_computer_name = shift; |
| if (!$new_computer_name) { |
| $new_computer_name = $self->data->get_computer_short_name(); |
| if (!$new_computer_name) { |
| notify($ERRORS{'WARNING'}, 0, "computer name argument was not supplied and could not be retrieved from the reservation data"); |
| return; |
| } |
| |
| # Append the image ID to the computer name |
| my $image_id = $self->data->get_image_id(); |
| $new_computer_name .= "-$image_id" if $image_id; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\ComputerName\\ComputerName] |
| "ComputerName"="$new_computer_name" |
| |
| [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters] |
| "Hostname"="$new_computer_name" |
| "NV Hostname"="$new_computer_name" |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'DEBUG'}, 0, "set registry keys to change the computer name of $computer_node_name to $new_computer_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set registry keys to change the computer name of $computer_node_name to $new_computer_name"); |
| return; |
| } |
| |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_security_center_notifications |
| |
| Parameters : None. |
| Returns : If successful: true |
| If failed: false |
| Description : Disables Windows Security Center notifications which are |
| displayed in the notification area (system tray). |
| |
| =cut |
| |
| sub disable_security_center_notifications { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<'EOF'; |
| Windows Registry Editor Version 5.00 |
| |
| [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Security Center] |
| "AntiSpywareDisableNotify"=dword:00000001 |
| "AntiVirusDisableNotify"=dword:00000001 |
| "FirewallDisableNotify"=dword:00000001 |
| "UacDisableNotify"=dword:00000001 |
| "UpdatesDisableNotify"=dword:00000001 |
| "FirstRunDisabled"=dword:00000001 |
| |
| [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Security Center\Svc] |
| "AntiVirusOverride"=dword:00000001 |
| "AntiSpywareOverride"=dword:00000001 |
| "FirewallOverride"=dword:00000001 |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "set the registry keys to disable security center notifications"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the registry key to disable security center notifications"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_automatic_updates |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Disables Windows Automatic Updates by configuring a local group |
| policy: |
| HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU\NoAutoUpdate=1 |
| |
| This must be done using a policy in order to prevent |
| Windows Security Center will display a warning icon in the |
| notification area. Windows Update can be disabled via the GUI |
| which configures the following key but a warning will be |
| presented to the user: |
| HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update |
| |
| =cut |
| |
| sub disable_automatic_updates { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<'EOF'; |
| Windows Registry Editor Version 5.00 |
| |
| [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU] |
| "NoAutoUpdate"=dword:00000001 |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "disabled automatic updates"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the registry key to disable automatic updates"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_windows_defender |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Disables Windows Defender by doing the following: |
| -Configures local group policy to disable Windows Defender |
| -Removes HKLM...Run registry key to start Windows Defender at logon |
| -Stops the Windows Defender service |
| -Disables the Windows Defender service |
| |
| =cut |
| |
| sub disable_windows_defender { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<'EOF'; |
| Windows Registry Editor Version 5.00 |
| |
| [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defender] |
| "DisableAntiSpyware"=dword:00000001 |
| |
| [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run] |
| "Windows Defender"=- |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'DEBUG'}, 0, "set the registry keys to disable Windows defender"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the registry key to disable Windows defender"); |
| return 0; |
| } |
| |
| # Check if WinDefend service exists |
| if ($self->service_exists('WinDefend')) { |
| # Stop the Windows Defender service |
| if ($self->stop_service('WinDefend')) { |
| notify($ERRORS{'DEBUG'}, 0, "stopped the Windows Defender service"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to stop the Windows Defender service"); |
| return 0; |
| } |
| |
| # Disable the Windows Defender service |
| if ($self->set_service_startup_mode('WinDefend', 'disabled')) { |
| notify($ERRORS{'DEBUG'}, 0, "disabled the Windows Defender service"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to disable the Windows Defender service"); |
| return 0; |
| } |
| } |
| |
| notify($ERRORS{'OK'}, 0, "disabled Windows Defender"); |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 registry_query_value |
| |
| Parameters : $key_name (required), $value_name (optional) |
| Returns : If successful: true |
| If failed: false |
| Description : Queries the registry. If a value name is specified as the 2nd |
| argument, the value is returned. If a value name is not |
| specified, the output from reg.exe /s is returned containing the |
| subkeys and values. |
| |
| =cut |
| |
| sub registry_query_value { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| 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; |
| |
| # Get and check the arguments |
| my $key_name = shift; |
| my $value_name = shift; |
| if (!$key_name) { |
| notify($ERRORS{'WARNING'}, 0, "registry key name argument was not specified"); |
| return; |
| } |
| |
| # Assemble the query command string |
| my $reg_query_command = $system32_path . "/reg.exe QUERY \"$key_name\""; |
| |
| # Check if the value name argument was specified |
| my $query_mode; |
| if ($value_name && $value_name eq '(Default)') { |
| # Value name argument is (Default), query default value using /ve switch |
| $reg_query_command .= " /ve"; |
| $query_mode = 'default'; |
| } |
| elsif ($value_name) { |
| # Value name argument was specified, query it using /v switch |
| $reg_query_command .= " /v \"$value_name\""; |
| $query_mode = 'value'; |
| } |
| else { |
| # Value name argument was not specified, query all subkeys and values |
| $reg_query_command .= " /s"; |
| $query_mode = 'subkeys'; |
| } |
| |
| # Attempt to query the registry key |
| my ($reg_query_exit_status, $reg_query_output) = run_ssh_command($computer_node_name, $management_node_keys, $reg_query_command, '', '', 1); |
| if (defined($reg_query_output) && grep(/unable to find the specified registry/i, @$reg_query_output)) { |
| notify($ERRORS{'OK'}, 0, "registry key or value does not exist"); |
| return; |
| } |
| if (defined($reg_query_exit_status) && $reg_query_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "queried registry key, output:\n" . join("\n", @{$reg_query_output})); |
| } |
| elsif (defined($reg_query_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to query registry key, exit status: $reg_query_exit_status, output:\n@{$reg_query_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to query registry key"); |
| return; |
| } |
| |
| # Check to see if the output appears normal |
| if (@{$reg_query_output}[0] !~ /reg\.exe version/i) { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output, 1st line doesn't contain REG.EXE VERSION:\n" . join("\n", @{$reg_query_output})); |
| } |
| |
| # Check what was asked for, if subkeys, return entire query output string joined with newlines |
| if ($query_mode eq 'subkeys') { |
| return join("\n", @{$reg_query_output}); |
| } |
| |
| # Find the array element containing the line with the value |
| my ($value_line) = grep(/($value_name|no name)/i, @{$reg_query_output}); |
| notify($ERRORS{'DEBUG'}, 0, "value output line: $value_line"); |
| |
| # Split the line up and return the value |
| my ($retrieved_key_name, $type, $retrieved_value); |
| if ($query_mode eq 'value') { |
| ($retrieved_key_name, $type, $retrieved_value) = $value_line =~ /\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)/; |
| } |
| else { |
| ($retrieved_key_name, $type, $retrieved_value) = $value_line =~ /\s*(<NO NAME>)\s+([^\s]+)\s+([^\s]+)/; |
| } |
| |
| return $retrieved_value; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_static_public_address |
| |
| Parameters : |
| Returns : If successful: true |
| If failed: false |
| Description : Sets a static IP address for the public interface. The IP address |
| stored in the database for the computer is used. The subnet |
| mask, default gateway, and DNS servers configured for the |
| management node are used. |
| |
| A persistent route is added to the routing table to the public |
| default gateway. |
| |
| =cut |
| |
| sub set_static_public_address { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| 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; |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| # Make sure public IP configuration is static |
| my $ip_configuration = $self->data->get_management_node_public_ip_configuration(); |
| if ($ip_configuration !~ /static/i) { |
| notify($ERRORS{'WARNING'}, 0, "static public address can only be set if IP configuration is static, current value: $ip_configuration"); |
| return; |
| } |
| |
| # Get the IP configuration |
| my $interface_name = $self->get_public_interface_name() || '<undefined>'; |
| my $ip_address = $self->data->get_computer_ip_address() || '<undefined>'; |
| my $subnet_mask = $self->data->get_management_node_public_subnet_mask() || '<undefined>'; |
| my $default_gateway = $self->data->get_management_node_public_default_gateway() || '<undefined>'; |
| my @dns_servers = $self->data->get_management_node_public_dns_servers(); |
| |
| # Assemble a string containing the static IP configuration |
| my $configuration_info_string = <<EOF; |
| public interface name: $interface_name |
| public IP address: $ip_address |
| public subnet mask: $subnet_mask |
| public default gateway: $default_gateway |
| public DNS server(s): @dns_servers |
| EOF |
| |
| # Make sure required info was retrieved |
| if ("$interface_name $ip_address $subnet_mask $default_gateway" =~ /undefined/) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve required network configuration for $computer_name:\n$configuration_info_string"); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "attempting to set static public IP address on $computer_name:\n$configuration_info_string"); |
| } |
| |
| # Set the static public IP address |
| my $address_command = "$system32_path/netsh.exe interface ip set address name=\"$interface_name\" source=static addr=$ip_address mask=$subnet_mask gateway=$default_gateway gwmetric=0"; |
| |
| # Set number of attempts to try netsh.exe commands |
| my $max_attempts = 3; |
| my $address_attempts = 0; |
| while ($address_attempts < $max_attempts) { |
| $address_attempts++; |
| my ($address_exit_status, $address_output) = run_ssh_command($computer_node_name, $management_node_keys, $address_command); |
| if (defined($address_exit_status) && $address_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "set static public IP address to $ip_address"); |
| last; |
| } |
| elsif (defined($address_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "attempt $address_attempts/$max_attempts: failed to set static public IP address to $ip_address, exit status: $address_exit_status, output:\n@{$address_output}"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "attempt $address_attempts/$max_attempts: failed to run ssh command to set static public IP address to $ip_address"); |
| } |
| |
| # Check if max attempts has been reached. |
| if ($address_attempts >= $max_attempts) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set static public IP address after making $address_attempts attempts"); |
| return 0; |
| } |
| |
| sleep 2; |
| } |
| |
| my $primary_dns_server_address = shift @dns_servers; |
| notify($ERRORS{'DEBUG'}, 0, "primary DNS server address: $primary_dns_server_address\nalternate DNS server address(s):\n" . (join("\n", @dns_servers) || '<none>')); |
| |
| # Set the static DNS server address |
| my $dns_command = "$system32_path/netsh.exe interface ip set dns name=\"$interface_name\" source=static addr=$primary_dns_server_address register=none"; |
| my ($dns_exit_status, $dns_output) = run_ssh_command($computer_node_name, $management_node_keys, $dns_command); |
| if (!defined($dns_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to set static primary DNS server address to $primary_dns_server_address"); |
| return; |
| } |
| elsif ($dns_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set static primary DNS server address to $primary_dns_server_address, exit status: $dns_exit_status, output:\n@{$dns_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "set static primary DNS server address to $primary_dns_server_address"); |
| } |
| |
| |
| # We are only going to set up alternate dns server |
| for my $alternate_dns_server_address (@dns_servers) { |
| my $alternate_dns_command = "$system32_path/netsh.exe interface ip add dns name=\"$interface_name\" addr=$alternate_dns_server_address"; |
| my ($alternate_dns_exit_status, $alternate_dns_output) = run_ssh_command($computer_node_name, $management_node_keys, $dns_command); |
| if (!defined($alternate_dns_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to set static alternate DNS server address to $alternate_dns_server_address"); |
| } |
| elsif ($alternate_dns_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set static alternate DNS server address to $alternate_dns_server_address, exit status: $alternate_dns_exit_status, output:\n" . join("\n", @$alternate_dns_output)); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "set static alternate DNS server address to $alternate_dns_server_address"); |
| } |
| } |
| |
| # Add persistent static public default route |
| if (!$self->set_public_default_route()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to add persistent static public default route"); |
| return; |
| } |
| |
| notify($ERRORS{'OK'}, 0, "configured static address for public interface '$interface_name'"); |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_default_routes |
| |
| Parameters : |
| Returns : If successful: true |
| If failed: false |
| Description : Deletes all default (0.0.0.0) routes. |
| |
| =cut |
| |
| sub delete_default_routes { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Delete all default routes |
| my $route_delete_command = "route delete 0.0.0.0"; |
| my ($route_delete_exit_status, $route_delete_output) = run_ssh_command($computer_node_name, $management_node_keys, $route_delete_command); |
| if (defined($route_delete_exit_status) && $route_delete_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "deleted all default routes"); |
| } |
| elsif (defined($route_delete_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete all default routes, exit status: $route_delete_exit_status, output:\n@{$route_delete_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to delete all default routes"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_public_default_route |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Adds a persistent route to the default gateway for the public |
| network. |
| |
| =cut |
| |
| sub set_public_default_route { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| # Check the management node's DHCP IP configuration mode |
| # Get the default gateway address |
| my $default_gateway; |
| my $ip_configuration = $self->data->get_management_node_public_ip_configuration(); |
| if ($ip_configuration && $ip_configuration =~ /static/i) { |
| # Static addresses used, get default gateway address configured for management node |
| $default_gateway = $self->data->get_management_node_public_default_gateway(); |
| } |
| else { |
| # Dynamic addresses used, get default gateway address assigned to computer |
| $default_gateway = $self->get_public_default_gateway(); |
| if (!$default_gateway) { |
| $default_gateway = $self->data->get_management_node_public_default_gateway(); |
| } |
| } |
| |
| # Make sure default gateway was retrieved |
| if (!$default_gateway) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve default gateway address"); |
| return; |
| } |
| |
| # Delete all default routes before adding |
| # Do this only after successfully retrieving default gateway address |
| if (!$self->delete_default_routes()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to delete existing default routes"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Add a persistent route to the public default gateway |
| my $route_add_command = "route -p ADD 0.0.0.0 MASK 0.0.0.0 $default_gateway METRIC 1"; |
| my ($route_add_exit_status, $route_add_output) = run_ssh_command($computer_node_name, $management_node_keys, $route_add_command); |
| if (defined($route_add_exit_status) && $route_add_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "added persistent route to public default gateway: $default_gateway"); |
| } |
| elsif (defined($route_add_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to add persistent route to public default gateway: $default_gateway, exit status: $route_add_exit_status, output:\n@{$route_add_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to add persistent route to public default gateway: $default_gateway"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_volume_list |
| |
| Parameters : None |
| Returns : If successful: array containing volume drive letters |
| If failed: false |
| Description : |
| |
| =cut |
| |
| sub get_volume_list { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Echo the diskpart script to a temp file on the node |
| my $for_command = 'for i in `ls /cygdrive 2>/dev/null`; do echo $i; done;'; |
| my ($for_exit_status, $for_output) = run_ssh_command($computer_node_name, $management_node_keys, $for_command, '', '', 1); |
| if (defined($for_exit_status) && $for_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "retrieved drive letter list under /cygdrive:\n" . join("\n", @$for_output)); |
| } |
| elsif ($for_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve drive letter list under /cygdrive, exit status: $for_exit_status, output:\n@{$for_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to retrieve drive letter list under /cygdrive"); |
| return; |
| } |
| |
| my @drive_letters; |
| for my $for_output_line (@$for_output) { |
| if ($for_output_line =~ /^[a-z]$/) { |
| push @drive_letters, $for_output_line; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output from for command: $for_output_line"); |
| } |
| } |
| |
| return @drive_letters; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 configure_time_synchronization |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Configures the Windows Time service and synchronizes the time. |
| |
| =cut |
| |
| sub configure_time_synchronization { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| 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; |
| |
| my $time_source = "time.nist.gov time-a.nist.gov time-b.nist.gov time.windows.com"; |
| |
| # Assemble the time command |
| my $time_command; |
| |
| # Kill d4.exe if it's running, this will prevent Windows built-in time synchronization from working |
| $time_command .= "$system32_path/taskkill.exe /IM d4.exe /F 2>/dev/null ; "; |
| |
| # Register the w32time service |
| $time_command .= "$system32_path/w32tm.exe /register ; "; |
| |
| # Start the service and configure it |
| $time_command .= "$system32_path/net.exe start w32time 2>/dev/null ; "; |
| $time_command .= "$system32_path/w32tm.exe /config /manualpeerlist:\"$time_source\" /syncfromflags:manual /update ; "; |
| $time_command .= "$system32_path/net.exe stop w32time && $system32_path/net.exe start w32time ; "; |
| |
| # Synchronize the time |
| $time_command .= "$system32_path/w32tm.exe /resync /nowait"; |
| |
| # Run the assembled command |
| my ($time_exit_status, $time_output) = run_ssh_command($computer_node_name, $management_node_keys, $time_command); |
| if (defined($time_output) && @$time_output[-1] =~ /The command completed successfully/i) { |
| notify($ERRORS{'DEBUG'}, 0, "configured and synchronized Windows time"); |
| } |
| elsif (defined($time_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure configure and synchronize Windows time, exit status: $time_exit_status, output:\n@{$time_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run ssh command to configure and synchronize Windows time"); |
| return; |
| } |
| |
| # Set the w32time service startup mode to auto |
| if ($self->set_service_startup_mode('w32time', 'auto')) { |
| notify($ERRORS{'DEBUG'}, 0, "set w32time service startup mode to auto"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set w32time service startup mode to auto"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 is_64_bit |
| |
| Parameters : None |
| Returns : If 64-bit: true |
| If 32-bit: false |
| Description : Determines if Windows OS is 64 or 32-bit. |
| |
| =cut |
| |
| sub is_64_bit { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| # Check if architecture has previously been determined |
| if (defined($self->{OS_ARCHITECTURE}) && $self->{OS_ARCHITECTURE} eq '64') { |
| notify($ERRORS{'DEBUG'}, 0, '64-bit Windows OS previously detected'); |
| return 1; |
| } |
| elsif (defined($self->{OS_ARCHITECTURE}) && $self->{OS_ARCHITECTURE} eq '32') { |
| notify($ERRORS{'DEBUG'}, 0, '32-bit Windows OS previously detected'); |
| return 0; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_key = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'; |
| my $registry_value = 'PROCESSOR_IDENTIFIER'; |
| |
| # Run reg.exe QUERY |
| my $query_registry_command .= "reg.exe QUERY \"$registry_key\" /v \"$registry_value\""; |
| my ($query_registry_exit_status, $query_registry_output) = run_ssh_command($computer_node_name, $management_node_keys, $query_registry_command, '', '', 0); |
| |
| if (!defined($query_registry_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to query registry key: $registry_key, value: $registry_value"); |
| return; |
| } |
| |
| my ($output_line) = grep(/^\s*$registry_value/i, @$query_registry_output); |
| if (!$output_line) { |
| notify($ERRORS{'WARNING'}, 0, "unable to find registry value line in reg.exe output:\n" . join("\n", @$query_registry_output)); |
| return; |
| } |
| |
| my ($registry_data) = $output_line =~ /\s*$registry_value\s+[\w_]+\s+(.*)/; |
| |
| if ($registry_data && $registry_data =~ /64/) { |
| $self->{OS_ARCHITECTURE} = 64; |
| notify($ERRORS{'DEBUG'}, 0, "64-bit Windows OS detected, PROCESSOR_IDENTIFIER: $registry_data"); |
| return 1; |
| } |
| elsif ($registry_value) { |
| $self->{OS_ARCHITECTURE} = 32; |
| notify($ERRORS{'DEBUG'}, 0, "32-bit Windows OS detected, PROCESSOR_IDENTIFIER: $registry_data"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if OS is 32 or 64-bit, failed to query PROCESSOR_IDENTIFIER registry key, reg.exe output:\n" . join("\n", @$query_registry_output)); |
| return; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_system32 |
| |
| Parameters : None |
| Returns : If 64-bit: true |
| If 32-bit: false |
| Description : |
| |
| =cut |
| |
| sub get_system32_path { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| # Check if architecture has previously been determined |
| return $self->{SYSTEM32_PATH} if $self->{SYSTEM32_PATH}; |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| # Make sure SSH is responding |
| if (!$self->is_ssh_responding()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine System32 path to use for $computer_name, computer is not responding to SSH"); |
| return; |
| } |
| |
| if ($self->is_64_bit()) { |
| $self->{SYSTEM32_PATH} = 'C:/Windows/Sysnative'; |
| notify($ERRORS{'DEBUG'}, 0, "64-bit Windows OS installed on $computer_name, using $self->{SYSTEM32_PATH}"); |
| } |
| else { |
| $self->{SYSTEM32_PATH} = 'C:/Windows/System32'; |
| notify($ERRORS{'DEBUG'}, 0, "32-bit Windows OS installed on $computer_name, using $self->{SYSTEM32_PATH}"); |
| } |
| |
| return $self->{SYSTEM32_PATH}; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_product_name |
| |
| Parameters : None |
| Returns : If successful: string containing Windows product name |
| If failed: false |
| Description : Retrieves the Windows product name from the registry. This is |
| stored at: |
| HKLM\Software\Microsoft\Windows NT\CurrentVersion\ProductName |
| |
| The product name stored in the registry is used in the |
| winProductKey table to match a product key up with a product. It |
| must match exactly. Known strings for some versions of Windows: |
| "Microsoft Windows XP" |
| "Microsoft Windows Server 2003" |
| "Windows Server (R) 2008 Datacenter" |
| "Windows Vista (TM) Enterprise" |
| |
| =cut |
| |
| sub get_product_name { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| # Check if product name has previously been retrieved from registry |
| if ($self->{PRODUCT_NAME}) { |
| notify($ERRORS{'DEBUG'}, 0, "Windows product name previously retrieved: $self->{PRODUCT_NAME}"); |
| return $self->{PRODUCT_NAME}; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Get the Windows product name from the registry |
| my $product_name = $self->reg_query('HKLM/Software/Microsoft/Windows NT/CurrentVersion', 'ProductName'); |
| if ($product_name) { |
| notify($ERRORS{'DEBUG'}, 0, "retrieved Windows product name: $product_name"); |
| $self->{PRODUCT_NAME} = $product_name; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve Windows product name from registry"); |
| return; |
| } |
| |
| return $self->{PRODUCT_NAME}; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 format_path_unix |
| |
| Parameters : path |
| Returns : If successful: path formatted for Unix |
| If failed: false |
| Description : |
| |
| =cut |
| |
| sub format_path_unix { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| # Get the path argument |
| my $path = shift; |
| if (!$path) { |
| notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); |
| return; |
| } |
| |
| # Replace all forward slashes and backslashes with a single forward slash |
| $path =~ s/[\/\\]+/\//g; |
| |
| # Escape all spaces |
| $path =~ s/ /\\ /g; |
| |
| # Change %VARIABLE% to $VARIABLE |
| $path =~ s/\%(.+)\%/\$$1/g; |
| |
| #notify($ERRORS{'DEBUG'}, 0, "formatted path for Unix: $path"); |
| return $path; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 format_path_dos |
| |
| Parameters : path |
| Returns : If successful: path formatted for DOS |
| If failed: false |
| Description : |
| |
| =cut |
| |
| sub format_path_dos { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| # Get the path argument |
| my $path = shift; |
| if (!$path) { |
| notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); |
| return; |
| } |
| |
| # Replace all forward slashes with 2 backslashes |
| $path =~ s/[\/\\]/\\\\/g; |
| |
| # Change $VARIABLE to %VARIABLE% |
| $path =~ s/\$([^\\]+)/\%$1\%/g; |
| |
| #notify($ERRORS{'DEBUG'}, 0, "formatted path for DOS: $path"); |
| return $path; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_system_restore |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Sets registry key to disable Windows System Restore. Disabling |
| System Restore helps reduce the image size. |
| |
| =cut |
| |
| sub disable_system_restore { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $registry_string .= <<"EOF"; |
| Windows Registry Editor Version 5.00 |
| |
| [HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\SystemRestore] |
| "DisableConfig"=dword:00000001 |
| "DisableSR"=dword:00000001 |
| EOF |
| |
| # Import the string into the registry |
| if ($self->import_registry_string($registry_string)) { |
| notify($ERRORS{'OK'}, 0, "disabled system restore"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to disable system restore"); |
| return 0; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 user_logged_in |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub user_logged_in { |
| 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 $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 get the username from the arguments |
| # If no argument was supplied, use the user specified in the DataStructure |
| my $username = shift; |
| |
| # Remove spaces from beginning and end of username argument |
| # Fixes problem if string containing only spaces is passed |
| $username =~ s/(^\s+|\s+$)//g if $username; |
| |
| # Check if username argument was passed |
| if (!$username) { |
| $username = $self->data->get_user_login_id(); |
| } |
| notify($ERRORS{'DEBUG'}, 0, "checking if $username is logged in to $computer_node_name"); |
| |
| # Run qwinsta.exe to display terminal session information |
| my ($exit_status, $output) = run_ssh_command($computer_node_name, $management_node_keys, "$system32_path/qwinsta.exe"); |
| if ($exit_status > 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe on $computer_node_name, exit status: $exit_status, output:\n@{$output}"); |
| return; |
| } |
| elsif (!defined($exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe SSH command on $computer_node_name"); |
| return; |
| } |
| |
| # Find lines in qwinsta.exe output indicating a logged in user, lines may look like this: |
| # ' rdp-tcp#2 root 2 Active rdpwd' |
| # '>console root 0 Active wdcon' |
| my @user_connection_lines = grep(/[\s>]+(\S+)\s+($username)\s+(\d+)\s+(Active)/, @{$output}); |
| if (@user_connection_lines) { |
| notify($ERRORS{'OK'}, 0, "$username appears to be logged in on $computer_node_name:\n" . join("\n", @user_connection_lines)); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "$username does NOT appear to be logged in on $computer_node_name"); |
| return 0; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 wait_for_logoff |
| |
| Parameters : Username (optional), maximum number of minutes to wait (optional) |
| Returns : True if user is not logged in |
| False if user is still logged in after waiting |
| Description : Waits the specified amount of time for the user to log off. The |
| default username is the reservation user and the default time to |
| wait is 2 minutes. |
| |
| =cut |
| |
| sub wait_for_logoff { |
| 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(); |
| |
| # Attempt to get the username from the arguments |
| # If no argument was supplied, use the user specified in the DataStructure |
| my $username = shift; |
| |
| # Remove spaces from beginning and end of username argument |
| # Fixes problem if string containing only spaces is passed |
| $username =~ s/(^\s+|\s+$)//g if $username; |
| |
| # Check if username argument was passed |
| if (!$username) { |
| $username = $self->data->get_user_login_id(); |
| } |
| |
| # Attempt to get the total number of minutes to wait from the arguments |
| my $total_wait_minutes = shift; |
| if (!defined($total_wait_minutes) || $total_wait_minutes !~ /^\d+$/) { |
| $total_wait_minutes = 2; |
| } |
| |
| # Looping configuration variables |
| # Seconds to wait in between loop attempts |
| my $attempt_delay = 5; |
| # Total loop attempts made |
| # Add 1 to the number of attempts because if you're waiting for x intervals, you check x+1 times including at 0 |
| my $attempts = ($total_wait_minutes * 12) + 1; |
| |
| notify($ERRORS{'DEBUG'}, 0, "waiting for $username to logoff, maximum of $total_wait_minutes minutes"); |
| |
| # Loop until computer is user is not logged in |
| for (my $attempt = 1; $attempt <= $attempts; $attempt++) { |
| if ($attempt > 1) { |
| notify($ERRORS{'OK'}, 0, "attempt " . ($attempt - 1) . "/" . ($attempts - 1) . ": $username is logged in, sleeping for $attempt_delay seconds"); |
| sleep $attempt_delay; |
| } |
| |
| if (!$self->user_logged_in($username)) { |
| notify($ERRORS{'OK'}, 0, "$username is NOT logged in to $computer_node_name, returning 1"); |
| return 1; |
| } |
| } |
| |
| # Calculate how long this waited |
| my $total_wait = ($attempts * $attempt_delay); |
| notify($ERRORS{'WARNING'}, 0, "$username is still logged in to $computer_node_name after waiting for $total_wait seconds"); |
| return 0; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_product_key |
| |
| Parameters : $affiliation_identifier (optional), $product_name (optional) |
| Returns : If successful: string containing product key |
| If failed: false |
| Description : Retrieves the Windows product key from the database. This is |
| stored in the winProductKey table. |
| |
| Optional affiliation identifier and product name arguments may be |
| passed. Either both arguments must be passed or none. The |
| affiliation identifier may either be an affiliation ID or name. |
| If passed, the only data returned will be the data matching that |
| specific identifier. Global affiliation data will not be |
| returned. |
| |
| If the affiliation identifier argument is not passed, the |
| affiliation is determined by the affiliation of the owner of the |
| image for the reservation. If a product key has not been |
| configured for that specific affiliation, the product key |
| configured for the Global affiliation is returned. |
| |
| =cut |
| |
| sub get_product_key { |
| 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; |
| } |
| |
| # Remember if this sub was called with arguments |
| # Used to determine whether or not Global activation data will be checked |
| # If affiliation ID argument is specified, assume caller only wants the data for that affiliation and not the Global data |
| my $include_global; |
| if (scalar(@_) == 2) { |
| $include_global = 0; |
| notify($ERRORS{'DEBUG'}, 0, "subroutine was called with arguments, global affiliation data will be ignored"); |
| } |
| elsif (scalar(@_) == 0) { |
| $include_global = 1; |
| notify($ERRORS{'DEBUG'}, 0, "subroutine was NOT called with arguments, global affiliation data will be included"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "subroutine argument count = " . scalar(@_) . ", it must only be called with 0 or 2 arguments"); |
| return; |
| } |
| |
| # Get the affiliation identifer, may be ID or name |
| my $affiliation_identifier = shift; |
| if (!defined($affiliation_identifier)) { |
| $affiliation_identifier = $self->data->get_image_affiliation_id(); |
| } |
| if (!defined($affiliation_identifier)) { |
| notify($ERRORS{'WARNING'}, 0, "affiliation identifier argument was not passed and could not be determined from image"); |
| return; |
| } |
| |
| # Get the product name from the registry on the computer |
| my $product_name = shift || $self->get_product_name(); |
| if (!$product_name) { |
| notify($ERRORS{'WARNING'}, 0, "product name argument was not passed and could not be determined from computer"); |
| return; |
| } |
| |
| # Normalize the product name string from the registry |
| # Remove Microsoft from the beginning - some products have this and some don't |
| $product_name =~ s/Microsoft//ig; |
| # Remove anything in parenthesis such as (R) or (TM) |
| $product_name =~ s/\(.*\)//ig; |
| # Replace spaces with % |
| $product_name =~ s/\s/%/ig; |
| # Add % to the beginning and end |
| $product_name = "%$product_name%"; |
| # Replace multiple % characters with a single % |
| $product_name =~ s/%+/%/ig; |
| |
| # Create the affiliation-specific select statement |
| # Check if the affiliation identifier is a number or word |
| # If a number, use affiliation.id directly |
| # If a word, reference affiliation.name |
| my $affiliation_select_statement; |
| if ($affiliation_identifier =~ /^\d+$/) { |
| notify($ERRORS{'DEBUG'}, 0, "affiliation identifier is a number, retrieving winProductKey.affiliationid=$affiliation_identifier"); |
| $affiliation_select_statement = <<EOF; |
| SELECT |
| winProductKey.* |
| FROM |
| winProductKey |
| WHERE |
| winProductKey.productname LIKE '$product_name' |
| AND winProductKey.affiliationid = $affiliation_identifier |
| EOF |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "affiliation identifier is NOT a number, retrieving affiliation.name=$affiliation_identifier"); |
| $affiliation_select_statement = <<EOF; |
| SELECT |
| winProductKey.* |
| FROM |
| winProductKey, |
| affiliation |
| WHERE |
| winProductKey.productname LIKE '$product_name' |
| AND winProductKey.affiliationid = affiliation.id |
| AND affiliation.name LIKE '$affiliation_identifier' |
| EOF |
| } |
| |
| # Create the select statement |
| my $global_select_statement = <<EOF; |
| SELECT |
| winProductKey.* |
| FROM |
| winProductKey, |
| affiliation |
| WHERE |
| winProductKey.productname LIKE '$product_name' |
| AND winProductKey.affiliationid = affiliation.id |
| AND affiliation.name LIKE 'Global' |
| EOF |
| |
| # Call the database select subroutine |
| my @affiliation_rows = database_select($affiliation_select_statement); |
| |
| # Get the rows for the Global affiliation if this subroutine wasn't called with arguments |
| my @global_rows = (); |
| if ($include_global) { |
| @global_rows = database_select($global_select_statement); |
| } |
| |
| # Create an array containing the combined rows |
| my @combined_rows = (@affiliation_rows, @global_rows); |
| |
| # Check to make sure rows were returned |
| if (!@combined_rows) { |
| notify($ERRORS{'WARNING'}, 0, "0 rows were retrieved from winProductKey table for affiliation=$affiliation_identifier, product=$product_name"); |
| return; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "retrieved rows from winProductKey table for affiliation=$affiliation_identifier, product=$product_name:\n" . format_data(\@combined_rows)); |
| |
| my $product_key = $combined_rows[0]->{productkey}; |
| notify($ERRORS{'DEBUG'}, 0, "returning product key: $product_key"); |
| |
| return $product_key; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_product_key_info |
| |
| Parameters : none |
| Returns : hash reference |
| Description : Returns the contents of the winProductKey table as a hash |
| reference. The hash keys are the affiliation IDs. |
| |
| =cut |
| |
| sub get_product_key_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; |
| } |
| |
| # Create the select statement |
| my $select_statement = <<EOF; |
| SELECT |
| * |
| FROM |
| winProductKey |
| EOF |
| |
| # Call the database select subroutine |
| my @selected_rows = database_select($select_statement); |
| |
| # Transform the array of database rows into a hash |
| my %product_key_info; |
| map { $product_key_info{$_->{affiliationid}}{$_->{productname}} = $_->{productkey} } @selected_rows; |
| |
| notify($ERRORS{'DEBUG'}, 0, "retrieved product key info:\n" . format_data(\%product_key_info)); |
| return \%product_key_info; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_kms_server_info |
| |
| Parameters : none |
| Returns : hash reference |
| Description : Returns the contents of the winKMS table as a hash |
| reference. The hash keys are the affiliation IDs. |
| |
| =cut |
| |
| sub get_kms_server_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; |
| } |
| |
| # Create the select statement |
| my $select_statement = <<EOF; |
| SELECT |
| * |
| FROM |
| winKMS |
| EOF |
| |
| # Call the database select subroutine |
| my @selected_rows = database_select($select_statement); |
| |
| # Transform the array of database rows into a hash |
| my %kms_server_info; |
| map { $kms_server_info{$_->{affiliationid}}{$_->{address}} = $_->{port} } @selected_rows; |
| |
| notify($ERRORS{'DEBUG'}, 0, "retrieved KMS server info:\n" . format_data(\%kms_server_info)); |
| return \%kms_server_info; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_product_key |
| |
| Parameters : $affiliation_id, $product_name, $product_key |
| Returns : If successful: true |
| If failed: false |
| Description : Inserts or updates a row in the winProductKey table in the |
| database. |
| |
| =cut |
| |
| sub set_product_key { |
| 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; |
| } |
| |
| # Get and check the arguments |
| my ($affiliation_identifier, $product_name, $product_key) = @_; |
| if (!defined($affiliation_identifier) || !defined($product_name) || !defined($product_key)) { |
| notify($ERRORS{'WARNING'}, 0, "affiliation ID, product name, and product key arguments not passed correctly"); |
| return; |
| } |
| |
| # Create the insert statement |
| # Check if the affiliation identifier is a number or word |
| # If a number, set affiliation.id directly |
| # If a word, reference affiliation.name |
| my $insert_statement; |
| if ($affiliation_identifier =~ /^\d+$/) { |
| notify($ERRORS{'DEBUG'}, 0, "affiliation identifier is a number, setting winProductKey.affiliationid=$affiliation_identifier"); |
| $insert_statement = <<"EOF"; |
| INSERT INTO winProductKey |
| ( |
| affiliationid, |
| productname, |
| productkey |
| ) |
| VALUES |
| ( |
| '$affiliation_identifier', |
| '$product_name', |
| '$product_key' |
| ) |
| ON DUPLICATE KEY UPDATE |
| affiliationid=VALUES(affiliationid), |
| productname=VALUES(productname), |
| productkey=VALUES(productkey) |
| EOF |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "affiliation identifier is NOT a number, setting affiliation.name=$affiliation_identifier"); |
| $insert_statement = <<"EOF"; |
| INSERT INTO winProductKey |
| ( |
| affiliationid, |
| productname, |
| productkey |
| ) |
| VALUES |
| ( |
| (SELECT id FROM affiliation WHERE name='$affiliation_identifier'), |
| '$product_name', |
| '$product_key' |
| ) |
| ON DUPLICATE KEY UPDATE |
| affiliationid=VALUES(affiliationid), |
| productname=VALUES(productname), |
| productkey=VALUES(productkey) |
| EOF |
| } |
| |
| # Execute the insert statement, the return value should be the id of the row |
| my $insert_result = database_execute($insert_statement); |
| if (defined($insert_result)) { |
| notify($ERRORS{'DEBUG'}, 0, "set product key in database:\naffiliation ID: $affiliation_identifier\nproduct name: $product_name\nproduct key: $product_key"); |
| |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set product key in database:\naffiliation ID: $affiliation_identifier\nproduct name: $product_name\nproduct key: $product_key"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_product_key |
| |
| Parameters : $affiliation_id, $product_name |
| Returns : If successful: true |
| If failed: false |
| Description : Deletes a row from the winProductKey table in the database. |
| |
| =cut |
| |
| sub delete_product_key { |
| 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; |
| } |
| |
| # Get and check the arguments |
| my ($affiliation_id, $product_name, $product_key) = @_; |
| if (!defined($affiliation_id) || !defined($product_name) || !defined($product_key)) { |
| notify($ERRORS{'WARNING'}, 0, "affiliation ID, product name, and product key arguments not passed correctly"); |
| return; |
| } |
| |
| # Construct the delete statement |
| my $delete_statement = <<"EOF"; |
| DELETE FROM |
| winProductKey |
| WHERE |
| affiliationid = $affiliation_id |
| AND productname = '$product_name' |
| AND productkey = '$product_key' |
| EOF |
| |
| # Execute the delete statement |
| my $delete_result = database_execute($delete_statement); |
| if (defined($delete_result)) { |
| notify($ERRORS{'DEBUG'}, 0, "deleted product key from database:\naffiliation ID: $affiliation_id\nproduct name: $product_name\nproduct key: $product_key, result: $delete_result"); |
| |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete product key from database:\naffiliation ID: $affiliation_id\nproduct name: $product_name\nproduct key: $product_key"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_kms_server |
| |
| Parameters : $affiliation_id, $address |
| Returns : If successful: true |
| If failed: false |
| Description : Deletes a row from the winKMS table in the database. |
| |
| =cut |
| |
| sub delete_kms_server { |
| 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; |
| } |
| |
| # Get and check the arguments |
| my ($affiliation_id, $address) = @_; |
| if (!defined($affiliation_id) || !defined($address)) { |
| notify($ERRORS{'WARNING'}, 0, "affiliation ID and KMS server address arguments not passed correctly"); |
| return; |
| } |
| |
| # Construct the delete statement |
| my $delete_statement = <<"EOF"; |
| DELETE FROM |
| winKMS |
| WHERE |
| affiliationid = $affiliation_id |
| AND address = '$address' |
| EOF |
| |
| # Execute the delete statement |
| my $delete_result = database_execute($delete_statement); |
| if (defined($delete_result)) { |
| notify($ERRORS{'DEBUG'}, 0, "deleted KMS server from database:\naffiliation ID: $affiliation_id\naddress: $address, result: $delete_result"); |
| |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete product key from database:\naffiliation ID: $affiliation_id\naddress: $address"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_kms_servers |
| |
| Parameters : $affiliation_identifier (optional) |
| Returns : If successful: reference to array of hashes |
| If failed: false |
| Description : Retrieves the KMS server data from the database. This is |
| stored in the winKMS table. |
| |
| An optional affiliation identifier argument may be passed. This |
| may either be an affiliation ID or name. If passed, the only data |
| returned will be the data matching that specific identifier. |
| Global affiliation data will not be returned. |
| |
| If the affiliation identifier argument is not passed, the |
| affiliation is determined by the affiliation of the owner of the |
| image for the reservation. If a KMS server has not been |
| configured for that specific affiliation, the KMS server |
| configured for the Global affiliation is returned. |
| |
| This subroutine returns an array reference. Each array element |
| contains a hash reference representing a row in the winKMS table. |
| |
| Example of returned data: |
| @{$kms_servers}[0] = |
| |--{address} = 'kms.affiliation.edu' |
| |--{affiliationid} = '1' |
| |--{port} = '1688' |
| @{$kms_servers}[1] = |
| |--{address} = 'kms.global.edu' |
| |--{affiliationid} = '0' |
| |--{port} = '1688' |
| |
| Example usage: |
| my $kms_servers = $self->os->get_kms_servers(); |
| my $kms_address = @{$kms_servers[0]}->{address}; |
| |
| =cut |
| |
| sub get_kms_servers { |
| 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; |
| } |
| |
| # Remember if this sub was called with arguments |
| # Used to determine whether or not global affiliation data will be checked |
| my $include_global; |
| if (scalar(@_) == 1) { |
| $include_global = 0; |
| notify($ERRORS{'DEBUG'}, 0, "subroutine was called with an affiliation argument, global affiliation data will be ignored"); |
| } |
| elsif (scalar(@_) == 0) { |
| $include_global = 1; |
| notify($ERRORS{'DEBUG'}, 0, "subroutine was NOT called with arguments, global affiliation data will be included"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "subroutine argument count = " . scalar(@_) . ", it must only be called with 0 or 1 arguments"); |
| return; |
| } |
| |
| # Get the image affiliation identifier, may be ID or name |
| my $affiliation_identifier = shift; |
| if (!defined($affiliation_identifier)) { |
| $affiliation_identifier = $self->data->get_image_affiliation_id(); |
| } |
| if (!defined($affiliation_identifier)) { |
| notify($ERRORS{'WARNING'}, 0, "affiliation argument was not passed and could not be determined from image"); |
| return; |
| } |
| |
| # Create the affiliation-specific select statement |
| # Check if the affiliation identifier is a number or word |
| # If a number, use affiliation.id directly |
| # If a word, reference affiliation.name |
| my $affiliation_select_statement; |
| if ($affiliation_identifier =~ /^\d+$/) { |
| notify($ERRORS{'DEBUG'}, 0, "affiliation identifier is a number, retrieving winKMS.affiliationid=$affiliation_identifier"); |
| $affiliation_select_statement = <<EOF; |
| SELECT |
| winKMS.* |
| FROM |
| winKMS |
| WHERE |
| winKMS.affiliationid = $affiliation_identifier |
| EOF |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "affiliation identifier is NOT a number, retrieving affiliation.name=$affiliation_identifier"); |
| $affiliation_select_statement .= <<EOF; |
| SELECT |
| winKMS.* |
| FROM |
| winKMS, |
| affiliation |
| WHERE |
| winKMS.affiliationid = affiliation.id |
| AND affiliation.name LIKE '$affiliation_identifier' |
| EOF |
| } |
| |
| # Create the Global affiliation select statement |
| my $global_select_statement .= <<EOF; |
| SELECT |
| winKMS.* |
| FROM |
| winKMS, |
| affiliation |
| WHERE |
| winKMS.affiliationid = affiliation.id |
| AND affiliation.name LIKE 'Global' |
| EOF |
| |
| # Call the database select subroutine |
| my @affiliation_rows = database_select($affiliation_select_statement); |
| |
| # Get the rows for the Global affiliation if this subroutine wasn't called with arguments |
| my @global_rows = (); |
| if ($include_global) { |
| @global_rows = database_select($global_select_statement); |
| } |
| |
| # Create an array containing the combined rows |
| my @combined_rows = (@affiliation_rows, @global_rows); |
| |
| # Check to make sure rows were returned |
| if (!@combined_rows) { |
| notify($ERRORS{'WARNING'}, 0, "0 rows were retrieved from winKMS table for affiliation=$affiliation_identifier"); |
| return; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "returning row array from winKMS table for affiliation=$affiliation_identifier:\n" . format_data(\@combined_rows)); |
| return \@combined_rows; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_kms_server |
| |
| Parameters : $affiliation_id, $address, $port (optional) |
| Returns : If successful: true |
| If failed: false |
| Description : Inserts or updates a row in the winKMS table in the database. |
| |
| =cut |
| |
| sub set_kms_server { |
| 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; |
| } |
| |
| # Get and check the arguments |
| my ($affiliation_identifier, $address, $port) = @_; |
| if (!defined($affiliation_identifier) || !defined($address)) { |
| notify($ERRORS{'WARNING'}, 0, "affiliation ID and KMS address arguments not passed correctly"); |
| return; |
| } |
| |
| # Set the default port if argument wasn't passed |
| if (!defined($port)) { |
| $port = 1688; |
| } |
| |
| # Create the insert statement |
| # Check if the affiliation identifier is a number or word |
| # If a number, set affiliation.id directly |
| # If a word, reference affiliation.name |
| my $insert_statement; |
| if ($affiliation_identifier =~ /^\d+$/) { |
| notify($ERRORS{'DEBUG'}, 0, "affiliation identifier is a number, setting winKMS.affiliationid=$affiliation_identifier"); |
| $insert_statement = <<"EOF"; |
| INSERT INTO winKMS |
| ( |
| affiliationid, |
| address, |
| port |
| ) |
| VALUES |
| ( |
| '$affiliation_identifier', |
| '$address', |
| '$port' |
| ) |
| ON DUPLICATE KEY UPDATE |
| affiliationid=VALUES(affiliationid), |
| address=VALUES(address), |
| port=VALUES(port) |
| EOF |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "affiliation identifier is NOT a number, setting affiliation.name=$affiliation_identifier"); |
| $insert_statement = <<"EOF"; |
| INSERT INTO winKMS |
| ( |
| affiliationid, |
| address, |
| port |
| ) |
| VALUES |
| ( |
| (SELECT id FROM affiliation WHERE name='$affiliation_identifier'), |
| '$address', |
| '$port' |
| ) |
| ON DUPLICATE KEY UPDATE |
| affiliationid=VALUES(affiliationid), |
| address=VALUES(address), |
| port=VALUES(port) |
| EOF |
| } |
| |
| # Execute the insert statement, the return value should be the id of the row |
| my $insert_result = database_execute($insert_statement); |
| if (defined($insert_result)) { |
| notify($ERRORS{'OK'}, 0, "set KMS address in database:\naffiliation ID: $affiliation_identifier\naddress: $address\nport: $port"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set KMS address in database:\naffiliation ID: $affiliation_identifier\naddress: $address\nport: $port"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_driver_inf_paths |
| |
| Parameters : Driver class (optional) |
| Returns : Array containing driver .inf paths |
| Description : This subroutine searches the node configuration drivers directory |
| on the computer for .inf files and returns an array containing |
| the paths of the .inf files. The node configuration drivers |
| directory is: C:\cygwin\home\root\VCL\Drivers |
| |
| An optional driver class argument can be supplied which will |
| cause this subroutine to only return drivers matching the class |
| specified. Each driver .inf file should have a Class= line which |
| specified the type of device the driver is intended for. This |
| argument can be a regular expression. For example, to search for |
| all storage drivers, pass the following string to this |
| subroutine: |
| (scsiadapter|hdc) |
| |
| The driver paths are formatted with forward slashes. |
| |
| =cut |
| |
| sub get_driver_inf_paths { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Check if a driver class argument was specified |
| my $driver_class = shift; |
| if ($driver_class) { |
| notify($ERRORS{'DEBUG'}, 0, "attempting to locate driver .inf paths matching class: $driver_class"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "attempting to locate driver .inf paths matching any class"); |
| } |
| |
| my $drivers_directory = $self->get_node_configuration_directory() . '/Drivers'; |
| |
| # Find the paths of .inf files in the drivers directory with a Class=SCSIAdapter or HDC line |
| # These are the storage driver .inf files |
| my @inf_paths = (); |
| my $grep_command .= '/usr/bin/grep.exe -Eirl --include="*.[iI][nN][fF]" '; |
| if ($driver_class) { |
| $grep_command .= '"class[ ]*=[ ]*' . $driver_class . '" '; |
| } |
| else { |
| $grep_command .= '".*" '; |
| } |
| $grep_command .= $drivers_directory; |
| |
| my ($grep_exit_status, $grep_output) = run_ssh_command($computer_node_name, $management_node_keys, $grep_command, '', '', 1); |
| if (defined($grep_exit_status) && $grep_exit_status > 1) { |
| notify($ERRORS{'WARNING'}, 0, "failed to find driver paths, exit status: $grep_exit_status, output:\n@{$grep_output}"); |
| return; |
| } |
| elsif (defined($grep_output)) { |
| my @inf_paths = grep(/:[\\\/]/, @$grep_output); |
| notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@inf_paths) . " driver .inf paths, grep output:\n". join("\n", @$grep_output)); |
| return @inf_paths; |
| } |
| elsif (defined($grep_exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to find driver paths, exit status: $grep_exit_status, output:\n@{$grep_output}"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to find driverpaths"); |
| return; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_device_path_key |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Determines the paths to all of the driver .inf files copied to |
| the computer and sets the following Windows registry key: |
| HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DevicePath |
| |
| This key contains paths to driver .inf files. Windows searches |
| these files when attempting to load a device driver. |
| |
| =cut |
| |
| sub set_device_path_key { |
| 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 $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $device_path_value; |
| |
| # Find the paths of .inf files in the drivers directory |
| my @inf_paths = $self->get_driver_inf_paths(); |
| if (!@inf_paths || $inf_paths[0] eq '0') { |
| # No driver paths were found, just use the inf path |
| $device_path_value = '%SystemRoot%\\inf'; |
| notify($ERRORS{'DEBUG'}, 0, "no driver .inf paths were found"); |
| } |
| else { |
| # Remove the .inf filenames from the paths |
| map(s/\/[^\/]*$//, @inf_paths); |
| |
| # Remove duplicate paths, occurs if a directory has more than 1 .inf file |
| my %inf_path_hash; |
| my @inf_paths_unique = grep { !$inf_path_hash{$_}++ } @inf_paths; |
| notify($ERRORS{'DEBUG'}, 0, "found " . scalar(@inf_paths_unique) . " unique driver .inf paths"); |
| |
| # Assemble the device path value |
| $device_path_value = '%SystemRoot%\\inf;' . join(";", @inf_paths_unique); |
| |
| # Replace forward slashes with backslashes |
| $device_path_value =~ s/\//\\/g; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "device path value: $device_path_value"); |
| |
| # Attempt to set the DevicePath key |
| my $registry_key = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion'; |
| if ($self->reg_add($registry_key, 'DevicePath', 'REG_EXPAND_SZ', $device_path_value)) { |
| notify($ERRORS{'OK'}, 0, "set the DevicePath registry key"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the DevicePath registry key"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_hibernation |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Disables hibernation mode. |
| |
| =cut |
| |
| sub disable_hibernation { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| 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; |
| |
| # Run powercfg.exe to disable hibernation |
| my $powercfg_command = "$system32_path/powercfg.exe -HIBERNATE OFF"; |
| my ($powercfg_exit_status, $powercfg_output) = run_ssh_command($computer_node_name, $management_node_keys, $powercfg_command, '', '', 1); |
| if (defined($powercfg_exit_status) && $powercfg_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "disabled hibernation"); |
| } |
| elsif ($powercfg_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to disable hibernation, exit status: $powercfg_exit_status, output:\n" . join("\n", @$powercfg_output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to disable hibernation"); |
| return; |
| } |
| |
| # Delete hiberfil.sys |
| if (!$self->delete_file('$SYSTEMDRIVE/hiberfil.sys')) { |
| notify($ERRORS{'WARNING'}, 0, "failed to disable hibernation, hiberfil.sys could not be deleted"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_ceip |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Disables the Windows Customer Experience Improvement Program |
| features. |
| |
| =cut |
| |
| sub disable_ceip { |
| 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; |
| } |
| |
| # Attempt to set the CEIPEnable key |
| my $registry_key_software = 'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\SQMClient\\Windows'; |
| if ($self->reg_add($registry_key_software, 'CEIPEnable', 'REG_DWORD', 0)) { |
| notify($ERRORS{'OK'}, 0, "set the CEIPEnable software registry key to 0"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the CEIPEnable registry key to 0"); |
| return; |
| } |
| |
| # Attempt to set the CEIPEnable policy key |
| my $registry_key_policy = 'HKEY_LOCAL_MACHINE\\Software\\Policies\\Microsoft\\SQMClient\\Windows'; |
| if ($self->reg_add($registry_key_policy, 'CEIPEnable', 'REG_DWORD', 0)) { |
| notify($ERRORS{'OK'}, 0, "set the CEIPEnable policy registry key to 0"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the CEIPEnable policy registry key to 0"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_shutdown_event_tracker |
| |
| Parameters : None |
| Returns : If successful: true |
| If failed: false |
| Description : Disables the Shutdown Event Tracker. This is enabled by default |
| on Windows Server 2003. It is what causes a box to appear which |
| asks for a reason when the computer is shutdown or rebooted. The |
| box also appears during login if the computer is shut down |
| unexpectedly. This causes the autologon sequence to break. |
| |
| =cut |
| |
| sub disable_shutdown_event_tracker { |
| 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; |
| } |
| |
| # Attempt to set the ShutdownReasonOn key |
| my $registry_key_software = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Windows NT\\Reliability'; |
| if ($self->reg_add($registry_key_software, 'ShutdownReasonOn', 'REG_DWORD', 0)) { |
| notify($ERRORS{'OK'}, 0, "set the ShutdownReasonOn software registry key to 0"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set the ShutdownReasonOn registry key to 0"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 setup |
| |
| Parameters : none |
| Returns : |
| Description : Presents a command-line menu interface to the user to configure |
| the Windows OS modules when vcld is run in setup mode. |
| |
| =cut |
| |
| sub setup { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| push @{$ENV{setup_path}}, 'Windows'; |
| |
| my @operation_choices = ( |
| 'Configure Product Keys', |
| 'Configure KMS Servers', |
| ); |
| |
| my @setup_path = @{$ENV{setup_path}}; |
| OPERATION: while (1) { |
| @{$ENV{setup_path}} = @setup_path; |
| |
| print '-' x 76 . "\n"; |
| |
| $self->setup_check(); |
| |
| print "Choose an operation:\n"; |
| my $operation_choice_index = setup_get_array_choice(@operation_choices); |
| last if (!defined($operation_choice_index)); |
| my $operation_name = $operation_choices[$operation_choice_index]; |
| print "\n"; |
| |
| push @{$ENV{setup_path}}, $operation_name; |
| |
| if ($operation_name =~ /product keys/i) { |
| $self->setup_product_keys(); |
| } |
| elsif ($operation_name =~ /kms/i) { |
| $self->setup_kms_servers(); |
| } |
| } |
| |
| pop @{$ENV{setup_path}}; |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 setup_check |
| |
| Parameters : none |
| Returns : |
| Description : Checks various configuration settings and displays a message to |
| the user if any important settings are not configured. This gets |
| called every time Windows.pm::setup() is called. |
| |
| =cut |
| |
| sub setup_check { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my @messages; |
| |
| # Get a hash containing all of the information from the affiliation table |
| my $affiliation_info = get_affiliation_info(); |
| if (!$affiliation_info) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve affiliation info"); |
| return; |
| } |
| |
| my ($global_affiliation_id) = grep { $affiliation_info->{$_}{name} =~ /^global$/i } (keys %$affiliation_info); |
| if (!defined($global_affiliation_id)) { |
| print "ERROR: unable to determine global affiliation ID:\n" . format_data($affiliation_info) . "\n"; |
| return; |
| } |
| |
| # Get the product key information from the database |
| my $product_key_info = $self->get_product_key_info(); |
| if (!defined($product_key_info)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve product key information from the database"); |
| return; |
| } |
| |
| my @product_names = keys %{$product_key_info->{$global_affiliation_id}}; |
| |
| if (!grep(/Windows XP/, @product_names)) { |
| push @messages, "A Windows XP product key is not configured for the Global affiliation. Captured Windows XP images using Sysprep may fail to load if the product key is not configured."; |
| } |
| if (!grep(/Server 2003/, @product_names)) { |
| push @messages, "A Windows Server 2003 product key is not configured for the Global affiliation. Captured Windows Server 2003 images using Sysprep may fail to load if the product key is not configured."; |
| } |
| |
| for my $message (@messages) { |
| chomp $message; |
| setup_print_wrap("*** $message ***\n\n"); |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 setup_product_keys |
| |
| Parameters : none |
| Returns : nothing |
| Description : Used to list, set, and delete product keys from the winProductKey |
| table in the database when vcld is run in setup mode. |
| |
| =cut |
| |
| sub setup_product_keys { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get a hash containing all of the information from the affiliation table |
| my $affiliation_info = get_affiliation_info(); |
| if (!$affiliation_info) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve affiliation info"); |
| return; |
| } |
| |
| my @product_names = ( |
| 'Windows XP', |
| 'Windows Server 2003', |
| 'Windows Vista Business', |
| 'Windows Vista Business N', |
| 'Windows Vista Enterprise', |
| 'Windows Vista Enterprise N', |
| 'Windows Server 2008 Datacenter', |
| 'Windows Server 2008 Datacenter without Hyper-V', |
| 'Windows Server 2008 for Itanium-Based Systems', |
| 'Windows Server 2008 Enterprise', |
| 'Windows Server 2008 Enterprise without Hyper-V', |
| 'Windows Server 2008 Standard', |
| 'Windows Server 2008 Standard without Hyper-V', |
| 'Windows Web Server 2008', |
| 'Windows Server 2008 HPC', |
| 'Windows 7 Professional', |
| 'Windows 7 Professional N', |
| 'Windows 7 Professional E', |
| 'Windows 7 Enterprise', |
| 'Windows 7 Enterprise N', |
| 'Windows 7 Enterprise E', |
| 'Windows Server 2008 R2 Web', |
| 'Windows Server 2008 R2 HPC edition', |
| 'Windows Server 2008 R2 Standard', |
| 'Windows Server 2008 R2 Enterprise', |
| 'Windows Server 2008 R2 Datacenter', |
| 'Windows Server 2008 R2 for Itanium-based Systems', |
| 'Other', |
| ); |
| |
| my @operation_choices = ( |
| 'List Product Keys', |
| 'Add Product Key', |
| 'Delete Product Key', |
| ); |
| |
| my @setup_path = @{$ENV{setup_path}}; |
| OPERATION: while (1) { |
| @{$ENV{setup_path}} = @setup_path; |
| |
| print '-' x 76 . "\n"; |
| |
| print "Choose an operation:\n"; |
| my $operation_choice_index = setup_get_array_choice(@operation_choices); |
| last if (!defined($operation_choice_index)); |
| my $operation_name = $operation_choices[$operation_choice_index]; |
| print "\n"; |
| |
| push @{$ENV{setup_path}}, $operation_name; |
| |
| if ($operation_name =~ /list/i) { |
| $self->setup_display_product_key_info(); |
| print "\n"; |
| } |
| |
| elsif ($operation_name =~ /add/i) { |
| print "Choose an affiliation:\n"; |
| my $affiliation_id = setup_get_hash_choice($affiliation_info, 'name'); |
| next if (!defined($affiliation_id)); |
| my $affiliation_name = $affiliation_info->{$affiliation_id}{name}; |
| print "Selected affiliation: $affiliation_name\n\n"; |
| |
| $self->setup_display_product_key_info($affiliation_id); |
| print "\n"; |
| |
| print "Choose a Windows product:\n"; |
| my $product_choice_index = setup_get_array_choice(@product_names); |
| next OPERATION if (!defined($product_choice_index)); |
| |
| my $product_name = $product_names[$product_choice_index]; |
| if ($product_name eq 'Other') { |
| $product_name = setup_get_input_string("Enter a product name"); |
| next OPERATION if (!defined($product_name)); |
| } |
| print "Windows product: $product_name\n\n"; |
| |
| my $product_key; |
| while (!$product_key) { |
| $product_key = setup_get_input_string("Enter the product key xxxxx-xxxxx-xxxxx-xxxxx-xxxxx"); |
| next OPERATION if (!defined($product_key)); |
| if ($product_key !~ /(\w{5}-?){5}/) { |
| print "Product key is not in the correct format: $product_key\n"; |
| $product_key = 0; |
| } |
| } |
| $product_key = uc($product_key); |
| print "\n"; |
| |
| # Attempt to set the product key in the database |
| if ($self->set_product_key($affiliation_id, $product_name, $product_key)) { |
| print "Product key has been saved to the database:\nAffiliation: $affiliation_name\nProduct name: $product_name\nProduct key: $product_key\n"; |
| } |
| else { |
| print "ERROR: failed to save product key to the database:\nAffiliation: $affiliation_name\nProduct name: $product_name\nProduct key: $product_key\n"; |
| } |
| } |
| |
| elsif ($operation_name =~ /delete/i) { |
| # Get the product key information from the database |
| my $product_key_info = $self->get_product_key_info(); |
| if (!defined($product_key_info)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve product key information from the database"); |
| return; |
| } |
| |
| my %product_keys; |
| for my $affiliation_id (keys %$product_key_info) { |
| my $affiliation_name = $affiliation_info->{$affiliation_id}{name}; |
| |
| for my $product_name (keys %{$product_key_info->{$affiliation_id}}) { |
| my $product_key = $product_key_info->{$affiliation_id}{$product_name}; |
| |
| my $product_key_choice_name = "$affiliation_name: '$product_name' ($product_key)"; |
| |
| $product_keys{$product_key_choice_name}{affiliation_id} = $affiliation_id; |
| $product_keys{$product_key_choice_name}{product_name} = $product_name; |
| $product_keys{$product_key_choice_name}{product_key} = $product_key; |
| } |
| } |
| |
| # Choose an affiliation with populated product keys |
| print "Choose a product key to delete:\n"; |
| my $product_key_choice_name = setup_get_hash_choice(\%product_keys); |
| next if (!defined($product_key_choice_name)); |
| print "\n"; |
| |
| my $affiliation_id = $product_keys{$product_key_choice_name}{affiliation_id}; |
| my $affiliation_name = $affiliation_info->{$affiliation_id}{name}; |
| my $product_name = $product_keys{$product_key_choice_name}{product_name}; |
| my $product_key = $product_keys{$product_key_choice_name}{product_key}; |
| |
| # Attempt to delete the product key from the database |
| if ($self->delete_product_key($affiliation_id, $product_name, $product_key)) { |
| print "Product key for has been deleted from the database:\nAffiliation: $affiliation_name\nProduct name: $product_name\nProduct key: $product_key\n"; |
| } |
| else { |
| print "ERROR: failed to delete product key from the database:\nAffiliation: $affiliation_name\nProduct name: $product_name\nProduct key: $product_key\n"; |
| } |
| } |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 setup_display_product_key_info |
| |
| Parameters : $affiliation_id (optional) |
| Returns : |
| Description : Displays the product keys configured in the winProductKey table |
| in the database. If an affiliation ID argument is specified, only |
| the information for that affiliation is displayed. |
| |
| =cut |
| |
| sub setup_display_product_key_info { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get a hash containing all of the information from the affiliation table |
| my $affiliation_info = get_affiliation_info(); |
| if (!$affiliation_info) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve affiliation info"); |
| return; |
| } |
| |
| # Get the affiliation ID argument if it was specified |
| my $affiliation_id_argument = shift; |
| if ($affiliation_id_argument && !defined($affiliation_info->{$affiliation_id_argument})) { |
| notify($ERRORS{'WARNING'}, 0, "affiliation does not exist for affiliation ID argument: $affiliation_id_argument"); |
| return; |
| } |
| |
| # Get the product key information from the database |
| my $product_key_info = $self->get_product_key_info(); |
| if (!defined($product_key_info)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve product key information from the database"); |
| return; |
| } |
| |
| # Print the title |
| if ($affiliation_id_argument) { |
| my $affiliation_name = $affiliation_info->{$affiliation_id_argument}{name}; |
| print "Product key configuration for $affiliation_name ($affiliation_id_argument):\n"; |
| } |
| else { |
| print "Product key configuration for all affiliations:\n"; |
| } |
| |
| |
| my $product_key_info_string; |
| for my $affiliation_id (sort { $a <=> $b } keys %$product_key_info) { |
| |
| if (defined($affiliation_id_argument) && $affiliation_id ne $affiliation_id_argument) { |
| next; |
| } |
| |
| my $affiliation_name = $affiliation_info->{$affiliation_id}{name}; |
| |
| $product_key_info_string .= "$affiliation_name ($affiliation_id):\n"; |
| for my $product_name (keys %{$product_key_info->{$affiliation_id}}) { |
| my $product_key = $product_key_info->{$affiliation_id}{$product_name}; |
| $product_key_info_string .= " $product_name: $product_key\n"; |
| } |
| } |
| |
| $product_key_info_string = "<not configured>\n" if !$product_key_info_string; |
| print "$product_key_info_string"; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 setup_kms_servers |
| |
| Parameters : none |
| Returns : nothing |
| Description : Configures KMS servers in the winKMS table in the database when |
| vcld is run in setup mode. |
| |
| =cut |
| |
| sub setup_kms_servers { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get a hash containing all of the information from the affiliation table |
| my $affiliation_info = get_affiliation_info(); |
| if (!$affiliation_info) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve affiliation info"); |
| return; |
| } |
| |
| my @operation_choices = ( |
| 'List KMS Servers', |
| 'Add KMS Server', |
| 'Delete KMS Server', |
| ); |
| |
| my @setup_path = @{$ENV{setup_path}}; |
| OPERATION: while (1) { |
| @{$ENV{setup_path}} = @setup_path; |
| print '-' x 76 . "\n"; |
| |
| print "Choose an operation:\n"; |
| my $operation_choice_index = setup_get_array_choice(@operation_choices); |
| last if (!defined($operation_choice_index)); |
| my $operation_name = $operation_choices[$operation_choice_index]; |
| print "\n"; |
| |
| push @{$ENV{setup_path}}, $operation_name; |
| |
| if ($operation_name =~ /list/i) { |
| $self->setup_display_kms_server_info(); |
| print "\n"; |
| } |
| |
| elsif ($operation_name =~ /add/i) { |
| print "Choose an affiliation:\n"; |
| my $affiliation_id = setup_get_hash_choice($affiliation_info, 'name'); |
| next if (!defined($affiliation_id)); |
| my $affiliation_name = $affiliation_info->{$affiliation_id}{name}; |
| print "Selected affiliation: $affiliation_name\n\n"; |
| |
| $self->setup_display_kms_server_info($affiliation_id); |
| print "\n"; |
| |
| my $address; |
| while (!$address) { |
| $address = setup_get_input_string("Enter the KMS server host name or address"); |
| next OPERATION if (!defined($address)); |
| if (!is_valid_dns_host_name($address) && !is_valid_ip_address($address)) { |
| print "Address is not a valid DNS host name or IP address: $address\n"; |
| $address = ''; |
| } |
| } |
| print "\n"; |
| |
| my $port; |
| while (!$port) { |
| $port = setup_get_input_string("Enter the KMS server port", 1688); |
| next OPERATION if (!defined($port)); |
| if ($port !~ /^\d+$/) { |
| print "Port must be an integer: $port\n"; |
| $port = ''; |
| } |
| } |
| |
| print "\n"; |
| |
| # Attempt to set the KMS server in the database |
| if ($self->set_kms_server($affiliation_id, $address, $port)) { |
| print "KMS server added to the database:\nAffiliation: $affiliation_name\nAddress: $address\nPort: $port\n"; |
| } |
| else { |
| print "ERROR: failed to save product key to the database:\nAffiliation: $affiliation_name\nAddress: $address\nPort: $port\n"; |
| } |
| } |
| |
| elsif ($operation_name =~ /delete/i) { |
| # Get the KMS server information from the database |
| my $kms_server_info = $self->get_kms_server_info(); |
| if (!defined($kms_server_info)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve KMS server information from the database"); |
| return; |
| } |
| |
| my %kms_servers; |
| for my $affiliation_id (keys %$kms_server_info) { |
| my $affiliation_name = $affiliation_info->{$affiliation_id}{name}; |
| |
| for my $address (keys %{$kms_server_info->{$affiliation_id}}) { |
| my $port = $kms_server_info->{$affiliation_id}{$address}; |
| |
| my $kms_server_choice_name = "$affiliation_name: $address:$port"; |
| |
| $kms_servers{$kms_server_choice_name}{affiliation_id} = $affiliation_id; |
| $kms_servers{$kms_server_choice_name}{address} = $address; |
| $kms_servers{$kms_server_choice_name}{port} = $port; |
| } |
| } |
| |
| # Choose an affiliation populated with a KMS server |
| print "Choose a KMS server to delete:\n"; |
| my $kms_server_choice_name = setup_get_hash_choice(\%kms_servers); |
| next if (!defined($kms_server_choice_name)); |
| print "\n"; |
| |
| my $affiliation_id = $kms_servers{$kms_server_choice_name}{affiliation_id}; |
| my $affiliation_name = $affiliation_info->{$affiliation_id}{name}; |
| my $address = $kms_servers{$kms_server_choice_name}{address}; |
| my $port = $kms_servers{$kms_server_choice_name}{port}; |
| |
| ## Attempt to delete the product key from the database |
| if ($self->delete_kms_server($affiliation_id, $address)) { |
| print "KMS server has been deleted from the database:\nAffiliation: $affiliation_name\nAddress: $address\nPort: $port\n"; |
| } |
| else { |
| print "ERROR: failed to delete product key from the database:\nAffiliation: $affiliation_name\nAddress: $address\nPort: $port\n"; |
| } |
| } |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 setup_display_kms_server_info |
| |
| Parameters : $affiliation_id (optional) |
| Returns : |
| Description : Displays the KMS server configuration stored in the winKMS table |
| in the database to STDOUT. |
| |
| =cut |
| |
| sub setup_display_kms_server_info { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get a hash containing all of the information from the affiliation table |
| my $affiliation_info = get_affiliation_info(); |
| if (!$affiliation_info) { |
| notify($ERRORS{'WARNING'}, 0, "unable to retrieve affiliation info"); |
| return; |
| } |
| |
| # Get the affiliation ID argument if it was specified |
| my $affiliation_id_argument = shift; |
| if ($affiliation_id_argument && !defined($affiliation_info->{$affiliation_id_argument})) { |
| notify($ERRORS{'WARNING'}, 0, "affiliation does not exist for affiliation ID argument: $affiliation_id_argument"); |
| return; |
| } |
| |
| # Get the KMS server information from the database |
| my $kms_server_info = $self->get_kms_server_info(); |
| if (!defined($kms_server_info)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve KMS server information from the database"); |
| return; |
| } |
| |
| # Print the title |
| if ($affiliation_id_argument) { |
| my $affiliation_name = $affiliation_info->{$affiliation_id_argument}{name}; |
| print "KMS server configuration for $affiliation_name ($affiliation_id_argument):\n"; |
| } |
| else { |
| print "KMS server configuration for all affiliations:\n"; |
| } |
| |
| # Print the KMS serer information |
| my $kms_server_info_string; |
| for my $affiliation_id (sort { $a <=> $b } keys %$kms_server_info) { |
| # Ignore non-matching affiliations if the affiliation ID argument was specified |
| if (defined($affiliation_id_argument) && $affiliation_id ne $affiliation_id_argument) { |
| next; |
| } |
| |
| my $affiliation_name = $affiliation_info->{$affiliation_id}{name}; |
| |
| $kms_server_info_string .= "$affiliation_name ($affiliation_id):\n"; |
| for my $address (keys %{$kms_server_info->{$affiliation_id}}) { |
| my $port = $kms_server_info->{$affiliation_id}{$address}; |
| $kms_server_info_string .= " $address:$port\n"; |
| } |
| } |
| |
| $kms_server_info_string = "<not configured>\n" if !$kms_server_info_string; |
| print "$kms_server_info_string"; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_time_zone_name |
| |
| Parameters : none |
| Returns : string |
| Description : Returns the name of the time zone configured for the management |
| node. The date command is run locally on the management node and |
| the time zone abbreviation is parsed from the output. This |
| %TIME_ZONE_INFO hash is searched for matching time zone |
| information and the time zone name is returned. If a matching |
| time zone is not found, 'Eastern Standard Time' is returned. |
| Example: 'HVL' returns 'Tasmania Standard Time' |
| |
| =cut |
| |
| sub get_time_zone_name { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $default_time_zone_name = 'Eastern Standard Time'; |
| |
| # Call date to determine the time zone abbreviation in use on the management node |
| my ($exit_status, $output) = run_command('date +%Z'); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to determine time zone configured for management node, returning '$default_time_zone_name'"); |
| return $default_time_zone_name; |
| } |
| |
| # Extract the time zone abbreviation from the output |
| my ($set_abbreviation) = grep(/^\w{3}$/, @$output); |
| if (!$set_abbreviation) { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine time zone abbreviation from output, returning '$default_time_zone_name':\n" . join("\n", @$output)); |
| return $default_time_zone_name; |
| } |
| |
| # Windows time zone codes don't include corresponding daylight time abbreviations, e.g. EDT |
| # Convert *DT --> *ST |
| if ($set_abbreviation =~ /(.)DT/i) { |
| $set_abbreviation = "$1ST"; |
| notify($ERRORS{'DEBUG'}, 0, "time zone abbreviation converted to standard time: $1DT --> $set_abbreviation"); |
| } |
| |
| # Loop through the time zone codes until a matching abbreviation is found |
| for my $time_zone_name (sort keys %TIME_ZONE_INFO) { |
| my $time_zone_abbreviation = $TIME_ZONE_INFO{$time_zone_name}{abbreviation}; |
| |
| next if (!$time_zone_abbreviation || $set_abbreviation !~ /^$time_zone_abbreviation$/i); |
| |
| notify($ERRORS{'DEBUG'}, 0, "determined name of time zone configured for management node: '$time_zone_name'"); |
| return $time_zone_name; |
| } |
| |
| # Return the code for EST if a match was not found |
| notify($ERRORS{'WARNING'}, 0, "unable to determine name of time zone configured for management node, abbreviation: $set_abbreviation, returning '$default_time_zone_name'"); |
| return $default_time_zone_name; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_time_zone_code |
| |
| Parameters : none |
| Returns : string |
| Description : Returns the Windows numerical code of the time zone configured |
| for the management node. If a matching time zone is not found, 35 |
| is returned. |
| Example: 'HVL' returns 265 |
| |
| =cut |
| |
| sub get_time_zone_code { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $time_zone_name = $self->get_time_zone_name() || return; |
| |
| my $time_zone_code = $TIME_ZONE_INFO{$time_zone_name}{code}; |
| if ($time_zone_code) { |
| notify($ERRORS{'DEBUG'}, 0, "determined Windows code of time zone configured for management node: $time_zone_code"); |
| return $time_zone_code; |
| } |
| else { |
| my $default = 35; |
| notify($ERRORS{'WARNING'}, 0, "time zone code could not be determined for time zone: '$time_zone_name', returning $default"); |
| return $default; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 sanitize_files |
| |
| Parameters : @file_paths (optional) |
| Returns : boolean |
| Description : Removes the Windows root password from files on the computer. |
| |
| =cut |
| |
| sub sanitize_files { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get the file path arguments, add the node configuration directory |
| my @file_paths = @_; |
| push @file_paths, $self->get_node_configuration_directory(); |
| |
| # Loop through each file path, remove the Windows root password from each |
| my $error_occurred = 0; |
| for my $file_path (@file_paths) { |
| if (!$self->search_and_replace_in_files($file_path, $WINDOWS_ROOT_PASSWORD, 'WINDOWS_ROOT_PASSWORD')) { |
| notify($ERRORS{'WARNING'}, 0, "failed to remove the Windows root password from: $file_path"); |
| $error_occurred = 1; |
| } |
| } |
| |
| if ($error_occurred) { |
| return; |
| } |
| else { |
| return 1; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 clear_event_log |
| |
| Parameters : @logfile_names (optional) |
| Returns : boolean |
| Description : Clears the Windows 'Application', 'Security', 'System' event |
| logs. One or more event logfile names may be specified to only |
| clear certain event logs. |
| |
| =cut |
| |
| sub clear_event_log { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my @logfile_names = @_; |
| @logfile_names = ('Application', 'Security', 'System') if !@logfile_names; |
| |
| 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; |
| |
| # Assemble the command |
| # Call wmic.exe - the WMI shell |
| # wmic.exe will hang if it is called by itself. It has something to do with TTY/PTY |
| # Piping the echo command seems to prevent it from hanging |
| my $command; |
| for my $logfile_name (@logfile_names) { |
| $command .= "echo | $system32_path/Wbem/wmic.exe NTEVENTLOG WHERE LogFileName=\\\"$logfile_name\\\" CALL ClearEventLog ; "; |
| } |
| |
| # Remove the last ' ; ' added to the command |
| $command =~ s/[\s;]*$//g; |
| |
| my ($status, $output) = run_ssh_command($computer_node_name, $management_node_keys, $command); |
| if (!defined($output)) { |
| notify($ERRORS{'DEBUG'}, 0, "failed to run SSH command to clear the event log: @logfile_names"); |
| return; |
| } |
| elsif (grep(/ERROR/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to clear event log: @logfile_names, output:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (grep(/Method execution successful/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "cleared event log: @logfile_names"); |
| $self->create_eventlog_entry("Event log cleared by VCL"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output while clearing event log: @logfile_names, output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 disable_login_screensaver |
| |
| Parameters : None |
| Returns : |
| Description : Sets the registry keys to disable to login screensaver. |
| |
| =cut |
| |
| sub disable_login_screensaver { |
| 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 $registry_key = 'HKEY_USERS\\.DEFAULT\\Control Panel\\Desktop'; |
| if ($self->reg_add($registry_key, 'ScreenSaveActive', 'REG_SZ', 0) && |
| $self->reg_add($registry_key, 'ScreenSaveTimeOut', 'REG_SZ', 0)) { |
| notify($ERRORS{'DEBUG'}, 0, "set registry keys to disable the login screensaver"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to set registry keys to disable the login screensaver"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| =head2 fix_default_profile |
| |
| Parameters : none |
| Returns : boolean |
| Description : Attempts to correct common problems with the default user |
| profile by loading the default user registry hive from the |
| ntuser.dat file into the registry, making changes, then unloading |
| the hive. |
| |
| =cut |
| |
| sub fix_default_profile { |
| 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 $root_key = 'HKEY_USERS\DEFAULT_USER_PROFILE'; |
| my $profile_list_key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'; |
| |
| # Determine the default user profile path |
| my $profile_list_registry_info = $self->reg_query($profile_list_key); |
| if (!$profile_list_registry_info) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve profile information from the registry on $computer_node_name"); |
| return; |
| } |
| elsif (!$profile_list_registry_info->{$profile_list_key}) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine default profile path, '$profile_list_key' key does not exist in the registry data:\n" . format_data($profile_list_registry_info)); |
| return; |
| } |
| |
| # The default profile path should either be stored in the 'Default' value or can be assembled from combining the 'ProfilesDirectory' and 'DefaultUserProfile' values |
| my $default_profile_path; |
| if ($profile_list_registry_info->{$profile_list_key}{Default}) { |
| $default_profile_path = $profile_list_registry_info->{$profile_list_key}{Default}; |
| } |
| elsif ($profile_list_registry_info->{$profile_list_key}{ProfilesDirectory} && $profile_list_registry_info->{$profile_list_key}{DefaultUserProfile}) { |
| $default_profile_path = "$profile_list_registry_info->{$profile_list_key}{ProfilesDirectory}\\$profile_list_registry_info->{$profile_list_key}{DefaultUserProfile}"; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine default profile path from the registry on $computer_node_name:\n" . format_data($profile_list_registry_info->{$profile_list_key})); |
| return; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "determined default profile path from the registry on $computer_node_name: '$default_profile_path'"); |
| |
| # Load the default profile hive file into the registry |
| my $hive_file_path = "$default_profile_path\\ntuser.dat"; |
| if (!$self->reg_load($root_key, $hive_file_path)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to load the default profile hive into the registry on $computer_node_name"); |
| return; |
| } |
| |
| # Fix registry values known to cause problems |
| # The "Shell Folders" key may contain paths pointing to a specific user's profile |
| # Any paths under "Shell Folders" can be deleted |
| my $registry_string .= <<EOF; |
| Windows Registry Editor Version 5.00 |
| [-$root_key\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders] |
| [$root_key\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders] |
| EOF |
| |
| # Import the string into the registry |
| if (!$self->import_registry_string($registry_string)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to fix problematic registry settings in the default profile"); |
| return; |
| } |
| |
| # Unoad the default profile hive |
| if (!$self->reg_unload($root_key)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to unload the default profile hive from the registry on $computer_node_name"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #///////////////////////////////////////////////////////////////////////////// |
| |
| 1; |
| __END__ |
| |
| =head1 SEE ALSO |
| |
| L<http://cwiki.apache.org/VCL/> |
| |
| =cut |