Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

[70-536, #19] Query WMI, ManagementObjectSearcher e BackgroundWorker

In questo post parleremo in modo particolare di come fare delle query verso il mondo Windows Management Instrumentation (WMI). Tramite WMI, possiamo infatti accedere dal mondo managed di .NET a tutta una serie di informazioni gestite da Win32, alcune delle quali particolarmente interessanti, specialmente se si lavora un po' più a basso livello rispetto a quanto facciamo di solito. Tanto per citarne qualcosa, è possibile eseguire delle query tipo:

SELECT FROM Win32_NTLogEvent        -- Log degli eventi
SELECT FROM Win32_BIOS            -- Info sul BIOS
SELECT FROM Win32_NetworkAdapter    -- Info sulle schede di rete
SELECT FROM Win32_LogicalDisk        -- Info sui dischi
SELECT FROM Win32_Product            -- Info sui software installati
                                    -- con Windows Installer

Il linguaggio utilizzato è un SQL-style, utilizza la clausola SELECT * FROM identifer, ed eventualmente possiamo aggiungere una clausola WHERE per filtrare determinati contenuti. MSDN riporta la struttura delle classi esposte da Win32, giusto per capire, per esempio, a cosa possiamo accedere (share di rete, scheda madre, servizi di sistema, etc. etc.). Per esempio, la classe Win32_Processor espone molte proprietà relative alla CPU installata nel sistema. Questa pagina riporta maggiori dettagli.

.NET permette di accedere a queste informazioni con delle classi contenute nel namespace System.Management. Nel codice che metterò a disposizione ho fatto uso delle classi ManagementObjectSearcher, ObjectQuery, ManagementObject e così via. Lo vedremo fra poco. Siccome il reperimento di alcune informazioni potrebbe essere un po' lento, ho fatto uso del componente BackgroundWorker per rendere asincrona tale operazione. E mi sono divertito parecchio!

Come interrogare WMI ed ottenere quello che vogliamo
Interrogare WMI significa semplicemente istanziare un oggetto ManagementObjectSearcher, passando come parametro un ObjectQuery che rappresenta ciò che vogliamo ottenere:

ObjectQuery qry = new ObjectQuery(query);
ManagementObjectSearcher src = 
new ManagementObjectSearcher(qry);

objects = src.Get();
foreach (ManagementObject obj in objects)
    
// gestisto ogni obj per popolare
    // una ListBox

Nel codice qui sopra, la variabile query è una semplice string come "SELECT * FROM Win32_DesktopMonitor", ad esempio, o una qualsiasi riportate nel primo riquadro. Il metodo Get() della classe ritorna un oggetto ManagementObjectCollection che contiene tutti gli oggetti ritornati. Tale collection può essere ovviamente ciclata con un banale foreach. Ogni elemento della collection è in realtà un'istanza di ManagementObject che, per definizione, può contenere qualsiasi cosa, dipendentemente dalla query che abbiamo eseguito (dal log degli eventi, alla struttura delle partizioni di avvio del sistema, dal BIOS alla porta seriale del PC): per questo motivo, la classe ManagementObject espone tutta una serie di metodi (primo fra tutti GetPropertyValue) che ci permette di ottenere ciò che vogliamo. GetPropertyValue ritorna in ogni caso un object, che dobbiamo castare al tipo corretto.

Qui sotto riporto uno screenshot della piccola applicazione che ho scritto:

Ho creato una ComboBox che contiene alcune query WMI carine, una prima ListBox che elenca gli oggetti recuperati dalla query, ed una seconda ListBox che contiene le proprietà di ogni singolo oggetto che selezioniamo dal primo elenco. Per ottenere l'elenco delle proprietà, ho utilizzato la proprietà Properties di ogni singolo ManagementObject, che ritorna un'istanza di PropertyDataCollection .

Siccome l'operazione può essere un po' lunga...lo facciamo asincrono?
A seconda della query, il click su Button Refresh della mia WF può impiegare un po' di tempo, bloccando la UI e di certo questa non è una cosa bella. Provate a fare una query con "SELECT * FROM Win32_Product" e capirete subito cosa intendo.  Ho quindi utilizzato il componente BackgroundWorker per rendere asincrona l'esecuzione della query ed il popolamento della prima ListBox. Non voglio scendere troppo nel dettaglio, però voglio comunque fare il punto su cosa ho fatto per implementare questa cosa:

  1. ovviamente, ho aggiunto un BackgroundWorker sulla mia WF
  2. nel costruttore della WF, chiamo un mio metodo privato InitializeBackgroundWorker che associa 3 handler a 3 eventi del componente (DoWork, ProgressChanged e RunWorkerCompleted)
  3. Imposto la proprietà WorkerReportsProgress = true
  4. i tre handler sono, rispettivamente, i metodi Comincia, Durante e AllaFine della mia WF (ho dato nomi esplicativi perchè stavo impazzendo )
  5. sul click del Button, chiamo il metodo RunWorkerAsync()

Quando clicco sul Button, il BackgroundWorker comincia a fare il suo lavoro. Notare che all'interno della function Comincia, faccio una cosa particolare: imposto in e.Result il risultato dell'esecuzione di executeQuery, che è la function che interroga WMI e con un loop foreach gestisce i ManagementObject trovati in base alla query. All'interno di executeQuery chiamo il metodo ReportProgress che non fa nient'altro che aggiornare la UI dal suo stesso thread, evitando tutte le complicazioni che ben conosciamo. Attenzione: questa operazione fallisce sistematicamente se non impostiamo WorkerReportsProgress = true, come indicato nel punto (3).

C'è una cosa che voglio sottolineare, perchè riveste molta importanza: il thread secondario in cui gira il BackgroundWorker comunica al thread della UI cosa mostrare sulla WF stessa. Notate infatti la chiamata a ReportProgress all'interno di executeQuery: questa funzione gira nel thread secondario, non può di conseguenza aggiornare la UI (provate a scrivere qualcosa sulla WF in questo punto e vedrete). Quindi, come facciamo a fare un refresh e ad informare l'utente di quello che sta accadendo? Basta fare una cosa molto semplice: utilizzare il secondo parametro (che è un semplice object) per comunicare al thread della UI cosa mostrare. Nel mio esempio, passo semplicemente:

worker.ReportProgress(100, obj.GetText(TextFormat.Mof));

Sinceramente, non sono stato a chiedermi cosa sia quell'enum TextFormat: mi basta sapere che dal thread secondario al thread della UI passo una string che non è nient'altro che una rappresentazione dell'oggetto che ho reperito. Tale chiamata scatena l'esecuzione del mio metodo Durante che, banalmente, reperisce il valore che gli è arrivato e lo aggiunge alla ListBox:

void Durante(object sender, ProgressChangedEventArgs e)
{
    
this.Text = "In esecuzione...";
    lstItems.Items.Add(e.UserState 
as string);
    
// altre operazioni
}

Ovviamente, l'oggetto passato da un thread all'altro può essere qualsiasi cosa.

Il codice, se vi interessa, è scaricabile da qui. Contiene un po' tutto quello che ho scritto sinora: le classi su Storage-Shelf-Book, la serializzazione, il custom visualizer, il process viewer e, oggi, tutto questo discorso delle query WMI. Non è molto user-friendly, nel senso che per vedere il funzionamento di una cosa piuttosto che di un'altra, è necessario andare manualmente nel Main() e decidere quale WF aprire.  Non ho badato molto a questo aspetto, tanto...voglio dire...è tutto a scopo didattico. La cosa interessante è aprire il codice di ogni WF e tirar fuori quello che serve.

Forse, se troverò qualche spunto interessante, farò un altro post su questo tema, perchè le classi offerte da System.Management sono parecchie ed alcune hanno attirato la mia attenzione.

powered by IMHO 1.2

Print | posted on Monday, February 27, 2006 12:50 PM | Filed Under [ Esame 70-536 ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET