Hyper-V: Ottenere un thumbnail di una VM in VB.NET
Hyper-V può essere gestito tramite WMI in quanto espone un completo modello ad oggetti tramite cui è possibile ricavare informazioni ed eseguire operazioni.
Tra le varie operazioni vi è anche la possibilità di ottenere il thumbnail di una macchina virtuale in esecuzione, funzionalità sfruttata dalla console di gestione di Hyper-V (virtmgmt.msc).
Questa possibilità oltre che essere sfruttata per ottenere un thumbnail può essere utilizzata anche per ottenere degli screen shoot approccio che può tornare utile nel caso nella VM venga eseguito un’applicativo di puro monitoraggio.
Si pensi ad esempio ad applicativi per il controllo di macchine industriali in questi contesti spesso torna utile separare la rete “industriale” da quella aziendale, ma spesso occorre poter monitorare dei processi da computer che appartengono alla rete aziendale. Infatti in questo modo la VM apparterà alla rete “industriale” e comunicherà con i PLC di gestione dell’impianto industriale, il computer apparterà alla rete aziendale a cui apparterà anche l’Hyper-V server che fornirà lo screenshot della VM comunicando tramite il VMBus in modo sicuro senza necessità di mettere in comunicazione le due reti.
Un altra applicazione può essere quella di applicazione sempre di solo monitoraggio la cui esecuzione è dispendiosa in termini di risorse e/o traffico di rete per cui non prevista la possibilità di essere fruita via HTTP (scenario comune in applicazioni legacy). In questo caso infatti l’applicazione verrà fatta risiedere in una VM con risorse sufficienti in termini di processore, RAM e traffico di rete, i client invece richiederanno screenshot al server Hyper-V.
Per ottenere un thumbnail occorre utilizzare il metodo GetVirtualSystemThumbnailImage della classe Msvm_VirtualSystemManagementService. Nella documentazione MSDN relativa al metodo GetVirtualSystemThumbnailImage viene anche riportato un esempio in C# e uno in VBScript.
Il metodo consente di ricavare lo screenshot sottoforma di un’array di byte e il formato dell’immagine è RGB con 16 bit per pixel (5 bit per il rosso, 6 bit per il verde e 5 per il blu) ovvero il formato rappresentato dal valore Format16bppRgb565 dell’enumerativo PixelFormat.
A tal proposito il codice proposto per la conversione dell’immagine in file nell’esempio non risulta corretto:
static void SaveImageData(byte[] imageData)
{
FileStream fs = new FileStream(@”c:\test.bin”, FileMode.Create, FileAccess.Write);
fs.Write(imageData, 0, imageData.Length);
fs.Flush();
fs.Close();
}
Occorre prima eseguire una conversione di formato come mostrato nell’esempio di Adam Semel nel post Getting a thumbnail from Hyper-V or SCVMM in C#:
static void SaveImageData(byte[] imageData, string vmName)
{
path += @”\Thumbnails\”;
Bitmap VMThumbnail = new Bitmap(x, y, PixelFormat.Format16bppRgb565);
Rectangle rectangle = new Rectangle(0, 0, x, y);
BitmapData VMThumbnailBiltmapData = VMThumbnail.LockBits(rectangle, ImageLockMode.WriteOnly, PixelFormat.Format16bppRgb565);
System.Runtime.InteropServices.Marshal.Copy(imageData, 0, VMThumbnailBiltmapData.Scan0, x * y * 2);
VMThumbnail.UnlockBits(VMThumbnailBiltmapData);
VMThumbnail.Save(path + vmName + “.jpg”);
}
Si noti anche che un’immagine di 1024×768 richiederà un’array di 1.572.864 byte ovvero di 1,5 MB quindi occorrerà trovare il giusto compromesso tra dimensione dell’immagine e tempo di refresh per evitare di saturare la connessione di rete.
Questo ovviamente spiega anche la best practies che indica di non lasciare la console di gestione di Hyper-V (virtmgmt.msc) attiva se non utilizzata. Infatti durante il suo funzionamento la console esegue il refresh del thubnail delle VM attive richiedendo quindi un’immagine di 80×60 pari ad una array di 9600 byte quindi se vi sono 4 VM attive con un tempo di refresh di 0.5 sec vengono traferiti circa 4,4 MB al minuto solo per gestire i thumbnail.
Un esempio in PowerShell per ricavare il thumbnail è disponibile nel seguente post Hyper-V WMI: Creating a Thumbnail Image di Taylor Brown.
Mentre al fondo del seguente articolo è disponibile del codice in VB.NET per realizzare una classe VirtualMachine (che potrete espandere a piacimento) e che consente di ottenere nella proprietà BitMap Thubnail lo screenshot di una VM. La classe richiede la versione 2.0 del framework .NET e l’aggiunta di una reference all’assembly System.Management.
Di seguito un esempio di utilizzo:
Public Class Form1
Private vm As New VirtualMachine(“hvHostname”, “vmName”, “user”, “pass”)
Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefresh.Click
Try
vm.ThumbnailWidth = Me.picScreenshoot.Width
vm.ThumbnailHeight = Me.picScreenshoot.Height
vm.GetInfo()
Me.picScreenshoot.Image = Me.vm.Thumbnail
Me.lblStatus.Text = “Refresh successfully at ” & _
Now.ToLongTimeString()
Catch ex As Exception
Me.lblStatus.Text = “Refresh failed! ” & ex.Message
End Try
End Sub
End Class
Public Class VirtualMachine
Public Sub New(ByVal hvServerHostname As String, ByVal name As String)
Me.New(hvServerHostname, name, String.Empty, String.Empty)
End SubPublic Sub New(ByVal hvServerHostname As String, ByVal name As String, ByVal username As String, ByVal password As String)
Me.hvServerHostnameValue = hvServerHostname
Me.nameValue = name
Me.usernameValue = username
Me.passwordValue = password
End SubPrivate hvServerHostnameValue As String = String.Empty
Public ReadOnly Property HVServerHostname() As String
Get
Return Me.hvServerHostnameValue
End Get
End PropertyPrivate nameValue As String = String.Empty
Public ReadOnly Property Name() As String
Get
Return Me.nameValue
End Get
End PropertyPrivate usernameValue As String = String.Empty
Public ReadOnly Property Username() As String
Get
Return Me.usernameValue
End Get
End PropertyPrivate passwordValue As String = String.Empty
Public ReadOnly Property Password() As String
Get
Return Me.passwordValue
End Get
End PropertyPrivate thumbnailValue As System.Drawing.Bitmap = Nothing
Public ReadOnly Property Thumbnail() As System.Drawing.Bitmap
Get
Return Me.thumbnailValue
End Get
End PropertyPrivate thumbnailWidthValue As Integer = 320
Public Property ThumbnailWidth() As Integer
Get
Return Me.thumbnailWidthValue
End Get
Set(ByVal value As Integer)
Me.thumbnailWidthValue = value
End Set
End PropertyPrivate thumbnailHeightValue As Integer = 240
Public Property ThumbnailHeight() As Integer
Get
Return Me.thumbnailHeightValue
End Get
Set(ByVal value As Integer)
Me.thumbnailHeightValue = value
End Set
End PropertyPrivate Enum ReturnCodes As UInt32
Completed = 0
Started = 4096
Failed = 32768
AccessDenied = 32769
NotSupported = 32770
Unknown = 32771
Timeout = 32772
InvalidParameter = 32773
SystemInUse = 32774
InvalidState = 32775
IncorrectDataType = 32776
SystemNotAvailable = 32777
OutofMemory = 32778
End EnumPrivate Enum JobState As UInt16
[New] = 2
Starting = 3
Running = 4
Suspended = 5
ShuttingDown = 6
Completed = 7
Terminated = 8
Killed = 9
Exception = 10
Service = 11
End EnumPublic Sub GetInfo()
‘Impostazione ConnectionOptions per autenticazione
Dim co As System.Management.ConnectionOptions = NothingIf Not System.String.IsNullOrEmpty(Me.usernameValue) Then
co = New System.Management.ConnectionOptions()
co.Username = Me.usernameValue
co.Password = Me.passwordValue
End If
‘Impostazione Scope
Dim scope = New System.Management.ManagementScope( _
“\\” & Me.hvServerHostnameValue & “\root\virtualization”, co)
scope.Connect()‘Ricerca VM
Dim vm As System.Management.ManagementObject = NothingDim queryVM As New System.Management.ObjectQuery( _
String.Format(“SELECT * FROM Msvm_ComputerSystem WHERE ElementName = ‘{0}'”, _
Me.nameValue))
Using searcherVM As New System.Management.ManagementObjectSearcher(scope, queryVM)
Using vmCollection = searcherVM.Get()
Using vmEnumerator = vmCollection.GetEnumerator()
vmEnumerator.MoveNext()
vm = DirectCast(vmEnumerator.Current, System.Management.ManagementObject)
End Using
End Using
End Using‘Impostazione TargetSystem
Dim target As System.Management.ManagementObject = NothingUsing vm
Using targetCollection = vm.GetRelated( _
“Msvm_VirtualSystemSettingData”, _
“Msvm_SettingsDefineState”, _
Nothing, _
Nothing, _
“SettingData”, _
“ManagedElement”, _
False, _
Nothing)
Using targetEnumerator = targetCollection.GetEnumerator()
targetEnumerator.MoveNext()
target = DirectCast(targetEnumerator.Current, System.Management.ManagementObject)
End Using
End Using
End Using‘Impostazione VirtualSystemManagementService
Dim vmsService As System.Management.ManagementObject = NothingDim queryVMSService As New System.Management.ObjectQuery( _
“SELECT * FROM Msvm_VirtualSystemManagementService”)Using searcherVMSService As New System.Management.ManagementObjectSearcher(scope, queryVMSService)
Using vmsServiceCollection = searcherVMSService.Get()
Using vmsServiceEnumerator = vmsServiceCollection.GetEnumerator()
vmsServiceEnumerator.MoveNext()
vmsService = DirectCast(vmsServiceEnumerator.Current, System.Management.ManagementObject)
End Using
End Using
End Using‘Richiesta dati Thumbnail
Dim imageData As Byte() = Nothing
Using vmsService
Using target
Using inParams = vmsService.GetMethodParameters(“GetVirtualSystemThumbnailImage”)
inParams(“WidthPixels”) = Me.thumbnailWidthValue
inParams(“HeightPixels”) = Me.thumbnailHeightValue
inParams(“TargetSystem”) = target.Path.PathUsing outParams = vmsService.InvokeMethod(“GetVirtualSystemThumbnailImage”, inParams, Nothing)
If System.Convert.ToUInt32(outParams(“ReturnValue”)) = ReturnCodes.Started Then
Me.WaitJobCompleted(outParams, scope)
ElseIf System.Convert.ToUInt32(outParams(“ReturnValue”)) <> ReturnCodes.Completed Then
Throw New System.Exception(String.Format(“Failed to retrieve VM thumbnail [{0}].”, DirectCast(outParams(“ReturnValue”), ReturnCodes)))
End IfimageData = DirectCast(outParams(“ImageData”), Byte())
End Using
End Using
End Using
End Using‘Conversione dati Thumbnail
If imageData IsNot Nothing Then
Me.thumbnailValue = New System.Drawing.Bitmap( _
Me.thumbnailWidthValue, Me.thumbnailHeightValue, _
System.Drawing.Imaging.PixelFormat.Format16bppRgb565)Dim rect As New System.Drawing.Rectangle( _
0, 0, Me.thumbnailValue.Width, Me.thumbnailValue.Height)Dim bitmapData = Me.thumbnailValue.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format16bppRgb565)
System.Runtime.InteropServices.Marshal.Copy( _
imageData, 0, bitmapData.Scan0, _
Me.thumbnailValue.Width * Me.thumbnailValue.Height * 2)Me.thumbnailValue.UnlockBits(bitmapData)
bitmapData = Nothing
imageData = Nothing
End If‘Rilascio risorse
queryVMSService = Nothing
queryVM = Nothing
scope = Nothing
If co IsNot Nothing Then co = Nothing
End SubPrivate Sub WaitJobCompleted(ByVal outParams As System.Management.ManagementBaseObject, ByVal scope As System.Management.ManagementScope)
‘Ricerca msvc_StorageJob path (full wmi path)
Dim jobPath As String = DirectCast(outParams(“Job”), String)
Using job As New System.Management.ManagementObject(scope, New System.Management.ManagementPath(jobPath), Nothing)
‘Ricerca job information
job.[Get]()
While DirectCast(job(“JobState”), UInt16) = JobState.Starting OrElse _
DirectCast(job(“JobState”), UInt16) = JobState.Running
‘Console.WriteLine(“In progress… {0}% completed.”, job(“PercentComplete”))
System.Threading.Thread.Sleep(500)
job.[Get]()
End WhileIf DirectCast(job(“JobState”), UInt16) <> JobState.Completed Then
Throw New System.Exception( _
String.Format(“{0} (Error code = {1).”, _
DirectCast(job(“ErrorDescription”), String), _
DirectCast(job(“ErrorCode”), UInt16)))
End If
End Using
End SubEnd Class