Powershell, WMI e Hyper-V
Come vi ho annunciato qualche giorno fa, sono state rilasciate le specifiche delle classi WMI per la gestione di Hyper-V e delle macchine virtuali.
La documentazione è ancora ampiamente incompleta e in fase di sviluppo, ma da qualche giorno io sto comunque sperimentando per vedere di ricavare qualche script Powershell che, basandosi su quelle API, possa essere di una qualche utilità.
Al momento sono riuscito a ricavare gli esempi che trovate qui sotto, che vi rilascio senza alcuna pretesa ne di completezza, ne di codice ben scritto.
AVVERTENZE
Il codice che trovate in questo post è sperimentale, rilasciato "as is", non da diritto ad alcun supporto e non dove essere usato, in questo stadio di sviluppo, in un ambiente di produzione.
Se copiate il codice fate attenzione che l'impaginazione potrebbe avere sposato su più righe porzioni di codice che devono stare su un'unica riga!
Esempio 1 - Elenco delle macchine virtuali definite su un host
$State = ""
# Recupero degli oggetti VM dal computer locale
$VMs = get-wmiobject -class "MSVM_ComputerSystem" -namespace "root\virtualization" -computername "."
Format-Table -auto -inputobject $VMS @{Label="Nome VM"; expression={$_.ElementName}} , @{Label="GUID"; expression={$_.Name}} , @{Label="Stato"; expression={switch ($_.EnabledState) { 2 {"In esecuzione"} 3 {"Spenta"} 32768 {"In pausa"} 32769 {"Sospesa"} 32770 {"In avvio"} 32771 {"Snapshot in corso"} 32773 {"In salvataggio"} 32774 {"In arresto"} } }} | out-host
In quest'esempio usiamo get-WmiObject per ottenere una collezione di tutte le istanze della classe MSVM_ComputerSystem che definiscono le macchine virtuali in esecuzione su Hyper-V.
Tenendo presente che anche la Parent Partition è una VM ci serve un sistema per distinguere questa dalle Child Partition.
Possiamo usare a questo scopo la proprietà Caption, definita dalla classe MSVM_ComputerSystem, che assume il valore Microsoft Hosting Computer System per la parent partition e Microsoft Virtual Computer System per le child partition.
Di ogni VM restituiamo:
- il nome che compare nell'interfaccia di Hyper-V Manager, usando la proprietà ElementName,
- il vero nome della VM, che è un GUID ed è contenuto nella proprietà Name,
- lo stato della VM che può assumere i valori
Stato |
Codice |
Sconosciuto |
0 |
In esecuzione |
2 |
Spenta |
3 |
In pausa |
32768 |
Sospesa |
32769 |
In avvio |
32770 |
Snapshot in esecuzione |
32771 |
In migrazione (cluster) |
32772 |
Salvataggio inesecuzione |
32773 |
In fase di arresto |
32774 |
Cancellata |
32775 |
In pausa in esecuzione |
32776 |
Esempio 2 - Creazione di un hard disk virtuale
# Definizione dei parametri da passare allo script
param($VHDPath = "",$VHDSize = "",$HostName = ".")
# Verifica dei parametri passati
if (($VHDPath -eq "") -or ($VHDSize -eq ""))
{ write-host "Devono essere inseriti:"
write-host "- percorso completo del disco virtuale da creare"
write-host " es.: d:\VM\VM001.vhd"
write-host "- dimensione in GB del disco virtuale da creare"
write-host " es.: 50"
exit
}
# Definizione della variabile contenente la dimensione in byte
$GB = [System.UInt64] $VHDSize*1024*1024*1024
$ImageMgtService = get-wmiobject -class "Msvm_ImageManagementService" -namespace "root\virtualization" -computername $HostName
# Creazione del VHD
$Return = $ImageMgtService.CreateDynamicVirtualHardDisk($VHDPath.ToString(),$GB)
In questo caso, dopo aver controllato i parametri passati allo script, usiamo un'istanza della classe MSVM_ImageManagementService per creare un disco virtuale dinamico.
Usiamo per questo il metodo CreateDynamicVirtualHardDisk che richiede due parametri:
- Il path completo del VHD che si desidera creare passato come string (da qui l'uso del metodo ToString dell'oggetto $VHDPath)
- La dimensione in byte del VHD passato com Unsigned Interger a 64 bit (da qui l'uso del casting per forzare il tipo di dato della variabile $GB)
Gli altri metodi resi disponibili da MSVM_ImageManagementService per la gestione dei VHD sono:
- CompactVirtualHardDisk: compatta il VHD dinamico indicato
- ConvertVirtualHardDisk: converte un VHD da dinamico a fisso
- CreateDifferencingVirtualHardDisk: crea un VHD differenziale
- CreateFixedVirtualHardDisk: crea un VHD a dimensione fissa
- ExpandVirtualHardDisk: espande un VHD
- GetVirtualHardDiskInfo: restituisce le informazioni sul VHD indicato
- MergeVirtualHardDisk: fonde un disco differenziale e un parent disk
- Mount: esegue il mount come disco fisico locale della parent partition del VHD indicato
- ReconnectParentVirtualHardDisk: ripristina la connessione tra disco differenziale e parent disk
- Unmount: smonta un disco VHD dalla partizione parent
- ValidateVirtualHardDisk: verifica lo stato del VHD indicato
Esempio 3 - Mount/unmount di un VHD come disco locale della parent partition
# Definizione del parametro da passare allo script
param($VHDName="")
# Verifica del parametro passato
if ($VHDName -eq "")
{ write-host "Deve essere inserito il percorso completo del disco"
write-host "di cui fare il mount"
exit
}
# Recupero di MSVM_ImageManagementService
$VHDService = get-wmiobject -class "Msvm_ImageManagementService" -namespace "root\virtualization" -computername "."
# Mount del VHD
$Result = $VHDService.Mount($VHDName)
L'esempio è molto simile al precedente e richiede l'uso del metodo Mount definito dalla classe MSVM_ImageManagementService.
Per ottenere lo script che esegue la funzione opposta si deve solo sostituire nel codice al chiamata al metodo Mount con la chiamata al metodo Unmount.
Entrambi i metodi richiedono i passaggi dello stesso parametro: una stringa che contiene il percorso completo del disco VHD.
Attenzione
Modificare il contenuto dei VHD montati come dischi locali può danneggiare i VHD stessi e renderli inusabili. Se si usano gli snapshot, le ultime modifiche apportate non saranno visibili nel disco VHD montato localmente perché sono scritte nel file AVHD.
Esempio 3 - Creazione di uno snapshot
# Definizione del parametro richiesto dallo script
param($VMName = "")
# Verifica del corretto passaggio del parametro
if ($VMName -eq "")
{ write-host "Deve essere inserito il nome della VM di cui"
write-host "fare lo snapshot"
exit
}
# Recupero di MSVM_VirtualSystemManagementService
$VSService = get-wmiobject –namespace root\virtualization –class Msvm_VirtualSystemManagementService
# Recupero dell'oggetto legato alla VM su cui si vuole operare
$VM = get-wmiobject -namespace root\virtualization -class Msvm_ComputerSystem -filter " ElementName = '$($VMName)'"
# Esecuzione dello snapshot della VM
$Result = $VSService.CreateVirtualSystemSnapShot($VM.__PATH)
# Verifica e restituzione del risultato dell'operazione
switch ($Result.ReturnValue)
{ 2 { write-host "Snapshot eseguito correttamente: "
$Result.ReturnValue; break}
4096 { write-host "Snapshot eseguito correttamente: "
$Result.ReturnValue; break}
default { write-host "Si è verificato un errore nell'esecuzione
dello snapshot: " $Result.ReturnValue}
}
Per prima cosa creiamo un oggetto della classe MSVM_VirtualSystemManagementService che useremo per eseguire l'operazione di snapshot su una specifica VM.
Subito dopo usiamo la classe MSVM_ComputerSystem per ottenere l'elenco delle VM e selezionare la specifica VM di cui vogliamo eseguire lo snapshot.
Per ultimo chiamiamo il metodo CreateVirtualSystemSnapshot, definito dalla classe MSVM_VirtualSystemManagementService, per creare lo snapshot. Questo metodo richiede come parametro la proprietà __PATH (con doppio underscore) della VM che contiene un percorso del tipo
\\>server>\root\virtualization:Msvm_ComputerSystem.CreationClassName="Msvm_ComputerSystem",
Name=<VMname>
L'operazione può terminare con due codici di successo:
- 0=operazione terminata con successo
- 4096=operazione in eseguita in asincrono con successo
Esempio 5 - Creazione di una VM
Quest'esempio è ampiamento incompleto (manca la parte di configurazione delle risorse della VM creata: RAM,VHD, ecc), poco pulito e usa un escamotage in quanto non sono ancora riuscito a capire come instanziare un oggetto della classe Msvm_VirtualSystemGlobalSettingData per definire i parametri da passare al metodo di creazione della VM. Appena avrò risolto questi problemi aggiornerò il post con il codice completo.
# Definizione dei parametri richiesti dallo script
param($VMName="")
# Nome assegnato per default alla VM
$DefaultVMName = "New Virtual Machine"
if ($VMName -eq "")
{ write-host "Indicare il nome della macchina virtuale da creare"
exit
}
# Creo un oggetto Virtual System Management Service per la
# gestione della VM e creo un VM di default
$VSMgtService = get-wmiobject -class "Msvm_VirtualSystemManagementService" -namespace "root\virtualization" -computername "."
# Creazione di una VM con configurazione di default
$VSMgtService.DefineVirtualSystem()
# Recupero dell'oggetto VM appena creato per modificarlo
$query = "SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" + $DefaultVMName + "'"
$VM = get-wmiobject -query $query -namespace "root\virtualization" -computername "."
# Recupero delle impostazioni della VM appena creata
$query = "Associators of {$VM} WHERE AssocClass = MSVM_SettingsDefineState"
$VMSystemSettingData = get-wmiobject -query $query -namespace "root\virtualization" -computername $HostName
# Modifica della proprietà ElementName
$VMSystemSettingData.ElementName = $VMName
# Scrittura della proprietà modificata con ModifyVirtualSystem
$Result = $VSMgtService.ModifyVirtualSystem($VM.__PATH,$VMSystemSettingData.psbase.GetText(1))
Lo script usa il metodo DefineVirtualSystem, senza parametri, per creare una macchina virtuale.
Quando si chiama questo metodo senza parametri viene creata una VM con le impostazioni di default (percorso della VM coincidente con quello di default definito in Hyper-V Manager, 512MB di RAM, ...) e nome predefinito "New Virtual Machine"
Subito dopo possiamo usare una query WMI per ottenere un oggetto della classe MSVM_ComputerSystem che continene le informazioni relative alla VM appena creata.
Usiamo l'oggetto appena ricavato in un'altra query WMI che ci permette di ricavare i dati di configurazione della VM (classe MSVM_SettingsDefineState).
Possiamo ora modificare il parametro ElementName della VM per impostare il nome desiderato e quindi usare il metodo ModifyVirtualSystem, definito nella classe MSVM_VirtualSystemManagementService, per impostare il nuovo nome.
Come vedete questo script è veramente povero ed anche scarsamente utile. Con la documentazione che abbiamo a disposizione e con la mia scarsa esperienza di WMI e Powershell questo è tutto quello che per il momento sono riuscito ad ottenere.
Se qualcuno di voi riesce a fare meglio (e sono molto confidente che ci riusciate) lasciate un commento al post con il codice e il primo che arriva con qualcosa di bello e utile (a mio insindacabile giudizio) riceverà una sorpresa.
Esempio 6 - Start e shutdown di una VM
# Definizione dei parametri richiesti dallo script
param($VMName="",$Op="")
# Verifica del passaggio del nome della VM da spegnere
if (($VMName -eq "") -or ($Op -eq ""))
{ write-host "Devono essere inseriti:"
write-host "- il nome della VM"
write-host "- l'operazione da eseguire"
write-host " - start per avviare la VM"
write-host " - stop per eseguire lo shutdown della VM"
exit
}
# Recupero dell'oggetto VM
$query = "SELECT * FROM Msvm_ComputerSystem WHERE ElementName='" + $VMName + "'"
$VM = get-wmiobject -query $query -namespace "root\virtualization" -computername "."
# Esecuzione dell'operazione richiesta o messaggio di errore
switch ($Op)
{ "stop" { # Recupero dei componenti di shutdown della VM
$query = "SELECT * FROM Msvm_ShutdownComponent WHERE
SystemName='" + $VM.Name + "'"
$Shutdown = get-wmiobject -query $query -namespace
"root\virtualization" -computername "."
# Richiesta di uno shutdown forzato
$Result = $Shutdown.InitiateShutdown
($true,"Spegnimento programmato")
break
}
"start" { # Recupera l'oggetto VM
$query = "SELECT * FROM Msvm_ComputerSystem WHERE
ElementName='" + $VMName + "'"
$VM = get-wmiobject -query $query -namespace
"root\virtualization" -computername "."
# Richiesta del cambio di stato dell'oggetto VM
$Result = $VM.RequestStateChange(2)
}
default { # Parametri inseriti errati
write-host "Sono supportate solo le operazioni:"
write-host " - start"
write-host " - stop"
}
}
Questo ultimo script ci consente di avviare o eseguire lo shutdown forzato di una VM.
Lo script richiede il passaggio di due parametri:
- il nome della VM su cui si vuole operare, come compare in Hyper-V Manager (ElementName)
- l'operazione che si intende eseguire: start o stop
Per avviare una VM lo script recupera l'oggetto MSVM_ComputerSystem legato alla VM specifica e quindi chiama il metodo RequestStateChange (definito nella stessa classe) con il parametro 2 che significa Avvia.
Gli altri valori che possono essere passati al metodo RequestStateChange sono: 3 (Spegni), 32768 (Metti in pausa) e 32769 (Sospendi).
Per eseguire lo shutdown forzato della VM recuperiamo, con una query WMI, l'oggetto della classe MSVM_ShutdownComponent per la VM specifica e quindi ne chiamiamo il metodo InitiateShutdown passando due parametri:
- $true che in Powershell rappresenta il valore booleano vero/1
- Un commento che sarà passato allo Shutdown Event Tracker della VM
Lo script, così come scritto, funziona solo con VM Windows Server 2003/2008 con gli Integration Components installati.
Per ora mi fermo qui.
I miei esperimenti continuano (nel tempo che mi concedono le attività legate al lancio di Windows Server 2008, Visual Studio 2008 e SQL Server 2008). Non sarebbe male se contribuiste ad arricchire questo post con i risultati dei vostri esperimenti.
"Che WMI e Powershell siano con voi"
Giorgio
Technorati Tags: Microsoft,Windows Server 2008,Hyper-V,Poweshell,Windows Management Instrumentation,WMI,Scripting,PierGiorgio Malusardi