Stephane@243: using Microsoft.Win32.SafeHandles; Stephane@243: using System; StephaneLenclud@240: using System.Collections.Generic; Stephane@243: using System.Diagnostics; Stephane@243: using System.IO; StephaneLenclud@240: using System.Linq; Stephane@243: using System.Runtime.Serialization; StephaneLenclud@240: using System.Text; StephaneLenclud@240: using System.Threading.Tasks; Stephane@243: using SharpLib.Win32; Stephane@243: using System.ComponentModel; Stephane@243: using System.Runtime.InteropServices; StephaneLenclud@240: StephaneLenclud@240: namespace SharpLib.Ear StephaneLenclud@240: { Stephane@243: [DataContract] Stephane@243: [AttributeObject(Id = "Action.OpticalDrive.Eject", Name = "Eject", Description = "Eject media from an optical drive.")] Stephane@243: public class ActionOpticalDriveEject : Action StephaneLenclud@240: { Stephane@243: [DataMember] Stephane@243: [AttributeObjectProperty Stephane@243: ( Stephane@243: Id = "Action.OpticalDrive.Eject.Drive", Stephane@243: Name = "Drive to eject", Stephane@243: Description = "Select the drive you want to eject." Stephane@243: ) Stephane@243: ] Stephane@243: public PropertyComboBox Drive { get; set; } = new PropertyComboBox(); Stephane@243: Stephane@243: Stephane@243: protected override void DoConstruct() Stephane@243: { Stephane@243: base.DoConstruct(); Stephane@243: PopulateOpticalDrives(); Stephane@243: CheckCurrentItem(); Stephane@243: } Stephane@243: Stephane@243: Stephane@243: public override string Brief() Stephane@243: { Stephane@243: return Name + " " + Drive.CurrentItem ; Stephane@243: } Stephane@243: Stephane@243: public override bool IsValid() Stephane@243: { Stephane@243: //This object is valid if our current item is contained in our drive list Stephane@243: return Drive.Items.Contains(Drive.CurrentItem); Stephane@243: } Stephane@243: StephaneLenclud@258: protected override async Task DoExecute() Stephane@243: { Stephane@243: DriveEject(Drive.CurrentItem); Stephane@243: } Stephane@243: Stephane@243: Stephane@243: private void CheckCurrentItem() Stephane@243: { Stephane@243: if (!Drive.Items.Contains(Drive.CurrentItem) && Drive.Items.Count>0) Stephane@243: { Stephane@243: //Current item unknown, reset it then Stephane@243: Drive.CurrentItem = Drive.Items[0]; Stephane@243: } Stephane@243: } Stephane@243: Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: private void PopulateOpticalDrives() Stephane@243: { Stephane@243: //Reset our list of drives Stephane@243: Drive.Items = new List(); Stephane@243: //Go through each drives on our system and collected the optical ones in our list Stephane@243: DriveInfo[] allDrives = DriveInfo.GetDrives(); Stephane@243: foreach (DriveInfo d in allDrives) Stephane@243: { Stephane@243: Debug.WriteLine("Drive " + d.Name); Stephane@243: Debug.WriteLine(" Drive type: {0}", d.DriveType); Stephane@243: Stephane@243: if (d.DriveType == DriveType.CDRom) Stephane@243: { Stephane@243: //This is an optical drive, add it now Stephane@243: Drive.Items.Add(d.Name.Substring(0, 2)); Stephane@243: } Stephane@243: } Stephane@243: } Stephane@243: Stephane@243: Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: static private void CheckLastError(string aPrefix) Stephane@243: { Stephane@243: string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; Stephane@243: Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage); Stephane@243: } Stephane@243: Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: static private IntPtr MarshalToPointer(object data) Stephane@243: { Stephane@243: IntPtr buf = Marshal.AllocHGlobal( Stephane@243: Marshal.SizeOf(data)); Stephane@243: Marshal.StructureToPtr(data, Stephane@243: buf, false); Stephane@243: return buf; Stephane@243: } Stephane@243: Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: static private SafeFileHandle OpenVolume(string aDriveName) Stephane@243: { Stephane@243: return Function.CreateFile("\\\\.\\" + aDriveName, Stephane@243: SharpLib.Win32.FileAccess.GENERIC_READ, Stephane@243: SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE, Stephane@243: IntPtr.Zero, Stephane@243: CreationDisposition.OPEN_EXISTING, Stephane@243: 0, Stephane@243: IntPtr.Zero); Stephane@243: } Stephane@243: Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: static private bool LockVolume(SafeFileHandle aVolume) Stephane@243: { Stephane@243: //Hope that's doing what I think it does Stephane@243: IntPtr dwBytesReturned = new IntPtr(); Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there. Stephane@243: OVERLAPPED overlapped = new OVERLAPPED(); Stephane@243: Stephane@243: int tries = 0; Stephane@243: const int KMaxTries = 100; Stephane@243: const int KSleepTime = 10; Stephane@243: bool success = false; Stephane@243: Stephane@243: while (!success && tries < KMaxTries) Stephane@243: { Stephane@243: success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); Stephane@243: System.Threading.Thread.Sleep(KSleepTime); Stephane@243: tries++; Stephane@243: } Stephane@243: Stephane@243: CheckLastError("Lock volume: "); Stephane@243: Stephane@243: return success; Stephane@243: } Stephane@243: Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: static private bool DismountVolume(SafeFileHandle aVolume) Stephane@243: { Stephane@243: //Hope that's doing what I think it does Stephane@243: IntPtr dwBytesReturned = new IntPtr(); Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there. Stephane@243: OVERLAPPED overlapped = new OVERLAPPED(); Stephane@243: Stephane@243: bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); Stephane@243: CheckLastError("Dismount volume: "); Stephane@243: return res; Stephane@243: } Stephane@243: Stephane@243: Stephane@243: Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: static private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval) Stephane@243: { Stephane@243: //Hope that's doing what I think it does Stephane@243: IntPtr dwBytesReturned = new IntPtr(); Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there. Stephane@243: OVERLAPPED overlapped = new OVERLAPPED(); Stephane@243: // Stephane@243: PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL(); Stephane@243: preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval); Stephane@243: IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval); Stephane@243: Stephane@243: bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped); Stephane@243: CheckLastError("Media removal: "); Stephane@243: Marshal.FreeHGlobal(preventMediaRemovalParam); Stephane@243: Stephane@243: return result; Stephane@243: } Stephane@243: Stephane@243: /// Stephane@243: /// Eject optical drive media opening the tray if any. Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: static private bool MediaEject(SafeFileHandle aVolume) Stephane@243: { Stephane@243: //Hope that's doing what I think it does Stephane@243: IntPtr dwBytesReturned = new IntPtr(); Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there. Stephane@243: OVERLAPPED overlapped = new OVERLAPPED(); Stephane@243: Stephane@243: bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); Stephane@243: CheckLastError("Media eject: "); Stephane@243: return res; Stephane@243: } Stephane@243: Stephane@243: /// Stephane@243: /// Close an optical drive tray. Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: static private bool MediaLoad(SafeFileHandle aVolume) Stephane@243: { Stephane@243: //Hope that's doing what I think it does Stephane@243: IntPtr dwBytesReturned = new IntPtr(); Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there. Stephane@243: OVERLAPPED overlapped = new OVERLAPPED(); Stephane@243: Stephane@243: bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); Stephane@243: CheckLastError("Media load: "); Stephane@243: return res; Stephane@243: } Stephane@243: Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: /// Stephane@243: static private bool StorageCheckVerify(SafeFileHandle aVolume) Stephane@243: { Stephane@243: //Hope that's doing what I think it does Stephane@243: IntPtr dwBytesReturned = new IntPtr(); Stephane@243: //Should not be needed but I'm not sure how to pass NULL in there. Stephane@243: OVERLAPPED overlapped = new OVERLAPPED(); Stephane@243: Stephane@243: bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped); Stephane@243: Stephane@243: CheckLastError("Check verify: "); Stephane@243: Stephane@243: return res; Stephane@243: } Stephane@243: Stephane@243: Stephane@243: /// Stephane@243: /// Perform media ejection. Stephane@243: /// Stephane@243: static private void DriveEject(string aDrive) Stephane@243: { Stephane@243: string drive = aDrive; Stephane@243: if (drive.Length != 2) Stephane@243: { Stephane@243: //Not a proper drive spec. Stephane@243: //Probably 'None' selected. Stephane@243: return; Stephane@243: } Stephane@243: Stephane@243: SafeFileHandle handle = OpenVolume(drive); Stephane@243: if (handle.IsInvalid) Stephane@243: { Stephane@243: CheckLastError("ERROR: Failed to open volume: "); Stephane@243: return; Stephane@243: } Stephane@243: Stephane@243: if (LockVolume(handle) && DismountVolume(handle)) Stephane@243: { Stephane@243: Debug.WriteLine("Volume was dismounted."); Stephane@243: Stephane@243: if (PreventRemovalOfVolume(handle, false)) Stephane@243: { Stephane@243: //StorageCheckVerify(handle); Stephane@243: Stephane@243: DateTime before; Stephane@243: before = DateTime.Now; Stephane@243: bool ejectSuccess = MediaEject(handle); Stephane@243: double ms = (DateTime.Now - before).TotalMilliseconds; Stephane@243: Stephane@243: //We assume that if it take more than a certain time to for eject to execute it means we actually ejected. Stephane@243: //If our eject completes too rapidly we assume the tray is already open and we will try to close it. Stephane@243: if (ejectSuccess && ms > 100) Stephane@243: { Stephane@243: Debug.WriteLine("Media was ejected"); Stephane@243: } Stephane@243: else if (MediaLoad(handle)) Stephane@243: { Stephane@243: Debug.WriteLine("Media was loaded"); Stephane@243: } Stephane@243: } Stephane@243: } Stephane@243: else Stephane@243: { Stephane@243: Debug.WriteLine("Volume lock or dismount failed."); Stephane@243: } Stephane@243: Stephane@243: //This is needed to make sure we can open the volume next time around Stephane@243: handle.Dispose(); Stephane@243: } Stephane@243: StephaneLenclud@240: } StephaneLenclud@240: }