| #!/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); |
| |
| # Specify the version of this module |
| our $VERSION = '2.5.1'; |
| |
| # Specify the version of Perl to use |
| use 5.008000; |
| |
| use strict; |
| use warnings; |
| use diagnostics; |
| |
| use Encode; |
| use English '-no_match_vars'; |
| use File::Basename; |
| use MIME::Base64; |
| use Net::Netmask; |
| use Text::CSV_XS; |
| |
| use VCL::utils; |
| |
| ############################################################################### |
| |
| =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'}, |
| ); |
| |
| =head2 $KMS_CLIENT_PRODUCT_KEYS |
| |
| Data type : Hash reference |
| Description : Keys used to activate Windows using a KMS server. The key needs |
| to be configured before attempting to activate via KMS. Each |
| flavor of Windows uses a different KMS client key. These keys are |
| public information. |
| |
| =cut |
| |
| # These keys are publicly available: |
| # https://technet.microsoft.com/en-us/library/jj612867(v=ws.11).aspx |
| |
| our $KMS_CLIENT_PRODUCT_KEYS = { |
| 'Windows 10 Education' => 'NW6C2-QMPVW-D7KKK-3GKT6-VCFB2', |
| 'Windows 10 Education N' => '2WH4N-8QGBV-H22JP-CT43Q-MDWWJ', |
| 'Windows 10 Enterprise' => 'NPPR9-FWDCX-D2C8J-H872K-2YT43', |
| 'Windows 10 Enterprise 2015 LTSB' => 'WNMTR-4C88C-JK8YV-HQ7T2-76DF9', |
| 'Windows 10 Enterprise 2015 LTSB N' => '2F77B-TNFGY-69QQF-B8YKP-D69TJ', |
| 'Windows 10 Enterprise 2016 LTSB' => 'DCPHK-NFMTC-H88MJ-PFHPY-QJ4BJ', |
| 'Windows 10 Enterprise 2016 LTSB N' => 'QFFDN-GRT3P-VKWWX-X7T3R-8B639', |
| 'Windows 10 Enterprise N' => 'DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4', |
| 'Windows 10 Professional' => 'W269N-WFGWX-YVC9B-4J6C9-T83GX', |
| 'Windows 10 Professional N' => 'MH37W-N47XK-V7XM9-C7227-GCQG9', |
| 'Windows 7 Enterprise' => '33PXH-7Y6KF-2VJC9-XBBR8-HVTHH', |
| 'Windows 7 Enterprise E' => 'C29WB-22CC8-VJ326-GHFJW-H9DH4', |
| 'Windows 7 Enterprise N' => 'YDRBP-3D83W-TY26F-D46B2-XCKRJ', |
| 'Windows 7 Professional' => 'FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4', |
| 'Windows 7 Professional E' => 'W82YF-2Q76Y-63HXB-FGJG9-GF7QX', |
| 'Windows 7 Professional N' => 'MRPKT-YTG23-K7D7T-X2JMM-QY7MG', |
| 'Windows 8 Enterprise' => '32JNW-9KQ84-P47T8-D8GGY-CWCK7', |
| 'Windows 8 Enterprise N' => 'JMNMF-RHW7P-DMY6X-RF3DR-X2BQT', |
| 'Windows 8 Professional' => 'NG4HW-VH26C-733KW-K6F98-J8CK4', |
| 'Windows 8 Professional N' => 'XCVCF-2NXM9-723PB-MHCB7-2RYQQ', |
| 'Windows 8.1 Enterprise' => 'MHF9N-XY6XB-WVXMC-BTDCT-MKKG7', |
| 'Windows 8.1 Enterprise N' => 'TT4HM-HN7YT-62K67-RGRQJ-JFFXW', |
| 'Windows 8.1 Professional' => 'GCRJD-8NW9H-F2CDX-CCM8D-9D6T9', |
| 'Windows 8.1 Professional N' => 'HMCNV-VVBFX-7HMBH-CTY9B-B4FXY', |
| 'Windows Server 2008 Datacenter' => '7M67G-PC374-GR742-YH8V4-TCBY3', |
| 'Windows Server 2008 Datacenter without Hyper-V' => '22XQ2-VRXRG-P8D42-K34TD-G3QQC', |
| 'Windows Server 2008 Enterprise' => 'YQGMW-MPWTJ-34KDK-48M3W-X4Q6V', |
| 'Windows Server 2008 Enterprise without Hyper-V' => '39BXF-X8Q23-P2WWT-38T2F-G3FPG', |
| 'Windows Server 2008 for Itanium-Based Systems' => '4DWFP-JF3DJ-B7DTH-78FJB-PDRHK', |
| 'Windows Server 2008 HPC' => 'RCTX3-KWVHP-BR6TB-RB6DM-6X7HP', |
| 'Windows Server 2008 R2 Datacenter' => '74YFP-3QFB3-KQT8W-PMXWJ-7M648', |
| 'Windows Server 2008 R2 Enterprise' => '489J6-VHDMP-X63PK-3K798-CPX3Y', |
| 'Windows Server 2008 R2 for Itanium-based Systems' => 'GT63C-RJFQ3-4GMB6-BRFB9-CB83V', |
| 'Windows Server 2008 R2 HPC edition' => 'TT8MH-CG224-D3D7Q-498W2-9QCTX', |
| 'Windows Server 2008 R2 Standard' => 'YC6KT-GKW9T-YTKYR-T4X34-R7VHC', |
| 'Windows Server 2008 R2 Web' => '6TPJF-RBVHG-WBW2R-86QPH-6RTM4', |
| 'Windows Server 2008 Standard' => 'TM24T-X9RMF-VWXK6-X8JC9-BFGM2', |
| 'Windows Server 2008 Standard without Hyper-V' => 'W7VD6-7JFBR-RX26B-YKQ3Y-6FFFJ', |
| 'Windows Server 2012' => 'BN3D2-R7TKB-3YPBD-8DRP2-27GG4', |
| 'Windows Server 2012 Country Specific' => '4K36P-JN4VD-GDC6V-KDT89-DYFKP', |
| 'Windows Server 2012 Datacenter' => '48HP8-DN98B-MYWDG-T2DCC-8W83P', |
| 'Windows Server 2012 MultiPoint Premium' => 'XNH6W-2V9GX-RGJ4K-Y8X6F-QGJ2G', |
| 'Windows Server 2012 MultiPoint Standard' => 'HM7DN-YVMH3-46JC3-XYTG7-CYQJJ', |
| 'Windows Server 2012 N' => '8N2M2-HWPGY-7PGT9-HGDD8-GVGGY', |
| 'Windows Server 2012 R2 Datacenter' => 'W3GGN-FT8W3-Y4M27-J84CP-Q3VJ9', |
| 'Windows Server 2012 R2 Essentials' => 'KNC87-3J2TX-XB4WP-VCPJV-M4FWM', |
| 'Windows Server 2012 R2 Server Standard' => 'D2N9P-3P6X9-2R39C-7RTCD-MDVJX', |
| 'Windows Server 2012 Server Standard' => 'XC9B7-NBPP2-83J2H-RHMBY-92BT4', |
| 'Windows Server 2012 Single Language' => '2WN2H-YGCQR-KFX6K-CD6TF-84YXQ', |
| 'Windows Server 2016 Datacenter' => 'CB7KF-BWN84-R7R2Y-793K2-8XDDG', |
| 'Windows Server 2016 Essentials' => 'JCKRF-N37P4-C2D82-9YXRT-4M63B', |
| 'Windows Server 2016 Standard' => 'WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY', |
| 'Windows Vista Business' => 'YFKBB-PQJJV-G996G-VWGXY-2V3X8', |
| 'Windows Vista Business N' => 'HMBQG-8H2RH-C77VX-27R82-VMQBT', |
| 'Windows Vista Enterprise' => 'VKK3X-68KWM-X2YGT-QR4M6-4BWMV', |
| 'Windows Vista Enterprise N' => 'VTC42-BM838-43QHV-84HX6-XJXKV', |
| 'Windows Web Server 2008' => 'WYR28-R7TFJ-3X2YQ-YCY4H-M249D', |
| }; |
| |
| ############################################################################### |
| |
| =head1 INTERFACE OBJECT METHODS |
| |
| =cut |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 initialize |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub initialize { |
| 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; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "beginning Windows module initialization"); |
| |
| my $request_state = $self->data->get_request_state_name(); |
| |
| # If the request state is reserved, retrieve the firewall configuration now to reduce a delay after the user clicks Connect |
| if ($request_state =~ /reserved/) { |
| notify($ERRORS{'DEBUG'}, 0, "request state is $request_state, caching firewall configuration to reduce delays later on"); |
| $self->get_firewall_configuration('TCP'); |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "Windows module initialization complete"); |
| |
| if ($self->can("SUPER::initialize")) { |
| return $self->SUPER::initialize(); |
| } |
| else { |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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(); |
| |
| # Call OS::pre_capture to perform the pre-capture tasks common to all OS's |
| if (!$self->SUPER::pre_capture($args)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute parent class pre_capture() subroutine"); |
| return 0; |
| } |
| |
| notify($ERRORS{'OK'}, 0, "beginning Windows image capture preparation tasks on $computer_node_name"); |
| |
| =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 * |
| |
| 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 * |
| |
| 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 * |
| |
| If computer is part of Active Directory Domain, unjoin it |
| |
| =cut |
| |
| if ($self->ad_get_current_domain()) { |
| if (!$self->ad_unjoin()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to remove computer from Active Directory domain"); |
| return 0; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "computer successfully unjoined from domain, rebooting for change to take effect"); |
| # reboot if unjoin successful |
| if (!$self->reboot()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to reboot after unjoining from domain"); |
| } |
| } |
| |
| |
| =item * |
| |
| Set Administrator account password to known value |
| |
| =cut |
| |
| if (!$self->set_password('Administrator', $WINDOWS_ROOT_PASSWORD)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set Administrator password"); |
| 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 user assigned to this reservation |
| |
| =cut |
| |
| my $deleted_user_accounts = $self->delete_user_accounts(); |
| if (!$deleted_user_accounts) { |
| notify($ERRORS{'DEBUG'}, 0, "unable to delete user accounts, 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 * |
| |
| Enable DHCP on the private and public interfaces |
| |
| =cut |
| |
| if (!$self->enable_dhcp('public')) { |
| notify($ERRORS{'WARNING'}, 0, "failed to enable DHCP on the public interface"); |
| return; |
| } |
| |
| =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 * |
| |
| 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 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 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 ($self->data->get_computer_vmhost_id(0)) { |
| if (!$self->disable_login_screensaver()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to disable login screensaver"); |
| } |
| } |
| |
| =item * |
| |
| Enable audio redirection for RDP sessions |
| |
| =cut |
| |
| if (!$self->enable_rdp_audio()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to enable RDP audio redirection"); |
| } |
| |
| =item * |
| |
| Enable client-compatible color depth for RDP sessions |
| |
| =cut |
| |
| if (!$self->enable_client_compatible_rdp_color_depth()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to enable client-compatible color depth for RDP sessions"); |
| } |
| |
| =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 * |
| |
| 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 user assigned to this reservation if attempt before reboot failed |
| |
| =cut |
| |
| if (!$deleted_user_accounts && !$self->delete_user_accounts()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to delete user accounts after reboot"); |
| 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 * |
| |
| Delete the 'VCL Update Cygwin' scheduled task if it exists. It could conflict with other post_load scripts. |
| |
| =cut |
| |
| $self->delete_scheduled_task('VCL Update Cygwin'); |
| |
| =item * |
| |
| Set the Cygwin SSHD service startup mode to manual |
| |
| =cut |
| |
| if (!$self->set_service_startup_mode('sshd', 'manual') && !$self->set_service_startup_mode('cygsshd', 'manual')) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set sshd service startup mode to manual"); |
| return 0; |
| } |
| |
| =item * |
| |
| Unmount any NFS shares |
| |
| =cut |
| |
| if (!$self->unmount_nfs_shares()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to unmount NFS shares"); |
| return; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "Call to unmount_nfs_shares returned successfully"); |
| } |
| |
| =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 $imagedomain_domaindnsname = $self->data->get_image_domain_dns_name(0); |
| |
| my $node_configuration_directory = $self->get_node_configuration_directory(); |
| |
| notify($ERRORS{'OK'}, 0, "beginning Windows post-load tasks on $computer_node_name"); |
| |
| =item * |
| |
| 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 * |
| |
| Attempt to trigger and fix Cygwin's nodosfilewarning |
| |
| =cut |
| |
| $self->fix_cygwin_nodosfilewarning(); |
| |
| =item * |
| |
| Wait for root to log off |
| |
| =cut |
| |
| if (!$self->wait_for_logoff('root', 2)) { |
| notify($ERRORS{'WARNING'}, 0, "root account never logged off"); |
| |
| 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') && !$self->set_service_startup_mode('cygsshd', '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 * |
| |
| Check the image for user account names known to be bad or easily compromised |
| |
| =cut |
| |
| if (!$self->check_image()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to check the image for user accounts known to be bad"); |
| } |
| |
| =item * |
| |
| Update the public IP address |
| |
| =cut |
| |
| if (!$self->update_public_ip_address()) { |
| my $public_ip_configuration = $self->data->get_management_node_public_ip_configuration(); |
| if ($public_ip_configuration =~ /dhcp/i) { |
| notify($ERRORS{'WARNING'}, 0, "computer should have received a public IP address from DHCP but the address could not be determined, attempting to execute 'ipconfig /renew'"); |
| |
| if (!$self->ipconfig_renew()) { |
| notify($ERRORS{'WARNING'}, 0, "public IP address from DHCP but the address could not be determined, 'ipconfig /renew' failed"); |
| return; |
| } |
| |
| # Try to update the public IP address again |
| if (!$self->update_public_ip_address()) { |
| notify($ERRORS{'WARNING'}, 0, "computer should have received a public IP address from DHCP but the address could not be determined on second attempt after executing 'ipconfig /renew'"); |
| return; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "computer initially failed to obtain a public IP address from DHCP, executed 'ipconfig /renew', public IP address could then be determined"); |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "management node failed to set a static public IP address on the computer"); |
| return; |
| } |
| } |
| |
| =item * |
| |
| Set persistent public default route |
| |
| =cut |
| |
| if (!$self->set_static_default_gateway()) { |
| 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 the "Select a location for the network" prompt |
| |
| =cut |
| |
| $self->disable_set_network_location_prompt(); |
| |
| =item * |
| |
| Check if the RDP port configured on the computer matches the RDP connect method |
| |
| =cut |
| |
| if (!$self->check_rdp_port_configuration()) { |
| return 0; |
| } |
| |
| =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(); |
| $self->{root_password} = $root_random_password; |
| 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 * |
| |
| Install Windows updates saved under tools on the management node |
| |
| =cut |
| |
| if (!$self->install_updates()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run custom post_load scripts"); |
| } |
| |
| =item * |
| |
| Join Active Directory domain if configured for image |
| |
| =cut |
| |
| if ($imagedomain_domaindnsname) { |
| if (!$self->ad_check()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to join Active Directory domain"); |
| return 0; |
| } |
| } |
| elsif ($self->data->get_imagemeta_sethostname(0)) { |
| # Image configured to set hostname |
| if (!$self->set_computer_hostname()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to rename computer"); |
| return 0; |
| } |
| push @{$self->{reboot_required}}, 'computer was renamed'; |
| } |
| |
| =item * |
| |
| Reboot the computer if necessary |
| |
| =cut |
| |
| if ($self->{reboot_required}) { |
| notify($ERRORS{'DEBUG'}, 0, "attempting to reboot computer, reasons why necessary:\n" . join("\n", @{$self->{reboot_required}})); |
| if (!$self->reboot()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to reboot after renaming computer"); |
| } |
| delete $self->{reboot_required}; |
| } |
| |
| =item * |
| |
| Run custom post_load scripts residing in the image |
| |
| =cut |
| |
| my $script_path = '$SYSTEMROOT/vcl_post_load.cmd'; |
| if (!$self->file_exists($script_path)) { |
| notify($ERRORS{'DEBUG'}, 0, "custom post_load script does NOT exist in image: $script_path"); |
| } |
| else { |
| $self->run_script($script_path); |
| } |
| |
| =item * |
| |
| Delete the VCL Post Load' scheduled task if it exists |
| |
| =cut |
| |
| # Make sure the 'VCL Post Load' task is removed up if it exists to avoid conflicts |
| $self->delete_scheduled_task('VCL Post Load'); |
| |
| =item * |
| |
| Call OS.pm::post_load |
| |
| =cut |
| |
| return $self->SUPER::post_load(); |
| |
| =back |
| |
| =cut |
| |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 post_reserve |
| |
| Parameters : none |
| Returns : boolean |
| Description : Runs $SYSTEMROOT/vcl_post_reserve.cmd if it exists in the image. |
| Does not check if the actual script succeeded or not. |
| |
| =cut |
| |
| sub post_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 0; |
| } |
| |
| # Check if custom post_reserve script exists in the image |
| my $script_path = '$SYSTEMROOT/vcl_post_reserve.cmd'; |
| if ($self->file_exists($script_path)) { |
| # If post_reserve script exists, assume it does user or reservation-specific actions |
| # If the user never connects and the reservation times out, there's no way to revert these actions in order to clean the computer for another user |
| # Tag the image as tainted so it is reloaded |
| $self->set_tainted_status('post-reserve scripts residing in the image executed'); |
| |
| # Run the post_reserve script |
| $self->run_script($script_path); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "custom post_reserve script does NOT exist in image: $script_path"); |
| } |
| |
| return $self->SUPER::post_reserve(); |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 post_reservation |
| |
| Parameters : none |
| Returns : boolean |
| Description : Executes $SYSTEMROOT/vcl_post_reservation.cmd if it exists in the |
| image. Does not check if the actual script succeeded or not. |
| |
| =cut |
| |
| sub post_reservation { |
| 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 0; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| # Check if custom post_reservation script exists in image |
| my $script_path = '$SYSTEMROOT/vcl_post_reservation.cmd'; |
| if ($self->file_exists($script_path)) { |
| # Run the post_reservation script |
| $self->run_script($script_path); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "custom post_reservation script does NOT exist in image: $script_path"); |
| } |
| |
| $self->SUPER::post_reservation(); |
| |
| # Check if the computer is joined to any AD domain |
| my $computer_current_domain_name = $self->ad_get_current_domain(); |
| if ($computer_current_domain_name) { |
| $self->ad_delete_computer(); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 pre_reload |
| |
| Parameters : none |
| Returns : true |
| Description : Unjoins the computer from an Active Directory domain if |
| previously joined. This helps avoid orphaned computer objects. |
| |
| =cut |
| |
| sub pre_reload { |
| 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 0; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| # Check if the computer is joined to any AD domain |
| my $computer_current_domain_name = $self->ad_get_current_domain(); |
| if ($computer_current_domain_name) { |
| # check that node is not a domain controller |
| my $check_dc = $self->ad_check_domain_controller(); |
| if (!defined($check_dc) || $check_dc == 0) { |
| # if call to ad_check_domain_controller fails, still attempt to |
| # delete from domain; unusual for node to be a domain controller |
| notify($ERRORS{'DEBUG'}, 0, "attempting to delete computer from domain"); |
| $self->ad_delete_computer(); |
| } |
| elsif ($check_dc == 1) { |
| notify($ERRORS{'DEBUG'}, 0, "computer is a domain controller, not attempting to delete computer from its own domain"); |
| } |
| } |
| |
| return $self->SUPER::pre_reload(); |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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 the reservation users |
| if ($self->delete_user_accounts()) { |
| notify($ERRORS{'OK'}, 0, "users deleted from $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete users from $computer_node_name"); |
| return 0; |
| } |
| |
| if (!$self->unmount_nfs_shares()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to unmount nfs shares"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "successfully unmounted any NFS shares"); |
| } |
| |
| 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 $computer_node_name = $self->data->get_computer_node_name(); |
| my $system32_path = $self->get_system32_path(); |
| |
| if ($self->process_connect_methods("", 1)) { |
| notify($ERRORS{'OK'}, 0, "processed connection methods on $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to process connection methods on $computer_node_name"); |
| return; |
| } |
| |
| 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 $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 $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; |
| } |
| |
| # If ~ is passed as the directory path, skip directory creation attempt |
| # The command will create a /root/~ directory since the path is enclosed in quotes |
| return 1 if $path eq '~'; |
| |
| 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) = $self->execute($mkdir_command); |
| |
| 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 $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, "file 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 $path_unix_directory = parent_directory_path($path_unix); |
| my ($path_unix_pattern) = $path_unix =~ /\/?([^\/]+)$/; |
| |
| 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"; |
| |
| if ($path_unix_pattern =~ /\*/) { |
| $command .= " ; echo ---"; |
| $command .= " ; echo Calling \\\"rm.exe -rfv $path_unix_directory/.$path_unix_pattern\\\" to to delete file..."; |
| $command .= " ; /usr/bin/rm.exe -rfv $path_unix_directory/.$path_unix_pattern 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) = $self->execute($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; |
| } |
| |
| # 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) = $self->execute($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 : $base_directory, $regex_pattern, $max_depth (optional) |
| Returns : boolean |
| Description : Deletes all files found under the base directory |
| |
| =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 ($base_directory, $regex_pattern, $max_depth, $show_deleted) = @_; |
| |
| # Make sure base directory and pattern were specified |
| if (!($base_directory && $regex_pattern)) { |
| notify($ERRORS{'WARNING'}, 0, "base directory and pattern must be specified as arguments"); |
| return; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to delete files on $computer_name, base directory: '$base_directory', pattern: '$regex_pattern', max depth: " . ($max_depth ? $max_depth : 'unlimited')); |
| |
| # Check if the path begins with an environment variable |
| my ($base_directory_variable, $remainder) = $base_directory =~ /(\$[^\/\\]*)(.*)/g; |
| if ($base_directory_variable) { |
| my $cygpath_command = "/bin/cygpath.exe \"$base_directory_variable\""; |
| my ($cygpath_exit_status, $cygpath_output) = $self->execute($cygpath_command, 0); |
| if (!defined($cygpath_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to determine if $base_directory_variable environment variable is set on $computer_name: $cygpath_command"); |
| return; |
| } |
| elsif (grep(/cygpath:/, @$cygpath_output)) { |
| notify($ERRORS{'DEBUG'}, 0, "files not deleted because $base_directory_variable environment variable is not set on $computer_name"); |
| return; |
| } |
| elsif (!grep(/\w/, @$cygpath_output)) { |
| notify($ERRORS{'DEBUG'}, 0, "files not deleted because $base_directory_variable environment variable is empty on $computer_name"); |
| return; |
| } |
| |
| my ($base_directory_variable_value) = grep(/\w/, @$cygpath_output); |
| $remainder = '' unless defined($remainder); |
| |
| my $base_directory_new = "$base_directory_variable_value/$remainder"; |
| $base_directory_new =~ s/[\\\/]+/\//g; |
| |
| notify($ERRORS{'DEBUG'}, 0, "$base_directory_variable environment variable is set on $computer_name: '$base_directory' --> '$base_directory_new'"); |
| $base_directory = $base_directory_new; |
| } |
| |
| # Remove trailing slashes from base directory |
| $base_directory =~ s/[\/\\]*$/\//; |
| |
| # Assemble command |
| # Use find to locate all the files under the base directory matching the pattern specified |
| my $command = "/bin/find.exe \"$base_directory\""; |
| $command .= " -mindepth 1"; |
| $command .= " -maxdepth $max_depth" if $max_depth; |
| $command .= " -iregex \"$regex_pattern\""; |
| $command .= " -exec chown -R root {} \\;"; |
| $command .= " -exec chmod -R 777 {} \\;"; |
| $command .= " -exec rm -rvf {} \\;"; |
| |
| my ($exit_status, $output) = $self->execute($command, 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to delete files under $base_directory matching pattern $regex_pattern, command: $command"); |
| return; |
| } |
| elsif ($base_directory_variable && 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, command: '$command'"); |
| return 1; |
| } |
| elsif (grep(/(^Usage:)/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete files under $base_directory matching pattern $regex_pattern\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| my @deleted = grep(/removed /, @$output); |
| my @not_deleted = grep(/cannot remove/, @$output); |
| |
| my $message; |
| $message .= "attempted to delete files:\n"; |
| $message .= "base directory: $base_directory\n"; |
| $message .= "regular expression pattern: $regex_pattern\n"; |
| $message .= "files and directories deleted: " . scalar(@deleted) . "\n"; |
| $message .= "files and directories NOT deleted: " . scalar(@not_deleted) . "\n"; |
| $message .= "deleted:\n" . join("\n", @deleted) . "\n" if ($show_deleted && @deleted); |
| $message .= "NOT deleted:\n" . join("\n", @not_deleted) if (@not_deleted); |
| notify($ERRORS{'OK'}, 0, $message) if (@deleted || @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 $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 \\\"$path_dos\\\"\""; |
| my ($dir_exit_status, $dir_output) = $self->execute($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; |
| } |
| |
| # Checking if directory exists, no wildcard: (directory exists) |
| # $ cmd.exe /c "dir /a C:\test" |
| # Volume in drive C has no label. |
| # Volume Serial Number is 4C9E-6C37 |
| # |
| # Directory of C:\test |
| # |
| #05/16/2012 01:19 PM <DIR> . |
| #05/16/2012 01:19 PM <DIR> .. |
| # 0 File(s) 0 bytes |
| # 2 Dir(s) 17,999,642,624 bytes free |
| |
| # Checking if file or directory exists with wildcard: (file exists) |
| # $ cmd.exe /c "dir /a C:\te*" |
| # Volume in drive C has no label. |
| # Volume Serial Number is 4C9E-6C37 |
| # |
| # Directory of C:\ |
| # |
| #05/16/2012 01:19 PM <DIR> test |
| # 0 File(s) 0 bytes |
| # 1 Dir(s) 17,999,642,624 bytes free |
| |
| # Checking if file exists with wildcard: (file does not exist) |
| # $ cmd.exe /c "dir /a C:\test\*" |
| # Volume in drive C has no label. |
| # Volume Serial Number is 4C9E-6C37 |
| # |
| # Directory of C:\test |
| # |
| #05/16/2012 01:19 PM <DIR> . |
| #05/16/2012 01:19 PM <DIR> .. |
| # 0 File(s) 0 bytes |
| # 2 Dir(s) 17,999,642,624 bytes free |
| |
| 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; |
| } |
| elsif ($path =~ /\*/ && grep(/\s0 File/, @$dir_output) && grep(/\s2 Dir/, @$dir_output)) { |
| #notify($ERRORS{'DEBUG'}, 0, "file does NOT exist on $computer_node_name: '$path', exit status: $dir_exit_status, command: '$dir_command', output:\n" . join("\n", @$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; |
| } |
| |
| # Run chown |
| my ($chown_exit_status, $chown_output) = $self->execute("/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 $computer_node_name = $self->data->get_computer_node_name(); |
| my $system32_path = $self->get_system32_path() || return; |
| |
| my ($exit_status, $output) = $self->execute("$system32_path/qwinsta.exe", 1, 60); |
| if ($exit_status > 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe on $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (!defined($exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run qwinsta.exe command on $computer_node_name"); |
| return; |
| } |
| |
| # Find lines with the state = Active or Disc |
| # Disc will occur if the user disconnected the RDP session but didn't logoff |
| my @connection_lines = grep(/(Active)/, @$output); |
| 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) { |
| $connection_line =~ s/(^\s+|\s+$)//g; |
| 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 $session_identifier: '$connection_line'"); |
| |
| #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) = $self->execute("$system32_path/logoff.exe $session_identifier /V"); |
| if (!defined($logoff_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to log off session: $session_identifier"); |
| } |
| elsif ($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" . join("\n", @$logoff_output)); |
| } |
| } |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 user_exists |
| |
| Parameters : $username (optional) |
| Returns : boolean |
| Description : Executes 'net user <username>' to determine whether or not a |
| local user exists on the Windows computer. |
| |
| =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 $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 $command = "$system32_path/net.exe user \"$username\""; |
| my ($exit_status, $output) = $self->execute($command, 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to determine if user exists on $computer_node_name: $username"); |
| return; |
| } |
| |
| # Expected output if user exists: |
| # $ net user administrator |
| # User name Administrator |
| # Full Name |
| # Comment Built-in account for administering the computer/domain |
| # ... |
| # The command completed successfully. |
| |
| # Expected output if user does NOT exist: |
| # $ net user foo |
| # The user name could not be found. |
| # |
| # More help is available by typing NET HELPMSG 2221. |
| |
| # Note: exit status may not be reliable, see VCL-1054 |
| if ($exit_status == 2 || grep(/not.*found/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "user '$username' does NOT exist on $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return 0; |
| } |
| elsif (my ($username_line) = grep(/User name[\s\t]+$username[\s\t]*$/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "user '$username' exists on $computer_node_name, found matching line in '$command' output:\n$username_line"); |
| return 1; |
| } |
| elsif ($exit_status == 0) { |
| notify($ERRORS{'WARNING'}, 0, "returning 1 but unable to reliable determine if user '$username' exists on $computer_node_name, exit status is $exit_status but output does not contain a 'User name <space...> $username' line:\n" . join("\n", @$output)); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to determine if user '$username' exists on $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 create_user |
| |
| Parameters : hash reference |
| Returns : boolean |
| Description : Creates a user on the computer. The argument must be a hash |
| reference containing the following keys: |
| * username |
| * password |
| * root_access |
| |
| =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 $computer_node_name = $self->data->get_computer_node_name(); |
| my $system32_path = $self->get_system32_path() || return; |
| my $domain_dns_name = $self->data->get_image_domain_dns_name(); |
| |
| my $user_parameters = shift; |
| if (!$user_parameters) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create user, user parameters argument was not provided"); |
| return; |
| } |
| elsif (!ref($user_parameters) || ref($user_parameters) ne 'HASH') { |
| notify($ERRORS{'WARNING'}, 0, "unable to create user, argument provided is not a hash reference"); |
| return; |
| } |
| |
| my $username = $user_parameters->{username}; |
| if (!defined($username)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create user on $computer_node_name, argument hash does not contain a 'username' key:\n" . format_data($user_parameters)); |
| return; |
| } |
| |
| my $root_access = $user_parameters->{root_access}; |
| if (!defined($root_access)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create user on $computer_node_name, argument hash does not contain a 'root_access' key:\n" . format_data($user_parameters)); |
| return; |
| } |
| |
| my $password = $user_parameters->{password}; |
| |
| # Check if image is configured for Active Directory and a password should NOT be set |
| # OS.pm::add_user_accounts should have already called should_set_user_password which checks if AD is configured and if user exists in AD |
| # If user exists in AD, password argument should not be set |
| # If for some reason it is set, add local user account |
| if ($domain_dns_name && !$password) { |
| $username .= "@" . $domain_dns_name; |
| } |
| else { |
| if (!defined($password) && !$domain_dns_name) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create user on $computer_node_name, argument hash does not contain a 'password' key:\n" . format_data($user_parameters)); |
| return; |
| } |
| |
| # Not an AD image, check if user already exists |
| if (!$self->user_exists($username)) { |
| # Attempt to create the user account |
| my $add_user_command = "$system32_path/net.exe user \"$username\" \"$password\" /ADD /EXPIRES:NEVER /COMMENT:\"Account created by VCL\""; |
| my ($add_user_exit_status, $add_user_output) = $self->execute($add_user_command, 0); |
| if (!defined($add_user_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command create user on $computer_node_name: $username"); |
| return; |
| } |
| elsif ($add_user_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "created user on $computer_node_name: $username, password: $password"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to create user on $computer_node_name: $username, exit status: $add_user_exit_status, command: '$add_user_command', output:\n" . join("\n", @$add_user_output)); |
| return 0; |
| } |
| } |
| else { |
| # Account already exists on machine, set password |
| if (!$self->set_password($username, $password)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set password of existing user on $computer_node_name: $username"); |
| return; |
| } |
| } |
| } |
| |
| if (!$self->add_user_to_group($username, "Remote Desktop Users")) { |
| return; |
| } |
| |
| # Add user to Administrators group if necessary |
| if ($root_access) { |
| if (!$self->add_user_to_group($username, "Administrators")) { |
| return; |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "existing user NOT added to Administrators group on $computer_node_name: $username"); |
| } |
| |
| return 1; |
| } ## end sub create_user |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 add_user_to_group |
| |
| Parameters : $username, $group |
| Returns : boolean |
| Description : Adds a user to a group on the computer. |
| |
| =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 $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) = $self->execute($localgroup_user_command); |
| if (defined($localgroup_user_exit_status) && $localgroup_user_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "added user to '$group' group on $computer_node_name: $username"); |
| } |
| 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 is already a member of '$group' group on $computer_node_name: $username"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to add user to '$group' group on $computer_node_name: $username, 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 to '$group' group on $computer_node_name: $username, exit status: $localgroup_user_exit_status, output:\n@{$localgroup_user_output}"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to add user to '$group' group on $computer_node_name: $username"); |
| return; |
| } |
| |
| return 1; |
| } ## end sub add_user_to_group |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 remove_user_from_group |
| |
| Parameters : $username, $group |
| Returns : boolean |
| Description : Removes a user from a local group on the computer. If an AD user |
| account and local account exist with the same name, both will be |
| removed. |
| |
| =cut |
| |
| sub remove_user_from_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 $computer_name = $self->data->get_computer_node_name(); |
| my $system32_path = $self->get_system32_path() || return; |
| |
| my $username = shift; |
| if (!defined($username)) { |
| notify($ERRORS{'WARNING'}, 0, "username argument was not supplied"); |
| return; |
| } |
| |
| my $group = shift; |
| if (!defined($group)) { |
| notify($ERRORS{'WARNING'}, 0, "local group name argument was not supplied"); |
| return; |
| } |
| |
| my @group_members = $self->get_group_members($group); |
| if (!@group_members) { |
| notify($ERRORS{'DEBUG'}, 0, "$username not removed from $group local group on $computer_name, group is either empty or membership could not be retrieved"); |
| return 1; |
| } |
| |
| my @matching_members = grep(/(^|\\)$username$/i, @group_members); |
| if (!@matching_members) { |
| notify($ERRORS{'OK'}, 0, "$username is not a member of $group local group on $computer_name"); |
| return 1; |
| } |
| for my $matching_member (@matching_members) { |
| # Escape backslashes in domain usernames |
| $matching_member =~ s/\\/\\\\/; |
| my $command = "$system32_path/net.exe localgroup \"$group\" \"$matching_member\" /DELETE"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to remove $matching_member from $group local group on $computer_name: $command"); |
| return; |
| } |
| elsif (grep(/no such/, @$output)) { |
| # There is no such global user or group: admin. |
| notify($ERRORS{'OK'}, 0, "$matching_member is not a member of $group local group on $computer_name"); |
| return 1; |
| } |
| elsif ($exit_status ne '0') { |
| notify($ERRORS{'WARNING'}, 0, "failed to remove $matching_member from $group local group on $computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output)); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "removed $matching_member from $group local group on $computer_name"); |
| } |
| } |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_group_members |
| |
| Parameters : $group_name |
| Returns : array |
| Description : Retrieves the names of users who are members of a local Windows |
| group. |
| |
| =cut |
| |
| sub get_group_members { |
| 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_name = $self->data->get_computer_node_name(); |
| my $system32_path = $self->get_system32_path() || return; |
| |
| my $group = shift; |
| if (!defined($group)) { |
| notify($ERRORS{'WARNING'}, 0, "local group name argument was not supplied"); |
| return; |
| } |
| |
| my $command = "$system32_path/net.exe localgroup \"$group\""; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to retrieve members of $group local group on $computer_name: $command"); |
| return; |
| } |
| elsif ($exit_status ne '0') { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve members of $group local group on $computer_name, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output)); |
| return 0; |
| } |
| |
| # Alias name Remote Desktop Users |
| # Comment Members in this group are granted the right to logon remotely |
| # |
| # Members |
| # |
| # ------------------------------------------------------------------------------- |
| # AD\admin |
| # admin |
| # AD\domainuser |
| # admin |
| # tester1 |
| # ... |
| # test100 |
| # The command completed successfully. |
| my @group_members; |
| my $separator_line_found = 0; |
| for my $line (@$output) { |
| if (!$separator_line_found) { |
| if ($line =~ /---/) { |
| $separator_line_found = 1; |
| } |
| next; |
| } |
| elsif ($line =~ /The command/) { |
| last; |
| } |
| push @group_members, $line; |
| } |
| notify($ERRORS{'OK'}, 0, "retrieve members of $group local group on $computer_name: " . join(", ", @group_members)); |
| return @group_members; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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 $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) = $self->execute($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"); |
| |
| # Could be an AD domain user, make sure user is removed from groups |
| $self->remove_user_from_group($username, 'Administrators'); |
| $self->remove_user_from_group($username, 'Remote Desktop Users'); |
| } |
| 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; |
| } |
| |
| # IMPORTANT: be sure to test passwords containing the following: |
| # $! |
| # \ (single backslash) |
| # ' |
| # " |
| # ~ |
| # ` |
| # Special bash/Linux variables: = $0, $1, $#, $*, $@, $-, $!, $_, $?, $$ |
| # my $test_password = '$0, $1 $# $* $@ $- $! $_ $? $$\ !@#$%^&*()_+-={}[]":;<>?/.,`~' . "'"; |
| |
| 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; |
| my $user_password_only = 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 |
| my $password_escaped = _escape_password($password); |
| my $set_password_command = "$system32_path/net.exe user $username \"$password_escaped\""; |
| notify($ERRORS{'DEBUG'}, 0, "setting password of $username to '$password' on $computer_node_name, command:\n$set_password_command"); |
| my ($set_password_exit_status, $set_password_output) = $self->execute($set_password_command); |
| if ($set_password_exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "password changed to '$password' for user '$username' on $computer_node_name, command: '$set_password_command', output:\n" . join("\n", @$set_password_output)); |
| } |
| elsif (defined $set_password_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to change password to '$password' (escaped: '$password_escaped') for user '$username' on $computer_node_name, exit status: $set_password_exit_status, command: '$set_password_command', output:\n" . join("\n", @$set_password_output)); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to change password to '$password' for user '$username' on $computer_node_name"); |
| return 0; |
| } |
| return 1 if $user_password_only; |
| |
| # Get the list of services |
| my @services = $self->get_services_using_login_id($username); |
| if ($username eq 'root' && !@services) { |
| @services = ('sshd', 'cygsshd'); |
| } |
| |
| my $success = 0; |
| for my $service (@services) { |
| notify($ERRORS{'DEBUG'}, 0, "$service service is configured to run as $username, updating service credentials"); |
| if ($self->set_service_credentials($service, $username, $password)) { |
| $success = 1; |
| last; |
| } |
| } |
| if(!$success) { |
| my $servicelist = join(', ', @services); |
| notify($ERRORS{'WARNING'}, 0, "failed to set credentials to $username ($password) for any of $servicelist"); |
| } |
| |
| # Get the scheduled tasks - check if any are configured to run as the user |
| my $scheduled_task_info = $self->get_scheduled_task_info(); |
| for my $task_name (keys %$scheduled_task_info) { |
| my $run_as_user = $scheduled_task_info->{$task_name}{'Run As User'}; |
| if ($run_as_user && $run_as_user =~ /^(.+\\)?$username$/i) { |
| notify($ERRORS{'DEBUG'}, 0, "password needs to be updated for scheduled task '$task_name' set to run as user '$run_as_user'"); |
| |
| # Attempt to update the scheduled task credentials |
| # Don't return false if this fails - not extremely vital |
| if (!$self->set_scheduled_task_credentials($task_name, $username, $password)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set '$task_name' scheduled task credentials to $username ($password)"); |
| } |
| } |
| } |
| |
| notify($ERRORS{'OK'}, 0, "changed password for user: $username"); |
| return 1; |
| } ## end sub set_password |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 should_set_user_password |
| |
| Parameters : $user_id |
| Returns : boolean |
| Description : Determines if a random password should be set for a user. This is |
| the default behavior. A random password will not be set if: |
| * The image is configured for Active Directory |
| * The user exists in the domain |
| |
| =cut |
| |
| sub should_set_user_password { |
| my $self = shift; |
| if (ref($self) !~ /VCL::Module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my ($user_id) = shift; |
| if (!$user_id) { |
| notify($ERRORS{'WARNING'}, 0, "user ID argument was not supplied"); |
| return; |
| } |
| |
| if (defined($self->{should_set_user_password}{$user_id})) { |
| return $self->{should_set_user_password}{$user_id}; |
| } |
| |
| # Check if image is configured for Active Directory |
| my $domain_dns_name = $self->data->get_image_domain_dns_name(); |
| if ($domain_dns_name) { |
| my $user_info = get_user_info($user_id); |
| if (!$user_info) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if user password should be set, user info could not be retrieved for user ID $user_id"); |
| return; |
| } |
| |
| my $username = $user_info->{unityid}; |
| if ($self->ad_user_exists($username)) { |
| $self->{should_set_user_password}{$user_id} = 0; |
| notify($ERRORS{'DEBUG'}, 0, "verified user exists in $domain_dns_name Active Directory domain: $username (ID: $user_id), random password will NOT be set for user"); |
| } |
| else { |
| $self->{should_set_user_password}{$user_id} = 1; |
| notify($ERRORS{'WARNING'}, 0, "could not verify user exists in $domain_dns_name Active Directory domain: $username (ID: $user_id), random password will be set"); |
| } |
| } |
| else { |
| # Not configured for Active Directory, random password should be set |
| $self->{should_set_user_password}{$user_id} = 1; |
| } |
| |
| return $self->{should_set_user_password}{$user_id}; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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 $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) = $self->execute("$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_user |
| |
| Parameters : $username |
| Returns : boolean |
| Description : Disables the user account specified by the argument. |
| |
| =cut |
| |
| sub disable_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 $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Attempt to get the username from the arguments |
| my $username = shift; |
| if (!defined($username)) { |
| notify($ERRORS{'WARNING'}, 0, "username argument was not supplied"); |
| return; |
| } |
| |
| # Attempt to enable the user account (set ACTIVE=NO) |
| notify($ERRORS{'DEBUG'}, 0, "disbling user $username on $computer_node_name"); |
| my ($exit_status, $output) = $self->execute("net user $username /ACTIVE:NO"); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to disable user $username on $computer_node_name"); |
| return; |
| } |
| elsif (grep(/ successfully/, @$output)) { |
| notify($ERRORS{'OK'}, 0, "user $username disabled on $computer_node_name"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to disable user $username on $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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 $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) = $self->execute($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 $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) = $self->execute($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 $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 $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_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 $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) = $self->execute($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), $suppress_key_missing_error (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 $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; |
| |
| my $suppress_key_missing_error = 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\""; |
| } |
| |
| # Ignore error lines, it will throw off parsing |
| $command .= " 2>/dev/null"; |
| |
| # Run reg.exe QUERY |
| my ($exit_status, $output) = $self->execute($command, 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to query registry key: $key_argument"); |
| return; |
| } |
| elsif (grep(/unable to find the specified registry/, @$output)) { |
| my $message = "registry key or value does not exist:\nkey: '$key_argument'\n"; |
| $message .= "value: '$value_argument'\n" if defined($value_argument); |
| $message .= "command: '$command'\n"; |
| $message .= "exit status: $exit_status\n"; |
| $message .= "output:\n" . join("\n", @{$output}); |
| notify($ERRORS{'WARNING'}, 0, $message) unless $suppress_key_missing_error; |
| return; |
| } |
| elsif (!grep(/REG.EXE VERSION|HKEY/, @$output)) { |
| my $message = "failed to query registry:\nkey: '$key_argument'\n"; |
| $message .= "value: '$value_argument'\n" if defined($value_argument); |
| $message .= "command: '$command'\n"; |
| $message .= "exit status: $exit_status\n"; |
| $message .= "output:\n" . join("\n", @{$output}); |
| notify($ERRORS{'WARNING'}, 0, $message); |
| return; |
| } |
| |
| # 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; |
| |
| if ($type =~ /binary/i) { |
| #notify($ERRORS{'DEBUG'}, 0, "ignoring $type data, key: $key, value: $value"); |
| next; |
| } |
| |
| $value = '(Default)' if $value =~ /NO NAME/; |
| |
| $data = $self->reg_query_convert_data($type, $data); |
| |
| if (!defined($key) || !defined($value) || !defined($data) || !defined($type)) { |
| my $message = "some registry data is undefined:\n"; |
| $message .= "line: '$line'\n"; |
| $message .= "key: '" . ($key || 'undefined') . "'\n"; |
| $message .= "value: '" . ($value || 'undefined') . "'\n"; |
| $message .= "data: '" . ($data || 'undefined') . "'\n"; |
| $message .= "type: '" . ($type || 'undefined') . "'"; |
| notify($ERRORS{'WARNING'}, 0, $message); |
| } |
| else { |
| $registry_hash{$key}{$value}{type} = $type; |
| $registry_hash{$key}{$value} = $data; |
| } |
| } |
| elsif ($line =~ /^!/) { |
| # Ignore lines beginning with '!' |
| next; |
| } |
| elsif ($line =~ /^Error:/) { |
| # Ignore lines beginning with 'Error:' -- this is common and probably not a problem |
| # Example: |
| # Error: Access is denied in the key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\MRxDAV\EncryptedDirectories |
| next; |
| } |
| else { |
| # TODO: add support for registry values that span multiple lines. Example: |
| # Comments REG_SZ This security update is for Microsoft .NET Framework 3.5 SP1. |
| # If you later install a more recent service pack, this security update will be uninstalled automatically. |
| # For more information, visit http://support.microsoft.com/kb/2416473. |
| #notify($ERRORS{'WARNING'}, 0, "unexpected output in line: '" . string_to_ascii($line) . "'\ncommand: '$command'"); |
| } |
| } |
| |
| my $message = "retrieved registry data:\n"; |
| $message .= "key: '$key_argument'\n"; |
| $message .= "value: '$value_argument'\n" if defined($value_argument); |
| $message .= "keys found: " . scalar(keys %registry_hash); |
| notify($ERRORS{'DEBUG'}, 0, $message); |
| |
| return \%registry_hash; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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 =~ /dword/i) { |
| if ($data =~ /^[a-fA-F0-9]+$/) { |
| $data = "0x$data"; |
| } |
| |
| # Make sure a valid hex value was returned |
| if ($data !~ /^0x[a-fA-F0-9]+$/) { |
| notify($ERRORS{'WARNING'}, 0, "invalid $type value: '$data'"); |
| return; |
| } |
| |
| # Convert the hex value to decimal |
| $data = hex($data); |
| } |
| elsif ($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; |
| } |
| elsif ($type =~ /hex/) { |
| # Split data into an array, data values are separated in the output by ',00' |
| my @hex_values = split(/,00,?/, $data); |
| my $string; |
| for my $hex_value (@hex_values) { |
| my $decimal_value = hex $hex_value; |
| $string .= pack("C*", $decimal_value); |
| } |
| return $string; |
| } |
| |
| return $data; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reg_add |
| |
| Parameters : $registry_key, $registry_value, $registry_type, $registry_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 $system32_path = $self->get_system32_path() || return; |
| |
| # Get the arguments |
| my ($registry_key, $registry_value, $registry_type, $registry_data) = @_; |
| if (!defined($registry_key)) { |
| notify($ERRORS{'WARNING'}, 0, "registry key argument was not supplied"); |
| return; |
| } |
| |
| # Replace forward slashes with backslashes in registry key |
| $registry_key =~ s/\//\\\\/g; |
| |
| if (defined($registry_value)) { |
| if (!defined($registry_type)) { |
| notify($ERRORS{'WARNING'}, 0, "registry value argument was supplied but type argument was not"); |
| return; |
| } |
| |
| my $valid_types = 'REG_SZ|REG_MULTI_SZ|REG_DWORD_BIG_ENDIAN|REG_DWORD|REG_BINARY|REG_DWORD_LITTLE_ENDIAN|REG_NONE|REG_EXPAND_SZ'; |
| if ($registry_type !~ /^($valid_types)$/) { |
| notify($ERRORS{'WARNING'}, 0, "invalid registry type was specified: '$registry_type', the following types are supported:\n" . join("\n", sort split(/\|/, $valid_types))); |
| return; |
| } |
| |
| if (!defined($registry_data)) { |
| notify($ERRORS{'WARNING'}, 0, "registry value argument was supplied but data argument was not"); |
| return; |
| } |
| } |
| |
| # Assemble the reg.exe ADD command |
| my $add_registry_command = "$system32_path/reg.exe ADD \"$registry_key\""; |
| if ($registry_value) { |
| if ($registry_value =~ /^default$/i) { |
| $add_registry_command .= " /ve"; |
| } |
| else { |
| $add_registry_command .= " /v \"$registry_value\""; |
| } |
| $add_registry_command .= " /t $registry_type /d \"$registry_data\""; |
| } |
| $add_registry_command .= " /f"; |
| |
| my ($add_registry_exit_status, $add_registry_output) = $self->execute($add_registry_command, 1); |
| if (!defined($add_registry_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to add registry key: $registry_key"); |
| return; |
| } |
| elsif ($add_registry_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "added registry key, command: $add_registry_command, output:\n" . join("\n", @$add_registry_output)); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to add registry key: $registry_key, exit status: $add_registry_exit_status, command:\n$add_registry_command\noutput:\n" . join("\n", @$add_registry_output)); |
| return 0; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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 $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) = $self->execute($delete_registry_command, 0); |
| if (defined($delete_registry_exit_status) && $delete_registry_exit_status == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "deleted registry key: $registry_key, value: $registry_value, output:\n" . join("\n", @$delete_registry_output)); |
| } |
| elsif ($delete_registry_output && grep(/unable to find/i, @$delete_registry_output)) { |
| # Error: The system was unable to find the specified registry key or value |
| notify($ERRORS{'DEBUG'}, 0, "registry key does NOT exist: $registry_key"); |
| } |
| elsif ($delete_registry_exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete registry key: $registry_key, value: $registry_value, exit status: $delete_registry_exit_status, output:\n@{$delete_registry_output}"); |
| return; |
| } |
| 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 $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) = $self->execute($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 $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_dos($registry_file_path); |
| |
| # Replace forward slashes with backslashes in registry key |
| $root_key =~ s/\//\\\\/g; |
| |
| # Run reg.exe EXPORT |
| my $command .= "cmd.exe /c \"del /Q \\\"$registry_file_path.tmp\\\" 2>NUL & $system32_path/reg.exe EXPORT $root_key \\\"$registry_file_path.tmp\\\" && type \\\"$registry_file_path.tmp\\\" > \\\"$registry_file_path\\\" && del /Q \\\"$registry_file_path.tmp\\\"\""; |
| my ($exit_status, $output) = $self->execute($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 $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) = $self->execute($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 $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) = $self->execute($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 $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) = $self->execute($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_key |
| |
| 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 $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) = $self->execute($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 set_scheduled_task_credentials |
| |
| Parameters : $task_name, $username, $password |
| Returns : boolean |
| Description : Sets the credentials under which a scheduled task runs. |
| |
| =cut |
| |
| sub set_scheduled_task_credentials { |
| my $self = shift; |
| if (ref($self) !~ /windows/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my ($task_name, $username, $password) = @_; |
| if (!defined($task_name) || !defined($username) || !defined($password)) { |
| notify($ERRORS{'WARNING'}, 0, "scheduled task name, username, and password arguments were not supplied"); |
| return; |
| } |
| |
| my $system32_path = $self->get_system32_path() || return; |
| |
| my $password_escaped = _escape_password($password); |
| my $command = "$system32_path/schtasks.exe /Change /RU \"$username\" /RP \"$password_escaped\" /TN \"$task_name\""; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to change password for scheduled task: $task_name"); |
| return; |
| } |
| elsif (grep (/^SUCCESS:/, @$output)) { |
| notify($ERRORS{'OK'}, 0, "changed password for scheduled task: $task_name"); |
| return 1; |
| } |
| elsif (grep (/The parameter is incorrect/, @$output)) { |
| if ($task_name =~ /{/) { |
| # Ignore task such as: \User_Feed_Synchronization-{88DE35B9-C115-4DE3-AB5E-B9D2C4A2DB66} |
| # This one always fails and is not important |
| notify($ERRORS{'DEBUG'}, 0, "unable to change password for scheduled task '$task_name' due to Windows bug\ncommand: '$command'\noutput:\n" . join("\n", @$output)); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unable to change password for scheduled task '$task_name' due to Windows bug\ncommand: '$command'\noutput:\n" . join("\n", @$output)); |
| # Don't return false - There is a bug in Windows 7 |
| # If a scheduled task is created using the GUI using a schedule the password cannot be set via schtasks.exe |
| # schtasks.exe displays: ERROR: The parameter is incorrect. |
| # If the same task is changed to run on an event such as logon it works |
| } |
| return 1; |
| } |
| elsif (grep (/^ERROR:/, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to change password for scheduled task: $task_name, command:\n$command\noutput:\n" . join("\n", @$output)); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned while attempting to change password for scheduled task: $task_name, command:\n$command\noutput:\n" . join("\n", @$output)); |
| return 0; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_scheduled_task |
| |
| Parameters : |
| 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 $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) = $self->execute($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 : $task_name, $task_command, $task_user, $task_password |
| 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 $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 $password_escaped = _escape_password($task_password); |
| my $command = "$system32_path/schtasks.exe /Create /RU \"$task_user\" /RP \"$password_escaped\" /RL HIGHEST /SC ONSTART /TN \"$task_name\" /TR \"$task_command\""; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute ssh command created scheduled task '$task_name' on $computer_node_name"); |
| return; |
| } |
| elsif (grep(/password is incorrect/, @$output)) { |
| # ERROR: The user name or password is incorrect. |
| notify($ERRORS{'WARNING'}, 0, "failed to create scheduled task '$task_name' on $computer_node_name\n" . |
| "username : '$task_user'\n" . |
| "password : '$task_password'\n" . |
| "escaped password : '$password_escaped'\n" . |
| "command:\n$command\n" . |
| "output:\n" . join("\n", @$output) |
| ); |
| return 0; |
| } |
| elsif ($exit_status != 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create scheduled task '$task_name' on $computer_node_name, exit status: $exit_status, command: '$command', output:\n@$output"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "created scheduled task '$task_name' on $computer_node_name"); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 create_update_cygwin_startup_scheduled_task |
| |
| Parameters : none |
| Returns : boolean |
| Description : Creates a scheduled task that runs on startup named 'VCL Update |
| Cygwin' which runs update_cygwin.cmd as root. |
| |
| =cut |
| |
| sub create_update_cygwin_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; |
| } |
| |
| # Avoid doing this more than once |
| if ($self->{created_update_cygwin_startup_scheduled_task}) { |
| return 1; |
| } |
| |
| my $request_state = $self->data->get_request_state_name(); |
| my $node_configuration_directory = $self->get_node_configuration_directory(); |
| |
| my $root_password; |
| if ($request_state =~ /(image|checkpoint)/) { |
| $root_password = $WINDOWS_ROOT_PASSWORD; |
| } |
| else { |
| if ($self->{root_password}) { |
| $root_password = $self->{root_password}; |
| } |
| else { |
| $root_password = getpw(); |
| $self->{root_password} = $root_password; |
| } |
| } |
| |
| if (!$self->set_password('root', $root_password)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create startup scheduled task to update Cygwin, failed to set root password"); |
| return; |
| } |
| |
| # Make sure the 'VCL Post Load' task doesn't exist or they will conflict |
| $self->delete_scheduled_task('VCL Post Load'); |
| |
| # Copy the current version of update_cygwin.cmd to the computer |
| $self->copy_file_to("$SOURCE_CONFIGURATION_DIRECTORY/Scripts/update_cygwin.cmd", "$node_configuration_directory/Scripts/update_cygwin.cmd"); |
| |
| # Create a scheduled task to run post_load.cmd when the image boots |
| my $task_command = "$node_configuration_directory/Scripts/update_cygwin.cmd >> $node_configuration_directory/Logs/update_cygwin.log"; |
| if ($self->create_startup_scheduled_task('VCL Update Cygwin', $task_command, 'root', $root_password)) { |
| $self->{created_update_cygwin_startup_scheduled_task} = 1; |
| return 1; |
| } |
| else { |
| return 0; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =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 $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 $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 $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) = $self->execute($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 : $total_wait_seconds, $attempt_delay_seconds, $attempt_limit, $pre_configure |
| Returns : boolean |
| 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; |
| } |
| |
| # Check if an arguments were supplied |
| |
| # Attempt to get the total number of seconds to wait from the arguments |
| my $total_wait_seconds = shift; |
| if (!defined($total_wait_seconds) || $total_wait_seconds !~ /^\d+$/) { |
| $total_wait_seconds = 300; |
| } |
| |
| # Seconds to wait in between loop attempts |
| my $attempt_delay_seconds = shift; |
| if (!defined($attempt_delay_seconds) || $attempt_delay_seconds !~ /^\d+$/) { |
| $attempt_delay_seconds = 15; |
| } |
| |
| # Number of power reset attempts to make if reboot fails |
| my $attempt_limit = shift; |
| if (!defined($attempt_limit) || $attempt_limit !~ /^\d+$/) { |
| $attempt_limit = 2; |
| } |
| |
| my $pre_configure = shift; |
| $pre_configure = 1 unless defined $pre_configure; |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| my $system32_path = $self->get_system32_path(); |
| |
| 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 ($system32_path && $self->wait_for_ssh(0)) { |
| # Perform pre-reboot configuration tasks unless $pre_configure argument was supplied and is false |
| if ($pre_configure) { |
| # 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 manual |
| if (!$self->set_service_startup_mode('sshd', 'manual') && !$self->set_service_startup_mode('cygsshd', 'manual')) { |
| notify($ERRORS{'WARNING'}, 0, "reboot not attempted, unable to set sshd service startup mode to manual"); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "sshd service set to manual start"); |
| } |
| |
| # 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'); |
| |
| # Make sure update_cygwin.cmd runs after the computer is rebooted with the new hostname |
| $self->create_update_cygwin_startup_scheduled_task(); |
| } |
| |
| # Delete cached network configuration information so it is retrieved next time it is needed |
| delete $self->{network_configuration}; |
| |
| # 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) = $self->execute($reboot_command); |
| if (!defined($reboot_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to reboot $computer_node_name"); |
| return; |
| } |
| elsif ($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)) |
| |
| # Wait for the reboot to complete |
| my $result = $self->wait_for_reboot($total_wait_seconds, $attempt_delay_seconds, $attempt_limit); |
| my $reboot_duration = (time - $reboot_start_time); |
| if ($result) { |
| # Reboot was successful, calculate how long reboot took |
| notify($ERRORS{'OK'}, 0, "reboot complete on $computer_node_name, took $reboot_duration seconds"); |
| |
| # Clear any previous reboot_required reasons to prevent unnecessary reboots |
| delete $self->{reboot_required}; |
| |
| # Clear any imagemeta postoption reboot flag |
| $self->data->set_imagemeta_postoption(''); |
| |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "reboot failed on $computer_node_name, waited $reboot_duration seconds for computer to respond"); |
| 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 $enable_dhcp = shift; |
| |
| 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 ($enable_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 $NODE_CONFIGURATION_DIRECTORY/Logs/ipreset.log & "; |
| $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) = $self->execute($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 $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) = $self->execute($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 $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_
|