1 using Microsoft.Win32.SafeHandles;
3 using System.Collections.Generic;
4 using System.Diagnostics;
7 using System.Runtime.Serialization;
9 using System.Threading.Tasks;
11 using System.ComponentModel;
12 using System.Runtime.InteropServices;
14 namespace SharpLib.Ear
17 [AttributeObject(Id = "Action.OpticalDrive.Eject", Name = "Eject", Description = "Eject media from an optical drive.")]
18 public class ActionOpticalDriveEject : Action
21 [AttributeObjectProperty
23 Id = "Action.OpticalDrive.Eject.Drive",
24 Name = "Drive to eject",
25 Description = "Select the drive you want to eject."
28 public PropertyComboBox Drive { get; set; } = new PropertyComboBox();
31 protected override void DoConstruct()
34 PopulateOpticalDrives();
39 public override string BriefBase()
41 return AttributeName + " " + Drive.CurrentItem ;
44 public override bool IsValid()
46 //This object is valid if our current item is contained in our drive list
47 return Drive.Items.Contains(Drive.CurrentItem);
50 protected override async Task DoExecute()
52 DriveEject(Drive.CurrentItem);
56 private void CheckCurrentItem()
58 if (!Drive.Items.Contains(Drive.CurrentItem) && Drive.Items.Count>0)
60 //Current item unknown, reset it then
61 Drive.CurrentItem = Drive.Items[0];
68 private void PopulateOpticalDrives()
70 //Reset our list of drives
71 Drive.Items = new List<string>();
72 //Go through each drives on our system and collected the optical ones in our list
73 DriveInfo[] allDrives = DriveInfo.GetDrives();
74 foreach (DriveInfo d in allDrives)
76 Debug.WriteLine("Drive " + d.Name);
77 Debug.WriteLine(" Drive type: {0}", d.DriveType);
79 if (d.DriveType == DriveType.CDRom)
81 //This is an optical drive, add it now
82 Drive.Items.Add(d.Name.Substring(0, 2));
91 /// <param name="aPrefix"></param>
92 static private void CheckLastError(string aPrefix)
94 string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
95 Debug.WriteLine(aPrefix + Marshal.GetLastWin32Error().ToString() + ": " + errorMessage);
101 /// <param name="data"></param>
102 /// <returns></returns>
103 static private IntPtr MarshalToPointer(object data)
105 IntPtr buf = Marshal.AllocHGlobal(
106 Marshal.SizeOf(data));
107 Marshal.StructureToPtr(data,
115 /// <returns></returns>
116 static private SafeFileHandle OpenVolume(string aDriveName)
118 return Function.CreateFile("\\\\.\\" + aDriveName,
119 SharpLib.Win32.FileAccess.GENERIC_READ,
120 SharpLib.Win32.FileShare.FILE_SHARE_READ | SharpLib.Win32.FileShare.FILE_SHARE_WRITE,
122 CreationDisposition.OPEN_EXISTING,
130 /// <param name="aVolume"></param>
131 /// <returns></returns>
132 static private bool LockVolume(SafeFileHandle aVolume)
134 //Hope that's doing what I think it does
135 IntPtr dwBytesReturned = new IntPtr();
136 //Should not be needed but I'm not sure how to pass NULL in there.
137 OVERLAPPED overlapped = new OVERLAPPED();
140 const int KMaxTries = 100;
141 const int KSleepTime = 10;
142 bool success = false;
144 while (!success && tries < KMaxTries)
146 success = Function.DeviceIoControl(aVolume, Const.FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
147 System.Threading.Thread.Sleep(KSleepTime);
151 CheckLastError("Lock volume: ");
159 /// <param name="aVolume"></param>
160 /// <returns></returns>
161 static private bool DismountVolume(SafeFileHandle aVolume)
163 //Hope that's doing what I think it does
164 IntPtr dwBytesReturned = new IntPtr();
165 //Should not be needed but I'm not sure how to pass NULL in there.
166 OVERLAPPED overlapped = new OVERLAPPED();
168 bool res = Function.DeviceIoControl(aVolume, Const.FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
169 CheckLastError("Dismount volume: ");
178 /// <param name="aVolume"></param>
179 /// <param name="aPreventRemoval"></param>
180 /// <returns></returns>
181 static private bool PreventRemovalOfVolume(SafeFileHandle aVolume, bool aPreventRemoval)
183 //Hope that's doing what I think it does
184 IntPtr dwBytesReturned = new IntPtr();
185 //Should not be needed but I'm not sure how to pass NULL in there.
186 OVERLAPPED overlapped = new OVERLAPPED();
188 PREVENT_MEDIA_REMOVAL preventMediaRemoval = new PREVENT_MEDIA_REMOVAL();
189 preventMediaRemoval.PreventMediaRemoval = Convert.ToByte(aPreventRemoval);
190 IntPtr preventMediaRemovalParam = MarshalToPointer(preventMediaRemoval);
192 bool result = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_MEDIA_REMOVAL, preventMediaRemovalParam, Convert.ToUInt32(Marshal.SizeOf(preventMediaRemoval)), IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
193 CheckLastError("Media removal: ");
194 Marshal.FreeHGlobal(preventMediaRemovalParam);
200 /// Eject optical drive media opening the tray if any.
202 /// <param name="aVolume"></param>
203 /// <returns></returns>
204 static private bool MediaEject(SafeFileHandle aVolume)
206 //Hope that's doing what I think it does
207 IntPtr dwBytesReturned = new IntPtr();
208 //Should not be needed but I'm not sure how to pass NULL in there.
209 OVERLAPPED overlapped = new OVERLAPPED();
211 bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
212 CheckLastError("Media eject: ");
217 /// Close an optical drive tray.
219 /// <param name="aVolume"></param>
220 /// <returns></returns>
221 static private bool MediaLoad(SafeFileHandle aVolume)
223 //Hope that's doing what I think it does
224 IntPtr dwBytesReturned = new IntPtr();
225 //Should not be needed but I'm not sure how to pass NULL in there.
226 OVERLAPPED overlapped = new OVERLAPPED();
228 bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
229 CheckLastError("Media load: ");
236 /// <param name="aVolume"></param>
237 /// <returns></returns>
238 static private bool StorageCheckVerify(SafeFileHandle aVolume)
240 //Hope that's doing what I think it does
241 IntPtr dwBytesReturned = new IntPtr();
242 //Should not be needed but I'm not sure how to pass NULL in there.
243 OVERLAPPED overlapped = new OVERLAPPED();
245 bool res = Function.DeviceIoControl(aVolume, Const.IOCTL_STORAGE_CHECK_VERIFY2, IntPtr.Zero, 0, IntPtr.Zero, 0, dwBytesReturned, ref overlapped);
247 CheckLastError("Check verify: ");
254 /// Perform media ejection.
256 static private void DriveEject(string aDrive)
258 string drive = aDrive;
259 if (drive.Length != 2)
261 //Not a proper drive spec.
262 //Probably 'None' selected.
266 SafeFileHandle handle = OpenVolume(drive);
267 if (handle.IsInvalid)
269 CheckLastError("ERROR: Failed to open volume: ");
273 if (LockVolume(handle) && DismountVolume(handle))
275 Debug.WriteLine("Volume was dismounted.");
277 if (PreventRemovalOfVolume(handle, false))
279 //StorageCheckVerify(handle);
282 before = DateTime.Now;
283 bool ejectSuccess = MediaEject(handle);
284 double ms = (DateTime.Now - before).TotalMilliseconds;
286 //We assume that if it take more than a certain time to for eject to execute it means we actually ejected.
287 //If our eject completes too rapidly we assume the tray is already open and we will try to close it.
288 if (ejectSuccess && ms > 100)
290 Debug.WriteLine("Media was ejected");
292 else if (MediaLoad(handle))
294 Debug.WriteLine("Media was loaded");
300 Debug.WriteLine("Volume lock or dismount failed.");
303 //This is needed to make sure we can open the volume next time around