WMI, PHP et plein d’autres trucs en trois ou quatre lettres (part 4)

Vendredi, c’est raviolis et WMI, quatrième partie !

To boldly go where no man has gone before

Dans la partie précédente, nous avons vu comment faire des requêtes (en langage WQL) WMI sur le PC local. Comme je vous l’ai dit, la connexion à un PC distant change un peu, on ne se contentera pas d’une seule ligne cette fois-ci. Voyons déjà à quoi ça ressemble :

<?php

$ip="192.168.0.2";
$WMIadmin="administrateur";
$WMIpwd="motdepasseenclairouhlala";

$WbemLocator = new COM ("WbemScripting.SWbemLocator");
$WbemServices = $WbemLocator->ConnectServer($ip, 'root\cimv2', $WMIadmin, $WMIpwd);
$WbemServices->Security_->ImpersonationLevel = 3;

?>

Décortiquons un peu tout ça. Au début du script, nous définissons trois variables :

  1.  $ip contient l’adresse IP de la machine distante que nous allons interroger (vous pouvez aussi mettre son nom, du moment que la résolution de noms fonctionne, par DNS ou hosts)
  2. $WMIadmin contient le nom d’un compte disposant des droits d’administration sur la machine distante. Si vous ne précisez rien, c’est un compte local (de la machine distante). Si vous voulez spécifier un nom de domaine, il suffit de l’indiquer sous cette forme : « domaineadministrateur ». Du très classique sous Windows.
  3. $WMIadmin contient le mot de passe (en clair…) de ce compte. Oui, coller un mot de passe en dur dans un script est rarement une bonne idée, car il suffit d’avoir accès au système de fichiers du serveur pour accéder au script sans qu’il soit exécuté…

Ensuite, nous établissons la connexion d’une manière différente :

  1. On crée une instance d’objet COM de type « WbemScripting.SWbemLocator »
  2. On se connecte au PC distant en appelant la méthode ConnectServer, et en spécifiant dans l’ordre l’adresse IP/le nom de la machine distante, le namespace qui nous intéresse (c’est le même qu’en local), le compte et le mot de passe.
  3. Enfin, on indique le niveau de sécurité souhaité. Ici, le serveur s’authentifie sur le contexte local de la machine distante (le client). Les différents niveaux sont détaillés ici mais en général, vous vous contenterez du niveau 3 (« Impersonation »).

Nous sommes désormais connectés, on peut lancer nos requêtes et récupérer les propriétés qui nous intéressent comme on l’a vu dans la troisième partie.

J’ai la requête qui colle

Je vous livre ici quelques classes et propriétés qui peuvent trouver leur utilité (c’est mal parce que ça vous évite de chercher, mais c’est bien parce que partager mes informations me rend beaucoup plus sympathique, non ?).

  • Connaître la version de Windows et le Service Pack installé

$system = $WbemServices->ExecQuery("Select * from Win32_OperatingSystem");
foreach($system as $os)
{
echo $os->Caption." ".$os->Version." (SP".$os->ServicePackMajorVersion.".".$os->ServicePackMinorVersion.")";
}

Ici, on peut se passer de la propriété ServicePackMinorVersion, cela évite d’afficher SP1.0 au lieu de SP1, mais sait-on jamais, peut-être que Microsoft sortira un jour un SP1.1… donc méfiance.

  • Informations sur le processeur

(oui, c’est le corrigé de l’exercice de l’autre fois).

$proc = $WbemServices->ExecQuery("Select * from Win32_Processor");
foreach ($proc as $p)
{
$proc_deviceid=$p->DeviceID;
$proc_manufacturer=$p->Manufacturer;
$proc_maxclockspeed=$p->MaxClockSpeed;
$proc_currentclockspeed=$p->CurrentClockSpeed;
$proc_name=$p->Name;
$proc_nbcores=$p->NumberOfCores;
$proc_nbvcores=$p->NumberOfLogicalProcessors;
$proc_socket=$p->SocketDesignation;
$proc_version=$p->Version;
}

Je vous laisse en faire un joli tableau comme celui-ci (tiré d’une de mes applis), les variables sont réutilisées dans cet ordre.

Oui je sais, c’est un vieux clou.

N’oubliez pas qu’on peut avoir plusieurs processeurs sur une carte mère, que la boucle foreach va regarder chaque « ligne » de résultat de la requête.  Il faut donc qu’avant la fin de la boucle vous affichiez les valeurs retournées. Si vous ne le faites qu’après la boucle, vous n’aurez les informations que pour le deuxième processeur…

Une astuce au passage : si $proc_nbvcores est égal au double de $proc_nbcores, c’est que l’HyperThreading est activé sur ce processeur. La valeur de la propriété SocketDesignation est assez spéciale : parfois on a le numéro du socket, parfois il s’agit du format de socket (LGA775 par exemple, ou U3E1 assez régulièrement pour des PC portables). Malheureusement, d’un système à l’autre (et donc d’un constructeur à l’autre), les valeurs ne sont toujours pas consistantes. Il ne faut donc pas estimer que si la valeur est correcte sur votre PC, elle le sera sur un autre.

  • Informations sur la RAM

Un exemple typique de classe où les valeurs des propriétés varient énormément d’un PC à un autre…

$memorytype=array(0=>"Unknown",1=>"Other",2=>"DRAM",3=>"Synchronous DRAM",4=>"Cache DRAM",5=>"EDO",6=>"EDRAM",7=>"VRAM",8=>"SRAM",9=>"RAM",10=>"ROM",11=>"Flash",
12=>"EEPROM",13=>"FEPROM",14=>"EPROM",15=>"CDRAM",16=>"3DRAM",17=>"SDRAM",18=>"SGRAM",19=>"RDRAM",20=>"DDR",21=>"DDR2",22=>"DDR2 FB-DIMM",24=>"DDR3",25=>"FBD2");

$ram = $WbemServices->ExecQuery("Select * from Win32_PhysicalMemory");

foreach($ram as $r)
{
$ram_capacity=$r->Capacity;
$ram_manufacturer=$r->Manufacturer;
$ram_model=$r->Model;
$ram_name=$r->Name;
$ram_speed=$r->Speed;
$ram_sn=$r->SerialNumber;
$ram_version=$r->Version;
$ram_bank=$r->BankLabel;
$ram_dl=$r->DeviceLocator;
$ram_type=$memorytype[$r->MemoryType];
}

Si vous regardez la doc de la classe Win32_PhysicalMemory, la propriété MemoryType peut avoir une valeur de 0 à 23. Comme un bête nombre n’est pas toujours compréhensible pour un humain non-mutant, j’ai ajouté le tableau $memorytype dans lequel on ira chercher, pour la valeur de MemoryType, la signification « en clair » (enfin, faut s’y connaître un peu en technologie de mémoire, sinon ce n’est pas si clair que ça…). C’est ce que fait la dernière ligne. De nombreuses autres propriétés nécessiteront ce genre de tableau de correspondance, un peu pénible à saisir, hélas.

Comme pour les processeurs, il peut y avoir plusieurs barrettes de mémoire sur la carte mère, donc pensez à faire votre affichage dans la boucle foreach.

La requête du Graal

Jusqu’à présent, nous avons vu des requêtes très simples (Select * from Win32_bidule), qui serviront la plupart du temps. Cela dit, WQL est un langage de requête un peu plus évolué que ça. Ainsi, au lieu de récupérer TOUTES les propriétés d’une classe (l’astérisque), vous pouvez définir une clause where pour être un peu plus spécifique.

Par exemple, dans l’exemple précédent, imaginons que vous ayez une barrette fabriquée par Micron, et une autre par Hynix/Hyundai (oui oui, Hyundai, comme pour les voitures… vous voyez que les comparaisons informatique/automobile tiennent la route !). Vous pouvez très bien construire votre requête pour n’avoir QUE la barrette (en réalité, les puces… même si vous avez une barrette avec un logo Kingston, vous verrez le nom du fabricant des puces mémoire) Micron, et même, n’avoir QUE les propriétés Manufacturer et Speed (vitesse du bus en MHz) :

SELECT Manufacturer, Speed FROM Win32_PhysicalMemory WHERE Manufacturer= »Micron »

J’ai volontairement mis SELECT, FROM et WHERE en majuscules, afin que vous vous rendiez bien compte à quel point WQL est proche du langage SQL. De fait, vous pouvez jouer avec des AND, des OR, des LIKE, TRUE, FALSE, NULL et j’en passe. La référence de WQL sur le MSDN se trouve ici.

Je n’irai pas volontairement plus loin dans l’utilisation de WQL et passe sous silence un certain nombres de points de WMI. Vous aurez par contre constaté en lisant les docs Microsoft que bien souvent, les classes Win32_machin héritent des propriétés d’autres classes (souvent des CIM_truc). Par exemple, pour la propriété Capacity de Win32_PhysicalMemory, on peut lire ceci :

Capacity
Data type: uint64
Access type: Read-only

Total capacity of the physical memory—in bytes. This property is inherited from CIM_PhysicalMemory.

Si vous suivez le lien, vous verrez que la classe CIM_PhysicalMemory possède des propriétés qu’on ne retrouve pas dans Win32_PhysicalMemory : c’est particulièrement intéressant car vous aurez ainsi plus d’informations sur un matériel. Après, tout dépend de ce que vous compter en faire, évidemment.

Team Requête ! Plus rapide que la lumière !

Rendez-vous, ou ce sera la guerre !

Jusqu’à présent, nous n’avons fait des requêtes que sur un seul namespace, rootcimv2. C’est déjà pas mal, on a accès à énormément d’infos. Mais à force de jouer avec WMI et à découvrir des classes et propriétés intéressantes, j’ai me suis mis dans l’idée qu’il devait être possible de savoir quelles applications étaient installées sur un PC donné.

Et là, malgré des heures de recherche dans wbemtest, j’ai fait chou blanc.

Et pour une raison toute simple, il faut aller voir dans un autre namespace. Car oui, il y en a plusieurs.
Celui qui nous intéresse s’appelle rootcimv2sms, et là bas, les classes ont tendance à s’appeler SMS_quelquechose.

Si dans votre programme vous voulez interroger deux namespaces, il faudra pour chacun faire une connexion différente (c’est exactement le même principe qu’avec les bases de données : une connexion par base. Là c’est une connexion par namespace).

$WbemLocator = new COM ("WbemScripting.SWbemLocator");
$SMS = $WbemLocator->ConnectServer($ip, 'root\cimv2\sms', $WMIadmin, $WMIpwd);
$SMS->Security_->ImpersonationLevel = 3;

$sms_soft = $SMS->ExecQuery("Select * from SMS_InstalledSoftware");
foreach($sms_soft as $ss)
{
$sms_pub=$ss->Publisher;
$sms_pn=$ss->ProductName;
$sms_pv=$ss->ProductVersion;
$sms_id=$ss->InstallDate
}

Voilà le genre de résultat que vous pouvez obtenir (il faut penser à reformater la valeur de la propriété InstallDate à votre convenance) :

J’en ai tellement qu’on ne voit sur la capture d’écran qu’Office. Mais tout ce qui est dans Ajout/Suppression de programme (pardon : Programmes et fonctionnalités depuis Windows 7) remonte dans ma liste.

Un petit avertissement : cette requête prend du temps (à fortiori sur une machine distante) à être exécutée, et il m’est arrivé plus d’une fois qu’une machine dépasse les 30 secondes réglementaires pour répondre à ma requête. Résultat, mon script se terminait tristement en Fatal error: Maximum execution time of 30 seconds exceeded… Il suffit dans ce cas de prévoir un peu plus de temps grâce à la fonction set_time_limit de PHP.

 
N’oubliez pas que vous pouvez utiliser la clause Where dans vos requêtes ! Par exemple, vous pourriez très bien ne rechercher que les applis de tel éditeur, ou tout ce qui a été installé depuis telle date, etc etc… Là encore, expérimentez !

This is the end, my friend

Les bonnes choses ont toujours une fin et nous arrivons au terme de cette série d’articles. J’espère qu’elle vous a plu et donné envie d’en savoir un peu plus sur les possibilités offertes par WMI, et sur son exploitation en PHP. Si vous êtes fan d’un autre langage, vous pouvez tout à fait faire du WMI avec celui-ci. Vous trouverez de nombreux exemples en PowerShell (ce qui ravira les sysadmins), VB ou C++ sur Internet.

Si vous avez des questions, n’hésitez pas à les poser en commentaire, j’essaierai d’y répondre du mieux possible. De même, si vous voulez partager votre code avec le monde entier, c’est l’occasion ou jamais ! Sur ce, je file à la sieste ! Ciao !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.