#!/usr/bin/perl -w
####################################################################################################################################
# Written by: Jared Cheney <jared@ileaf.net>
## Copyright 2005 Jared Cheney
##
## License:
##  CLeWMI (hereafter referred to as "program") is free software;
##  you can redistribute it and/or modify it under the terms of the GNU General
##  Public License as published by the Free Software Foundation; either version
##  2 of the License, or (at your option) any later version.
##  Note that when redistributing modified versions of this source code, you
##  must ensure that this disclaimer and the above coder's names are included
##  VERBATIM in the modified code.
##  
## Disclaimer:
##  This program is provided with no warranty of any kind, either expressed or
##  implied.  It is the responsibility of the user (you) to fully research and
##  comprehend the usage of this program.  As with any tool, it can be misused,
##  either intentionally (you're a vandal) or unintentionally (you're a moron).
##  THE AUTHOR(S) IS(ARE) NOT RESPONSIBLE FOR ANYTHING YOU DO WITH THIS PROGRAM
##  or anything that happens because of your use (or misuse) of this program,
##  including but not limited to anything you, your lawyers, or anyone else
##  can dream up.  And now, a relevant quote directly from the GPL:
##    
## NO WARRANTY
## 
##  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
##  FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
##  OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
##  PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
##  OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
##  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
##  TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
##  PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
##  REPAIR OR CORRECTION.
##    
# Creation Date: Apr 16, 2005
#
# Changelog:
#
# 2006-10-02  v1.3 Jared Cheney
#  - modified ExecQuery statements to do ForwardOnly lookups, to conserve memory on a host 
#    and speed up the return of data.
#
# 2005-06-19  v1.2-RC2  Jared Cheney
#  - fixed display bugs when listing in default format and when selecting specific
#    properties (versus all)
#  - added flag to just print version
#
# 2005-05-25  v1.1-RC2  Jared Cheney
#  - fixed bug that prevented all fields from displaying properly when specifying properties
#
# 2005-05-17  v1.0-RC2  Jared Cheney
#  - fixed convertDate option to work when time offset is +***
#  - changed output of csv and tab format to not include wmi class and instance
#    and computername by default; also removed --nodetail flag since now it's 
#    not necessary.
#  - instead of outputting [BLANK], just changed it to actually output nothing
#  - added WQL flag to allow custom WQL query to be passed in
#  - added noheader flag to suppress field names in output
#
# 2005-05-03  v1.0-RC1  Jared Cheney
#  - script ready for first release
#
#############################################################################

## FIXME's:
## - add username and password options (prompt for password if not specified with user)

use strict;
no warnings 'uninitialized'; ## cheap, but I don't want to trap for every undef or "" string in this script...
use Win32::OLE qw( in );
use Win32::OLE::Variant; # support for OLE data types
##$Win32::OLE::Warn = 3;


use constant wbemFlagReturnImmediately =>  16; #0x10;
use constant wbemFlagForwardOnly =>  32; #0x20;



## Global Variable(s)
my %conf = (
    "programName"          => $0,                                ## The name of this program
    "version"              => '1.3',                         ## The version of this program
    "authorName"           => 'Jared Cheney',                    ## Author's Name
    "authorEmail"          => 'jared@iLeaf.net',                 ## Author's Email Address
    "debug"                => 0,                                 ## Default debug level

    "mode"                 => '',
    "logging"              => '1',                               ## set to 1 if you want to enable logging


    ## PROGRAM VARIABLES
    "logFile"              => '',                                ## default log file, if none specified on command line
    "remoteServer"         => 'localhost',                       ## server we're connecting to
    "htmlOutput"           => '',                                ## file you want HTML output to go to
    "tabOutput"            => '',                                ## file you want tab-delimited output to go to
    "xmlOutput"            => '',                                ## file you want XML output to go to
    "wmiNameSpace"         => '',                                ## default WMI NameSpace
    "wmiProperty"          => '*',                               ## default WMI Properties to return (all)
    "wmiFilter"            => '',                                ## default WMI Filter to apply to query (blank)
    "wmiRegexpFilter"      => '',                                ## regexp filter applied to instance properties
    "wmiClass"             => '',                                ## WMI Class to connect to when enumerating instances
    "listProperties"       => 0,                                 ## if 1, list all possible properties available for an instance
                                                                 ## in specified wmiClass
    "enumClass"            => 0,                                 ## if 1, list all possible subClasses of specified class (all if no parent class specified)
    "enumClassRecursive"   => 0,                                 ## if 1, list all possible subClasses of specified class, recursive (all if no parent class specified
    "enumNameSpace"        => 0,                                 ## if 1, list all possible namespaces, beginning with specified namespace
    "output"               => 'list',                            ## default output format to screen, either csv or tab
    "manualQueryWMI"       => '',                                ## Used to perform an interactive query against WMI
    "nobanner"             => 0,                                 ## if set, no banner is displayed when executing program
    "noheader"             => 0,                                 ## if set, field headers are not displayed in output
    "nodetail"             => 1,                                 ## prevents WMI class and namespace from being displayed with each line
    "username"             => '',                                ## username used to connect to remote machines
);

$conf{'programName'} =~ s/(.)*[\/,\\]//;                         ## Remove path from filename
$0 = "[$conf{'programName'}]";

## set remoteServer to correct name if available from env. variable
if ($ENV{'COMPUTERNAME'}) {$conf{'remoteServer'} = $ENV{'COMPUTERNAME'};}
else { $conf{'remoteServer'} = "."; }

## Variables populated by the configuration file
my %audit = ();
my @userVars = ();

## global array used for output later
my @globalOutput = ();





#############################
##
##      MAIN PROGRAM
##
#############################
my $stopwatch = time();  ## keep track of how long it takes to run the script

## Initialize
initialize();

## Process Command Line
processCommandLine();


if ($conf{'mode'} eq "running") {
    printmsg ("\nCLeWMI (Command Line WMI) v$conf{'version'}\nby $conf{'authorName'} ($conf{'authorEmail'})\nPROGRAM STARTED on $conf{'remoteServer'} - " . localtime() . "\n",0,1) unless ($conf{'nobanner'});
    
    #############################
    ########    MAIN CODE    ####
    #############################
   
    
    if ($conf{'wmiFilter'}) {
        ## make sure our OS version supports this flag; XP and 2003 only
        printmsg ("Going to check OS Version to make sure we can support the -f option",2);
        filterCheckOSVersion();
    }
    
    if ($conf{'enumNameSpace'}) {
        ## establish a connection to WMI
        my $objectHandle;
        connectWMI(\$objectHandle,$conf{'userName'}); ## passing a reference to the function
        recurseNameSpace ($objectHandle,$conf{'wmiNameSpace'},0);
        quit();
    }
    
    if ($conf{'enumClass'} || $conf{'enumClassRecursive'}) {
        ## establish a connection to WMI
        my $objectHandle;
        connectWMI(\$objectHandle,$conf{'userName'}); ## passing a reference to the function
        printmsg ("INFO => Beginning class enumeration for namespace $conf{'wmiNameSpace'}. Be patient, this may take a while",0);
        recurseClasses ($objectHandle,$conf{'wmiClass'}) if ($conf{'enumClass'}); ## top level only
        recurseClasses ($objectHandle,$conf{'wmiClass'},0,1) if ($conf{'enumClassRecursive'}); ## recursive
        quit();
    }
    
    
    if ($conf{'listProperties'}) {
        ## establish a connection to WMI
        my $objectHandle;
        connectWMI(\$objectHandle,$conf{'userName'}); ## passing a reference to the function
        listProperties($objectHandle,$conf{'wmiClass'});
        quit();
    }
    
    if ($conf{'listMethods'}) {
        ## establish a connection to WMI
        my $objectHandle;
        connectWMI(\$objectHandle,$conf{'userName'}); ## passing a reference to the function
        listMethods($objectHandle,$conf{'wmiClass'});
        quit();
    }

    
    
    #wmiLook ("/root/cimv2","Win32_Service","*","");
    wmiLook ($conf{'wmiNameSpace'},$conf{'wmiClass'},$conf{'wmiProperty'},$conf{'wmiFilter'});
    writeList (\@globalOutput,"SCREEN") unless ($conf{'listProperties'});
    
    if ($conf{'tabOutput'}) {
        printmsg ("INFO => generating TAB output now",0);
        writeTAB (\@globalOutput); 
    }
    
    if ($conf{'htmlOutput'}) {
        printmsg ("INFO => generating HTML output now",0);
        writeHTML (\@globalOutput); 
    }
    
    if ($conf{'xmlOutput'}) {
        printmsg ("INFO => generating xml output now (FIXME - not implemented yet",0);
        ## FIXME - write this function:
        ## writeXML (\@globalOutput); 
    }
    
}






## Quit
printmsg ("INFO => Program execution finished in " . (time() - $stopwatch) . " seconds",1);
quit("",0);







######################################################################
## Function:    version ()
##
## Description: Prints program version and author
## 
######################################################################
sub version {
print <<EOM;
$conf{'programName'} v$conf{'version'} by $conf{'authorName'} <$conf{'authorEmail'}>
Licensed under the terms of the GNU General Public License.
EOM
exit(1);
}



######################################################################
## Function:    help ()
##
## Description: For all those newbies ;) 
##              Prints a help message and exits the program.
## 
######################################################################
sub help {
print <<EOM;


$conf{'programName'} v$conf{'version'} by $conf{'authorName'} <$conf{'authorEmail'}>
Licensed under the terms of the GNU General Public License.

Command Line WMI (CLeWMI) query tool.  Allows one to perform simple Data
queries against WMI.  Future release should support Schema and Event queries.

Usage:  $conf{'programName'} [options]

 -c <class>                  WMI Class; i.e. Win32_Service
 -s <server>                 Server you want to connect to. By default it is
                             localhost
 -n <namespace>              WMI NameSpace you want to connect to. By default
                             it is /root/cimV2
 -p <properties>             Specify which properties of each instance to 
                             return. List the properties separated by commas
                             (no spaces) (i.e. Name,DisplayName,Size).
                             Without this flag, all properties will be
                             returned.
 -f <Property[~|=]Value>     Only Valid on Windows XP or 2003. Use -r flag for
                             Windows 2000. Filter what instances of a class
                             are returned (i.e. Name~Alert% will return 
                             instances where Name property begins with 'Alert'
                             followed by anything)
                             To restrict to an exact match, instead 
                             use = instead of ~ (i.e. Name=Alert will return
                             instances where Name property exactly matches
                             'Alert') You can also reverse the logic of the = 
                             sign by prefacing it with ! (i.e. Name!=Alert
                             returns anything that does NOT have Name matching
                             'Alert') You can also filter on NULL or NOT NULL,
                             like this:
                             e.g. Name=NULL (returns instances with NULL Name
                                  property)
                                  Name!=NULL (returns instances whose Name 
                                  property is not NULL)
 -r <Property[~|=]Value>     Similar to above filter option, but less efficient.
                             It causes all instances of the specified class to 
                             be returned from the WMI query, and then just
                             filters the output based on the value specified.
                             Windows 2000 does not support the LIKE operator in
                             WQL queries, hence this flag was created.
                             Syntax is PropertyName~Value, where Value is 
                             evaluated as a regular expression, thus allowing
                             great flexibility. RegExp is case insensitive.
                             e.g. Name~windows.* (returns anything with name
                                  property beginning with windows and followed
                                  by 0 or more characters)
                             Like the -f option, if you use the = character
                             instead of the ~ character, it changes the
                             behaviour of the query slightly. Using != reverses
                             the match.
 -w <WQL Query String>       This flag allows you to specify an actual WQL
                             query string. If specified, then it overrides the
                             -f or -r flags, as well as the -c flags.  Don't
                             use this unless you know what you're doing.
                             e.g. \"Select * FROM Win32_Service WHERE Name = 
                                    \'Alerter\'\"
 -o tab|csv|list             Choose whether to display output in tab separated,
                             comma separated, or list style format. Default 
                             is to display in $conf{'output'} format.
 --list                      Lists all possible properties for an instance of
                             the specified class.
 --listMethods               Lists all possible methods available for the 
                             specified class.
 --listNameSpaces            Lists all available WMI NameSpaces on a machine
 --listClasses               Lists all available classes for the given 
                             NameSpace, top level only (doesn't recurse in).
                             Will start at specific Class, if specified.
                             Only dynamic classes are listed.
 --recurseClasses            Lists all available classes for a given NameSpace,
                             and recurses down into each class. Will start
                             at specific Class, if specified. Lists all 
                             subclasses, not just dynamic ones.
 -v                          verbosity - use multiple times for greater effect
 -l <logFile>                logFile to be used.
 -h | --help                 Display this help message
 -V                          Display program version and exit
 --nobanner                  Suppresses display of program name and version at
                             top of script output. Useful when gathering data
                             for import into a spreadsheet.
 --convertDate               Specify this option if you want dates returned
                             by WMI to be in a readable format. Default is to 
                             simply return them in the format given.


    
 EXAMPLES
 $conf{'programName'} -c Win32_Service --list
        Lists all available properties for instances of the Win32_Service class
    
 $conf{'programName'} -c Win32_Service -p Name,Status -o tab --nobanner
        Returns the Name and Status of all Services on a box in tab delimited 
        format. Script banner information is suppressed.
    
 $conf{'programName'} -c Win32_Service -p Name,DisplayName -f \"state~%run%\"
        Returns the Name and DisplayName of all Services whose state contains
        the word 'run' somewhere in it (-f only works on XP and 2003)
        
 $conf{'programName'} -c Win32_Service -p Name,DisplayName -r \"state=run.*\" -o csv
        Returns the Name and DisplayName of all services whose state contains
        the word 'run' followed by any set of characters. Results are listed 
        in csv format
    
 $conf{'programName'} --listNameSpaces
        Lists all available namespaces installed on a computer

 $conf{'programName'} -n /root/cimv2 -s servername --listClasses 
        Lists all available top level classes available to the /root/cimv2
        namespace on the remote computer servername

  
EOM
exit(1);
}

    #--html <htmlOutputFile>     HTML output - File you want HTML output written to
    #--tab <tabOutputFile>       Tab-delimited output - File you want Tab-delimited
    #                            output written to
    #--xml <xmlOutputFile>       XML output - File you want XML output written to
    #-u <username>               If you want to connect to a remote computer, use
    #                            this flag.
    #                            e.g. -u "DOMAIN\john"
    #                            You'll be prompted for a password when the 
    #                            connection is being made
        






######################################################################
##  Function: initialize ()
##  
##  Does all the script startup jibberish.
##  
######################################################################
sub initialize {

  ## Set STDOUT to flush immediatly after each print  
  $| = 1;

  ## Intercept signals
  $SIG{'QUIT'}  = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); };
  $SIG{'INT'}   = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); };
  $SIG{'KILL'}  = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); };
  $SIG{'TERM'}  = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); };
  
  ## ALARM and HUP signals are not supported in Win32
  unless ($^O =~ /win/i) {
      $SIG{'HUP'}   = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); };
      $SIG{'ALRM'}  = sub { quit("$$ - $conf{'programName'} - EXITING: Received SIG$_[0]", 1); };
  }
  
  return(1);
}











######################################################################
##  Function: processCommandLine ()
##  
##  Processes command line storing important data in global var %conf
##  
######################################################################
sub processCommandLine {
    
    
    ############################
    ##  Process command line  ##
    ############################
    
    my $x;
    my @ARGS = @ARGV;
    my $numargv = scalar(@ARGS);
    help() unless ($numargv);
    for (my $i = 0; $i < $numargv; $i++) {
        $x = $ARGS[$i];
        if    ($x =~ /^--help$|^-h$/i)      { help(); }
        elsif ($x =~ /^-V$/)               { version(); }
        elsif ($x =~ /^-v+/)               { my $tmp = (length($&) - 1); $conf{'debug'} += $tmp; }
        elsif ($x =~ /^-l$/i)               { $i++; $conf{'logFile'}    = $ARGS[$i];}
        elsif ($x =~ /^-s$/i)               { $i++; $conf{'remoteServer'} = $ARGS[$i];}
        elsif ($x =~ /^-n$/i)               { $i++; $conf{'wmiNameSpace'} = $ARGS[$i];}
        elsif ($x =~ /^-p$/i)               { $i++; $conf{'wmiProperty'} .= ",$ARGS[$i]"; $conf{'wmiProperty'} =~ s/\*//;} ## add to the string, to allow -p to be used multiple places
        elsif ($x =~ /^-f$/i)               { $i++; $conf{'wmiFilter'} = $ARGS[$i];}
        elsif ($x =~ /^-r$/i)               { $i++; $conf{'wmiRegexpFilter'} = $ARGS[$i];}
        elsif ($x =~ /^-o$/i)               { $i++; $conf{'output'} = $ARGS[$i]; help() if ($conf{'output'} !~ /tab|csv|list/i);}
        elsif ($x =~ /^-u$/i)               { $i++; $conf{'userName'} = $ARGS[$i];}
        elsif ($x =~ /^-w$|^--wql=$/i)      { $i++; $conf{'wql'} = $ARGS[$i];}
        elsif ($x =~ /^--list$/i)           { $conf{'listProperties'} = 1;}
        elsif ($x =~ /^--listMethods$/i)    { $conf{'listMethods'} = 1;}
        elsif ($x =~ /^--listClasses$/i)    { $conf{'enumClass'} = 1;}
        elsif ($x =~ /^--recurseClasses$/i) { $conf{'enumClassRecursive'} = 1;}
        elsif ($x =~ /^--listNameSpaces$/i) { $conf{'enumNameSpace'} = 1;}
        elsif ($x =~ /^--nobanner$/i)       { $conf{'nobanner'} = 1;}
        elsif ($x =~ /^--noheader$/i)       { $conf{'noheader'} = 1;}
        #elsif ($x =~ /^--nodetail/i)        { $conf{'nodetail'} = 1;}
        elsif ($x =~ /^--convertDate/i)     { $conf{'convertDate'} = 1;}
        elsif ($x =~ /^--html$/i)           { $i++; $conf{'htmlOutput'} = $ARGS[$i];}
        elsif ($x =~ /^--tab$/i)            { $i++; $conf{'tabOutput'} = $ARGS[$i];}
        elsif ($x =~ /^--xml$/i)            { $i++; $conf{'xmlOutput'} = $ARGS[$i];}
        elsif ($x =~ /^-c$/i)               { $i++; $conf{'wmiClass'} = $ARGS[$i];}
        elsif ($x =~ /^--audit$/i)          { $i++; $conf{'manualQueryWMI'} = $ARGS[$i];}
        else  { 
            quit ("Error: \"$x\" is not a recognised option! Try '$conf{'programName'} -h' for a usage summary", 1);
            ## help(); 
        }
    }

    ## need to remove leading comma, if there
    $conf{'wmiProperty'} =~ s/^,//;
    
    ## set some defaults, if necessary, and fix some things
    if ($conf{'enumNameSpace'}) {
        if ("x$conf{'wmiNameSpace'}" eq "x") {
            $conf{'wmiNameSpace'} = "/root";
        }
    }
    
    ## Set default value of namespace to /root/cimv2, if not already set
    if ("x$conf{'wmiNameSpace'}" eq "x") {
        $conf{'wmiNameSpace'} = "/root/cimv2";
    }

    ## add leading slash if wasn't specified from command line
    if ($conf{'wmiNameSpace'} !~ /^\/|\\/) { $conf{'wmiNameSpace'} = "/$conf{'wmiNameSpace'}"; }
    
    my @required = (
                      'wmiClass',
    );
    
    #if (!$conf{'userName'} && $conf{'remoteServer'} !~ /^\.$|^$ENV{'COMPUTERNAME'}$/) {
    #    quit("ERROR: Value [userName] was not set after parsing command line arguments! It is required when specifying a remote computer to connect to.", 1);
    #}        
    
    if ($conf{'wql'}) { ## set class to something to prevent error from occurring below
        $conf{'wmiClass'} = "overriddenByWQLStatement";
    }
    
    foreach (@required) {
        if ("x$conf{$_}" eq "x") {
            quit("ERROR: Value [$_] was not set after parsing command line arguments!", 1) unless ($conf{'enumClass'} || $conf{'enumClassRecursive'} || $conf{'enumNameSpace'});
        }
    }
    
    ## made it past checks, so safe to set mode to running
    $conf{'mode'} = "running";
    return(1);
}
 














###############################################################################################
##  Function:    printmsg (string $message, int $level)
##
##  Description: Handles all messages - logging them to a log file, 
##               printing them to the screen or both depending on
##               the $level passed in, $conf{'debug'} and wether
##               $conf{'mode'}.
##
##  Input:       $message                A message to be printed, logged, etc.
##               $level                  The debug level of the message. If
##                                       not defined 0 will be assumed.  0 is
##                                       considered a normal message, 1 and 
##                                       higher is considered a debug message.
##               $leaveCarriageReturn    Whether or not to strip carriage returns (always will strip, unless other than 0)
##  
##  Output:      Prints to STDOUT, to LOGFILE, both, or none depending 
##               on the state of the program.
##  
##  Example:     printmsg ("WARNING: We believe in generic error messages... NOT!", 1);
###############################################################################################
sub printmsg {
    my %incoming = ();
    (
        $incoming{'message'},
        $incoming{'level'},
        $incoming{'leaveCarriageReturn'},
    ) = @_;
    $incoming{'level'} = 0 if (!defined($incoming{'level'}));
    $incoming{'leaveCarriageReturn'} = 0 if (!defined($incoming{'leaveCarriageReturn'}));
    $incoming{'message'} =~ s/\r|\n/ /sg unless ($incoming{'leaveCarriageReturn'} >= 1);

    ## Continue on if the debug level is >= the incoming message level
    if ($conf{'debug'} >= $incoming{'level'}) {
       ## Print to the log file
        if ($conf{'logFile'}) {
            open (LOGFILE, ">>$conf{'logFile'}");
            print LOGFILE "$conf{'programName'}-v$conf{'version'}   ". localtime() . "   L$incoming{'level'}   $incoming{'message'}\n";
            close (LOGFILE);
        }
        if ($conf{'alertCommand'} && ($conf{'debug'} == 0) && ($incoming{'message'} =~ /ERR|CRIT|WARN/) ) {
            my $tmpAlert = $conf{'alertCommand'};
            $tmpAlert =~ s/MESSAGE/$incoming{'message'}/g;
            system ($tmpAlert);
        }
        ## Print to STDOUT
        if ($conf{'debug'} > 0) {
            print STDOUT "$conf{'programName'}-v$conf{'version'}-". localtime() . "-dbg:$incoming{'level'}   $incoming{'message'}\n";
        }
        else {
            print STDOUT "$incoming{'message'}\n";
        }
            
    }

    ## Return
    return(1);
}










######################################################################
##  Function:    quit (string $message, int $errorLevel)
##  
##  Description: Exits the program, optionally printing $message.  It 
##               returns an exit error level of $errorLevel to the 
##               system  (0 means no errors, and is assumed if empty.)
##
##  Example:     quit("Exiting program normally", 0);
######################################################################
sub quit {
  my %incoming = ();
  (
    $incoming{'message'},
    $incoming{'errorLevel'}
  ) = @_;
  $incoming{'errorLevel'} = 0 if (!defined($incoming{'errorLevel'}));
  
  ## Print exit message
  if ($incoming{'message'}) { 
      printmsg($incoming{'message'}, 0);
  }
  
  ## Exit
  exit($incoming{'errorLevel'});
}






























##############################################
##############################################
##                                          ##
##  WMI specific functions below here     ##
##                                          ##
##############################################
##############################################












###############################################################################################
##  Function:    wmiLook (string $queryString,string $description)
##
##  Description: Performs a lookup of wmi value(s)
##             
##
##  Input:       $queryString    Full path of wmi property you wish to find
##                                 - If ends in '/', then it assumes it is a class and 
##                                   will recurse through all instances and values and return
##                                   data for each value
##                                 - If last word BEGINS with '//' and does not have trailing '/',
##                                   assumes it is a single instance/value and will return the data for it
##                                 - you can include multiple instances of a class at the end of the query,
##                                   just separate them by commas and leave no whitespace.
##                                   Also, you can check to see if a value is matched by putting the 
##                                   instancename=matchvalue (see example)
##
##               $description      String you want generated in the output to 'label' the value looked up
##
##  Output:      Prints to STDOUT, to LOGFILE, OUTPUTFILE, all, or none depending 
##               on the state of the program.
##  
##  Examples:    wmiLook ("/root/cimv2","Win32_Service","*","");
##               wmiLook ($conf{'wmiNameSpace'},$conf{'wmiClass'},$conf{'wmiProperty'},$conf{'wmiFilter'});
###############################################################################################
sub wmiLook {
    #my %incoming = ();
    #(
    #    $incoming{'wmiQuery'},
    #    $incoming{'description'},
    #) = @_;
    
    
    my (
        $wmiNameSpace,
        $wmiClass,
        $wmiProperty,
        $wmiFilter,
    ) = @_;
    
    my $wmiInstance = "";
    my $wmiFilterValue = "";
    my @wmiOutput = ();
    my $sep = "~";  ## separation character
    
    ## if all properties are desired, then set the wmiProperty field to NULL
    if ($wmiProperty eq "*") {
        $wmiProperty = "NULL";
        printmsg ("going to return all properties of each instance of class $wmiClass, because wmiProperty field is a *",1);
    }
    else {
        printmsg ("INFO => Going to return properties [$wmiProperty] for instances of class $wmiClass",1);
    }
    
    
    ## get the filter ready
    if ("x$wmiFilter" eq "x") {
        printmsg ("INFO => No filter specified, thus returning all instances of class $wmiClass",1) unless ($conf{'wmiRegexpFilter'});
    }
    elsif ($wmiFilter =~ /\!=NULL/i) {
        my ($tmpProperty,$tmpValue) = split(/\!=/,$wmiFilter);
        $wmiFilter = "$tmpProperty IS NOT NULL";
        $wmiFilterValue = "[NOT NULL]";
        
    }
    elsif ($wmiFilter =~ /=NULL/i) {
        my ($tmpProperty,$tmpValue) = split(/\=/,$wmiFilter);
        $wmiFilter = "$tmpProperty IS NULL";
        $wmiFilterValue = "[NULL]";
        
    }
    elsif ($wmiFilter =~ /~/) {
        my ($tmpProperty,$tmpValue) = split(/~/,$wmiFilter);
        $wmiFilter = "$tmpProperty LIKE '$tmpValue'";
        $wmiFilterValue = $tmpValue;
        if ("x$wmiFilterValue" eq "x") {
                quit ("ERROR => Your wmiFilter string [$wmiFilter=$wmiFilterValue] is not formatted correctly - You should not specify a WMI Property in the query portion without also specifying a Value; i.e. InterfaceType~SCSI.  See documentation for more details",0);
        }
    }
    else {
        my ($tmpProperty,$tmpValue) = split(/=/,$wmiFilter);
        $wmiFilter = "$tmpProperty='$tmpValue'";
        $wmiFilterValue = $tmpValue;
        if ("x$wmiFilterValue" eq "x") {
                quit ("ERROR => Your wmiFilter string [$wmiFilter=$wmiFilterValue] is not formatted correctly - You should not specify a WMI Property in the query portion without also specifying a Value; i.e. InterfaceType~SCSI.  See documentation for more details",0);
        }
    }
    
    
    ## check to see if -r flag used with = sign
    if ("x$conf{'wmiRegexpFilter'}" eq "x" && ! $wmiFilter) {
        printmsg ("INFO => No regexp filter specified, thus displaying all instances of class $wmiClass",1);
    }
    elsif ($conf{'wmiRegexpFilter'} =~ /\!=NULL/i) {
        my ($tmpProperty,$tmpValue) = split(/\!=/,$conf{'wmiRegexpFilter'});
        $wmiFilter = "$tmpProperty IS NOT NULL";
        $wmiFilterValue = "[NOT NULL]";
        
    }
    elsif ($conf{'wmiRegexpFilter'} =~ /=NULL/i) {
        my ($tmpProperty,$tmpValue) = split(/\=/,$conf{'wmiRegexpFilter'});
        $wmiFilter = "$tmpProperty IS NULL";
        $wmiFilterValue = "[NULL]";
        
    }
    elsif ($conf{'wmiRegexpFilter'} =~ /.+=.+/) { ## make sure the regexpFilter is using = instead of ~
        my ($tmpProperty,$tmpValue) = split(/=/,$conf{'wmiRegexpFilter'});
        $wmiFilter = "$tmpProperty='$tmpValue'";
        $wmiFilterValue = $tmpValue;
        if ("x$wmiFilterValue" eq "x") {
                quit ("ERROR => Your wmiFilter string [$conf{'wmiRegexpFilter'}] is not formatted correctly - You should not specify a WMI Property in the query portion without also specifying a Value; i.e. InterfaceType~SCSI.  See documentation for more details",0);
        }
    }
    
    
    printmsg ("Details of Query => server:[$conf{'remoteServer'}] - wmiNameSpace:[$wmiNameSpace] - wmiClass:[$wmiClass] - wmiProperty:[$wmiProperty] - wmiFilter:[$wmiFilter]",1);
    
    
        
    ## establish a connection to WMI
    my $objectHandle;
    connectWMI(\$objectHandle,$conf{'userName'}); ## passing a reference to the function
        
    
    #if ($objectHandle = Win32::OLE->GetObject("WinMgmts:{impersonationLevel=impersonate}!//$conf{'remoteServer'}$wmiNameSpace")) {
    #    printmsg ("INFO => Successfully opened a handle to the WMI Name Space $wmiNameSpace",1);
    #}
    #else {
    #      quit ("ERROR => problem binding to WMI Path. Error was:" . Win32::OLE->LastError() . " Ending execution of script",3);
    #}

    
    
    
    ## will be used later on
    my $headerLine = "SERVERNAME" .$sep. "WMI NAMESPACE" .$sep. "WMI CLASS" .$sep;
    ## Now let's look inside 
    if ($objectHandle) {
                
        printmsg ("obtained WMI Object handle",3);
            ## initialize match flag
            my $match = "NOCOMPARE";
            
            my $wmiRegexpFilter = "";
            my $wmiRegexpProp = "";
            if ($conf{'wmiRegexpFilter'} =~ /~/) {
                $wmiRegexpProp = (split(/~/,$conf{'wmiRegexpFilter'}))[0];
                $wmiRegexpFilter = (split(/~/,$conf{'wmiRegexpFilter'}))[1];
            }
            
            ## determine whether to enumerate all properties or just a select few
            if ("x $wmiProperty" eq "x NULL") {
                printmsg ("proceeding with all properties of each instance from $wmiClass, because wmiProperty is blank",3);
                my $wmiQuery = "SELECT * FROM $wmiClass";
                my $wmiClassDisplay = $wmiClass; ## String used in output later.
                if ($wmiFilter) {
                    #$wmiQuery = "SELECT $wmiFilter FROM $wmiClass";
                    #$wmiClassDisplay .= "." . $wmiProperty;
                    #if ($wmiFilter) {
                        $wmiQuery = "SELECT * FROM $wmiClass WHERE $wmiFilter";
                        $wmiClassDisplay .= "=" . $wmiFilterValue;
                    #}
                }
                
                ## check to see if custom wql query passed in
                if ($conf{'wql'}) {
                    $wmiQuery = "$conf{'wql'}";
                    printmsg ("INFO => received a custom WQL Query, so using that instead of anything that was passed in as a filter with -r or -f flags",1);
                }
                
                printmsg ("wmi Query = [$wmiQuery]",2);
                ##my $results = $objectHandle->ExecQuery($wmiQuery); #,"WQL",wbemFlagReturnImmediately + wbemFlagForwardOnly);
                ## doing a forwardOnly query prevents the computer from having to retain all the objects in memory, we handle it within our perl script instead
                ## This should cause things to return a little faster as well.
                my $results = $objectHandle->ExecQuery($wmiQuery,"WQL",wbemFlagReturnImmediately + wbemFlagForwardOnly);
                
                ## FIXME - check for errors now
                
                
                
                my $resultString = "";
                my $i = 0;
                foreach my $instance (in ($results)) {
                    $i++;
                    printmsg("INFO => --------------------------processing an instance------------------------------",3);
                    
                    ## check to see if we should keep this instance or not
                    my $keep = 1; ## flag used to keep track of whether this instance gets skipped or not
                    if ($wmiRegexpFilter && $instance->{$wmiRegexpProp} =~ /$wmiRegexpFilter/i) {
                        printmsg ("Matches filter [$wmiRegexpFilter], continuing",2);
                    }
                    elsif ($wmiRegexpFilter) {
                        $keep = 0;
                        printmsg ("Skipping this instance, it didn't match filter criteria for regular expression option",2);
                        next;
                    }
                    foreach my $item (in $instance->{Properties_}) {
                        if (ref($item->{Value}) eq "ARRAY") { ## sometimes we'll get back an array
                            my $tmpString = "[";
                            foreach my $value (in($item->{Value}) ) { ## loop through array and add each element to a csv string
                                $tmpString .= "$value;";
                            }
                            $tmpString =~ s/;$//g; ## remove trailing comma
                            $tmpString .= "]"; ## close off array brackets
                            printmsg ("INFO => Array contents are: $tmpString",3);
                            $headerLine .= "$item->{Name}$sep" if ($i == 1);
                            $resultString .= "$tmpString$sep"; 
                        }
                        elsif ($item->{Value}) { ## otherwise a normal value, if populated
                            printmsg ("$item->{Name} = $item->{Value}",3);
                            $headerLine .= "$item->{Name}$sep" if ($i == 1);
                            ## see if it matches date format, and convert it if so
                            if ($conf{'convertDate'} && $item->{Value} =~ /\d{14}\.\d{6}[+-][\d|\*]{3}/i) {
                                ## example: 20050305121057.000000-420
                                ##          yyyymmddHHMMSS.mmmmmmsUUU
                                my $tmp = ($item->{Value});
                                $tmp =~ s/^(....)(..)(..)(..)(..)(..)\.......(.)(...)$/$2\/$3\/$1 $4:$5:$6/;
                                my $timeOffset = $8;
                                my $sign = $7;
                                if ($timeOffset =~ /\*+/) {
                                    printmsg ("asterisks found in 8th pattern:$sign$timeOffset",4);
                                    $tmp .= " (GMT $sign" . "0)";
                                }
                                else {
                                    printmsg ("asterisks NOT found in 8th pattern:$sign$timeOffset",4);
                                    $tmp .= " (GMT $sign" . ($timeOffset/60) . ")";
                                }
                                $resultString .= "$tmp$sep";
                            }
                            else {
                                $resultString .= "$item->{Value}$sep";
                            }
                        }
                        else {
                            printmsg ("$item->{Name} = [BLANK]",3);
                            $headerLine .= "$item->{Name}$sep" if ($i == 1);
                            $resultString .= "$sep"; 
                        }
                    }
                    $resultString =~ s/\$sep$//;
                    push (@wmiOutput,$resultString) if ($keep);
                    $resultString = "";
                }
                if ($i <= 0) {
                    printmsg ("WARNING => Check the computer and class name.  No information was received from the specified class. wmi query: [$wmiQuery]",0);
                    push (@wmiOutput,"NO VALUES FOUND");
                }
                
            }
            ## Specific properties were specified..
            else {
                printmsg ("Proceeding with individual properties of instances within class $wmiClass. Properties are [$wmiProperty]",3);
                ## since we're looking for specific instances, let's improve the efficiency of our query
                my $propertyList = $wmiProperty;
                ## if we're filtering results using -r option, need to make sure filter criteria is included in the select query
                if ($wmiRegexpProp && $propertyList !~ /$wmiRegexpProp/i) {
                    $propertyList .= ",$wmiRegexpProp";
                }
                my $wmiQuery = "SELECT $propertyList FROM $wmiClass";
                my $wmiClassDisplay = $wmiClass; ## String used in output later.
                if ($wmiFilter) {
                    $wmiClassDisplay .= "." . $wmiFilter . "=" . $wmiFilterValue;
                    $wmiQuery = "SELECT $propertyList FROM $wmiClass WHERE $wmiFilter";
                }
                
                ## check to see if custom wql query passed in
                if ($conf{'wql'}) {
                    $wmiQuery = "$conf{'wql'}";
                    printmsg ("INFO => received a custom WQL Query, so using that instead of anything that was passed in as a filter with -r or -f flags",1);
                }
                
                printmsg ("wmi Query = [$wmiQuery]",2);
                ##my $results = $objectHandle->ExecQuery($wmiQuery);
                my $results = $objectHandle->ExecQuery($wmiQuery,"WQL",wbemFlagReturnImmediately + wbemFlagForwardOnly);
                
                ## FIXME - do an OLE error check here
                my $i = 0;
                foreach my $instance (in ($results)) {
                    $i++;
                    printmsg("INFO => --------------------------processing an instance------------------------------",3);
                    ## check to see if we should keep this instance or not
                    my $keep = 1; ## flag used to keep track of whether this instance gets skipped or not
                    if ($wmiRegexpFilter && $instance->{$wmiRegexpProp} =~ /$wmiRegexpFilter/i) {
                        printmsg ("Matches filter [$wmiRegexpFilter], continuing",2);
                    }
                    elsif ($wmiRegexpFilter) {
                        $keep = 0;
                        printmsg ("Skipping this instance, it didn't match filter criteria for regular expression option",2);
                        #next;
                    }
                    my @properties = split (/,/,$wmiProperty);
                    my $resultString = "";
                    foreach my $prop (@properties) {
                        if (! $instance->{$prop}) {
                            printmsg ("$prop = [NO VALUE FOUND]",3);
                            $headerLine .= "$prop$sep" if ($i == 1);
                            $resultString .= "$sep";
                        }
                        else {
                            printmsg ("$prop = [$instance->{$prop}]",3);
                            if (ref($instance->{$prop}) eq "ARRAY") {
                                my $tmpString = "[";
                                foreach my $value (in($instance->{$prop}) ) {
                                    $tmpString .= "$value;";
                                }
                                $tmpString =~ s/;$//g;
                                $tmpString .= "]";
                                printmsg ("INFO => Array contents are: $tmpString",2);
                                $headerLine .= "$prop$sep" if ($i == 1);
                                $resultString .= "$tmpString$sep";
                            }
                            else {
                                $headerLine .= "$prop$sep" if ($i == 1);
                                ## check to see if matches date format and if so, convert it to readable format
                                if ($conf{'convertDate'} && $instance->{$prop} =~ /\d{14}\.\d{6}[+-][\d\*]{3}/i) {
                                    ## example: 20050305121057.000000-420
                                    ##          yyyymmddHHMMSS.mmmmmmsUUU
                                    my $tmp = ($instance->{$prop});
                                    $tmp =~ s/^(....)(..)(..)(..)(..)(..)\.......(.)(...)$/$2\/$3\/$1 $4:$5:$6/;
                                    my $timeOffset = $8;
                                    my $sign = $7;
                                    if ($timeOffset =~ /\*+/) {
                                        printmsg ("asterisks found in 8th pattern:$sign$timeOffset",4);
                                        $tmp .= " (GMT $sign" . "0)";
                                    }
                                    else {
                                        printmsg ("asterisks NOT found in 8th pattern:$sign$timeOffset",4);
                                        $tmp .= " (GMT $sign" . ($timeOffset/60) . ")";
                                    }
                                    $resultString .= "$tmp$sep";
                                }
                                else {
                                    $resultString .= "$instance->{$prop}$sep";
                                }
                            }
                        }
                    }
                
                #printmsg ("Result String for this instance is currenlty [$resultString]",0);
                $resultString =~ s/$sep$//; ## trim off any trailing separator characters
                #printmsg ("Result String for this instance is currenlty [$resultString]",0);
                push (@wmiOutput,$resultString) if ($keep); ## only add to result array if this is an instance that matched any filters
                $resultString = "";
            }
            
            if ($i <= 0) {
                printmsg ("WARNING => Check the computer and class name.  No information was received from the specified class. wmi query: [$wmiQuery]",0);
                push (@wmiOutput,"NO VALUES FOUND");
            }
                
        }
    }
    else {
        quit("ERROR - unable to talk to WMI on this computer, check to make sure that it is available over the network",3);
    }
    
    printmsg ("headerLine is now [$headerLine]",3);
    $headerLine =~ s/$sep$//;
    printmsg ("headerLine is now [$headerLine]",3);
    ## Now add all results onto the global output array for parsing later.
    push (@globalOutput, "$headerLine");
    foreach my $tmp (@wmiOutput) {
        # "SERVERNAME\tAUDITTYPE\tDESCRIPTION\tMATCHSTATUS\tInspectedElement\tMatchString\tOutput\n";
        push (@globalOutput, "$conf{'remoteServer'}$sep$wmiNameSpace$sep$wmiClass$sep" . $tmp);
    }
        
    
        
}





    
###############################################################################################
## FUNCTION:    
##   connectWMI ()
## 
## DESCRIPTION: 
###############################################################################################
    sub connectWMI {
        my (
            $handle,
            $username,
            $password,
            $namespace,
        ) = @_;
        
        ## validate variable value
        if ("x$namespace" eq "x") {
            $namespace = $conf{'wmiNameSpace'};
        }
        
        if ("x$username" eq "x") {
            if (${$handle} = Win32::OLE->GetObject("WinMgmts:{impersonationLevel=impersonate,authenticationLevel=pktPrivacy}!//$conf{'remoteServer'}$namespace")) {
                printmsg ("INFO => Successfully opened a handle to the WMI Name Space $namespace",1);
            }
            else {
                #quit ("ERROR => problem binding to WMI namespace $conf{'wmiNameSpace'}.",1);
                quit ("ERROR => problem binding to WMI namespace $namespace. Error was:" . Win32::OLE->LastError() . " Ending execution of script",3);
            }
        }
        else {
            if ("x$password" eq "x") {
                my $objPassword = Win32::OLE->new('ScriptPW.Password');
                print STDOUT 'Please enter your password:';
                $conf{'password'} = $objPassword->GetPassword();
                $password = $conf{'password'};
                print STDOUT "\n";
            }
            
            my $objSWbemLocator = Win32::OLE->new('WbemScripting.SWbemLocator');
            ${$handle} = $objSWbemLocator->ConnectServer($conf{'remoteServer'}, "$namespace", $username, $password, 'MS_409','');
            #my $colSwbemObjectSet = ${$handle}->ExecQuery('Select Properties_ From Win32_Process');
            #foreach my $objProcess (in $colSwbemObjectSet) {
            #    print 'Process Name: ' . $objProcess->Name, "\n";
            #}
        }
        
#      [ strServer = "" ],
#      [ strNamespace = "" ],
#      [ strUser = "" ],
#      [ strPassword = "" ],
#      [ strLocale = "" ],
#      [ strAuthority = "" ],
#      [ iSecurityFlags = 0 ],
#      [ objwbemNamedValueSet = null ]
    }

    
    
###############################################################################################
## FUNCTION:    
##   listProperties ()
## 
## DESCRIPTION: 
###############################################################################################
sub listProperties {
    my (
        $objService,
        $strClass,
        $i,
    ) = @_;
    
    print "Class [$strClass] Available Properties\n\n";
    my $objWEBM = $objService->Get($strClass);
    my $colProperties = $objWEBM->{Properties_};
    foreach my $prop (in($colProperties)) {
        print "$strClass : $prop->{Name}\n";
    }
    
    #my $colProperties = $objService->{Properties_};
    #foreach my $prop (in($colProperties)) {
    #    print "$strClass : $prop->{Name}\n";
    #}
}
  



###############################################################################################
## FUNCTION:    
##   listMethods ()
## 
## DESCRIPTION: 
###############################################################################################
sub listMethods {
    my (
        $objService,
        $strClass,
        $i,
    ) = @_;
    
    print "Class [$strClass] Available Methods (this script doesn't invoke methods, just reports them)\n\n";
    my $objWEBM = $objService->Get($strClass);
    my $colMethods = $objWEBM->{Methods_};
    foreach my $method (in($colMethods)) {
        print "$strClass : $method->{Name}\n";
    }

    #my $colMethods = $objService->{Methods_};
    #foreach my $method (in($colMethods)) {
    #    print "$strClass : $method->{Name}\n";
    #}
        
}
  


###############################################################################################
## FUNCTION:    
##   recurseNameSpace ()
## 
## DESCRIPTION: 
###############################################################################################
sub recurseNameSpace {
        my (
            $objService,
            $strNameSpace,
            $i,
            ) = @_;
    
    my $colNameSpaces = $objService->InstancesOf("__NAMESPACE");
    foreach my $new (in($colNameSpaces)) {
        my $tmp = $new->{Name};
        my $strNewNameSpace .= "$strNameSpace/$tmp";
        printmsg ($strNewNameSpace,0);
        $conf{'wmiNameSpace'} = $strNewNameSpace;
        my $objServiceTmp;
        connectWMI(\$objServiceTmp,$conf{'userName'},$conf{'password'}); ## passing a reference to the function
        #$objServiceTmp = Win32::OLE->GetObject("WinMgmts:{impersonationLevel=impersonate,authenticationLevel=pktPrivacy}!//$conf{'remoteServer'}$strNewNameSpace");
        ##print "error " . Win32::OLE->LastError() . "\n";
        recurseNameSpace ($objServiceTmp,$strNewNameSpace,$i++);
    }
    
    
    #my $objectHandle;
    #    connectWMI(\$objectHandle,$conf{'userName'}); ## passing a reference to the function
    #    recurseNameSpace ($objectHandle,$conf{'wmiNameSpace'},0);
        
        
}
  
  
###############################################################################################
## FUNCTION:    
##   recurseClasses ()
## 
## DESCRIPTION: 
###############################################################################################
sub recurseClasses {
      my (
          $objService,
          $strClass,
          $i,
          $recurse,
      ) = @_;
      
      if ("x$i" eq "x") { $i = 0; }
      my $colClasses = $objService->SubclassesOf("$strClass");
      my $count = 0;
      my $dynamicCount = 0;
      my @final = ();
      print STDERR "Querying..." unless ($recurse);
      foreach my $class (in($colClasses)) {
          print STDERR "." unless ($count % 15 || $recurse);
          $count++;
          my $padding = "";
          if (! $recurse) {
              foreach my $objClassQualifier (in($class->{Qualifiers_})) {
                  #print $class->{Path_}->{Class} . " => " . $objClassQualifier->{Name} . ":" . $objClassQualifier->{Value} . "\n" if ($objClassQualifier->{Name} eq "dynamic");
                  if ($objClassQualifier->{Name} eq "dynamic") {
                      #print "$padding" . $class->{Path_}->{Class} . "\n";
                      $dynamicCount++;
                      push (@final,$class->{Path_}->{Class} . "\n");
                  }
              }
          }
          else {
              for (my $j = 0;$j<$i;$j++) {
                  $padding.=" ";
              }
              print "$padding" . $class->{Path_}->{Class} . "\n";
              recurseClasses ($objService,$class->{Path_}->{Class},$i+1,$recurse);
          }
          
      }
      if (! $recurse) {
          print STDERR "\n";
          print sort(@final);
      }
      printmsg ("Total top level Classes: $count, $dynamicCount dynamic classes shown",0) unless ($recurse);
}
  




###############################################################################################
## FUNCTION:    
##   filterCheckOSVersion ()
## 
## DESCRIPTION: 
###############################################################################################
sub filterCheckOSVersion {
    
    ## if not XP or 2003, need to indicate that -f option won't work.
    
    ## unless doing a NULL query
    #if ($conf{'wmiFilter'} =~ /NULL/) {
    #    ## doesn't apply, so skip checking
    #    return 0;
    #}
    
    ## bind to WMI
    my $objectHandle;
    connectWMI(\$objectHandle,$conf{'username'},"","/root/cimv2");
    
    ## check OS version
    printmsg ("Checking OS Version to ensure -f flag is valid",2);
    
    my $result = $objectHandle->execQuery("SELECT Version from Win32_OperatingSystem") || die ("Error trying to query WMI for OS Version");
    if (scalar(in($result)) lt "1") {
        printmsg ("WARNING OSCheck => Check the computer and class name.  No information was received from the specified class. wmi query: [SELECT Version from Win32_OperatingSystem]",0);
        quit ("ERROR while trying to verify OS compatibility with the -f option : " . Win32::OLE->LastError(),1);
    }
    
    foreach my $instance (in($result)) {
        printmsg ("INFO => OS Version is:" . $instance->{Version},2);
        my $version = $instance->{Version};
        if ($version =~ /^5\.[^0]/) {
            printmsg ("INFO => Version indicates this is XP or 2003 or higher, proceeding with use of -f flag",1);
        }
        elsif ($version =~ /^5\.0/) {
            quit ("ERROR => the -f flag can only be used on XP or 2003 systems. On Windows 2000, use the -r flag instead to do a regular expression filter.  See '$conf{'programName'} -h' for details",1);
        }
        else {
            quit ("ERROR => the -f flag can only be used on XP or 2003 systems. On older versions of Windows, use the -r flag instead to do a regular expression filter.  See '$conf{'programName'} -h' for details",1);
        }
    }
    
    
    
}




  
  
  
  








###############################################################################################
## FUNCTION:    
##   writeList ()
## 
## 
## DESCRIPTION: 
##   uses the @globalOutput array and goes through it line by line and outputs the results to screen 
##
## Example: 
##   writeList ();
##
###############################################################################################
sub writeList {
    
    ## Get incoming variables
    my %incoming = ();
    (
      $incoming{'arrayPointer'},
      $incoming{'destination'},
    ) = @_;
    
    my @globalCopy = @globalOutput;
    my $sep = "~";
    
    #my $headerline = "SERVERNAME\tWMI NAME SPACE\tWMI CLASS\tPROPERTIES\n";
    my $i = 0;
    my @header = "";
    my @properties = "";
    my $propCount = 0;
    foreach my $line (@globalCopy) {
        printmsg ("globalCopy Line: [$line]",3);
        ## format output
        if ($conf{'output'} =~ /list/i) {
            if ($i == 0) {
                my @numElements = split(/$sep/,$line);
                my $numElements = scalar(@numElements);
                printmsg ("number of elements to process is: " . $numElements . " First line is: $line",4);
                @properties = split (/$sep/,$line);
                $propCount = $numElements;
            }
            elsif ($i == 1) {
                @header = split (/$sep/,$line);
                print "Server Name: " . $header[0] . "\n" unless ($conf{'nobanner'});
                print "WMI NameSpace: " . $header[1] . "\n" unless ($conf{'nobanner'});
                print "WMI Class: " . $header[2] . "\n" unless ($conf{'nobanner'});
                print "\n\n" unless ($conf{'nobanner'});
                print "[instance $i]\n";
                for (my $field = 3;$field < $propCount; $field++) {
                    printmsg("Now processing record $field:" . $properties[$field] . "-" . (split(/$sep/,$line))[$field],4);
                    if ($conf{'noheader'}) {
                        printmsg ("Only printing values since --noheader was specified",5);
                        if ("x" . (split(/$sep/,$line))[$field] ne "x") {
                            printf ("%s\n", (split(/$sep/,$line))[$field]); #$properties[$field] . " : " . (split(/$sep/,$line))[$field] . "\n";
                        }
                    }
                    else {
                        printmsg ("Printing entire string since didn't specify --noheader",5);
                        printf ("%-30s = %s\n", $properties[$field], (split(/$sep/,$line))[$field]); #$properties[$field] . " : " . (split(/$sep/,$line))[$field] . "\n";
                    }
                    #printf ("%-20s = %s\n", $properties[$field], (split(/$sep/,$line))[$field]); #$properties[$field] . " : " . (split(/$sep/,$line))[$field] . "\n";
                }

            }
            elsif ($i >=2) {
                print "\n[instance $i]\n";
                for (my $field = 3;$field < $propCount; $field++) {
                    printmsg("Now processing record $field:" . $properties[$field] . "-" . (split(/$sep/,$line))[$field],4);
                    if ($conf{'noheader'}) {
                        printmsg ("Only printing values since --noheader was specified",3);
                        printf ("%s\n", (split(/$sep/,$line))[$field]); #$properties[$field] . " : " . (split(/$sep/,$line))[$field] . "\n";
                    }
                    else {
                        printmsg ("Printing entire string since didn't specify --noheader",3);
                        printf ("%-30s = %s\n", $properties[$field], (split(/$sep/,$line))[$field]); #$properties[$field] . " : " . (split(/$sep/,$line))[$field] . "\n";
                    }
                }
            }
        }
        else {
            if (! $conf{'nobanner'} && $i==0) {
                print "Server Name: " . $conf{'remoteServer'} . "\n";
                print "WMI NameSpace: " . $conf{'wmiNameSpace'} . "\n";
                print "WMI Class: " . $conf{'wmiClass'} . "\n";
                print "\n\n";
            }
            $line =~ s/.*?$sep.*?$sep.*?$sep// if ($conf{'nodetail'});
            $line =~ s/$sep/\t/g if ($conf{'output'} =~ /tab/i);
            $line =~ s/$sep/,/g if ($conf{'output'} =~ /csv/i);
            $line =~ s/\,$//;
            print "$line\n" unless ($i == 0 && $conf{'noheader'});
        }
        
        ## increment counter
        $i++;
    }
    
    print "\n\nIf you need the output in a format easily imported into a \nspreadsheet program, run '$conf{'programName'} -h' and read \nabout the -o option\n" if (($conf{'output'} =~ /list/i) && ! $conf{'nobanner'});
     
}



  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  



###############################################################################################
## FUNCTION:    
##   writeHTML ()
## 
## 
## DESCRIPTION: 
##   uses the @globalOutput array and goes through it line by line and outputs the results to a 
##   HTML formatted file.
##
## Example: 
##   writeHTML ();
##
###############################################################################################
sub writeHTML {
my $date = localtime();
if (! open (OUTHTML,">$conf{'htmlOutput'}") ) { 
        printmsg ("ERROR: Cannot open html file for writing: $!",0);
        return (2);
    }
    
    my @globalCopy = @globalOutput;
    
    ## print out nice HTML headers first
    print OUTHTML <<EOM;
        <html>\n
        <head>\n
        <style type="text/css">
            pre {
                font-family:verdana;
                font-size:11px;
                width:350px;
                overflow:scroll;
            }
            .header {
                border-bottom:5px solid black;
                text-align:center;
                font-weight:bold;
                background-color:#ffffcc;
                padding-top:40px;
                font-size:18px;
                font-family:times,arial;
            }
            .line {
                border:1px solid gray;
            }
            .nomatch {
                background-color:#ffb6c1;
                color:black;
                border-left:1px solid white;
                border-bottom:1px solid white;
                padding:3px;
            }
            .match {
                background-color:#ccffcc;
                border-left:1px solid gray;
                border-bottom:1px solid gray;
                padding:3px;
            }
            .none {
                background-color:#f0f8ff;
                border-left:1px solid gray;
                border-bottom:1px solid gray;
                padding:3px;
            }
            .matched {
                font-weight:bold;
                background-color:white;
            }
            .cell {
                font-family:times,arial;
                font-weight:bold;
                font-size:14px;
                border-left:15px solid #cccccc;
                border-bottom:2px solid #cccccc;
            }
            .hmatch {
                font-family:times,arial;
                font-weight:bold;
                font-size:14px;
                /*background-color:#66cc66;*/
                border-left:15px solid #66cc66;
                border-bottom:2px solid #66cc66;
                
            }
            .hnomatch {
                font-family:times,arial;
                font-weight:bold;
                font-size:14px;
                /*background-color:#ffb6c1;*/
                border-left:15px solid red;
                border-bottom:2px solid red;
                
            }
            td {
                padding-left:2px;
                padding-right:2px;
                padding-top:2px;
                font-family:arial;
                font-size:12px;
            }
            subDetail {
                font-size:9px;
                font-family:arial;
            }
            .plusminus {
                font-family:arial;
                font-size:14px;
                font-weight:bold;
                padding-right:0px;
            }
            
                    
        </style>
        
        
        
        

        
        <script language="javascript">
        
            /**
             * This debug function displays plain-text debugging messages in a
             * special box at the end of a document. It is a useful alternative
             * to using alert(  ) to display debugging messages.
             **/
            function debug(msg) {
                // If we haven't already created a box within which to display
                // our debugging messages, then do so now. Note that to avoid
                // using another global variable, we store the box node as
                // a proprty of this function.
                if (!debug.box) {
                    // Create a new <div> element
                    debug.box = document.createElement("div");
                    // Specify what it looks like using CSS style attributes
                    debug.box.setAttribute("style",
                                           "background-color: white; " +
                                           "font-family: monospace; " +
                                           "border: solid black 3px; " +
                                           "padding: 10px;");
                    
                    // Append our new <div> element to the end of the document
                    document.body.appendChild(debug.box);
             
                    // Now add a title to our <div>. Note that the innerHTML property is
                    // used to parse a fragment of HTML and insert it into the document.
                    // innerHTML is not part of the W3C DOM standard, but it is supported
                    // by Netscape 6 and Internet Explorer 4 and later. We can avoid
                    // the use of innerHTML by explicitly creating the <h1> element,
                    // setting its style attribute, adding a Text node to it, and
                    // inserting it into the document, but this is a nice shortcut.
                    debug.box.innerHTML = "<h1 style='text-align:center'>Debugging Output</h2>";
                }
             
                // When we get here, debug.box refers to a <div> element into which
                // we can insert our debugging message.
                // First create a <p> node to hold the message.
                var p = document.createElement("p");
                // Now create a text node containing the message, and add it to the <p>
                p.appendChild(document.createTextNode(msg));
                // And append the <p> node to the <div> that holds the debugging output
                debug.box.appendChild(p);
            }
        
        
            
            
        //////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////
        function collapseAll (matchMe) {
            toProcess = document.getElementsByTagName("table");
            for (var i = 0; i < toProcess.length; i++) {
                if (toProcess[i].id) { //make sure this element has an id tag defined
                    if (toProcess[i].id.match(matchMe)) {
                        toProcess[i].style.display="none";    
                    }
                }
            }
        
        
        }
            
        
        
        
        //////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////
        function expandAll (matchMe) {
            
            toProcess = document.getElementsByTagName("table");
            for (var i = 0; i < toProcess.length; i++) {
                if (toProcess[i].id) { //make sure this element has an id tag defined
                    if (toProcess[i].id.match(matchMe)) {
                        toProcess[i].style.display="";    
                    }
                }
            }
            
        
        }
        
        
        function hide (matchMe) {
            toProcess = document.getElementsByTagName("span");
            for (var i = 0; i < toProcess.length; i++) {
                if (toProcess[i].id) {
                    if (toProcess[i].id.match(matchMe)) {
                        toProcess[i].style.display="none";
                    }
                }
            }
        }
        
        
        function show (matchMe) {
            toProcess = document.getElementsByTagName("span");
            for (var i = 0; i < toProcess.length; i++) {
                if (toProcess[i].id) {
                    if (toProcess[i].id.match(matchMe)) {
                        toProcess[i].style.display="";
                    }
                }
            }
            
            //document.all.subD_APP.style.display= document.all.subD_APP.style.display=='none' ? '' : 'none'\" title=\"Click to display details\">
        }
        
        </script>

        
        
        </head>\n
        <body>\n
        <h1>$ENV{COMPUTERNAME} Audit report</h1>
        <h3>$date</h3>
        <table border="0" cellspacing="0" cellpadding="0" width="200" >
            <tr>
            <td><a href="#" onclick="expandAll('header_');show('minus');hide('plus');return false;">Expand All</a></td> 
            <td><a href="#" onclick="collapseAll('header_');show('plus');hide('minus');return false;">Collapse All</a></td>
            <td><a href="#" onclick="window.location.reload(false);">Refresh page</a></td>
            
            <td></td>
            </tr>
        </table>
        <br><br>
        <hr>
        <table border=0 cellspacing=0 cellpadding=0>\n
EOM
    
        
   
    my $header = "UNDEF"; ## flag to indicate whether headers have been printed yet
    my $ctr = 0;
    my $headerClass = "cell";    
    my @output = ();
    my $pastFirstHeader = 0;
    foreach my $line (@globalCopy) {
        $ctr++;  # used to create unique div id's
        my $i = $ctr . "_$conf{'remoteServer'}";
	my $class = "none";  ## used for CSS formatting
        
        if ($line =~ /^:::/) {  ## This line indicates a group header to be processed.
            $header = ":::";
            
            ## unless this is the first header encountered, we need to flush the output array
            ## in preparation for the next header and sub objects.
            if (! $pastFirstHeader) {
                ## do nothing
                printmsg ("At the first header during HTML processing",4);
                ## increment pastFirstHeader now that we've encountered the first one
                $pastFirstHeader++;
            }
            else { ## print out what's in @output 
                push (@output,"</table>\n");
                $output[0] =~ s/HEADERCLASS/$headerClass/g;
                print OUTHTML @output;
                ## add another to pastFirstHeader count
                $pastFirstHeader++;
            }
            
            @output = ();
            $headerClass = "cell";
            my $tmp = substr($line,rindex($line,":::")+3);
            $header =     "<tr><td valign=\"bottom\" colspan=\"99\" style=\"cursor: hand;\" onclick=\"document.all.header_$i.style.display=(document.all.header_$i.style.display)=='none'?'':'none';
                                                                    document.all.plus_$i.style.display=(document.all.plus_$i.style.display)=='none'?'':'none';
                                                                    document.all.minus_$i.style.display=(document.all.minus_$i.style.display)=='none'?'':'none';\"
                                                                    title=\"Click to Display Details\">
                           <table border=0 cellspacing=0 cellpadding=0>
                           <tr>
                           <td><span id=\"plus_$i\" valign=\"top\" class=\"plusminus\" style=\"display:;\">+</span><span id=\"minus_$i\" valign=\"top\" class=\"plusminus\" style=\"display:none;\">&nbsp;-</span></td>
                           <td class=\"HEADERCLASS\">$tmp</td></tr></table>
             
                               
                               
                               
                          </td></tr>
                          <tr><td>
                          <table border=0 cellspacing=0 cellpadding=0 id=\"header_$i\" style=\"display:none;border:1px solid black;margin-left:30px;margin-top:5px;margin-bottom:15px;\">
                           <tr>
                            <td valign=\"top\" class=\"none\" width=250>
                                <center><b>Description</b></center>
                            </td>
                            <td valign=\"top\" class=\"none\">
                                <center><b>Inspected Element</b></center>
                            </td> 
                            <td valign=\"top\" class=\"none\">
                                <center><b>Audit Type</b></center>
                            </td>
                            <td valign=\"top\" class=\"none\">
                                <center><b>Match Status</b></center>
                            </td>
                           </tr>";
            push (@output,$header);
        }
        
        ## format it with table code
        if ($line =~ /\|NOMATCH\|/) { ## no match
            $class = "nomatch";
            $headerClass= "hnomatch";
        }
        elsif ($line =~ /\|MATCH\|/) { ## match
            $class = "match";
            $headerClass = "hmatch" unless ($headerClass =~/nomatch/);
        }
        elsif ($line =~ /\|NOCOMPARE\|/) { ## don't care
            $class = "none";
        }

        if ($line =~ /^:::/) { $line = ""; } ## this indicates a group header line
        
        
        # 0 SERVERNAME
        # 1 SECTIONNAME
        # 2 AUDITTYPE
        # 3 DESCRIPTION
        # 4 MATCHSTATUS
        # 5 InspectedElement
        # 6 MatchString
        # 7 Output
        
        my @details = split(/\|/,$line);
        
        ## format inspected element differently for LDAP type queries for HTML output
        if ($details[2] eq "LDAP") {
            $details[5] =~ s/(.*)(ATTRIBUTE.*)/$2/;
        }
        
        my $entry = "<td class=\"$class\" valign=\"top\">" . $details[3]
                    . "</td><td class=\"$class\" valign=\"top\" "
                    . "style=\"color:blue\; cursor: hand\;\" onclick=\"document.all.subD_$i.style.display= document.all.subD_$i.style.display=='none' ? '' : 'none'\" title=\"Click to display details\" >"
                    . $details[5]
                    . "</td><td class=\"$class\" valign=\"top\">" . $details[2] . "</td>"
                    . "<td class=\"$class\" valign=\"top\">" . $details[4]
                    . "</td>";
                    
        #$line =~ s/\|/\<\/td\>\<td class="$class" valign="top"\>/g;
        my $subDetail = "";
        my $z = 0;
        my @headers = ("ServerName","SectionName","AuditType","Description","MatchStatus","InspectedElement","MatchString","Output");
        foreach my $detail (split(/\|/,$line)) {
            $subDetail .= "<li class=\"subDetail\"><b>$headers[$z]</b> : $detail";
            $z++;
        }
        
        $subDetail = "<tr><td></td><td colspan=2 valign=\"top\" id=\"subD_$i\" style=\"display:none\">$subDetail</td></tr>";
        $line = "<tr>$entry</tr>";
        $line .= $subDetail . "\n";
        
        ## fix match strings for CMD output
        $line =~ s/:MATCHED-:/<font color="#e9967a"><b>/g;
        $line =~ s/:-MATCHED:/<\/font><\/b>/g;
	## convert newlines to <Br>'s
	$line =~ s/\\n/\<br\>/g;
        
        if ($class eq "nomatch") {
            #print OUTHTML "<!--:::NOMATCH=>$ENV{COMPUTERNAME}:::-->$line\n";
            push (@output, "<!--:::NOMATCH=>$ENV{COMPUTERNAME}:::-->$line\n");
        }
        else { 
            #print OUTHTML "$line\n";
            push (@output, "$line\n");
        }
        
    }
    
    ## perform final section write
    push (@output,"</table>\n");
    $output[0] =~ s/HEADERCLASS/$headerClass/g;
    print OUTHTML @output;
    
    ## print out nice closing HTML code
    print OUTHTML <<EOM;
        </table>
        </body>
        </html>
EOM
    
    close (OUTHTML);
}















###############################################################################################
## FUNCTION:    
##   writeTAB ()
## 
## 
## DESCRIPTION: 
##   uses the @globalOutput array and goes through it line by line and outputs the results to a 
##   tab-delimited file.
##
## Example: 
##   writeTAB ();
##
###############################################################################################
sub writeTAB {
    
    ## Get incoming variables
    my %incoming = ();
    (
      $incoming{'arrayPointer'},
      $incoming{'destination'},
    ) = @_;
    
    my @globalCopy = @globalOutput;
    
    
    if ($incoming{'destination'} eq "SCREEN") { 
    }
    elsif (! open (OUTTAB,">$conf{'tabOutput'}") ) { 
        printmsg ("ERROR: Cannot open tab-delimited file for writing: $!",0);
        return (2);
    }
    my $header = "UNDEF"; ## flag to indicate whether headers have been printed yet


    my $headerline = "SERVERNAME\tSECTIONNAME\tAUDITTYPE\tDESCRIPTION\tMATCHSTATUS\tInspectedElement\tMatchString\tOutput\n";
    if ($incoming{'destination'} eq "SCREEN") {
        print $headerline;
    }
    else {
        print OUTTAB $headerline;
    }
    
    foreach my $line (@globalCopy) {
        printmsg ("globalCopy Line: [$line]",3);
        if ($line =~ /^:::/) {  ## This line indicates a group header to be processed.
            ## do nothing
            printmsg ("Group header, doing nothing",3);
        }
        ## if CMD output, we won't display the output because it's an array
        elsif ($line =~ /\|CMD\|/) {
            printmsg ("processing CMD: [[$line]]",3);
            $line =~ s/(.*)\|.*$/$1\|ARRAY-therefore not displayed/gs unless ($line !~ /\n/g);
            #$line =~ s/\n/\\n/g;
        }
        
        ## format it with tabs
        $line =~ s/\|/\t/g;
        if ($incoming{'destination'} eq "SCREEN") {
            print "$line\n" unless ($line =~ /^:::/);
        }
        else {
            print OUTTAB "$line\n" unless ($line =~ /^:::/);
        }
        
        
    }
    
     if ($incoming{'destination'} eq "SCREEN") {}
     else {close (OUTTAB);}
    
}










