开发者

Eject USB device via C#

开发者 https://www.devze.com 2023-04-12 07:08 出处:网络
I was looking for a short way to eject USB-devices via C#-code, so I coded a little class myself, yet it simply doesn\'t work. Since there\'s no popup that says \"Lock success!\" I assume that the pro

I was looking for a short way to eject USB-devices via C#-code, so I coded a little class myself, yet it simply doesn't work. Since there's no popup that says "Lock success!" I assume that the problem relies within the "LockVolume"-function, but I don't know where.

Does anybody see the mistake I made?

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

    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool DeviceIoControl(
        IntPtr hDevice, 
        uint dwIoControlCode,
        IntPtr lpInBuffer, 
        uint nInBufferSize,
        IntPtr lpOutBuffer, 
        uint nOutBufferSize,
        out uint lpBytesReturned, 
        IntPtr lpOverlapped
    );

    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool DeviceIoControl(
        IntPtr hDevice, 
        uint dwIoControlCode,
        byte[] lpInBuffer, 
        uint nInBufferSize,
        IntPtr lpOutBuffer, 
        uint nOutBufferSize,
        out uint lpBytesReturned, 
        IntPtr lpOverlapped
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);

    private IntPtr handle = IntPtr.Zero;

    const int GENERIC_READ = 0x80000000;
    const int GENERIC_WRITE = 0x40000000;
    const int FILE_SHARE_READ = 0x1;
    const int FILE_SHARE_WRITE = 0x2;
    const int FSCTL_LOCK_VOLUME = 0x00090018;
    const int FSCTL_DISMOUNT_VOLUME = 0x00090020;
    const int IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
    const int IOCTL_STORAGE_MEDIA_REMOVAL = 0x002D4804;

    /// <summary>
    /// Constructor for the USBEject class
    /// </summary>
    /// <param name="driveLetter">This should be the drive letter. Format: F:/, C:/..</param>

    public USBEject(string driveLetter)
    {
        string filename = @"\\.\" + driveLetter[0] + ":";
        handle = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, 0x3, 0, IntPtr.Zero);
    }

    public bool Eject()
    {
        if (LockVolume(handle) && DismountVolume(handle))
        {
            PreventRemovalOfVolume(handle, false);
            return AutoEjectVolume(handle);
        }

        return false;
    }

    private bool LockVolume(IntPtr handle)
    {
        uint byteReturned;

        for (int i = 0; i < 10; i++)
        {
            if (DeviceIoControl(handle, FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero))
            {
                System.Windows.Forms.MessageBox.Show("Lock success!");
                return true;
            }
            Thread.Sleep(500);
        }
        return false;
开发者_JAVA技巧    }

    private bool PreventRemovalOfVolume(IntPtr handle, bool prevent)
    {
        byte[] buf = new byte[1];
        uint retVal;

        buf[0] = (prevent) ? (byte)1 : (byte)0;
        return DeviceIoControl(handle, IOCTL_STORAGE_MEDIA_REMOVAL, buf, 1, IntPtr.Zero, 0, out retVal, IntPtr.Zero);
    }

    private bool DismountVolume(IntPtr handle)
    {
        uint byteReturned;
        return DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero);
    }

    private bool AutoEjectVolume(IntPtr handle)
    {
        uint byteReturned;
        return DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero);
    }

    private bool CloseVolume(IntPtr handle)
    {
        return CloseHandle(handle);
    }
}


Changed just a little bit your code and it goes as follows:

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

    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool DeviceIoControl(
        IntPtr hDevice,
        uint dwIoControlCode,
        IntPtr lpInBuffer,
        uint nInBufferSize,
        IntPtr lpOutBuffer,
        uint nOutBufferSize,
        out uint lpBytesReturned,
        IntPtr lpOverlapped
    );

    [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
    private static extern bool DeviceIoControl(
        IntPtr hDevice,
        uint dwIoControlCode,
        byte[] lpInBuffer,
        uint nInBufferSize,
        IntPtr lpOutBuffer,
        uint nOutBufferSize,
        out uint lpBytesReturned,
        IntPtr lpOverlapped
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr hObject);

    private IntPtr handle = IntPtr.Zero;

    const uint GENERIC_READ = 0x80000000;
    const uint GENERIC_WRITE = 0x40000000;
    const int FILE_SHARE_READ = 0x1;
    const int FILE_SHARE_WRITE = 0x2;
    const int FSCTL_LOCK_VOLUME = 0x00090018;
    const int FSCTL_DISMOUNT_VOLUME = 0x00090020;
    const int IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
    const int IOCTL_STORAGE_MEDIA_REMOVAL = 0x002D4804;

    /// <summary>
    /// Constructor for the USBEject class
    /// </summary>
    /// <param name="driveLetter">This should be the drive letter. Format: F:/, C:/..</param>

    public IntPtr USBEject(string driveLetter)
    {
        string filename = @"\\.\" + driveLetter[0] + ":";
        return CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, 0x3, 0, IntPtr.Zero);
    }

    public bool Eject(IntPtr handle)
    {
        bool result = false;

        if (LockVolume(handle) && DismountVolume(handle))
        {
            PreventRemovalOfVolume(handle, false);
            result = AutoEjectVolume(handle);
        }
        CloseHandle(handle);
        return result;
    }

    private bool LockVolume(IntPtr handle)
    {
        uint byteReturned;

        for (int i = 0; i < 10; i++)
        {
            if (DeviceIoControl(handle, FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero))
            {
                System.Windows.Forms.MessageBox.Show("Lock success!");
                return true;
            }
            Thread.Sleep(500);
        }
        return false;
    }

    private bool PreventRemovalOfVolume(IntPtr handle, bool prevent)
    {
        byte[] buf = new byte[1];
        uint retVal;

        buf[0] = (prevent) ? (byte)1 : (byte)0;
        return DeviceIoControl(handle, IOCTL_STORAGE_MEDIA_REMOVAL, buf, 1, IntPtr.Zero, 0, out retVal, IntPtr.Zero);
    }

    private bool DismountVolume(IntPtr handle)
    {
        uint byteReturned;
        return DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero);
    }

    private bool AutoEjectVolume(IntPtr handle)
    {
        uint byteReturned;
        return DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero);
    }

    private bool CloseVolume(IntPtr handle)
    {
        return CloseHandle(handle);
    }

So you can use it in two ways:

        handle = USBEject("D:");
        Eject(handle);

or directly:

        Eject(USBEject("D:"));

It works for me on my Windows 10 machine (preview 14291)


Found the answer for my issue by using some of Roger Deep's code for the CreateFile call.

My code to remove a USB drive inside WPF Window:

private void Button_Click_1(object sender, RoutedEventArgs e)
{
    EjectDrive('K');
}
void EjectDrive(char driveLetter)
{
    string path = @"\\.\" + driveLetter + @":";
    IntPtr handle = CreateFile(path, GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, 0x3, 0, IntPtr.Zero);

    if ((long)handle == -1)
    {
        MessageBox.Show("Unable to open drive " + driveLetter);
        return;
    }

    int dummy = 0;

    DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0,
        IntPtr.Zero, 0, ref dummy, IntPtr.Zero);

    CloseHandle(handle);

    MessageBox.Show("OK to remove drive.");
}
[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr CreateFile
    (string filename, uint desiredAccess,
        uint shareMode, IntPtr securityAttributes,
        int creationDisposition, int flagsAndAttributes,
        IntPtr templateFile);
[DllImport("kernel32")]
private static extern int DeviceIoControl
    (IntPtr deviceHandle, uint ioControlCode,
        IntPtr inBuffer, int inBufferSize,
        IntPtr outBuffer, int outBufferSize,
        ref int bytesReturned, IntPtr overlapped);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);


Here's some code that I converted from a powershell script. You need to run with admin privileges and It works to "unmount" the USB drive. However, when you try to unplug the USB drive and plug it in again, it doesn't show up as a drive letter. (To get around that you need to type "WindowsKey-X" and select Disk-Manager to reassign the drive less to the USB device. (If anybody knows how to fix that problem please post to commits.) Here's the Code:

// Right click Project and Add Reference to System.Management.dll
using System.Management;

string mq = "SELECT * FROM Win32_Volume Where Name = 'E:\\'"; 
ManagementObjectSearcher ms = new ManagementObjectSearcher(mq);
foreach (ManagementObject mo in ms.Get())
{
    mo["DriveLetter"] = null;
    mo.Put();       
    ManagementBaseObject inParams = mo.GetMethodParameters("Dismount");
    inParams["Force"] = false;  
    inParams["Permanent"] = false;
    mo.InvokeMethod("Dismount", inParams, null);
}

Note that the powershell script also has the same problem of reattaching the USB device after ejecting. Here's the powershell script for your reference:

$vol = get-wmiobject -Class Win32_Volume | 
    where{$_.Name -eq 'E:\'}         
$vol.DriveLetter = $null  
$vol.Put()  
$vol.Dismount($false, $false)

Here's a class that I just wrote to Manage Mounting and Unmounting of Removable USB Drives using WMI:

using System;
using System.IO;
using System.Text;
using System.Windows;
using System.Management; //<-- right-click on project and add reference
using System.Collections.Generic;
using System.Text.RegularExpressions;

// This Class implements Mount/Unmount for USB Removable Drives
//  in a way similar to "Disk Manager" in the Control Panel.
//
//  Currently, It doesn't implement "Eject" like when you right
//    right-click on the USB icon on lower right of screen.
//    The "Unmount" is similar to "Eject" except it dosn't
//    cleanup the registry so that the USB drive can be automatically
//    recognized again without manually mounting it from "Disk Manager"
//    If somebody knows how to fix this class to gain this function...
//       please post it to their thread.  Thanks.
namespace WPM {

    public struct UsbDriveItem_t {
        public int    Index;
        public string DeviceId;
        public char   DriveLetter;
        public string Label;

        public override string ToString() {
            if (Index < 0)
                return "<none>";
            else 
                return String.Format("{0}: {1}", DriveLetter, Label);
        }               
    };

    delegate void UsbEvent();

    class UsbDriveRemovable {

        public static int Unmount(char DriveLetter) {
            bool success = ValidateAdmin("UsbDriveRemovable.Unmount()");
            if (!success) return -1;

            string Name = "'" + DriveLetter + ":\\\\'";

            string mq   = "SELECT * FROM Win32_Volume Where Name = " + Name;
            ManagementObjectSearcher ms = new ManagementObjectSearcher(mq);
            ManagementObjectCollection mc = ms.Get();
            foreach (ManagementObject mo in mc) {
                var DriveLetterI  = mo["DriveLetter"].ToString();
                mo["DriveLetter"] = null;
                mo.Put();
                ManagementBaseObject inParams = mo.GetMethodParameters("Dismount");
                inParams["Force"] = false;
                inParams["Permanent"] = false;
                ManagementBaseObject outParams = mo.InvokeMethod("Dismount", inParams, null);
                string rc = outParams["ReturnValue"].ToString();
                mo.Dispose();
            }
            mc.Dispose();
            ms.Dispose();
            return 0;
        }

        public static int Mount(string DeviceId, char Letter = '?') {
            bool success = ValidateAdmin("UsbDriveRemovable.Mount()");
            if (!success) return -1;

            if (Letter == '?' || Letter == '#') {
                GetFirstUnsedLetter(out Letter);
            }

            string FixDeviceId = Regex.Replace(DeviceId, @"\\", @"\\");

            string mq = "SELECT * FROM Win32_Volume WHERE DeviceId = '"
                + FixDeviceId
                + "'";

            ManagementObjectSearcher ms = new ManagementObjectSearcher(mq);
            ManagementObjectCollection mc = ms.Get();
            foreach (ManagementObject mo in mc) {
                ManagementBaseObject inParams = mo.GetMethodParameters("AddMountPoint");
                inParams["Directory"] = Letter + ":\\";
                ManagementBaseObject outParams = mo.InvokeMethod("AddMountPoint", inParams, null);
                string rc = outParams["ReturnValue"].ToString();
                mo.Dispose();
            }
            mc.Dispose();
            ms.Dispose();
            return 0;
        }

        /*List<UsbDriveItem_t>*/ 
        public static int ListDrives(ref List<UsbDriveItem_t> DriveList) {
            DriveList.Clear();
            string mq = "SELECT * FROM Win32_Volume Where DriveType = '2'";
            ManagementObjectSearcher ms   = new ManagementObjectSearcher(mq);
            ManagementObjectCollection mc = ms.Get();
            int count = 0;
            foreach (ManagementObject mo in mc) {
                UsbDriveItem_t item = new UsbDriveItem_t();
                item.Index       = count;
                item.Label       = (mo["Label"] == null)       ? "<none>" : mo["Label"].ToString();
                item.DriveLetter = (mo["DriveLetter"] == null) ? '#' : mo["DriveLetter"].ToString()[0];
                item.DeviceId    = (mo["DeviceId"] == null)    ? "<none>" : mo["DeviceId"].ToString();
                DriveList.Add(item);
                mo.Dispose();
            }
            count++;
            mc.Dispose();
            ms.Dispose();

            return 0;
        }

        public static void MountItem(UsbDriveItem_t DriveItem) {            
            char   DriveLetter = DriveItem.DriveLetter;
            string DriveLabel  = DriveItem.Label;
            string DeviceId    = DriveItem.DeviceId;

            // Mount Drive if its not already Mounted
            if (DriveLetter == '#') {
                UsbDriveRemovable.GetFirstUnsedLetter(out DriveLetter);
                UsbDriveRemovable.Mount(DeviceId, DriveLetter);
            }
            return; 
        }

        public static void UnmountItem(UsbDriveItem_t DriveItem) {      
            char   DriveLetter = DriveItem.DriveLetter;         
            UsbDriveRemovable.Unmount(DriveLetter);
            return;
        }

        public static int GetFirstUnsedLetter(out char Letter) {
            bool[] alphabet = new bool[26];

            for (int i=0; i < 26; i++) {
                alphabet[i] = false;
            }

            string mq = "SELECT * FROM Win32_Volume";
            ManagementObjectSearcher ms   = new ManagementObjectSearcher(mq);
            ManagementObjectCollection mc = ms.Get();
            foreach (ManagementObject mo in mc) {
                if (mo["DriveLetter"] != null) {
                    char cc      = mo["DriveLetter"].ToString()[0];
                    int  ci      = char.ToUpper(cc) - 65;
                    alphabet[ci] = true;
                }
                mo.Dispose();
            }
            mc.Dispose();
            ms.Dispose();

            int found = -1;
            for (int i=3; i < 26; i++) {
                if (alphabet[i] == false) {
                    found = i;
                    break;
                }
            }

            if (found >= 0) {
                Letter = (char)(found + 65);
                return 0;
            }
            else {
                Letter = '?';
                return -1;
            }
        }


        public static object
            RegisterInsertEvent(UsbEvent InsertEvent) {
            var insertQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
            var insertWatcher = new ManagementEventWatcher(insertQuery);            
            insertWatcher.EventArrived += delegate(object sender, EventArrivedEventArgs e) {
                // string driveName = e.NewEvent.Properties["DriveName"].Value.ToString();              
                Action action = delegate {
                    InsertEvent();
                };
                Application.Current.Dispatcher.BeginInvoke(action);
            };
            insertWatcher.Start();
            return (object)insertWatcher;
        }

        public static object RegisterRemoveEvent(UsbEvent RemoveEvent) {
            var removeQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
            var removeWatcher = new ManagementEventWatcher(removeQuery);
            removeWatcher.EventArrived += delegate(object sender, EventArrivedEventArgs e) {
                // string driveName = e.NewEvent.Properties["DriveName"].Value.ToString();              
                Action action = delegate {                  
                    RemoveEvent();
                };
                Application.Current.Dispatcher.BeginInvoke(action);
            };
            removeWatcher.Start();
            return (object)removeWatcher;
        }

        // Mount all UsbRemovable Drives that are not currently mounted
        public static int MountAll() {
            List<UsbDriveItem_t> DriveList = new List<UsbDriveItem_t>();
            ListDrives(ref DriveList);

            foreach (UsbDriveItem_t item in DriveList) {
                if (item.DriveLetter == '?') {
                    Mount(item.DeviceId);
                }
            }
            return 0;
        }

        // Unmount all UsbRemovable Drives
        public static int UnmountAll() {
            List<UsbDriveItem_t> DriveList = new List<UsbDriveItem_t>();
            ListDrives(ref DriveList);

            foreach (UsbDriveItem_t item in DriveList) {
                if (item.DriveLetter != '?') {
                    Unmount(item.DriveLetter);
                }
            }
            return 0;
        }

        public static bool IsAdministrator()
        {
            var id   = System.Security.Principal.WindowsIdentity.GetCurrent();
            var prin = new System.Security.Principal.WindowsPrincipal(id);
            return prin.IsInRole(
                System.Security.Principal.WindowsBuiltInRole.Administrator);
        }

        public static bool ValidateAdmin(string CalledFrom = null) {
            if (CalledFrom == null) {
                CalledFrom = "";
            }
            if (!IsAdministrator()) {
                string msg = "Please rerun this application with admin privileges.\r\n\r\n"
                + "Access denied to call " + CalledFrom + "\r\n\r\n";
                MessageBox.Show(msg, "ERROR");
                return false;
            }
            return true;
        }

        public static void StartExplorer(char DriveLetter) 
        {
            var proc1 = new System.Diagnostics.Process();
            proc1.StartInfo.FileName               = @"C:\\Windows\\System32\\explorer.exe";
            proc1.StartInfo.Arguments              = DriveLetter.ToString();
            proc1.StartInfo.CreateNoWindow         = true;
            proc1.StartInfo.UseShellExecute        = false;
            proc1.StartInfo.RedirectStandardOutput = true;
            proc1.StartInfo.RedirectStandardError  = true;
            proc1.Start();
            proc1.WaitForExit();
            string proc1out = proc1.StandardOutput.ReadToEnd();
            string proc1err = proc1.StandardError.ReadToEnd();
            //if (proc1.ExitCode != 0) {
            //  string msg = proc1out + "\r\n\r\n" + proc1err;
            //  MessageBox.Show(msg, "Error: Mountvol /R");
            //}
            proc1.Close();                  
        }       

    } //class
} //namespace



/*  DOESN'T WORK WELL...

        // Kludge to get USB Drive to be recognized again
        void UsbCleanup() {
            var proc1 = new System.Diagnostics.Process();
            proc1.StartInfo.FileName               = @"C:\\Windows\\System32\\mountvol.exe";
            proc1.StartInfo.Arguments              = @"/R";
            proc1.StartInfo.CreateNoWindow         = true;
            proc1.StartInfo.UseShellExecute        = false;
            proc1.StartInfo.RedirectStandardOutput = true;
            proc1.StartInfo.RedirectStandardError  = true;
            proc1.Start();
            proc1.WaitForExit();
            string proc1out = proc1.StandardOutput.ReadToEnd();
            string proc1err = proc1.StandardError.ReadToEnd();
            if (proc1.ExitCode != 0) {
                string msg = proc1out + "\r\n\r\n" + proc1err;
                MessageBox.Show(msg, "Error: Mountvol /R");
            }
            proc1.Close();

            var proc2 = new System.Diagnostics.Process();
            proc2.StartInfo.FileName               = @"C:\\Windows\\System32\\mountvol.exe";
            proc2.StartInfo.Arguments              = @"/E";
            proc2.StartInfo.CreateNoWindow         = true;
            proc2.StartInfo.UseShellExecute        = false;
            proc2.StartInfo.RedirectStandardOutput = true;
            proc2.StartInfo.RedirectStandardError  = true;
            proc2.Start();
            proc2.WaitForExit();
            string proc2out = proc2.StandardOutput.ReadToEnd();
            string proc2err = proc2.StandardError.ReadToEnd();
            if (proc2.ExitCode != 0) {
                string msg = proc1out + "\r\n\r\n" + proc1err;
                MessageBox.Show(msg, "Error: Mountvol /E");
            }
            proc2.Close();
            return;
        }
*/


You could convert the following PowerShell into C#:

$Eject = New-Object -comObject Shell.Application
$Eject.NameSpace(17).ParseName($usbDrvLetter+“:”).InvokeVerb(“Eject”)
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号