blob: a95691872bc45b04a5e38becbde716741ef81814 [file] [log] [blame]
#!/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