Alguien me pudiera aclarar como se puede obtener el número de serie de un disco físico, también el modelo del disco.
OJO!!! Que sea usando la API de Windows y en VB
Cita de: bybaal en 3 Febrero 2021, 04:46 AM
Alguien me pudiera aclarar como se puede obtener el número de serie de un disco físico, también el modelo del disco.
OJO!!! Que sea usando la API de Windows y en VB
GetVolumeInformationA function (fileapi.h)
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationa
Encontré el uso en C/C++ solo tradúcelo y adáptalo a tus necesidades que tampoco creo en los foros se hagan encargos de codigo a modo.
https://gist.github.com/micjabbour/a2fbe50160862e6abe439c0b0769c3fb
Lo modifique para listara los números de serie de todos los discos físicos instalados (es un asco como lo adapte, lo se jajajaja).
#include <windows.h>
#include <memory>
#include <string>
#include <iostream>
#include <bitset>
#include <vector>
using namespace std;
#define CTL_CODE(DeviceType, Function, Method, Access) \
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
#define METHOD_BUFFERED 0
//returns the serial number of the first physical drive in a string or an empty string in case of failure
//based on http://codexpert.ro/blog/2013/10/26/get-physical-drive-serial-number-part-1/
string getFirstHddSerialNumber(wchar_t *pDisk) {
//get a handle to the first physical drive
HANDLE h = CreateFileW(pDisk, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if(h == INVALID_HANDLE_VALUE) return {};
//an unique_ptr is used to perform cleanup automatically when returning (i.e. to avoid code duplication)
unique_ptr<remove_pointer<HANDLE>::type, void(*)(HANDLE)> hDevice{h, [](HANDLE handle){CloseHandle(handle);}};
//initialize a STORAGE_PROPERTY_QUERY data structure (to be used as input to DeviceIoControl)
STORAGE_PROPERTY_QUERY storagePropertyQuery{};
storagePropertyQuery.PropertyId= StorageDeviceProperty;
storagePropertyQuery.QueryType= PropertyStandardQuery;
//initialize a STORAGE_DESCRIPTOR_HEADER data structure (to be used as output from DeviceIoControl)
STORAGE_DESCRIPTOR_HEADER storageDescriptorHeader{};
//the next call to DeviceIoControl retrieves necessary size (in order to allocate a suitable buffer)
//call DeviceIoControl and return an empty string on failure
DWORD dwBytesReturned= 0;
if(!DeviceIoControl(hDevice.get(), IOCTL_STORAGE_QUERY_PROPERTY, &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),
&storageDescriptorHeader, sizeof(STORAGE_DESCRIPTOR_HEADER), &dwBytesReturned, NULL))
return {};
//allocate a suitable buffer
const DWORD dwOutBufferSize= storageDescriptorHeader.Size;
unique_ptr<BYTE[]> pOutBuffer{new BYTE[dwOutBufferSize]{}};
//call DeviceIoControl with the allocated buffer
if(!DeviceIoControl(hDevice.get(), IOCTL_STORAGE_QUERY_PROPERTY, &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),
pOutBuffer.get(), dwOutBufferSize, &dwBytesReturned, NULL))
return {};
//read and return the serial number out of the output buffer
STORAGE_DEVICE_DESCRIPTOR* pDeviceDescriptor= reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(pOutBuffer.get());
const DWORD dwSerialNumberOffset= pDeviceDescriptor->SerialNumberOffset;
if(dwSerialNumberOffset==0) return {};
const char* serialNumber= reinterpret_cast<const char*>(pOutBuffer.get() + dwSerialNumberOffset);
return serialNumber;
}
void printPhysicalDriversSerial() {
bitset<32> drives(GetLogicalDrives());
vector<char> goodDrives;
for (char c = 'A'; c <= 'Z'; ++c) {
if (drives[c - 'A']) {
//if (GetDriveType((c + string(":\\")).c_str()) == DRIVE_FIXED) { descomentar para solo obtener los Discos físicos internos.
goodDrives.push_back(c);
//}
}
}
for (auto & drive : goodDrives) {
string s = string("\\\\.\\") + drive + ":";
HANDLE h = CreateFileA(
s.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS, NULL
);
if (h == INVALID_HANDLE_VALUE) {
cerr << "Drive " << drive << ":\\ cannot be opened" << endl;
continue;
}
DWORD bytesReturned;
VOLUME_DISK_EXTENTS vde;
if (!DeviceIoControl(
h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
NULL, 0, &vde, sizeof(vde), &bytesReturned, NULL
)) {
cerr << "Drive " << drive << ":\\ cannot be mapped into physical drive" << endl;
continue;
}
for (int i = 0; i < vde.NumberOfDiskExtents; ++i) {
wchar_t disk [MAX_PATH];
swprintf(disk, MAX_PATH, L"\\\\.\\PhysicalDrive%d", vde.Extents[i].DiskNumber);
string serialNumber = getFirstHddSerialNumber(disk);
fputws ( disk, stdout );
cout << endl;
if(serialNumber.empty()) {
cout << "failed to retrieve serial number" << endl;
} else {
cout << "serial number: " << serialNumber << endl;
}
}
}
}
int main() {
printPhysicalDriversSerial();
return 0;
}
Para corroborar los datos pintados, ejecutar en CMD:
wmic diskdrive get Name, Manufacturer, Model, InterfaceType, MediaType, SerialNumber
Saludos.
Buscando me he encontrado un código en vb6, pero me parece que no logró adaptarlo correctamente a vb.net, ya que me genera un error cuando se va a usar la api ZeroMemory
Alguien que tenga alguna idea de que hay que corregir, ya que a mi no se me ocurre nada más
Private Declare Auto Function CreateFile _
Lib "kernel32" Alias "CreateFileA" (
ByVal lpFileName As String,
ByVal dwDesiredAccess As Integer,
ByVal dwShareMode As Integer,
ByVal lpSecurityAttributes As Integer,
ByVal dwCreationDisposition As Integer,
ByVal dwFlagsAndAttributes As Integer,
ByVal hTemplateFile As IntPtr
) As Integer
Private Declare Auto Function CloseHandle _
Lib "kernel32" (
ByVal hObject As IntPtr
) As Integer
Private Declare Auto Function DeviceIoControl _
Lib "kernel32" (
ByVal hDevice As IntPtr,
ByVal dwIoControlCode As Integer,
<MarshalAs(UnmanagedType.AsAny)>
lpInBuffer As Object,
ByVal nInBufferSize As Integer,
<MarshalAs(UnmanagedType.AsAny)>
lpOutBuffer As Object,
ByVal nOutBufferSize As Integer,
lpBytesReturned As Integer,
ByVal lpOverlapped As Integer
) As Integer
Private Declare Auto Sub ZeroMemory _
Lib "kernel32" Alias "RtlZeroMemory" (
<MarshalAs(UnmanagedType.AsAny)>
dest As Object,
ByVal numBytes As Integer)
'Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Object, Source As Object, ByVal Length As Integer)
Private Declare Auto Function GetLastError Lib "kernel32" () As Integer
Private Const DFP_RECEIVE_DRIVE_DATA = &H7C088
Private Const FILE_SHARE_READ = &H1
Private Const FILE_SHARE_WRITE = &H2
Private Const GENERIC_READ = &H80000000
Private Const GENERIC_WRITE = &H40000000
Private Const OPEN_EXISTING = 3
Private Const CREATE_NEW = 1
Private Enum HDInfo
HD_MODEL_NUMBER = 0
HD_SERIAL_NUMBER = 1
HD_FIRMWARE_REVISION = 2
End Enum
Private Structure IDEREGS
Public bFeaturesReg As Byte
Public bSectorCountReg As Byte
Public bSectorNumberReg As Byte
Public bCylLowReg As Byte
Public bCylHighReg As Byte
Public bDriveHeadReg As Byte
Public bCommandReg As Byte
Public bReserved As Byte
End Structure
Private Structure SENDCMDINPARAMS
Public cBufferSize As Integer
Public irDriveRegs As IDEREGS
Public bDriveNumber As Byte
<VBFixedArray(1, 3)>
Public bReserved() As Byte
<VBFixedArray(1, 4)>
Public dwReserved() As Integer
End Structure
Private Structure DRIVERSTATUS
Public bDriveError As Byte
Public bIDEStatus As Byte
<VBFixedArray(1, 2)>
Public bReserved() As Byte
<VBFixedArray(1, 2)>
Public dwReserved() As Integer
End Structure
Private Structure SENDCMDOUTPARAMS
Public cBufferSize As Integer
Public DStatus As DRIVERSTATUS
<VBFixedArray(1, 512)>
Public bBuffer() As Byte
End Structure
Private mvarCurrentDrive As Byte
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
MsgBox(GetHDDInfo(0, "serial"))
End
End Sub
Public Function GetHDDInfo(ByVal Number As Byte, ByVal Cmd As String) As String
mvarCurrentDrive = Number
Select Case LCase(Cmd)
Case "model" : GetHDDInfo = CmnGetHDData(HDInfo.HD_MODEL_NUMBER)
Case "serial" : GetHDDInfo = CmnGetHDData(HDInfo.HD_SERIAL_NUMBER)
Case "firmware" : GetHDDInfo = CmnGetHDData(HDInfo.HD_FIRMWARE_REVISION)
Case Else : GetHDDInfo = ""
End Select
End Function
Private Function CmnGetHDData(hdi As HDInfo) As String
Dim Bin As SENDCMDINPARAMS
Dim Bout As SENDCMDOUTPARAMS
Dim hdh As Long
Dim br As Long
Dim Ix As Long
Dim hddfr As Long
Dim hddln As Long
Dim S As String
Select Case hdi
Case HDInfo.HD_MODEL_NUMBER
hddfr = 55
hddln = 40
Case HDInfo.HD_SERIAL_NUMBER
hddfr = 21
hddln = 20
Case HDInfo.HD_FIRMWARE_REVISION
hddfr = 47
hddln = 8
Case Else : Err.Raise(10001, "Illegal HD Data type")
End Select
hdh = CreateFile("\\.\PhysicalDrive" & mvarCurrentDrive, GENERIC_READ + GENERIC_WRITE, FILE_SHARE_READ + FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0)
If hdh = 0 Then Err.Raise(10003, , "Error on CreateFile")
ZeroMemory(Bin, Len(Bin))
ZeroMemory(Bout, Len(Bout))
With Bin
.bDriveNumber = mvarCurrentDrive
.cBufferSize = 512
With .irDriveRegs
If (mvarCurrentDrive And 1) Then
.bDriveHeadReg = &HB0
Else
.bDriveHeadReg = &HA0
End If
.bCommandReg = &HEC
.bSectorCountReg = 1
.bSectorNumberReg = 1
End With
End With
DeviceIoControl(hdh, DFP_RECEIVE_DRIVE_DATA, Bin, Len(Bin), Bout, Len(Bout), br, 0)
S = ""
For Ix = hddfr To hddfr + hddln - 1 Step 2
If Bout.bBuffer(Ix + 1) = 0 Then Exit For
S &= Chr(Bout.bBuffer(Ix + 1))
If Bout.bBuffer(Ix) = 0 Then Exit For
S &= Chr(Bout.bBuffer(Ix))
Next Ix
CloseHandle(hdh)
CmnGetHDData = Trim(S)
End Function
Puedes omitir el llamado de ZeroMemory* en VB .NET las variables ya se inicializan limpias* y no se ve que las uses entre la declaracion y la implementación de las mismas.
ZeroMemory
La función ZeroMemory llena un bloque de memoria con ceros.
Sintaxis
VOID ZeroMemory (
PVOID Destination, // puntero al bloque a llenar con ceros
DWORD Length, // tamaño, en bytes, del bloque a llenar
);
Parámetros
Destination: puntero a la dirección de comienzo del bloque de memoria a llenar con ceros.
Length: especifica el tamaño, en bytes, del bloque de memoria a llenar con ceros.
Valor de retorno
Esta función no tiene valor de retorno.
Saludos.
Cita de: bybaal en 3 Febrero 2021, 04:46 AM
Alguien me pudiera aclarar como se puede obtener el número de serie de un disco físico, también el modelo del disco.
OJO!!! Que sea usando la API de Windows y en VB
.NET es muy completo, no necesitas usar APIs...
Si partes desde vb6, por ejemplo tiene sel objeto Scripting, que contiene la clase FilesystemObject, que a su vez contiene las clases Drives y Drive, para obtener datos de la unidad, si bien trae el SerialNumber (que los objetos Drive de .NET, no lo incorporan), tampoco trae el modelo de la unidad.
Aquí una sopa de código para rescatar algunos datos desde diversas fuentes en .NET...
Nota sin embargo que para usar el objeto Scripting tienes que agregar la referencia COM al proyecto, antes de instanciar y poder usarla...
Al final, accedemos al registro, para rescatar los nombres de las unidades conectadas internamente, las conectadas externamente ('removibles') se acceden desde la clave 'UsbStor' (incluídas las unidades ópticas). El ejemplo te vale para profundizar y modificar a tu gusto... como el registro es una base de datos fuertemente jerarquizada es engorroso, para escribir código rápido pues debes recorrerlo iterativa o recursivamente si no te construyes alguna clase exprofeso para manejarlo con comodidad.
Imports Microsoft.Win32
Public Class Form1
Private Enum DriveTypeScripting
DRIVE_TYPE_DESCONOCIDO = 0
DRIVE_TYPE_DESCONECTABLE = 1
DRIVE_TYPE_DISCODURO = 2
DRIVE_TYPE_REMOTO = 3
DRIVE_TYPE_OPTICA = 4
DRIVE_TYPE_RAMDISK = 5
End Enum
Private Function DriveTypeToString(ByVal DtS As DriveTypeScripting) As String
Select Case DtS
Case 0 : Return "Desconocido"
Case 1 : Return "Desconectable"
Case 2 : Return "Disco Duro"
Case 3 : Return "Unida de Red"
Case 4 : Return "Unidad óptica"
Case 5 : Return "Unidad de Memoria"
'Case else
End Select
Return ""
End Function
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim dev As Devices.Computer
Dim info As String
Dim fso As New Scripting.FileSystemObject
Dim ds As Scripting.Drives
Dim key As RegistryKey, keyDrive As RegistryKey, keyIde As RegistryKey
Dim hk As RegistryHive
Dim rv As RegistryView
' Acceso desde el spacename IO
For Each d As IO.DriveInfo In IO.DriveInfo.GetDrives
With d
info = (.Name & vbNewLine & .DriveType.ToString)
If (.IsReady) Then info += (vbNewLine & .DriveFormat)
MessageBox.Show(info)
'info.
End With
Next
' Acceso desde el namespace IO y/o Win32.Devices
dev = New Devices.Computer
For Each d As IO.DriveInfo In dev.FileSystem.Drives
MessageBox.Show(d.Name)
Next
' Acceso a los datos de las unidades mediante el objeto FileSystemObject de la referencia Scripcing (que debe agregar desde COM).
Dim dtipo As Scripting.DriveTypeConst
Dim volLabel As String
ds = fso.Drives
For Each disk As Scripting.Drive In ds
With disk
dtipo = .DriveType
If (dtipo = DriveTypeScripting.DRIVE_TYPE_DISCODURO) Then
volLabel = .VolumeName & vbNewLine
Else
If .IsReady Then
volLabel = .VolumeName & vbNewLine
Else
volLabel = ""
End If
End If
info = (.DriveLetter & ":\" & vbNewLine & volLabel & DriveTypeToString(dtipo))
If .IsReady Then info += (vbNewLine & .SerialNumber)
MessageBox.Show(info)
End With
Next
' Acceso al registro para rescatar el modelo de la unidad...
Dim subkeys() As String
hk = RegistryHive.LocalMachine
rv = RegistryView.Registry32
key = RegistryKey.OpenBaseKey(hk, rv)
Try
key = key.OpenSubKey("System").OpenSubKey("CurrentControlSet").OpenSubKey("Enum").OpenSubKey("IDE") ' "UsbStor"
subkeys = key.GetSubKeyNames
For k As Integer = 1 To key.SubKeyCount
keyIde = key.OpenSubKey(subkeys(k - 1))
keyDrive = keyIde.OpenSubKey(keyIde.GetSubKeyNames(0))
MsgBox(keyDrive.GetValue("FriendlyName"))
keyDrive.Close() : keyIde.Close()
Next
Catch ex As Exception ' no tienes permiso,
MessageBox.Show(ex.Message)
Finally
key.Close()
End Try
End Sub
End Class
Y ya desde ahí compón lo precises...
Scripting.FileSystemObject... de hecho dicho objeto no parte de vb6 parte de vba, al final del día ya tiene varias formas de hacerlo
API Win32
Objetos COM (vba) en vb .NET
En cualquier caso según veo se debe ejecutar con privilegios de administrador, así que deberá agregar un MANIFEST para que escale permisos o que se ejecute con dicho permiso.
El código posteado por serapis (Solo parte del COM) por lo que se ve lista volúmenes lógicos y no físicos que para lo que se requiere es similar a este (VBS) crear un archivo en escritorio y con extensión "vbs" como ejecutar.vbs y pegar este código y darle doble click:
Dim fs, d, dc, s
Set fs = CreateObject("Scripting.FileSystemObject")
Set dc = fs.Drives
For Each d in dc
s = s & d.DriveLetter & " tipo " & d.DriveType & " serialNumber (Logico) " & d.SerialNumber & vbNewLine
Next
MsgBox s
Saludos.
Un tal
ElektroStudios, programador enfocado en el desarrollo de aplicaciones de escritorio con VB.NET, publicó cierto programa / API open-source con fines educativos para demostrar cómo obtener todo tipo de información S.M.A.R.T de un disco, incluyendo por supuesto el número de serie y modelo:
S.M.A.R.T. Demo:
https://github.com/ElektroStudios/S.M.A.R.T.-Tool-for-.NET
(https://i.imgur.com/UENfa0U.png)
Dicho esto, ten en cuenta que .NET Framework / VB.NET es en gran parte un gigantesco y optimizado
wrapper de la API de Windows, así que en la mayoría de escenarios habituales de un programador con necesidades normales y corrientes no es necesario
ni tampoco recomendable utilizar directamente la API de Windows, a menos que sea para escenarios y por motivos muy específicos, y siempre teniendo una base mínima de cococimiento sobre el uso de la librería de Windows y su implementación en .NET mediante Platform Invoke.
El código fuente del programa
S.M.A.R.T. Demo se apoya en las funcionalidades de la infrastructura WMI (que hace uso interno de la API de Windows) para obtener toda la información.
Esto es lo más completo que vas a encontrar de forma open-source para .NET, la API es reutilizable así que es practicamente copiar y pegar código (todo el contenido de la carpeta 'DevCase') y ya estaría listo para darle uso en tu aplicación siguiendo y adaptando el ejemplo del programa...
Imports DevCase.Core.IO
For Each drive As HardDriveInfo In HardDriveInfo.GetDrives()
Dim sb As New StringBuilder()
sb.AppendLine($"{NameOf(drive.Name)}: {drive.Name}")
sb.AppendLine($"{NameOf(drive.VolumeLabel)}: {drive.VolumeLabel}")
sb.AppendLine($"{NameOf(drive.SerialNumber)}: {drive.SerialNumber}")
Console.WriteLine(sb.ToString())
Next drive
CitarName: C:\
VolumeLabel: Windows 10
SerialNumber: S4X6NJ0MC20670B
Pero también te digo que es bien fácil buscar en Google un simple código de pocas lineas para obtener el número de serie de un disco mediante WMI, ya sea en VB.NET y en C# también, y así no te complicas la vida usando toda una extensa API para lograr el mismo objetivo.
Saludos!