Obtener número de serie de un disco físico (no volumen lógico)

Iniciado por bybaal, 3 Febrero 2021, 04:46 AM

0 Miembros y 2 Visitantes están viendo este tema.

bybaal

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

BlackZeroX

#1
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).
Código (cpp) [Seleccionar]

#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.
The Dark Shadow is my passion.

bybaal

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

Código (vbnet) [Seleccionar]

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

BlackZeroX

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.
The Dark Shadow is my passion.

Serapis

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.

Código (vbnet) [Seleccionar]

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...


BlackZeroX

#5
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:

Código (vbs) [Seleccionar]

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.
The Dark Shadow is my passion.

Mr. NoBody

#6
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



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...

Código (vbnet) [Seleccionar]
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!