Ok, I've always used the AllocConsole() method for console output when it comes to needing a Winform as well, because I use a wide variety of color when it comes to writing to console.
Using VS 2015 and below, AllocConsole in Debug mode always worked properly, Console.WriteLine wrote to it properly. Now using VS 2017, The Console Shows when AllocConsole is called, however, instead of console.WriteLine outputs going to that console, it is going to the Output window of Visual Studio.
I prefer to use the AllocConsole rather than the Output window because I rely heavily on color. I've done plenty of searching on how to fix this, but I can't seem to find an answer.
AllocConsole() does not work on it's own, because VS 2017 does some "debug stdout redirect magic". To fix this you need to create a console with AllocConsole() and fix the stdout handle.
Here is the snipped I found:
[DllImport("kernel32.dll",
EntryPoint = "AllocConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
uint lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
uint hTemplateFile);
private const int MY_CODE_PAGE = 437;
private const uint GENERIC_WRITE = 0x40000000;
private const uint FILE_SHARE_WRITE = 0x2;
private const uint OPEN_EXISTING = 0x3;
public static void CreateConsole()
{
AllocConsole();
IntPtr stdHandle = CreateFile(
"CONOUT$",
GENERIC_WRITE,
FILE_SHARE_WRITE,
0, OPEN_EXISTING, 0, 0
);
SafeFileHandle safeFileHandle = new SafeFileHandle(stdHandle, true);
FileStream fileStream = new FileStream(safeFileHandle, FileAccess.Write);
Encoding encoding = System.Text.Encoding.GetEncoding(MY_CODE_PAGE);
StreamWriter standardOutput = new StreamWriter(fileStream, encoding);
standardOutput.AutoFlush = true;
Console.SetOut(standardOutput);
Console.Write("This will show up in the Console window.");
}
Special thanks to Ramkumar Ramesh for the work-around:
Console Output is gone in VS2017
Building on the answer from wischi, if you want properties for the static Console class to work correctly, for instance Console.ForegroundColor it is important to set the desiredAccess to GENERIC_READ | GENERIC_WRITE. I think the reason for this is that Console is using GetConsoleScreenBufferInfo internally and when I tried to use that method on the handle returned by CreateFile with only GENERIC_WRITE that gave me an ACCESS_DENIED error.
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(string lpFileName
, [MarshalAs(UnmanagedType.U4)] DesiredAccess dwDesiredAccess
, [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode
, uint lpSecurityAttributes
, [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition
, [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes
, uint hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetStdHandle(StdHandle nStdHandle, IntPtr hHandle);
private enum StdHandle : int
{
Input = -10,
Output = -11,
Error = -12
}
[Flags]
enum DesiredAccess : uint
{
GenericRead = 0x80000000,
GenericWrite = 0x40000000,
GenericExecute = 0x20000000,
GenericAll = 0x10000000
}
public static void CreateConsole()
{
if (AllocConsole())
{
//https://developercommunity.visualstudio.com/content/problem/12166/console-output-is-gone-in-vs2017-works-fine-when-d.html
// Console.OpenStandardOutput eventually calls into GetStdHandle. As per MSDN documentation of GetStdHandle: http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231(v=vs.85).aspx will return the redirected handle and not the allocated console:
// "The standard handles of a process may be redirected by a call to SetStdHandle, in which case GetStdHandle returns the redirected handle. If the standard handles have been redirected, you can specify the CONIN$ value in a call to the CreateFile function to get a handle to a console's input buffer. Similarly, you can specify the CONOUT$ value to get a handle to a console's active screen buffer."
// Get the handle to CONOUT$.
var stdOutHandle = CreateFile("CONOUT$", DesiredAccess.GenericRead | DesiredAccess.GenericWrite, FileShare.ReadWrite, 0, FileMode.Open, FileAttributes.Normal, 0);
if (stdOutHandle == new IntPtr(-1))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
if (!SetStdHandle(StdHandle.Output, stdOutHandle))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
var standardOutput = new StreamWriter(Console.OpenStandardOutput());
standardOutput.AutoFlush = true;
Console.SetOut(standardOutput);
}
}
Related
I'm trying to extract an icon from imageres.dll. Specifically the "My Computer" or "This PC" icon. The problem is that at between Win7 and Win10, the icon number changes. However, the icon group does not (109). Is there a way to get that icon group, and then let the computer figure out which icon to use of that group, in the same way it figures out which icon to use for my app?
This is the code I'm using to get the specific icon via the index:
public class GetIcon {
public static Icon Extract(string file, int number) {
IntPtr large;
IntPtr small;
ExtractIconEx(file, number, out large, out small, 1);
try {
return Icon.FromHandle(small);
}
catch {
return null;
}
}
[DllImport("Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern int ExtractIconEx(string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons);
}
Thanks.
There are a couple of ways to do this. The most reliable, and potentially most time consuming (provided you can't find an existing library), is to parse the PE File (i.e. .exe, .dll) and extract the relevant Icon group data yourself. Here's a good resource for the format: https://msdn.microsoft.com/en-us/library/ms809762.aspx
The second way, can be done easily enough with Windows functions, however there is one caveat. It will only work on PE files that are of the same bit-type as your application. So, for example, if your application is 64-bit, it will only work on 64-bit PE files.
Here's a function I just wrote - based off this: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648051(v=vs.85).aspx#_win32_Sharing_Icon_Resources, that takes a file name, group number, and desired icon size, and returns a System.Drawing.Icon
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string lpFileName);
[DllImport("kernel32.dll")]
static extern IntPtr FindResource(IntPtr hModule, int lpName, int lpType);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
[DllImport("kernel32.dll")]
static extern IntPtr LockResource(IntPtr hResData);
[DllImport("user32.dll")]
static extern int LookupIconIdFromDirectoryEx(byte[] presbits, bool fIcon, int cxDesired, int cyDesired, uint Flags);
[DllImport("user32.dll")]
static extern IntPtr CreateIconFromResourceEx(byte[] pbIconBits, uint cbIconBits, bool fIcon, uint dwVersion, int cxDesired, int cyDesired, uint uFlags);
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);
const int RT_GROUP_ICON = 14;
const int RT_ICON = 0x00000003;
private System.Drawing.Icon GetIconFromGroup(string file, int groupId, int size)
{
IntPtr hExe = LoadLibrary(file);
if(hExe != IntPtr.Zero)
{
IntPtr hResource = FindResource(hExe, groupId, RT_GROUP_ICON);
IntPtr hMem = LoadResource(hExe, hResource);
IntPtr lpResourcePtr = LockResource(hMem);
uint sz = SizeofResource(hExe, hResource);
byte[] lpResource = new byte[sz];
Marshal.Copy(lpResourcePtr, lpResource, 0, (int)sz);
int nID = LookupIconIdFromDirectoryEx(lpResource, true, size, size, 0x0000);
hResource = FindResource(hExe, nID, RT_ICON);
hMem = LoadResource(hExe, hResource);
lpResourcePtr = LockResource(hMem);
sz = SizeofResource(hExe, hResource);
lpResource = new byte[sz];
Marshal.Copy(lpResourcePtr, lpResource, 0, (int)sz);
IntPtr hIcon = CreateIconFromResourceEx(lpResource, sz, true, 0x00030000, size, size, 0);
System.Drawing.Icon testIco = System.Drawing.Icon.FromHandle(hIcon);
return testIco;
}
return null;
}
The process basically works like this:
use LoadLibrary to load up the .exe or .dll file
Get the handle & data of the RT_GROUP_ICON resource
Pass the data, along with the desired size to LookupIconIdFromDirectoryEx, to get the icon index
From there, you can either use ExtractIconEx, or just repeat step 2 with the icon index, and RT_ICON instead, followed by using CreateIconFromResourceEx to get your icon handle.
I have an C#/.NET application that behaves similarly to svchost--we use it to start our services, each of which resides in a separate DLL. When looking in the Task Manager, all of these show up with the same name--the name of the executable process that started them.
We would like to make each of these be named uniquely. Is there an API call that can be made to set the process name dynamically--i.e. to a string that is read from a configuration file?
I've read the other similar topics, but they haven't helped. A response to Question #14711100 suggested using symlinks, to which the response was that doesn't work. That question was also about killing and starting, so the answers were geared toward that problem.
Others mentioned setting the window title bar text or the process name of the executable. Those don't apply here because there is no window title bar, and the same executable is used to launch different processes.
Question #1402824 seems to be the closest. Is there any libc implementation available under Windows that would work?
If this can't be done, then are there any ways to somehow fake it? Our main goal is to be able to distinguish which process is which in Task Manager and similar process lists.
If we could even use the PID to get at the full command line, that would probably help.
As a last resort, if I set up some sort of supervisor that copies the EXE to a new name before launching, what are the implications that I should consider? Would I need to worry about anything duplicated in memory? Anything shared in memory? Anyone stomping on anyone's data, files, etc? An answer to Question #1402824 mentioned the same context. What do we need to know about that?
Thanks.
EDIT
I learned that I could get the process information using the wmic tool:
wmic service where (pathname like "%%svchostOrWhatever.exe%%" and state="Running") get DisplayName,Name,PathName,ProcessId,StartMode
This might end up being what we have to live with if we can't change the image name. Regarding that, I did find this VB 6.0 project by Billy Conner:
http://www.planetsourcecode.com/vb/scripts/ShowCode.asp?txtCodeId=66443&lngWId=1
I understand things have changed a lot since 2006, but I thought it's worth a try. (FYI I'm on Windows 7.)
It looks like it's basically using SendMessage to send a LVM_SETITEMTEXT message with the desired process name. I translated the calls to C# using P/Invoke, but it doesn't appear to work.
If I understand the API correctly, SendMessage will return true (nonzero) for LVM_SETITEMTEXT if it succeeded and false (zero) if failed, and then I can find out more information as usual using the managed version of GetLastError.
When I ran this in my svchost equivalent, SendMessage() returned zero. I called Marshal.GetLastWin32Error() and got back 1400, which looks like I'm getting ERROR_INVALID_WINDOW_HANDLE. Ok, maybe I can't run this on a Windows service since it doesn't have a window.
Then, I tried it in a dummy WPF application. This time, I still got a 0 back from SendMessage(), but GetLastWin32Error is also returning zero.
The dummy application has an App.xaml and a MainWindow.xaml. I placed the following call at the end of my MainWindow constructor in the code behind:
IntPtr handle = System.Diagnostics.Process.GetCurrentProcess().Handle;
SetItemText(handle, "Gobbledygook.exe", 0);
Here is the definition of SetItemText as taken and translated from Connor's project:
public static void SetItemText(IntPtr handle,
string desiredImageName,
long index,
long subIndex = 0)
{
// TODO: Check return values for errors...
desiredImageName = string.Format("{0}\0", desiredImageName); // TODO: Is the nul character required?
byte[] memStorage = System.Text.Encoding.ASCII.GetBytes(desiredImageName);
long size = memStorage.Length;
// allocate some shared memory for our string
IntPtr sharedProcMemString = VirtualAllocEx(handle,
IntPtr.Zero,
(uint)size,
AllocationType.Reserve | AllocationType.Commit,
MemoryProtection.ReadWrite);
// setup some info
LVITEM lvi = new LVITEM()
{
iItem = (int)index,
iSubItem = (int)subIndex,
cchTextMax = (int)size,
pszText = sharedProcMemString // store our string handle
};
// allocate some shared memory for our Struct
IntPtr sharedProcMem = VirtualAllocEx(handle,
IntPtr.Zero,
(uint)System.Runtime.InteropServices.Marshal.SizeOf(lvi),
AllocationType.Reserve | AllocationType.Commit,
MemoryProtection.ReadWrite);
// write to memory
WriteProcessMemory(handle, sharedProcMemString, memStorage);
WriteProcessMemory(handle, sharedProcMem, GetBytes(lvi));
// get the text
IntPtr result = SendMessage(handle, LVM_SETITEMTEXTA, (IntPtr)index, sharedProcMem);
if ((int)result == 0) // false, i.e. error
{
int errorCode = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
// TODO: do something with errorCode - e.g. log it?
}
// clean up
VirtualFreeEx(handle, sharedProcMem, System.Runtime.InteropServices.Marshal.SizeOf(lvi), AllocationType.Release);
VirtualFreeEx(handle, sharedProcMemString, (int)size, AllocationType.Release);
}
Here are some supporting routines:
private static byte[] GetBytes(LVITEM str)
{
int size = System.Runtime.InteropServices.Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = System.Runtime.InteropServices.Marshal.AllocHGlobal(size);
System.Runtime.InteropServices.Marshal.StructureToPtr(str, ptr, true);
System.Runtime.InteropServices.Marshal.Copy(ptr, arr, 0, size);
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
return arr;
}
public static void WriteProcessMemory(IntPtr handle, IntPtr writeAddress, byte[] buffer)
{
int bytesWritten;
WriteProcessMemory(handle, writeAddress, buffer, (uint)buffer.Length, out bytesWritten);
}
Here are the necessary P/Invoke declarations:
private const int LVM_FIRST = 0x1000;
private const int LVM_GETITEMCOUNT = LVM_FIRST + 4;
private const int LVM_GETITEM = LVM_FIRST + 75;
private const int LVIF_TEXT = 0x0001;
private const int LVM_SETITEMTEXTA = (LVM_FIRST + 46);
[Flags]
public enum AllocationType
{
Commit = 0x1000,
Reserve = 0x2000,
Decommit = 0x4000,
Release = 0x8000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000,
WriteWatch = 0x200000,
LargePages = 0x20000000
}
[Flags]
public enum MemoryProtection
{
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
GuardModifierflag = 0x100,
NoCacheModifierflag = 0x200,
WriteCombineModifierflag = 0x400
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
private struct LVITEM
{
public uint mask;
public int iItem;
public int iSubItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public IntPtr lParam;
}
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd,
int Msg,
IntPtr wParam,
IntPtr lParam);
[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
AllocationType flAllocationType,
MemoryProtection flProtect);
[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool VirtualFreeEx(IntPtr hProcess,
IntPtr lpAddress,
int dwSize,
AllocationType dwFreeType);
[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
uint nSize,
out int lpNumberOfBytesWritten);
Any thoughts? Thanks.
I am using CDB (Microsoft Console Debugger,) and WinDbg to try to force a break when heap corruption occurs by P/Invoke into ReadFile. I read many more bytes from a text file than what I've allocated to the chBuf array. The debugger does not see the access violation until after GC.Collect, which is too late for me. Prior to running my program, I run
gflags -p /enable testheap.exe /unaligned
The effect seems useless. I wrote this little test program to apply what I find to debugging a much larger commercial program that is having heap corruption issues.
I have also tried DebugDiag with Application Verifier and MDA callbackOnCollectedDelegate without success. Isn't my use of gflags supposed to detect heap corruption immediately after ReadFile?
The code:
namespace TestHeap
public partial class Form1 : Form
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadFile(SafeFileHandle hFile, [Out] byte[] lpBuffer,
uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
string fileName = "testHeap.txt";
const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING = 3;
SafeFileHandle sh;
byte[] chBuf = new byte[8];
public Form1()
{
InitializeComponent();
}
private void testBtn_Click(object sender, EventArgs e)
{
bool nStat;
uint bytesToRead = 1025;
uint bytesRead = 0;
if (!(nStat = ReadFile( sh, chBuf, bytesToRead, out bytesRead, IntPtr.Zero)))
Debug.Print("testBtn_Click error in ReadFile, nStat = {0}", nStat);
MessageBox.Show(string.Format("After ReadFile, bytesToRead = {0},\n bytes read = {1}", bytesToRead, bytesRead));
GC.Collect();
MessageBox.Show("testBtn_Click end, after GC.Collect");
}
private void Form1_Load(object sender, EventArgs e)
{
sh = CreateFile(fileName, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
}
}
}
Just a guess but I believe the unexpected gflags behaviour is caused by this line:
byte[] chBuf = new byte[8];
Since chBuf is managed by the CLR, gflags is not able to put the fill pattern after it to detect buffer overruns. Try changing that to:
IntPtr chBuf = Marshal.AllocHGlobal(8);
So that you will be allocating in the unmanaged heap. Gflags should be able to work with that. Also, you may need to change the signature of ReadFile for that to work:
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadFile(SafeFileHandle hFile, [Out] IntPtr lpBuffer,
uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
FileSystemInfo.LastWriteTime property is readonly in CF.
Is there an alternative way to change that date?
P/Invoke SetFileTime.
EDIT
Something along these lines (warning: untested)
[DllImport("coredll.dll")]
private static extern bool SetFileTime(string path,
ref long creationTime,
ref long lastAccessTime,
ref long lastWriteTime);
public void SetFileTimes(string path, DateTime time)
{
var ft = time.ToFileTime();
SetFileTime(path, ref ft, ref ft, ref ft);
}
Here is a fuller implementation, adapted from the answer ctacke provides above and this StackOverflow question. I hope this proves useful to someone:
// Some Windows constants
// File access (using CreateFileW)
public const uint GENERIC_READ = 0x80000000;
public const uint GENERIC_WRITE = 0x40000000;
public const uint GENERIC_READ_WRITE = (GENERIC_READ + GENERIC_WRITE);
public const int INVALID_HANDLE_VALUE = -1;
// File creation (using CreateFileW)
public const int CREATE_NEW = 1;
public const int OPEN_EXISTING = 3;
// File attributes (using CreateFileW)
public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
// P/Invokes
[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr CreateFileW(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr pSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplatefile);
[DllImport("coredll.dll", SetLastError = true)]
public static extern int CloseHandle(IntPtr hObject);
// Note: Create related P/Invokes to change creation or last access time.
// This one modifies the last write time only.
[DllImport("coredll.dll", EntryPoint = "SetFileTime", SetLastError = true)]
private static extern bool SetFileWriteTime(
IntPtr hFile,
IntPtr lpCreationTimeUnused,
IntPtr lpLastAccessTimeUnused,
ref long lpLastWriteTime);
// Open a handle to the file you want changed
IntPtr hFile = CreateFileW(
path, GENERIC_READ_WRITE, 0,
IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
IntPtr.Zero);
// Modify the last write time and close the file
long lTimeNow = DateTime.Now.ToFileTime();
SetFileWriteTime(hFile, IntPtr.Zero, IntPtr.Zero, ref lTimeNow);
CloseHandle(hFile);
Note that you can use System.IO.File.GetLastWriteTime (which is exposed in the .NET Compact Framework) to read the last write time if required.
This question already has answers here:
Closed 11 years ago.
Possible Duplicates:
Windows CDROM Eject
Open CD/DVD door with a Windows API call?
I have looked around and can't find a simple solution to what I want to do.
I want to open a CD-Rom from my C# app. It should check if the media is in fact a cd- rom and then open it. Is there a quick solution to this or am I missing something?
Check this URL, it has both managed and unmanaged code for .net
http://bytes.com/topic/c-sharp/answers/273513-how-eject-cd-rom-c
Try below code :
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace EjectMedia
{
class Program
{
static void Main(string[] args)
{
// My CDROM is on drive E:
EjectMedia.Eject(#"\\.\E:");
}
}
class EjectMedia
{
// Constants used in DLL methods
const uint GENERICREAD = 0x80000000;
const uint OPENEXISTING = 3;
const uint IOCTL_STORAGE_EJECT_MEDIA = 2967560;
const int INVALID_HANDLE = -1;
// File Handle
private static IntPtr fileHandle;
private static uint returnedBytes;
// Use Kernel32 via interop to access required methods
// Get a File Handle
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
uint desiredAccess,
uint shareMode,
IntPtr attributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);
[DllImport("kernel32", SetLastError=true)]
static extern int CloseHandle(IntPtr driveHandle);
[DllImport("kernel32", SetLastError = true)]
static extern bool DeviceIoControl(IntPtr driveHandle,
uint IoControlCode,
IntPtr lpInBuffer,
uint inBufferSize,
IntPtr lpOutBuffer,
uint outBufferSize,
ref uint lpBytesReturned,
IntPtr lpOverlapped);
public static void Eject(string driveLetter)
{
try
{
// Create an handle to the drive
fileHandle = CreateFile(driveLetter,
GENERICREAD,
0,
IntPtr.Zero,
OPENEXISTING,
0,
IntPtr.Zero);
if ((int)fileHandle != INVALID_HANDLE)
{
// Eject the disk
DeviceIoControl(fileHandle,
IOCTL_STORAGE_EJECT_MEDIA,
IntPtr.Zero, 0,
IntPtr.Zero, 0,
ref returnedBytes,
IntPtr.Zero);
}
}
catch
{
throw new Exception(Marshal.GetLastWin32Error().ToString());
}
finally
{
// Close Drive Handle
CloseHandle(fileHandle);
fileHandle = IntPtr.Zero;
}
}
}
}