#-----------------------------------------------------------------// # Script: WMIFE.pm # # (c) 2001,2002 Microsoft Corporation. All rights reserved. # # addresses the generic OLE->WMI interface to test various stuff # defined on the given remote box # # Version: <2.01> 06/11/2002 : Serguei Kouzmine # #-----------------------------------------------------------------// package WMIFE; use lib $ENV{RAZZLETOOLPATH} . "\\PostBuildScripts"; use lib $ENV{RAZZLETOOLPATH}; use PbuildEnv; use Logmsg; require Exporter; use vars qw( @ISA @EXPORT_OK ); @ISA = ( 'Exporter' ); @EXPORT_OK = qw( $verbose ); my $hPrivileges = {"SeBackupPrivilege" => "Backup", "SeSecurityPrivilege" => "Security", "SeRestorePrivilege" => "Restore", "SeShutdownPrivilege" => "ShutDown", "SeRemoteShutdownPrivilege" => "RemoteShutdown" }; use strict; no strict 'refs'; use Win32::OLE qw(in with); use Win32::OLE::Variant; # use 'in' loop for retrieve WQL cursors use vars qw( $verbose $NameSpace $impersonationLevel $authLevel $Privileges $ComputerName $MasterData $Provider $Query $DisplayFields $User $Password $DEBUG $TIMEOUT $sRootKey $sBootTestDataSubKey $sLogonUserDataSubKey ); my $VERSION = "1.1"; $TIMEOUT = 60; $DEBUG = 0; $verbose = ["NO","YES"]; my $SEPARATOR = "\n". join("", map{chr(61)} 0..79). "\n"; $NameSpace = "root\\cimv2"; $sRootKey = "HKEY_LOCAL_MACHINE"; $sBootTestDataSubKey = "SOFTWARE\\MICROSOFT\\BOOTTEST"; $sLogonUserDataSubKey = "SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION\\WINLOGON"; $impersonationLevel = "impersonate"; $authLevel = "pktPrivacy"; $Privileges = join(",", values(%$hPrivileges)); $Provider = q(WbemScripting.SWbemLocator); $MasterData= q(winmgmts:{impersonationLevel=$impersonationLevel,authenticationLevel=$authLevel, ($Privileges)}! \\\\$ComputerName\\$NameSpace); sub ObjectExecMethod{ my ($WbemObjClassName, $MethodName, $ComputerName, $ParameterName, $ParameterValue, $User, $Password) = @_; # play with the command line expansion # here. $DEBUG and print STDERR "ObjectExecMethod: using PROVIDER: ", $Provider, " " , $User, "\n"; my $WbemLocator = Win32::OLE->new($Provider); OLEerrmsg("Could not instatiate $Provider") unless defined $WbemLocator; my $SWbemServices = ($User =~ /\*/) ? $WbemLocator->ConnectServer( $ComputerName, $NameSpace) : $WbemLocator->ConnectServer( $ComputerName, $NameSpace, $User, $Password ); defined $SWbemServices or OLEerrmsg("Cannot connect: $ComputerName, $NameSpace"); # Make sure we are using the appropriate security level OLEerrmsg("Error setting security level") if ( !($SWbemServices->{Security_}->{ImpersonationLevel} = 3) ); map { $SWbemServices->{Security_}->{Privileges}->AddAsString($_) or OLEerrmsg( "Error setting $hPrivileges->$_ privileges"); } keys %$hPrivileges; # Get the process object on the remote machine my $WbemObject = $SWbemServices->Get($WbemObjClassName); my $Result = $WbemObject->SetProperty($ParameterName, $ParameterValue) unless !defined($ParameterName); my $ProcessId = \0; #~ $REALDEBUG and #~ print $SEPARATOR , $WbemObject->Invoke("GetObjectText_"), $SEPARATOR; #~ too much info here my $ResultArrayRef = [(in $WbemObject->Invoke("Methods_"))]; $DEBUG and print STDERR "$WbemObjClassName: METHODS_:\n"; $DEBUG and map {&FormatDebugMsg($_->{"Name"}, $_->{"value"})} @$ResultArrayRef; OLEerrmsg("undefined method: $MethodName") unless grep {/$MethodName/i} map {$_->{"Name"}} @$ResultArrayRef; defined $WbemObject or OLEerrmsg("Could not create remote $WbemObjClassName"); # Now invoke the method $DEBUG and print STDERR $WbemObject->{CommandLine}, "\n"; OLEerrmsg("At $ComputerName, $ParameterName($ParameterValue) => $Result" ) if ($Result = $WbemObject->Invoke($MethodName, $WbemObject->{CommandLine}, undef, undef, $ProcessId)); # ):- $ProcessId not filled up :-( undef $WbemLocator; undef $SWbemServices; undef $WbemObject; Win32::OLE->Uninitialize(); $DEBUG and print STDERR "OK\n"; 0; } sub ExecQuery{ local ($ComputerName, $Query, $DisplayFields, $User, $Password) = @_; # the "local" keyword is necessary to get search/replace able to do /e switch my @MasterData = split (/\n/, $MasterData); my %Caption = map {$_=> $_} @$DisplayFields; # The values of %Caption must not be identical to the retrieved field names my $Caption = \%Caption; grep {s/\$\{?(\w+)\}?/${$1}/ge} @MasterData, $Query; # here we replace all the vars with their values $MasterData = join("", @MasterData); $Query =~ s/([^\\])\\([^\\])/$1\\\\$2/g; my $ResultSet = undef; $DEBUG and print STDERR "ExecQuery: using PROVIDER: ", $Provider, " " , $User, "\n"; my $WbemLocator = Win32::OLE->new($Provider); OLEerrmsg("Could not instatiate $Provider") unless defined $WbemLocator; my $SWbemServices = ($User =~ /\*/) ? $WbemLocator->ConnectServer( $ComputerName, $NameSpace) : $WbemLocator->ConnectServer( $ComputerName, $NameSpace, $User, $Password ); defined $SWbemServices or OLEerrmsg("Cannot connect: $ComputerName, $NameSpace"); # Make sure we are using the appropriate security level OLEerrmsg("Error setting security level") if ( !($SWbemServices->{Security_}->{ImpersonationLevel} = 3) ); map { $SWbemServices->{Security_}->{Privileges}->AddAsString($_) or OLEerrmsg( "Error setting $hPrivileges->$_ privileges"); } keys %$hPrivileges; $DEBUG and print STDERR "\$Query: ", qq($Query), "\n"; ### ### eval { $ResultSet = Win32::OLE->GetObject($MasterData)->ExecQuery($Query); }; ### $DEBUG and $Query = "" eval { $ResultSet = $SWbemServices->ExecQuery($Query); }; # # my $ResultArrayRef = [(in $ResultSet)] unless ($@); $ResultArrayRef or print STDERR "Win32::OLE::LastError ", Win32::OLE::LastError, "\n"; my @ResultCursor; # pack ResultSet -> ResultCursor; map {map {push @ResultCursor, {$Caption->{$_} => $ResultArrayRef->[0]->{$_}}} @$DisplayFields and shift @$ResultArrayRef} (0..$#$ResultArrayRef); Win32::OLE->Uninitialize(); \@ResultCursor; } # &IsRunning($ProcMask, $runAtHost); # lists processes on the host $runAtHost to # find pattern $ProcMask. # for all process found, collect the # $1 of the search # e.g. you get language of the running postbuiild with # the pattern: # "\\\\POSTBUILDSCRIPTS\\\\PBUILD\\\.CMD\\\"\\s\+\\\-l[: ]\(\\w\{3\}\)"; # # plus some properties of the $ProcessSet # return value: array pointer. The arguments are filtered out by default: # (/S:\\BLD_WNXF1\RELEASE\GER\3562.X86FRE.MAIN.011002-1901\PRO /TEMPDRIVE:D /#T:7) # PID= 00220 # NAME= winnt32.exe # CMD= \\bld_wnxf1\release\ger\3562.x86fre.main.011002-1901\pro\i386\winnt32.exe /s:\\bld_wnxf1\release\ger\3562.x86fre.main.011002-1901\pro /tempdrive:d /#t:7 sub IsRunning{ my ( $WMIclass, $ProcMask, $Field, $Fields, $runAtHost) = @_; # filter out arguments $ProcMask = $ProcMask."\\\"\? \(\.\+\)\$" unless $ProcMask=~/\(/; $DEBUG and print STDERR $ProcMask, "\n"; my $ProcessSet; my @result; my $Moniker = "winmgmts:{impersonationLevel=impersonate}!\\\\HOST\\root\\cimv2"; $Moniker =~ s/HOST/$runAtHost/; $DEBUG and print STDERR "using MONIKER: ", $Moniker, "\n"; eval { $ProcessSet = Win32::OLE->GetObject($Moniker)->InstancesOf ($WMIclass); }; unless($@){ foreach my $ProcessInst (in $ProcessSet){ push @result, join("\n", uc($1), map {"$_=".$ProcessInst->{$_}} @$Fields), if $ProcessInst->{$Field} =~ m/$ProcMask/gi; } }else{ print STDERR Win32::OLE->LastError, "\n"; } $DEBUG and print STDERR join ("\n", @result) , "\n"; \@result; } # supplyRegData # usage: # $defaultData = { # "DEBUG" => $DEBUG, # "Lang" => $Lang , # ... # } # $volatileData = {} # # &supplyRegData($sTargetcomputer, $REGISTRY_KEY, # $defaultData, $volatileData , $FovWrite); # # sub supplyRegData{ my ($sTargetcomputer, $sBootTestDataSubKey, $defaultData, $volatileData, $FovWrite) = @_; use strict; use Win32API::Registry 0.21 qw(HKEY_LOCAL_MACHINE REG_OPTION_VOLATILE REG_OPTION_NON_VOLATILE KEY_ALL_ACCESS KEY_READ REG_SZ regLastError RegConnectRegistry RegOpenKeyEx RegEnumValue RegQueryInfoKey RegQueryValueEx RegCreateKeyEx RegSetValueEx RegDeleteValue RegCloseKey ); # This provides fairly low-level access to the Win32 System API calls # dealing with the Registry [mostly from WINREG.H]. This is mostly my $hRootKey = $sRootKey; { no strict 'refs'; $hRootKey =~ s/(\w+)/$1/eeg; use strict 'refs'; } my $sStatusSubKey = "STATUS"; my $ohKey = undef; my $ohSubKey = undef; my $ohStatusSubKey = undef; my $ocValues; my $olValName; my $olValData; my $olSecDesc; my $opValData; my $osValName; my $sValueNames = []; my $sValueName; my $nStatus = undef; $DEBUG and print STDERR "COMPUTER: ".uc($sTargetcomputer)."\n"; &RegConnectRegistry( $sTargetcomputer, $hRootKey, $ohKey ) or die "Can't open $hRootKey: ", regLastError(),"\n"; $DEBUG and print STDERR "Registry Key: ".$sRootKey."\\$sBootTestDataSubKey\n"; $nStatus = &RegOpenKeyEx( $ohKey, $sBootTestDataSubKey ."\\". $sStatusSubKey, 0, KEY_READ, $ohStatusSubKey ); if (scalar(keys(%$volatileData))){ $sValueName = (keys(%$volatileData))[0]; $opValData = undef; if ($nStatus){ &RegQueryValueEx( $ohStatusSubKey, $sValueName , [], [], $opValData, [] ); # error is ignored. $opValData = "(undef)" unless $opValData; &RegCloseKey($ohStatusSubKey); } if ($nStatus ){ $DEBUG and print STDERR "\\\\$sTargetcomputer\\$sRootKey\\$sBootTestDataSubKey\\$sStatusSubKey\\$sValueName: ",$opValData, "\n"; $FovWrite or return 1; } } RegCreateKeyEx($ohKey, $sBootTestDataSubKey, 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, [], $ohSubKey, []); RegCloseKey($ohSubKey); &RegOpenKeyEx( $ohKey, $sBootTestDataSubKey , 0, KEY_ALL_ACCESS, $ohSubKey ) or die "Can't open $sBootTestDataSubKey: \n", regLastError(),"\n"; &RegQueryInfoKey($ohSubKey, [], [], [], [], [], [], $ocValues, $olValName, $olValData, [], [] ) or die "Can't query $sBootTestDataSubKey: ", regLastError(),"\n"; $DEBUG and print STDERR $ocValues, " values found\n"; foreach my $Data (keys(%$defaultData)){ if ($FovWrite){ # tsanders: existing values do not get overwritten. &RegDeleteValue($ohSubKey, $Data); $DEBUG and print STDERR "\t\tDeleting Default Data: \"$Data\"\n\t\t$^E\n"; #ignore the "$^E" here. } &RegSetValueEx($ohSubKey, $Data, 0, REG_SZ, $defaultData->{$Data}, 0 ) or die "Can't RegSetValueEx($Data,...$defaultData->{$Data}: ", regLastError(),"\n"; } foreach my $nCnt (0..$ocValues-1){ &RegEnumValue($ohSubKey, $nCnt, $osValName, 0, [], [], $opValData, 0 ); push @$sValueNames, $osValName; } foreach $sValueName (@$sValueNames){ &RegQueryValueEx( $ohSubKey, $sValueName , [], [], $opValData, [] ) or die "Can't read $sValueName: ", regLastError(),"\n"; $DEBUG and print STDERR "$sValueName=$opValData\n"; } RegCreateKeyEx($ohSubKey, $sStatusSubKey, 0, "", REG_OPTION_VOLATILE, KEY_ALL_ACCESS, [], $ohStatusSubKey, []); foreach my $Data (keys(%$volatileData)){ &RegSetValueEx($ohStatusSubKey, $Data, 0, REG_SZ, $volatileData->{$Data}, 0 ) or die "Can't RegSetValueEx($Data,...$volatileData->{$Data}: ", regLastError(),"\n"; } map {defined($_) and RegCloseKey($_)} ($ohSubKey, $ohKey, $ohStatusSubKey); 0; } sub OLEerrmsg($) { my $text = shift; errmsg( "$text (". Win32::OLE->LastError(). ")" ); exit 1; } sub ScheduleTime{ use Time::localtime; my $shiftTime = shift; my $tNow = time(); $DEBUG and print STDERR "Current time: " , &localtime->hour(), ":", &localtime->min(), "\n"; $tNow += $shiftTime; my @result = map {sprintf("%02d", $_)} (&localtime($tNow)-> hour(), &localtime($tNow)-> min(), &localtime($tNow)-> sec(), &localtime($tNow)-> mon() + 1, &localtime($tNow)-> mday(), &localtime($tNow)-> year() + 1900 ); $DEBUG and print STDERR "Schedule time: " , $result[0], ":", $result[1], ":", $result[2], " ", $result[3], "/", $result[4], "/", $result[5], "\n"; \@result; } sub FormatDebugMsg{ format STDERR = # field name field value @<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $_[0] $_[1] . write STDERR; } # BackTick # sample usage: &BackTick("dir"); # &BackTick(<new("WScript.Shell"); $opBrws->Popup(join("\n", @infa), $TIMEOUT , ref($opBrws) . "<- $0,". $VERSION, 0 + 64); undef($opBrws); Win32::OLE->FreeUnusedLibraries(); Win32::OLE->Uninitialize(); 1; } 1; __END__ #-------------------------------------------------------------// =head1 NAME B - Interface the query engine exposed by WMI =head1 SYNOPSIS use WMIFE; &WMIFE::ExecQuery($ComputerName, $Query, [@Fields]); &WMIFE::FormatDebugMsg($Comment, $Field); =head1 DESCRIPTION Generates the WQL (A.K.A. WMI SQL) query on the remote machine against its namespace. Sample objects that reside on the remote box $ComputerName are: * local files [/.//ROOT/CIMV2/CIM_DATAFILE], * running processes [/.//ROOT/CIMV2/WIN32_PROCESSES], * registry entries [/.//ROOT/DEFAULT/STDREGPROV]. The WMI is functional even in the absence of logon sessions on $ComputerName. =head1 METHODS =head2 FormatDebugMsg($fieldName, $FieldValue) Nice (?) formatting of the RDB like table field output (A.K.A. browse view): Name perl.exe Description perl.exe ProcessID 2920 ParentProce 2692 ExecutableP d:\ntt\tools\perl\bin\perl.exe ... =head2 ExecQuery($ComputerName, $Query, $DisplayFields) Execute the WMI WQL query against the namespace of the remote machine where $ComputerName is the name of the machine which namespaces are about to be queried. $Query the string representing the query, with the dollarsigned names placeholders for parameters $DisplayFields reference to the field list to retusn the result The return value is a reference to the like the RDB cursor. (array of hashes where keys are described in $DisplayFields array). If no data is returned from the query the returned value is a reference to undefined value. =head2 ObjectExecMethod($MethodName, $ComputerName, $ParameterName, $ParameterValue, $User, $Password Access the namespace on the remote box $ComputerName, and execute the method $MethodName of the class $WbemObjClassName (currently only for Win32_Process). Supply the parameter(s) via name/value. PErfurm the call under $User account, proving the password. =head2 Sample calls: =head2 To list files in the root directory of the logical disk labeled $Drive of the machine $Machine $Results = &WMIFE::ExecQuery($Machine, qq(SELECT * FROM cim_Datafile WHERE Drive="$Drive" AND Path="\\\\" "), [qw(Drive Path Name)]); =head2 To list logical disks of the machine $Machine $Results = &WMIFE::ExecQuery($Machine, qq(SELECT * FROM Win32_LogicalDisk WHERE DriveType=3), [qw(Name Description)]); =head2 To list the running processes $Results = &WMIFE::ExecQuery($ComputerName, qq(SELECT * FROM Win32_Process), [qw(Name Description ProcessID ParentProcessId ExecutablePath )]); =head2 To view installed software $Results = &WMIFE::ExecQuery($ComputerName, qq(SELECT * FROM Win32_Product), [qw(Name Vendor Version)]); =head2 To verify administrators group $Results = &WMIFE::ExecQuery($ComputerName, qq(SELECT * FROM Win32_GroupUser WHERE GroupComponent="Win32_Group.Domain=\\"$ComputerName\\",Name=\\"Administrators\\""), [qw(GroupComponent)]); =head2 To browse the process dependency hierarchy $Results = &WMIFE::ExecQuery($ComputerName, qq(SELECT * FROM CIM_ProcessExecutable), [qw(Antecedent Dependent)]); =head2 Query who is currently logged to somewhere. $Results = &WMIFE::ExecQuery($ENV{"COMPUTERNAME"}, # interactive session qq(SELECT * FROM Win32_LogonSession WHERE LogonType=2), [qw(LogonId LogonType)]); foreach my $Result (@$Results){ $DEBUG and map {&WMIFE::FormatDebugMsg($_, $Result->{$_})} keys (%$Result); push @Ids, $Result->{"LogonId"}; } $Results = &WMIFE::ExecQuery($ENV{"COMPUTERNAME"}, # Difficult to build the WHERE condition. qq(SELECT * FROM Win32_LoggedOnUser), [qw(Antecedent Dependent)]); my @Users = (); my $User = undef; my $LoggedUser = undef; my @LoggedUsers = (); # a little bit ugly. foreach my $Id (grep {/\d+/} @Ids){ foreach my $Result (@$Results){ $User = $Result->{"Antecedent"}; if ($User =~ /\S/){ push @Users, $User; } if ((grep {/\b$Id\b/} $Result->{"Dependent"})){ $LoggedUser = pop @Users; push @LoggedUsers, $LoggedUser; $DEBUG and &WMIFE::FormatDebugMsg("win32_user", $LoggedUser); $DEBUG and &WMIFE::FormatDebugMsg("win32_logonSession", $Id); } } } map {($user, $domain) = (/Win32_Account.Domain=\"(\w+)\",Name=\"(\w+)\"/); $DEBUG and print $user, "\t" , $domain,"\n"; } @LoggedUsers; =head2 To list the $Result contents, the following framework may be used: use WMIFE; &WMIFE::ExecQuery($ENV{"COMPUTERNAME"}, qq(SELECT * FROM Win32_BootConfiguration), [qw(Caption BootDirectory Description)]); foreach my $Result (@$Results){ map {&WMIFE::FormatDebugMsg($_, $Result->{$_})} keys (%$Result); } =head2 To design the new query please use the %WINDIR%\SYSTEM32\WBEM\wbemtest.exe vendor shell for accessing and querying the WMI classes and then use the query string designed in that shell as an argument to the Perl qq() [double backslashes] Some queries may take long time to execute, before being sufficiently refined. =head1 SEE ALSO Whole bunch of tools interfacing WMI is in progress. Note that the Perl interface to WMI is not as powerful as VBSCRIPT/JSCRIPT since there is no working examples for P/P (A.K.A. object sinking) and in general any interface(s) other than SQL. the design of such sample code might prove to be complicated indeed. =head2 supplyRegData($sTargetcomputer, $Lang, $BldNum, $Sku, $Drive, $BuildName, $Unattend, $FovWrite) Write the data to the REGISTRY of the Boot test machine to start the execution of test build installation. The REGISTRY path where the data is stored is: HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\BOOTTEST The data includes the following fields: -------------+----------------------------------------------------------- $Lang | fields are used to find the test bits, in the form: $BuildName | $Drive\$Lang\$BuildName $BldNum | $BldNum, $Sku and $Lang are used to verify the $Sku | REGISTRY data is actual for the build being installed. $Drive | target drive for winnt32.exe $Unattend | location of the answer file The data is written by the postbuild on the build machine (NTDEV\NTBUILD), and is later consumed from boot machine side by (NTDEV\ user). The registry access across the network hence the use of HKEY_LOCAL_MACHINE. The default REGISTRY key names are: HKLM\SOFTWARE\MICROSOFT\BOOTTEST\_BldNum => $BldNum HKLM\SOFTWARE\MICROSOFT\BOOTTEST\_BuildArch => $_BuildArch HKLM\SOFTWARE\MICROSOFT\BOOTTEST\LANG => $Lang HKLM\SOFTWARE\MICROSOFT\BOOTTEST\_BldSku => $Sku HKLM\SOFTWARE\MICROSOFT\BOOTTEST\_BuildType => $_BuildType HKLM\SOFTWARE\MICROSOFT\BOOTTEST\_BldDrive => $Drive HKLM\SOFTWARE\MICROSOFT\BOOTTEST\Unattend => $Unattend HKLM\SOFTWARE\MICROSOFT\BOOTTEST\_BldName => $BuildName Running regedit.exe will show the following data: +----------------------------------------------- |Windows Registry Editor Version 5.00 | |[HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\BOOTTEST] |"_BuildArch"="x86" |"_BuildType"="fre" |"_BldSku"="SRV" |"_BldName"="3615.x86fre.main.020306-1639" |"_BldDrive"="D" |"Unattend"="unattend.txt" |"LANG"="GER" |"_BldNum"="3615" | |[HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\BOOTTEST\Status] |"COMPUTERNAME"="BLD_WNXF1" +----------------------------------------------- The Status subkey is to prevent concurrent execution of the more than one test build install on the single test machine. The function will return the data observed e.g. \\sergueik4\HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\BOOTTEST\STATUS\COMPUTERNAME: SERGUEIK2 To ignore the presence of the Status set $FovWrite = 1. Booting test machine (but not logging off of the user) will cause Status key to disappear. =head2 &ScheduleTime($Delay) Calculate the time/date for execution of the scheduled tasks in $Delay secs from NOW. Example usage: use WMIFE; $WMIFE::DEBUG = 1; my $DELAY1 = 1800; # delay in 1800 secs = 30 min my $string = "/st %HH%:%MM%:%SS% /sd %mm%/%dd%/%yyyy%" ; my $marker = ["HH" , "MM", "SS", "mm", "dd", "yyyy"]; # this is the way to set the date/time of scheduled task my $ScheduleTime = &WMIFE::ScheduleTime($DELAY1); print STDERR "\"$string\"\t"; map {$string =~ s/\%$marker->[$_]\%/$ScheduleTime->[$_]/g;} (0..$#$marker); print STDERR "\"$string\"\n"; Current time: 15:51 Schedule time: 16:21:32 03/25/2002 "/st %HH%:%MM%:%SS% /sd %mm%/%dd%/%yyyy%" "/st 16:21:32 /sd 03/25/2002" =head1 AUTHOR Serguei Kouzmine sergueik@microsoft.com =cut 1;