From ecdd314dfff0f6b51a3098a98660a8f0f3c66f9f Mon Sep 17 00:00:00 2001 From: dahall Date: Mon, 31 May 2021 21:45:26 -0600 Subject: [PATCH] FIxes and improvements to PInvoke.VirtDisk (some breaking changes) and Vanara.VirtualDisk (chaged Enabled to ResilientChangeTrackingEnabled to avoid confusion). --- PInvoke/VirtDisk/VirtDisk.cs | 265 +++++++++--- UnitTests/VirtualDisk/VirtualDiskTests.cs | 690 ++++++++++++++++++------------ VirtualDisk/VirtualDisk.cs | 93 ++-- 3 files changed, 672 insertions(+), 376 deletions(-) diff --git a/PInvoke/VirtDisk/VirtDisk.cs b/PInvoke/VirtDisk/VirtDisk.cs index 23c0667e..afb53428 100644 --- a/PInvoke/VirtDisk/VirtDisk.cs +++ b/PInvoke/VirtDisk/VirtDisk.cs @@ -11,7 +11,10 @@ namespace Vanara.PInvoke public static partial class VirtDisk { /// The virtual storage type vendor Microsoft - public static readonly Guid VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT = new Guid("EC984AEC-A0F9-47e9-901F-71415A66345B"); + public static readonly Guid VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT = new(0xec984aec, 0xa0f9, 0x47e9, 0x90, 0x1f, 0x71, 0x41, 0x5a, 0x66, 0x34, 0x5b); + + /// The virtual storage type vendor unknown. + public static readonly Guid VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN = new(0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); /// Contains flags affecting the behavior of the ApplySnapshotVhdSet function. [PInvokeData("VirtDisk.h", MSDNShortId = "mt638035")] @@ -827,7 +830,7 @@ namespace Vanara.PInvoke /// [DllImport(Lib.VirtDisk, ExactSpelling = true)] [PInvokeData("VirtDisk.h")] - public static extern Win32Error AddVirtualDiskParent(VIRTUAL_DISK_HANDLE VirtualDiskHandle, [MarshalAs(UnmanagedType.LPWStr)] string ParentPath); + public static extern Win32Error AddVirtualDiskParent([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, [MarshalAs(UnmanagedType.LPWStr)] string ParentPath); /// Applies a snapshot of the current virtual disk for VHD Set files. /// @@ -842,7 +845,7 @@ namespace Vanara.PInvoke /// [DllImport(Lib.VirtDisk, ExactSpelling = true)] [PInvokeData("VirtDisk.h")] - public static extern Win32Error ApplySnapshotVhdSet(VIRTUAL_DISK_HANDLE VirtualDiskHandle, in APPLY_SNAPSHOT_VHDSET_PARAMETERS Parameters, APPLY_SNAPSHOT_VHDSET_FLAG Flags); + public static extern Win32Error ApplySnapshotVhdSet([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in APPLY_SNAPSHOT_VHDSET_PARAMETERS Parameters, APPLY_SNAPSHOT_VHDSET_FLAG Flags); /// /// Attaches a virtual hard disk (VHD) or CD or DVD image file (ISO) by locating an appropriate VHD provider to accomplish the attachment. @@ -872,8 +875,8 @@ namespace Vanara.PInvoke /// [DllImport(Lib.VirtDisk, ExactSpelling = true)] [PInvokeData("VirtDisk.h")] - public static extern Win32Error AttachVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, [Optional] PSECURITY_DESCRIPTOR SecurityDescriptor, ATTACH_VIRTUAL_DISK_FLAG Flags, - uint ProviderSpecificFlags, in ATTACH_VIRTUAL_DISK_PARAMETERS Parameters, [In] IntPtr Overlapped); + public static extern Win32Error AttachVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, [In, Optional] PSECURITY_DESCRIPTOR SecurityDescriptor, ATTACH_VIRTUAL_DISK_FLAG Flags, + uint ProviderSpecificFlags, in ATTACH_VIRTUAL_DISK_PARAMETERS Parameters, [In, Optional] IntPtr Overlapped); /// /// Attaches a virtual hard disk (VHD) or CD or DVD image file (ISO) by locating an appropriate VHD provider to accomplish the attachment. @@ -903,8 +906,39 @@ namespace Vanara.PInvoke /// [DllImport(Lib.VirtDisk, ExactSpelling = true)] [PInvokeData("VirtDisk.h")] - public static extern Win32Error AttachVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, [Optional] PSECURITY_DESCRIPTOR SecurityDescriptor, ATTACH_VIRTUAL_DISK_FLAG Flags, - uint ProviderSpecificFlags, in ATTACH_VIRTUAL_DISK_PARAMETERS Parameters, ref NativeOverlapped Overlapped); + public static extern Win32Error AttachVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, [In, Optional] PSECURITY_DESCRIPTOR SecurityDescriptor, ATTACH_VIRTUAL_DISK_FLAG Flags, + uint ProviderSpecificFlags, [In, Optional] IntPtr Parameters, [In, Optional] IntPtr Overlapped); + + /// + /// Attaches a virtual hard disk (VHD) or CD or DVD image file (ISO) by locating an appropriate VHD provider to accomplish the attachment. + /// + /// + /// A handle to an open virtual disk. For information on how to open a virtual disk, see the OpenVirtualDisk function. + /// + /// + /// An optional pointer to a SECURITY_DESCRIPTOR to apply to the attached virtual disk. If this parameter is NULL, the security + /// descriptor of the virtual disk image file is used. + /// + /// Ensure that the security descriptor that AttachVirtualDisk applies to the attached virtual disk grants the write attributes + /// permission for the user, or that the security descriptor of the virtual disk image file grants the write attributes permission + /// for the user if you specify NULL for this parameter. If the security descriptor does not grant write attributes permission for a + /// user, Shell displays the following error when the user accesses the attached virtual disk: The Recycle Bin is corrupted. Do you + /// want to empty the Recycle Bin for this drive? + /// + /// + /// A valid combination of values of the ATTACH_VIRTUAL_DISK_FLAG enumeration. + /// Flags specific to the type of virtual disk being attached. May be zero if none are required. + /// A pointer to a valid ATTACH_VIRTUAL_DISK_PARAMETERS structure that contains attachment parameter data. + /// An optional pointer to a valid OVERLAPPED structure if asynchronous operation is desired. + /// + /// Status of the request. + /// If the function succeeds, the return value is ERROR_SUCCESS. + /// If the function fails, the return value is an error code. For more information, see System Error Codes. + /// + [DllImport(Lib.VirtDisk, ExactSpelling = true)] + [PInvokeData("VirtDisk.h")] + public static extern Win32Error AttachVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, [In, Optional] PSECURITY_DESCRIPTOR SecurityDescriptor, ATTACH_VIRTUAL_DISK_FLAG Flags, + uint ProviderSpecificFlags, in ATTACH_VIRTUAL_DISK_PARAMETERS Parameters, in NativeOverlapped Overlapped); /// Breaks a previously initiated mirror operation and sets the mirror to be the active virtual disk. /// @@ -918,7 +952,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h", MSDNShortId = "hh448676")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error BreakMirrorVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle); + public static extern Win32Error BreakMirrorVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle); /// Reduces the size of a virtual hard disk (VHD) backing store file. /// @@ -970,7 +1004,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error CompactVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, COMPACT_VIRTUAL_DISK_FLAG Flags, in COMPACT_VIRTUAL_DISK_PARAMETERS Parameters, ref NativeOverlapped Overlapped); + public static extern Win32Error CompactVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, COMPACT_VIRTUAL_DISK_FLAG Flags, in COMPACT_VIRTUAL_DISK_PARAMETERS Parameters, in NativeOverlapped Overlapped); /// Reduces the size of a virtual hard disk (VHD) backing store file. /// @@ -1022,7 +1056,59 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error CompactVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, COMPACT_VIRTUAL_DISK_FLAG Flags, in COMPACT_VIRTUAL_DISK_PARAMETERS Parameters, IntPtr Overlapped); + public static extern Win32Error CompactVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, COMPACT_VIRTUAL_DISK_FLAG Flags, in COMPACT_VIRTUAL_DISK_PARAMETERS Parameters, [In, Optional] IntPtr Overlapped); + + /// Reduces the size of a virtual hard disk (VHD) backing store file. + /// + /// A handle to the open virtual disk, which must have been opened using the VIRTUAL_DISK_ACCESS_METAOPS flag in the + /// VirtualDiskAccessMask parameter passed to OpenVirtualDisk. For information on how to open a virtual disk, see the OpenVirtualDisk function. + /// + /// Must be the COMPACT_VIRTUAL_DISK_FLAG_NONE value (0) of the COMPACT_VIRTUAL_DISK_FLAG enumeration. + /// + /// A optional pointer to a valid COMPACT_VIRTUAL_DISK_PARAMETERS structure that contains compaction parameter data. + /// + /// An optional pointer to a valid OVERLAPPED structure if asynchronous operation is desired. + /// + /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails, the return value is an error code. For more + /// information, see System Error Codes. + /// + /// + /// Compaction can be run only on a virtual disk that is dynamically expandable or differencing. + /// There are two different types of compaction. + /// + /// + /// + /// The first type, file-system-aware compaction, uses the NTFS file system to determine free space. This is done by attaching the + /// VHD as a read-only device by including the VIRTUAL_DISK_ACCESS_METAOPS and VIRTUAL_DISK_ACCESS_ATTACH_RO flags in the + /// VirtualDiskAccessMask parameter passed to OpenVirtualDisk, attaching the VHD by calling AttachVirtualDisk, and while the VHD is + /// attached calling CompactVirtualDisk. Detaching the VHD before compaction is done can cause compaction to return failure before it + /// is done (similar to cancellation of compaction). + /// + /// + /// + /// + /// The second type, file-system-agnostic compaction, does not involve the file system but only looks for VHD blocks filled entirely + /// with zeros (0). This is done by including the VIRTUAL_DISK_ACCESS_METAOPS flag in the VirtualDiskAccessMask parameter passed to + /// OpenVirtualDisk, and calling CompactVirtualDisk. + /// + /// + /// + /// + /// File-system-aware compaction is the most efficient compaction type but using first the file-system-aware compaction followed by + /// the file-system-agnostic compaction will produce the smallest VHD. + /// + /// + /// A compaction operation on a virtual disk can be safely interrupted and re-run later. Re-opening a virtual disk file that has been + /// interrupted may result in the reduction of a virtual disk file's size at the time of opening. + /// + /// + /// Compaction can be CPU-intensive and/or I/O-intensive, depending on how large the virtual disk is and how many blocks require movement. + /// + /// The CompactVirtualDisk function runs on the virtual disk in the same security context as the caller. + /// + [PInvokeData("VirtDisk.h")] + [DllImport(Lib.VirtDisk, ExactSpelling = true)] + public static extern Win32Error CompactVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, COMPACT_VIRTUAL_DISK_FLAG Flags, [In, Optional] IntPtr Parameters, [In, Optional] IntPtr Overlapped); /// /// Creates a virtual hard disk (VHD) image file, either using default parameters or using an existing VHD or physical disk. @@ -1047,8 +1133,9 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, CharSet = CharSet.Unicode, ExactSpelling = true)] - public static extern Win32Error CreateVirtualDisk(in VIRTUAL_STORAGE_TYPE VirtualStorageType, string Path, VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, PSECURITY_DESCRIPTOR SecurityDescriptor, - CREATE_VIRTUAL_DISK_FLAG Flags, int ProviderSpecificFlags, in CREATE_VIRTUAL_DISK_PARAMETERS Parameters, IntPtr Overlapped, out SafeVIRTUAL_DISK_HANDLE Handle); + public static extern Win32Error CreateVirtualDisk(in VIRTUAL_STORAGE_TYPE VirtualStorageType, string Path, VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + [In, Optional] PSECURITY_DESCRIPTOR SecurityDescriptor, CREATE_VIRTUAL_DISK_FLAG Flags, int ProviderSpecificFlags, + in CREATE_VIRTUAL_DISK_PARAMETERS Parameters, [In, Optional] IntPtr Overlapped, out SafeVIRTUAL_DISK_HANDLE Handle); /// /// Creates a virtual hard disk (VHD) image file, either using default parameters or using an existing VHD or physical disk. @@ -1073,8 +1160,9 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, CharSet = CharSet.Unicode, ExactSpelling = true)] - public static extern Win32Error CreateVirtualDisk(in VIRTUAL_STORAGE_TYPE VirtualStorageType, string Path, VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, PSECURITY_DESCRIPTOR SecurityDescriptor, - CREATE_VIRTUAL_DISK_FLAG Flags, int ProviderSpecificFlags, in CREATE_VIRTUAL_DISK_PARAMETERS Parameters, ref NativeOverlapped Overlapped, out SafeVIRTUAL_DISK_HANDLE Handle); + public static extern Win32Error CreateVirtualDisk(in VIRTUAL_STORAGE_TYPE VirtualStorageType, string Path, VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + [In, Optional] PSECURITY_DESCRIPTOR SecurityDescriptor, CREATE_VIRTUAL_DISK_FLAG Flags, int ProviderSpecificFlags, + in CREATE_VIRTUAL_DISK_PARAMETERS Parameters, in NativeOverlapped Overlapped, out SafeVIRTUAL_DISK_HANDLE Handle); /// Deletes a snapshot from a VHD Set file. /// A handle to the open virtual disk. @@ -1086,7 +1174,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error DeleteSnapshotVhdSet(VIRTUAL_DISK_HANDLE VirtualDiskHandle, in DELETE_SNAPSHOT_VHDSET_PARAMETERS Parameters, DELETE_SNAPSHOT_VHDSET_FLAG Flags); + public static extern Win32Error DeleteSnapshotVhdSet([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in DELETE_SNAPSHOT_VHDSET_PARAMETERS Parameters, DELETE_SNAPSHOT_VHDSET_FLAG Flags); /// Deletes metadata from a virtual disk. /// A handle to the open virtual disk. @@ -1097,7 +1185,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error DeleteVirtualDiskMetadata(VIRTUAL_DISK_HANDLE VirtualDiskHandle, in Guid Item); + public static extern Win32Error DeleteVirtualDiskMetadata([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in Guid Item); /// /// Detaches a virtual hard disk (VHD) or CD or DVD image file (ISO) by locating an appropriate virtual disk provider to accomplish @@ -1116,7 +1204,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error DetachVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, DETACH_VIRTUAL_DISK_FLAG Flags, int ProviderSpecificFlags); + public static extern Win32Error DetachVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, DETACH_VIRTUAL_DISK_FLAG Flags, int ProviderSpecificFlags); /// Enumerates the metadata associated with a virtual disk. /// Handle to an open virtual disk. @@ -1135,7 +1223,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error EnumerateVirtualDiskMetadata(VIRTUAL_DISK_HANDLE VirtualDiskHandle, ref uint NumberOfItems, IntPtr Items); + public static extern Win32Error EnumerateVirtualDiskMetadata([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, ref uint NumberOfItems, + [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Guid[] Items); /// Increases the size of a fixed or dynamic virtual hard disk (VHD). /// @@ -1150,7 +1239,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error ExpandVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, EXPAND_VIRTUAL_DISK_FLAG Flags, in EXPAND_VIRTUAL_DISK_PARAMETERS Parameters, IntPtr Overlapped); + public static extern Win32Error ExpandVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, EXPAND_VIRTUAL_DISK_FLAG Flags, + in EXPAND_VIRTUAL_DISK_PARAMETERS Parameters, [In, Optional] IntPtr Overlapped); /// Increases the size of a fixed or dynamic virtual hard disk (VHD). /// @@ -1165,7 +1255,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error ExpandVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, EXPAND_VIRTUAL_DISK_FLAG Flags, in EXPAND_VIRTUAL_DISK_PARAMETERS Parameters, ref NativeOverlapped Overlapped); + public static extern Win32Error ExpandVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, EXPAND_VIRTUAL_DISK_FLAG Flags, + in EXPAND_VIRTUAL_DISK_PARAMETERS Parameters, in NativeOverlapped Overlapped); /// Get the paths of all attached virtual disks. /// Size of the buffer supplied in . @@ -1176,7 +1267,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error GetAllAttachedVirtualDiskPhysicalPaths(ref uint PathsBufferSizeInBytes, IntPtr PathsBuffer); + public static extern Win32Error GetAllAttachedVirtualDiskPhysicalPaths(ref uint PathsBufferSizeInBytes, [Out] IntPtr PathsBuffer); /// /// Returns the relationships between virtual hard disks (VHDs) or the volumes contained within those disks and their parent disk or volume. @@ -1194,7 +1285,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error GetStorageDependencyInformation(VIRTUAL_DISK_HANDLE ObjectHandle, GET_STORAGE_DEPENDENCY_FLAG Flags, int StorageDependencyInfoSize, IntPtr StorageDependencyInfo, ref int SizeUsed); + public static extern Win32Error GetStorageDependencyInformation([In] VIRTUAL_DISK_HANDLE ObjectHandle, GET_STORAGE_DEPENDENCY_FLAG Flags, + int StorageDependencyInfoSize, [In, Out] IntPtr StorageDependencyInfo, ref int SizeUsed); /// Retrieves information about a virtual hard disk (VHD). /// @@ -1212,7 +1304,27 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error GetVirtualDiskInformation(VIRTUAL_DISK_HANDLE VirtualDiskHandle, ref uint VirtualDiskInfoSize, IntPtr VirtualDiskInfo, out uint SizeUsed); + public static extern Win32Error GetVirtualDiskInformation([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, ref uint VirtualDiskInfoSize, + ref GET_VIRTUAL_DISK_INFO VirtualDiskInfo, out uint SizeUsed); + + /// Retrieves information about a virtual hard disk (VHD). + /// + /// A handle to the open VHD, which must have been opened using the VIRTUAL_DISK_ACCESS_GET_INFO flag. + /// + /// A pointer to a ULONG that contains the size of the VirtualDiskInfo parameter. + /// + /// A pointer to a valid structure. The format of the data returned is dependent on the value + /// passed in the Version member by the caller. + /// + /// A pointer to a ULONG that contains the size used. + /// + /// If the function succeeds, the return value is ERROR_SUCCESS and the Handle parameter contains a valid pointer to the new virtual + /// disk object. If the function fails, the return value is an error code and the value of the Handle parameter is undefined. + /// + [PInvokeData("VirtDisk.h")] + [DllImport(Lib.VirtDisk, ExactSpelling = true)] + public static extern Win32Error GetVirtualDiskInformation([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, ref uint VirtualDiskInfoSize, + [In, Out] IntPtr VirtualDiskInfo, out uint SizeUsed); /// Retrieves the specified metadata from the virtual disk. /// Handle to an open virtual disk. @@ -1230,7 +1342,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error GetVirtualDiskMetadata(VIRTUAL_DISK_HANDLE VirtualDiskHandle, in Guid Item, ref uint MetaDataSize, IntPtr MetaData); + public static extern Win32Error GetVirtualDiskMetadata([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in Guid Item, ref uint MetaDataSize, [Out] IntPtr MetaData); /// Checks the progress of an asynchronous virtual hard disk (VHD) operation. /// A valid handle to a virtual disk with a pending asynchronous operation. @@ -1246,7 +1358,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error GetVirtualDiskOperationProgress(VIRTUAL_DISK_HANDLE VirtualDiskHandle, ref NativeOverlapped Overlapped, out VIRTUAL_DISK_PROGRESS Progress); + public static extern Win32Error GetVirtualDiskOperationProgress([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in NativeOverlapped Overlapped, out VIRTUAL_DISK_PROGRESS Progress); /// Retrieves the path to the physical device object that contains a virtual hard disk (VHD). /// @@ -1260,7 +1372,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, CharSet = CharSet.Unicode, ExactSpelling = true)] - public static extern Win32Error GetVirtualDiskPhysicalPath(VIRTUAL_DISK_HANDLE VirtualDiskHandle, ref int DiskPathSizeInBytes, StringBuilder DiskPath); + public static extern Win32Error GetVirtualDiskPhysicalPath([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, ref int DiskPathSizeInBytes, [Out] StringBuilder DiskPath); /// Merges a child virtual hard disk (VHD) in a differencing chain with parent disks in the chain. /// @@ -1275,7 +1387,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error MergeVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, MERGE_VIRTUAL_DISK_FLAG Flags, in MERGE_VIRTUAL_DISK_PARAMETERS Parameters, IntPtr Overlapped); + public static extern Win32Error MergeVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, MERGE_VIRTUAL_DISK_FLAG Flags, + in MERGE_VIRTUAL_DISK_PARAMETERS Parameters, [In, Optional] IntPtr Overlapped); /// Merges a child virtual hard disk (VHD) in a differencing chain with parent disks in the chain. /// @@ -1290,7 +1403,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error MergeVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, MERGE_VIRTUAL_DISK_FLAG Flags, in MERGE_VIRTUAL_DISK_PARAMETERS Parameters, ref NativeOverlapped Overlapped); + public static extern Win32Error MergeVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, MERGE_VIRTUAL_DISK_FLAG Flags, + in MERGE_VIRTUAL_DISK_PARAMETERS Parameters, in NativeOverlapped Overlapped); /// /// Initiates a mirror operation for a virtual disk. Once the mirroring operation is initiated it will not complete until either @@ -1310,7 +1424,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error MirrorVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, MIRROR_VIRTUAL_DISK_FLAG Flags, in MIRROR_VIRTUAL_DISK_PARAMETERS Parameters, ref NativeOverlapped Overlapped); + public static extern Win32Error MirrorVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, MIRROR_VIRTUAL_DISK_FLAG Flags, + in MIRROR_VIRTUAL_DISK_PARAMETERS Parameters, in NativeOverlapped Overlapped); /// /// Modifies the internal contents of a virtual disk file. Can be used to set the active leaf, or to fix up snapshot entries. @@ -1324,7 +1439,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error ModifyVhdSet(VIRTUAL_DISK_HANDLE VirtualDiskHandle, in MODIFY_VHDSET_PARAMETERS Parameters, MODIFY_VHDSET_FLAG Flags); + public static extern Win32Error ModifyVhdSet([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in MODIFY_VHDSET_PARAMETERS Parameters, MODIFY_VHDSET_FLAG Flags); /// Opens a virtual hard disk (VHD) or CD or DVD image file (ISO) for use. /// A pointer to a valid VIRTUAL_STORAGE_TYPE structure. @@ -1343,8 +1458,9 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true, ThrowOnUnmappableChar = true)] - public static extern Win32Error OpenVirtualDisk(in VIRTUAL_STORAGE_TYPE VirtualStorageType, [MarshalAs(UnmanagedType.LPWStr)] string Path, VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, - OPEN_VIRTUAL_DISK_FLAG Flags, [In] OPEN_VIRTUAL_DISK_PARAMETERS Parameters, out SafeVIRTUAL_DISK_HANDLE Handle); + public static extern Win32Error OpenVirtualDisk(in VIRTUAL_STORAGE_TYPE VirtualStorageType, [MarshalAs(UnmanagedType.LPWStr)] string Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, OPEN_VIRTUAL_DISK_FLAG Flags, + [In, Optional] OPEN_VIRTUAL_DISK_PARAMETERS Parameters, out SafeVIRTUAL_DISK_HANDLE Handle); /// /// Retrieves information about changes to the specified areas of a virtual hard disk (VHD) that are tracked by resilient change @@ -1387,8 +1503,10 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error QueryChangesVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, [MarshalAs(UnmanagedType.LPWStr)] string ChangeTrackingId, ulong ByteOffset, ulong ByteLength, - QUERY_CHANGES_VIRTUAL_DISK_FLAG Flags, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 6)] QUERY_CHANGES_VIRTUAL_DISK_RANGE[] Ranges, ref uint RangeCount, out ulong ProcessedLength); + public static extern Win32Error QueryChangesVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, [MarshalAs(UnmanagedType.LPWStr)] string ChangeTrackingId, + ulong ByteOffset, ulong ByteLength, QUERY_CHANGES_VIRTUAL_DISK_FLAG Flags, + [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 6)] QUERY_CHANGES_VIRTUAL_DISK_RANGE[] Ranges, ref uint RangeCount, + out ulong ProcessedLength); /// Issues an embedded SCSI request directly to a virtual hard disk. /// @@ -1408,7 +1526,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error RawSCSIVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, in RAW_SCSI_VIRTUAL_DISK_PARAMETERS Parameters, RAW_SCSI_VIRTUAL_DISK_FLAG Flags, out RAW_SCSI_VIRTUAL_DISK_RESPONSE Response); + public static extern Win32Error RawSCSIVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in RAW_SCSI_VIRTUAL_DISK_PARAMETERS Parameters, + RAW_SCSI_VIRTUAL_DISK_FLAG Flags, out RAW_SCSI_VIRTUAL_DISK_RESPONSE Response); /// Resizes a virtual disk. /// Handle to an open virtual disk. @@ -1425,7 +1544,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error ResizeVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, RESIZE_VIRTUAL_DISK_FLAG Flags, in RESIZE_VIRTUAL_DISK_PARAMETERS Parameters, IntPtr Overlapped); + public static extern Win32Error ResizeVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, RESIZE_VIRTUAL_DISK_FLAG Flags, + in RESIZE_VIRTUAL_DISK_PARAMETERS Parameters, IntPtr Overlapped); /// Resizes a virtual disk. /// Handle to an open virtual disk. @@ -1442,7 +1562,8 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error ResizeVirtualDisk(VIRTUAL_DISK_HANDLE VirtualDiskHandle, RESIZE_VIRTUAL_DISK_FLAG Flags, in RESIZE_VIRTUAL_DISK_PARAMETERS Parameters, ref NativeOverlapped Overlapped); + public static extern Win32Error ResizeVirtualDisk([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, RESIZE_VIRTUAL_DISK_FLAG Flags, + in RESIZE_VIRTUAL_DISK_PARAMETERS Parameters, in NativeOverlapped Overlapped); /// Sets information about a virtual hard disk (VHD). /// @@ -1455,7 +1576,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error SetVirtualDiskInformation(VIRTUAL_DISK_HANDLE VirtualDiskHandle, in SET_VIRTUAL_DISK_INFO VirtualDiskInfo); + public static extern Win32Error SetVirtualDiskInformation([In, Out] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in SET_VIRTUAL_DISK_INFO VirtualDiskInfo); /// Sets a metadata item for a virtual disk. /// Handle to an open virtual disk. @@ -1468,7 +1589,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows8)] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error SetVirtualDiskMetadata(VIRTUAL_DISK_HANDLE VirtualDiskHandle, in Guid Item, uint MetaDataSize, IntPtr MetaData); + public static extern Win32Error SetVirtualDiskMetadata([In, Out] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in Guid Item, uint MetaDataSize, IntPtr MetaData); /// Creates a snapshot of the current virtual disk for VHD Set files. /// A handle to the open virtual disk. This must be a VHD Set file. @@ -1480,7 +1601,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows10)] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error TakeSnapshotVhdSet(VIRTUAL_DISK_HANDLE VirtualDiskHandle, in TAKE_SNAPSHOT_VHDSET_PARAMETERS Parameters, TAKE_SNAPSHOT_VHDSET_FLAG Flags); + public static extern Win32Error TakeSnapshotVhdSet([In] VIRTUAL_DISK_HANDLE VirtualDiskHandle, in TAKE_SNAPSHOT_VHDSET_PARAMETERS Parameters, TAKE_SNAPSHOT_VHDSET_FLAG Flags); /// Contains snapshot parameters, indicating information about the new snapshot to be applied. [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows10)] @@ -1528,7 +1649,7 @@ namespace Vanara.PInvoke public ATTACH_VIRTUAL_DISK_PARAMETERS_Version1 Version1; /// Gets the default value for this structure. This is currently the only valid value for . - public static ATTACH_VIRTUAL_DISK_PARAMETERS Default => new ATTACH_VIRTUAL_DISK_PARAMETERS { Version = ATTACH_VIRTUAL_DISK_VERSION.ATTACH_VIRTUAL_DISK_VERSION_1 }; + public static ATTACH_VIRTUAL_DISK_PARAMETERS Default => new() { Version = ATTACH_VIRTUAL_DISK_VERSION.ATTACH_VIRTUAL_DISK_VERSION_1 }; /// A structure with the following member. [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows7)] @@ -1555,7 +1676,7 @@ namespace Vanara.PInvoke public COMPACT_VIRTUAL_DISK_PARAMETERS_Version1 Version1; /// Gets the default value for this structure. This is currently the only valid value for . - public static COMPACT_VIRTUAL_DISK_PARAMETERS Default => new COMPACT_VIRTUAL_DISK_PARAMETERS { Version = COMPACT_VIRTUAL_DISK_VERSION.COMPACT_VIRTUAL_DISK_VERSION_1 }; + public static COMPACT_VIRTUAL_DISK_PARAMETERS Default => new() { Version = COMPACT_VIRTUAL_DISK_VERSION.COMPACT_VIRTUAL_DISK_VERSION_1 }; /// A structure with the following member. [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows7)] @@ -2244,48 +2365,74 @@ namespace Vanara.PInvoke /// Contains virtual hard disk (VHD) information for set request. [PInvokeData("VirtDisk.h", MSDNShortId = "dd323686", MinClient = PInvokeClient.Windows7)] - [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, +#if X64 + Pack = 8 +#else + Pack = 4 +#endif + )] public struct SET_VIRTUAL_DISK_INFO { /// /// A SET_VIRTUAL_DISK_INFO_VERSION enumeration that specifies the version of the SET_VIRTUAL_DISK_INFO structure being passed to /// or from the VHD functions. This determines the type of information set. /// - [FieldOffset(0)] public SET_VIRTUAL_DISK_INFO_VERSION Version; + public SET_VIRTUAL_DISK_INFO_VERSION Version; + + private UNION union; /// Path to the parent backing store. [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows7)] - [FieldOffset(8)] public StrPtrUni ParentFilePath; + public StrPtrUni ParentFilePath { get => union.ParentFilePath; set => union.ParentFilePath = value; } /// Unique identifier of the VHD. [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows7)] - [FieldOffset(8)] public Guid UniqueIdentifier; + public Guid UniqueIdentifier { get => union.UniqueIdentifier; set => union.UniqueIdentifier = value; } /// Sets the parent file path and the child depth. [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows8)] - [FieldOffset(8)] public SET_VIRTUAL_DISK_INFO_ParentPathWithDepthInfo ParentPathWithDepthInfo; + public SET_VIRTUAL_DISK_INFO_ParentPathWithDepthInfo ParentPathWithDepthInfo { get => union.ParentPathWithDepthInfo; set => union.ParentPathWithDepthInfo = value; } /// Sets the physical sector size reported by the VHD. [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows8)] - [FieldOffset(8)] public uint VhdPhysicalSectorSize; + public uint VhdPhysicalSectorSize { get => union.VhdPhysicalSectorSize; set => union.VhdPhysicalSectorSize = value; } /// /// The identifier that is uniquely created when a user first creates the virtual disk to attempt to uniquely identify that /// virtual disk. /// [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows81)] - [FieldOffset(8)] public Guid VirtualDiskId; + public Guid VirtualDiskId { get => union.VirtualDiskId; set => union.VirtualDiskId = value; } /// Turns resilient change tracking (RCT) on or off for the VHD. TRUE turns RCT on. FALSE turns RCT off. [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows10)] - [FieldOffset(8), MarshalAs(UnmanagedType.Bool)] public bool ChangeTrackingEnabled; + public bool ChangeTrackingEnabled { get => union.ChangeTrackingEnabled; set => union.ChangeTrackingEnabled = value; } /// /// Sets the parent linkage information that differencing VHDs store. Parent linkage information is metadata used to locate and /// correctly identify the next parent in the virtual disk chain. /// [PInvokeData("VirtDisk.h", MinClient = PInvokeClient.Windows10)] - [FieldOffset(8)] public SET_VIRTUAL_DISK_INFO_ParentLocator ParentLocator; + public SET_VIRTUAL_DISK_INFO_ParentLocator ParentLocator { get => union.ParentLocator; set => union.ParentLocator = value; } + + [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] + private struct UNION + { + [FieldOffset(0)] public StrPtrUni ParentFilePath; + + [FieldOffset(0)] public Guid UniqueIdentifier; + + [FieldOffset(0)] public SET_VIRTUAL_DISK_INFO_ParentPathWithDepthInfo ParentPathWithDepthInfo; + + [FieldOffset(0)] public uint VhdPhysicalSectorSize; + + [FieldOffset(0)] public Guid VirtualDiskId; + + [FieldOffset(0), MarshalAs(UnmanagedType.Bool)] public bool ChangeTrackingEnabled; + + [FieldOffset(0)] public SET_VIRTUAL_DISK_INFO_ParentLocator ParentLocator; + } /// /// Sets the parent linkage information that differencing VHDs store. Parent linkage information is metadata used to locate and @@ -2299,7 +2446,7 @@ namespace Vanara.PInvoke public Guid LinkageId; /// The path of the file for the parent VHD. - public IntPtr ParentFilePath; + public StrPtrUni ParentFilePath; } /// Sets the parent file path and the child depth. @@ -2311,7 +2458,7 @@ namespace Vanara.PInvoke public uint ChildDepth; /// Specifies the depth to the parent from the leaf. The leaf itself is at depth 1. - public IntPtr ParentFilePath; + public StrPtrUni ParentFilePath; } } @@ -2403,14 +2550,14 @@ namespace Vanara.PInvoke [StructLayout(LayoutKind.Sequential)] public struct VIRTUAL_DISK_HANDLE : IHandle { - private IntPtr handle; + private readonly IntPtr handle; /// Initializes a new instance of the struct. /// An object that represents the pre-existing handle to use. public VIRTUAL_DISK_HANDLE(IntPtr preexistingHandle) => handle = preexistingHandle; /// Returns an invalid handle by instantiating a object with . - public static VIRTUAL_DISK_HANDLE NULL => new VIRTUAL_DISK_HANDLE(IntPtr.Zero); + public static VIRTUAL_DISK_HANDLE NULL => new(IntPtr.Zero); /// Gets a value indicating whether this instance is a null handle. public bool IsNull => handle == IntPtr.Zero; @@ -2423,7 +2570,7 @@ namespace Vanara.PInvoke /// Performs an implicit conversion from to . /// The pointer to a handle. /// The result of the conversion. - public static implicit operator VIRTUAL_DISK_HANDLE(IntPtr h) => new VIRTUAL_DISK_HANDLE(h); + public static implicit operator VIRTUAL_DISK_HANDLE(IntPtr h) => new(h); /// Implements the operator !=. /// The first handle. @@ -2438,7 +2585,7 @@ namespace Vanara.PInvoke public static bool operator ==(VIRTUAL_DISK_HANDLE h1, VIRTUAL_DISK_HANDLE h2) => h1.Equals(h2); /// - public override bool Equals(object obj) => obj is VIRTUAL_DISK_HANDLE h ? handle == h.handle : false; + public override bool Equals(object obj) => obj is VIRTUAL_DISK_HANDLE h && handle == h.handle; /// public override int GetHashCode() => handle.GetHashCode(); @@ -2496,7 +2643,7 @@ namespace Vanara.PInvoke /// /// Gets an instance of that represents a Microsoft Virtual Hard Drive or .vhd file. /// - public static VIRTUAL_STORAGE_TYPE VHD => new VIRTUAL_STORAGE_TYPE(VIRTUAL_STORAGE_TYPE_DEVICE_TYPE.VIRTUAL_STORAGE_TYPE_DEVICE_VHD); + public static VIRTUAL_STORAGE_TYPE VHD => new(VIRTUAL_STORAGE_TYPE_DEVICE_TYPE.VIRTUAL_STORAGE_TYPE_DEVICE_VHD); } /// Contains virtual disk open request parameters. @@ -2574,7 +2721,7 @@ namespace Vanara.PInvoke } /// Gets the default value for this structure. This is currently the only valid value for . - public static OPEN_VIRTUAL_DISK_PARAMETERS DefaultV2 => new OPEN_VIRTUAL_DISK_PARAMETERS(false); + public static OPEN_VIRTUAL_DISK_PARAMETERS DefaultV2 => new(false); /// public override string ToString() diff --git a/UnitTests/VirtualDisk/VirtualDiskTests.cs b/UnitTests/VirtualDisk/VirtualDiskTests.cs index c5f54adc..9c228e57 100644 --- a/UnitTests/VirtualDisk/VirtualDiskTests.cs +++ b/UnitTests/VirtualDisk/VirtualDiskTests.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using System; +using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Threading; using System.Threading.Tasks; @@ -12,9 +13,73 @@ namespace Vanara.IO.Tests [TestFixture()] public class VirtualDiskTests { - static readonly string tmpfn = Vanara.PInvoke.Tests.TestCaseSources.TempDirWhack + "TestVHD.vhd"; - static readonly string tmpcfn = Vanara.PInvoke.Tests.TestCaseSources.TempDirWhack + "TestVHD - Diff.vhd"; - static readonly string fn = Vanara.PInvoke.Tests.TestCaseSources.VirtualDisk; + private static readonly string fn = Vanara.PInvoke.Tests.TestCaseSources.VirtualDisk; + private static readonly string tmpcfn = Vanara.PInvoke.Tests.TestCaseSources.TempDirWhack + "TestVHD - Diff.vhd"; + private static readonly string tmpfn = Vanara.PInvoke.Tests.TestCaseSources.TempDirWhack + "TestVHD.vhd"; + + [Test()] + public void CompactTest() + { + using var vhd = VirtualDisk.Open(fn, false); + Assert.That(() => vhd.Compact(), Throws.Nothing); + } + + [Test()] + public async Task CompactTest1() + { + using var vhd = VirtualDisk.Open(fn, false); + var cts = new CancellationTokenSource(); + var rpt = new Reporter(); + var lastVal = 0; + rpt.NewVal += (o, e) => TestContext.WriteLine($"{DateTime.Now:o} NewVal={lastVal = e}"); + await vhd.Compact(cts.Token, rpt); + Assert.That(lastVal, Is.EqualTo(100)); + } + + [Test()] + public void CreateDiffTest() + { + const int sz = 0x03010400; + try + { + using var vhdp = VirtualDisk.Create(tmpfn, sz); + using var vhd = VirtualDisk.CreateDifferencing(tmpcfn, tmpfn); + Assert.That(System.IO.File.Exists(tmpcfn)); + Assert.That(System.IO.File.Exists(tmpfn)); + Assert.That(System.IO.File.Exists(tmpfn)); + Assert.That(vhd.Attached, Is.False); + Assert.That(vhd.BlockSize, Is.EqualTo(0x200000)); + Assert.That(vhd.DiskType, Is.EqualTo(VirtualDisk.DeviceType.Vhd)); + Assert.That(vhd.ResilientChangeTrackingEnabled, Is.False); + //Assert.That(vhd.FragmentationPercentage, Is.Zero); // must be non-differencing + Assert.That(vhd.Identifier, Is.Not.EqualTo(Guid.Empty)); + Assert.That(vhd.Is4kAligned); + Assert.That(vhd.IsLoaded, Is.False); + Assert.That(vhd.IsRemote, Is.False); + Assert.That(vhd.LogicalSectorSize, Is.EqualTo(0x200)); + //Assert.That(vhd.MostRecentId, Is.Null.Or.Empty); + Assert.That(vhd.NewerChanges, Is.False); + Assert.That(vhd.ParentBackingStore, Is.EqualTo(tmpfn)); // must be differencing + Assert.That(vhd.ParentIdentifier, Is.Not.EqualTo(Guid.Empty)); // must be differencing + Assert.That(vhd.ParentPaths, Is.Null); // must be differencing + Assert.That(vhd.ParentTimeStamp, Is.Zero); // must be differencing + //TestContext.WriteLine(vhd.PhysicalPath); // must be attached + Assert.That(vhd.PhysicalSectorSize, Is.EqualTo(0x200)); + Assert.That(vhd.PhysicalSize, Is.LessThan(sz)); + Assert.That(vhd.ProviderSubtype, Is.EqualTo(VirtualDisk.Subtype.Differencing)); + Assert.That(vhd.SectorSize, Is.EqualTo(0x200)); + //Debug.WriteLine(vhd.SmallestSafeVirtualSize); // must have file system + Assert.That(vhd.VendorId, Is.Not.EqualTo(Guid.Empty)); + Assert.That(vhd.VhdPhysicalSectorSize, Is.EqualTo(0x200)); + Assert.That(vhd.VirtualDiskId, Is.Not.EqualTo(Guid.Empty)); + Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); + } + finally + { + System.IO.File.Delete(tmpcfn); + System.IO.File.Delete(tmpfn); + } + } [Test()] public void CreateDynPropTest() @@ -22,38 +87,53 @@ namespace Vanara.IO.Tests const int sz = 0x03010200; try { - using (var vhd = VirtualDisk.Create(tmpfn, sz)) - //using (var pv = new PrivilegedCodeBlock(SystemPrivilege.ManageVolume)) - { - //vhd.Attach(true); - Assert.That(System.IO.File.Exists(tmpfn)); - Assert.That(vhd.Attached, Is.False); - Assert.That(vhd.BlockSize, Is.EqualTo(0x200000)); - Assert.That(vhd.DiskType, Is.EqualTo(VirtualDisk.DeviceType.Vhd)); - Assert.That(vhd.Enabled, Is.False); - Assert.That(vhd.FragmentationPercentage, Is.Zero); - Assert.That(vhd.Identifier, Is.Not.EqualTo(Guid.Empty)); - Assert.That(vhd.Is4kAligned); - Assert.That(vhd.IsLoaded, Is.False); - Assert.That(vhd.IsRemote, Is.False); - Assert.That(vhd.LogicalSectorSize, Is.EqualTo(0x200)); - Assert.That(vhd.MostRecentId, Is.Null.Or.Empty); - Assert.That(vhd.NewerChanges, Is.False); - //Debug.WriteLine(vhd.ParentBackingStore); // must be differencing - //Debug.WriteLine(vhd.ParentIdentifier); // must be differencing - //Debug.WriteLine(vhd.ParentPaths); // must be differencing - //Debug.WriteLine(vhd.ParentTimeStamp); // must be differencing - //Debug.WriteLine(vhd.PhysicalPath); // must be attached - Assert.That(vhd.PhysicalSectorSize, Is.EqualTo(0x200)); - Assert.That(vhd.PhysicalSize, Is.LessThan(sz)); - Assert.That(vhd.ProviderSubtype, Is.EqualTo(VirtualDisk.Subtype.Dynamic)); - Assert.That(vhd.SectorSize, Is.EqualTo(0x200)); - //Debug.WriteLine(vhd.SmallestSafeVirtualSize); // must have file system - Assert.That(vhd.VendorId, Is.Not.EqualTo(Guid.Empty)); - Assert.That(vhd.VhdPhysicalSectorSize, Is.EqualTo(0x200)); - Assert.That(vhd.VirtualDiskId, Is.Not.EqualTo(Guid.Empty)); - Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); - } + using var vhd = VirtualDisk.Create(tmpfn, sz); + //vhd.Attach(true); + Assert.That(System.IO.File.Exists(tmpfn)); + Assert.That(vhd.Attached, Is.False); + Assert.That(vhd.BlockSize, Is.EqualTo(0x200000)); + Assert.That(vhd.DiskType, Is.EqualTo(VirtualDisk.DeviceType.Vhd)); + Assert.That(vhd.ResilientChangeTrackingEnabled, Is.False); + Assert.That(vhd.FragmentationPercentage, Is.Zero); + Assert.That(vhd.Identifier, Is.Not.EqualTo(Guid.Empty)); + Assert.That(vhd.Is4kAligned); + Assert.That(vhd.IsLoaded, Is.False); + Assert.That(vhd.IsRemote, Is.False); + Assert.That(vhd.LogicalSectorSize, Is.EqualTo(0x200)); + Assert.That(vhd.MostRecentId, Is.Null.Or.Empty); + Assert.That(vhd.NewerChanges, Is.False); + //Debug.WriteLine(vhd.ParentBackingStore); // must be differencing + //Debug.WriteLine(vhd.ParentIdentifier); // must be differencing + //Debug.WriteLine(vhd.ParentPaths); // must be differencing + //Debug.WriteLine(vhd.ParentTimeStamp); // must be differencing + //Debug.WriteLine(vhd.PhysicalPath); // must be attached + Assert.That(vhd.PhysicalSectorSize, Is.EqualTo(0x200)); + Assert.That(vhd.PhysicalSize, Is.LessThan(sz)); + Assert.That(vhd.ProviderSubtype, Is.EqualTo(VirtualDisk.Subtype.Dynamic)); + Assert.That(vhd.SectorSize, Is.EqualTo(0x200)); + //Debug.WriteLine(vhd.SmallestSafeVirtualSize); // must have file system + Assert.That(vhd.VendorId, Is.Not.EqualTo(Guid.Empty)); + Assert.That(vhd.VhdPhysicalSectorSize, Is.EqualTo(0x200)); + Assert.That(vhd.VirtualDiskId, Is.Not.EqualTo(Guid.Empty)); + Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); + } + finally + { + System.IO.File.Delete(tmpfn); + } + } + + [Test()] + public void CreateFixedPropTest() + { + const int sz = 0x03010400; + try + { + using var vhd = VirtualDisk.Create(tmpfn, sz, false, null); + Assert.That(System.IO.File.Exists(tmpfn)); + Assert.That(vhd.PhysicalSize, Is.EqualTo(sz + 512)); + Assert.That(vhd.ProviderSubtype, Is.EqualTo(VirtualDisk.Subtype.Fixed)); + Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); } finally { @@ -66,11 +146,9 @@ namespace Vanara.IO.Tests { try { - using (var vhd = VirtualDisk.CreateFromSource(tmpfn, fn)) - { - Assert.That(System.IO.File.Exists(tmpfn)); - vhd.Close(); - } + using var vhd = VirtualDisk.CreateFromSource(tmpfn, fn); + Assert.That(System.IO.File.Exists(tmpfn)); + vhd.Close(); } finally { @@ -78,192 +156,6 @@ namespace Vanara.IO.Tests } } - //[Test()] - //public async Task CreateFromSourceTest1() - //{ - // VirtualDisk vd = null; - // try - // { - // var cts = new CancellationTokenSource(); - // var rpt = new Reporter(); - // var lastVal = 0; - // rpt.NewVal += (o, e) => TestContext.WriteLine($"{DateTime.Now:o} NewVal={lastVal = e}"); - // vd = await VirtualDisk.CreateFromSource(tmpfn, fn, cts.Token, rpt); - // Assert.That(lastVal, Is.EqualTo(100)); - // Assert.That(System.IO.File.Exists(tmpfn)); - // TestContext.WriteLine($"New file sz: {new System.IO.FileInfo(tmpfn).Length}"); - // } - // finally - // { - // vd?.Close(); - // try { System.IO.File.Delete(tmpfn); } catch { } - // } - //} - - [Test()] - public void CreateFixedPropTest() - { - const int sz = 0x03010400; - try - { - using (var vhd = VirtualDisk.Create(tmpfn, sz, false, null)) - { - Assert.That(System.IO.File.Exists(tmpfn)); - Assert.That(vhd.PhysicalSize, Is.EqualTo(sz + 512)); - Assert.That(vhd.ProviderSubtype, Is.EqualTo(VirtualDisk.Subtype.Fixed)); - Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); - } - } - finally - { - System.IO.File.Delete(tmpfn); - } - } - - [Test()] - public void CreateDiffTest() - { - const int sz = 0x03010400; - try - { - using (var vhdp = VirtualDisk.Create(tmpfn, sz)) - using (var vhd = VirtualDisk.CreateDifferencing(tmpcfn, tmpfn)) - { - Assert.That(System.IO.File.Exists(tmpcfn)); - Assert.That(System.IO.File.Exists(tmpfn)); - Assert.That(System.IO.File.Exists(tmpfn)); - Assert.That(vhd.Attached, Is.False); - Assert.That(vhd.BlockSize, Is.EqualTo(0x200000)); - Assert.That(vhd.DiskType, Is.EqualTo(VirtualDisk.DeviceType.Vhd)); - Assert.That(vhd.Enabled, Is.False); - //Assert.That(vhd.FragmentationPercentage, Is.Zero); // must be non-differencing - Assert.That(vhd.Identifier, Is.Not.EqualTo(Guid.Empty)); - Assert.That(vhd.Is4kAligned); - Assert.That(vhd.IsLoaded, Is.False); - Assert.That(vhd.IsRemote, Is.False); - Assert.That(vhd.LogicalSectorSize, Is.EqualTo(0x200)); - //Assert.That(vhd.MostRecentId, Is.Null.Or.Empty); - Assert.That(vhd.NewerChanges, Is.False); - Assert.That(vhd.ParentBackingStore, Is.EqualTo(tmpfn)); // must be differencing - Assert.That(vhd.ParentIdentifier, Is.Not.EqualTo(Guid.Empty)); // must be differencing - Assert.That(vhd.ParentPaths, Is.Null); // must be differencing - Assert.That(vhd.ParentTimeStamp, Is.Zero); // must be differencing - //TestContext.WriteLine(vhd.PhysicalPath); // must be attached - Assert.That(vhd.PhysicalSectorSize, Is.EqualTo(0x200)); - Assert.That(vhd.PhysicalSize, Is.LessThan(sz)); - Assert.That(vhd.ProviderSubtype, Is.EqualTo(VirtualDisk.Subtype.Differencing)); - Assert.That(vhd.SectorSize, Is.EqualTo(0x200)); - //Debug.WriteLine(vhd.SmallestSafeVirtualSize); // must have file system - Assert.That(vhd.VendorId, Is.Not.EqualTo(Guid.Empty)); - Assert.That(vhd.VhdPhysicalSectorSize, Is.EqualTo(0x200)); - Assert.That(vhd.VirtualDiskId, Is.Not.EqualTo(Guid.Empty)); - Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); - } - } - finally - { - System.IO.File.Delete(tmpcfn); - System.IO.File.Delete(tmpfn); - } - } - - [Test()] - public void OpenAttachRawTest() - { - try - { - var param = new OPEN_VIRTUAL_DISK_PARAMETERS(false); - using (var vhd = VirtualDisk.Open(fn, OPEN_VIRTUAL_DISK_FLAG.OPEN_VIRTUAL_DISK_FLAG_NONE, param, VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_NONE)) - { - Assert.That(vhd.Attached, Is.False); - var flags = ATTACH_VIRTUAL_DISK_FLAG.ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY; - var aparam = ATTACH_VIRTUAL_DISK_PARAMETERS.Default; - var sd = ConvertStringSecurityDescriptorToSecurityDescriptor("O:BAG:BAD:(A;;GA;;;WD)"); - vhd.Attach(flags, ref aparam, sd); - Assert.That(vhd.Attached, Is.True); - vhd.Detach(); - Assert.That(vhd.Attached, Is.False); - } - } - finally - { - } - } - - private static FileSecurity GetFileSecurity(string sddl) - { - var sd = new FileSecurity(); - sd.SetSecurityDescriptorSddlForm(sddl); - return sd; - } - - private static FileSecurity GetWorldFullFileSecurity() => GetFileSecurity("O:BAG:BAD:(A;;GA;;;WD)"); - - [Test()] - public void OpenAttachTest() - { - try - { - using (var vhd = VirtualDisk.Open(fn, true)) - { - Assert.That(vhd.Attached, Is.False); - vhd.Attach(true, true, false, GetWorldFullFileSecurity()); - Assert.That(vhd.Attached, Is.True); - TestContext.WriteLine(vhd.PhysicalPath); - Assert.That(vhd.PhysicalPath, Is.Not.Null); // must be attached - vhd.Detach(); - Assert.That(vhd.Attached, Is.False); - vhd.Attach(); - Assert.That(vhd.Attached, Is.True); - vhd.Close(); - Assert.That(vhd.Attached, Is.False); - } - } - finally - { - } - } - - [Test] - public void GetSetMetadataTest() - { - const int sz = 0x03010200; - var lfn = tmpfn + "x"; - try - { - using (var vhd = VirtualDisk.Create(lfn, sz)) - { - var count = 0; - Assert.That(() => count = vhd.Metadata.Count, Throws.Nothing); - - // Try get and set - var guid = Guid.NewGuid(); - Assert.That(() => vhd.Metadata.Add(guid, new SafeCoTaskMemHandle("Testing")), Throws.Nothing); - Assert.That(vhd.Metadata.Count, Is.EqualTo(count + 1)); - Assert.That(vhd.Metadata.ContainsKey(Guid.NewGuid()), Is.False); - Assert.That(vhd.Metadata.TryGetValue(guid, out var mem), Is.True); - Assert.That(mem.ToString(-1), Is.EqualTo("Testing")); - - // Try enumerate and get - foreach (var mkv in vhd.Metadata) - { - Assert.That(mkv.Key, Is.Not.EqualTo(Guid.Empty)); - Assert.That(mkv.Value.Size, Is.Not.Zero); - TestContext.WriteLine($"{mkv.Key}={mkv.Value.Size}b:{mkv.Value.ToString(-1)}"); - } - - // Try remove - Assert.That(vhd.Metadata.Remove(guid), Is.True); - Assert.That(vhd.Metadata.TryGetValue(guid, out mem), Is.False); - Assert.That(vhd.Metadata.Count, Is.EqualTo(count)); - } - } - finally - { - System.IO.File.Delete(lfn); - } - } - //[Test()] public void DetachTest() { @@ -286,6 +178,24 @@ namespace Vanara.IO.Tests } } + [Test()] + public void ExpandTest() + { + const int sz = 0x810400; + try + { + using var vhd = VirtualDisk.Create(tmpfn, sz, true, null); + Assert.That(System.IO.File.Exists(tmpfn)); + Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); + vhd.Expand(sz * 2); + Assert.That(vhd.VirtualSize, Is.EqualTo(sz * 2)); + } + finally + { + System.IO.File.Delete(tmpfn); + } + } + [Test()] public void GetAllAttachedVirtualDiskPathsTest() { @@ -298,31 +208,77 @@ namespace Vanara.IO.Tests Assert.That(VirtualDisk.GetAllAttachedVirtualDiskPaths(), Is.Empty); } - [Test()] - public void CompactTest() + [Test] + public void GetSetMetadataTest() { - using (var vhd = VirtualDisk.Open(fn, false)) + const int sz = 0x03010200; + var lfn = tmpfn + "x"; + try { - Assert.That(() => vhd.Compact(), Throws.Nothing); + using var vhd = VirtualDisk.Create(lfn, sz); + var count = 0; + Assert.That(() => count = vhd.Metadata.Count, Throws.Nothing); + + // Try get and set + var guid = Guid.NewGuid(); + Assert.That(() => vhd.Metadata.Add(guid, new SafeCoTaskMemHandle("Testing")), Throws.Nothing); + Assert.That(vhd.Metadata.Count, Is.EqualTo(count + 1)); + Assert.That(vhd.Metadata.ContainsKey(Guid.NewGuid()), Is.False); + Assert.That(vhd.Metadata.TryGetValue(guid, out SafeCoTaskMemHandle mem), Is.True); + Assert.That(mem.ToString(-1), Is.EqualTo("Testing")); + + // Try enumerate and get + foreach (System.Collections.Generic.KeyValuePair mkv in vhd.Metadata) + { + Assert.That(mkv.Key, Is.Not.EqualTo(Guid.Empty)); + Assert.That(mkv.Value.Size, Is.Not.Zero); + TestContext.WriteLine($"{mkv.Key}={mkv.Value.Size}b:{mkv.Value.ToString(-1)}"); + } + + // Try remove + Assert.That(vhd.Metadata.Remove(guid), Is.True); + Assert.That(vhd.Metadata.TryGetValue(guid, out mem), Is.False); + Assert.That(vhd.Metadata.Count, Is.EqualTo(count)); + } + finally + { + System.IO.File.Delete(lfn); + } + } + + //[Test] + public void GetSetPropTest() + { + const int sz = 0x03010200; + var lfn = tmpfn + "x"; + try + { + using var vhd = VirtualDisk.Create(lfn, sz); + var b = vhd.ResilientChangeTrackingEnabled; + Assert.That(() => vhd.ResilientChangeTrackingEnabled = !b, Throws.Nothing); + Assert.AreEqual(!b, vhd.ResilientChangeTrackingEnabled); + } + finally + { + System.IO.File.Delete(lfn); } } [Test()] - public void ExpandTest() + public void MergeTest() { - const int sz = 0x810400; + const int sz = 0x03010400; try { - using (var vhd = VirtualDisk.Create(tmpfn, sz, true, null)) - { + using (var vhdp = VirtualDisk.Create(tmpfn, sz)) Assert.That(System.IO.File.Exists(tmpfn)); - Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); - vhd.Expand(sz * 2); - Assert.That(vhd.VirtualSize, Is.EqualTo(sz * 2)); - } + using var vhd = VirtualDisk.CreateDifferencing(tmpcfn, tmpfn); + Assert.That(System.IO.File.Exists(tmpcfn)); + vhd.Merge(1, 2); } finally { + System.IO.File.Delete(tmpcfn); System.IO.File.Delete(tmpfn); } } @@ -335,11 +291,9 @@ namespace Vanara.IO.Tests { using (var vhdp = VirtualDisk.Create(tmpfn, sz)) Assert.That(System.IO.File.Exists(tmpfn)); - using (var vhd = VirtualDisk.CreateDifferencing(tmpcfn, tmpfn)) - { - Assert.That(System.IO.File.Exists(tmpcfn)); - vhd.MergeWithParent(); - } + using var vhd = VirtualDisk.CreateDifferencing(tmpcfn, tmpfn); + Assert.That(System.IO.File.Exists(tmpcfn)); + vhd.MergeWithParent(); } finally { @@ -348,24 +302,68 @@ namespace Vanara.IO.Tests } } + //[Test()] + //public async Task CreateFromSourceTest1() + //{ + // VirtualDisk vd = null; + // try + // { + // var cts = new CancellationTokenSource(); + // var rpt = new Reporter(); + // var lastVal = 0; + // rpt.NewVal += (o, e) => TestContext.WriteLine($"{DateTime.Now:o} NewVal={lastVal = e}"); + // vd = await VirtualDisk.CreateFromSource(tmpfn, fn, cts.Token, rpt); + // Assert.That(lastVal, Is.EqualTo(100)); + // Assert.That(System.IO.File.Exists(tmpfn)); + // TestContext.WriteLine($"New file sz: {new System.IO.FileInfo(tmpfn).Length}"); + // } + // finally + // { + // vd?.Close(); + // try { System.IO.File.Delete(tmpfn); } catch { } + // } + //} [Test()] - public void MergeTest() + public void OpenAttachRawTest() { - const int sz = 0x03010400; try { - using (var vhdp = VirtualDisk.Create(tmpfn, sz)) - Assert.That(System.IO.File.Exists(tmpfn)); - using (var vhd = VirtualDisk.CreateDifferencing(tmpcfn, tmpfn)) - { - Assert.That(System.IO.File.Exists(tmpcfn)); - vhd.Merge(1, 2); - } + var param = new OPEN_VIRTUAL_DISK_PARAMETERS(false); + using var vhd = VirtualDisk.Open(fn, OPEN_VIRTUAL_DISK_FLAG.OPEN_VIRTUAL_DISK_FLAG_NONE, param, VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_NONE); + Assert.That(vhd.Attached, Is.False); + ATTACH_VIRTUAL_DISK_FLAG flags = ATTACH_VIRTUAL_DISK_FLAG.ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY; + ATTACH_VIRTUAL_DISK_PARAMETERS aparam = ATTACH_VIRTUAL_DISK_PARAMETERS.Default; + SafePSECURITY_DESCRIPTOR sd = ConvertStringSecurityDescriptorToSecurityDescriptor("O:BAG:BAD:(A;;GA;;;WD)"); + vhd.Attach(flags, ref aparam, sd); + Assert.That(vhd.Attached, Is.True); + vhd.Detach(); + Assert.That(vhd.Attached, Is.False); + } + finally + { + } + } + + [Test()] + public void OpenAttachTest() + { + try + { + using var vhd = VirtualDisk.Open(fn, true); + Assert.That(vhd.Attached, Is.False); + vhd.Attach(true, true, false, GetWorldFullFileSecurity()); + Assert.That(vhd.Attached, Is.True); + TestContext.WriteLine(vhd.PhysicalPath); + Assert.That(vhd.PhysicalPath, Is.Not.Null); // must be attached + vhd.Detach(); + Assert.That(vhd.Attached, Is.False); + vhd.Attach(); + Assert.That(vhd.Attached, Is.True); + vhd.Close(); + Assert.That(vhd.Attached, Is.False); } finally { - System.IO.File.Delete(tmpcfn); - System.IO.File.Delete(tmpfn); } } @@ -375,13 +373,11 @@ namespace Vanara.IO.Tests const int sz = 0x810400; try { - using (var vhd = VirtualDisk.Create(tmpfn, sz, true, null)) - { - Assert.That(System.IO.File.Exists(tmpfn)); - Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); - vhd.Resize(sz * 2); - Assert.That(vhd.VirtualSize, Is.EqualTo(sz * 2)); - } + using var vhd = VirtualDisk.Create(tmpfn, sz, true, null); + Assert.That(System.IO.File.Exists(tmpfn)); + Assert.That(vhd.VirtualSize, Is.EqualTo(sz)); + vhd.Resize(sz * 2); + Assert.That(vhd.VirtualSize, Is.EqualTo(sz * 2)); } finally { @@ -395,12 +391,10 @@ namespace Vanara.IO.Tests const int sz = 0x810400; try { - using (var vhd = VirtualDisk.Create(tmpfn, sz * 2, true, null)) - { - Assert.That(System.IO.File.Exists(tmpfn)); - Assert.That(vhd.VirtualSize, Is.EqualTo(sz * 2)); - Assert.That(() => vhd.UnsafeResize(sz), Throws.Exception); - } + using var vhd = VirtualDisk.Create(tmpfn, sz * 2, true, null); + Assert.That(System.IO.File.Exists(tmpfn)); + Assert.That(vhd.VirtualSize, Is.EqualTo(sz * 2)); + Assert.That(() => vhd.UnsafeResize(sz), Throws.Exception); } finally { @@ -408,28 +402,164 @@ namespace Vanara.IO.Tests } } - [Test()] - public async Task CompactTest1() + [Test] + public void ParentChildTest() { - using (var vhd = VirtualDisk.Open(fn, false)) + const uint PhysicalSectorSize = 4096; + const int sz = 0x03010400; + + var tmpfn = VirtualDiskTests.tmpfn + "x"; + var tmpcfn = VirtualDiskTests.tmpcfn + "x"; + + try { - var cts = new CancellationTokenSource(); - var rpt = new Reporter(); - var lastVal = 0; - rpt.NewVal += (o, e) => TestContext.WriteLine($"{DateTime.Now:o} NewVal={lastVal = e}"); - await vhd.Compact(cts.Token, rpt); - Assert.That(lastVal, Is.EqualTo(100)); + VirtualDisk.Create(tmpfn, sz).Dispose(); + VirtualDisk.CreateDifferencing(tmpcfn, tmpfn).Dispose(); + + // + // Specify UNKNOWN for both device and vendor so the system will use the + // file extension to determine the correct VHD format. + // + + VIRTUAL_STORAGE_TYPE storageType = new(); + storageType.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_TYPE.VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN; + storageType.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN; + + // + // Open the parent so it's properties can be queried. + // + // A "GetInfoOnly" handle is a handle that can only be used to query properties or + // metadata. + // + + OPEN_VIRTUAL_DISK_PARAMETERS openParameters = new(false, true); + + // + // Open the VHD/VHDX. + // + // VIRTUAL_DISK_ACCESS_NONE is the only acceptable access mask for V2 handle opens. + // OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS indicates the parent chain should not be opened. + // + + OpenVirtualDisk(storageType, tmpfn, + VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_NONE, + OPEN_VIRTUAL_DISK_FLAG.OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS, + openParameters, out var parentVhdHandle).ThrowIfFailed(); + + using (parentVhdHandle) + { + // + // Get the disk ID of the parent. + // + + GET_VIRTUAL_DISK_INFO parentDiskInfo = new(); + var diskInfoSize = (uint)Marshal.SizeOf(typeof(GET_VIRTUAL_DISK_INFO)); + parentDiskInfo.Version = GET_VIRTUAL_DISK_INFO_VERSION.GET_VIRTUAL_DISK_INFO_IDENTIFIER; + + GetVirtualDiskInformation(parentVhdHandle, ref diskInfoSize, ref parentDiskInfo, out _).ThrowIfFailed(); + + // + // Open the VHD/VHDX so it's properties can be queried. + // + // VIRTUAL_DISK_ACCESS_NONE is the only acceptable access mask for V2 handle opens. + // OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS indicates the parent chain should not be opened. + // + + OpenVirtualDisk(storageType, tmpcfn, + VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_NONE, + OPEN_VIRTUAL_DISK_FLAG.OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS, + openParameters, out var childVhdHandle).ThrowIfFailed(); + + using (childVhdHandle) + { + // + // Get the disk ID expected for the parent. + // + + GET_VIRTUAL_DISK_INFO childDiskInfo = new(); + childDiskInfo.Version = GET_VIRTUAL_DISK_INFO_VERSION.GET_VIRTUAL_DISK_INFO_PARENT_IDENTIFIER; + diskInfoSize = (uint)Marshal.SizeOf(childDiskInfo); + + GetVirtualDiskInformation(childVhdHandle, ref diskInfoSize, ref childDiskInfo, out _).ThrowIfFailed(); + + // + // Verify the disk IDs match. + // + + if (parentDiskInfo.Identifier != childDiskInfo.ParentIdentifier) + Assert.Fail("Disk ID mismatch"); + + // + // Reset the parent locators in the child. + // + } + + // + // This cannot be a "GetInfoOnly" handle because the intent is to alter the properties of the + // VHD/VHDX. + // + // VIRTUAL_DISK_ACCESS_NONE is the only acceptable access mask for V2 handle opens. + // OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS indicates the parent chain should not be opened. + // + + openParameters.Version2.GetInfoOnly = false; + + OpenVirtualDisk(storageType, tmpcfn, + VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_NONE, + OPEN_VIRTUAL_DISK_FLAG.OPEN_VIRTUAL_DISK_FLAG_NO_PARENTS, + openParameters, out childVhdHandle).ThrowIfFailed(); + + using (childVhdHandle) + { + // + // Update the path to the parent. + // + + using var pParentPath = new SafeCoTaskMemString(tmpfn); + SET_VIRTUAL_DISK_INFO setInfo = new(); + setInfo.Version = SET_VIRTUAL_DISK_INFO_VERSION.SET_VIRTUAL_DISK_INFO_PARENT_PATH_WITH_DEPTH; + setInfo.ParentPathWithDepthInfo = new SET_VIRTUAL_DISK_INFO.SET_VIRTUAL_DISK_INFO_ParentPathWithDepthInfo + { + ChildDepth = 1, + ParentFilePath = (IntPtr)pParentPath + }; + + SetVirtualDiskInformation(childVhdHandle, setInfo).ThrowIfFailed(); + + // + // Set the physical sector size. + // + // This operation will only succeed on VHDX. + // + + setInfo.Version = SET_VIRTUAL_DISK_INFO_VERSION.SET_VIRTUAL_DISK_INFO_PHYSICAL_SECTOR_SIZE; + setInfo.VhdPhysicalSectorSize = PhysicalSectorSize; + + SetVirtualDiskInformation(childVhdHandle, setInfo).ThrowIfFailed(); + } + } + } + finally + { + System.IO.File.Delete(tmpcfn); + System.IO.File.Delete(tmpfn); } } + private static FileSecurity GetFileSecurity(string sddl) + { + var sd = new FileSecurity(); + sd.SetSecurityDescriptorSddlForm(sddl); + return sd; + } + + private static FileSecurity GetWorldFullFileSecurity() => GetFileSecurity("O:BAG:BAD:(A;;GA;;;WD)"); + private class Reporter : IProgress { public event EventHandler NewVal; - public void Report(int value) - { - NewVal?.Invoke(this, value); - } + public void Report(int value) => NewVal?.Invoke(this, value); } } } \ No newline at end of file diff --git a/VirtualDisk/VirtualDisk.cs b/VirtualDisk/VirtualDisk.cs index 2f91ec3b..66b4dca4 100644 --- a/VirtualDisk/VirtualDisk.cs +++ b/VirtualDisk/VirtualDisk.cs @@ -34,7 +34,7 @@ namespace Vanara.IO ver = version; } - private delegate Win32Error RunAsyncMethod(ref NativeOverlapped overlap); + private delegate Win32Error RunAsyncMethod(in NativeOverlapped overlap); /// Represents the format of the virtual disk. public enum DeviceType : uint @@ -83,9 +83,6 @@ namespace Vanara.IO /// The device identifier. public DeviceType DiskType => (DeviceType)GetInformation(GET_VIRTUAL_DISK_INFO_VERSION.GET_VIRTUAL_DISK_INFO_VIRTUAL_STORAGE_TYPE); - /// Whether RCT is turned on. TRUE if RCT is turned on; otherwise FALSE. - public bool Enabled => GetInformation(GET_VIRTUAL_DISK_INFO_VERSION.GET_VIRTUAL_DISK_INFO_CHANGE_TRACKING_STATE); - /// The fragmentation level of the virtual disk. public uint FragmentationPercentage => GetInformation(GET_VIRTUAL_DISK_INFO_VERSION.GET_VIRTUAL_DISK_INFO_FRAGMENTATION); @@ -117,7 +114,7 @@ namespace Vanara.IO /// Gets the metadata associated with this virtual disk. Currently on VHDX files support metadata. /// The metadata. - public VirtualDiskMetadata Metadata => metadata ?? (metadata = new VirtualDiskMetadata(this)); + public VirtualDiskMetadata Metadata => metadata ??= new VirtualDiskMetadata(this); /// /// The change tracking identifier for the change that identifies the state of the virtual disk that you want to use as the basis of @@ -196,6 +193,17 @@ namespace Vanara.IO /// Provider-specific subtype. public Subtype ProviderSubtype => (Subtype)GetInformation(GET_VIRTUAL_DISK_INFO_VERSION.GET_VIRTUAL_DISK_INFO_PROVIDER_SUBTYPE); + /// Whether RCT is turned on. TRUE if RCT is turned on; otherwise FALSE. + public bool ResilientChangeTrackingEnabled + { + get => GetInformation(GET_VIRTUAL_DISK_INFO_VERSION.GET_VIRTUAL_DISK_INFO_CHANGE_TRACKING_STATE); + set + { + var si = new SET_VIRTUAL_DISK_INFO { Version = SET_VIRTUAL_DISK_INFO_VERSION.SET_VIRTUAL_DISK_INFO_CHANGE_TRACKING_STATE, ChangeTrackingEnabled = value }; + SetVirtualDiskInformation(Handle, si).ThrowIfFailed(); + } + } + /// Sector size of the VHD, in bytes. public uint SectorSize => GetInformation(GET_VIRTUAL_DISK_INFO_VERSION.GET_VIRTUAL_DISK_INFO_SIZE, 20); @@ -275,8 +283,8 @@ namespace Vanara.IO /// size of the virtual disk. /// /// - /// Internal size of the virtual disk object blocks, in bytes. For VHDX this must be a multiple of 1 MB between 1 and 256 MB. For VHD - /// 1 this must be set to one of the following values: 0 (default), 0x80000 (512K), or 0x200000 (2MB) + /// Internal size of the virtual disk object blocks, in bytes. For VHDX this must be a multiple of 1 MB between 1 and 256 MB. For + /// VHD 1 this must be set to one of the following values: 0 (default), 0x80000 (512K), or 0x200000 (2MB) /// /// /// Internal size of the virtual disk object sectors. For VHDX must be set to 512 (0x200) or 4096 (0x1000). For VHD 1 must be set to 512. @@ -358,8 +366,8 @@ namespace Vanara.IO } /// - /// Detach a virtual disk that was previously attached with the ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME flag or calling - /// and setting autoDetach to false. + /// Detach a virtual disk that was previously attached with the ATTACH_VIRTUAL_DISK_FLAG_PERMANENT_LIFETIME flag or calling and setting autoDetach to false. /// /// A valid path to the virtual disk image to detach. public static void Detach(string path) @@ -498,8 +506,8 @@ namespace Vanara.IO /// /// true if operation completed without error or cancellation; false otherwise. public async Task Compact(CancellationToken cancellationToken, IProgress progress) => - await RunAsync(cancellationToken, progress, Handle, (ref NativeOverlapped vhdOverlap) => - CompactVirtualDisk(Handle, COMPACT_VIRTUAL_DISK_FLAG.COMPACT_VIRTUAL_DISK_FLAG_NONE, COMPACT_VIRTUAL_DISK_PARAMETERS.Default, ref vhdOverlap)); + await RunAsync(cancellationToken, progress, Handle, (in NativeOverlapped vhdOverlap) => + CompactVirtualDisk(Handle, COMPACT_VIRTUAL_DISK_FLAG.COMPACT_VIRTUAL_DISK_FLAG_NONE, COMPACT_VIRTUAL_DISK_PARAMETERS.Default, vhdOverlap)); /// /// Detaches a virtual hard disk (VHD) or CD or DVD image file (ISO) by locating an appropriate virtual disk provider to accomplish @@ -523,7 +531,7 @@ namespace Vanara.IO /// New size, in bytes, for the expansion request. public void Expand(ulong newSize) { - if (ver < OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_2) + if (ver < OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_2) throw new NotSupportedException(@"Expansion is only available to virtual disks opened under version 2 or higher."); ExpandVirtualDisk(Handle, EXPAND_VIRTUAL_DISK_FLAG.EXPAND_VIRTUAL_DISK_FLAG_NONE, new EXPAND_VIRTUAL_DISK_PARAMETERS(newSize), IntPtr.Zero).ThrowIfFailed(); } @@ -538,12 +546,12 @@ namespace Vanara.IO /// disable progress reporting. /// /// true if operation completed without error or cancellation; false otherwise. - public async Task Expand(ulong newSize, CancellationToken cancellationToken, IProgress progress) => - await RunAsync(cancellationToken, progress, Handle, (ref NativeOverlapped vhdOverlap) => + public async Task Expand(ulong newSize, CancellationToken cancellationToken, IProgress progress) => + await RunAsync(cancellationToken, progress, Handle, (in NativeOverlapped vhdOverlap) => { if (ver < OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_2) throw new NotSupportedException(@"Expansion is only available to virtual disks opened under version 2 or higher."); var param = new EXPAND_VIRTUAL_DISK_PARAMETERS(newSize); - return ExpandVirtualDisk(Handle, EXPAND_VIRTUAL_DISK_FLAG.EXPAND_VIRTUAL_DISK_FLAG_NONE, param, ref vhdOverlap); + return ExpandVirtualDisk(Handle, EXPAND_VIRTUAL_DISK_FLAG.EXPAND_VIRTUAL_DISK_FLAG_NONE, param, vhdOverlap); } ); @@ -570,7 +578,7 @@ namespace Vanara.IO /// public void Resize(ulong newSize) { - if (ver < OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_2) + if (ver < OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_2) throw new NotSupportedException(@"Expansion is only available to virtual disks opened under version 2 or higher."); var flags = newSize == 0 ? RESIZE_VIRTUAL_DISK_FLAG.RESIZE_VIRTUAL_DISK_FLAG_RESIZE_TO_SMALLEST_SAFE_VIRTUAL_SIZE : RESIZE_VIRTUAL_DISK_FLAG.RESIZE_VIRTUAL_DISK_FLAG_NONE; var param = new RESIZE_VIRTUAL_DISK_PARAMETERS(newSize); @@ -588,18 +596,18 @@ namespace Vanara.IO /// /// true if operation completed without error or cancellation; false otherwise. public async Task Resize(ulong newSize, CancellationToken cancellationToken, IProgress progress) => - await RunAsync(cancellationToken, progress, Handle, (ref NativeOverlapped vhdOverlap) => + await RunAsync(cancellationToken, progress, Handle, (in NativeOverlapped vhdOverlap) => { if (ver < OPEN_VIRTUAL_DISK_VERSION.OPEN_VIRTUAL_DISK_VERSION_2) throw new NotSupportedException(@"Expansion is only available to virtual disks opened under version 2 or higher."); var param = new RESIZE_VIRTUAL_DISK_PARAMETERS(newSize); - return ResizeVirtualDisk(Handle, RESIZE_VIRTUAL_DISK_FLAG.RESIZE_VIRTUAL_DISK_FLAG_NONE, param, ref vhdOverlap); + return ResizeVirtualDisk(Handle, RESIZE_VIRTUAL_DISK_FLAG.RESIZE_VIRTUAL_DISK_FLAG_NONE, param, vhdOverlap); } ); /// - /// Resizes a virtual disk without checking the virtual disk's partition table to ensure that this truncation is safe. - /// This method can cause unrecoverable data loss; use with care. + /// Resizes a virtual disk without checking the virtual disk's partition table to ensure that this truncation is safe. This method can cause unrecoverable data loss; use with care. /// /// New size, in bytes, for the expansion request. public void UnsafeResize(ulong newSize) @@ -634,7 +642,7 @@ namespace Vanara.IO var param = new CREATE_VIRTUAL_DISK_PARAMETERS(0, IsPreWin8 ? 1U : 2U); var h = IntPtr.Zero; - var b = await RunAsync(cancellationToken, progress, VIRTUAL_DISK_HANDLE.NULL, (ref NativeOverlapped vhdOverlap) => + var b = await RunAsync(cancellationToken, progress, VIRTUAL_DISK_HANDLE.NULL, (in NativeOverlapped vhdOverlap) => { var sp = new SafeCoTaskMemString(sourcePath); var stType = new VIRTUAL_STORAGE_TYPE(); @@ -644,7 +652,7 @@ namespace Vanara.IO else param.Version2.SourcePath = sp; var flags = CREATE_VIRTUAL_DISK_FLAG.CREATE_VIRTUAL_DISK_FLAG_NONE; - var err = CreateVirtualDisk(stType, path, mask, PSECURITY_DESCRIPTOR.NULL, flags, 0, param, ref vhdOverlap, out var hVhd); + var err = CreateVirtualDisk(stType, path, mask, PSECURITY_DESCRIPTOR.NULL, flags, 0, param, vhdOverlap, out var hVhd); if (err.Succeeded) { h = hVhd.DangerousGetHandle(); @@ -670,7 +678,7 @@ namespace Vanara.IO progress?.Report(0); while (true) { - var perr = GetVirtualDiskOperationProgress(phVhd, ref reset, out var prog); + var perr = GetVirtualDiskOperationProgress(phVhd, reset, out var prog); perr.ThrowIfFailed(); if (cancellationToken.IsCancellationRequested) return false; switch (prog.OperationStatus) @@ -700,7 +708,7 @@ namespace Vanara.IO { var vhdOverlapEvent = new ManualResetEvent(false); var vhdOverlap = new NativeOverlapped { EventHandle = vhdOverlapEvent.SafeWaitHandle.DangerousGetHandle() }; - var err = method(ref vhdOverlap); + var err = method(in vhdOverlap); if (err != Win32Error.ERROR_IO_PENDING) err.ThrowIfFailed(); return await GetProgress(hVhd, vhdOverlap, cancellationToken, progress); } @@ -819,19 +827,29 @@ namespace Vanara.IO { get { - if (!supported) return new Guid[0]; - if (parent.Handle.IsClosed) throw new InvalidOperationException("Virtual disk not valid."); - uint count = 0; - var err = EnumerateVirtualDiskMetadata(parent.Handle, ref count, IntPtr.Zero); - if (err != Win32Error.ERROR_MORE_DATA && err != Win32Error.ERROR_INSUFFICIENT_BUFFER) err.ThrowIfFailed(); - if (count == 0) return new Guid[0]; - var mem = new SafeCoTaskMemHandle(Marshal.SizeOf(typeof(Guid)) * (int)count); - EnumerateVirtualDiskMetadata(parent.Handle, ref count, mem).ThrowIfFailed(); - return mem.ToArray((int)count); + var ret = new Guid[0]; + if (supported) + { + if (parent.Handle.IsClosed) + throw new InvalidOperationException("Virtual disk not valid."); + uint count = 0; + var err = EnumerateVirtualDiskMetadata(parent.Handle, ref count, null); + if (err != Win32Error.ERROR_MORE_DATA && err != Win32Error.ERROR_INSUFFICIENT_BUFFER) + err.ThrowIfFailed(); + if (count != 0) + { + ret = new Guid[count]; + EnumerateVirtualDiskMetadata(parent.Handle, ref count, ret).ThrowIfFailed(); + return ret; + } + } + return ret; } } - /// Gets an containing the values in the . + /// + /// Gets an containing the values in the . + /// public ICollection Values => Keys.Select(k => this[k]).ToList(); /// Gets the number of elements contained in the . @@ -880,8 +898,8 @@ namespace Vanara.IO /// at a particular index. /// /// - /// The one-dimensional that is the destination of the elements copied from - /// . The must have zero-based indexing. + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) @@ -946,7 +964,8 @@ namespace Vanara.IO /// The object to remove from the . /// /// true if was successfully removed from the ; - /// otherwise, false. This method also returns false if is not found in the original . + /// otherwise, false. This method also returns false if is not found in the original . /// bool ICollection>.Remove(KeyValuePair item) => Remove(item.Key);