0

I'd like to evaluate wether a drive is in use (if I'm not mistaken this means that some read/write stuff is happening with the drive) using C#. I wouldn't mind for a solution using bash scripts or similiar either, as I could use them in a C# application. I already found a question regarding bash scripts here, but couldn't solve my problem with the answers given.

I considered to use the DriveInfo class already, however it didn't seem to have anything useful for me. I wondered wether I could use the IsReady property from DriveInfo, because I guessed that it wouldn't be ready while it is read/written, but this attempt seems rather botchy to me.

However I still tried it:

private static bool IsDriveInUse ()
{
    var drive = DriveInfo.GetDrives ().FirstOrDefault(info => info.Name.StartsWith(DRIVE_LETTER.ToString()));
    return drive != null && !drive.IsReady;
}

But it didn't work (it returned false while I played music from my drive).

An optimal solution for me would be a function that tells me wether the drive was in use in a specific span of time (let's stick to the name IsDriveInUse). That means that if the time was for example 60 seconds, IsDriveInUse should return true if 5 seconds before the function call content from the drive was read and false if there was no read/write action in the passed 60 seconds.


EDIT To specify what exactly I mean by in use, I'll try to explain what I'm trying to do. I am writing a tool, which automatically spins down my hard drive, when it's been idle or when I press a keyboard shortcut. I managed to spin it down programmatically (even though either the windows integrated tool nor other tools I found were able to do that, but that's another problem). However, it now spins down the hard drive every minute, regardless of wether it's currently in use or not. That means, if I play music from my hard drive, it's still spinned down, just to spin up directly after it, which doesn't decrease noise development.

I hope this clarified the matter.


EDIT I now tried using the FSCTL_LOCK_VOLUME control code (I couldn't find a value for IOCTL_DISK_PERFORMANCE), but it still returned false for IsDriveInUse() while I was playing music. Furthermore it caused windows to directly spin the drive up again as I spinned it down (probably because the releasing made Windows think that something was using the drive). This is what I tried:

public class DriveManager
{
    public const int FSCTL_LOCK_VOLUME = 0x00090018;
    public const int FSCTL_UNLOCK_VOLUME = 0x0009001c;

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

    [return: MarshalAs (UnmanagedType.Bool)]
    [DllImport ("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool DeviceIoControl (
        [In] SafeFileHandle hDevice,
        [In] int dwIoControlCode, [In] IntPtr lpInBuffer,
        [In] int nInBufferSize, [Out] IntPtr lpOutBuffer,
        [In] int nOutBufferSize, out int lpBytesReturned,
        [In] IntPtr lpOverlapped);

    public static SafeFileHandle CreateFileR (string device)
    {
        string str = device.EndsWith (@"\") ? device.Substring (0, device.Length - 1) : device;
        return new SafeFileHandle (
            CreateFile (@"\\.\" + str, WinntConst.GENERIC_READ, WinntConst.FILE_SHARE_READ, IntPtr.Zero,
                        WinntConst.OPEN_EXISTING, WinntConst.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
    }

    internal class WinntConst
    {
        // Fields
        internal static uint FILE_ATTRIBUTE_NORMAL = 0x80;

        internal static uint FILE_SHARE_READ = 1;
        internal static uint GENERIC_READ = 0x80000000;
        internal static uint OPEN_EXISTING = 3;
    }

    public static bool IsDriveInUse (string deviceName)
    {
        var handle = CreateFileR (deviceName);
        var buffer = Marshal.AllocHGlobal (sizeof (int));
        try
        {
            return
                DeviceIoControl (handle,
                                 FSCTL_LOCK_VOLUME,
                    IntPtr.Zero, 
                    0,
                    buffer,
                    sizeof(int),
                    out var bytesReturned,
                    IntPtr.Zero
                );
        }
        finally
        {
            var sessionId = Marshal.ReadInt32 (buffer);
            Marshal.FreeHGlobal (buffer);
            handle.Close ();
        }
    }

And the implementation:

private static bool IsDriveInUse () => DriveManager.IsDriveInUse ($@"{DRIVE_LETTER}:\");

Maybe it helps to see the part in which I'm spinning the disc down as well (I used Smartmontools for this):

internal static class Program
{
    private const string PROGRAM_PATH = @"External\smartctl.exe";
    private const string ARGUMENTS_SHUTDOWN = @"-s standby,now {0}:";
    private const char DRIVE_LETTER = 'd';


    public static void Main (string [] args)
    {
        InitializeHotKey ();
        Console.WriteLine ("Hotkey registered!");

        while (true)
        {
            Thread.Sleep (60000);
            if (!IsDriveInUse ())
                ShutDownDrive (true);
        }
    }

    private static bool IsDriveInUse () => DriveManager.IsDriveInUse ($@"{DRIVE_LETTER}:\");

    private static void InitializeHotKey ()
    {
        HotKeyManager.RegisterHotKey (Keys.D, KeyModifiers.Alt | KeyModifiers.Control);
        HotKeyManager.HotKeyPressed += HotKeyPressed;
    }

    private static void HotKeyPressed (object sender, HotKeyEventArgs hotKeyEventArgs) => ShutDownDrive (true);

    private static void ShutDownDrive (bool withDialog = false)
    {
        Process process;
        (process = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                WindowStyle = ProcessWindowStyle.Hidden,
                FileName = PROGRAM_PATH,
                Arguments = string.Format (ARGUMENTS_SHUTDOWN, DRIVE_LETTER)
            }
        }).Start ();

        process.WaitForExit ();
        process.Close ();

        if (withDialog)
            Console.WriteLine ("Drive shut down!");
    }
}
MetaColon
  • 2,895
  • 3
  • 16
  • 38
  • Specify what you mean by "in use" and "ready", exactly. Disks can do multiple things at once, through scheduling. On a workstation, as well as a server hard disk, there's almost always reading or writing going on. What problem exactly are you trying to solve? Is it a drive fixed in a machine, a removable drive or a network drive? – CodeCaster Sep 09 '17 at 21:43
  • Also suppose that you can get the correct info about "InUse". Its state may change before you take an action. – L.B Sep 09 '17 at 21:45
  • @L.B as I'm writing this tool mainly for me, I can promise you that this won't be the case, as I don't use my hard drive very frequently. – MetaColon Sep 09 '17 at 21:50
  • think you need [`FSCTL_LOCK_VOLUME`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa364575(v=vs.85).aspx) - *If there are any open files on the volume, this operation will fail. Conversely, success of this operation indicates there are no open files.* – RbMm Sep 09 '17 at 22:16
  • @RbMm this might be obvious to people being used to P/Invoke, but can you provide a usage example of this? – MetaColon Sep 09 '17 at 22:38
  • You do realize that Windows *already* spins down the drive when it isn't in use? The default is ten minutes if on battery, twenty minutes if on mains power, but it can be reconfigured, either programmatically or via the control panel. – Harry Johnston Sep 09 '17 at 22:59
  • @HarryJohnston As I said, I know this *should* happen, however it doesn't happen, but (as I said) this is another problem. – MetaColon Sep 09 '17 at 23:00
  • I think `FSCTL_LOCK_VOLUME` would be liable to cause side-effects, even if you unlock it again straight away an application might have tried to access the drive in the meantime. `IOCTL_DISK_PERFORMANCE` might be your best bet, except that if this is the disk Windows is installed on I think you'll find it is never idle because of all the background stuff Windows does. There's some discussion of using I/O control codes in C# [in this question](https://stackoverflow.com/q/1067216/886887). – Harry Johnston Sep 09 '17 at 23:13
  • @HarryJohnston it isn't the drive windows is installed on. – MetaColon Sep 09 '17 at 23:14
  • OK, then that won't be a problem. WMI also provides performance information, e.g., [see this article](https://blogs.technet.microsoft.com/georgewallace/2013/10/29/pulling-performance-counters-from-wmi-with-powershell/). Never tried it myself but might be easier than P/Invoking I/O control codes. – Harry Johnston Sep 09 '17 at 23:18
  • @HarryJohnston I edited my question to show what I tried so far. I couldn't however quite follow the article about WMI you provided. – MetaColon Sep 10 '17 at 11:08
  • Your InDriveInUse function looks back-to-front to me; if the drive is in use, `FSCTL_LOCK_VOLUME` will fail and the DeviceIoControl call will return zero, which gets converted to `false`. Conversely, if the drive is not in use InDriveInUse will return `true`. – Harry Johnston Sep 10 '17 at 21:09
  • @HarryJohnston you're right, I'll try it later. – MetaColon Sep 10 '17 at 21:11

1 Answers1

1

Perhaps you could use the Windows Performance Counter relevant to your drive ? "Disk Read/sec" seems quite relevant for what youhave in mind.

In .Net, the counters are available via System.Diagnostics.PerformanceCounter see there : https://msdn.microsoft.com/en-us/library/system.diagnostics.performancecounter(v=vs.110).aspx

Madgui
  • 405
  • 2
  • 10