Skip to content

Commit

Permalink
2.4.x+wmi task updates (#435)
Browse files Browse the repository at this point in the history
* Simulate inheritance if necessary while testing enabling function
* Don't submit inventory based on WMI if not validated
* Preload remoteIs64bits() API while connected and use this API while in remote WMI
* Fix debug2 level wmi query releated messages
* Add function to initialize Logger config in Win32::OLE worker thread
* Don't support no-win32-ole-workaround option in WMI script
* Support Win32::OLE worker logger setup after first created logger
* Handle Win32:OLE last error for better error reporting
* Revert back use of WMI Async queries as not remotely working at the moment
* Still keep expiration logic, but expiration raises only after receiving an instance
  • Loading branch information
g-bougard authored Dec 29, 2017
1 parent 4f1570c commit e5d15b3
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 42 deletions.
14 changes: 8 additions & 6 deletions bin/fusioninventory-agent
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,6 @@ if ($options->{daemon}) {
}
}

if ($OSNAME eq 'MSWin32' && ! $options->{'no-win32-ole-workaround'}) {
# From here we may need to avoid crashes due to not thread-safe Win32::OLE
FusionInventory::Agent::Tools::Win32->require();
FusionInventory::Agent::Tools::Win32::start_Win32_OLE_Worker();
}

my $agent = $options->{daemon} ?
FusionInventory::Agent::Daemon->new(%setup)
:
Expand Down Expand Up @@ -140,6 +134,14 @@ if ($options->{wait}) {

eval {
$agent->init(options => $options);

if ($OSNAME eq 'MSWin32' && ! $options->{'no-win32-ole-workaround'}) {
# From here we may need to avoid crashes due to not thread-safe Win32::OLE
FusionInventory::Agent::Tools::Win32->require();
FusionInventory::Agent::Tools::Win32::start_Win32_OLE_Worker();
FusionInventory::Agent::Tools::Win32::setupWorkerLogger(config => $agent->{config});
}

$agent->run();
};

Expand Down
24 changes: 17 additions & 7 deletions bin/fusioninventory-wmi
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,18 @@ if ($options->{'local'} && !$options->{'server'} && !$options->{'config'}) {
$options->{'server'} = "";
}

unless ($options->{'no-win32-ole-workaround'}) {
# From here we may need to avoid crashes due to not thread-safe Win32::OLE
FusionInventory::Agent::Tools::Win32->require();
FusionInventory::Agent::Tools::Win32::start_Win32_OLE_Worker();
}

my $config = FusionInventory::Agent::Config->new(
options => $options,
);

my $logger = FusionInventory::Agent::Logger->new(config => $config);

# From here we may need to avoid crashes due to not thread-safe Win32::OLE
FusionInventory::Agent::Tools::Win32->require();
FusionInventory::Agent::Tools::Win32::start_Win32_OLE_Worker();
FusionInventory::Agent::Tools::Win32::setupWorkerLogger(config => $config);

# Get targets
my $targets = $config->getTargets(
logger => $logger,
Expand All @@ -122,13 +122,23 @@ foreach my $target (@{$targets}) {
config => $config
);

die "Connection failure\n"
die _error("Connection failure")
unless $wmitask->connect(%{$options});
$wmitask->run();
warn _error("Failed to create inventory for ".$target->_getName()." ".$target->_getType()." target")
unless $wmitask->run();
}

exit(0);

sub _error {
my ($failure) = @_;

my ($error, $message) = FusionInventory::Agent::Tools::Win32::getLastError();

return "$failure\n" unless $error && $message;

return sprintf("%s: Err=0x%08X\n%s\n", $failure, $error, $message);
}

__END__
Expand Down
3 changes: 3 additions & 0 deletions lib/FusionInventory/Agent/Daemon/Win32.pm
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ sub Continue {
sub ApplyServiceOptimizations {
my ($self) = @_;

# Setup worker Logger after service Logger
FusionInventory::Agent::Tools::Win32::setupWorkerLogger(config => $self->{config});

$self->SUPER::ApplyServiceOptimizations();

# Win32 only service optimization
Expand Down
19 changes: 19 additions & 0 deletions lib/FusionInventory/Agent/Task/Inventory.pm
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ sub run {

$self->_initModulesList(\%disabled);
$self->_feedInventory($inventory, \%disabled);
return unless $self->_validateInventory($inventory);
$self->_submitInventory( %params, inventory => $inventory );
return 1;
}

# Method to override if inventory needs to be validate
sub _validateInventory { 1 }

sub _submitInventory {
my ($self, %params) = @_;

my $inventory = $params{inventory};

if ($self->{target}->isa('FusionInventory::Agent::Target::Local')) {
my $path = $self->{target}->getPath();
Expand Down Expand Up @@ -196,6 +208,13 @@ sub _initModulesList {
next;
}

# Simulate tested function inheritance as we test a module, not a class
unless (defined(*{$module."::".$isEnabledFunction})) {
no strict 'refs'; ## no critic (ProhibitNoStrict)
*{$module."::".$isEnabledFunction} =
\&{"FusionInventory::Agent::Task::Inventory::Module::$isEnabledFunction"};
}

my $enabled = runFunction(
module => $module,
function => $isEnabledFunction,
Expand Down
16 changes: 16 additions & 0 deletions lib/FusionInventory/Agent/Task/WMI.pm
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use parent 'FusionInventory::Agent::Task::Inventory';
use UNIVERSAL::require;
use English qw(-no_match_vars);

use FusionInventory::Agent::Tools::Expiration;

sub isEnabled {
my ($self, $response) = @_;

Expand Down Expand Up @@ -69,11 +71,25 @@ sub connect {
# Set now we are remote
$self->setRemote('wmi');

# Preload remoteIs64bits()
setExpirationTime( timeout => $self->{config}->{'backend-collect-timeout'} );
remoteIs64bits();
setExpirationTime();

return 1

} else {
$logger->error("can't connect to host $host with '$user' user") if $logger;
return 0;
}
}

sub _validateInventory {
my ($self, $inventory) = @_;

# Hardware name is mandatory to compute deviceid, something surely goes wrong
# if its missing
return $inventory->getHardware('NAME') ? 1 : 0;
}

1;
115 changes: 86 additions & 29 deletions lib/FusionInventory/Agent/Tools/Win32.pm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ my $localCodepage;

our @EXPORT = qw(
is64bit
remoteIs64bits
encodeFromRegistry
KEY_WOW64_64
KEY_WOW64_32
Expand All @@ -55,6 +56,7 @@ my $_is64bits = undef;
sub is64bit {
# Cache is64bit() result in a private module variable to avoid a lot of wmi
# calls and as this value won't change during the service/task lifetime
return remoteIs64bits() if _remoteWmi();
return $_is64bits if $_is64bits;
return $_is64bits =
any { $_->{AddressWidth} eq 64 }
Expand Down Expand Up @@ -120,10 +122,6 @@ sub _getWMIObjects {
$WMIService = getWMIService( moniker => $params{altmoniker} );
}
} else {
# We must re-initialize Win32::OLE to support Events
Win32::OLE->Uninitialize();
Win32::OLE->Initialize(Win32::OLE::COINIT_OLEINITIALIZE());

$WMIService = Win32::OLE->GetObject($params{moniker});
# Support alternate moniker if provided and main failed to open
if (!defined($WMIService) && $params{altmoniker}) {
Expand All @@ -133,37 +131,28 @@ sub _getWMIObjects {

return unless (defined($WMIService));

my @events = ();
# Prepare events sink
my $WMISink = Win32::OLE->CreateObject("WbemScripting.SWbemSink");
Win32::OLE->WithEvents($WMISink, sub { shift; push @events, \@_; });

my $Instances;
if ($params{query}) {
$logthat = "WMI query: $params{query}";
$logger->debug2("Doing WMI $logthat") if $logger;
$WMIService->ExecQueryAsync($WMISink, $params{query});
$logthat = "$params{query} WMI query";
$logger->debug2("Doing $logthat") if $logger;
$Instances = $WMIService->ExecQuery($params{query});
} else {
$logthat = "$params{class} class WMI objects";
$logger->debug2("Looking for $logthat") if $logger;
$WMIService->InstancesOfAsync($WMISink, $params{class});
$Instances = $WMIService->InstancesOf($params{class});
}

return unless $Instances;

my @objects;
while (1) {
foreach my $instance ( in $Instances ) {
my $object;
my $nextevent = shift @events;
if (!$nextevent) {
if (time >= $expiration) {
$logger->info("Timeout reached on $logthat") if $logger;
last;
}
Win32::OLE->SpinMessageLoop();
delay(0.2);
next;

if (time >= $expiration) {
$logger->info("Timeout reached on $logthat") if $logger;
last;
}
my ( $event, $instance ) = @{$nextevent};
last if $event eq 'OnCompleted';
next unless ($event eq 'OnObjectReady' && $instance);

# Handle Win32::OLE object method, see _getLoggedUsers() method in
# FusionInventory::Agent::Task::Inventory::Win32::Users as example to
# use or enhance this feature
Expand Down Expand Up @@ -223,9 +212,6 @@ sub _getWMIObjects {
push @objects, $object;
}

# Reset event sink
Win32::OLE->WithEvents($WMISink);

return @objects;
}

Expand Down Expand Up @@ -884,6 +870,7 @@ sub FreeAgentMem {

my $worker ;
my $worker_semaphore;
my $worker_lasterror = [];
my $wmiService;
my $wmiLocator;
my $wmiRegistry;
Expand All @@ -903,6 +890,55 @@ sub start_Win32_OLE_Worker {
}
}

sub setupWorkerLogger {
my (%params) = @_;

# Just create a new Logger object in worker to update default module configuration
return defined(FusionInventory::Agent::Logger->new(%params))
unless (defined($worker));

return _call_win32_ole_dependent_api({
funct => 'setupWorkerLogger',
args => [ %params ]
});
}

sub getLastError {

return @{$worker_lasterror}
unless (defined($worker));

return _call_win32_ole_dependent_api({
funct => 'getLastError',
array => 1,
args => []
});
}

my %known_ole_errors = (
scalar(0x80041003) => "Access denied as the current or specified user name and password were not valid or authorized to make the connection.",
scalar(0x8004100E) => "Invalid namespace",
scalar(0x80041064) => "User credentials cannot be used for local connections",
scalar(0x80070005) => "Access denied",
scalar(0x800706BA) => "The RPC server is unavailable",
);

sub _keepOleLastError {

my $lasterror = Win32::OLE->LastError();
if ($lasterror) {
my $error = 0x80000000 | ($lasterror & 0x7fffffff);
# Don't report not accurate and not failure error
if ($error != 0x80004005) {
$worker_lasterror = [ $error, $known_ole_errors{$error} ];
my $logger = FusionInventory::Agent::Logger->new();
$logger->debug("Win32::OLE ERROR: ".($known_ole_errors{$error}||$lasterror));
}
} else {
$worker_lasterror = [];
}
}

sub _win32_ole_worker {
# Load Win32::OLE as late as possible in a dedicated worker
Win32::OLE->require() or return;
Expand Down Expand Up @@ -942,6 +978,9 @@ sub _win32_ole_worker {
$result = &{$funct}(@{$call->{'args'}});
}

# Keep Win32::OLE error for later reporting
_keepOleLastError() unless $funct == \&getLastError;

# Share back the result
$call->{'result'} = shared_clone($result);

Expand Down Expand Up @@ -1027,11 +1066,19 @@ sub _call_win32_ole_dependent_api {

if (exists($call->{'array'}) && $call->{'array'}) {
my @results = &{$funct}(@{$call->{'args'}});

# Keep Win32::OLE error for later reporting
_keepOleLastError() unless $funct == \&getLastError;

# Reset expiration
setExpirationTime();
return @results;
} else {
my $result = &{$funct}(@{$call->{'args'}});

# Keep Win32::OLE error for later reporting
_keepOleLastError() unless $funct == \&getLastError;

# Reset expiration
setExpirationTime();
return $result;
Expand All @@ -1043,6 +1090,16 @@ sub _remoteWmi {
return $wmiParams->{host} ? 1 : 0;
}

sub remoteIs64bits {
return $wmiParams->{is64bits} if $wmiParams->{is64bits};
# Retrieve and save is64bit result
return $wmiParams->{is64bits} = any { $_->{AddressWidth} eq 64 }
getWMIObjects(
class => 'Win32_Processor',
properties => [ qw/AddressWidth/ ]
);
}

sub _connectToService {
my (%params) = @_;

Expand Down

0 comments on commit e5d15b3

Please sign in to comment.