2

How do I programatically change Volume Serial of a Fat32 partition from C#. I found this example, but it is written with C++ which I don't read well. Could someone please answer a C# code snippet?

Update: I can see the C++ function from above example which I think it's possible to direct port to C#

void CVolumeSerialDlg::ChangeSerialNumber(DWORD Drive, const DWORD newSerial)
{
  const max_pbsi = 3;

  struct partial_boot_sector_info
  {
    LPSTR Fs; // file system name

    DWORD FsOffs; // offset of file system name in the boot sector

    DWORD SerialOffs; // offset of the serialnumber in the boot sector

  };

  partial_boot_sector_info pbsi[max_pbsi] =
  {
   {"FAT32", 0x52, 0x43},
   {"FAT",   0x36, 0x27},
   {"NTFS",  0x03, 0x48}
  };

  TCHAR szDrive[12];

  char Sector[512];

  DWORD i;

  sprintf(szDrive, "%c:\\", Drive & 0xFF);

  if (!disk.Open(szDrive))
  {
    ShowErrorString("Could not open disk!");
    return;
  }

  // read sector
  if (!disk.ReadSector(0, Sector))
  {
    ShowErrorString("Could not read sector!");
    return;
  }

  // try to search for a valid boot sector
  for (i=0;i<max_pbsi;i++)
  {
    if (strncmp(pbsi[i].Fs, Sector+pbsi[i].FsOffs, strlen(pbsi[i].Fs)) == 0)
    {
      // we found a valid signature
      break;
    }
  }

  if (i >= max_pbsi)
  {
    MessageBox(_T("Cannot change serial number of this file system!"),
       _T("Error"), MB_ICONERROR);
    return;
  }

  // patch serial number
  *(PDWORD)(Sector+pbsi[i].SerialOffs) = newSerial;

  // write boot sector
  if (!disk.WriteSector(0, Sector))
  {
    ShowErrorString("Could not write sector!");
    return;
  }

  ShowErrorString("Volume serial number changed successfully!\r"
        "You might want to restart your system for changes to take effect!");
}
VOX
  • 2,883
  • 2
  • 33
  • 43
  • please show the exant snippet you need in C# - nobody is going to translate the whole source... – Yahia Aug 27 '11 at 17:00

2 Answers2

5

No guarantees, be careful.

void ChangeSerialNumber(char volume, uint newSerial)
{
    var fsInfo = new[]
    {
        new { Name = "FAT32", NameOffs = 0x52, SerialOffs = 0x43 },
        new { Name = "FAT", NameOffs = 0x36, SerialOffs = 0x27 },
        new { Name = "NTFS", NameOffs = 0x03, SerialOffs = 0x48 }
    };

    using (var disk = new Disk(volume))
    {
        var sector = new byte[512];
        disk.ReadSector(0, sector);

        var fs = fsInfo.FirstOrDefault(
                f => Strncmp(f.Name, sector, f.NameOffs)
            );
        if (fs == null) throw new NotSupportedException("This file system is not supported");

        var s = newSerial;
        for (int i = 0; i < 4; ++i, s >>= 8) sector[fs.SerialOffs + i] = (byte)(s & 0xFF);

        disk.WriteSector(0, sector);
    }
}

bool Strncmp(string str, byte[] data, int offset)
{
    for(int i = 0; i < str.Length; ++i)
    {
        if (data[i + offset] != (byte)str[i]) return false;
    }
    return true;
}

class Disk : IDisposable
{
    private SafeFileHandle handle;

    public Disk(char volume)
    {
        var ptr = CreateFile(
            String.Format("\\\\.\\{0}:", volume),
            FileAccess.ReadWrite,
            FileShare.ReadWrite,
            IntPtr.Zero,
            FileMode.Open,
            0,
            IntPtr.Zero
            );

        handle = new SafeFileHandle(ptr, true);

        if (handle.IsInvalid) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    }

    public void ReadSector(uint sector, byte[] buffer)
    {
        if (buffer == null) throw new ArgumentNullException("buffer");
        if (SetFilePointer(handle, sector, IntPtr.Zero, EMoveMethod.Begin) == INVALID_SET_FILE_POINTER) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

        uint read;
        if (!ReadFile(handle, buffer, buffer.Length, out read, IntPtr.Zero)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        if (read != buffer.Length) throw new IOException();
    }

    public void WriteSector(uint sector, byte[] buffer)
    {
        if (buffer == null) throw new ArgumentNullException("buffer");
        if (SetFilePointer(handle, sector, IntPtr.Zero, EMoveMethod.Begin) == INVALID_SET_FILE_POINTER) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

        uint written;
        if (!WriteFile(handle, buffer, buffer.Length, out written, IntPtr.Zero)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        if (written != buffer.Length) throw new IOException();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (handle != null) handle.Dispose();
        }
    }

    enum EMoveMethod : uint
    {
        Begin = 0,
        Current = 1,
        End = 2
    }

    const uint INVALID_SET_FILE_POINTER = 0xFFFFFFFF;

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr CreateFile(
        string fileName,
        [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
        [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
        IntPtr securityAttributes,
        [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
        int flags,
        IntPtr template);

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern uint SetFilePointer(
         [In] SafeFileHandle hFile, 
         [In] uint lDistanceToMove,
         [In] IntPtr lpDistanceToMoveHigh,
         [In] EMoveMethod dwMoveMethod);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool ReadFile(SafeFileHandle hFile, [Out] byte[] lpBuffer,
        int nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);

    [DllImport("kernel32.dll")]
    static extern bool WriteFile(SafeFileHandle hFile, [In] byte[] lpBuffer,
        int nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten,
        [In] IntPtr lpOverlapped);
}

Use e.g. ChangeSerialNumber('D', 0x12345678);

Mormegil
  • 7,955
  • 4
  • 42
  • 77
  • looks like your answer is more safe than @Hansjoerg's answer. Correct me if I'm wrong. – VOX Aug 28 '11 at 02:21
  • @VOX - Not really, it is principally the same technique, only I used more direct conversion of the article you linked (so, the class supports not only FAT32, but also FAT[16] and NTFS) to .NET, Hansjoerg’s answer uses more .NET wrappers already available. – Mormegil Aug 28 '11 at 16:12
  • @Mormegil The new serial 12345678 after applied will result in the following vol id: 00BC 614E, how would you suggest if any of us wanted to specify the vol id in the following way: 03EF 12OE. Convert the string like vol id to the appropriate uint, or alter your method in a way it will receive string serials. – Fábio Antunes Mar 06 '13 at 18:19
  • @FábioAntunes The volume serial number is usually displayed in hexadecimal. As I wrote, if you want to have "1234 5678" displayed, you need to use 0x12345678. If you want to have volume ID "03EF 120E", you just need to use 0x03EF120E, where is the problem? If you need to convert from a _string_, use something like `Int32.Parse("03EF120E", NumberStyles.HexNumber)`. – Mormegil Mar 06 '13 at 18:52
  • @Mormegil I didn't noticed you specified the volume serial is displayed in hexadecimal, that's why I found odd a number like 12345678 would result in 00BC614E, didn't occurred to me and seems I've missed the part where you said the serial is presented in hexadecimal, that's why I've asked. There is no problem of course I just didn't noticed it. Thanks for reminding. By the way, a bulk test of your method didn't returned errors when changing FAT32 volumes, haven't made bulk tests on FAT and NTFS yet, but seems they will do fine. Thanks. +1 – Fábio Antunes Mar 07 '13 at 14:02
  • Just wanted to say thanks for this, it also converted to VB .net quite easily which is exactly what I was looking for. – The Blue Dog Oct 07 '14 at 18:12
4

Here is a small example for reading and writing the serial number of a FAT32 volume. To keep the sample small all error handling has been omitted.

Please note that direct access to the sectors of a volume may lead to data loss or corruption. So be careful in using the example below (it is not an example suitable for production usage). No guarantee!

In the example below I use the Win32 API GetDiskFreeSpace (using .Net interop) to get the bytes per sector for the FAT32 volume. To open the fat volume, I use the Win32 API CreateFile because the FileStream class does not support opening disk partitions directly.

static uint GenericRead = 0x80000000;
static uint GenericWrite = 0x40000000;                       
static uint OpenExisting = 3;

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool GetDiskFreeSpace(string lpRootPathName, out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters, out uint lpTotalNumberOfClusters);

static void ReadAndSetSerialNumber()
{
  const string driveLetter = "e:"; // Drive with FAT32 file system.

  uint sectorsPerCluster;
  uint bytesPerSector;
  uint numberOfFreeClusters;
  uint totalNumberOfClusters;

  GetDiskFreeSpace(String.Format(@"{0}\", driveLetter), out sectorsPerCluster, out bytesPerSector,
    out numberOfFreeClusters, out totalNumberOfClusters);

  Console.Out.WriteLine("Info for drive {0}", driveLetter);
  Console.Out.WriteLine("Bytes per sector: {0}", bytesPerSector);

  const int fatSerialOffset = 0x43;
  const int fatIdOffset = 0x52;
  const string fatFileSystemId = "FAT32";

  using (SafeFileHandle sfh = CreateFile(String.Format("\\\\.\\{0}", driveLetter), GenericRead | GenericWrite,
    (uint)FileShare.ReadWrite, IntPtr.Zero, OpenExisting, 0, IntPtr.Zero))
  {
    using (FileStream fs = new FileStream(sfh, FileAccess.ReadWrite))
    {
      byte[] firstSector = new byte[bytesPerSector];

      fs.Read(firstSector, 0, (int)bytesPerSector);

      if (Encoding.ASCII.GetString(firstSector, fatIdOffset, fatFileSystemId.Length) == fatFileSystemId)
      {
        Console.Out.WriteLine("FAT32 file system found...");

        uint serial = BitConverter.ToUInt32(firstSector, fatSerialOffset);

        Console.Out.WriteLine("Read serial number: {0:X4}-{1:X4}", serial >> 16, serial & 0xFFFF);

        // Write new serial number.
        byte[] newserial = BitConverter.GetBytes((uint)10000123);

        Array.Copy(newserial, 0, firstSector, fatSerialOffset, newserial.Length);
        fs.Seek(0, SeekOrigin.Begin);
        fs.Write(firstSector, 0, (int)bytesPerSector); 
      }                   
    }
  }
}

Furthermore you could use the .Net Framework's DriveInfo class to enumerate the available drives on your computer.

Hope, this helps.

Hans
  • 12,902
  • 2
  • 57
  • 60