#!/usr/bin/perl -w
# nagios: -epn

################################################
#
# Checks for WMI-Counters over the Samba4 wmic-Command
#
# Thomas Sesselmann <t.sesselmann@dkfz.de> 2010
#
# This 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 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
# Changelog:
# 0.11v 2011.01.21 (ts)
#   * bugfix in perfdata: sprintf "%u" would not work (overflow) with large integers
#   * service: output the full name of the not ok service in long output
#   * MSCLUSTER: add ActiveNode to each ResourceGroup
#   * fix/hack for wmic versions without --delimiter (you have to set delimiter to wmic default '|' !!)
# 0.10v 2010.11.29 (ts)
#   * more informations/doku in usage (separate warn for each device in volfree possible ...)
#   * diskfree/volfree possible to query only specific devices (with cparam)
#   * bugfix/workaround CPULOAD (Timestamp move forward big values ?!?)
# 0.9v  2010.10.22 (ts)
#   * rename RESGROUP to MSCLUSTER
#   * check for NTERROR (NTSTATUS)
#   * added option to affect the returncode by error (UNKONWN or CRITICAL) 
#     defaults for some checks differ
# 0.8v  2010.10.06 (ts)
#   Version at the OSMC2010
#   runs with some checks (volfree/diskfree/system/cpuload/memfree/services/procs/users)
#
####


use strict;
use warnings;
use Getopt::Long;    # libgetopt-mixed-perl perl-Getopt-Mixed
use Date::Parse;     # perl-TimeDate
#use Math::BigInt;

our %ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4);
our %R_ERRORS=(); foreach ( keys %ERRORS ) { $R_ERRORS{$ERRORS{$_}} = $_; }
#$ENV{LANG} = "C";

#TODO
# Win32_TerminalService
# ...
# exchange
# ad
# MSSQL
# Citrix
# stat_disk (IO)
# stat_net



our $check;
our $childpid;

our %opts = (
  help      => 0,
  verbose   => 0,
  debug     => 0,
  user      => 'AD/nagios',
  password  => 'xxx',
  wmic      => '/usr/local/nagios-plugins/wmic',
  delimiter => '-xXx-',
  host      => '',
  check     => '',
  warning   => '',
  critical  => '',
  cparam    => '',
  timeout   => 10,
  onerror   => '',
  tmpdir    => '/tmp/check_wmi',
);
my %check_def=( 
   diskfree => {
     w => '10%', c => '5%',
     object    => 'Win32_LogicalDisk',
     #namespace => 'root/cimV2',
     attr      => 'Caption,DeviceID,FreeSpace,Size,DriveType,VolumeName,FileSystem',
     condition => 'DriveType = 3', # 0-Unknown, 1-NoRootDir, 2-RemoveableDisk, 3-LocalDisk, 4-NetworkDrive, 5-CD, 6-RAM-Disk
     preroutine => \&pre_diskfree,
     routine   => \&wmicheck_diskfree,
     help_txt  => "DISKFREE\t - Free disk space\n"
                 ."\t\t   cparam is a list of disks to query\n"
                 ."\t\t   warning (and crititcal) are possible for each device and in % or B, KB, MB, GB, TB\n"
                 ."\t\t   Example:  -a 'C:,L:' -w '20%,C:50%,L:200GB' -c '10%'",
   },
   volfree => {
     w => '10%', c => '5%',
     object    => 'Win32_Volume',  
     attr      => 'Caption,DeviceID,FreeSpace,Capacity,DriveType,DirtyBitSet,Label,FileSystem',
     condition => 'DriveType = 3', # 0-Unknown, 1-NoRootDir, 2-RemoveableDisk, 3-LocalDisk, 4-NetworkDrive, 5-CD, 6-RAM-Disk
     preroutine => \&pre_diskfree,
     routine   => \&wmicheck_diskfree,
     help_txt  => "VOLFREE\t - Free disk space (check volumes)\n"
                 ."\t\t   cparam is a list of volumes to query ('/' will be translated to '\\')\n"
                 ."\t\t   warning (and crititcal) also lists and in %, B, KB, MB, GB or TB\n"
                 ."\t\t   Example:  -a 'C:,L:/SG25-SG30' -w '20%,C:50%,L:/SG25-SG30:200GB' -c '10%'",
   },
   'system' => {
     object    => 'Win32_OperatingSystem',
     attr      => 'Caption,CSDVersion,CSName,Version,InstallDate,LastBootUpTime,LocalDateTime,Status,TotalVirtualMemorySize,TotalVisibleMemorySize',
     routine   => \&wmicheck_system,
     help_txt  => "SYSTEM\t - Gets system name and uptime"
   },
   'cpuload' => {
     w => '90%', c => '95%',
     #object    => 'Win32_PerfFormattedData_PerfOS_Processor',
     object    => 'Win32_PerfRawData_PerfOS_Processor',
     attr      => 'PercentIdleTime,PercentInterruptTime,PercentPrivilegedTime,PercentUserTime,Frequency_Sys100NS,Timestamp_Sys100NS',
     condition => 'Name = "_Total"',
     subquery1 => 'SELECT ProcessorQueueLength FROM Win32_PerfRawData_PerfOS_System',
     routine   => \&wmicheck_cpuload,
     help_txt  => "CPULOAD\t - Gets (total) CPU load in avg (use temp file to calculate)"
   },
   'memfree' => {
     w => '0', c => '0',  #FIXME other defaults (10% + 100M ?) => also with virtual mem 
     object    => 'Win32_PerfRawData_PerfOS_Memory',
     attr      => 'CommittedBytes,AvailableBytes,CacheBytes,PoolNonpagedBytes,PoolPagedBytes,CacheFaultsPersec,PagesPersec,TimeStamp_Sys100NS,Frequency_Sys100NS',
     routine   => \&wmicheck_memfree,
     subquery1 => 'SELECT FreeSpaceInPagingFiles, FreeVirtualMemory, TotalVirtualMemorySize, TotalVisibleMemorySize FROM Win32_OperatingSystem',
     help_txt  => "MEMFREE\t - Gets memory and pagefile usage"
   },
   'procs' => {
     w => '200', c=> '500',
     object    => 'Win32_OperatingSystem',
     attr      => 'NumberOfProcesses',
     routine   => \&wmicheck_procs,
     help_txt  => "PROCS\t - Get number of running processes"
   },
   'users' => {     
     w => '200', c=> '500',
     object    => 'Win32_OperatingSystem',
     attr      => 'NumberOfUsers',
     routine   => \&wmicheck_users,
     help_txt  => "USERS\t - Get number of connected users"
   },
   'service' => {
     w => 1, c => 3, cparam => 'SysmonLog,ShellHWDetection,sppsvc,clr_optimization_v4.0.30319_32,clr_optimization_v4.0.30319_64,TBS,wecsvc,spupdsvc',
     object    => 'Win32_Service',
     attr      => 'Caption,Started,StartMode,State,Status,ProcessId',
     #condition => 'StartMode = "Auto" AND State != "Running"',
     routine   => \&wmicheck_service,
     help_txt  => "SERVICE\t - Check service state\n"
                 ."\t\t   cparam is a list of services to ignore\n"
                 ."\t\t   if -w or -c set, only this services are queried to run or with prefix '^' or '!' not to run",
   },
   'mscluster' => {
     onerror   => 'CRITICAL',
     object    => 'MSCluster_ResourceGroup',
     namespace => 'root/MSCluster',
     attr      => 'State',
     subquery1 => 'SELECT Name,PersistentState,State,Type FROM MSCluster_Resource WHERE State != 2',
     subquery2 => 'SELECT GroupComponent,PartComponent FROM MSCluster_NodeToActiveGroup',
     routine   => \&wmicheck_mscluster,
     help_txt  => "MSCLUSTER\t - Gets MSCluster (resource group) status"
   },
   'resgroup' => {
     obsolete  => 'MSCLUSTER', # obsolete use MSCLUSTER now!! #TODO delete at next versions
   },
   'dummy' => {
     #object    => 'Win32_Session',
     #object    => 'Win32_TerminalService',  # >= Win2008
     #object    => 'Win32_PerfRawData_TermService_TerminalServices',  # < Win2008
     #object    => 'Win32_PerfRawData_TermService_TerminalServicesSession',
     object    => 'Win32_PerfFormattedData_CitrixICA_ICASession',

     #object    => 'MSCluster_Resource',
     #namespace => 'root/MSCluster',
     #object    => 'MSAD_DomainController', #MSAD_ReplNeighbor
     #namespace => 'root/MicrosoftActiveDirectory',
     #condition => 'State != 2', # -1 StateUnknown, 0 Inherited, 1 Initializing, 2 Online, 3 Offline, 4 Failed, 128 Pending, 129 Online Pending, 130 Offline Pending
     #attr      => 'Name,PersistentState,State,Type',
     #object    => 'Win32_Volume',  #Capacity, FreeSpace, FileSystem, DriveType, Caption, Name, Label, DirtyBitSet, SystemName
     #object    => 'Win32_PhysicalMemory',
     #object    => 'Win32_PerfFormattedData_PerfOS_Processor',  # _Total
     #object    => 'Win32_PerfRawData_PerfOS_Processor', # _Total
     #object    => 'Win32_Service', # state, startmode,...
     #'SELECT * FROM Win32_NTLogEvent WHERE Type="Error" AND LogFile="System" AND TimeGenerated > "20100622000000.000000+120"
     routine   => \&wmicheck_dummy,
   },
);


sub start_stuff();
sub print_exit($$);
sub check_wmic();
sub run_query($);
sub read_tmpfile($);
sub write_tmpfile($$);




## Get Options
start_stuff();

## Check
check_wmic();






###############################################################

### Start Stuff (Get Options ... )
sub start_stuff()
{

  ## get options
  Getopt::Long::Configure("no_ignore_case");
  Getopt::Long::GetOptions( \%opts, 
    'help|h',
    'verbose|v',
    'debug|d',
    'user|U=s',
    'password|P=s',
    'host|H=s',
    'wmic=s',
    'delimiter=s',
    'warning|w=s',
    'critical|c=s',
    'timeout|t=i',
    'cparam|a=s',
    'tmpdir=s',
    'onerror=s',
    ) or $opts{help}=1;


  ## checks:
  $opts{help} = 1 if ( scalar @ARGV != 1 );
  $opts{help} = 1 if ( $opts{host} eq "" );
  unless ( -x $opts{wmic} ) {
    $opts{help} = 1;
    warn "Error: Can't find WMIC ('$opts{wmic}').\n";
  }
  $opts{verbose} = 1 if $opts{debug};
  $opts{delimiter} = "|" unless $opts{delimiter};
  
  my $checkstr = lc shift @ARGV;
  if ( exists $check_def{$checkstr} ) {
    $check = $check_def{$checkstr};
    if ( exists $check->{obsolete} ) { 
      printf "'%s' is OBSOLETE please use '%s'! ",uc $checkstr,$check->{obsolete};
      $check = $check_def{lc $check->{obsolete}};
    }

    if (( $opts{warning} eq "" ) and ( defined $check->{w} )) {
      $opts{warning} = $check->{w};
    }
    if (( $opts{critical} eq "" ) and ( defined $check->{c} )) {
      $opts{critical} = $check->{c};
    }
    if (( $opts{cparam} eq "" ) and ( defined $check->{cparam} )) {
      $opts{cparam} = $check->{cparam};
    }
    if (( $opts{onerror} eq "" ) and ( defined $check->{onerror} )) {
      $opts{onerror} = $check->{onerror};
    } else {
      $opts{onerror} = "UNKNOWN";
    }
  } else {
    $opts{help}=1;
    warn "Error: Check '$check' not defined!\n";
  }

  $opts{onerror} = $R_ERRORS{$opts{onerror}} if exists $R_ERRORS{$opts{onerror}};
  if ( not exists $ERRORS{$opts{onerror}} ) {
    warn "Error: No valide return-code on error '$opts{onerror}'\n";
    $opts{help} = 1;
  }


  
  ## Usage:
  if ( $opts{help} )  {
    my $usage = "Checks for WMI-Counters over the Samba4 wmic-Command

Usage: $0 [Options]* [check]
Options:
     -h, --help          print this help message
     -v, --verbose       verbose output (Default: '$opts{verbose}')
     -d, --debug         very verbose output (Default: '$opts{debug}')
     -U, --user=<s>      Domain User with WMI-Read rights (Default: '$opts{user}')
     -P, --password=<s>  Password for User
     -H, --host=<s>      Host to query (Default: '$opts{host}')
     --wmic=<s>          wmic-command (Default: '$opts{wmic}')
     --delimiter=<s>     delimiter of the wmic-command (Default: '$opts{delimiter}')
     -w, --warning=<s>   Warning Level (Default: '$opts{warning}')
     -c, --critical=<s>  Critical Level (Default: '$opts{critical}')
     -a, --cparam=<s>    additional parameters for the check (Default: '$opts{cparam}')
     -t, --timeout=<d>   Timeout for this script (Default: '$opts{timeout}')
     --oneror=<s>        return-code/state on error, id or name is possible (Default: '$opts{onerror}')
     --tmpdir=<s>        Temporary directory for some checks/stats (Default: '$opts{tmpdir}')
Check:\n";
    foreach my $c ( sort keys %check_def ) {
      if ( exists $check_def{$c}{help_txt} ) {
        $usage .= "    $check_def{$c}{help_txt}\n";
      }
    }
    $usage .= "Hints:
Try to use '$0 -h [check]' to see the defaults for the check!
As delimiter for the lists in parameters (w,c,a) you can use/try ',' or ';', space only sometimes.
You can get the wmic-command (source) at: http://dev.zenoss.org/svn/trunk/inst/externallibs/ => wmi-*.tar.bz2
";
    die "$usage\n";
  }

}


### Print Message and Exit with result-code
sub print_exit($$) 
{
  my $msg = shift;
  my $return_code = shift;

  print "$msg";
  exit $return_code;
}


### Checks and run the evaluation routine ... 
sub check_wmic()
{

  if ( exists $check->{preroutine} ) {
    &{ $check->{preroutine} }()
  }

  my $query = "SELECT ";
  if ( $opts{debug} or not exists $check->{attr} ) {
    $query .= "*";
  } else {
    $query .= $check->{attr};
  }
  $query .= " FROM $check->{object}";
  $query .= " WHERE $check->{condition}" if ( exists $check->{condition} and $check->{condition} );


  ## timeout
  local $SIG{'ALRM'} = sub { 
    #kill $childpid;
    #sleep 2;
    kill 9,$childpid;
    print_exit("UNKNOWN - timeout after $opts{timeout} seconds.\n",$ERRORS{UNKNOWN});
  };
  alarm($opts{timeout});

  ## run query 
  my ($wmi_err,@wmic_out) = run_query($query);


  my $status = "OK";
  my $out = "";
  my $perf_data = "";

  if ( $wmi_err ) {
    $status = $opts{onerror};
    $out = "get ERRORS: ($wmi_err)";
  } elsif ( @wmic_out ) { 
    if ( exists $check->{routine} ) {
       ( $status, $out, $perf_data ) = &{ $check->{routine} }(\@wmic_out);
    } else {
      $status = "UNKNOWN";
      $out = "No routine for evaluation defined!";
    }
  } else {
    $status = $opts{onerror};
    $out = "No NTERROR but no data (WMI not available?)!";
  }

  ## Timeout off
  alarm(0);

  print_exit("$status - $out|$perf_data\n",$ERRORS{$status});
}


## Run Query for WMI
sub run_query($)
{
  my $query = shift;

  my $cmd = "$opts{wmic} -U $opts{user}%$opts{password} ";
  $cmd .= "--namespace=$check->{namespace} " if ( exists $check->{namespace} and $check->{namespace} );
  $cmd .= "--delimiter=$opts{delimiter} " if $opts{delimiter} ne "|";
  $cmd .= "//$opts{host} ";

  $query =~ s/'/"/g;
  $cmd .= "'$query'";

  my @out = ();
  my @errors = ();
  print "Run Query '$query'\n" if $opts{verbose};
  print "debug>$cmd\n" if $opts{debug};
  $childpid = open TMP, "$cmd 2>&1 |" or die "Error: Can't open pipe for command '$cmd': $!\n";

  my %keys = ();
  my $head = 0;
  my $nkeys = 0;
  while(<TMP>) { 
    chomp $_;
    #s/^\S*\s*//;
    print "debug>$_\n" if $opts{debug};
    print "$.>> " if $opts{verbose};

    ## Class
    if ( $_ =~ /^CLASS: (.*)/ ) { 
      print "Class: $1\n" if $opts{verbose};
      $head = 1;
      next;
    }

    ## Header
    if ( $head ) { # Line 1 is info to CLASS
      my $i = 0;
      foreach my $k ( split /$opts{delimiter}/,$_ ) { 
        $keys{$i}="$k";
        #printf "debug> key $i: $k\n" if $opts{debug};
        $i++;
      }
      print "Header\n" if $opts{verbose};
      $head = 0;
      $nkeys = scalar keys %keys;
      next;
    } else {
      print "\n" if $opts{verbose};
    }

    ## Data
    if ( $nkeys ) {
      my $i = 0;
      my %vals = ();
      foreach my $v ( split /$opts{delimiter}/,$_ ) {
        $vals{$keys{$i}} = $v;
        printf " $i %s = %s\n",$keys{$i},$v if $opts{verbose};
        $i++;
      }
      push @out, \%vals;
      next;
    }

    ## Errors?
    if ( $_ =~ /NTSTATUS:\s*(.+)\s*-\s*(.+)\s*/ ) {
      my $err = $1;
      $err = $1 if ( $err =~ /NT code (.+?)\s*$/ );
      push @errors,$err;
      printf "$_\n" if $opts{verbose};
      next;
    }
    warn "Warning: Unable to pares line: '$_'\n";

  }
  close TMP or warn $! ? "Warning: Error closing pipe: $!" : "Warning: Exit status $? for '$cmd'\n";

  return join(",",@errors),@out;
}


## Read Data from Tempfile if exists (to hash key=timestamp of hash(key/values from WMI)
sub read_tmpfile($)
{
  my $file = shift;

  my %data = ();

  if ( $opts{tmpdir} and -e "$opts{tmpdir}/$file" ) {
    if ( open FILE, "<$opts{tmpdir}/$file" ) {
      my $t = 0;
      while (<FILE>) {
        chomp $_;
        if ( $_ =~ /^\d+$/ ) {
          $t = $_;
          $data{$t} = {};
        }
        if ( $t and $_ =~ /^([^=]+)=(.*)$/ ) {
          $data{$t}->{$1} = $2;
        }
      }
      close FILE;
    } else {
      warn "Warning: Can't open (existing!) temporary file '$opts{tmpdir}/$file'\n";
    }
  }

  return \%data;
}


## Write Data to Tempfile (hash key=timestamp of hash(key/values from WMI)
sub write_tmpfile($$)
{
  my $file = shift;
  my $data = shift;

  if ( $opts{tmpdir} ) {
    unless ( -e $opts{tmpdir} ) {
      unless ( mkdir "$opts{tmpdir}" ) {
        warn "Warning: Can't create temporary directory '$opts{tmpdir}': $!\n";
        return;
      }
    }

    if ( open FILE, ">$opts{tmpdir}/$file" ) {
      foreach my $t ( reverse sort keys %$data ) {
        print FILE "$t\n";
        foreach my $k ( keys %{ $data->{$t} } ) {
          print FILE "$k=$data->{$t}->{$k}\n";
        }
        print FILE "\n";
      }
    } else {
      warn "Warning: Can't open file '$opts{tmpdir}/$file' for writing: $!\n";
    }
  }
}


## Check Dummy routing
sub wmicheck_dummy($)
{
  my @lines = @{ ( shift ) };

  my $status = "OK";
  my $out = "Dummy: ";
  my $perf_data = "";

  return $status, $out, $perf_data;
}


## Pre Diskfree -> select specific devices or all?
sub pre_diskfree()
{
  $check->{cparam} = $opts{cparam} if defined $opts{cparam};
  if ( $check->{cparam} ) {
    if ( $check->{object} eq "Win32_Volume" ) {
      $check->{cparam} =~ s!([,;]|$)!/$1!g; # add an / at the end
    }

    $check->{cparam} =~ s|/|\\\\|g;  # convert / to \ ...

    $check->{condition} .= "AND ( Caption = '"
      .join("' OR Caption = '",split(/\s*[,;]\s*/,$check->{cparam}))
      ."' )";
  }
}


## Check Diskfree
sub wmicheck_diskfree($)
{
  my @lines = @{ ( shift ) };

  my $status = "OK";
  my $out = "FREE Diskspace: ";
  my $perf_data = "";

  my %warn = ( default=>0, defaultU=>'B' );
  my %crit = ( default=>0, defaultU=>'B' );
  # -w "10% C:1GB" -c "5% C:100MB"   => is ok!
  foreach my $a ( split '[,;\s]+',$opts{warning} ) {
    if ( $a =~ /^([\d.]+)([KMGT]?B|%)$/i ) { $warn{default} = $1; $warn{defaultU} = uc $2; }
    if ( $a =~ /^(.+):([\d.]+)([KMGT]?B|%)$/i ) { $warn{$1} = $2; $warn{"${1}U"} = uc $3; }
  }
  foreach my $a ( split '[,;\s]+',$opts{critical} ) {
    if ( $a =~ /^([\d.]+)([KMGT]?B|%)$/i ) { $crit{default} = $1; $crit{defaultU} = uc $2; }
    if ( $a =~ /^(.+):([\d.]+)([KMGT]?B|%)$/i ) { $crit{$1} = $2; $crit{"${1}U"} = uc $3; }
  }


  if ( scalar @lines == 0 ) {
    $status = "UNKNOWN";
    $out .= "No Disks found!";
  }


  foreach my $a ( @lines ) {
    #next unless defined $a->{FileSystem};
    #next if $a->{FileSystem} eq "CDFS";
    #next if $a->{FileSystem} eq "UDF";

    #next unless $a->{DriveType} == 3; # 0-Unknown, 1-NoRootDir, 2-RemoveableDisk, 3-LocalDisk, 4-NetworkDrive, 5-CD, 6-RAM-Disk

    $a->{Size}        = $a->{Capacity}    if not exists $a->{Size}        and exists $a->{Capacity};
    $a->{VolumeDirty} = $a->{DirtyBitSet} if not exists $a->{VolumeDirty} and exists $a->{DirtyBitSet};
    $a->{VolumeName}  = $a->{Label}       if not exists $a->{VolumeName}  and exists $a->{Label};

    my $free = $a->{FreeSpace};
    my $max  = $a->{Size};
    my $w = $warn{default}; my $wu = $warn{defaultU};
    my $c = $crit{default}; my $cu = $crit{defaultU};
    my $freep = 0;
    $freep = $free/$max*100  if ( $max > 0 );
    my $unit = "B";
    my $state = "";
    my $dev  = $a->{Caption};
    $dev =~ s/\\/\//g; # \ => / at all
    $dev =~ s/\/$//;   # last / away
    $dev =~ s/:$//;    # last : away

    ## calc units and warn/crit levels
    if ( exists $warn{$dev} ) { $w  = $warn{$dev}; $wu = $warn{"${dev}U"}; }
    if ( exists $crit{$dev} ) { $c  = $crit{$dev}; $cu = $crit{"${dev}U"}; }
    if ( $wu eq "%" )  { $w = int($w*$max/100); }
    if ( $wu eq "TB" ) { $w *= 1024; $wu = "GB"; }
    if ( $wu eq "GB" ) { $w *= 1024; $wu = "MB"; }
    if ( $wu eq "MB" ) { $w *= 1024; $wu = "KB"; }
    if ( $wu eq "KB" ) { $w *= 1024; $wu = "B"; }
    if ( $cu eq "%" )  { $c = int($c*$max/100); }
    if ( $cu eq "TB" ) { $c *= 1024; $cu = "GB"; }
    if ( $cu eq "GB" ) { $c *= 1024; $cu = "MB"; }
    if ( $cu eq "MB" ) { $c *= 1024; $cu = "KB"; }
    if ( $cu eq "KB" ) { $c *= 1024; $cu = "B"; }

    ## Perfdata in B
    $perf_data .= "${dev}=${free}B;$w;$c;0;${max} ";


    ## check state
    if ( $w > $free ) { 
      $status = "WARNING" if $ERRORS{$status} < $ERRORS{"WARNING"};
      $state = "WARN:";
    }
    if ( exists $a->{VolumeDirty} and $a->{VolumeDirty} !~ /FALSE/i ) {
      #VolumeDirty: 
      # If True, the disk requires ChkDsk to be run at the next restart. This property is 
      # only applicable to those instances of logical disk that represent a physical disk
      # in the machine. It is not applicable to mapped logical drives.
      $status = "WARNING";
      $state = "DIRTY:";
    }
    if ( $c > $free ) { 
      $status = "CRITICAL" if $ERRORS{$status} < $ERRORS{"CRITICAL"};
      $state = "CRIT:";
    }

    ## check unit
    if ( $free > 10*1024 ) { $free /= 1024; $max /= 1024; $unit = "KB"; }
    if ( $free > 10*1024 ) { $free /= 1024; $max /= 1024; $unit = "MB"; }
    if ( $free > 10*1024 ) { $free /= 1024; $max /= 1024; $unit = "GB"; }
    if ( $free > 10*1024 ) { $free /= 1024; $max /= 1024; $unit = "TB"; }

    $out .= sprintf "$state$dev: %2.0f/%2.0f$unit (%2.1f%%), ",$free,$max,$freep;
    if ( $a->{FileSystem} ne "NTFS" ) { $out .= sprintf "($a->{FileSystem})"; }

    #debug>DeviceID="F:",FileSystem="NTFS",FreeSpace=154614009856,Size=440315654144,VolumeName="DB1",VolumeDirty=FALSE

  }
  $out =~ s/, $//;

  return $status, $out, $perf_data;
}


## Check System (+uptime)
sub wmicheck_system($)
{
  my @lines = @{ ( shift ) };

  my $status = "OK";
  my $out = "System: ";
  my $perf_data = "";

  if ( scalar @lines != 1 ) {
    $status = "UNKNOWN";
    $out .= sprintf "%d States found!",scalar @lines;
  } else {
    my $a = shift @lines;

    $out .= "$a->{CSName}, ";

    if ( $a->{Status} !~ /OK/i ) {
      $status="WARNING";
      $out .= "Status=$a->{Status}, ";
    }

    $out .= "$a->{Caption}, $a->{CSDVersion} (Version: $a->{Version}), ";

    
    my $inst_t = 0;
    my $akt_t  = 0;
    my $boot_t = 0;
    if ( $a->{InstallDate} =~ s/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\..*$/$1.$2.$3 $4:$5:$6/ ) {
      $inst_t = str2time($a->{InstallDate});
    }
    if ( $a->{LastBootUpTime} =~ s/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\..*$/$1.$2.$3 $4:$5:$6/ ) {
      $boot_t = str2time($a->{LastBootUpTime});
    }
    if ( $a->{LocalDateTime} =~ s/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\..*$/$1.$2.$3 $4:$5:$6/ ) {
      $akt_t = str2time($a->{LocalDateTime});
    }

    $a->{InstallDate} =~ s/\s.*//;
    $perf_data .= sprintf "uptime=%.0fs ",$akt_t-$boot_t;

    $inst_t = $akt_t - $inst_t;
    $boot_t = $akt_t - $boot_t;
    
    if ( $inst_t > 86400*365 ) {
      $inst_t /= 86400;
      $out .= sprintf "Installed $a->{InstallDate} (%u years %u days ago), ",$inst_t/365,$inst_t%365;
    } elsif ( $inst_t > 86400 ) {
      $out .= sprintf "Installed $a->{InstallDate} (%u days ago), ",$inst_t/86400;
    }
    if ( $boot_t > 86400 ) {
      $out .= sprintf "Up %u days",$boot_t/86400;
    } elsif ( $boot_t > 3600 ) {
      $out .= sprintf "Up %u hours",$boot_t/3600;
    } else {
      $out .= sprintf "Up %u minutes",$boot_t/60;
    }

    $out .= sprintf " RAM: %.1fGB, Pagefile: %.1fGB",$a->{TotalVisibleMemorySize}/1048576,
        ($a->{TotalVirtualMemorySize}-$a->{TotalVisibleMemorySize})/1048576;

  }

  return $status, $out, $perf_data;
}


## Check Number of Processes
sub wmicheck_procs($)
{

  my @lines = @{ ( shift ) };

  my $status = "OK";
  my $out = "PROCS:";
  my $perf_data = "";

  $check->{w} = $opts{warning} if defined $opts{warning};
  $check->{c} = $opts{critical} if defined $opts{critical};

  if ( scalar @lines != 1 ) {
    $status = "UNKNOWN";
    $out .= sprintf "%d Lines found!",scalar @lines;
  } else {
    my $a = shift @lines;

    $out .= sprintf " %d running Processes",$a->{NumberOfProcesses};

    $perf_data .= sprintf "procs=%u;",$a->{NumberOfProcesses};
    $perf_data .= sprintf "%u",$check->{w} if $check->{w};
    $perf_data .= ";";
    $perf_data .= sprintf "%u",$check->{c} if $check->{c};
    $perf_data .= ";0; ";

    $status = "WARNING"  if ( $check->{w} > 0 and $a->{NumberOfProcesses} >= $check->{w} );
    $status = "CRITICAL" if ( $check->{c} > 0 and $a->{NumberOfProcesses} >= $check->{c} );
  }

  return $status, $out, $perf_data;

}


## Check Number of Users
sub wmicheck_users($)
{

  my @lines = @{ ( shift ) };

  my $status = "OK";
  my $out = "USERS:";
  my $perf_data = "";

  $check->{w} = $opts{warning} if defined $opts{warning};
  $check->{c} = $opts{critical} if defined $opts{critical};

  if ( scalar @lines != 1 ) {
    $status = "UNKNOWN";
    $out .= sprintf "%d Lines found!",scalar @lines;
  } else {
    my $a = shift @lines;

    $out .= sprintf " %d connected user sessions",$a->{NumberOfUsers};

    $perf_data .= sprintf "users=%u;",$a->{NumberOfUsers};
    $perf_data .= sprintf "%u",$check->{w} if $check->{w};
    $perf_data .= ";";
    $perf_data .= sprintf "%u",$check->{c} if $check->{c};
    $perf_data .= ";0; ";

    $status = "WARNING"  if ( $check->{w} > 0 and $a->{NumberOfUsers} >= $check->{w} );
    $status = "CRITICAL" if ( $check->{c} > 0 and $a->{NumberOfUsers} >= $check->{c} );
  }

  return $status, $out, $perf_data;

}


## Check Service with state
#http://msdn.microsoft.com/en-us/library/aa394418(VS.85).aspx
sub wmicheck_service($)
{
  my @lines = @{ ( shift ) };

  my $status = "OK";
  my $out = "SRV: ";
  my $perf_data = "";
  my $out_long = "";

  $check->{w} = $opts{warning} if defined $opts{warning};    # should run  (use ! or ^ to negate)
  $check->{c} = $opts{critical} if defined $opts{critical};  # have to run (use ! or ^ to negate)
  $check->{cparam} = $opts{cparam} if defined $opts{cparam}; # ignore
  # whitespaces in Servicenames will be deleted

  my %ignore = ();
  my %warn = ();
  my %crit = ();
  my %all = ();
  my $list_all = 0; # list all or only special Services
  foreach ( split(/[,;\s]+/,$check->{cparam}) ) { 
    $ignore{lc($_)} = 1; 
  }
  if ( $check->{w} !~ /^\d*$/ ) { # no number
    foreach ( split(/[,;\s]+/,$check->{w}) ) {
      if ( $_ =~ /^[!^](.*)/ ) {
        $warn{lc($1)} = 0;
        $all{lc($1)} = 0;
      } else {
        $warn{lc($_)} = 1;
        $all{lc($_)} = 0;
      }
    }
  }
  if ( $check->{c} !~ /^\d*$/ ) { # no number
    foreach ( split(/[,;\s]+/,$check->{c}) ) {
      if ( $_ =~ /^[!^](.*)/ ) {
        $crit{lc($1)} = 0;
        $all{lc($1)} = 0;
      } else {
        $crit{lc($_)} = 1;
        $all{lc($_)} = 0;
      }
    }
  }
  $list_all = 1 if ( scalar(keys(%warn)) + scalar(keys(%crit)) == 0 );

  my %states = ( Running => 0, Stopped => 0);
  my %state_nok = ();
  my %status_nok = ();
  foreach my $d ( @lines ) {
    next unless exists $d->{Name};
    $d->{Name} =~ s/[!^,\s]+//g; # have to be deleted, because of list delemiter
    my $srv = lc $d->{Name};

    ## Ignore not queryed services
    next unless ( $list_all or exists $warn{$srv} or exists $crit{$srv} );
   
    ## group services by state
    if ( exists $states{$d->{State}} ) {
      $states{$d->{State}}++;
    } else {
      $states{$d->{State}} = 1;
    }

    ## Ignore this services (only for list_all = 1)
    next if ( $list_all and exists $ignore{$srv} );

    ## List all ?
    if ( $list_all ) {
      if (( $d->{StartMode} eq "Auto" )and( $d->{State} ne "Running" )) {
        if ( exists $state_nok{$d->{State}} ) {
          $state_nok{$d->{State}} .= ",$d->{Name}";
        }else{
          $state_nok{$d->{State}} = "$d->{Name}";
        }
        $out_long .= sprintf "\n%s: %s",$d->{Name}, $d->{Caption};
      }
    }


    ## Status of a service
    if ( $d->{Status} ne "OK" ) {
      $status = "CRITICAL"  if $ERRORS{$status} < $ERRORS{CRITICAL};
      if ( exists $status_nok{$d->{Status}} ) {
        $status_nok{$d->{Status}} .= ",$d->{Name}";
      }else{
        $status_nok{$d->{Status}} = "$d->{Name}";
      }
    }

    ## Warning check
    if ( exists $warn{$srv} ) {
      $all{$srv}++;
      if ( $warn{$srv} ) { # should run
        if ( $d->{State} ne "Running" ) {
          $status = "WARNING"  if $ERRORS{$status} < $ERRORS{WARNING};
          if ( exists $state_nok{$d->{State}} ) {
            $state_nok{$d->{State}} .= ",$d->{Name}";
          }else{
            $state_nok{$d->{State}} = "$d->{Name}";
          }
          $out_long .= sprintf "\n%s: %s",$d->{Name}, $d->{Caption};
        }
      } else { # should not run
        if ( $d->{State} ne "Stopped" ) {
          $status = "WARNING"  if $ERRORS{$status} < $ERRORS{WARNING};
          if ( exists $state_nok{$d->{State}} ) {
            $state_nok{$d->{State}} .= ",$d->{Name}";
          }else{
            $state_nok{$d->{State}} = "$d->{Name}";
          }
          $out_long .= sprintf "\n%s: %s",$d->{Name}, $d->{Caption};
        }
      }
    }

    ## Critical check
    if ( exists $crit{$srv} ) {
      $all{$srv}++;
      if ( $crit{$srv} ) { # should run
        if ( $d->{State} ne "Running" ) {
          $status = "CRITICAL"  if $ERRORS{$status} < $ERRORS{CRITICAL};
          if ( exists $state_nok{$d->{State}} ) {
            $state_nok{$d->{State}} .= ",$d->{Name}";
          }else{
            $state_nok{$d->{State}} = "$d->{Name}";
          }
          $out_long .= sprintf "\n%s: %s",$d->{Name}, $d->{Caption};
        }
      } else { # should not run
        if ( $d->{State} ne "Stopped" ) {
          $status = "CRITICAL"  if $ERRORS{$status} < $ERRORS{CRITICAL};
          if ( exists $state_nok{$d->{State}} ) {
            $state_nok{$d->{State}} .= ",$d->{Name}";
          }else{
            $state_nok{$d->{State}} = "$d->{Name}";
          }
          $out_long .= sprintf "\n%s: %s",$d->{Name}, $d->{Caption};
        }
      }
    }

  } # end foreach lines

  ## Gen state
  if ( $list_all ) {
    my $problems = 0;
    map { $problems += 1 + $_ =~ tr/,// } values %state_nok; # if key exists the the #srv is # of ',' + 1
        
    $status = "WARNING"  if( $ERRORS{$status} < $ERRORS{WARNING}  and $check->{w} > 0 and $problems >= $check->{w} );
    $status = "CRITICAL" if( $ERRORS{$status} < $ERRORS{CRITICAL} and $check->{c} > 0 and $problems >= $check->{c} );
  } else {
    foreach my $k ( keys %all ) {
      if ( not $all{$k} ) {
        if ( exists $state_nok{unknown} ) {
          $state_nok{unknown} .= ",$k";
        }else{
          $state_nok{unknown} = "$k";
        }
        $status = "UNKNOWN" if $ERRORS{$status} < $ERRORS{UNKNOWN};
      }
    }
  }

  ## Gen output
  my $other = 0;
  foreach my $k ( sort keys %states ) {
    $out .= sprintf " %d %s,",$states{$k},$k;
    if (( $k ne "Running" )and( $k ne "Stopped")) {
      $other += $states{$k};
    }
  }
  $out =~ s/,$//;
  $perf_data .= sprintf "%s=%d ","Running",$states{Running};
  $perf_data .= sprintf "%s=%d ","Stopped",$states{Stopped};
  $perf_data .= sprintf "%s=%d ","Other",$other;

  if ( keys %state_nok ) {
    $out .= " (State NOT OK: ";
    foreach my $k ( keys %state_nok ) {
      $out .= "$k=$state_nok{$k}, ";
    }
    $out =~ s/, $//;
    $out .= ")";
  }

  if ( keys %status_nok ) {
    $out .= " (Status NOT OK: ";
    foreach my $k ( keys %status_nok ) {
      $out .= "$k=$status_nok{$k}, ";
    }
    $out =~ s/, $//;
    $out .= ")";
  }
  
  return $status, "$out$out_long", $perf_data;
}


## Check Memory Usage
sub wmicheck_memfree($)
{

  my @lines = @{ ( shift ) };

  my $status = "OK";
  my $out = "MemFree: ";
  my $perf_data = "";

  $check->{w} = $opts{warning} if defined $opts{warning};
  $check->{c} = $opts{critical} if defined $opts{critical};

  if ( scalar @lines != 1 ) {
    $status = "UNKNOWN";
    $out .= sprintf "%d Lines found!",scalar @lines;
    return $status, $out, $perf_data;
  } 
  my $a = shift @lines;

  # Sums and Max from Win32_OperatingSystem (Note: Values are KB)
  my $wmi_err;
  if ( $check->{subquery1} ) {
    ($wmi_err,@lines) = run_query($check->{subquery1});
  }
  if ( scalar @lines != 1 ) {
    $status = "UNKNOWN";
    $out .= sprintf "subquery: %d Lines found!",scalar @lines;
    return $status, $out, $perf_data;
  } 

  $a->{TotalVirtualMemorySize} = $lines[0]->{TotalVirtualMemorySize} * 1024;
  $a->{TotalVisibleMemorySize} = $lines[0]->{TotalVisibleMemorySize} * 1024;
  $a->{FreeSpaceInPagingFiles} = $lines[0]->{FreeSpaceInPagingFiles} * 1024;
  $a->{FreeVirtualMemory}      = $lines[0]->{FreeVirtualMemory} * 1024;

  if ( $a->{AvailableBytes} > 1024*1024*500 ) {
    $out .= sprintf " %.1fGB",$a->{AvailableBytes}/1024/1024/1024;
  } elsif ( $a->{AvailableBytes} > 1024*500 ) {
    $out .= sprintf " %.1fMB",$a->{AvailableBytes}/1024/1024;
  } else {
    $out .= sprintf " %.1fKB",$a->{AvailableBytes}/1024;
  }
  if ( $a->{TotalVisibleMemorySize} > 1024*1024*500 ) {
    $out .= sprintf "/%.1fGB",$a->{TotalVisibleMemorySize}/1024/1024/1024;
  } else {
    $out .= sprintf "/%.1fMB",$a->{TotalVisibleMemorySize}/1024/1024;
  }

  $out .= " free (Virtual Memory: ";
  if ( $a->{FreeVirtualMemory} > 1024*1024*500 ) {
    $out .= sprintf "%.1fGB",$a->{FreeVirtualMemory}/1024/1024/1024;
  } elsif ( $a->{FreeVirtualMemory} > 1024*500 ) {
    $out .= sprintf "%.1fMB",$a->{FreeVirtualMemory}/1024/1024;
  } else {
    $out .= sprintf "%.1fKB",$a->{FreeVirtualMemory}/1024;
  }
  if ( $a->{TotalVirtualMemorySize} > 1024*1024*500 ) {
    $out .= sprintf "/%.1fGB",$a->{TotalVirtualMemorySize}/1024/1024/1024;
  } else {
    $out .= sprintf "/%.1fMB",$a->{TotalVirtualMemorySize}/1024/1024;
  }
  $out .= " free)";

  ## recalc warn and crit values
  if ( $check->{w} =~ /^([\.\d]+)%$/ ) { 
    $check->{w} = $1 * $a->{TotalVirtualMemorySize} / 100; 
  } elsif ( $check->{w} =~ /^([\.\d]+)G$/ ) { 
    $check->{w} = $1 * 1024 * 1024 * 1024; 
  } elsif ( $check->{w} =~ /^([\.\d]+)M$/ ) { 
    $check->{w} = $1 * 1024 * 1024; 
  } elsif ( $check->{w} =~ /^([\.\d]+)K$/ ) { 
    $check->{w} = $1 * 1024; 
  } elsif ( $check->{w} =~ /^[\.\d]+$/ ) {
    #ok
  } else {
    $check->{w} = $a->{TotalVirtualMemorySize};
  }
  if ( $check->{c} =~ /^([\.\d]+)%$/ ) { 
    $check->{c} = $1 * $a->{TotalVirtualMemorySize} / 100; 
  } elsif ( $check->{c} =~ /^([\.\d]+)G$/ ) { 
    $check->{c} = $1 * 1024 * 1024 * 1024; 
  } elsif ( $check->{c} =~ /^([\.\d]+)M$/ ) { 
    $check->{c} = $1 * 1024 * 1024; 
  } elsif ( $check->{c} =~ /^([\.\d]+)K$/ ) { 
    $check->{c} = $1 * 1024; 
  } elsif ( $check->{c} =~ /^[\.\d]+$/ ) {
    #ok
  } else {
    $check->{c} = $a->{TotalVirtualMemorySize};
  }

  $perf_data .= sprintf "memFree=%.0fB;%.0f;%.0f;0;%.0f ",$a->{AvailableBytes},$check->{w},$check->{c},$a->{TotalVisibleMemorySize};
  $perf_data .= sprintf "memCache=%.0fB ",$a->{CacheBytes};
  $perf_data .= sprintf "cacheFaults=%.0fc ",$a->{CacheFaultsPersec};
  $perf_data .= sprintf "committed=%.0fB ",$a->{CommittedBytes};
  $perf_data .= sprintf "poolNonPaged=%.0fB ",$a->{PoolNonpagedBytes};
  $perf_data .= sprintf "poolPaged=%.0fB ",$a->{PoolPagedBytes};
  $perf_data .= sprintf "pages=%.0fc ",$a->{PagesPersec};

  $perf_data .= sprintf "virtMemFree=%.0fB;;;0;%.0f ",$a->{FreeVirtualMemory},$a->{TotalVirtualMemorySize};
  $perf_data .= sprintf "freePageFile=%.0fB ",$a->{FreeSpaceInPagingFiles};


  $status = "WARNING"  if ( $a->{AvailableBytes} < $check->{w} );
  $status = "CRITICAL" if ( $a->{AvailableBytes} < $check->{c} );


  return $status, $out, $perf_data;
}


## Check CPU Load
sub wmicheck_cpuload($)
{

  my @lines = @{ ( shift ) };

  my $status = "OK";
  my $out = "CPU-Load:";
  my $perf_data = "";

  $check->{w} = $opts{warning} if defined $opts{warning};
  $check->{w} =~ s/%$//;
  $check->{c} = $opts{critical} if defined $opts{critical};
  $check->{c} =~ s/%$//;

  my $tmpfile = $opts{host}."_cpu.stats";
  my $tdata = read_tmpfile($tmpfile);
  my $now = time;

  if ( scalar @lines != 1 ) {
    $status = "UNKNOWN";
    $out .= sprintf "%d Lines found!",scalar @lines;
  } else {
    my $a = shift @lines;

    ## Add actual value to tdata 
    $tdata->{$now} = {};
    foreach my $k ( keys %$a ) {
      $tdata->{$now}->{$k} = $a->{$k};
    }

    ## search best old value for 5min and 15min average
    my $t5min  = 0;
    my $t15min = 0;
    my $o;
    foreach my $t ( keys %$tdata ) {
      next if $t == $now;
      $o = $now-5*60; # optimal value
      if ( abs($t5min-$o) > abs($t-$o) ) { $t5min = $t; }
      $o = $now-15*60; # optimal value
      if ( abs($t15min-$o) > abs($t-$o) ) { $t15min = $t; }
    }
    $t5min  = $now if $t5min == 0;
    $t15min = $now if $t15min == 0;
    $t5min  = $now if $now-$t5min  > 15*60;
    $t15min = $now if $now-$t15min > 30*60;

    ## calc and print average values
    my $diff5 = 18446744073709551615; # 64bit unsigned int
    if ( $tdata->{$now}->{Timestamp_Sys100NS} >= $tdata->{$t5min}->{Timestamp_Sys100NS} ) {
      $diff5 = $tdata->{$now}->{Timestamp_Sys100NS} - $tdata->{$t5min}->{Timestamp_Sys100NS};
    } else {
      $diff5 -= $tdata->{$t5min}->{Timestamp_Sys100NS} - $tdata->{$now}->{Timestamp_Sys100NS};
    }
    my $i5 = 18446744073709551615;
    if ( $tdata->{$now}->{PercentIdleTime} >= $tdata->{$t5min}->{PercentIdleTime} ) {
      $i5 = $tdata->{$now}->{PercentIdleTime} - $tdata->{$t5min}->{PercentIdleTime};
    } else {
      $i5 -= $tdata->{$t5min}->{PercentIdleTime} - $tdata->{$now}->{PercentIdleTime};
    }
    if ( $diff5 > 0 and $diff5 < 100*($now-$t5min)*$tdata->{$now}->{Frequency_Sys100NS} ) { # sometimes the Timestamp move forward big values :-/ ? 
      $i5 = $i5 * 100 / $diff5;
    } else { 
      $i5 = 101;
    }

    my $diff15 = 18446744073709551615; # 64bit unsigned int
    if ( $tdata->{$now}->{Timestamp_Sys100NS} >= $tdata->{$t15min}->{Timestamp_Sys100NS} ) {
      $diff15 = $tdata->{$now}->{Timestamp_Sys100NS} - $tdata->{$t15min}->{Timestamp_Sys100NS};
    } else {
      $diff15 -= $tdata->{$t15min}->{Timestamp_Sys100NS} - $tdata->{$now}->{Timestamp_Sys100NS};
    }
    my $i15 = 18446744073709551615;
    if ( $tdata->{$now}->{PercentIdleTime} >= $tdata->{$t15min}->{PercentIdleTime} ) {
      $i15 = $tdata->{$now}->{PercentIdleTime} - $tdata->{$t15min}->{PercentIdleTime};
    } else {
      $i15 -= $tdata->{$t15min}->{PercentIdleTime} - $tdata->{$now}->{PercentIdleTime};
    }
    if ( $diff15 > 0 and $diff15 < 100*($now-$t15min)*$tdata->{$now}->{Frequency_Sys100NS}) { # sometimes the Timestamp move forward big values :-/ ? 
      $i15 = $i15 * 100 / $diff15;
    } else { 
      $i15 = 101;
    }

    $out .= sprintf " %3.1f%% 5min AVG (%ds)",100-$i5,$diff5/$tdata->{$now}->{Frequency_Sys100NS};
    $out .= sprintf ", %3.1f%% 15min AVG (%ds)",100-$i15,$diff15/$tdata->{$now}->{Frequency_Sys100NS};


    $perf_data .= sprintf "idleTime=%uc;%d;%d;0;100 ",100*$a->{PercentIdleTime}/$a->{Frequency_Sys100NS},100-$check->{w},100-$check->{c};
    $perf_data .= sprintf "interruptTime=%uc;;;0;100 ",100*$a->{PercentInterruptTime}/$a->{Frequency_Sys100NS};
    $perf_data .= sprintf "privilegedTime=%uc;;;0;100 ",100*$a->{PercentPrivilegedTime}/$a->{Frequency_Sys100NS};
    $perf_data .= sprintf "userTime=%uc;;;0;100 ",100*$a->{PercentUserTime}/$a->{Frequency_Sys100NS};
   
    $status = "WARNING" if ( 100-$i15 > $check->{w} );
    $status = "CRITICAL" if ( 100-$i15 > $check->{c} );

    ## delete old values from $tdata
    foreach my $t ( keys %$tdata ) {
      if ( $now-$t > 1200 ) { # delete after 20minutes
        delete $tdata->{$t};
      }
    }

    #ProcessorQueueLength
    if ( $check->{subquery1} ) {
      my ($wmi_err,@l2) = run_query($check->{subquery1});
      if ( scalar @l2 == 1 ) {
        $out .= sprintf " (ProcessorQueue=%u)",$l2[0]->{ProcessorQueueLength};
        $perf_data .= sprintf "processorQueueLength=%u ",$l2[0]->{ProcessorQueueLength};
      }
    }

    ## Timeout off now (skript has to write file completely)
    alarm(0); 

    write_tmpfile($tmpfile,$tdata);
  }

  return $status, $out, $perf_data;
}


## Check Windows Cluster State
sub wmicheck_mscluster($)
{

  my @lines = @{ ( shift ) };

  my $status = "OK";
  my $out = "MSClusterState: ";
  my $perf_data = "";

  #State - The current state of the resource group.
  # -1 StateUnknown
  #  0 Online
  #  1 Offline
  #  2 Failed
  #  3 PartialOnline
  #  4 Pending
  my %map = (
    "-1" => "Unknown",
    0    => "Online",
    1    => "Offline",
    2    => "Failed",
    3    => "PartialOnline",
    4    => "Pending",
  );
  foreach my $a ( @lines ) {
    $map{$a->{State}} = $a->{State} unless exists $map{$a->{State}};
    $out .= "'$a->{Name}' is $map{$a->{State}}, ";
    $a->{Name} =~ s/\s+/_/;
    $perf_data.= "$a->{Name}=$a->{State} ";

    if ( $a->{State} != 0 ) {
      if (( $a->{State} == -1 )or( $a->{State} == 4 )) {
        $status = "WARNING" if $ERRORS{$status} < $ERRORS{WARNING};
      } else {
        $status = "CRITICAL" if $ERRORS{$status} < $ERRORS{CRITICAL};
      }
    }

  }
  $out =~ s/, $//;


  ## ResourcesGroup to Node
  if ( $check->{subquery2} ) {
     my ($wmi_err,@l2) = run_query($check->{subquery2});
     
     foreach my $a ( @l2 ) {
       $a->{GroupComponent} =~ s/^MSCluster_Node.Name=\"(.*)\"/$1/;
       $a->{PartComponent}  =~ s/^MSCluster_ResourceGroup.Name=\"(.*)\"/$1/;
       $out =~ s/('$a->{PartComponent}')/$1 ($a->{GroupComponent})/; 
     }
  }


  ## Resources not Online
  if ( $check->{subquery1} ) {
    my ($wmi_err,@l2) = run_query($check->{subquery1});

    #State - The current state of the resource.
    #  -1 StateUnknown     The operation was not successful.
    #   0 Inherited        TBD 
    #   1 Initializing     The resource is performing initialization.
    #   2 Online           The resource is operational and functioning normally.
    #   3 Offline          The resource is not operational. This value will be returned if the resource reported a state of ClusterResourceOffline (3) or ClusterResourceCannotComeOnlineOnThisNode (127).
    #   4 Failed           The resource has failed. This value will be returned if the resource reported a state of ClusterResourceFailed (4) or ClusterResourceCannotComeOnlineOnAnyNode (126).
    # 128 Pending          The resource is coming online or going offline.
    # 129 Online Pending   The resource is coming online.
    # 130 Offline Pending  The resource is going offline.
    my %map = (
      "-1" => "Unknown",
      0    => "Inherited",
      1    => "Initializing",
      2    => "Online",
      3    => "Offline",
      4    => "Failed",
      128  => "Pending",
      129  => "Online Pending",
      130  => "Offline Pending",
     );
    if ( scalar @l2 > 0 ) {
      $out .= ", (";
      foreach my $a ( @l2 ) {
        $out .= "'$a->{Name}' is $map{$a->{State}}, ";
      }
      $out =~ s/, $//;
      $out .= ")";
    }
  }

  return $status, $out, $perf_data;
}



__END__


