VirtualDisk: Added support for mounting specific path or drive when attaching, added properties for mount information, CreateAsync, AttachAsync, VHD set, snapshot and mirror methods.

pull/299/head
dahall 2022-04-11 15:46:20 -06:00
parent cc33c451b6
commit c24dbf8659
6 changed files with 1842 additions and 1333 deletions

View File

@ -1,10 +1,13 @@
using NUnit.Framework;
using System;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Threading;
using System.Threading.Tasks;
using Vanara.InteropServices;
using Vanara.PInvoke.Tests;
using static Vanara.PInvoke.Kernel32;
using static Vanara.PInvoke.VirtDisk;
namespace Vanara.IO.Tests
@ -12,10 +15,10 @@ namespace Vanara.IO.Tests
[TestFixture]
public class VirtualDiskTests
{
private static readonly string badfn = Vanara.PInvoke.Tests.TestCaseSources.TempDirWhack + "TestInvalid.vhdx";
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";
private static readonly string badfn = TestCaseSources.TempDirWhack + "TestInvalid.vhdx";
private static readonly string fn = TestCaseSources.VirtualDisk;
private static readonly string tmpcfn = TestCaseSources.TempDirWhack + "TestVHD - Diff.vhd";
private static readonly string tmpfn = TestCaseSources.TempDirWhack + "TestVHD.vhd";
[Test]
public async Task CompactAsync1Test()
@ -34,7 +37,7 @@ namespace Vanara.IO.Tests
using var vhd = VirtualDisk.Open(fn, false);
var rpt = new Reporter();
rpt.NewVal += (o, e) => TestContext.WriteLine($"{DateTime.Now:o} NewVal={e}");
await vhd.CompactAsync(VirtualDisk.CompactionMode.Quick, default, rpt);
await vhd.CompactAsync(VirtualDisk.CompactionMode.Full, default, rpt);
Assert.That(rpt.lastVal, Is.EqualTo(100));
}
@ -58,9 +61,9 @@ namespace Vanara.IO.Tests
finally
{
var fn2 = fn.TrimEnd('x');
while (System.IO.File.Exists(fn2))
while (File.Exists(fn2))
{
try { System.IO.File.Delete(fn2); } catch { Thread.Sleep(500); }
try { File.Delete(fn2); } catch { Thread.Sleep(500); }
}
}
}
@ -73,9 +76,9 @@ namespace Vanara.IO.Tests
{
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(File.Exists(tmpcfn));
Assert.That(File.Exists(tmpfn));
Assert.That(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));
@ -107,8 +110,8 @@ namespace Vanara.IO.Tests
}
finally
{
System.IO.File.Delete(tmpcfn);
System.IO.File.Delete(tmpfn);
File.Delete(tmpcfn);
File.Delete(tmpfn);
}
}
@ -120,7 +123,7 @@ namespace Vanara.IO.Tests
{
using var vhd = VirtualDisk.Create(tmpfn, sz);
//vhd.Attach(true);
Assert.That(System.IO.File.Exists(tmpfn));
Assert.That(File.Exists(tmpfn));
Assert.That(vhd.Attached, Is.False);
Assert.That(vhd.BlockSize, Is.GreaterThan(0));
Assert.That(vhd.DiskType, Is.EqualTo(VirtualDisk.DeviceType.Vhd));
@ -151,7 +154,7 @@ namespace Vanara.IO.Tests
}
finally
{
System.IO.File.Delete(tmpfn);
File.Delete(tmpfn);
}
}
@ -162,29 +165,14 @@ namespace Vanara.IO.Tests
try
{
using var vhd = VirtualDisk.Create(tmpfn, sz, false, null);
Assert.That(System.IO.File.Exists(tmpfn));
Assert.That(File.Exists(tmpfn));
Assert.That(vhd.PhysicalSize, Is.GreaterThanOrEqualTo(sz));
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 CreateFromSourceTest()
{
try
{
using var vhd = VirtualDisk.CreateFromSource(tmpfn, fn);
Assert.That(System.IO.File.Exists(tmpfn));
vhd.Close();
}
finally
{
System.IO.File.Delete(tmpfn);
File.Delete(tmpfn);
}
}
@ -198,35 +186,28 @@ namespace Vanara.IO.Tests
rpt.NewVal += (o, e) => TestContext.WriteLine($"{DateTime.Now:o} NewVal={e}");
vd = await VirtualDisk.CreateFromSourceAsync(tmpfn, fn, default, rpt);
Assert.That(rpt.lastVal, Is.EqualTo(100));
Assert.That(System.IO.File.Exists(tmpfn));
TestContext.WriteLine($"New file sz: {new System.IO.FileInfo(tmpfn).Length}");
Assert.That(File.Exists(tmpfn));
TestContext.WriteLine($"New file sz: {new FileInfo(tmpfn).Length}");
}
finally
{
vd?.Close();
try { System.IO.File.Delete(tmpfn); } catch { }
try { File.Delete(tmpfn); } catch { }
}
}
[Test]
public void DetachTest()
public void CreateFromSourceTest()
{
const int sz = 0x03010400;
try
{
using (var vhd = VirtualDisk.Create(tmpfn, sz))
{
Assert.That(vhd.Attached, Is.False);
vhd.Attach(true, false, true, null);
Assert.That(vhd.Attached, Is.True);
}
Assert.That(VirtualDisk.IsAttached(tmpfn), Is.True);
Assert.That(() => VirtualDisk.Detach(tmpfn), Throws.Nothing);
Assert.That(VirtualDisk.IsAttached(tmpfn), Is.False);
using var vhd = VirtualDisk.CreateFromSource(tmpfn, fn);
Assert.That(File.Exists(tmpfn));
vhd.Close();
}
finally
{
System.IO.File.Delete(tmpfn);
File.Delete(tmpfn);
}
}
@ -237,14 +218,14 @@ namespace Vanara.IO.Tests
try
{
using var vhd = VirtualDisk.Create(tmpfn, sz, true, null);
Assert.That(System.IO.File.Exists(tmpfn));
Assert.That(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);
File.Delete(tmpfn);
}
}
@ -291,7 +272,7 @@ namespace Vanara.IO.Tests
}
finally
{
System.IO.File.Delete(lfn);
File.Delete(lfn);
}
}
@ -309,7 +290,44 @@ namespace Vanara.IO.Tests
}
finally
{
System.IO.File.Delete(lfn);
File.Delete(lfn);
}
}
[Test]
public async Task GetVHDSetInformationTestAsync()
{
var newfn = tmpfn + "s";
try
{
using VirtualDisk vhd = await MakeSet();
VirtualDiskSetInformation si = default;
Assert.That(() => si = vhd.GetVHDSetInformation(), Throws.Nothing);
Assert.That(si?.Path, Is.Not.Null);
vhd.Close();
foreach (FileInfo a in si.AllPaths.Select(s => new FileInfo(Path.Combine(Path.GetDirectoryName(newfn), s))).OrderByDescending(f => f.CreationTime))
//await VirtualDisk.MergeAsync(a.FullName, newfn);
File.Delete(a.FullName);
}
finally
{
File.Delete(newfn);
}
}
private async Task<VirtualDisk> MakeSet()
{
var newfn = tmpfn + "x";
File.Copy(fn, newfn);
try
{
await VirtualDisk.ConvertToVHDSetAsync(newfn);
Assert.That(File.Exists(tmpfn + "s"));
return VirtualDisk.Open(tmpfn + "s", false);
}
finally
{
File.Delete(newfn);
}
}
@ -320,15 +338,15 @@ namespace Vanara.IO.Tests
try
{
using (var vhdp = VirtualDisk.Create(tmpfn, sz))
Assert.That(System.IO.File.Exists(tmpfn));
Assert.That(File.Exists(tmpfn));
using var vhd = VirtualDisk.CreateDifferencing(tmpcfn, tmpfn);
Assert.That(System.IO.File.Exists(tmpcfn));
Assert.That(File.Exists(tmpcfn));
vhd.Merge(1, 2);
}
finally
{
System.IO.File.Delete(tmpcfn);
System.IO.File.Delete(tmpfn);
File.Delete(tmpcfn);
File.Delete(tmpfn);
}
}
@ -339,18 +357,32 @@ namespace Vanara.IO.Tests
try
{
using (var vhdp = VirtualDisk.Create(tmpfn, sz))
Assert.That(System.IO.File.Exists(tmpfn));
Assert.That(File.Exists(tmpfn));
using var vhd = VirtualDisk.CreateDifferencing(tmpcfn, tmpfn);
Assert.That(System.IO.File.Exists(tmpcfn));
Assert.That(File.Exists(tmpcfn));
vhd.MergeWithParent();
}
finally
{
System.IO.File.Delete(tmpcfn);
System.IO.File.Delete(tmpfn);
File.Delete(tmpcfn);
File.Delete(tmpfn);
}
}
[Test]
public void MirrorTestAsync()
{
var mvhd = TestCaseSources.TempDirWhack + "mirror.vhdx";
using (var vhd = VirtualDisk.Open(fn, true))
{
vhd.MirrorAsync(mvhd).Wait();
Assert.That(File.Exists(mvhd));
vhd.BreakMirror();
}
Task.Delay(500);
File.Delete(mvhd);
}
[Test]
public void OpenAttachRawTest()
{
@ -373,23 +405,56 @@ namespace Vanara.IO.Tests
[Test]
public void OpenAttachTest()
{
using var vhd = VirtualDisk.Open(fn, true);
Assert.That(vhd.Attached, Is.False);
var beforeDrives = GetLogicalDrives();
vhd.Attach("T:\\", true, true, GetWorldFullFileSecurity());
Assert.That(vhd.Attached, Is.True);
Assert.That(beforeDrives, Is.Not.EqualTo(GetLogicalDrives()));
TestContext.WriteLine(vhd.PhysicalPath);
TestContext.WriteLine(vhd.VolumeGuidPath);
TestContext.WriteLine(vhd.VolumeMountPoint);
Assert.That(vhd.PhysicalPath, Is.Not.Null); // must be attached
vhd.Detach();
Assert.That(vhd.Attached, Is.False);
vhd.Attach(true, true, true);
Assert.That(vhd.Attached, Is.True);
Assert.That(beforeDrives, Is.EqualTo(GetLogicalDrives()));
vhd.Close();
Assert.That(vhd.Attached, Is.False);
}
/*[Test]
public void SetSnapshotInformationAsyncTest()
{
var vhd = MakeSet().Result;
var newfn = vhd.ImagePath;
vhd.Dispose();
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);
VirtualDisk.SetSnapshotInformationAsync(new VirtualDiskSnapshotInformation(newfn, Guid.NewGuid())).Wait();
}
finally
{
File.Delete(newfn);
}
}*/
// [Test] Don't know how to create a file that shows query changes
public void QueryChangesTest()
{
using var vhd = MakeSet().Result;
var newfn = vhd.ImagePath;
try
{
vhd.ResilientChangeTrackingEnabled = true;
vhd.TakeSnapshot(Guid.NewGuid(), true);
QUERY_CHANGES_VIRTUAL_DISK_RANGE[] chgs = vhd.QueryChanges("rctX:e59e6991:208a:44d9:ae6a:2f14351d792f:00000000");
}
finally
{
vhd.Dispose();
File.Delete(newfn);
}
}
@ -411,17 +476,56 @@ namespace Vanara.IO.Tests
try
{
using var vhd = VirtualDisk.Create(tmpfn, sz, true, null);
Assert.That(System.IO.File.Exists(tmpfn));
Assert.That(File.Exists(tmpfn));
Assert.That(vhd.VirtualSize, Is.EqualTo(sz));
vhd.Resize(sz * 2);
Assert.That(vhd.VirtualSize, Is.EqualTo(sz * 2));
}
finally
{
System.IO.File.Delete(tmpfn);
File.Delete(tmpfn);
}
}
[Test]
public async Task SnapshotTest()
{
VirtualDisk vhd = await MakeSet();
var newfn = vhd.ImagePath;
try
{
var id = Guid.NewGuid();
vhd.TakeSnapshot(id);
vhd.DeleteSnapshot(id);
}
finally
{
vhd?.Dispose();
File.Delete(newfn);
}
}
/*[Test]
public void GetSnapshotInformationTest()
{
var vhd = MakeSet().Result;
var newfn = vhd.ImagePath;
try
{
var id = Guid.NewGuid();
vhd.TakeSnapshot(id);
vhd.Dispose();
vhd = null;
var si = VirtualDisk.GetSnapshotInformation(newfn, id);
Assert.That(si.FilePath, Is.Not.Null);
}
finally
{
vhd?.Dispose();
File.Delete(newfn);
}
}*/
[Test]
public void UnsafeResizeTest()
{
@ -429,13 +533,13 @@ namespace Vanara.IO.Tests
try
{
using var vhd = VirtualDisk.Create(tmpfn, sz * 2, true, null);
Assert.That(System.IO.File.Exists(tmpfn));
Assert.That(File.Exists(tmpfn));
Assert.That(vhd.VirtualSize, Is.EqualTo(sz * 2));
Assert.That(() => vhd.UnsafeResize(sz), Throws.Exception);
}
finally
{
System.IO.File.Delete(tmpfn);
File.Delete(tmpfn);
}
}
@ -452,6 +556,12 @@ namespace Vanara.IO.Tests
Assert.That(!res);
}
[Test]
public void ValidatePersistentReservationSupportTest()
{
Assert.That(VirtualDisk.ValidatePersistentReservationSupport(fn), Is.False);
}
private static FileSecurity GetFileSecurity(string sddl)
{
var sd = new FileSecurity();

View File

@ -1,154 +1,282 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Vanara.Management;
using Vanara.Management;
namespace Vanara.IO
namespace Vanara.IO;
public partial class VirtualDisk
{
public partial class VirtualDisk
private static readonly System.Management.ManagementScope scope = new(@"root\virtualization\v2", null);
/// <summary>Compaction options for <see cref="CompactAsync(CompactionMode, CancellationToken, IProgress{int})"/>.</summary>
public enum CompactionMode : ushort
{
private static readonly System.Management.ManagementScope scope = new(@"root\virtualization\v2", null);
/// <summary>Full.</summary>
Full = 0,
/// <summary>Compaction options for <see cref="CompactAsync(CompactionMode, CancellationToken, IProgress{int})"/>.</summary>
public enum CompactionMode : ushort
/// <summary>Quick.</summary>
Quick,
/// <summary>Retrimmed</summary>
Retrim,
/// <summary>Pretrimmed</summary>
Pretrimmed,
/// <summary>Prezeroed</summary>
Prezeroed
}
/// <summary>
/// Converts an existing virtual hard disk to a different type or format. This method creates a new virtual hard disk and does not
/// convert the source virtual hard disk in place.
/// </summary>
/// <param name="path">
/// The fully qualified path of the source virtual hard disk file to convert. This file will not be modified as a result of this operation.
/// </param>
/// <param name="targetType">The format for the target virtual hard disk.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to disable
/// progress reporting.
/// </param>
public static async Task ConvertAsync(string path, DeviceType targetType, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
var dest = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(path), $"{System.IO.Path.GetFileNameWithoutExtension(path)}.{targetType.ToString().ToLower()}");
if (string.Equals(path, dest, StringComparison.InvariantCultureIgnoreCase))
return;
VirtualDisk vhd = Open(path, true, true);
Subtype subType = vhd.ProviderSubtype;
vhd.Close();
var data = new VirtualDiskSettingData(subType, targetType, dest);
await ConvertAsync(path, data, cancellationToken, progress);
}
/// <summary>
/// Converts an existing virtual hard disk to a different type or format. This method creates a new virtual hard disk and does not
/// convert the source virtual hard disk in place.
/// </summary>
/// <param name="sourcePath">
/// The fully qualified path of the source virtual hard disk file to convert. This file will not be modified as a result of this operation.
/// </param>
/// <param name="destinationSettings">The settings, including path, for the target virtual hard disk.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to disable
/// progress reporting.
/// </param>
public static async Task ConvertAsync(string sourcePath, VirtualDiskSettingData destinationSettings, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
/// <summary>Full.</summary>
Full = 0,
/// <summary>Quick.</summary>
Quick,
/// <summary>Retrimmed</summary>
Retrim,
/// <summary>Pretrimmed</summary>
Pretrimmed,
/// <summary>Prezeroed</summary>
Prezeroed
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "ConvertVirtualHardDisk", ("SourcePath", sourcePath), ("VirtualDiskSettingData", destinationSettings.GetInstanceText()));
output.GetResultOrThrow(true);
}
/// <summary>
/// Converts an existing virtual hard disk to a different type or format. This method creates a new virtual hard disk and does not
/// convert the source virtual hard disk in place.
/// </summary>
/// <param name="path">
/// The fully qualified path of the source virtual hard disk file to convert. This file will not be modified as a result of this operation.
/// </param>
/// <param name="targetType">The format for the target virtual hard disk.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to
/// disable progress reporting.
/// </param>
public static async Task ConvertAsync(string path, DeviceType targetType, CancellationToken cancellationToken = default, IProgress<int> progress = default)
catch (Exception ex)
{
var dest = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(path), $"{System.IO.Path.GetFileNameWithoutExtension(path)}.{targetType.ToString().ToLower()}");
if (string.Equals(path, dest, StringComparison.InvariantCultureIgnoreCase))
return;
var vhd = Open(path, true, true);
var subType = vhd.ProviderSubtype;
vhd.Close();
var data = new VirtualDiskSettingData(subType, targetType, dest);
await ConvertAsync(path, data, cancellationToken, progress);
}
/// <summary>
/// Converts an existing virtual hard disk to a different type or format. This method creates a new virtual hard disk and does not
/// convert the source virtual hard disk in place.
/// </summary>
/// <param name="sourcePath">
/// The fully qualified path of the source virtual hard disk file to convert. This file will not be modified as a result of this operation.
/// </param>
/// <param name="destinationSettings">The settings, including path, for the target virtual hard disk.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to
/// disable progress reporting.
/// </param>
public static async Task ConvertAsync(string sourcePath, VirtualDiskSettingData destinationSettings, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "ConvertVirtualHardDisk", ("SourcePath", sourcePath), ("VirtualDiskSettingData", destinationSettings.GetInstanceText()));
output.GetResultOrThrow(true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to convert virtual disk.", ex);
}
}
/// <summary>Determines whether a virtual hard disk file is valid.</summary>
/// <param name="path">
/// The fully qualified path of the virtual hard disk file to validate. This file will not be modified as a result of this operation.
/// </param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to
/// disable progress reporting.
/// </param>
/// <returns><c>true</c> if operation completed without error or cancellation; <c>false</c> otherwise.</returns>
public static async Task<bool> ValidateAsync(string path, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "ValidateVirtualHardDisk", ("Path", path));
return output.GetResultOrThrow(true);
}
catch
{
return false;
}
}
/// <summary>Reduces the size of a virtual hard disk (VHD) backing store file.</summary>
/// <param name="mode">The mode.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to
/// disable progress reporting.
/// </param>
public async Task CompactAsync(CompactionMode mode, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "CompactVirtualHardDisk", ("Path", ImagePath), ("Mode", (ushort)mode));
output.GetResultOrThrow(true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to compact virtual disk.", ex);
}
}
/// <summary>Creates a new VHD Set file alongside the existing virtual hard disk.</summary>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to
/// disable progress reporting.
/// </param>
public async Task ConvertToVHDSetAsync(CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "ConvertVirtualHardDiskToVHDSet", ("VirtualHardDiskPath", ImagePath));
output.GetResultOrThrow(true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to create virtual disk set file.", ex);
}
throw new InvalidOperationException("Failed to convert virtual disk.", ex);
}
}
/// <summary>Converts a virtual hard disk file by creating a new VHD Set file alongside the existing virtual hard disk.</summary>
/// <param name="path">The path to the virtual hard disk file. The new VHD Set file will have the same filename but with the .VHDS extension.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to disable
/// progress reporting.
/// </param>
/// <exception cref="System.InvalidOperationException">Failed to create virtual disk set file.</exception>
public static async Task ConvertToVHDSetAsync(string path, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "ConvertVirtualHardDiskToVHDSet", ("VirtualHardDiskPath", path));
output.GetResultOrThrow(true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to create virtual disk set file.", ex);
}
}
/// <summary>
/// Merges a child virtual hard disk in a differencing chain with one or more parent virtual hard disks in the chain. See Remarks for
/// usage restrictions for this method.
/// <para>If the user executing this function does not have permission to update the virtual machines, then this function will fail.</para>
/// </summary>
/// <param name="sourcePath">A fully qualified path that specifies the location of the virtual hard disk file to merge.</param>
/// <param name="destPath">
/// A fully qualified path that specifies the location of the parent virtual hard disk file into which data is to be merged. This could
/// be the immediate parent virtual hard disk of the merging file or the parent disk image a few levels up the differencing chain.
/// </param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to disable
/// progress reporting.
/// </param>
public static async Task MergeAsync(string sourcePath, string destPath, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "MergeVirtualHardDisk", ("SourcePath", sourcePath), ("DestinationPath", destPath));
output.GetResultOrThrow(true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to merge virtual disks.", ex);
}
}
/// <summary>Optimizes a VHD Set file to use less disk space.</summary>
/// <param name="path">A fully-qualified path that specifies the location of the VHD Set file.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to disable
/// progress reporting.
/// </param>
/// <exception cref="System.InvalidOperationException">Failed to create virtual disk set file.</exception>
public static async Task OptimizeVHDSetAsync(string path, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "OptimizeVHDSet", ("VHDSetPath", path));
output.GetResultOrThrow(true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to optimize virtual disk set file.", ex);
}
}
/// <summary>Determines whether a virtual hard disk file is valid.</summary>
/// <param name="path">
/// The fully qualified path of the virtual hard disk file to validate. This file will not be modified as a result of this operation.
/// </param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to disable
/// progress reporting.
/// </param>
/// <returns><c>true</c> if operation completed without error or cancellation; <c>false</c> otherwise.</returns>
public static async Task<bool> ValidateAsync(string path, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "ValidateVirtualHardDisk", ("Path", path));
return output.GetResultOrThrow(true);
}
catch
{
return false;
}
}
/// <summary>Validates whether a file system can support a virtual hard disk with persistent reservations enabled.</summary>
/// <param name="path">
/// A fully-qualified path that specifies the location of a disk image file or a directory in which a disk image file might be placed.
/// </param>
/// <returns><see langword="true"/> if the file system can support a virtual hard disk with persistent reservations enabled.</returns>
public static bool ValidatePersistentReservationSupport(string path)
{
try
{
return scope.CallJobMethodAsync(default, default, "Msvm_ImageManagementService", "ValidatePersistentReservationSupport", ("Path", path)).Result.GetResultOrThrow(true);
}
catch
{
return false;
}
}
/// <summary>Reduces the size of a virtual hard disk (VHD) backing store file.</summary>
/// <param name="mode">The mode.</param>
/// <param name="cancellationToken">
/// A cancellation token that can be used to cancel the operation. This value can be <see cref="CancellationToken.None"/> to disable cancellation.
/// </param>
/// <param name="progress">
/// A class that implements <see cref="IProgress{T}"/> that can be used to report on progress. This value can be <c>null</c> to disable
/// progress reporting.
/// </param>
public async Task CompactAsync(CompactionMode mode, CancellationToken cancellationToken = default, IProgress<int> progress = default)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(cancellationToken, progress, "Msvm_ImageManagementService", "CompactVirtualHardDisk", ("Path", ImagePath), ("Mode", (ushort)mode));
output.GetResultOrThrow(true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to compact virtual disk.", ex);
}
}
/// <summary>Retrieves information about a VHD Set file.</summary>
/// <returns>The information for the requested VHD Set file as an <see cref="VirtualDiskSetInformation"/> instance.</returns>
/// <exception cref="System.InvalidOperationException">Failed to get virtual disk set information.</exception>
public VirtualDiskSetInformation GetVHDSetInformation()
{
try
{
var info = new uint[] { 0, 1, 2 };
System.Management.ManagementBaseObject output = scope.CallJobMethodAsync(default, default, "Msvm_ImageManagementService", "GetVHDSetInformation", ("VHDSetPath", ImagePath), ("AdditionalInformation", info)).Result;
output.GetResultOrThrow(true);
return VirtualDiskSetInformation.Parse(Convert.ToString(output.Properties["Information"].Value));
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to get virtual disk set information.", ex);
}
}
/// <summary>Retrieves information about a VHD Snapshot within a VHD Set file.</summary>
/// <param name="path">A fully-qualified path that specifies the location of the VHD Set file.</param>
/// <param name="snapshotId">The snapshot identifier.</param>
/// <returns>The information for the requested VHD Set file as an <see cref="VirtualDiskSnapshotInformation"/> instance.</returns>
/// <exception cref="System.InvalidOperationException">Failed to get virtual disk set information.</exception>
// TODO: Fix problem with it failing regardless of what parameters are supplied
internal static VirtualDiskSnapshotInformation GetSnapshotInformation(string path, Guid snapshotId)
{
try
{
var info = new uint[] { 2 };
System.Management.ManagementBaseObject output = scope.CallJobMethodAsync(default, default, "Msvm_ImageManagementService", "GetVHDSnapshotInformation",
("VHDSetPath", path), ("AdditionalInformation", info), ("SnapshotIds", new[] { snapshotId.ToString("D") })).Result;
output.GetResultOrThrow(true);
return VirtualDiskSnapshotInformation.Parse(Convert.ToString(output.Properties["SnapshotInformation"].Value));
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to get virtual disk snapshot information.", ex);
}
}
/// <summary>Sets the snapshot information asynchronous.</summary>
/// <param name="info">The information.</param>
/// <exception cref="System.InvalidOperationException">Failed to set virtual disk snapshot information.</exception>
// TODO: Fix problem with it failing regardless of what parameters are supplied
internal static async Task SetSnapshotInformationAsync(VirtualDiskSnapshotInformation info)
{
try
{
System.Management.ManagementBaseObject output = await scope.CallJobMethodAsync(default, default, "Msvm_ImageManagementService", "SetVHDSnapshotInformation",
("Information", info.GetInstanceText()));
output.GetResultOrThrow(true);
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to set virtual disk snapshot information.", ex);
}
}
// TODO: Create listener events for states in RequestStateChange
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
using System.Runtime.Serialization;
using Vanara.Management;
namespace Vanara.IO;
/// <summary>Provides information about a VHD Set file.</summary>
[DataContract(Name = "Msvm_VHDSetInformation", Namespace = @"root\virtualization\v2")]
public class VirtualDiskSetInformation
{
/// <summary>
/// A list of all files encompassed by the VHD Set file, including any unreferenced files and any parents of the root virtual hard
/// disk. All files listed after the root virtual hard disk are unmanaged by this VHD Set file. This field may be empty if this
/// information was not specifically requested.
/// </summary>
public string[] AllPaths { get; internal set; }
/// <summary>The path of the VHD Set file.</summary>
public string Path { get; internal set; }
/// <summary>A list of GUIDs representing all of the snapshots contained by this VHD Set file.</summary>
[IgnoreDataMember]
public Guid[] SnapshotIdList => Array.ConvertAll(Ids, s => Guid.Parse(s));
[DataMember(Name = "SnapshotIdList")]
internal string[] Ids { get; set; }
internal static VirtualDiskSetInformation Parse(string embeddedInstance) => ManagementExtensions.Parse<VirtualDiskSetInformation>(embeddedInstance);
}

View File

@ -0,0 +1,72 @@
using System.Runtime.Serialization;
using Vanara.Management;
namespace Vanara.IO;
/// <summary>Provides information about a snapshot within a VHD Set file.</summary>
[DataContract(Name = "Msvm_VHDSnapshotInformation", Namespace = @"root\virtualization\v2")]
public class VirtualDiskSnapshotInformation
{
/// <summary>Initializes a new instance of the <see cref="VirtualDiskSnapshotInformation"/> class.</summary>
/// <param name="vhdsFilePath">The path of the VHD Set file.</param>
/// <param name="snapshotId">
/// A GUID that uniquely identifies this snapshot within the VHD Set file.
/// <para>
/// If the supplied Snapshot Id already exists, the existing Snapshot entry will be overwritten with the new entry. Otherwise, the new
/// entry will be added to the VHD Set file.
/// </para>
/// </param>
/// <param name="resilientChangeTrackingId">The optional resilient change tracking ID associated with this snapshot.</param>
public VirtualDiskSnapshotInformation(string vhdsFilePath, Guid snapshotId, string resilientChangeTrackingId = null)
{
FilePath = vhdsFilePath;
SnapshotId = snapshotId;
ResilientChangeTrackingId = resilientChangeTrackingId;
}
/// <summary>Initializes a new instance of the <see cref="VirtualDiskSnapshotInformation"/> class.</summary>
public VirtualDiskSnapshotInformation() { }
/// <summary>Gets or sets the date and time of this snapshot's creation.</summary>
[IgnoreDataMember]
public DateTime? CreationTime
{
get => ManagementExtensions.CimToDateTime(CreationTimeString);
// set => CreationTimeString = value.HasValue ? value.Value.DateTimeToCim() : null;
}
/// <summary>The path of the VHD Set file.</summary>
public string FilePath { get; set; }
/// <summary>
/// A list of file paths representing all of the files on which this snapshot depends. This field will be empty unless specifically
/// requested. The first entry is the file's immediate parent, with the last entry being the root.
/// </summary>
public string[] ParentPathsList { get; internal set; }
/// <summary>Gets or sets the resilient change tracking ID, if any, associated with this snapshot.</summary>
public string ResilientChangeTrackingId { get; set; }
/// <summary>A GUID that uniquely identifies this snapshot within the VHD Set file.</summary>
[IgnoreDataMember]
public Guid? SnapshotId
{
get => Guid.TryParse(Id, out var id) ? id : null;
set => Id = value?.ToString("D");
}
/// <summary>The path of the file represented by this snapshot. This field may be empty if there is no file associated with this snapshot.</summary>
public string SnapshotPath { get; set; }
[DataMember(Name = "CreationTime")]
internal string CreationTimeString { get; set; }
[DataMember(Name = "SnapshotId")]
internal string Id { get; set; }
internal static VirtualDiskSnapshotInformation Parse(string embeddedInstance) => ManagementExtensions.Parse<VirtualDiskSnapshotInformation>(embeddedInstance);
/// <summary>Gets the embedded instance string usable by WMI</summary>
/// <returns>Embedded instance string usable by WMI.</returns>
internal string GetInstanceText(string serverName = ".") => ManagementExtensions.GetInstanceText(this, serverName);
}

View File

@ -1,5 +1,4 @@
using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization;
namespace Vanara.IO
{