From 0ce814c2ca8040e9aefa6601f1b48296376879c8 Mon Sep 17 00:00:00 2001 From: David Hall Date: Fri, 22 Dec 2017 09:09:04 -0700 Subject: [PATCH] Virtual disk metadata fully working and tested --- PInvoke/VirtDisk/VirtDisk.cs | 2 +- System/VirtualDisk.cs | 346 ++++++++++++++++++++++++++--------- UnitTests/System/VirtualDiskTests.cs | 35 ++++ 3 files changed, 294 insertions(+), 89 deletions(-) diff --git a/PInvoke/VirtDisk/VirtDisk.cs b/PInvoke/VirtDisk/VirtDisk.cs index 13c77f72..2ed1c7c4 100644 --- a/PInvoke/VirtDisk/VirtDisk.cs +++ b/PInvoke/VirtDisk/VirtDisk.cs @@ -1014,7 +1014,7 @@ namespace Vanara.PInvoke /// [PInvokeData("VirtDisk.h")] [DllImport(Lib.VirtDisk, ExactSpelling = true)] - public static extern Win32Error EnumerateVirtualDiskMetadata(SafeFileHandle VirtualDiskHandle, ref uint NumberOfItems, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Guid[] Items); + public static extern Win32Error EnumerateVirtualDiskMetadata(SafeFileHandle VirtualDiskHandle, ref uint NumberOfItems, IntPtr Items); /// Increases the size of a fixed or dynamic virtual hard disk (VHD). /// A handle to the open VHD, which must have been opened using the VIRTUAL_DISK_ACCESS_METAOPS flag. diff --git a/System/VirtualDisk.cs b/System/VirtualDisk.cs index dc3d506e..9292a3f7 100644 --- a/System/VirtualDisk.cs +++ b/System/VirtualDisk.cs @@ -1,41 +1,34 @@ using Microsoft.Win32.SafeHandles; using System; -using System.ComponentModel; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Linq; -using System.Threading; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text; #if !(NET20 || NET35 || NET40) +using System.Threading; using System.Threading.Tasks; #endif -using System.Security.AccessControl; using Vanara.Extensions; using Vanara.InteropServices; using Vanara.PInvoke; using static Vanara.PInvoke.VirtDisk; -using System.Collections.Generic; -using System.Text; -using System.Runtime.InteropServices; -using System.Diagnostics; -using System.IO; namespace Vanara.IO { public class VirtualDisk : IDisposable { + private static bool IsPreWin8 = Environment.OSVersion.Version < new Version(6, 2); private bool attached = false; private SafeFileHandle hVhd; + private VirtualDiskMetadata metadata; private OPEN_VIRTUAL_DISK_VERSION ver; - private static bool IsPreWin8 = Environment.OSVersion.Version < new Version(6, 2); - private VirtualDisk(SafeFileHandle handle, OPEN_VIRTUAL_DISK_VERSION version) { hVhd = handle; ver = version; } - public enum Subtype : uint - { - Fixed = 2, - Dynamic = 3, - Differencing = 4 - } - public enum DeviceType : uint { /// Device type is unknown or not valid. @@ -56,12 +49,17 @@ namespace Vanara.IO /// Vhdx = VIRTUAL_STORAGE_TYPE_DEVICE_TYPE.VIRTUAL_STORAGE_TYPE_DEVICE_VHDX, - /// - /// - /// + /// VhdSet = VIRTUAL_STORAGE_TYPE_DEVICE_TYPE.VIRTUAL_STORAGE_TYPE_DEVICE_VHDSET } + public enum Subtype : uint + { + Fixed = 2, + Dynamic = 3, + Differencing = 4 + } + /// Indicates whether this virtual disk is currently attached. public bool Attached => attached; @@ -105,6 +103,8 @@ namespace Vanara.IO /// The logical sector size of the physical disk. public uint LogicalSectorSize => GetInformation(GET_VIRTUAL_DISK_INFO_VERSION.GET_VIRTUAL_DISK_INFO_PHYSICAL_DISK); + public VirtualDiskMetadata Metadata => 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 comparison to /// determine whether the NewerChanges member reports new changes. @@ -211,7 +211,10 @@ namespace Vanara.IO /// A reference to a valid CREATE_VIRTUAL_DISK_PARAMETERS structure that contains creation parameter data. /// Creation flags, which must be a valid combination of the CREATE_VIRTUAL_DISK_FLAG enumeration. /// The VIRTUAL_DISK_ACCESS_MASK value to use when opening the newly created virtual disk file. - /// An optional pointer to a SECURITY_DESCRIPTOR to apply to the virtual disk image file. If this parameter is IntPtr.Zero, the parent directory's security descriptor will be used. + /// + /// An optional pointer to a SECURITY_DESCRIPTOR to apply to the virtual disk image file. If this parameter is IntPtr.Zero, the parent directory's + /// security descriptor will be used. + /// /// If successful, returns a valid instance for the newly created virtual disk. public static VirtualDisk Create(string path, ref CREATE_VIRTUAL_DISK_PARAMETERS param, CREATE_VIRTUAL_DISK_FLAG flags = CREATE_VIRTUAL_DISK_FLAG.CREATE_VIRTUAL_DISK_FLAG_NONE, VIRTUAL_DISK_ACCESS_MASK mask = VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_NONE, IntPtr securityDescriptor = default(IntPtr)) { @@ -269,32 +272,6 @@ namespace Vanara.IO return Create(path, ref param, flags, mask, sd); } - /// Creates a virtual hard disk (VHD) image file, either using default parameters or using an existing VHD or physical disk. - /// A valid file path that represents the path to the new virtual disk image file. - /// - /// A fully qualified path to pre-populate the new virtual disk object with block data from an existing disk. This path may refer to a virtual - /// disk or a physical disk. - /// - /// If successful, returns a valid instance for the newly created virtual disk. - public static VirtualDisk CreateFromSource(string path, string sourcePath) - { - if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); - if (string.IsNullOrEmpty(sourcePath)) throw new ArgumentNullException(nameof(sourcePath)); - - var param = new CREATE_VIRTUAL_DISK_PARAMETERS(0, IsPreWin8 ? 1U : 2U); - var mask = IsPreWin8 ? VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_CREATE : VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_NONE; - var sp = new SafeCoTaskMemString(sourcePath); - if (sourcePath != null) - { - if (IsPreWin8) - param.Version1.SourcePath = (IntPtr)sp; - else - param.Version2.SourcePath = (IntPtr)sp; - } - var flags = CREATE_VIRTUAL_DISK_FLAG.CREATE_VIRTUAL_DISK_FLAG_NONE; - return Create(path, ref param, flags, mask, IntPtr.Zero); - } - /// Creates a virtual hard disk (VHD) image file, either using default parameters or using an existing VHD or physical disk. /// A valid string that represents the path to the new virtual disk image file. /// @@ -325,8 +302,35 @@ namespace Vanara.IO return Create(path, ref param, CREATE_VIRTUAL_DISK_FLAG.CREATE_VIRTUAL_DISK_FLAG_NONE, mask, sd); } + /// Creates a virtual hard disk (VHD) image file, either using default parameters or using an existing VHD or physical disk. + /// A valid file path that represents the path to the new virtual disk image file. + /// + /// A fully qualified path to pre-populate the new virtual disk object with block data from an existing disk. This path may refer to a virtual disk or a + /// physical disk. + /// + /// If successful, returns a valid instance for the newly created virtual disk. + public static VirtualDisk CreateFromSource(string path, string sourcePath) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + if (string.IsNullOrEmpty(sourcePath)) throw new ArgumentNullException(nameof(sourcePath)); + + var param = new CREATE_VIRTUAL_DISK_PARAMETERS(0, IsPreWin8 ? 1U : 2U); + var mask = IsPreWin8 ? VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_CREATE : VIRTUAL_DISK_ACCESS_MASK.VIRTUAL_DISK_ACCESS_NONE; + var sp = new SafeCoTaskMemString(sourcePath); + if (sourcePath != null) + { + if (IsPreWin8) + param.Version1.SourcePath = (IntPtr)sp; + else + param.Version2.SourcePath = (IntPtr)sp; + } + var flags = CREATE_VIRTUAL_DISK_FLAG.CREATE_VIRTUAL_DISK_FLAG_NONE; + return Create(path, ref param, flags, mask, IntPtr.Zero); + } + /// - /// 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) @@ -374,7 +378,10 @@ namespace Vanara.IO /// A valid path to the virtual disk image to open. /// If TRUE, indicates the file backing store is to be opened as read-only. /// If TRUE, indicates the handle is only to be used to get information on the virtual disk. - /// Open the VHD file (backing store) without opening any differencing-chain parents. Used to correct broken parent links. This flag is not supported for ISO virtual disks. + /// + /// Open the VHD file (backing store) without opening any differencing-chain parents. Used to correct broken parent links. This flag is not supported for + /// ISO virtual disks. + /// public static VirtualDisk Open(string path, bool readOnly, bool getInfoOnly = false, bool noParents = false) { if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); @@ -477,13 +484,6 @@ namespace Vanara.IO ExpandVirtualDisk(hVhd, EXPAND_VIRTUAL_DISK_FLAG.EXPAND_VIRTUAL_DISK_FLAG_NONE, ref param, IntPtr.Zero).ThrowIfFailed(); } - /// Merges a child virtual hard disk (VHD) in a differencing chain with its immediate parent disk in the chain. - public void MergeWithParent() - { - var param = new MERGE_VIRTUAL_DISK_PARAMETERS(1); - MergeVirtualDisk(hVhd, MERGE_VIRTUAL_DISK_FLAG.MERGE_VIRTUAL_DISK_FLAG_NONE, ref param, IntPtr.Zero).ThrowIfFailed(); - } - /// Merges a child virtual hard disk (VHD) in a differencing chain with parent disks in the chain. /// Depth from the leaf from which to begin the merge. The leaf is at depth 1. /// Depth from the leaf to target the merge. The leaf is at depth 1. @@ -493,8 +493,18 @@ namespace Vanara.IO MergeVirtualDisk(hVhd, MERGE_VIRTUAL_DISK_FLAG.MERGE_VIRTUAL_DISK_FLAG_NONE, ref param, IntPtr.Zero).ThrowIfFailed(); } + /// Merges a child virtual hard disk (VHD) in a differencing chain with its immediate parent disk in the chain. + public void MergeWithParent() + { + var param = new MERGE_VIRTUAL_DISK_PARAMETERS(1); + MergeVirtualDisk(hVhd, MERGE_VIRTUAL_DISK_FLAG.MERGE_VIRTUAL_DISK_FLAG_NONE, ref param, IntPtr.Zero).ThrowIfFailed(); + } + /// Resizes a virtual disk. - /// New size, in bytes, for the expansion request. Setting this value to '0' will shrink the disk to the smallest safe virtual size possible without truncating past any existing partitions. + /// + /// New size, in bytes, for the expansion request. Setting this value to '0' will shrink the disk to the smallest safe virtual size possible without + /// truncating past any existing partitions. + /// public void Resize(ulong newSize) { 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."); @@ -503,8 +513,9 @@ namespace Vanara.IO ResizeVirtualDisk(hVhd, flags, ref param, IntPtr.Zero).ThrowIfFailed(); } - /// 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) @@ -515,37 +526,17 @@ namespace Vanara.IO ResizeVirtualDisk(hVhd, flags, ref param, IntPtr.Zero).ThrowIfFailed(); } - private T GetInformation(GET_VIRTUAL_DISK_INFO_VERSION info, long offset = 0) - { - var sz = 32U; - using (var mem = new SafeHGlobalHandle((int)sz)) - { - Marshal.WriteInt32(mem.DangerousGetHandle(), (int)info); - var err = GetVirtualDiskInformation(hVhd, ref sz, mem, out uint req); - if (err == Win32Error.ERROR_INSUFFICIENT_BUFFER) - { - mem.Size = (int)sz; - Marshal.WriteInt32(mem.DangerousGetHandle(), (int)info); - err = GetVirtualDiskInformation(hVhd, ref sz, mem, out req); - } - Debug.WriteLineIf(err.Succeeded, $"GetVirtualDiskInformation: Id={info.ToString().Remove(0,22)}; Unk={Marshal.ReadInt32((IntPtr)mem, 4)}; Sz={req}; Bytes={string.Join(" ", mem.ToEnumerable((int)req/4).Select(b => b.ToString("X8")).ToArray())}"); - err.ThrowIfFailed(); - - var ms = new MarshalingStream(mem.DangerousGetHandle(), mem.Size) { Position = 8 + offset }; - if (typeof(T) == typeof(string)) return (T)(object)System.Text.Encoding.Unicode.GetString(mem.ToArray((int)sz), 8 + (int)offset, (int)sz - 8 - (int)offset).TrimEnd('\0'); - return typeof(T) == typeof(bool) ? (T)Convert.ChangeType(ms.Read() != 0, typeof(bool)) : ms.Read(); - } - } - #if !(NET20 || NET35 || NET40) /// Creates a virtual hard disk (VHD) image file, either using default parameters or using an existing VHD or physical disk. /// A valid file path that represents the path to the new virtual disk image file. /// - /// A fully qualified path to pre-populate the new virtual disk object with block data from an existing disk. This path may refer to a virtual - /// disk or a physical disk. + /// A fully qualified path to pre-populate the new virtual disk object with block data from an existing disk. This path may refer to a virtual disk or a + /// physical disk. /// /// A cancellation token that can be used to cancel the operation. This value can be null to disable cancellation. - /// A class that implements that can be used to report on progress. This value can be null to disable progress reporting. + /// + /// A class that implements that can be used to report on progress. This value can be null to disable progress reporting. + /// /// If successful, returns a valid instance for the newly created virtual disk. public async static Task CreateFromSource(string path, string sourcePath, CancellationToken cancellationToken, IProgress progress) { @@ -579,7 +570,9 @@ namespace Vanara.IO /// Reduces the size of a virtual hard disk (VHD) backing store file. /// A cancellation token that can be used to cancel the operation. This value can be null to disable cancellation. - /// A class that implements that can be used to report on progress. This value can be null to disable progress reporting. + /// + /// A class that implements that can be used to report on progress. This value can be null to disable progress reporting. + /// /// true if operation completed without error or cancellation; false otherwise. public async Task Compact(CancellationToken cancellationToken, IProgress progress) { @@ -594,7 +587,9 @@ namespace Vanara.IO /// Increases the size of a fixed or dynamic virtual hard disk (VHD). /// New size, in bytes, for the expansion request. /// A cancellation token that can be used to cancel the operation. This value can be null to disable cancellation. - /// A class that implements that can be used to report on progress. This value can be null to disable progress reporting. + /// + /// A class that implements that can be used to report on progress. This value can be null to disable progress reporting. + /// /// true if operation completed without error or cancellation; false otherwise. public async Task Expand(ulong newSize, CancellationToken cancellationToken, IProgress progress) { @@ -610,7 +605,9 @@ namespace Vanara.IO /// Resizes a virtual disk. /// New size, in bytes, for the expansion request. /// A cancellation token that can be used to cancel the operation. This value can be null to disable cancellation. - /// A class that implements that can be used to report on progress. This value can be null to disable progress reporting. + /// + /// A class that implements that can be used to report on progress. This value can be null to disable progress reporting. + /// /// true if operation completed without error or cancellation; false otherwise. public async Task Resize(ulong newSize, CancellationToken cancellationToken, IProgress progress) { @@ -643,7 +640,8 @@ namespace Vanara.IO break; default: - throw new Win32Exception((int)prog.OperationStatus); + new Win32Error((int)prog.OperationStatus).ThrowIfFailed(); + break; } if (prog.CurrentValue == prog.CompletionValue) return true; if (cancellationToken == null) @@ -729,5 +727,177 @@ namespace Vanara.IO void ReportProgress(int percent) { progress.Report(new Tuple(percent, $"Compacting VHD volume \"{loc}\"")); } }*/ #endif + + private T GetInformation(GET_VIRTUAL_DISK_INFO_VERSION info, long offset = 0) + { + var sz = 32U; + using (var mem = new SafeHGlobalHandle((int)sz)) + { + Marshal.WriteInt32(mem.DangerousGetHandle(), (int)info); + var err = GetVirtualDiskInformation(hVhd, ref sz, mem, out uint req); + if (err == Win32Error.ERROR_INSUFFICIENT_BUFFER) + { + mem.Size = (int)sz; + Marshal.WriteInt32(mem.DangerousGetHandle(), (int)info); + err = GetVirtualDiskInformation(hVhd, ref sz, mem, out req); + } + Debug.WriteLineIf(err.Succeeded, $"GetVirtualDiskInformation: Id={info.ToString().Remove(0, 22)}; Unk={Marshal.ReadInt32((IntPtr)mem, 4)}; Sz={req}; Bytes={string.Join(" ", mem.ToEnumerable((int)req / 4).Select(b => b.ToString("X8")).ToArray())}"); + err.ThrowIfFailed(); + + var ms = new MarshalingStream(mem.DangerousGetHandle(), mem.Size) { Position = 8 + offset }; + if (typeof(T) == typeof(string)) return (T)(object)System.Text.Encoding.Unicode.GetString(mem.ToArray((int)sz), 8 + (int)offset, (int)sz - 8 - (int)offset).TrimEnd('\0'); + return typeof(T) == typeof(bool) ? (T)Convert.ChangeType(ms.Read() != 0, typeof(bool)) : ms.Read(); + } + } + + /// Supports getting and setting metadata on a virtual disk. + /// + public class VirtualDiskMetadata : IDictionary + { + private VirtualDisk parent; + private bool supported; + + /// Initializes a new instance of the class. + /// The VHD. + internal VirtualDiskMetadata(VirtualDisk vhd) + { + parent = vhd; + supported = vhd.DiskType == DeviceType.Vhdx; + } + + /// Gets a value indicating whether the is read-only. + public bool IsReadOnly => false; + + /// Gets an containing the keys of the . + public ICollection Keys + { + 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.ThrowIfFailed(); + if (count == 0) return new Guid[0]; + var mem = new SafeCoTaskMemHandle(Marshal.SizeOf(typeof(Guid)) * (int)count); + EnumerateVirtualDiskMetadata(parent.Handle, ref count, (IntPtr)mem).ThrowIfFailed(); + return mem.ToArray((int)count); + } + } + + /// Gets an containing the values in the . + public ICollection Values => Keys.Select(k => this[k]).ToList(); + + /// Gets the number of elements contained in the . + public int Count => Keys.Count(); + + /// Gets or sets the with the specified key. + /// The . + /// The key. + public SafeCoTaskMemHandle this[Guid key] + { + get + { + if (!supported) throw new PlatformNotSupportedException(); + if (parent.Handle.IsClosed) throw new InvalidOperationException("Virtual disk not valid."); + uint sz = 0; + var err = GetVirtualDiskMetadata(parent.Handle, key, ref sz, SafeCoTaskMemHandle.Null); + if (err != Win32Error.ERROR_MORE_DATA) err.ThrowIfFailed(); + var ret = new SafeCoTaskMemHandle((int)sz); + GetVirtualDiskMetadata(parent.Handle, key, ref sz, ret).ThrowIfFailed(); + return ret; + } + set + { + if (!supported) throw new PlatformNotSupportedException(); + if (parent.Handle.IsClosed) throw new InvalidOperationException("Virtual disk not valid."); + SetVirtualDiskMetadata(parent.Handle, key, (uint)value.Size, (IntPtr)value).ThrowIfFailed(); + } + } + + /// Adds an element with the provided key and value to the . + /// The object to use as the key of the element to add. + /// The object to use as the value of the element to add. + public void Add(Guid key, SafeCoTaskMemHandle value) => this[key] = value; + + /// Adds an item to the . + /// The object to add to the . + void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); + + /// Removes all items from the . + /// + void ICollection>.Clear() => throw new PlatformNotSupportedException(); + + /// Determines whether the contains a specific value. + /// The object to locate in the . + /// true if is found in the ; otherwise, false. + bool ICollection>.Contains(KeyValuePair item) => ContainsKey(item.Key) && this[item.Key].DangerousGetHandle() == item.Value.DangerousGetHandle(); + + /// Determines whether the contains an element with the specified key. + /// The key to locate in the . + /// true if the contains an element with the key; otherwise, false. + public bool ContainsKey(Guid key) => Keys.Contains(key); + + /// + /// Copies the elements of the to an , starting at a particular + /// index. + /// + /// + /// 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) + { + var a = GetEnum().ToArray(); + Array.Copy(a, 0, array, arrayIndex, a.Length); + } + + /// Returns an enumerator that iterates through the collection. + /// A that can be used to iterate through the collection. + public IEnumerator> GetEnumerator() => GetEnum().GetEnumerator(); + + /// Removes the element with the specified key from the . + /// The key of the element to remove. + /// + /// true if the element is successfully removed; otherwise, false. This method also returns false if was not found in the + /// original . + /// + public bool Remove(Guid key) + { + if (!supported) throw new PlatformNotSupportedException(); + if (parent.Handle.IsClosed) throw new InvalidOperationException("Virtual disk not valid."); + return DeleteVirtualDiskMetadata(parent.Handle, key).Succeeded; + } + + /// Removes the first occurrence of a specific object from the . + /// 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 . + /// + bool ICollection>.Remove(KeyValuePair item) => Remove(item.Key); + + /// Gets the value associated with the specified key. + /// The key whose value to get. + /// + /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the + /// parameter. This parameter is passed uninitialized. + /// + /// + /// true if the object that implements contains an element with the specified key; otherwise, false. + /// + public bool TryGetValue(Guid key, out SafeCoTaskMemHandle value) + { + try { value = this[key]; return true; } + catch { value = SafeCoTaskMemHandle.Null; return false; } + } + + /// Returns an enumerator that iterates through a collection. + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private IEnumerable> GetEnum() => Keys.Select(k => new KeyValuePair(k, this[k])); + } } } \ No newline at end of file diff --git a/UnitTests/System/VirtualDiskTests.cs b/UnitTests/System/VirtualDiskTests.cs index 967e1da7..8b9636d8 100644 --- a/UnitTests/System/VirtualDiskTests.cs +++ b/UnitTests/System/VirtualDiskTests.cs @@ -226,6 +226,41 @@ namespace Vanara.IO.Tests } } + [Test] + public void GetSetMetadataTest() + { + const int sz = 0x03010200; + var lfn = tmpfn + "x"; + try + { + using (var vhd = VirtualDisk.Create(lfn, sz)) + { + vhd.Attach(true, true, false, GetWorldFullFileSecurity()); + + // 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 set and remove + var guid = Guid.NewGuid(); + Assert.That(() => vhd.Metadata.Add(guid, new SafeCoTaskMemHandle("Testing")), Throws.Nothing); + Assert.That(vhd.Metadata.TryGetValue(Guid.NewGuid(), out SafeCoTaskMemHandle mem), Is.False); + Assert.That(vhd.Metadata.TryGetValue(guid, out mem), Is.True); + Assert.That(mem.ToString(-1), Is.EqualTo("Testing")); + Assert.That(vhd.Metadata.Remove(guid), Is.True); + Assert.That(vhd.Metadata.TryGetValue(guid, out mem), Is.False); + } + } + finally + { + System.IO.File.Delete(lfn); + } + } + //[Test()] public void DetachTest() {