Skip to content

Commit

Permalink
Propagate error info from Hyper-V manager to the callers and telemetr…
Browse files Browse the repository at this point in the history
…y for configuration operation
  • Loading branch information
sshilov7 committed Sep 14, 2024
1 parent 9e0592b commit 4684e90
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Xml.Serialization;

namespace HyperVExtension.HostGuestCommunication;

// Helper class to convert from the DevSetupEngine COM types to the .NET types and use them
Expand Down Expand Up @@ -55,16 +57,19 @@ public ApplyConfigurationResult()
{
}

public ApplyConfigurationResult(int resultCode, string? resultDescription = null)
public ApplyConfigurationResult(int resultCode, string? resultDescription = null, string? resultDiagnosticText = null)
{
ResultCode = resultCode;
ResultDescription = resultDescription ?? string.Empty;
ResultDiagnosticText = resultDiagnosticText ?? string.Empty;
}

public int ResultCode { get; set; }

public string ResultDescription { get; set; } = string.Empty;

public string ResultDiagnosticText { get; set; } = string.Empty;

public OpenConfigurationSetResult? OpenConfigurationSetResult { get; set; }

public ApplyConfigurationSetResult? ApplyConfigurationSetResult { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private SDK.ConfigurationSetChangeData GetSdkProgressData(
return new SDK.ApplyConfigurationResult(sdkOpenConfigurationSetResult, sdkApplyConfigurationSetResult);
}

var hresultException = new HResultException(completionStatus.ResultCode);
var hresultException = new HResultException(completionStatus.ResultCode, completionStatus.ResultDiagnosticText);

return new SDK.ApplyConfigurationResult(hresultException, completionStatus.ResultDescription, hresultException.Message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ namespace HyperVExtension.Exceptions;

public class HyperVManagerException : Exception
{
public HyperVManagerException(string? message)
public HyperVManagerException(string? message, int hresult = 0)
: base(message)
{
HResult = hresult;
}

public HyperVManagerException(string? message, Exception? innerException)
: base(message, innerException)
{
HResult = innerException?.HResult ?? 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,10 @@ public SDK.ApplyConfigurationResult ApplyConfiguration(ApplyConfigurationOperati
var startResult = Start(string.Empty);
if (startResult.Result.Status == ProviderOperationStatus.Failure)
{
return operation.CompleteOperation(new HostGuestCommunication.ApplyConfigurationResult(startResult.Result.ExtendedError.HResult, startResult.Result.DisplayMessage));
return operation.CompleteOperation(new HostGuestCommunication.ApplyConfigurationResult(
startResult.Result.ExtendedError.HResult,
startResult.Result.DisplayMessage,
startResult.Result.DiagnosticText));
}

using var guestSession = new GuestKvpSession(Guid.Parse(Id));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ public interface IPowerShellSession

/// <summary> Gets the error messages associated with this instance of the PowerShell session. </summary>
public string GetErrorMessages();

/// <summary> Gets the first error HRESULT associated with this instance of the PowerShell session. </summary>
public int GetErrorFirstHResult();
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ public class PowerShellResult : PowerShellResultBase

/// <inheritdoc cref="PowerShellResultBase.CommandOutputErrorMessage"/>
public override string CommandOutputErrorMessage { get; set; } = string.Empty;

/// <inheritdoc cref="PowerShellResultBase.CommandOutputErrorFirstHresult"/>
public override int CommandOutputErrorFirstHResult { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,12 @@ public abstract class PowerShellResultBase
/// in the PowerShell runspace. This is used for logging and debugging purposes.
/// </remark>
public abstract string CommandOutputErrorMessage { get; set; }

/// <summary> Gets or sets HRESULT from the first error return by PowerShell. </summary>
/// <remark>
/// This is the error HRESULT that is returned when the command returns an error
/// in the PowerShell runspace. PowerShell can return a list of exceptions. This is the HRESULT
/// from the first exception in the list. This is used for logging and debugging purposes.
/// </remark>
public abstract int CommandOutputErrorFirstHResult { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Management.Automation;

namespace HyperVExtension.Models;
Expand Down Expand Up @@ -55,7 +56,30 @@ public void ClearSession()
/// <inheritdoc cref="IPowerShellSession.GetErrorMessages"/>
public string GetErrorMessages()
{
return string.Join(Environment.NewLine, _powerShellSession.Streams.Error.Select(err => err.Exception.Message));
if (_powerShellSession.Streams.Error.Count > 0)
{
List<string> errors = new List<string>();
for (int i = 0; i < _powerShellSession.Streams.Error.Count; i++)
{
var exception = _powerShellSession.Streams.Error[i].Exception;
errors.Add($"{exception.Message} (0x{exception.HResult.ToString("X", CultureInfo.InvariantCulture)})");
}

return string.Join(Environment.NewLine, errors);
}

return string.Empty;
}

/// <inheritdoc cref="IPowerShellSession.GetErrorFirstHResult"/>
public int GetErrorFirstHResult()
{
if (_powerShellSession.Streams.Error.Count > 0)
{
return _powerShellSession.Streams.Error[0].Exception.HResult;
}

return 0;
}

protected virtual void Dispose(bool disposing)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using HyperVExtension.Models;
using Microsoft.Extensions.Hosting;
using Serilog;
using Windows.Win32.Foundation;

namespace HyperVExtension.Services;

Expand Down Expand Up @@ -136,7 +137,9 @@ public HyperVVirtualMachine GetVirtualMachine(Guid vmId)

if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException($"Unable to get VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}");
throw new HyperVManagerException(
$"Unable to get VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}",
result.CommandOutputErrorFirstHResult);
}

// If we found the VM there should only be one psObject in the list.
Expand All @@ -146,7 +149,9 @@ public HyperVVirtualMachine GetVirtualMachine(Guid vmId)
return _hyperVVirtualMachineFactory(psObject);
}

throw new HyperVManagerException($"Unable to get VM with Id {vmId} due to PowerShell returning a null PsObject");
throw new HyperVManagerException(
$"Unable to get VM with Id {vmId} due to PowerShell returning a null PsObject",
HRESULT.E_UNEXPECTED);
}
finally
{
Expand Down Expand Up @@ -188,7 +193,9 @@ public bool StopVirtualMachine(Guid vmId, StopVMKind stopVMKind)

if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException($"Unable to stop VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}");
throw new HyperVManagerException(
$"Unable to stop VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}",
result.CommandOutputErrorFirstHResult);
}

// The VM will be the returned object since we used the "PassThru" parameter.
Expand Down Expand Up @@ -229,7 +236,9 @@ public bool StartVirtualMachine(Guid vmId)

if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException($"Unable to start VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}");
throw new HyperVManagerException(
$"Unable to start VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}",
result.CommandOutputErrorFirstHResult);
}

// The VM will be the returned object since we used the "PassThru" parameter.
Expand Down Expand Up @@ -270,7 +279,9 @@ public bool PauseVirtualMachine(Guid vmId)

if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException($"Unable to pause VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}");
throw new HyperVManagerException(
$"Unable to pause VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}",
result.CommandOutputErrorFirstHResult);
}

// The VM will be the returned object since we used the "PassThru" parameter.
Expand Down Expand Up @@ -313,7 +324,9 @@ public bool ResumeVirtualMachine(Guid vmId)

if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException($"Unable to resume VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}");
throw new HyperVManagerException(
$"Unable to resume VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}",
result.CommandOutputErrorFirstHResult);
}

// The VM will be the returned object since we used the "PassThru" parameter.
Expand Down Expand Up @@ -356,7 +369,9 @@ public bool RemoveVirtualMachine(Guid vmId)
var result = _powerShellService.Execute(commandLineStatements, PipeType.PipeOutput);
if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException($"Unable to remove VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}");
throw new HyperVManagerException(
$"Unable to remove VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}",
result.CommandOutputErrorFirstHResult);
}

// The VM will be the returned object since we used the "PassThru" parameter.
Expand Down Expand Up @@ -544,7 +559,7 @@ public bool CreateCheckpoint(Guid vmId)
if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException(
$"Unable to create a new checkpoint for VM with Id: {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}");
$"Unable to create a new checkpoint for VM with Id: {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}", result.CommandOutputErrorFirstHResult);
}

var newCheckpoint = result.PsObjects.FirstOrDefault();
Expand Down Expand Up @@ -587,7 +602,9 @@ public bool RestartVirtualMachine(Guid vmId)

if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException($"Unable to start VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}");
throw new HyperVManagerException(
$"Unable to start VM with Id {vmId} due to PowerShell error: {result.CommandOutputErrorMessage}",
result.CommandOutputErrorFirstHResult);
}

// The VM will be the returned object since we used the "PassThru" parameter.
Expand Down Expand Up @@ -644,7 +661,9 @@ public HyperVVirtualMachineHost GetVirtualMachineHost()

if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException($"Unable to get Local Hyper-V host: {result.CommandOutputErrorMessage}");
throw new HyperVManagerException(
$"Unable to get Local Hyper-V host: {result.CommandOutputErrorMessage}",
result.CommandOutputErrorFirstHResult);
}

// return the host object. It should be the only object in the list.
Expand All @@ -668,7 +687,9 @@ public HyperVVirtualMachine CreateVirtualMachineFromGallery(VirtualMachineCreati
var result = _powerShellService.Execute(statementBuilderForNewVm, PipeType.None);
if (!string.IsNullOrEmpty(result.CommandOutputErrorMessage))
{
throw new HyperVManagerException($"Unable to create the virtual machine: {result.CommandOutputErrorMessage}");
throw new HyperVManagerException(
$"Unable to create the virtual machine: {result.CommandOutputErrorMessage}",
result.CommandOutputErrorFirstHResult);
}

var returnedPsObject = result.PsObjects.First();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ public PowerShellResult Execute(IEnumerable<PowerShellCommandlineStatement> comm
_powerShellSession.ClearSession();
var psObjectList = ExecuteStatements(commandLineStatements, pipeType);
var commandOutputErrorMessage = _powerShellSession.GetErrorMessages();
var hresult = _powerShellSession.GetErrorFirstHResult();

return new PowerShellResult
{
PsObjects = psObjectList,
CommandOutputErrorMessage = commandOutputErrorMessage,
CommandOutputErrorFirstHResult = hresult,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,10 @@ public string GetErrorMessages()
{
return ErrorText;
}

/// <inheritdoc cref="IPowerShellSession.GetErrorFirstHResult"/>
public int GetErrorFirstHResult()
{
return 0;
}
}

0 comments on commit 4684e90

Please sign in to comment.