Sicheres Entfernen eines USB-Sticks

    • C#

    Es gibt 1 Antwort in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

      Sicheres Entfernen eines USB-Sticks

      Moin Leute,
      bei Codeproject fand ich den Artikel How to Prepare a USB Drive for Safe Removal.
      Dabei waren die Quellen eines entsprechenden C-Projekts.

      In Ermangelung anderer Aufgaben habe ich mich rangesetzt und diesen Code nach C# überttragen.
      Mit freundlicher Zustimmung des Autors Uwe Sieber uwe-sieber.de
      möchte ich Euch an dem Projekt teilhaben lassen.
      Ich habe es unter Win10 mit x86 und x64 getestet, es dürfte also unter AnyCPU laufen.
      Ich habe die Kommentare etwas präzisiert, fragt aber bitte nicht, wie das ganze funktioniert, das weiß ich auch nicht so richtig.
      In der Vorlage wurden einige Rückgabewerte ignoriert, die teste ich ab, was zum Übertragen nach C# erforderlich war.
      Aus einigen der Strukturen habe ich Klassen gemacht. Dies war erforderlich, weil es Aufrufe mit null als Parameter gab.

      Das ganze ist hier als Console-Anwendung geschrieben, die eigentliche(n) Prozedur(en) ist aber separiert, so dass sie in jedes Projekt eingefügt werden können.

      Program.cs

      C#-Quellcode

      1. //#define TEST_FIXED_LETTER // Defaultwert zum einfachen Testen
      2. // Ursprüngliche C-Vorlage
      3. // https://www.codeproject.com/Articles/13839/How-to-Prepare-a-USB-Drive-for-Safe-Removal
      4. // System-Error-Codes vom Betriebssystem
      5. // https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--0-499-
      6. using System;
      7. using System.IO;
      8. using System.Runtime.InteropServices;
      9. using System.Runtime.Serialization;
      10. using System.Runtime.Serialization.Formatters.Binary;
      11. using System.Text;
      12. namespace RemoveDriveByLetter
      13. {
      14. using HANDLE = IntPtr;
      15. internal class Program
      16. {
      17. private static void Main(string[] args)
      18. {
      19. char driveLetter;
      20. #if TEST_FIXED_LETTER
      21. Console.WriteLine("################################################");
      22. driveLetter = 'F';
      23. Console.WriteLine("Fixed Letter '{0}'", driveLetter);
      24. Console.WriteLine("################################################");
      25. #else
      26. if (args.Length == 0)
      27. {
      28. Console.WriteLine("RemoveDriveByLetter X");
      29. Console.WriteLine("Press any key to exit");
      30. Console.ReadKey();
      31. return;
      32. }
      33. driveLetter = args[0][0];
      34. #endif
      35. Console.WriteLine("System: {0}", Environment.Is64BitProcess ? "x64" : "x86");
      36. Console.WriteLine();
      37. Program.Eject(driveLetter);
      38. Console.WriteLine("Press any key to exit");
      39. Console.ReadKey();
      40. }
      41. /// <summary>
      42. /// Stand Alone-Methode zum Auswerfen eines USB-Sticks mit dem Laufwerksbuchstaben
      43. /// </summary>
      44. /// <param name="driveLetter">der Laufwerksbuchstabe</param>
      45. /// <returns>Success</returns>
      46. private static bool Eject(char driveLetter)
      47. {
      48. driveLetter = char.ToUpper(driveLetter);
      49. if (driveLetter < 'A' || driveLetter > 'Z')
      50. {
      51. Console.WriteLine("Argument is not a Drive Letter");
      52. return false;
      53. }
      54. // "\\.\X:" -> to open the volume
      55. string szVolumeAccessPath = string.Format(@"\\.\{0}:", driveLetter);
      56. int deviceNumber = -1;
      57. // open the storage volume
      58. HANDLE hVolume = NativeMethods.CreateFile(szVolumeAccessPath, 0, System.IO.FileShare.Read | System.IO.FileShare.Write, IntPtr.Zero, System.IO.FileMode.Open, 0, IntPtr.Zero);
      59. if (hVolume == NativeMethods.INVALID_HANDLE_VALUE)
      60. {
      61. Console.WriteLine("Device not available");
      62. return false;
      63. }
      64. // get the volume's device number
      65. NativeMethods.STORAGE_DEVICE_NUMBER sdn = default(NativeMethods.STORAGE_DEVICE_NUMBER);
      66. uint dwBytesReturned = 0;
      67. bool res = NativeMethods.DeviceIoControl(hVolume, NativeMethods.IOCTL_STORAGE_GET_DEVICE_NUMBER, IntPtr.Zero, 0, ref sdn, Marshal.SizeOf(sdn), ref dwBytesReturned, IntPtr.Zero);
      68. if (res)
      69. {
      70. deviceNumber = sdn.DeviceNumber;
      71. }
      72. NativeMethods.CloseHandle(hVolume);
      73. if (deviceNumber == -1)
      74. {
      75. Console.WriteLine("Invalid Device Number");
      76. return false;
      77. }
      78. string szDevicePath = driveLetter + ":";
      79. System.IO.DriveInfo di = new System.IO.DriveInfo(szDevicePath);
      80. StringBuilder lpTargetPath = new StringBuilder(NativeMethods.MAX_PATH);
      81. // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
      82. res = NativeMethods.QueryDosDevice(szDevicePath, lpTargetPath, NativeMethods.MAX_PATH);
      83. if (!res)
      84. {
      85. Console.WriteLine("Invalid Device");
      86. return false;
      87. }
      88. // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
      89. int devInst = Program.GetDrivesDevInstByDeviceNumber(deviceNumber, di.DriveType, lpTargetPath.ToString());
      90. if (devInst == 0)
      91. {
      92. return false;
      93. }
      94. NativeMethods.PNP_VETO_TYPE VetoType = NativeMethods.PNP_VETO_TYPE.PNP_VetoTypeUnknown;
      95. char[] VetoNameW = new char[NativeMethods.MAX_PATH];
      96. VetoNameW[0] = '\0';
      97. bool bSuccess = false;
      98. // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
      99. uint DevInstParent = 0;
      100. uint res2 = NativeMethods.CM_Get_Parent(out DevInstParent, devInst, 0);
      101. // res2 = 0 !
      102. for (int tries = 1; tries <= 3; tries++)
      103. {
      104. // sometimes we need some tries...
      105. VetoNameW[0] = '\0';
      106. res2 = NativeMethods.CM_Request_Device_Eject(DevInstParent, out VetoType, VetoNameW, NativeMethods.MAX_PATH, 0);
      107. bSuccess = (res2 == 0 && VetoType == NativeMethods.PNP_VETO_TYPE.PNP_VetoTypeUnknown);
      108. if (bSuccess)
      109. {
      110. break;
      111. }
      112. System.Threading.Thread.Sleep(500); // required to give the next tries a chance!
      113. }
      114. if (bSuccess)
      115. {
      116. Console.WriteLine("Success\n\n");
      117. }
      118. else
      119. {
      120. Console.WriteLine("failed\n");
      121. Console.WriteLine(string.Format("Result = {0}\n", res));
      122. if (VetoNameW[0] != '\0')
      123. {
      124. Console.WriteLine("VetoName = {0]", VetoNameW);
      125. }
      126. }
      127. return true;
      128. }
      129. private static int GetDrivesDevInstByDeviceNumber(int DeviceNumber, System.IO.DriveType driveType, string szDosDeviceName)
      130. {
      131. bool isFloppy = szDosDeviceName.Contains("\\Floppy"); // who knows a better way?
      132. Guid guid;
      133. // ReSharper disable once SwitchStatementMissingSomeCases
      134. switch (driveType)
      135. {
      136. case System.IO.DriveType.Removable:
      137. if (isFloppy)
      138. {
      139. guid = new Guid(0x53f56311, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
      140. }
      141. else
      142. {
      143. guid = new Guid(0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
      144. }
      145. break;
      146. case System.IO.DriveType.Fixed:
      147. guid = new Guid(0x53f56307, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
      148. break;
      149. case System.IO.DriveType.CDRom:
      150. guid = new Guid(0x53f56308, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
      151. break;
      152. default:
      153. return 0;
      154. }
      155. // Get device interface info set handle for all devices attached to system
      156. HANDLE hDevInfo = NativeMethods.SetupDiGetClassDevs(ref guid, IntPtr.Zero, IntPtr.Zero, NativeMethods.DIGCF_PRESENT | NativeMethods.DIGCF_DEVICEINTERFACE);
      157. if (hDevInfo == NativeMethods.INVALID_HANDLE_VALUE)
      158. {
      159. return 0;
      160. }
      161. // Retrieve a context structure for a device interface of a device information set
      162. uint dwIndex = 0;
      163. bool res;
      164. // class
      165. NativeMethods.SP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = new NativeMethods.SP_DEVICE_INTERFACE_DETAIL_DATA();
      166. // struct
      167. NativeMethods.SP_DEVICE_INTERFACE_DATA spdid = default(NativeMethods.SP_DEVICE_INTERFACE_DATA);
      168. // class
      169. NativeMethods.SP_DEVINFO_DATA spdd = new NativeMethods.SP_DEVINFO_DATA();
      170. spdid.cbSize = Marshal.SizeOf(spdid); // 28
      171. uint dwSize;
      172. while (true)
      173. {
      174. res = NativeMethods.SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, ref guid, dwIndex, ref spdid);
      175. if (!res)
      176. {
      177. break;
      178. }
      179. // check the buffer size
      180. dwSize = 0;
      181. res = NativeMethods.SetupDiGetDeviceInterfaceDetail(hDevInfo, ref spdid, IntPtr.Zero, 0, out dwSize, null);
      182. // dieser Rückgabewert wurde in der C-Vorlage einfach ignoriert.
      183. if (!res)
      184. {
      185. int error = Marshal.GetLastWin32Error();
      186. int error2 = 122; // steht im www
      187. if (Environment.Is64BitProcess)
      188. {
      189. // x64 error = 1008, in der C-Vorlage ausprobiert
      190. error2 = 1008;
      191. }
      192. // das muss so sein!
      193. if (error != error2)
      194. {
      195. Console.WriteLine("Error = {0}", error);
      196. }
      197. }
      198. if (dwSize != 0 && dwSize <= pspdidd.DevicePath.Length)
      199. {
      200. spdd.Clear();
      201. spdd.cbSize = Marshal.SizeOf(spdd);
      202. // Bestimmung der realen Größe der pspdidd-Instanz
      203. int objectSize = Program.GetObjectSize(pspdidd);
      204. // unmanaged Speicher bereitstellen
      205. IntPtr p1 = Marshal.AllocHGlobal(objectSize);
      206. // Länge der nativen Struktur reinschreiben, Werte aus der C-Vorlage übernommen
      207. if (Environment.Is64BitProcess)
      208. {
      209. Marshal.WriteInt32(p1, 8);
      210. }
      211. else
      212. {
      213. Marshal.WriteInt32(p1, 5);
      214. }
      215. res = NativeMethods.SetupDiGetDeviceInterfaceDetail(hDevInfo, ref spdid, p1, dwSize, out dwSize, spdd);
      216. if (!res)
      217. {
      218. int error = Marshal.GetLastWin32Error();
      219. Console.WriteLine("Error = {0}", error);
      220. // Speicher wieder freigeben
      221. Marshal.FreeHGlobal(p1);
      222. }
      223. else
      224. {
      225. // Index 4 geht irgendwie nicht richtig
      226. Marshal.Copy(p1, pspdidd.DevicePath, 0, pspdidd.DevicePath.Length - 4);
      227. // Speicher wieder freigeben
      228. Marshal.FreeHGlobal(p1);
      229. string fileName = pspdidd.GetFileName((int)dwSize);
      230. // Ausgabe des System-Pfades zur Kontrolle
      231. Console.WriteLine(fileName);
      232. // open the disk or cdrom or floppy
      233. HANDLE hDrive = NativeMethods.CreateFile(fileName, 0, System.IO.FileShare.Read | System.IO.FileShare.Write, IntPtr.Zero, System.IO.FileMode.Open, 0, IntPtr.Zero);
      234. if (hDrive != NativeMethods.INVALID_HANDLE_VALUE)
      235. {
      236. // get its device number
      237. NativeMethods.STORAGE_DEVICE_NUMBER sdn = default(NativeMethods.STORAGE_DEVICE_NUMBER);
      238. uint dwBytesReturned = 0;
      239. res = NativeMethods.DeviceIoControl(hDrive, NativeMethods.IOCTL_STORAGE_GET_DEVICE_NUMBER, IntPtr.Zero, 0, ref sdn, Marshal.SizeOf(sdn), ref dwBytesReturned, IntPtr.Zero);
      240. if (res)
      241. {
      242. if (DeviceNumber == sdn.DeviceNumber)
      243. {
      244. // match the given device number with the one of the current device
      245. NativeMethods.CloseHandle(hDrive);
      246. NativeMethods.SetupDiDestroyDeviceInfoList(hDevInfo);
      247. return spdd.DevInst;
      248. }
      249. }
      250. NativeMethods.CloseHandle(hDrive);
      251. }
      252. }
      253. }
      254. dwIndex++;
      255. }
      256. NativeMethods.SetupDiDestroyDeviceInfoList(hDevInfo);
      257. return 0;
      258. }
      259. /// <summary>
      260. /// Bestimmen der Größe der SP_DEVICE_INTERFACE_DETAIL_DATA-Instanz
      261. /// </summary>
      262. /// <param name="pspdidd">die Instant</param>
      263. /// <returns>deren Größe</returns>
      264. private static int GetObjectSize(NativeMethods.SP_DEVICE_INTERFACE_DETAIL_DATA pspdidd)
      265. {
      266. int objectSize;
      267. using (MemoryStream stream = new MemoryStream())
      268. {
      269. IFormatter formatter = new BinaryFormatter();
      270. formatter.Serialize(stream, pspdidd);
      271. objectSize = (int)stream.Position;
      272. }
      273. return objectSize;
      274. }
      275. }
      276. }
      NativeMethods.cs

      C#-Quellcode

      1. using System;
      2. using System.Runtime.InteropServices;
      3. using System.Text;
      4. namespace RemoveDriveByLetter
      5. {
      6. internal static class NativeMethods
      7. {
      8. #region Enum
      9. /// <summary>
      10. /// Enum für CM_Request_Device_Eject
      11. /// </summary>
      12. internal enum PNP_VETO_TYPE
      13. {
      14. PNP_VetoTypeUnknown, // Name is unspecified
      15. PNP_VetoLegacyDevice, // Name is an Instance Path
      16. PNP_VetoPendingClose, // Name is an Instance Path
      17. PNP_VetoWindowsApp, // Name is a Module
      18. PNP_VetoWindowsService, // Name is a Service
      19. PNP_VetoOutstandingOpen, // Name is an Instance Path
      20. PNP_VetoDevice, // Name is an Instance Path
      21. PNP_VetoDriver, // Name is a Driver Service Name
      22. PNP_VetoIllegalDeviceRequest, // Name is an Instance Path
      23. PNP_VetoInsufficientPower, // Name is unspecified
      24. PNP_VetoNonDisableable, // Name is an Instance Path
      25. PNP_VetoLegacyDriver, // Name is a Service
      26. PNP_VetoInsufficientRights // Name is unspecified
      27. }
      28. #endregion Enum
      29. #region Classes
      30. [Serializable]
      31. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
      32. internal class SP_DEVICE_INTERFACE_DETAIL_DATA
      33. {
      34. /// <summary>Größe der Struktur</summary>
      35. public int cbSize;
      36. /// <summary>Speicher für den Pfad</summary>
      37. public byte[] DevicePath;
      38. public SP_DEVICE_INTERFACE_DETAIL_DATA()
      39. {
      40. this.DevicePath = new byte[1024];
      41. }
      42. /// <summary>
      43. /// Auslesen des File-Namens
      44. /// </summary>
      45. /// <param name="size">C-String-Länge inklusive '\0'</param>
      46. /// <returns>.NET-File-Name</returns>
      47. public string GetFileName(int size)
      48. {
      49. string fileName = System.Text.Encoding.UTF8.GetString(this.DevicePath, 4, size);
      50. // String am 0-Terminierer abschneiden
      51. fileName = fileName.Split(new[] { '\0' }, 2)[0];
      52. return fileName;
      53. }
      54. }
      55. [StructLayout(LayoutKind.Sequential)]
      56. internal class SP_DEVINFO_DATA
      57. {
      58. public int cbSize;
      59. public Guid ClassGuid;
      60. public int DevInst; // DEVINST handle
      61. public IntPtr Reserved;
      62. public void Clear()
      63. {
      64. this.cbSize = 0;
      65. this.ClassGuid = Guid.Empty;
      66. this.DevInst = 0;
      67. this.Reserved = IntPtr.Zero;
      68. }
      69. }
      70. #endregion Classes
      71. #region Structs
      72. [StructLayout(LayoutKind.Sequential)]
      73. internal struct SP_DEVICE_INTERFACE_DATA
      74. {
      75. public int cbSize;
      76. public Guid InterfaceClassGuid;
      77. public uint Flags;
      78. public IntPtr Reserved;
      79. }
      80. [StructLayout(LayoutKind.Sequential)]
      81. internal struct STORAGE_DEVICE_NUMBER
      82. {
      83. /// <summary>The FILE_DEVICE_XXX type for this device.</summary>
      84. public int DeviceType;
      85. /// <summary>The number of this device</summary>
      86. public int DeviceNumber;
      87. /// <summary>
      88. /// If the device is partitionable, the partition number of the device.
      89. /// Otherwise -1
      90. /// </summary>
      91. public int PartitionNumber;
      92. }
      93. #endregion Structs
      94. #region Statics, Consts
      95. internal static IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);
      96. internal const int MAX_PATH = 260;
      97. internal const int DIGCF_PRESENT = 0x02;
      98. internal const int DIGCF_DEVICEINTERFACE = 0x10;
      99. internal const uint METHOD_BUFFERED = 0;
      100. //internal const uint METHOD_IN_DIRECT = 1;
      101. //internal const uint METHOD_OUT_DIRECT = 2;
      102. //internal const uint METHOD_NEITHER = 3;
      103. private const int FILE_ANY_ACCESS = 0;
      104. //private const int FILE_READ_ACCESS = 1;
      105. //private const int FILE_WRITE_ACCESS = 2;
      106. //private const int OPEN_EXISTING = 3;
      107. // Device type in the "User Defined" range.
      108. private const uint IOCTL_STORAGE_BASE = 0x0000002d;
      109. #endregion Statics, Consts
      110. #region Kernel32.dll
      111. [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
      112. internal static extern IntPtr CreateFile(
      113. string fileName,
      114. uint fileAccess,
      115. [MarshalAs(UnmanagedType.U4)] System.IO.FileShare fileShare,
      116. IntPtr securityAttributes,
      117. [MarshalAs(UnmanagedType.U4)] System.IO.FileMode creationDisposition,
      118. int flags,
      119. IntPtr template);
      120. [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
      121. [return: MarshalAs(UnmanagedType.Bool)]
      122. internal static extern bool DeviceIoControl(
      123. IntPtr hDevice, // Microsoft.Win32.SafeHandles.SafeFileHandle hDevice,
      124. uint dwIoControlCode, // EIOControlCode IoControlCode,
      125. IntPtr InBuffer, // [MarshalAs(UnmanagedType.AsAny)] [In] object InBuffer,
      126. int nInBufferSize, // Size in Byte of Data in InBuffer
      127. //IntPtr OutBuffer, // [MarshalAs(UnmanagedType.AsAny)] [Out] object OutBuffer,
      128. [MarshalAs(UnmanagedType.Struct)] ref STORAGE_DEVICE_NUMBER lpSystemInfo,
      129. int nOutBufferSize, // Size in Byte of Data in OutBuffer
      130. ref uint nBytesReturned, // Number of Bytes pushed in OutBuffer (<= nOutBufferSize)
      131. IntPtr lpOverlapped); // [In] ref System.Threading.NativeOverlapped Overlapped
      132. [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
      133. [return: MarshalAs(UnmanagedType.Bool)]
      134. internal static extern bool QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
      135. [DllImport("kernel32.dll", SetLastError = true)]
      136. [return: MarshalAs(UnmanagedType.Bool)]
      137. internal static extern bool CloseHandle(IntPtr hObject);
      138. #endregion Kernel32.dll
      139. #region SetupApi.dll
      140. [DllImport("setupapi.dll", SetLastError = true)]
      141. internal static extern IntPtr SetupDiGetClassDevs(ref Guid classGuid, IntPtr enumerator, IntPtr hwndParent, UInt32 flags);
      142. [DllImport("setupapi.dll", SetLastError = true)]
      143. [return: MarshalAs(UnmanagedType.Bool)]
      144. internal static extern bool SetupDiEnumDeviceInterfaces(IntPtr hDevInfo, IntPtr devInfo, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
      145. [DllImport("setupapi.dll", SetLastError = true)]
      146. [return: MarshalAs(UnmanagedType.Bool)]
      147. internal static extern bool SetupDiGetDeviceInterfaceDetail(
      148. IntPtr hDevInfo,
      149. ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
      150. //SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData,
      151. IntPtr deviceInterfaceDetailData,
      152. uint deviceInterfaceDetailDataSize,
      153. out uint requiredSize,
      154. SP_DEVINFO_DATA deviceInfoData);
      155. [DllImport("setupapi.dll", SetLastError = true)]
      156. [return: MarshalAs(UnmanagedType.Bool)]
      157. internal static extern bool SetupDiDestroyDeviceInfoList(IntPtr hDevInfo);
      158. [DllImport("setupapi.dll")]
      159. internal static extern uint CM_Get_Parent(out UInt32 pdnDevInst, int dnDevInst, int ulFlags);
      160. [DllImport("setupapi.dll", CharSet = CharSet.Auto)]
      161. internal static extern uint CM_Request_Device_Eject(UInt32 devinst, out PNP_VETO_TYPE pVetoType, char[] pszVetoName, int ulNameLength, int ulFlags);
      162. #endregion SetupApi.dll
      163. #region Properties
      164. internal static uint IOCTL_STORAGE_GET_DEVICE_NUMBER
      165. {
      166. get { return NativeMethods.CTL_CODE(NativeMethods.IOCTL_STORAGE_BASE, 0x0420, NativeMethods.METHOD_BUFFERED, NativeMethods.FILE_ANY_ACCESS); }
      167. }
      168. #endregion Properties
      169. #region Private Stuff
      170. /// <summary>
      171. /// from WinIoCtl.h
      172. /// Macro definition for defining IOCTL and FSCTL function control codes.
      173. /// Note that function codes 0-2047 are reserved for Microsoft Corporation,
      174. /// and 2048-4095 are reserved for customers.
      175. /// </summary>
      176. /// <returns>Control Code</returns>
      177. private static uint CTL_CODE(uint deviceType, uint function, uint method, uint access)
      178. {
      179. return (((deviceType) << 16) | ((access) << 14) | ((function) << 2) | (method));
      180. }
      181. #endregion Private Stuff
      182. }
      183. }
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „RodFromGermany“ () aus folgendem Grund: Ausgabe der Prozessbreite (x64 / x86)

      Unter Win7-32 als auch Win7-64 läuft das ganze mit AnyCPU.
      ====
      Einen kleinen Effekt gibt es beim Win7-32, das läuft als VM unter Ubuntu.
      Das Windows weiß danach zwar nix mehr von dem entfernten Laufwerk, allerdings wird es im Ubuntu-Menü Geräte=>USB immer noch mit einem Haken als aktiv angezeigt.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!