From 8faf84770d73eab3cac3816409a7d758f340365a Mon Sep 17 00:00:00 2001 From: Branden Bonaby <105318831+bbonaby@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:16:19 -0700 Subject: [PATCH 01/22] Add Hyper-V settings card styled adaptive cards (#2489) * add initial changes * add parsers * fix build * update existing classes and xaml * add hyper-v adaptive card for creation * add changes for hyper-v * fix build * update with latest changes * add recent changes * fix build * update comments and itemsviews choice set to prevent the choiceset from holding onto items view control internally * Use adaptive card render service to render adaptive card within content dialog * update json * remove DevHome.Common using statement from HyperVProvider to fix build --- .../src/HyperVExtension/Constants.cs | 2 + .../AdaptiveCardInvalidActionException.cs | 18 + .../Helpers/AdaptiveCardActionPayload.cs | 5 + .../HyperVExtension/HyperVExtension.csproj | 9 +- .../VMGalleryCreationAdaptiveCardSession.cs | 341 ++++++++++++++++++ .../VMGalleryJsonToClasses/VMGalleryDisk.cs | 2 +- .../VMGalleryCreationUserInput.cs | 3 + .../Providers/HyperVProvider.cs | 15 +- .../Services/DownloaderService.cs | 6 + .../Services/IDownloaderService.cs | 2 + .../Services/VMGalleryService.cs | 10 + .../Strings/en-US/Resources.resw | 68 ++++ .../InitialVMGalleryCreationForm.json | 85 +++++ .../Templates/ReviewFormForVMGallery.json | 98 +++++ .../Mocks/DownloaderServiceMock.cs | 6 + 15 files changed, 664 insertions(+), 6 deletions(-) create mode 100644 HyperVExtension/src/HyperVExtension/Exceptions/AdaptiveCardInvalidActionException.cs create mode 100644 HyperVExtension/src/HyperVExtension/Models/VMGalleryCreationAdaptiveCardSession.cs create mode 100644 HyperVExtension/src/HyperVExtension/Templates/InitialVMGalleryCreationForm.json create mode 100644 HyperVExtension/src/HyperVExtension/Templates/ReviewFormForVMGallery.json diff --git a/HyperVExtension/src/HyperVExtension/Constants.cs b/HyperVExtension/src/HyperVExtension/Constants.cs index 8a9971b66a..378dbe17c3 100644 --- a/HyperVExtension/src/HyperVExtension/Constants.cs +++ b/HyperVExtension/src/HyperVExtension/Constants.cs @@ -22,4 +22,6 @@ internal sealed class Constants #else public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome.Dev/Files/HyperVExtension/Assets/hyper-v-provider-icon.png"; #endif + + public const string ExtensionIconInternal = "ms-appx:///HyperVExtension/Assets/hyper-v-provider-icon.png"; } diff --git a/HyperVExtension/src/HyperVExtension/Exceptions/AdaptiveCardInvalidActionException.cs b/HyperVExtension/src/HyperVExtension/Exceptions/AdaptiveCardInvalidActionException.cs new file mode 100644 index 0000000000..38c73aeddc --- /dev/null +++ b/HyperVExtension/src/HyperVExtension/Exceptions/AdaptiveCardInvalidActionException.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HyperVExtension.Exceptions; + +public class AdaptiveCardInvalidActionException : Exception +{ + public AdaptiveCardInvalidActionException(string message) + : base(message) + { + } +} diff --git a/HyperVExtension/src/HyperVExtension/Helpers/AdaptiveCardActionPayload.cs b/HyperVExtension/src/HyperVExtension/Helpers/AdaptiveCardActionPayload.cs index 3b3d4f1b3c..d4bc237b70 100644 --- a/HyperVExtension/src/HyperVExtension/Helpers/AdaptiveCardActionPayload.cs +++ b/HyperVExtension/src/HyperVExtension/Helpers/AdaptiveCardActionPayload.cs @@ -30,6 +30,11 @@ public string? Type get; set; } + public string? Mode + { + get; set; + } + public bool IsCancelAction() { return Id == "cancelAction"; diff --git a/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj b/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj index 446faa6e5c..0b72751d04 100644 --- a/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj +++ b/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj @@ -19,7 +19,14 @@ Always - + + + Always + + + Always + + diff --git a/HyperVExtension/src/HyperVExtension/Models/VMGalleryCreationAdaptiveCardSession.cs b/HyperVExtension/src/HyperVExtension/Models/VMGalleryCreationAdaptiveCardSession.cs new file mode 100644 index 0000000000..9ebeef4edb --- /dev/null +++ b/HyperVExtension/src/HyperVExtension/Models/VMGalleryCreationAdaptiveCardSession.cs @@ -0,0 +1,341 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Runtime.InteropServices.WindowsRuntime; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using HyperVExtension.Common; +using HyperVExtension.Exceptions; +using HyperVExtension.Helpers; +using HyperVExtension.Models.VirtualMachineCreation; +using HyperVExtension.Models.VMGalleryJsonToClasses; +using Microsoft.Windows.DevHome.SDK; +using Serilog; +using Windows.Foundation; +using Windows.Storage; +using Windows.Storage.Streams; + +namespace HyperVExtension.Models; + +public enum SessionState +{ + InitialCreationForm, + ReviewForm, +} + +public class VMGalleryCreationAdaptiveCardSession : IExtensionAdaptiveCardSession2 +{ + private readonly Serilog.ILogger _log = Log.ForContext("SourceContext", nameof(HyperVVirtualMachine)); + + private readonly string _pathToInitialCreationFormTemplate = Path.Combine(AppContext.BaseDirectory, @"HyperVExtension\Templates\", "InitialVMGalleryCreationForm.json"); + + private readonly string _pathToReviewFormTemplate = Path.Combine(AppContext.BaseDirectory, @"HyperVExtension\Templates\", "ReviewFormForVMGallery.json"); + + private readonly string _adaptiveCardNextButtonId = "DevHomeMachineConfigurationNextButton"; + + /// + /// The gallery images that will be displayed in the initial creation form. We retrieve these from the VMGallery.json file in the Microsoft servers. + /// + private readonly VMGalleryImageList _vMGalleryImageList; + + private readonly IStringResource _stringResource; + + /// + /// Gets the Json string that represents the user input that was passed to the adaptive card session. We'll keep this so we can pass it back to Dev Home + /// at the end of the session. + /// + public string OriginalUserInputJson { get; private set; } = string.Empty; + + public event TypedEventHandler? Stopped; + + public void Dispose() + { + } + + public VMGalleryCreationAdaptiveCardSession(VMGalleryImageList galleryImages, IStringResource stringResource) + { + _vMGalleryImageList = galleryImages; + _stringResource = stringResource; + } + + private IExtensionAdaptiveCard? _creationAdaptiveCard; + + public bool ShouldEndSession { get; private set; } + + public ProviderOperationResult Initialize(IExtensionAdaptiveCard extensionUI) + { + _creationAdaptiveCard = extensionUI; + + return GetInitialCreationFormAdaptiveCard(); + } + + public IAsyncOperation OnAction(string action, string inputs) + { + return Task.Run(async () => + { + ProviderOperationResult operationResult; + var shouldEndSession = false; + var adaptiveCardStateNotRecognizedError = _stringResource.GetLocalized("AdaptiveCardStateNotRecognizedError"); + + var actionPayload = Helpers.Json.ToObject(action); + if (actionPayload == null) + { + _log.Error($"Actions in Adaptive card action Json not recognized: {action}"); + var creationFormGenerationError = _stringResource.GetLocalized("AdaptiveCardUnRecognizedAction"); + var exception = new AdaptiveCardInvalidActionException(creationFormGenerationError); + return new ProviderOperationResult(ProviderOperationStatus.Failure, exception, creationFormGenerationError, creationFormGenerationError); + } + + switch (_creationAdaptiveCard?.State) + { + case "initialCreationForm": + operationResult = await HandleActionWhenFormInInitalState(actionPayload, inputs); + break; + case "reviewForm": + (operationResult, shouldEndSession) = await HandleActionWhenFormInReviewState(actionPayload); + break; + default: + shouldEndSession = true; + operationResult = new ProviderOperationResult( + ProviderOperationStatus.Failure, + new InvalidOperationException(nameof(action)), + adaptiveCardStateNotRecognizedError, + $"Unexpected state:{_creationAdaptiveCard?.State}"); + break; + } + + if (shouldEndSession) + { + // The session has now ended. We'll raise the Stopped event to notify anyone in Dev Home who was listening to this event, + // that the session has ended. + Stopped?.Invoke( + this, + new ExtensionAdaptiveCardSessionStoppedEventArgs(operationResult, OriginalUserInputJson)); + } + + return operationResult; + }).AsAsyncOperation(); + } + + /// + /// Loads the adaptive card template based on the session state. + /// + /// State the adaptive card session + /// A Json string representing the adaptive card + public string LoadTemplate(SessionState state) + { + var pathToTemplate = state switch + { + SessionState.InitialCreationForm => _pathToInitialCreationFormTemplate, + SessionState.ReviewForm => _pathToReviewFormTemplate, + _ => _pathToInitialCreationFormTemplate, + }; + + return File.ReadAllText(pathToTemplate, Encoding.Default); + } + + /// + /// Creates the initial form that will be displayed to the user. It will be a list of Windows community toolkit's settings + /// cards that can be displayed in the Dev Homes UI. + /// + /// Result of the operation + private ProviderOperationResult GetInitialCreationFormAdaptiveCard() + { + try + { + // Create the JSON array for the gallery images and add the data for each image. + // these will be display in the initial creation form. + var jsonArrayOfGalleryImages = new JsonArray(); + var primaryButtonForCreationFlowText = _stringResource.GetLocalized("PrimaryButtonLabelForCreationFlow"); + var secondaryButtonForCreationFlowText = _stringResource.GetLocalized("SecondaryButtonLabelForCreationFlow"); + var secondaryButtonForContentDialogText = _stringResource.GetLocalized("SecondaryButtonForContentDialogText"); + var buttonToLaunchContentDialogLabel = _stringResource.GetLocalized("ButtonToLaunchContentDialogLabel"); + var settingsCardLabel = _stringResource.GetLocalized("SettingsCardLabel"); + var enterNewVMNameLabel = _stringResource.GetLocalized("EnterNewVMNameLabel"); + var enterNewVMNamePlaceHolder = _stringResource.GetLocalized("EnterNewVMNamePlaceHolder"); + + foreach (var image in _vMGalleryImageList.Images) + { + var dataJson = new JsonObject + { + { "ImageDescription", GetMergedDescription(image) }, + { "SubDescription", image.Publisher }, + { "Header", image.Name }, + { "HeaderIcon", image.Symbol.Base64Image }, + { "ActionButtonText", "More info" }, + { "ContentDialogInfo", SetupContentDialogInfo(image) }, + { "ButtonToLaunchContentDialogLabel", buttonToLaunchContentDialogLabel }, + { "SecondaryButtonForContentDialogText", secondaryButtonForContentDialogText }, + }; + + jsonArrayOfGalleryImages.Add(dataJson); + } + + var templateData = + $"{{\"PrimaryButtonLabelForCreationFlow\" : \"{primaryButtonForCreationFlowText}\"," + + $"\"SecondaryButtonLabelForCreationFlow\" : \"{secondaryButtonForCreationFlowText}\"," + + $"\"SettingsCardLabel\": \"{settingsCardLabel}\"," + + $"\"EnterNewVMNameLabel\": \"{enterNewVMNameLabel}\"," + + $"\"EnterNewVMNamePlaceHolder\": \"{enterNewVMNamePlaceHolder}\"," + + $"\"GalleryImages\" : {jsonArrayOfGalleryImages.ToJsonString()}" + + $"}}"; + + var template = LoadTemplate(SessionState.InitialCreationForm); + + return _creationAdaptiveCard!.Update(template, templateData, "initialCreationForm"); + } + catch (Exception ex) + { + var creationFormGenerationError = _stringResource.GetLocalized("InitialCreationFormGenerationFailedError"); + return new ProviderOperationResult(ProviderOperationStatus.Failure, ex, creationFormGenerationError, ex.Message); + } + } + + /// + /// Creates the review form that will be displayed to the user. This will be an adaptive card that is displayed in Dev Homes + /// setup flow review page. + /// + /// Result of the operation + private async Task GetForReviewFormAdaptiveCardAsync(string inputJson) + { + try + { + var deserializedObject = JsonSerializer.Deserialize(inputJson, typeof(VMGalleryCreationUserInput)); + var inputForGalleryOperation = deserializedObject as VMGalleryCreationUserInput ?? throw new InvalidOperationException($"Json deserialization failed for input Json: {inputJson}"); + + if (inputForGalleryOperation.SelectedImageListIndex < 0 || + inputForGalleryOperation.SelectedImageListIndex > _vMGalleryImageList.Images.Count) + { + return new ProviderOperationResult(ProviderOperationStatus.Failure, null, "Failed to get review form", "Selected image index is out of range"); + } + + var galleryImage = _vMGalleryImageList.Images[inputForGalleryOperation.SelectedImageListIndex]; + var newVirtualMachineNameLabel = _stringResource.GetLocalized("NameLabelForNewVirtualMachine", ":"); + var primaryButtonForCreationFlowText = _stringResource.GetLocalized("PrimaryButtonLabelForCreationFlow"); + var secondaryButtonForCreationFlowText = _stringResource.GetLocalized("SecondaryButtonLabelForCreationFlow"); + var storageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(Constants.ExtensionIconInternal)); + var randomAccessStream = await storageFile.OpenReadAsync(); + + // Convert the stream to a byte array + var bytes = new byte[randomAccessStream.Size]; + await randomAccessStream.ReadAsync(bytes.AsBuffer(), (uint)randomAccessStream.Size, InputStreamOptions.None); + var providerBase64Image = Convert.ToBase64String(bytes); + var reviewFormData = new JsonObject + { + { "ProviderName", HyperVStrings.HyperVProviderDisplayName }, + { "DiskImageSize", BytesHelper.ConvertBytesToString(galleryImage.Disk.SizeInBytes) }, + { "VMGalleryImageName", galleryImage.Name }, + { "Publisher", galleryImage.Publisher }, + { "NameOfNewVM", inputForGalleryOperation.NewVirtualMachineName }, + { "NameLabel", newVirtualMachineNameLabel }, + { "Base64ImageForProvider", providerBase64Image }, + { "DiskImageUrl", galleryImage.Symbol.Uri }, + { "PrimaryButtonLabelForCreationFlow", primaryButtonForCreationFlowText }, + { "SecondaryButtonLabelForCreationFlow", secondaryButtonForCreationFlowText }, + }; + + var template = LoadTemplate(SessionState.ReviewForm); + + return _creationAdaptiveCard!.Update(LoadTemplate(SessionState.ReviewForm), reviewFormData.ToJsonString(), "reviewForm"); + } + catch (Exception ex) + { + var reviewFormGenerationError = _stringResource.GetLocalized("ReviewFormGenerationFailedError"); + return new ProviderOperationResult(ProviderOperationStatus.Failure, ex, reviewFormGenerationError, ex.Message); + } + } + + /// + /// The discription for VM gallery images is stored in a list of strings. This method merges the strings into one string. + /// + /// The c# class that represents the gallery image + /// A string that combines the original list of strings into one + public string GetMergedDescription(VMGalleryImage image) + { + var description = string.Empty; + for (var i = 0; i < image.Description.Count; i++) + { + description += image.Description[i].Replace("\n", string.Empty).Replace("\r", string.Empty); + } + + return string.Join(string.Empty, description); + } + + /// + /// In Dev Homes UI, the user can click a more info button to get more information about the VM gallery image. + /// This method sets up the content dialog's body data that will be displayed when the user clicks the more info button. + /// + /// The c# class that represents the gallery image + /// A Json object that contains the data needed to display an adaptive card within a content dialogs body + private JsonObject SetupContentDialogInfo(VMGalleryImage image) + { + var adaptiveCardImageFacts = new JsonArray(); + foreach (var fact in image.Details) + { + var adaptiveCardfactObj = new JsonObject + { + { "title", fact.Name }, + { "value", fact.Value }, + }; + adaptiveCardImageFacts.Add(adaptiveCardfactObj); + } + + var osVersionForContentDialog = _stringResource.GetLocalized("OsVersionForContentDialog"); + var localeForContentDialog = _stringResource.GetLocalized("LocaleForContentDialog"); + var lastUpdatedForContentDialog = _stringResource.GetLocalized("LastUpdatedForContentDialog"); + var downloadForContentDialog = _stringResource.GetLocalized("DownloadForContentDialog"); + + adaptiveCardImageFacts.Add(new JsonObject() { { "title", osVersionForContentDialog }, { "value", image.Version } }); + adaptiveCardImageFacts.Add(new JsonObject() { { "title", localeForContentDialog }, { "value", image.Locale } }); + adaptiveCardImageFacts.Add(new JsonObject() { { "title", lastUpdatedForContentDialog }, { "value", image.LastUpdated.ToLongDateString() } }); + adaptiveCardImageFacts.Add(new JsonObject() { { "title", downloadForContentDialog }, { "value", BytesHelper.ConvertBytesToString(image.Disk.SizeInBytes) } }); + + return new JsonObject + { + { "GalleryImageFacts", adaptiveCardImageFacts }, + { "ImageDescription", GetMergedDescription(image) }, + }; + } + + private async Task HandleActionWhenFormInInitalState(AdaptiveCardActionPayload actionPayload, string inputs) + { + ProviderOperationResult operationResult; + var actionButtonId = actionPayload.Id ?? string.Empty; + + if (actionButtonId.Equals(_adaptiveCardNextButtonId, StringComparison.OrdinalIgnoreCase)) + { + // if OnAction's state is initialCreationForm, then the user has selected a VM gallery image and is ready to review the form. + // we'll also keep the original user input so we can pass it back to Dev Home once the session ends. + OriginalUserInputJson = inputs; + operationResult = await GetForReviewFormAdaptiveCardAsync(inputs); + } + else + { + operationResult = GetInitialCreationFormAdaptiveCard(); + } + + return operationResult; + } + + private async Task<(ProviderOperationResult, bool)> HandleActionWhenFormInReviewState(AdaptiveCardActionPayload actionPayload) + { + ProviderOperationResult operationResult; + var shouldEndSession = false; + var actionButtonId = actionPayload.Id ?? string.Empty; + + if (actionButtonId.Equals(_adaptiveCardNextButtonId, StringComparison.OrdinalIgnoreCase)) + { + // if OnAction's state is reviewForm, then the user has reviewed the form and Dev Home has started the creation process. + // we'll show the same form to the user in Dev Homes summary page. + shouldEndSession = true; + operationResult = await GetForReviewFormAdaptiveCardAsync(OriginalUserInputJson); + } + else + { + operationResult = GetInitialCreationFormAdaptiveCard(); + } + + return (operationResult, shouldEndSession); + } +} diff --git a/HyperVExtension/src/HyperVExtension/Models/VMGalleryJsonToClasses/VMGalleryDisk.cs b/HyperVExtension/src/HyperVExtension/Models/VMGalleryJsonToClasses/VMGalleryDisk.cs index f8078cfcfb..9bf8594afd 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VMGalleryJsonToClasses/VMGalleryDisk.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VMGalleryJsonToClasses/VMGalleryDisk.cs @@ -10,5 +10,5 @@ public sealed class VMGalleryDisk : VMGalleryItemWithHashBase { public string ArchiveRelativePath { get; set; } = string.Empty; - public long SizeInBytes { get; set; } + public ulong SizeInBytes { get; set; } } diff --git a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryCreationUserInput.cs b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryCreationUserInput.cs index 51d52cba28..d43193e2b5 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryCreationUserInput.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryCreationUserInput.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Text.Json.Serialization; + namespace HyperVExtension.Models.VirtualMachineCreation; /// @@ -10,5 +12,6 @@ public sealed class VMGalleryCreationUserInput { public string NewVirtualMachineName { get; set; } = string.Empty; + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public int SelectedImageListIndex { get; set; } } diff --git a/HyperVExtension/src/HyperVExtension/Providers/HyperVProvider.cs b/HyperVExtension/src/HyperVExtension/Providers/HyperVProvider.cs index c625cd657e..b9d5b778e1 100644 --- a/HyperVExtension/src/HyperVExtension/Providers/HyperVProvider.cs +++ b/HyperVExtension/src/HyperVExtension/Providers/HyperVProvider.cs @@ -4,6 +4,7 @@ using System.Text.Json; using HyperVExtension.Common; using HyperVExtension.Helpers; +using HyperVExtension.Models; using HyperVExtension.Models.VirtualMachineCreation; using HyperVExtension.Services; using Microsoft.Windows.DevHome.SDK; @@ -25,14 +26,21 @@ public class HyperVProvider : IComputeSystemProvider private readonly VmGalleryCreationOperationFactory _vmGalleryCreationOperationFactory; + private readonly IVMGalleryService _vmGalleryService; + // Temporary will need to add more error strings for different operations. public string OperationErrorString => _stringResource.GetLocalized(errorResourceKey); - public HyperVProvider(IHyperVManager hyperVManager, IStringResource stringResource, VmGalleryCreationOperationFactory vmGalleryCreationOperationFactory) + public HyperVProvider( + IHyperVManager hyperVManager, + IStringResource stringResource, + VmGalleryCreationOperationFactory vmGalleryCreationOperationFactory, + IVMGalleryService vmGalleryService) { _hyperVManager = hyperVManager; _stringResource = stringResource; _vmGalleryCreationOperationFactory = vmGalleryCreationOperationFactory; + _vmGalleryService = vmGalleryService; } /// Gets or sets the default compute system properties. @@ -75,9 +83,8 @@ public IAsyncOperation GetComputeSystemsAsync(IDeveloperId public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForDeveloperId(IDeveloperId developerId, ComputeSystemAdaptiveCardKind sessionKind) { - // This won't be supported until creation is supported. - var notImplementedException = new NotImplementedException($"Method not implemented by Hyper-V Compute System Provider"); - return new ComputeSystemAdaptiveCardResult(notImplementedException, OperationErrorString, notImplementedException.Message); + var imageList = _vmGalleryService.GetGalleryImagesAsync().GetAwaiter().GetResult(); + return new ComputeSystemAdaptiveCardResult(new VMGalleryCreationAdaptiveCardSession(imageList, _stringResource)); } public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForComputeSystem(IComputeSystem computeSystem, ComputeSystemAdaptiveCardKind sessionKind) diff --git a/HyperVExtension/src/HyperVExtension/Services/DownloaderService.cs b/HyperVExtension/src/HyperVExtension/Services/DownloaderService.cs index 9a86fec523..6593a7adfc 100644 --- a/HyperVExtension/src/HyperVExtension/Services/DownloaderService.cs +++ b/HyperVExtension/src/HyperVExtension/Services/DownloaderService.cs @@ -55,6 +55,12 @@ public async Task DownloadByteArrayAsync(string sourceWebUri, Cancellati return await httpClient.GetByteArrayAsync(sourceWebUri, cancellationToken); } + public async Task GetHeaderContentLength(Uri sourceWebUri, CancellationToken cancellationToken) + { + var httpClient = _httpClientFactory.CreateClient(); + return GetTotalBytesToReceive(await httpClient.GetAsync(sourceWebUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken)); + } + private long GetTotalBytesToReceive(HttpResponseMessage response) { if (response.Content.Headers.ContentLength.HasValue) diff --git a/HyperVExtension/src/HyperVExtension/Services/IDownloaderService.cs b/HyperVExtension/src/HyperVExtension/Services/IDownloaderService.cs index 55704fe976..2d4e98226d 100644 --- a/HyperVExtension/src/HyperVExtension/Services/IDownloaderService.cs +++ b/HyperVExtension/src/HyperVExtension/Services/IDownloaderService.cs @@ -35,4 +35,6 @@ public interface IDownloaderService /// A token that can allow the operation to be cancelled while it is running /// Content returned by web server represented as an array of bytes public Task DownloadByteArrayAsync(string sourceWebUri, CancellationToken cancellationToken); + + public Task GetHeaderContentLength(Uri sourceWebUri, CancellationToken cancellationToken); } diff --git a/HyperVExtension/src/HyperVExtension/Services/VMGalleryService.cs b/HyperVExtension/src/HyperVExtension/Services/VMGalleryService.cs index 2c87c051f9..6f1ba9c3f1 100644 --- a/HyperVExtension/src/HyperVExtension/Services/VMGalleryService.cs +++ b/HyperVExtension/src/HyperVExtension/Services/VMGalleryService.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Globalization; using System.Security.Cryptography; using System.Text.Json; using HyperVExtension.Models.VMGalleryJsonToClasses; @@ -73,6 +74,15 @@ public async Task GetGalleryImagesAsync() image.Symbol.Base64Image = Convert.ToBase64String(byteArray); } + + if (!string.IsNullOrEmpty(image.Disk.Uri)) + { + var totalSizeOfDisk = await _downloaderService.GetHeaderContentLength(new Uri(image.Disk.Uri), cancellationTokenSource.Token); + if (ulong.TryParse(image.Requirements.DiskSpace, CultureInfo.InvariantCulture, out var requiredDiskSpace)) + { + image.Disk.SizeInBytes = (ulong)totalSizeOfDisk; + } + } } } catch (Exception ex) diff --git a/HyperVExtension/src/HyperVExtension/Strings/en-US/Resources.resw b/HyperVExtension/src/HyperVExtension/Strings/en-US/Resources.resw index 5586a3455e..2c0fd6dfa5 100644 --- a/HyperVExtension/src/HyperVExtension/Strings/en-US/Resources.resw +++ b/HyperVExtension/src/HyperVExtension/Strings/en-US/Resources.resw @@ -257,4 +257,72 @@ Please log on to your Hyper-V Title text of the dialog asking to log in to Hyper-V VM. + + Adaptive card state not recognized + Error text to show when we don't recognize the state of the adaptive card that was given to us + + + Action passed to the extension was not recognized. View the extension logs for more information + Error text to show when we don't recognize the adaptive card action that was passed to the extension + + + More Info + Text for a button that will launch a content dialog that displays more information to the user about a disk image + + + Download + label text for the download size of the disk image + + + New virtual machine name + Label text for textbox where users will enter the name for their new virtual machine + + + Enter the name of your new virtual machine + place holder text that will appear within a text box + + + Failed to generate the initial creation form + Error text to show the user when the was an error getting the initial disk image selection page in the creation wizard flow + + + Last updated + label text for when the disk image was last updated + + + Locale + label text for locale of operation system that is installed on the disk image + + + Name{0} + Locked="{0}" text label that will be on top of the name the user provides in a textbox. {0} is the colon e.g ":" special character + + + Version + label text for version of operating system that is installed on the disk image + + + Ok + Text for primary button of the content dialog + + + Next + Text to display to the user about what the primary button does in the UI + + + Failed to generate the review form + Error text to show the user when the was an error getting the review page in the creation wizard flow + + + Cancel + Text for the secondary button of the content dialog + + + Previous + Text to display to the user about what the secondary button does in the UI. + + + Choose an image to use + Label text for a list of cards that appear in the UI + \ No newline at end of file diff --git a/HyperVExtension/src/HyperVExtension/Templates/InitialVMGalleryCreationForm.json b/HyperVExtension/src/HyperVExtension/Templates/InitialVMGalleryCreationForm.json new file mode 100644 index 0000000000..95e64d78ba --- /dev/null +++ b/HyperVExtension/src/HyperVExtension/Templates/InitialVMGalleryCreationForm.json @@ -0,0 +1,85 @@ +{ + "type": "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5", + "body": [ + { + "type": "Input.Text", + "id": "NewVirtualMachineName", + "label": "${EnterNewVMNameLabel}", + "placeholder": "${EnterNewVMNamePlaceHolder}", + "maxLength": 100, + "isRequired": true, + "Spacing": "Large" + }, + { + "type": "DevHome.SettingsCardChoiceSet", + "id": "SelectedImageListIndex", + "label": "${SettingsCardLabel}", + "isRequired": true, + "devHomeSettingsCards": [ + { + "type": "DevHome.SettingsCard", + "$data": "${GalleryImages}", + "devHomeSettingsCardDescription": "${SubDescription}", + "devHomeSettingsCardHeader": "${Header}", + "devHomeSettingsCardHeaderIcon": "${HeaderIcon}", + "devHomeSettingsCardActionElement": { + "type": "DevHome.LaunchContentDialogButton", + "devHomeActionText": "${ButtonToLaunchContentDialogLabel}", + "devHomeContentDialogContent": { + "devHomeContentDialogTitle": "${Header}", + "devHomeContentDialogBodyAdaptiveCard": { + "type": "AdaptiveCard", + "version": "1.5", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "body": [ + { + "type": "Container", + "$data": "${ContentDialogInfo}", + "items": [ + { + "type": "TextBlock", + "text": "${ImageDescription}", + "isMultiline": true, + "Spacing": "Medium", + "size": "Medium", + "wrap": true + }, + { + "$data": "${GalleryImageFacts}", + "type": "FactSet", + "facts": [ + { + "title": "${title}", + "value": "${value}" + } + ] + } + ] + } + ] + }, + "devHomeContentDialogSecondaryButtonText": "${SecondaryButtonForContentDialogText}" + } + } + } + ] + }, + { + "type": "ActionSet", + "actions": [ + { + "id": "DevHomeMachineConfigurationNextButton", + "type": "Action.Submit", + "title": "${PrimaryButtonLabelForCreationFlow}" + }, + { + "id": "DevHomeMachineConfigurationPreviousButton", + "type": "Action.Submit", + "title": "${SecondaryButtonLabelForCreationFlow}" + } + ] + } + ] +} \ No newline at end of file diff --git a/HyperVExtension/src/HyperVExtension/Templates/ReviewFormForVMGallery.json b/HyperVExtension/src/HyperVExtension/Templates/ReviewFormForVMGallery.json new file mode 100644 index 0000000000..20335479bd --- /dev/null +++ b/HyperVExtension/src/HyperVExtension/Templates/ReviewFormForVMGallery.json @@ -0,0 +1,98 @@ +{ + "type": "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.6", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "auto", + "spacing": "medium", + "items": [ + { + "type": "TextBlock", + "text": "${ProviderName}", + "wrap": true, + "size": "medium" + }, + { + "type": "TextBlock", + "text": "${DiskImageSize}", + "wrap": true, + "size": "medium" + } + ] + }, + { + "type": "Column", + "width": "auto", + "spacing": "extraLarge", + "items": [ + { + "type": "TextBlock", + "text": "${NameLabel}", + "wrap": true, + "size": "medium" + }, + { + "type": "TextBlock", + "text": "${NameOfNewVM}", + "wrap": true, + "size": "medium" + } + ] + }, + { + "type": "Column", + "width": "auto", + "spacing": "extraLarge", + "verticalContentAlignment": "center", + "items": [ + { + "type": "Image", + "url": "${DiskImageUrl}", + "height": "32px" + } + ] + }, + { + "type": "Column", + "width": "auto", + "spacing": "medium", + "items": [ + { + "type": "TextBlock", + "text": "${VMGalleryImageName}", + "wrap": true, + "size": "medium" + }, + { + "type": "TextBlock", + "text": "${Publisher}", + "wrap": true, + "isSubtle": true, + "size": "medium" + } + ] + } + ] + }, + { + "type": "ActionSet", + "actions": [ + { + "id": "DevHomeMachineConfigurationNextButton", + "type": "Action.Submit", + "title": "${PrimaryButtonLabelForCreationFlow}" + }, + { + "id": "DevHomeMachineConfigurationPreviousButton", + "type": "Action.Submit", + "title": "${SecondaryButtonLabelForCreationFlow}" + } + ] + } + ] +} \ No newline at end of file diff --git a/HyperVExtension/test/HyperVExtension/Mocks/DownloaderServiceMock.cs b/HyperVExtension/test/HyperVExtension/Mocks/DownloaderServiceMock.cs index 06dc15a7ac..712f7cf5a3 100644 --- a/HyperVExtension/test/HyperVExtension/Mocks/DownloaderServiceMock.cs +++ b/HyperVExtension/test/HyperVExtension/Mocks/DownloaderServiceMock.cs @@ -56,4 +56,10 @@ public async Task DownloadByteArrayAsync(string sourceWebUri, Cancellati var httpClient = _httpClientFactory.CreateClient(); return await httpClient.GetByteArrayAsync(sourceWebUri, cancellationToken); } + + public async Task GetHeaderContentLength(Uri sourceWebUri, CancellationToken cancellationToken) + { + await Task.Delay(1, cancellationToken); + return 100L; + } } From 584e03892bbf5e3448b373719710f8006e6235bd Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:24:36 -0400 Subject: [PATCH 02/22] Add SupportedInteraces back into CoreExtension (#2542) --- src/Package.appxmanifest | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Package.appxmanifest b/src/Package.appxmanifest index 27b4f1e112..cc443e874c 100644 --- a/src/Package.appxmanifest +++ b/src/Package.appxmanifest @@ -86,6 +86,8 @@ + + @@ -296,4 +298,4 @@ - \ No newline at end of file + From 2e53127c6a5ec842bb89245b1b0db0d345bf6f8a Mon Sep 17 00:00:00 2001 From: Branden Bonaby <105318831+bbonaby@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:56:22 -0700 Subject: [PATCH 03/22] switch classIds to point to correct extension (#2543) --- src/Package.appxmanifest | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Package.appxmanifest b/src/Package.appxmanifest index cc443e874c..f6308beed4 100644 --- a/src/Package.appxmanifest +++ b/src/Package.appxmanifest @@ -84,7 +84,7 @@ - + @@ -97,7 +97,7 @@ - + From 0db3cfb9a79aa6a716f35c2a4d228835697d7d9e Mon Sep 17 00:00:00 2001 From: Branden Bonaby <105318831+bbonaby@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:57:35 -0700 Subject: [PATCH 04/22] Add initial environments creation flow to the machine configuration tool (#2493) * add initial changes * add parsers * fix build * update existing classes and xaml * add hyper-v adaptive card for creation * add changes for hyper-v * fix build * add changes to show settings cards from hyper v * update with latest changes * add recent changes * fix build * add updates for adaptive cards to appear in review page * add creation to experiements page and fix adaptive card not showing up after moving to a different page * add missing message classes * Delete tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreateEnvironmentTask.cs * update message passing * update based on comments, and add more comments * update comments and itemsviews choice set to prevent the choiceset from holding onto items view control internally * Use adaptive card render service to render adaptive card within content dialog * add updates * Fix merge conflicts and address comments --- .../Strings/en-us/Resources.resw | 8 + src/App.xaml | 1 + src/NavConfig.jsonc | 21 ++ .../Assets/CreateVirtualEnvironment.png | Bin 0 -> 12448 bytes .../AdaptiveCardNotRetrievedException.cs | 18 + .../Extensions/ServiceExtensions.cs | 16 + .../CreationAdaptiveCardSessionEndedData.cs | 31 ++ ...CreationAdaptiveCardSessionEndedMessage.cs | 18 + ...tionOptionsReviewPageDataRequestMessage.cs | 20 ++ .../CreationOptionsViewPageRequestMessage.cs | 20 ++ .../CreationProviderChangedMessage.cs | 19 ++ .../NewAdaptiveCardAvailableMessage.cs | 23 ++ .../Environments/RenderedAdaptiveCardData.cs | 23 ++ .../Selectors/ReviewTabViewSelector.cs | 7 + .../Selectors/SetupFlowViewSelector.cs | 11 +- .../Services/SetupFlowOrchestrator.cs | 55 +++ .../Services/StringResourceKey.cs | 6 + .../Strings/en-us/Resources.resw | 40 +++ .../EnvironmentCreationOptionsTaskGroup.cs | 46 +++ .../SelectEnvironmentProviderTaskGroup.cs | 34 ++ .../ComputeSystemProviderViewModel.cs | 45 +++ .../CreateEnvironmentReviewViewModel.cs | 21 ++ .../EnvironmentCreationOptionsViewModel.cs | 314 ++++++++++++++++++ .../SelectEnvironmentProviderViewModel.cs | 92 +++++ .../ViewModels/MainPageViewModel.cs | 18 + .../ViewModels/SetupPageViewModelBase.cs | 26 ++ .../CreateEnvironmentReviewView.xaml | 21 ++ .../CreateEnvironmentReviewView.xaml.cs | 80 +++++ .../EnvironmentCreationOptionsView.xaml | 61 ++++ .../EnvironmentCreationOptionsView.xaml.cs | 78 +++++ .../SelectEnvironmentProviderView.xaml | 88 +++++ .../SelectEnvironmentProviderView.xaml.cs | 17 + .../DevHome.SetupFlow/Views/MainPageView.xaml | 18 + .../DevHome.SetupFlow/Views/ReviewView.xaml | 6 + .../Views/SetupFlowPage.xaml | 11 + 35 files changed, 1311 insertions(+), 2 deletions(-) create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Assets/CreateVirtualEnvironment.png create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Exceptions/AdaptiveCardNotRetrievedException.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationAdaptiveCardSessionEndedData.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationAdaptiveCardSessionEndedMessage.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationOptionsReviewPageDataRequestMessage.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationOptionsViewPageRequestMessage.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationProviderChangedMessage.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Models/Environments/NewAdaptiveCardAvailableMessage.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Models/Environments/RenderedAdaptiveCardData.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/TaskGroups/EnvironmentCreationOptionsTaskGroup.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SelectEnvironmentProviderTaskGroup.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemProviderViewModel.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/CreateEnvironmentReviewViewModel.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml.cs diff --git a/settings/DevHome.Settings/Strings/en-us/Resources.resw b/settings/DevHome.Settings/Strings/en-us/Resources.resw index c1dcbd8a1b..d73581bd82 100644 --- a/settings/DevHome.Settings/Strings/en-us/Resources.resw +++ b/settings/DevHome.Settings/Strings/en-us/Resources.resw @@ -565,5 +565,13 @@ Silence and track background processes that may hinder device performance Inline description of the Quiet Background Processes experimental feature on the 'Settings -> Experiments' page where you enable it. + + + Create a local or cloud machine from Dev Home + Text within a display card that explains what users can do with the Environments creation feature. Users can choose to toggle this on/off to add or remove the feature. + + + Environments Creation + Title text for the Environments creation feature. \ No newline at end of file diff --git a/src/App.xaml b/src/App.xaml index 8398c3552d..d60987bf60 100644 --- a/src/App.xaml +++ b/src/App.xaml @@ -19,6 +19,7 @@ + diff --git a/src/NavConfig.jsonc b/src/NavConfig.jsonc index d134ce80be..fb191dc10a 100644 --- a/src/NavConfig.jsonc +++ b/src/NavConfig.jsonc @@ -100,6 +100,27 @@ "visible": false } ] + }, + { + "identity": "EnvironmentsCreationFlow", + "enabledByDefault": false, + "buildTypeOverrides": [ + { + "buildType": "dev", + "enabledByDefault": true, + "visible": true + }, + { + "buildType": "canary", + "enabledByDefault": true, + "visible": true + }, + { + "buildType": "release", + "enabledByDefault": false, + "visible": false + } + ] } ] } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Assets/CreateVirtualEnvironment.png b/tools/SetupFlow/DevHome.SetupFlow/Assets/CreateVirtualEnvironment.png new file mode 100644 index 0000000000000000000000000000000000000000..a8cb72c3a20b9d5653d1b33670a680820f9120fc GIT binary patch literal 12448 zcmV;RFkjD!P)@~0drDELIAGL9O(c600d`2O+f$vv5yPkqUwEdS!t8&}|ElwKALz>UXZ@u)}fCixqKF2ghM|^*dcfttn*rZJ% z!%Ah$A+Xg0zj)tE7ZqNT48VkMi14ito(W9EZ{0@3w+NmYJVSWFM+fuF0Fu1$%dzpl ziOQYM!L3NU+n8jw(DP9fxa5%llnNl?R~2~1;Q#(YC1~LG7_Q?=i+J$gesb?2 zcnPJK38nXaZ|P&q;SiedsVp%mfkS&&;U$n>5)AOUd%nAen)Zt%)gvZ&Gi@XB-PW{T&nt;%1juOGLHdFL z)HR*3xGbFDYy`k$8yHp(e1(lqLIbGwedy5M6}ZULMb7}g^DB?v(Ll3gTbvi7PXhx? ziSTWJdkhS~sY6&vLg00npmS%)kLo^OmI(|2858(|E&asR*1%o&9J=W+T*T?3W`NJ% z^XLI<>|(=kKeaG%gl`?;nE?diy{m__rCi=}#yQ!UVA{oV1;gl+A%pSv8LA7CIogkIb&}f>2^t7?-EoZfLcwMUgHrvKg!1{n?k7$Ol~0v z5-<}q73}@!XKy|V7fGt%B1sP}K0b1;9tU%pT=IqZ647%B#TA>8FnZ=8d4FCygdBoH zf^bdz*lE;L7RFwJP^R=BbQr{h>tS7rqPs%bvq{CsEd*#!GG z5g#+EKyJ;;IVcGa>0VBrE}jV-VG=3zISe0w(d9PvYZ9}%Eqhbvv22wo5`JC_h3SaA?~2U4C8^^5nS zxQ2-lbeY01UDXkSMF9ca^O*Q3$FT74-uplI!$puTxDCM2Zzk;#vhGLh=8Y!}ZQkFP z-o06Y9U+p|>u2)WQ&K+vne>10oIawXzClz|L=TBhvK`RSzG0hSbxUh^FCN}^K|{tD z90BTSdw@H3T!b(9{NB|IF?~?b4bYKLPrTC5%k{L}kR(>A69Z=?ek$K28$ynCU83F( z_b7&Oe_g#;9q|mhzFtT)z_ zxKW(ctDZeV1UHHYN^4w8uOfbi3VygxukjXjys{|}P{w*%<}sM2qGCT)=d}7~sLXkBz9I?sVO{Z5xR1yL7D!qhpT?CquNfDVR zUmIyr6tg_DTsOsNMbng$9&;DO?kTFs=f@{n(*LcU(l%kQV701G?p`{4`+4^WE(iix zs25r%{Vequ{e9iLmx4Sdv+%WrEj@4=x;511gFi8|0dx|Zz?Uu2yBy{tx}>W3)FVPV zf0s!Nn20zY0oWs$PB+~N7a&~_2B25J!-gSUJbteUL$^`2TGF#xDpLXCi$~*4!>uE{ z4ho@<#dORDql~{m6w?p$G46!LzW80HOdUiG!z@sxy;CpMt#AR-d1rw8_aB=lDJKN* z(%z_k{n7}_3@$KPz(-5B4F+RuWF0dI(yT?%j|?_SFQ?Q;SkQHXLbqd1_{hjGV;|Ln z5yA@!kTLkhc{tDMyfeUHu3m6HaVsNuF?R7L=vIxdxS?!pPgj%E5WC0`12m9~G0iX~ z-0RyiX2kN@sdPCO&oN^pRiurG1JM)kdw^5pO$%_I)A?5c*e-Afk3#^m_}egcpOX!G zSoiW}x-_0sQY~Cz%6zG_AfbhcmwKUGT`Q$Ldj$+7g=n3g!7y(1OqHE;oD*ux4$MyyKkbeQy$aP&Y6LS22`{- zv&7?b)Nw?FPN4;ZsWMB~uN+TMc%b=n)t+Y^*`Nk!8^L)_=T!xK=7Y!f@Scf}!SJ(v zx;MIHsPwrh+s^++Z3mc)P;y=FLEFzbvvhmkdCgF>>QmU2Q1X_j03(A$s+taKsH)-o z{@ecR2+m_VuM9A$2lFfVCk>f9xxOltITlPNQscr;bfy1KXW_fvv^z!VU`N^~ilRVP2u9zz7SnHf8rGG zo~mIIp^fF5RooS41WRTVmQ>@Zni>VS4Geo#>t8U3j13I>A; z>OtjmlQ(QN9oUUZSmd@9s1#z;YBmRgLgl|(Yym3)pKr~#R%(ulTo}7t$n%X zx_+%p+t%_)T)7GE`jTWSQKegNui5ScVso&YA(ZC$ELU{T#YvB6}&8jvcEQK!tXL zgiFZRDlec{>U2ZH`UKGpI8%akW1F6Pi+lQv!&wZNQeKKKqc4yYZ&AO!J>Z$El7%SZ zk~Ojq)`KitW4y6J{|*lZ?FSamaz^r5F~Ea7S+li^Y{93w`IrWCw8e@XsF*;A5YGeX zcz)))t9B`w0@i8REPqeJ9x0u9?=0P}pj)E>0Iq{lY518eMS}%Ir1*l&IyTvw1f0DR zCWz<#6Wmq9Phdnaiy!;!jR)ZcrWeEjuK(T)?ZCz%@G?;6UPT8}{Q!ZuM3z>+R-`h1t12d!>XO!22WZ`7*gqKi`T#-D^ zd5F`{n7P;h!=rTl_K*GcnbF{k4B&=tnct1F(fpL5zNI&B7BNYmnoC4a)Rb;3;DCPeXCH*^G3f$W)w*!%6EjKe1P6OrOo4+ zfUU4LAVIq_G-h8P6ZG~45b^J4;!m0+^=_JW@ZVYbU0b2AxRT4mRp#v^@IBl|LF|dq4D~4g-AV?ne)hYq#KZzMHETbiP`M zF>~+0x1&uhn?>mp(>!_(LHd5c<68&70*DL)nS)N!%hae{V9fd;W zwMtO3=c5{>WOSLuGog5Aw-3xzs?<=XV*(81);>&|x}Dc8q7v)5$mU8F~D@H(v?+2bbB-pN$|&{)kL2&5u35!kfL$gOB=n^4lTdij2_eCJW8i-Mj94u9bm^sk(^FGnDJcA?bwsg@b=Sv|N0*feLr&LJg=(WX#>?pW&S>4q?-%M4|ur84Eq&O!yFDxU{E{FPbA(^uBI`3kqPo7DJ2 zguF*j`C54iJ^CHgebCk0shrN^Gogt;Oi>o}njc& z-nZ%zps8;2-L$BtYNg(~lG7t|t>dm_jiB!7phC!FV~z6{@4#`v{kq|>^EkK08=~zr12oe#rpSsey_ZnBJl}n8U-4a4nF=UTffKJ!WZi6J+S)Z|3=S_l zeZ`=i)_c%~e*HMA$EsPnJug|f9085uMZHve-z#?2MW1vk@W3=gv|QmklRnc&yc{Va zQB-|3QkYH*+4Pj5erM+}Lp-Uq&FZV#H9Mn|SdIZ1Vte~<6v)I^L1+Urgi|qUG*0C= z?I){qw++ZLSa1t9Ag1woHZ*$Vo`Q=Vn)DFDs27BEZ&1JMph8c=0^MT}N0lF1k%^YA z7Ow6yN%TRQ%QqtQP&GA{KV<;UP-jmLg&c^nZvKpXv_i2u;yO|tAleOz4R(tFu@My# z-z*}pE+f9H(#sg%J$Wfq?4AKn)pD+08%FY!L$wk*rw4%6YW*B2*W?H4;~r4#E2qDa z!peP$yS*-j9B$=$Q4att-YGUCbfn$PW>i!huaTFl_Iai7|;LpDR}nb6L9*G)6R1DV+Hx4EyzStF9ub3O_S(f z5I=!WSak(b{DNftMq*1C@rl+ZVrQdWq1za-&`#6j2fY$p9%H9@ix*Mcu64B3sfYVe zpd^mfL(s1y*0p}1KwaYLz=VG8Oa$m-j=;$nITa&I>pF-* zWfVwYe_;_dL`u8?Xi|MmvTxS2>P7Ez<5rsu%)fmHTzT_6LHp1VeM(#Fx?aKU{VHd{ zE^)u>(6I3(5`+fAo4^R*n1PN-+Uu03FFmjhI@|Y7-Rzw9bxX($K7#MO8YQ zI$g6NA%syJ8?X=y0%pdY&MtJY&>1(V-<@l8B@zCrpV|qqY2XMwX)AD3-E8F~-hZSL zq9JXAz6Z5;3@%OV_9id^zbO%r%4Om*6L23cOL)DdhAZDR2baI@a`^rqJw=Rgg6c{t zq#A(#irK*B^Y&vE)Cneo5@h;)>I@ULKkxw`jK-9&n+klWmWqvPQX^)>NUfD$n4wdx zrUVK!>m0$Q%sh^w^Fi~J#2z}Zk@tN{AhVwPN>Std`x_+x8cEf~kz0@~> z@U0hKXH2`Y!z5TCHY%w%5edR^3<7yb7j;mfkKkE%&NITReta|R-19uF{O%)UL^e>F zfg1zJ6HJj2!1%d_Q8+L`T$FuG%kU1Uf-7`xB*X_L0mk&G0;8cnH?C4Es)rVwq9xBu zZ#b*mw1O0nkB$ut0f~y3aaX~uO3ceFF-k9dutz2N z5Qz>70hH7Pd&>x8a61JC@y%fz6Y!eCZcQ<)-1;FVtY9|SvJH0Mx(lBC*7x9GlBm^EqG&3 z&4J|GjbZ{#^r*=K@mY78kSF`XOhDuod|8!4LXQYlAZ%S5xaQ5T0#iQ*-~Zzuz@Xkt zOu+ho5KzIl4F;9q+fI5wgNguH8PK1j^n0Wm&9_aDbVEkRl(|Ew@Ng)XTg>zO77@cQ z>8E5$B=<%?Kv=~i3`oCBBtBqn*a0XzrSBbd-B<^cp~uAE@ptFRz^k3aPidnlxg)rX zn6?lCW`3U&J=7sEhMKqG^J;;M%m5vRarL#qvS)^HhHR~D2Y{<@xE2OmYxwRTJWfoo z1qRie`!s>6Fm?b-Ux1&uYXb^6m;d;nvG%xcU(6aNTH7Y4wvJ(GV#zTm62)^`9jGjb z=_binsul4H#BKAO?2k2(ONp1?8B=2fxsE;Jq#_uuqhGiV=C0jJM6bfe#K#QwQHW+S zBd|zx((i$osNOeY5`_o_T3|#66VyrNf=-oV1|q9FIR{scu6DNH@#ROr4k;Gl*knjN zg3dOOVIS7$R0UYDK*S_~^BK{T!`(q;hn52Fjh|h#?8!yF<0@|JP z9|!a5z^3CwOmv7~1TCy)5=_T|SxjJ#m{*?BvWLZ92ge=A4Q=PZHba?9B`*21uV}H>T4g z=-XZxLP>W#k4YtfQFJsNl!^5!nE(=FWvB$PTA7N-?h7|w^^;e?{F~;f@oJb*BTuIQ zQz3kC>%8P+M}h_NqxuO#VnnB(eg^QlVg=y8?^*b1`qRJ&9s#P&gQKpjfB*A;afCR0K|ayy z2Zf{vT3?C{3&lv=Xbk3&CrycU$J~WdOQQA>E+e3M0WhCo7jvspkTy-^}%Ww!QD=YBOA3g@pZTovr4=;0- zufn&5SVt!&2&zLNMP^f2f6=&6zhk;@OowB5d{(dmfct&?(12TFy1R#o1SGLn$aZ%OPe zMGs$pJ-yTf#?zDZ9;cl;Xq`%!_|?go0Zj7%H;H3_)6Mu0z1pgfDw0EWO_o@o8#p`f z{s<9++lEF_CT9lJVCpz_mw z;~CYE$-skHA5-YvKycM72O(cl;wF>Oj>N(Y2GiFBtx(scxCbG|VXSIP{(Z%pwo{~U z&KZ3Z*75npAe0t`ugoBVSau-62EDh@by#QFTZWuHrQdFAiaGm zFhQ4*#G^3EsEnRPJZF69n518}%XjXCSM1se;|Vjt$zT`~16a2A?4gIg`A=6O18}11 zEqUu*$Qf|e$QBw1QZAZbHTw*d@Fc9L*rn6s6oXm^yN8kZQQ}J?cZiWH!tcGQ4((;rXK!Ws7g_jeXh<^0Ys3IHdIG52 zOC4D>g$?5E0i)sw{GtP=4wwn9d;M#PuBTybvPw)q4A7i%YQWU(L20MR0KB#g`H2Tj z@IT@jG?Jn;%Lfl2jvdJSc16Iv$xWh6i~%Sx=u1kvO(E$sA-_l%|0G6|qn&Tt0fQZL zDYh@fZ!rXSjB@V;4KO1-NMI*()sUk_uxZm=sACWl;|VVdKe^v@lWnGv0X(B6Myk~F zA#)eiJ|Ym?0)hmj3drNQX4kcZ0?#>BFm(vf-22cs?^=;o$R~$Zc8Myx8V3w{6A>hI zn1>P}d$}ZWiCUS&V~>P}-?}$4FAFj|02CsWszcwW+XN5_)fA+Een35JJDl{l65?$z zLVqX7Bg7HEUYNi=(15lDVt&X|T-L>;)xWe}LIwHJ4nSpZ6sjnqffXb$p)+X^w^#4} zTLcUf#{h%?D*$Ut8Oim5vjqU}@^~Y-gBi6ntcJIV#$6=t45DSkg`Q+`9bKkAw8+k3 z|2+vbOnm;<-Q*t44MOjpkL|~}z7F67sW*xskvP#fRMM{)MjosQUT*Gs@EEk7=Z*l= zqgt{6lR}-OqD(*nZwn0SO~eGdoDDFxCk`$j{lJQDpU(8~U;C}ShbdZgFSzN1Er{~L zVq8FyL_r1vs0};p5-gL%Lc%jjLBTLk($;kD#p1p)c|i*pKoq`u-xaPq*9Cdpm&+jf z`r5#f5U$A@AAOfCn2zD*&I_Z!Y{H8wkh(z_vmjl=ozSpN=;PQ}BwwLVLVz%S&`tXo zz^)(pQ636@@LPZR(L-gs^ezzb(FbqZPmOgH!;>+vb{PJs8rdYm2SUe?YmfVtNw(>p z#~z~f993g8&c}Muzxln} z!^j;IWBk}EB4U!RN+UpK>|v5jP=;G{4^u^)#CI4V?WLcfW5kF7h>+POxvGJJK_uql z2ZjK54KtRvruW-~GZ|p%@ZQy`YWGphcm2zm{j7#6I0mb^sf zljp$u@cDB;-6)JOiF+%9BN7=LljL?{B1k;)$D#l=LI~mXy}D6Fva`ry_N-*Tu=PK; zV;JE-ES~}p{eH|~-P-`Z3pOJTD)+BrjF|}CqU*KJuhbgA61O&*u$svtB4LIKB~obt zh@Yhm#OIMk^n0{_|2IZ(HVnXN>F~bQr7ymHFUJV@%hzsmiw%UWZDJ3T^fGzIQ0oFa zNs_?`vXHc1PgpH3ZS`v7Hd^J*5{dVETf`r?ARn7b!=C)}($~nqqZXap^2$MyelAy8 z{?W+Nuo@8n5+s+zK)n}Yg+N-7x#5&z-v-(UQtpE+`rIuSnsZZ3gX{oI0@4x ziuEx_gNE~}uK~2-7x%#&lzdjAk`}h^+T<7j9Pv>h0OW+^R1m~S9=$Xu%!yl#%$oV!6x9YNBQY(<$h1K>`f- zM%wo>O?ETrSaAUTP@*u1S50n?USI$T`HIes#72=Yx|d?;*H6C=xBm2v)UoroVZ9{1 zexqB9~r=?v5xvp(RV66_0&`F)vtaHR#t8z=GqQg*firC zJZ6{qQ z_}72^=G{8iIr(VRq5u9aryBV8KU?_9o~mv~MBF`u4!fup^VGickzCkch5!iQ?C03o zm;7xyLzRv(+|Az0-29dWKdvu$29Ed&@*x;~Y(SoV8vgM+c17Zg@mdnpzZdpGahcCm zEf6}z1E9BzSY@M!0*g~uLyERN(UhpFp+{yYu=-I%}W_65W`sp(pxYUDYb z)b+r9+N}@b^)X9kY1Nnch0s=3UkA)Q2p8N@F$Bp!C9C!M=bwiYCr-ff@^SF4eb;!> z-({9Lf#CXf@^mROz^ZFh3{gCR_8oF;Q8v%RKs6TtLpxZs3t4e+ExJc|Fx>o0}L4klaUR z2ENTlLB+atjIdhP`;ub-C$6>wmt3!OE`*GAiY#F1X2&M zWCR$Jk8QTS0>)-996xaq+UeNqRU2ZFPW^@mpbg9e9Cd3c*1t*&kko>PbKY3;|NpUP z;4lB;)lR*5NftjsBwwP9q8@68Dw{WNqw=?f7{DOJ{8P-#hI+Za659s50z2Ohy5tyu z2y_(UnmiZ>cVMssv|Zp9WvJ#{E19iVz;LkDb$;o70GV1JBZncD-!@Ys%%o5m;`b*M zV8+?Jc?S`5dn9D4EU4&KIK{4B_|kag)&-agE?_y0F3kd#9wkDV4F;G1EDs$6*f^|9 z7HY^w`3Mt$XM$?LU-+JLqenV}H!<9c>DX3NVwmq`hG2qr?I92mhMNh>FLP=`OFyLS z0g=KXOMB(l2lA*P=M6}&Cx*~BLqAUj;XN_R1_ONP-Pf*s{J%W0LJc(XOkiTyua;&E zHi9q#|E+mduG&w#cJuVAaNXUfnTNZoO@L)HBX*F^*Cf$Qd_Rw`WPljo2Jf{aXa?$J z3o(V(O*d=8Hv;@82$)8Lblwl&$~Ttso^xTZks|Q|<_N&!G@SIahk?s| zZE;C500HG;zXDh5KB$sL_kft&$$OH~H(@VB#;U*=

^81r_GLzlqYRy#aq6RgbBz zW$GY-NM(IWQZoEP;3&YpC;rs+fza6ppeJ3D4Di9*uU{sRTs2|bm%)Mw)aalz;uiYH zz(5N}9XX8A+6_1z1RFPZ%EZ zaSksm_4n+haBo}$Le>MAuC3N}yF9DBOO^pXaQp7nwrTHW2JlQEZXx9G9#H^dpeG); zkPRH|s0fwBKhO1kx)#oM>X{ELf!-uhLe}{x_ucpI6_}AOSq9)Vp4cVh zYyn~d^h}Tq^H#(aRKS8U@`0fKSWiUJNe}#xo;~Tw>$-m_uXqeb*@L=|0K#Y|hI>zZ zzaj`7uM9U=OR!G5q#0oGuHCEF>BlKG{WP-&vI!re2Eb3X$Oe#VFCh?IB7KiPej=_2 zqI>0-@X`^~wu4R|zAq!~MZ*1wbe#0Q{~S!yOlVt+%fXNlcyK-G_wRFwr+fazZbpIS zDlmbY5R1tc5QDD@E+6ava?(a*hLA);pLk*mb@Y5c5PRxk|NQ&(>C>)DmwWZT`(2S{ z|4buD(_`(;y?udQzj?YIGta>BT#_9eK{pN4J`=J+5*o$ygU&iU+z5i#)*uneJ^6H!X zJuO~!%g?nJCdft+g_MlDbj}-k4Ed*EZS83oj~}OePt(TgV4ia9fGu0*sVDFX*AqAx z%(>8SUy#S!0bw{iCSRX*K1=*`dWyE^>70}PgTL_`?^=Wxm^K)oNC!XuSBC%xckuG7 z%n0aK+NeDmL?$Rl?c~Id1ap2l6KYIEKk0NdOD)Ipx#MRz-0JpBeEIl8w;cW@Aztp_ z5o_N|K9Bq7#h*L^Odt5vr`~lZoJG3yhJ>9VVSQ>pOt#!eExpIM-DwkKGtUGl;~u7(4U;5Y)&X2z~p$cQ0A{r=9e( zuXYuqcJ%nZgb*q8o1ag;c}absB7PI}z1R6}C2$A%F4}kRr#^Mdet3~-g8|NvKJt-2 z-j7%x;H4#)@U#z`9}|$!K&E@SzaU<~tnjS_iv&7S^f0Y|AOkvt_-x@b0|X>kr2rNg zeE(<(eu%We052ddE`ED7nN&xJ5k^e>gaW(|Yw<)szRJS7rKJ0m!@cz40m%HrHoAB| zzCQ_2zvg~h+b$0V?OkVz^1YC>!2oBG?z!jNcM`!5FeCUcbTJa}7rN^3)vgL;MGjB& ze5!ZO0GJV92rtB!jvc>^UEe6d@`8w*5eRuAt)o_7c${Z)t!$oM`iq(8ZMf&g@Fh4x<}8w_xc zlV6Gr_wYtEzL6QCZE>Dl{t;gn1|XMjh4ws8am5&WoOs|Uz-0Mc7(); @@ -194,4 +196,18 @@ private static IServiceCollection AddSetupTarget(this IServiceCollection service return services; } + + private static IServiceCollection AddCreateEnvironment(this IServiceCollection services) + { + // Task groups + services.AddTransient(); + services.AddTransient(); + + // View models + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + return services; + } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationAdaptiveCardSessionEndedData.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationAdaptiveCardSessionEndedData.cs new file mode 100644 index 0000000000..30214eb835 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationAdaptiveCardSessionEndedData.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DevHome.Common.Environments.Models; + +namespace DevHome.SetupFlow.Models.Environments; + +///

+/// Data payload for when the +/// session Ends. This data is used to send the user input from an adaptive card session back to an object +/// that subscribes to the +/// event. +/// +public class CreationAdaptiveCardSessionEndedData +{ + /// + /// Gets the JSON string of the user input from the adaptive card session + /// + public string UserInputResultJson { get; private set; } + + /// + /// Gets the provider details for the compute system provider. + /// + public ComputeSystemProviderDetails ProviderDetails { get; private set; } + + public CreationAdaptiveCardSessionEndedData(string userInputResultJson, ComputeSystemProviderDetails providerDetails) + { + UserInputResultJson = userInputResultJson; + ProviderDetails = providerDetails; + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationAdaptiveCardSessionEndedMessage.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationAdaptiveCardSessionEndedMessage.cs new file mode 100644 index 0000000000..b6b8439913 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationAdaptiveCardSessionEndedMessage.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using CommunityToolkit.Mvvm.Messaging.Messages; + +namespace DevHome.SetupFlow.Models.Environments; + +/// +/// Message for sending the data payload for the +/// object's session back to a subscriber when the session ends. +/// +public class CreationAdaptiveCardSessionEndedMessage : ValueChangedMessage +{ + public CreationAdaptiveCardSessionEndedMessage(CreationAdaptiveCardSessionEndedData value) + : base(value) + { + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationOptionsReviewPageDataRequestMessage.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationOptionsReviewPageDataRequestMessage.cs new file mode 100644 index 0000000000..52a595de06 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationOptionsReviewPageDataRequestMessage.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AdaptiveCards.Rendering.WinUI3; +using CommunityToolkit.Mvvm.Messaging.Messages; + +namespace DevHome.SetupFlow.Models.Environments; + +/// +/// Message for requesting a rendered adaptive card that was created from a +/// object in one view model to a view. +/// +/// +/// This is used when a view that displays an adaptive card needs to request the rendered adaptive card from the view model. +/// The view in this case would not want to using Binding to bind to the adaptive card, but instead request it and then manually +/// add it to its UI. This prevents xaml binding crashes with "Element is already the child of another element" exceptions. +/// +public sealed class CreationOptionsReviewPageDataRequestMessage : RequestMessage +{ +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationOptionsViewPageRequestMessage.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationOptionsViewPageRequestMessage.cs new file mode 100644 index 0000000000..ed4d2796cf --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationOptionsViewPageRequestMessage.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AdaptiveCards.Rendering.WinUI3; +using CommunityToolkit.Mvvm.Messaging.Messages; + +namespace DevHome.SetupFlow.Models.Environments; + +/// +/// Message for requesting a rendered adaptive card that was created from a +/// object in one view model to a view. +/// +/// +/// This is used when a view that displays an adaptive card needs to request the rendered adaptive card from the view model. +/// The view in this case would not want to using Binding to bind to the adaptive card, but instead request it and then manually +/// add it to its UI. This prevents xaml binding crashes with "Element is already the child of another element" exceptions. +/// +public sealed class CreationOptionsViewPageRequestMessage : RequestMessage +{ +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationProviderChangedMessage.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationProviderChangedMessage.cs new file mode 100644 index 0000000000..8ff238af50 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreationProviderChangedMessage.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using CommunityToolkit.Mvvm.Messaging.Messages; +using DevHome.Common.Environments.Models; + +namespace DevHome.SetupFlow.Models.Environments; + +/// +/// Message for sending the from one view model to +/// another view model when the provider changes. +/// +public class CreationProviderChangedMessage : ValueChangedMessage +{ + public CreationProviderChangedMessage(ComputeSystemProviderDetails value) + : base(value) + { + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/NewAdaptiveCardAvailableMessage.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/NewAdaptiveCardAvailableMessage.cs new file mode 100644 index 0000000000..2f5493b924 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/NewAdaptiveCardAvailableMessage.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using CommunityToolkit.Mvvm.Messaging.Messages; + +namespace DevHome.SetupFlow.Models.Environments; + +/// +/// Message for sending a rendered adaptive card that was created from a +/// object in one view model to a view. +/// +/// +/// Since multiple view models can listen for this message in the setup flow, this object is used to to send the rendered adaptive card +/// as well as the current view model that is being displayed in the setup flow. Listeners can use the current view model in use +/// by the to determine if they should display the adaptive card or not. +/// +public class NewAdaptiveCardAvailableMessage : ValueChangedMessage +{ + public NewAdaptiveCardAvailableMessage(RenderedAdaptiveCardData value) + : base(value) + { + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/RenderedAdaptiveCardData.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/RenderedAdaptiveCardData.cs new file mode 100644 index 0000000000..d3c52776bd --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/RenderedAdaptiveCardData.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AdaptiveCards.Rendering.WinUI3; + +namespace DevHome.SetupFlow.Models.Environments; + +/// +/// Data object that contains the rendered adaptive card and the current view model being used in +/// the setup flow by the . +/// +public class RenderedAdaptiveCardData +{ + public object CurrentSetupFlowViewModel { get; private set; } + + public RenderedAdaptiveCard RenderedAdaptiveCard { get; set; } + + public RenderedAdaptiveCardData(object currentSetupFlowViewModel, RenderedAdaptiveCard renderedAdaptiveCard) + { + CurrentSetupFlowViewModel = currentSetupFlowViewModel; + RenderedAdaptiveCard = renderedAdaptiveCard; + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Selectors/ReviewTabViewSelector.cs b/tools/SetupFlow/DevHome.SetupFlow/Selectors/ReviewTabViewSelector.cs index 38d8d86558..db467aeaf2 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Selectors/ReviewTabViewSelector.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Selectors/ReviewTabViewSelector.cs @@ -3,6 +3,7 @@ using System; using DevHome.SetupFlow.ViewModels; +using DevHome.SetupFlow.ViewModels.Environments; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -35,6 +36,11 @@ public DataTemplate SetupTargetTabTemplate get; set; } + public DataTemplate CreateEnvironmentTabTemplate + { + get; set; + } + protected override DataTemplate SelectTemplateCore(object item) { return ResolveDataTemplate(item, () => base.SelectTemplateCore(item)); @@ -59,6 +65,7 @@ private DataTemplate ResolveDataTemplate(object item, Func default RepoConfigReviewViewModel => RepoConfigTabTemplate, AppManagementReviewViewModel => AppManagementTabTemplate, SetupTargetReviewViewModel => SetupTargetTabTemplate, + CreateEnvironmentReviewViewModel => CreateEnvironmentTabTemplate, _ => defaultDataTemplate(), }; } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Selectors/SetupFlowViewSelector.cs b/tools/SetupFlow/DevHome.SetupFlow/Selectors/SetupFlowViewSelector.cs index 399b5a2f6a..9aadd5a359 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Selectors/SetupFlowViewSelector.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Selectors/SetupFlowViewSelector.cs @@ -3,6 +3,7 @@ using System; using DevHome.SetupFlow.ViewModels; +using DevHome.SetupFlow.ViewModels.Environments; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -29,7 +30,11 @@ public class SetupFlowViewSelector : DataTemplateSelector public DataTemplate SummaryTemplate { get; set; } - public DataTemplate ConfigurationFileTemplate { get; set; } + public DataTemplate ConfigurationFileTemplate { get; set; } + + public DataTemplate SelectEnvironmentsProviderTemplate { get; set; } + + public DataTemplate EnvironmentCreationOptionsTemplate { get; set; } protected override DataTemplate SelectTemplateCore(object item) { @@ -58,7 +63,9 @@ private DataTemplate ResolveDataTemplate(object item, Func default LoadingViewModel => LoadingTemplate, SummaryViewModel => SummaryTemplate, ConfigurationFileViewModel => ConfigurationFileTemplate, - SetupTargetViewModel => SetupTargetTemplate, + SetupTargetViewModel => SetupTargetTemplate, + SelectEnvironmentProviderViewModel => SelectEnvironmentsProviderTemplate, + EnvironmentCreationOptionsViewModel => EnvironmentCreationOptionsTemplate, _ => defaultDataTemplate(), }; } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowOrchestrator.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowOrchestrator.cs index 91c5820b27..955e524fe5 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowOrchestrator.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowOrchestrator.cs @@ -7,10 +7,15 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using AdaptiveCards.ObjectModel.WinUI3; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using DevHome.Common.DevHomeAdaptiveCards.CardModels; +using DevHome.Common.DevHomeAdaptiveCards.Parsers; +using DevHome.Common.Renderers; using DevHome.SetupFlow.Common.Contracts; using DevHome.SetupFlow.Common.Elevation; +using DevHome.SetupFlow.Common.Helpers; using DevHome.SetupFlow.Models; using DevHome.SetupFlow.ViewModels; using Projection::DevHome.SetupFlow.ElevatedComponent; @@ -31,6 +36,10 @@ public partial class SetupFlowOrchestrator : ObservableObject { private readonly ILogger _log = Log.ForContext("SourceContext", nameof(SetupFlowOrchestrator)); + private readonly string _adaptiveCardNextButtonId = "DevHomeMachineConfigurationNextButton"; + + private readonly string _adaptiveCardPreviousButtonId = "DevHomeMachineConfigurationPreviousButton"; + private readonly List _flowPages = new(); /// @@ -120,6 +129,13 @@ public IReadOnlyList FlowPages public bool IsMachineConfigurationInProgress => FlowPages.Count > 1; + /// + /// Gets the renderer for the Dev Home action set. This is used to invoke the the buttons within the top level + /// of the adaptive card. This stitches up the setup flow's next and previous buttons to two buttons within an + /// extensions adaptive card. + /// + public DevHomeActionSet DevHomeActionSetRenderer { get; private set; } = new(TopLevelCardActionSetVisibility.Hidden); + /// /// Gets or sets a value indicating whether the done button should be shown. When false, the cancel /// hyperlink button will be shown in the UI. @@ -179,6 +195,13 @@ public void ReleaseRemoteOperationObject() [RelayCommand(CanExecute = nameof(CanGoToPreviousPage))] public async Task GoToPreviousPage() { + // If an adaptive card is being shown in the setup flow, we need to invoke the action + // of the previous button in the action set to move the flow to the previous page in the adaptive card. + if (DevHomeActionSetRenderer?.ActionButtonInvoker != null) + { + DevHomeActionSetRenderer.InitiateAction(_adaptiveCardPreviousButtonId); + } + await SetCurrentPageIndex(_currentPageIndex - 1); } @@ -190,6 +213,17 @@ private bool CanGoToPreviousPage() [RelayCommand(CanExecute = nameof(CanGoToNextPage))] public async Task GoToNextPage() { + // If an adaptive card is being shown in the setup flow, we need to invoke the action + // of the primary button in the action set to move the flow to the next page in the adaptive card. + if (DevHomeActionSetRenderer?.ActionButtonInvoker != null) + { + if (!TryNavigateToNextAdaptiveCardPage(_adaptiveCardNextButtonId)) + { + // Don't navigate if there were validation errors. + return; + } + } + await SetCurrentPageIndex(_currentPageIndex + 1); } @@ -243,4 +277,25 @@ private async Task SetCurrentPageIndex(int index) await CurrentPageViewModel?.OnNavigateToAsync(); } + + /// + /// Performs the work needed to navigate to the next page in an adaptive card. This is used when the setup flow is + /// rendering a flow that includes an adaptive card style wizard flow. + /// + /// + /// Only adaptive cards that have input controls with the 'isRequired' property set to true will be validated. + /// All other elements within the adaptive card will be ignored. + /// + /// The string Id of the button + /// True when the user inputs have been validated and false otherwise. + private bool TryNavigateToNextAdaptiveCardPage(string buttonId) + { + if (DevHomeActionSetRenderer.TryValidateAndInitiateAction(buttonId, CurrentPageViewModel?.GetAdaptiveCardUserInputsForNavigationValidation())) + { + return true; + } + + _log.Warning($"Failed to invoke adaptive card action with Id: {buttonId} due to input validation failure"); + return false; + } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs index a1d3cb4f06..81050c7075 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs @@ -261,4 +261,10 @@ public static class StringResourceKey public static readonly string SetupTargetConfigurationProgressUpdate = nameof(SetupTargetConfigurationProgressUpdate); public static readonly string SetupTargetConfigurationUnitProgressErrorWithMsg = nameof(SetupTargetConfigurationUnitProgressErrorWithMsg); public static readonly string SetupTargetConfigurationUnitUnknown = nameof(SetupTargetConfigurationUnitUnknown); + + // Create Environment flow + public static readonly string SelectEnvironmentPageTitle = nameof(SelectEnvironmentPageTitle); + public static readonly string ConfigureEnvironmentPageTitle = nameof(ConfigureEnvironmentPageTitle); + public static readonly string EnvironmentCreationReviewPageTitle = nameof(EnvironmentCreationReviewPageTitle); + public static readonly string CreateEnvironmentButtonText = nameof(CreateEnvironmentButtonText); } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw index 2f00a48b7b..92b4555a2e 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw +++ b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw @@ -1732,4 +1732,44 @@ View file Button content for viewing a file. + + Select your environment + Title of the 'Select your environment' page in the create environment flow where users can select the provider they will use to create the environment, like Microsoft Hyper-V and Microsoft Dev Box + + + Configure your environment + Title of the 'Configure your environment' page in the environment creation flow where users can choose options that they'd like their environment to be created with + + + Begin by selecting an environment provider below + subtitle text advise the user that they can select an environment option in a list below the text + + + Create virtual environment + Header for a card that when clicked takes the user to a multi-step flow for creating an environment + + + Create a local or cloud environment + Body text description for a card than when clicked takes the user to a multi-step flow for creating an environment + + + Choose an environment provider to create a new dev environment + Description for the setup target page + + + Add options to create your environment + Description for the setup target page + + + There was an error retreiving the adaptive card session from the extension + Error text display when we are unable to retrieve the adaptive card information from an extension + + + Create Environment + Title for create environment review page + + + Create Environment + Text for button that starts creating a new virtual machine or cloud pc for the user + \ No newline at end of file diff --git a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/EnvironmentCreationOptionsTaskGroup.cs b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/EnvironmentCreationOptionsTaskGroup.cs new file mode 100644 index 0000000000..7661f0361e --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/EnvironmentCreationOptionsTaskGroup.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using DevHome.Common.Environments.Models; +using DevHome.Common.Environments.Services; +using DevHome.SetupFlow.Models; +using DevHome.SetupFlow.Services; +using DevHome.SetupFlow.ViewModels; +using DevHome.SetupFlow.ViewModels.Environments; + +namespace DevHome.SetupFlow.TaskGroups; + +public class EnvironmentCreationOptionsTaskGroup : ISetupTaskGroup +{ + private readonly ISetupFlowStringResource _setupFlowStringResource; + + private readonly IComputeSystemManager _computeSystemManager; + + private readonly EnvironmentCreationOptionsViewModel _environmentCreationOptionsViewModel; + + private readonly CreateEnvironmentReviewViewModel _createEnvironmentReviewViewModel; + + public ComputeSystemProviderDetails ProviderDetails { get; private set; } + + public EnvironmentCreationOptionsTaskGroup( + SetupFlowViewModel setupFlowViewModel, + IComputeSystemManager computeSystemManager, + ISetupFlowStringResource setupFlowStringResource, + EnvironmentCreationOptionsViewModel environmentCreationOptionsViewModel, + CreateEnvironmentReviewViewModel createEnvironmentReviewViewModel) + { + _environmentCreationOptionsViewModel = environmentCreationOptionsViewModel; + _createEnvironmentReviewViewModel = createEnvironmentReviewViewModel; + _setupFlowStringResource = setupFlowStringResource; + _computeSystemManager = computeSystemManager; + } + + public IEnumerable SetupTasks => new List(); + + public IEnumerable DSCTasks => new List(); + + public SetupPageViewModelBase GetSetupPageViewModel() => _environmentCreationOptionsViewModel; + + public ReviewTabViewModelBase GetReviewTabViewModel() => _createEnvironmentReviewViewModel; +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SelectEnvironmentProviderTaskGroup.cs b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SelectEnvironmentProviderTaskGroup.cs new file mode 100644 index 0000000000..fcb18c29ea --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SelectEnvironmentProviderTaskGroup.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DevHome.SetupFlow.Models; +using DevHome.SetupFlow.ViewModels; +using DevHome.SetupFlow.ViewModels.Environments; + +namespace DevHome.SetupFlow.TaskGroups; + +public class SelectEnvironmentProviderTaskGroup : ISetupTaskGroup +{ + private readonly SelectEnvironmentProviderViewModel _selectEnvironmentProviderViewModel; + + public SelectEnvironmentProviderTaskGroup(SelectEnvironmentProviderViewModel selectEnvironmentProviderViewModel) + { + _selectEnvironmentProviderViewModel = selectEnvironmentProviderViewModel; + } + + // No setup tasks needed for this task group. + public IEnumerable SetupTasks => new List(); + + // No dsc tasks needed for this task group. + public IEnumerable DSCTasks => new List(); + + public SetupPageViewModelBase GetSetupPageViewModel() => _selectEnvironmentProviderViewModel; + + // Review tab not needed for this task group. + public ReviewTabViewModelBase GetReviewTabViewModel() => null; +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemProviderViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemProviderViewModel.cs new file mode 100644 index 0000000000..10719a3ce9 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemProviderViewModel.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.Common.Environments.Models; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Imaging; + +namespace DevHome.SetupFlow.ViewModels.Environments; + +public partial class ComputeSystemProviderViewModel : ObservableObject +{ + private readonly string _packageFullName; + + public ComputeSystemProviderDetails ProviderDetails { get; private set; } + + [ObservableProperty] + private string _displayName; + + [ObservableProperty] + private ImageIcon _icon; + + [ObservableProperty] + private bool _isSelected; + + public ComputeSystemProviderViewModel(ComputeSystemProviderDetails providerDetails) + { + ProviderDetails = providerDetails; + _packageFullName = ProviderDetails.ExtensionWrapper.PackageFullName; + Icon = GetImageIcon(); + DisplayName = ProviderDetails.ComputeSystemProvider.DisplayName; + } + + private ImageIcon GetImageIcon() + { + var imageIcon = new ImageIcon(); + imageIcon.Source = CardProperty.ConvertMsResourceToIcon(ProviderDetails.ComputeSystemProvider.Icon, _packageFullName); + return imageIcon; + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/CreateEnvironmentReviewViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/CreateEnvironmentReviewViewModel.cs new file mode 100644 index 0000000000..a735e3f15a --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/CreateEnvironmentReviewViewModel.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.SetupFlow.Services; + +namespace DevHome.SetupFlow.ViewModels.Environments; + +public partial class CreateEnvironmentReviewViewModel : ReviewTabViewModelBase +{ + private readonly ISetupFlowStringResource _stringResource; + + public override bool HasItems => true; + + public CreateEnvironmentReviewViewModel( + ISetupFlowStringResource stringResource) + { + _stringResource = stringResource; + TabTitle = stringResource.GetLocalized(StringResourceKey.EnvironmentCreationReviewPageTitle); + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs new file mode 100644 index 0000000000..11a62a5522 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs @@ -0,0 +1,314 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Threading.Tasks; +using AdaptiveCards.ObjectModel.WinUI3; +using AdaptiveCards.Rendering.WinUI3; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using DevHome.Common.DevHomeAdaptiveCards.CardModels; +using DevHome.Common.DevHomeAdaptiveCards.Parsers; +using DevHome.Common.Environments.Models; +using DevHome.Common.Models; +using DevHome.Common.Renderers; +using DevHome.Common.Services; +using DevHome.SetupFlow.Exceptions; +using DevHome.SetupFlow.Models.Environments; +using DevHome.SetupFlow.Services; +using Microsoft.Windows.DevHome.SDK; +using Serilog; +using WinUIEx; + +namespace DevHome.SetupFlow.ViewModels.Environments; + +/// +/// View model for the Configure Environment page in the setup flow. This page will display an adaptive card that is provided by the selected +/// compute system provider. The adaptive card will be display in the middle of the page and will contain compute system provider specific UI +/// for the user to configure their creation options. +/// +public partial class EnvironmentCreationOptionsViewModel : SetupPageViewModelBase, IRecipient +{ + private readonly ILogger _log = Log.ForContext("SourceContext", nameof(SelectEnvironmentProviderViewModel)); + + private readonly AdaptiveCardRenderingService _adaptiveCardRenderingService; + + private readonly WindowEx _windowEx; + + private readonly AdaptiveElementParserRegistration _elementRegistration = new(); + + private readonly AdaptiveActionParserRegistration _actionRegistration = new(); + + private readonly SetupFlowViewModel _setupFlowViewModel; + + private ComputeSystemProviderDetails _curProviderDetails; + + private AdaptiveCardRenderer _adaptiveCardRenderer; + + private ComputeSystemProviderDetails _upcomingProviderDetails; + + private ExtensionAdaptiveCardSession _extensionAdaptiveCardSession; + + private ExtensionAdaptiveCard _extensionAdaptiveCard; + + private RenderedAdaptiveCard _renderedAdaptiveCard; + + private AdaptiveInputs _userInputsFromAdaptiveCard; + + [ObservableProperty] + private bool _isAdaptiveCardSessionLoaded; + + [ObservableProperty] + private string _sessionErrorMessage; + + public EnvironmentCreationOptionsViewModel( + ISetupFlowStringResource stringResource, + SetupFlowOrchestrator orchestrator, + SetupFlowViewModel setupFlow, + WindowEx windowEx, + AdaptiveCardRenderingService renderingService) + : base(stringResource, orchestrator) + { + PageTitle = stringResource.GetLocalized(StringResourceKey.ConfigureEnvironmentPageTitle); + _setupFlowViewModel = setupFlow; + _setupFlowViewModel.EndSetupFlow += OnEndSetupFlow; + _windowEx = windowEx; + + // Register for changes to the selected provider. This will be triggered when the user selects a provider. + // from the SelectEnvironmentProviderViewModel. This is a weak reference so that the recipient can be garbage collected. + WeakReferenceMessenger.Default.Register(this); + + // Register to receive the EnvironmentCreationOptionsViewRequestMessage so that we can populate the configure environment page with the current + // adaptive card information. This handles the case where the view is loaded after the view model has finished loading the adaptive card. + WeakReferenceMessenger.Default.Register(this, OnEnvironmentOptionsViewRequest); + + WeakReferenceMessenger.Default.Register(this, OnReviewPageViewRequest); + + // register the supported element parsers + _elementRegistration.Set(DevHomeSettingsCard.AdaptiveElementType, new DevHomeSettingsCardParser()); + _elementRegistration.Set(DevHomeSettingsCardChoiceSet.AdaptiveElementType, new DevHomeSettingsCardChoiceSetParser()); + _elementRegistration.Set(DevHomeLaunchContentDialogButton.AdaptiveElementType, new DevHomeLaunchContentDialogButtonParser()); + _elementRegistration.Set(DevHomeContentDialogContent.AdaptiveElementType, new DevHomeContentDialogContentParser()); + _adaptiveCardRenderingService = renderingService; + } + + /// + /// Weak reference message handler for when the selected provider changes in the select environment provider page. This will be triggered when the user + /// selects an item in the Select Environment Provider page. The next time the user goes to this configure environments page, we'll update the UI + /// with an adaptive card from the newly selected provider. + /// + /// Message data that contains the new provider details. + public void Receive(CreationProviderChangedMessage message) + { + _upcomingProviderDetails = message.Value; + } + + private void OnEndSetupFlow(object sender, EventArgs e) + { + ResetAdaptiveCardConfiguration(); + WeakReferenceMessenger.Default.UnregisterAll(this); + _setupFlowViewModel.EndSetupFlow -= OnEndSetupFlow; + } + + protected async override Task OnFirstNavigateToAsync() + { + _adaptiveCardRenderer = await GetAdaptiveCardRenderer(); + } + + /// + /// Make sure we only get the list of ComputeSystems from the ComputeSystemManager once when the page is first navigated to. + /// All other times will be through the use of the sync button. + /// + protected async override Task OnEachNavigateToAsync() + { + await Task.CompletedTask; + + var curSelectedProviderId = _curProviderDetails?.ComputeSystemProvider?.Id ?? string.Empty; + var upcomingSelectedProviderId = _upcomingProviderDetails?.ComputeSystemProvider?.Id; + + // Selected compute system provider may havechanged so we need to update the adaptive card in the UI + // with new a adaptive card from the new provider. + _curProviderDetails = _upcomingProviderDetails; + + IsAdaptiveCardSessionLoaded = false; + + // Its possible that an extension could take a long time to load the adaptive card session. + // So we run this on a background thread to prevent the UI from freezing. + _ = Task.Run(() => + { + var developerIdWrapper = _curProviderDetails.DeveloperIds.First(); + var result = _curProviderDetails.ComputeSystemProvider.CreateAdaptiveCardSessionForDeveloperId(developerIdWrapper.DeveloperId, ComputeSystemAdaptiveCardKind.CreateComputeSystem); + UpdateExtensionAdaptiveCard(result); + }); + } + + /// + /// Gets and configures the adaptive card that will be displayed on the configure environment page. + /// + public void UpdateExtensionAdaptiveCard(ComputeSystemAdaptiveCardResult adaptiveCardSessionResult) + { + _windowEx.DispatcherQueue.TryEnqueue(() => + { + try + { + CanGoToNextPage = false; + + // Reset error state and remove event handler from previous session. + ResetAdaptiveCardConfiguration(); + + if (adaptiveCardSessionResult.Result.Status == ProviderOperationStatus.Failure) + { + _log.Error($"{adaptiveCardSessionResult.Result.DisplayMessage} - {adaptiveCardSessionResult.Result.DiagnosticText}"); + throw new AdaptiveCardNotRetrievedException(adaptiveCardSessionResult.Result.DisplayMessage); + } + + // Create a new adaptive card session wrapper and add event handlers for when the session stops. + _extensionAdaptiveCardSession = new ExtensionAdaptiveCardSession(adaptiveCardSessionResult.ComputeSystemCardSession); + _extensionAdaptiveCardSession.Stopped += OnAdaptiveCardSessionStopped; + + // Create the Dev Home sdk extension adaptive card with our custom element and action parsers and send + // it to the extension who will update the card with an adaptive card template and data for the template. + // We use the OnAdaptiveCardUpdated method to update Dev Home's UI when IExtensionAdaptiveCard.Update is called. + _extensionAdaptiveCard = new ExtensionAdaptiveCard(_elementRegistration, _actionRegistration); + _extensionAdaptiveCard.UiUpdate += OnAdaptiveCardUpdated; + + // Initialize the adaptive card session with the extension adaptive card template and data with an initial + // call to IExtensionAdaptiveCard.Update. + _extensionAdaptiveCardSession.Initialize(_extensionAdaptiveCard); + } + catch (Exception ex) + { + _log.Error($"Failed to get creation options adaptive card from provider {_curProviderDetails.ComputeSystemProvider.Id}.", ex); + SessionErrorMessage = ex.Message; + } + }); + } + + /// + /// When the is updated by the extension we need to render the new adaptive card in the UI. + /// This method does the work needed to create an adaptive card renderer, render the adaptive card and send the new adaptive card + /// any view that is listening for the message. + /// + public void OnAdaptiveCardUpdated(object sender, AdaptiveCard adaptiveCard) + { + _windowEx.DispatcherQueue.TryEnqueue(() => + { + // Render the adaptive card and set the action event handler. + _renderedAdaptiveCard = _adaptiveCardRenderer.RenderAdaptiveCard(adaptiveCard); + _renderedAdaptiveCard.Action += OnRenderedAdaptiveCardAction; + + // Send new card to listeners + _userInputsFromAdaptiveCard = _renderedAdaptiveCard.UserInputs; + WeakReferenceMessenger.Default.Send(new NewAdaptiveCardAvailableMessage(new RenderedAdaptiveCardData(Orchestrator.CurrentPageViewModel, _renderedAdaptiveCard))); + IsAdaptiveCardSessionLoaded = true; + + // We set CanGoToNextPage to true here because we can only validate the inputs when the user interacts with the adaptive card + // via the action buttons. + CanGoToNextPage = true; + }); + } + + /// + /// When the user interacts with the adaptive card by clicking the next or previous buttons in the Setup flow, we need to send + /// the inputs and actions back to the extension. The extension will then process the inputs and actions and update the adaptive card + /// Which will ultimately cause the method to be called. + /// + /// The rendered adaptive card whose submite or execute action was just invoked + /// The action and user inputs from within the adaptive card + private void OnRenderedAdaptiveCardAction(object sender, AdaptiveActionEventArgs args) + { + _windowEx.DispatcherQueue.TryEnqueue(async () => + { + IsAdaptiveCardSessionLoaded = false; + + // Send the inputs and actions that the user entered back to the extension. + await _extensionAdaptiveCardSession.OnAction(args.Action.ToJson().Stringify(), args.Inputs.AsJson().Stringify()); + }); + } + + private void ResetAdaptiveCardConfiguration() + { + SessionErrorMessage = null; + if (_extensionAdaptiveCardSession != null) + { + _extensionAdaptiveCardSession.Stopped -= OnAdaptiveCardSessionStopped; + } + + if (_extensionAdaptiveCard != null) + { + _extensionAdaptiveCard.UiUpdate -= OnAdaptiveCardUpdated; + } + + if (_renderedAdaptiveCard != null) + { + _renderedAdaptiveCard.Action -= OnRenderedAdaptiveCardAction; + } + } + + /// + /// The configure environment view page will request an adaptive card to display in the UI if it loads after the extension sends out the CreationOptionsViewPageRequestMessage. + /// + /// The class that should be receiving the request + /// The payload of the message request + private void OnEnvironmentOptionsViewRequest(EnvironmentCreationOptionsViewModel recipient, CreationOptionsViewPageRequestMessage message) + { + message.Reply(_renderedAdaptiveCard); + } + + /// + /// The review environments view / summary page view will request an adaptive card to display in the UI if it loads after this view model sends out the original RenderedAdaptiveCard message. + /// this can happen when the user navigates away from the review page to another page in Dev Home. E.g the settings page, then navigates back to the review page. At this point the review + /// page is unloaded when the user navigates away from it. When they navigate back to it, a new view will be created and loaded, so we need to request the adaptive again from this view model. + /// + /// The class that should be receiving the request + /// The payload of the message request + private void OnReviewPageViewRequest(EnvironmentCreationOptionsViewModel recipient, CreationOptionsReviewPageDataRequestMessage message) + { + // Only send the adaptive card if the session has loaded. If the session hasn't loaded yet, we'll send an empty response. The review page should be sent the adaptive card + // once the session has loaded in the OnAdaptiveCardUpdated method. + if (!IsAdaptiveCardSessionLoaded) + { + return; + } + + message.Reply(_renderedAdaptiveCard); + } + + /// + /// When the extension indicates that the session has stopped, we need to get the result json from the session. Once we get this + /// we can send a message to the CreateEnvironmentTask to let it know that the adaptive card session has ended. + /// It will then update its setup tasks with information to create the compute system. + /// + /// The extension session object who stopped the session + /// Data payload that contains the users provided input + private void OnAdaptiveCardSessionStopped(ExtensionAdaptiveCardSession sender, ExtensionAdaptiveCardSessionStoppedEventArgs args) + { + // Send message to the CreateEnvironmentTask to let it know that the adaptive card session has ended. + // the task will use the ResultJson to create the compute system. + WeakReferenceMessenger.Default.Send(new CreationAdaptiveCardSessionEndedMessage(new CreationAdaptiveCardSessionEndedData(args.ResultJson, _curProviderDetails))); + sender.Stopped -= OnAdaptiveCardSessionStopped; + _extensionAdaptiveCard.UiUpdate -= OnAdaptiveCardUpdated; + } + + /// + /// Gets the adaptive card renderer that will be used to render the adaptive card in the UI. Its important to recreate the ItemsViewChoiceSet every time we want to + /// render an adaptive card because the parenting the ItemsView control to multiple parents will cause an exception to be thrown. + /// + private async Task GetAdaptiveCardRenderer() + { + var renderer = await _adaptiveCardRenderingService.GetRendererAsync(); + renderer.ElementRenderers.Set(DevHomeSettingsCardChoiceSet.AdaptiveElementType, new ItemsViewChoiceSet("SettingsCardWithButtonThatLaunchesContentDialog")); + + // We need to keep the same renderer for the ActionSet that is hooked up to the orchestrator as it will have the adaptive card + // context needed to invoke the adaptive card actions from outside the adaptive card. + renderer.ElementRenderers.Set("ActionSet", Orchestrator.DevHomeActionSetRenderer); + return renderer; + } + + /// + protected override AdaptiveInputs GetAdaptiveCardUserInputs() + { + return _userInputsFromAdaptiveCard; + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs new file mode 100644 index 0000000000..4ac0ecf9fb --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using DevHome.Common.Contracts.Services; +using DevHome.Common.Environments.Models; +using DevHome.SetupFlow.Models.Environments; +using DevHome.SetupFlow.Services; +using Serilog; +using WinUIEx; + +namespace DevHome.SetupFlow.ViewModels.Environments; + +public partial class SelectEnvironmentProviderViewModel : SetupPageViewModelBase +{ + private readonly ILogger _log = Log.ForContext("SourceContext", nameof(SelectEnvironmentProviderViewModel)); + + private readonly IComputeSystemService _computeSystemService; + + public ComputeSystemProviderDetails SelectedProvider { get; private set; } + + [ObservableProperty] + private bool _areProvidersLoaded; + + [ObservableProperty] + private int _selectedProviderIndex; + + [ObservableProperty] + private ObservableCollection _providersViewModels; + + public SelectEnvironmentProviderViewModel( + ISetupFlowStringResource stringResource, + SetupFlowOrchestrator orchestrator, + IComputeSystemService computeSystemService) + : base(stringResource, orchestrator) + { + PageTitle = stringResource.GetLocalized(StringResourceKey.SelectEnvironmentPageTitle); + _computeSystemService = computeSystemService; + } + + private async Task LoadProvidersAsync() + { + AreProvidersLoaded = false; + Orchestrator.NotifyNavigationCanExecuteChanged(); + + var providerDetails = await Task.Run(_computeSystemService.GetComputeSystemProvidersAsync); + + ProvidersViewModels = new(); + foreach (var providerDetail in providerDetails) + { + ProvidersViewModels.Add(new ComputeSystemProviderViewModel(providerDetail)); + } + + AreProvidersLoaded = true; + } + + protected async override Task OnFirstNavigateToAsync() + { + CanGoToNextPage = false; + await LoadProvidersAsync(); + } + + [RelayCommand] + private void ItemsViewSelectionChanged(ComputeSystemProviderViewModel sender) + { + if (sender != null) + { + // When navigating between the select providers page and the configure creation options page + // visual selection is lost, so we need deselect the providers first. Then select correct one. + // this will ensure that the correct provider is visually selected when navigating back to the select providers page. + foreach (var provider in ProvidersViewModels) + { + provider.IsSelected = false; + } + + sender.IsSelected = true; + SelectedProvider = sender.ProviderDetails; + + // Using the default channel to send the message to the recipient. In this case, the EnvironmentCreationOptionsViewModel. + // In the future if we support a multi-instance setup flow, we can use a custom channel/a message broker to send messages. + // For now, we are using the default channel. + WeakReferenceMessenger.Default.Send(new CreationProviderChangedMessage(SelectedProvider)); + CanGoToNextPage = true; + Orchestrator.NotifyNavigationCanExecuteChanged(); + } + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs index 18a5c5c17d..9e1f69ffdd 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs @@ -34,6 +34,8 @@ public partial class MainPageViewModel : SetupPageViewModelBase private const string EnvironmentsSetupFlowFeatureName = "EnvironmentsSetupTargetFlow"; + private const string EnvironmentsCreationFlowFeatureName = "EnvironmentsCreationFlow"; + private readonly IHost _host; private readonly IWindowsPackageManager _wpm; private readonly IDesiredStateConfiguration _dsc; @@ -59,6 +61,8 @@ public partial class MainPageViewModel : SetupPageViewModelBase public bool ShouldShowSetupTargetItem => _experimentationService.IsFeatureEnabled(EnvironmentsSetupFlowFeatureName); + public bool ShouldShowCreateEnvironmentItem => _experimentationService.IsFeatureEnabled(EnvironmentsCreationFlowFeatureName); + /// /// Event raised when the user elects to start the setup flow. /// The orchestrator for the whole flow subscribes to this event to handle @@ -184,6 +188,20 @@ private void StartRepoConfig(string flowTitle) _host.GetService()); } + /// + /// Starts the create environment flow. + /// + [RelayCommand] + private void StartCreateEnvironment(string flowTitle) + { + _log.Information("Starting flow for environment creation"); + StartSetupFlowForTaskGroups( + flowTitle, + "RepoConfig", + _host.GetService(), + _host.GetService()); + } + /// /// Starts a setup flow that only includes app management. /// diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupPageViewModelBase.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupPageViewModelBase.cs index d9b669bb3f..cc36f1b534 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupPageViewModelBase.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupPageViewModelBase.cs @@ -3,7 +3,11 @@ using System.Linq; using System.Threading.Tasks; +using AdaptiveCards.ObjectModel.WinUI3; +using AdaptiveCards.Rendering.WinUI3; using CommunityToolkit.Mvvm.ComponentModel; +using DevHome.Common.DevHomeAdaptiveCards.CardModels; +using DevHome.Common.DevHomeAdaptiveCards.Parsers; using DevHome.Common.TelemetryEvents.SetupFlow; using DevHome.SetupFlow.Services; using DevHome.Telemetry; @@ -189,4 +193,26 @@ protected async virtual Task OnFirstNavigateFromAsync() // Do nothing await Task.CompletedTask; } + + /// + /// Hook so the orchestrator can validate if the user can navigate to the next page when a page is rendering + /// an adaptive card that is hooked up to the . + /// + /// + /// The orchestrator takes care of calling this when appropriate through . + /// This runs on the UI thread, but the cost of validating the inputs should be minimal. + /// + protected virtual AdaptiveInputs GetAdaptiveCardUserInputs() + { + return new AdaptiveInputs(); + } + + /// + /// Performs the work to validate the user inputs when navigating to the next page when the page is rendering an adaptive card. + /// + /// The adaptive card inputs for the adaptive card currently presented to the user on the setup flow page + public AdaptiveInputs GetAdaptiveCardUserInputsForNavigationValidation() + { + return GetAdaptiveCardUserInputs(); + } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml new file mode 100644 index 0000000000..8a98075a45 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs new file mode 100644 index 0000000000..c1532b9b71 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using AdaptiveCards.Rendering.WinUI3; +using CommunityToolkit.Mvvm.Messaging; +using DevHome.SetupFlow.Models.Environments; +using DevHome.SetupFlow.ViewModels; +using DevHome.SetupFlow.ViewModels.Environments; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Serilog; + +namespace DevHome.SetupFlow.Views.Environments; + +public sealed partial class CreateEnvironmentReviewView : UserControl, IRecipient +{ + // Logging to capture any adaptive card rendering exceptions so the app doesn't crash + private readonly ILogger _log = Log.ForContext("SourceContext", nameof(CreateEnvironmentReviewView)); + + public CreateEnvironmentReviewViewModel ViewModel => (CreateEnvironmentReviewViewModel)this.DataContext; + + public CreateEnvironmentReviewView() + { + this.InitializeComponent(); + WeakReferenceMessenger.Default.Register(this); + } + + private void ViewUnloaded(object sender, RoutedEventArgs e) + { + AdaptiveCardGrid.Children.Clear(); + WeakReferenceMessenger.Default.UnregisterAll(this); + } + + /// + /// Recieves the adaptive card from the view model, when the view model finishes loading it. + /// + public void Receive(NewAdaptiveCardAvailableMessage message) + { + // Only process the message if the view model is the ReviewViewModel + if (message.Value.CurrentSetupFlowViewModel is ReviewViewModel) + { + AddAdaptiveCardToUI(message.Value.RenderedAdaptiveCard); + } + } + + /// + /// Request the adaptive cad from the view model + /// + private void ViewLoaded(object sender, RoutedEventArgs e) + { + var message = WeakReferenceMessenger.Default.Send(); + if (!message.HasReceivedResponse) + { + return; + } + + AddAdaptiveCardToUI(message.Response); + } + + private void AddAdaptiveCardToUI(RenderedAdaptiveCard renderedAdaptiveCard) + { + try + { + var frameworkElement = renderedAdaptiveCard?.FrameworkElement; + if (frameworkElement == null) + { + return; + } + + AdaptiveCardGrid.Children.Clear(); + AdaptiveCardGrid.Children.Add(frameworkElement); + } + catch (Exception ex) + { + // Log the exception + _log.Error("Error adding adaptive card UI in CreateEnvironmentReviewView", ex); + } + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml new file mode 100644 index 0000000000..d987ef1f54 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs new file mode 100644 index 0000000000..3bb7562cc0 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using AdaptiveCards.Rendering.WinUI3; +using CommunityToolkit.Mvvm.Messaging; +using DevHome.SetupFlow.Models.Environments; +using DevHome.SetupFlow.ViewModels.Environments; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Serilog; + +namespace DevHome.SetupFlow.Views.Environments; + +public sealed partial class EnvironmentCreationOptionsView : UserControl, IRecipient +{ + // Logging to capture any adaptive card rendering exceptions so the app doesn't crash + private readonly ILogger _log = Log.ForContext("SourceContext", nameof(EnvironmentCreationOptionsView)); + + public EnvironmentCreationOptionsViewModel ViewModel => (EnvironmentCreationOptionsViewModel)this.DataContext; + + public EnvironmentCreationOptionsView() + { + this.InitializeComponent(); + WeakReferenceMessenger.Default.Register(this); + } + + private void ViewUnloaded(object sender, RoutedEventArgs e) + { + AdaptiveCardGrid.Children.Clear(); + WeakReferenceMessenger.Default.UnregisterAll(this); + } + + /// + /// Request the adaptive cad from the view model + /// + private void ViewLoaded(object sender, RoutedEventArgs e) + { + var message = WeakReferenceMessenger.Default.Send(); + if (!message.HasReceivedResponse) + { + return; + } + + AddAdaptiveCardToUI(message.Response); + } + + /// + /// Receive the adaptive card from the view model, when the view model finishes loading it. + /// Note: There are times when the view is loaded after the view model has finished loading the adaptive card. + /// In these cases it would have "missed" the push message. This is where the ViewLoaded method comes in. + /// + public void Receive(NewAdaptiveCardAvailableMessage message) + { + // Only process the message if the view model is the EnvironmentCreationOptionsViewModel + if (message.Value.CurrentSetupFlowViewModel is EnvironmentCreationOptionsViewModel) + { + AddAdaptiveCardToUI(message.Value.RenderedAdaptiveCard); + } + } + + private void AddAdaptiveCardToUI(RenderedAdaptiveCard adaptiveCardData) + { + try + { + if (adaptiveCardData?.FrameworkElement != null) + { + AdaptiveCardGrid.Children.Clear(); + AdaptiveCardGrid.Children.Add(adaptiveCardData.FrameworkElement); + } + } + catch (Exception ex) + { + // Log the exception + _log.Error("Error adding adaptive card UI in EnvironmentCreationOptionsView", ex); + } + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml new file mode 100644 index 0000000000..659e66afa5 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml.cs new file mode 100644 index 0000000000..70707a5aa5 --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DevHome.SetupFlow.ViewModels.Environments; +using Microsoft.UI.Xaml.Controls; + +namespace DevHome.SetupFlow.Views.Environments; + +public sealed partial class SelectEnvironmentProviderView : UserControl +{ + public SelectEnvironmentProviderViewModel ViewModel => (SelectEnvironmentProviderViewModel)this.DataContext; + + public SelectEnvironmentProviderView() + { + this.InitializeComponent(); + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/MainPageView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/MainPageView.xaml index 36bb2ce586..8168967908 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/MainPageView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/MainPageView.xaml @@ -164,6 +164,24 @@ + + + + + + + + + @@ -139,6 +140,11 @@ + + + + + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml index 79e5e95a25..c76facd36d 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml @@ -9,6 +9,7 @@ xmlns:pg="using:DevHome.Common" xmlns:behaviors="using:DevHome.Common.Behaviors" xmlns:setupFlowBehaviors="using:DevHome.SetupFlow.Behaviors" + xmlns:environmentViews="using:DevHome.SetupFlow.Views.Environments" xmlns:selectors="using:DevHome.SetupFlow.Selectors" xmlns:views="using:DevHome.SetupFlow.Views" xmlns:controls="using:DevHome.SetupFlow.Controls" @@ -73,6 +74,16 @@ + + + + + + + + + + From 886ec807b380c2e0016d450297e6a3999b1c5e42 Mon Sep 17 00:00:00 2001 From: Felipe G Date: Fri, 5 Apr 2024 16:01:42 -0700 Subject: [PATCH 05/22] Workaround on accessibility bug on SettingsCard (#2525) * Workaround settings card bug * Adding comment --------- Co-authored-by: Felipe da Conceicao Guimaraes --- .../Views/ExtensionLibraryView.xaml | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml index c14d73765c..60cdaf1775 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml @@ -70,21 +70,24 @@ - + + + + From 3e77990ceaa28e78cdd9f9b06438a3b26988b42b Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Mon, 8 Apr 2024 09:54:55 -0400 Subject: [PATCH 06/22] Use Package and Extension display names (#2544) --- common/Services/ComputeSystemService.cs | 2 +- common/Services/IExtensionWrapper.cs | 47 ++++++------------- .../Views/FeedbackPage.xaml.cs | 2 +- src/Models/ExtensionWrapper.cs | 45 +++++------------- src/Services/AccountsService.cs | 2 +- .../TestModels/TestExtensionWrapper.cs | 4 +- .../ViewModels/ExtensionLibraryViewModel.cs | 6 +-- .../ViewModels/ExtensionSettingsViewModel.cs | 2 +- .../ViewModels/InstalledPackageViewModel.cs | 6 +-- .../Views/ExtensionLibraryView.xaml | 4 +- .../Models/RepositoryProvider.cs | 2 +- .../Models/RepositoryProviders.cs | 2 +- .../WinGetFeaturedApplicationsDataSource.cs | 2 +- .../ViewModels/AddRepoViewModel.cs | 2 +- 14 files changed, 45 insertions(+), 83 deletions(-) diff --git a/common/Services/ComputeSystemService.cs b/common/Services/ComputeSystemService.cs index dd24c44cc9..42be48d4b5 100644 --- a/common/Services/ComputeSystemService.cs +++ b/common/Services/ComputeSystemService.cs @@ -83,7 +83,7 @@ public async Task> GetComputeSystemProvidersA } catch (Exception ex) { - Log.Error($"Failed to get {nameof(IComputeSystemProvider)} provider from '{extension.Name}'", ex); + Log.Error($"Failed to get {nameof(IComputeSystemProvider)} provider from '{extension.PackageFamilyName}/{extension.ExtensionDisplayName}'", ex); } } diff --git a/common/Services/IExtensionWrapper.cs b/common/Services/IExtensionWrapper.cs index 026d36c1dc..7a861502c9 100644 --- a/common/Services/IExtensionWrapper.cs +++ b/common/Services/IExtensionWrapper.cs @@ -12,68 +12,49 @@ namespace DevHome.Common.Services; public interface IExtensionWrapper { /// - /// Gets name of the extension as mentioned in the manifest + /// Gets the DisplayName of the package as mentioned in the manifest /// - string Name - { - get; - } + string PackageDisplayName { get; } + + /// + /// Gets DisplayName of the extension as mentioned in the manifest + /// + string ExtensionDisplayName { get; } /// /// Gets PackageFullName of the extension /// - string PackageFullName - { - get; - } + string PackageFullName { get; } /// /// Gets PackageFamilyName of the extension /// - string PackageFamilyName - { - get; - } + string PackageFamilyName { get; } /// /// Gets Publisher of the extension /// - string Publisher - { - get; - } + string Publisher { get; } /// /// Gets class id (GUID) of the extension class (which implements IExtension) as mentioned in the manifest /// - string ExtensionClassId - { - get; - } + string ExtensionClassId { get; } /// /// Gets the date on which the application package was installed or last updated. /// - DateTimeOffset InstalledDate - { - get; - } + DateTimeOffset InstalledDate { get; } /// /// Gets the PackageVersion of the extension /// - PackageVersion Version - { - get; - } + PackageVersion Version { get; } /// /// Gets the Unique Id for the extension /// - public string ExtensionUniqueId - { - get; - } + public string ExtensionUniqueId { get; } /// /// Checks whether we have a reference to the extension process and we are able to call methods on the interface. diff --git a/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs b/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs index 5cb33dbf36..90f6f3607f 100644 --- a/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs +++ b/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs @@ -297,7 +297,7 @@ private string GetExtensions() var extensionsStr = stringResource.GetLocalized("Settings_Feedback_Extensions") + ": \n"; foreach (var extension in extensions) { - extensionsStr += extension.PackageFullName + "\n"; + extensionsStr += extension.PackageFullName + " (" + extension.ExtensionDisplayName + ")\n"; } return extensionsStr; diff --git a/src/Models/ExtensionWrapper.cs b/src/Models/ExtensionWrapper.cs index 2b6552c633..7f5743aff4 100644 --- a/src/Models/ExtensionWrapper.cs +++ b/src/Models/ExtensionWrapper.cs @@ -32,7 +32,8 @@ public class ExtensionWrapper : IExtensionWrapper public ExtensionWrapper(AppExtension appExtension, string classId) { - Name = appExtension.DisplayName; + PackageDisplayName = appExtension.Package.DisplayName; + ExtensionDisplayName = appExtension.DisplayName; PackageFullName = appExtension.Package.Id.FullName; PackageFamilyName = appExtension.Package.Id.FamilyName; ExtensionClassId = classId ?? throw new ArgumentNullException(nameof(classId)); @@ -42,40 +43,21 @@ public ExtensionWrapper(AppExtension appExtension, string classId) ExtensionUniqueId = appExtension.AppInfo.AppUserModelId + "!" + appExtension.Id; } - public string Name - { - get; - } + public string PackageDisplayName { get; } - public string PackageFullName - { - get; - } + public string ExtensionDisplayName { get; } - public string PackageFamilyName - { - get; - } + public string PackageFullName { get; } - public string ExtensionClassId - { - get; - } + public string PackageFamilyName { get; } - public string Publisher - { - get; - } + public string ExtensionClassId { get; } - public DateTimeOffset InstalledDate - { - get; - } + public string Publisher { get; } - public PackageVersion Version - { - get; - } + public DateTimeOffset InstalledDate { get; } + + public PackageVersion Version { get; } /// /// Gets the unique id for this Dev Home extension. The unique id is a concatenation of: @@ -86,10 +68,7 @@ public PackageVersion Version /// The Extension Id. This is the unique identifier of the extension within the application. /// /// - public string ExtensionUniqueId - { - get; - } + public string ExtensionUniqueId { get; } public bool IsRunning() { diff --git a/src/Services/AccountsService.cs b/src/Services/AccountsService.cs index 7127352bf4..0c628f7a5e 100644 --- a/src/Services/AccountsService.cs +++ b/src/Services/AccountsService.cs @@ -59,7 +59,7 @@ public async Task> GetDevIdProviders() } catch (Exception ex) { - _log.Error($"Failed to get {nameof(IDeveloperIdProvider)} provider from '{extension.Name}'", ex); + _log.Error($"Failed to get {nameof(IDeveloperIdProvider)} provider from '{extension.PackageFamilyName}/{extension.ExtensionDisplayName}'", ex); } } diff --git a/tools/Environments/DevHome.Environments/TestModels/TestExtensionWrapper.cs b/tools/Environments/DevHome.Environments/TestModels/TestExtensionWrapper.cs index aba63a03af..4a066c0e05 100644 --- a/tools/Environments/DevHome.Environments/TestModels/TestExtensionWrapper.cs +++ b/tools/Environments/DevHome.Environments/TestModels/TestExtensionWrapper.cs @@ -12,7 +12,9 @@ namespace DevHome.Environments.TestModels; public class TestExtensionWrapper : IExtensionWrapper { - public string Name => throw new NotImplementedException(); + public string PackageDisplayName => throw new NotImplementedException(); + + public string ExtensionDisplayName => throw new NotImplementedException(); public string PackageFullName => "Microsoft.Windows.DevHome.Dev_0.0.0.0_x64__8wekyb3d8bbwe"; diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs index 53b24f8638..01f434838a 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs @@ -79,7 +79,7 @@ private async Task GetInstalledExtensionsAsync() InstalledPackagesList.Clear(); - extensionWrappers = extensionWrappers.OrderBy(extensionWrapper => extensionWrapper.Name); + extensionWrappers = extensionWrappers.OrderBy(extensionWrapper => extensionWrapper.PackageDisplayName); foreach (var extensionWrapper in extensionWrappers) { @@ -90,7 +90,7 @@ private async Task GetInstalledExtensionsAsync() } var hasSettingsProvider = extensionWrapper.HasProviderType(ProviderType.Settings); - var extension = new InstalledExtensionViewModel(extensionWrapper.Name, extensionWrapper.ExtensionUniqueId, hasSettingsProvider); + var extension = new InstalledExtensionViewModel(extensionWrapper.ExtensionDisplayName, extensionWrapper.ExtensionUniqueId, hasSettingsProvider); // Each extension is shown under the package that contains it. Check if we have the package in the list // already and if not, create it and add it to the list of packages. Then add the extension to that @@ -99,7 +99,7 @@ private async Task GetInstalledExtensionsAsync() if (package == null) { package = new InstalledPackageViewModel( - extensionWrapper.Name, + extensionWrapper.PackageDisplayName, extensionWrapper.Publisher, extensionWrapper.PackageFamilyName, extensionWrapper.InstalledDate, diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionSettingsViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionSettingsViewModel.cs index cb1aac5ad3..3a4cb47105 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionSettingsViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionSettingsViewModel.cs @@ -46,7 +46,7 @@ private async Task OnSettingsContentLoadedAsync(ExtensionAdaptiveCardPanel exten if ((_navigationService.LastParameterUsed != null) && ((string)_navigationService.LastParameterUsed == extensionWrapper.ExtensionUniqueId)) { - FillBreadcrumbBar(extensionWrapper.Name); + FillBreadcrumbBar(extensionWrapper.ExtensionDisplayName); var settingsProvider = Task.Run(() => extensionWrapper.GetProviderAsync()).Result; if (settingsProvider != null) diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/InstalledPackageViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/InstalledPackageViewModel.cs index 3232666dad..7593a2ac7a 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/InstalledPackageViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/InstalledPackageViewModel.cs @@ -87,7 +87,7 @@ private void NavigateSettings() public partial class InstalledPackageViewModel : ObservableObject { [ObservableProperty] - private string _title; + private string _displayName; [ObservableProperty] private string _publisher; @@ -103,9 +103,9 @@ public partial class InstalledPackageViewModel : ObservableObject public ObservableCollection InstalledExtensionsList { get; set; } - public InstalledPackageViewModel(string title, string publisher, string packageFamilyName, DateTimeOffset installedDate, PackageVersion version) + public InstalledPackageViewModel(string displayName, string publisher, string packageFamilyName, DateTimeOffset installedDate, PackageVersion version) { - _title = title; + _displayName = displayName; _publisher = publisher; _packageFamilyName = packageFamilyName; _installedDate = installedDate; diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml index 60cdaf1775..5f56915dde 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml @@ -62,7 +62,7 @@ - - diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs index 11610776ae..dc47ac7579 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs @@ -60,7 +60,7 @@ public RepositoryProvider(IExtensionWrapper extensionWrapper) public string DisplayName => _repositoryProvider.DisplayName; - public string ExtensionDisplayName => _extensionWrapper.Name; + public string ExtensionDisplayName => _extensionWrapper.ExtensionDisplayName; /// /// Starts the extension if it isn't running. diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs index 7fcdb85427..346ddb20ea 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProviders.cs @@ -37,7 +37,7 @@ public string DisplayName(string providerName) public RepositoryProviders(IEnumerable extensionWrappers) { - _providers = extensionWrappers.ToDictionary(extensionWrapper => extensionWrapper.Name, extensionWrapper => new RepositoryProvider(extensionWrapper)); + _providers = extensionWrappers.ToDictionary(extensionWrapper => extensionWrapper.ExtensionDisplayName, extensionWrapper => new RepositoryProvider(extensionWrapper)); } public void StartAllExtensions() diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs index b58908efbc..a4c8819ced 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs @@ -171,7 +171,7 @@ private async Task ForEachEnabledExtensionAsync(Func extension.HasProviderType(ProviderType.Repository) && - extension.HasProviderType(ProviderType.DeveloperId)).OrderBy(extensionWrapper => extensionWrapper.Name); + extension.HasProviderType(ProviderType.DeveloperId)).OrderBy(extensionWrapper => extensionWrapper.ExtensionDisplayName); _providers = new RepositoryProviders(extensions); From bbd93a0d7c156d8d3476731c821f62f5ca669fb8 Mon Sep 17 00:00:00 2001 From: Felipe G Date: Mon, 8 Apr 2024 10:41:28 -0700 Subject: [PATCH 07/22] Announcing removing widgets from dashboard (#2537) * adding call to screen reader service * Adding string to be said * moving logic to control code --------- Co-authored-by: Felipe da Conceicao Guimaraes --- src/Views/ShellPage.xaml | 2 +- .../Controls/WidgetControl.xaml.cs | 2 + .../Strings/en-us/Resources.resw | 65 ++++++++++++++++++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/Views/ShellPage.xaml b/src/Views/ShellPage.xaml index 37b0b4086f..2b58a85590 100644 --- a/src/Views/ShellPage.xaml +++ b/src/Views/ShellPage.xaml @@ -126,7 +126,7 @@ from reading the control value. --> - + diff --git a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs index d3b607beaa..c559a4ee09 100644 --- a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs @@ -105,6 +105,8 @@ private async void OnRemoveWidgetClick(object sender, RoutedEventArgs e) var widgetIdToDelete = widgetViewModel.Widget.Id; var widgetToDelete = widgetViewModel.Widget; _log.Debug($"User removed widget, delete widget {widgetIdToDelete}"); + var stringResource = new StringResource("DevHome.Dashboard.pri", "DevHome.Dashboard/Resources"); + Application.Current.GetService().Announce(stringResource.GetLocalized("WidgetRemoved")); DashboardView.PinnedWidgets.Remove(widgetViewModel); try { diff --git a/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw b/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw index b1231169a4..077df6bc2e 100644 --- a/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw +++ b/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw @@ -1,5 +1,64 @@  + @@ -190,4 +249,8 @@ Close Text for a button to close a content dialog. - + + Widget removed + This is said by narrator whenever a widget is removed + + \ No newline at end of file From efdffbc5d8c6c90854edb594609da9da69b4ac90 Mon Sep 17 00:00:00 2001 From: Felipe G Date: Mon, 8 Apr 2024 11:13:27 -0700 Subject: [PATCH 08/22] Adding AutomationProperties.Name string to dashboard page (#2545) * Adding string to dashboard page * Removing automation --------- Co-authored-by: Felipe da Conceicao Guimaraes --- .../DevHome.Dashboard/Strings/en-us/Resources.resw | 6 +++++- tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw b/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw index 077df6bc2e..5ca6692709 100644 --- a/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw +++ b/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw @@ -249,8 +249,12 @@ Close Text for a button to close a content dialog. + + Dashboard + Dashboard accessible name to be narrated + Widget removed This is said by narrator whenever a widget is removed - \ No newline at end of file + diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml index 22512232aa..c2f82f92e8 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml +++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml @@ -3,6 +3,7 @@ Date: Tue, 9 Apr 2024 09:47:10 -0700 Subject: [PATCH 09/22] Finish UI for environments creation (#2539) * add initial changes * add parsers * fix build * update existing classes and xaml * add hyper-v adaptive card for creation * add changes for hyper-v * fix build * add changes to show settings cards from hyper v * update with latest changes * add recent changes * fix build * add updates for adaptive cards to appear in review page * add creation to experiements page and fix adaptive card not showing up after moving to a different page * add missing message classes * Delete tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreateEnvironmentTask.cs * update message passing * add initial changes * add more updates * update based on comments, and add more comments * update comments and itemsviews choice set to prevent the choiceset from holding onto items view control internally * Use adaptive card render service to render adaptive card within content dialog * add updates * add * update json * add more updates * add more changes * only update UI if status method returns a mail * add updates for UI * add finishing UI changed * update based on comments and make sure a new create environment flow is made when you click the create environment button * update spelling --- .../Extensions/StreamExtensions.cs | 15 +- .../Models/ByteTransferProgress.cs | 28 +++ .../VMGalleryCreationAdaptiveCardSession.cs | 32 +-- .../ArchiveExtractionReport.cs | 13 +- .../DotNetZipArchiveProvider.cs | 6 +- .../DownloadOperationReport.cs | 9 +- .../IOperationReport.cs | 4 +- .../VMGalleryCreationUserInput.cs | 2 +- .../VMGalleryVMCreationOperation.cs | 41 ++-- .../Services/DownloaderService.cs | 8 +- .../Strings/en-US/Resources.resw | 12 +- .../InitialVMGalleryCreationForm.json | 2 +- .../HyperVExtensionIntegrationTest.cs | 2 +- .../Services/HyperVProviderTests.cs | 2 +- .../Mocks/DownloaderServiceMock.cs | 9 +- .../CardStateColorToBrushConverter.cs | 1 + .../Environments/CustomControls/CardBody.xaml | 46 +++-- .../CustomControls/CardBody.xaml.cs | 16 ++ ...teCreateComputeSystemOperationException.cs | 18 ++ common/Environments/Models/CardProperty.cs | 1 + .../Models/ComputeSystemProvider.cs | 21 +- .../Models/CreateComputeSystemOperation.cs | 183 +++++++++++++++++ .../FailedCreateComputeSystemOperation.cs | 35 ++++ .../Services/ComputeSystemManager.cs | 45 ++++ .../Services/IComputeSystemManager.cs | 9 + common/Services/INavigationService.cs | 2 + common/Strings/en-us/Resources.resw | 12 +- .../DevHome.Environments.csproj | 1 - .../Selectors/CardItemTemplateSelector.cs | 42 ++++ .../Strings/en-us/Resources.resw | 28 +++ .../ViewModels/ComputeSystemCardBase.cs | 48 +++++ .../ViewModels/ComputeSystemViewModel.cs | 40 +--- .../CreateComputeSystemOperationViewModel.cs | 150 ++++++++++++++ .../ViewModels/LandingPageViewModel.cs | 129 +++++++++--- .../ViewModels/OperationsViewModel.cs | 32 ++- .../Views/LandingPage.xaml | 135 ++++++++---- .../Views/LandingPage.xaml.cs | 5 - .../Controls/SetupShell.xaml | 2 +- .../Controls/SetupShell.xaml.cs | 7 + .../Environments/CreateEnvironmentTask.cs | 193 ++++++++++++++++++ .../Services/SetupFlowOrchestrator.cs | 1 + .../Services/StringResourceKey.cs | 18 ++ .../Strings/en-us/Resources.resw | 63 +++++- .../EnvironmentCreationOptionsTaskGroup.cs | 5 +- .../CreateEnvironmentReviewViewModel.cs | 2 +- .../EnvironmentCreationOptionsViewModel.cs | 4 +- .../SelectEnvironmentProviderViewModel.cs | 1 - .../ViewModels/MainPageViewModel.cs | 4 +- .../ViewModels/ReviewViewModel.cs | 22 ++ .../ViewModels/SetupFlowViewModel.cs | 24 +++ .../ViewModels/SummaryViewModel.cs | 52 ++++- .../EnvironmentCreationOptionsView.xaml | 8 +- .../SelectEnvironmentProviderView.xaml | 4 +- .../DevHome.SetupFlow/Views/ReviewView.xaml | 13 +- .../Views/SetupFlowPage.xaml.cs | 7 + .../DevHome.SetupFlow/Views/SummaryView.xaml | 43 +++- .../Views/SummaryView.xaml.cs | 55 ++++- 57 files changed, 1485 insertions(+), 227 deletions(-) create mode 100644 HyperVExtension/src/HyperVExtension/Models/ByteTransferProgress.cs create mode 100644 common/Environments/Exceptions/CreateCreateComputeSystemOperationException.cs create mode 100644 common/Environments/Models/CreateComputeSystemOperation.cs create mode 100644 common/Environments/Models/FailedCreateComputeSystemOperation.cs create mode 100644 tools/Environments/DevHome.Environments/Selectors/CardItemTemplateSelector.cs create mode 100644 tools/Environments/DevHome.Environments/ViewModels/ComputeSystemCardBase.cs create mode 100644 tools/Environments/DevHome.Environments/ViewModels/CreateComputeSystemOperationViewModel.cs create mode 100644 tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreateEnvironmentTask.cs diff --git a/HyperVExtension/src/HyperVExtension/Extensions/StreamExtensions.cs b/HyperVExtension/src/HyperVExtension/Extensions/StreamExtensions.cs index a1af58afa9..c44703db17 100644 --- a/HyperVExtension/src/HyperVExtension/Extensions/StreamExtensions.cs +++ b/HyperVExtension/src/HyperVExtension/Extensions/StreamExtensions.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using HyperVExtension.Models; namespace HyperVExtension.Extensions; @@ -19,10 +20,11 @@ public static class StreamExtensions /// The object that progress will be reported to /// The size of the buffer which is used to read data from the source stream and write it to the destination stream /// A cancellation token that will allow the caller to cancel the operation - public static async Task CopyToAsync(this Stream source, Stream destination, IProgress progressProvider, int bufferSize, CancellationToken cancellationToken) + public static async Task CopyToAsync(this Stream source, Stream destination, IProgress progressProvider, int bufferSize, long totalBytesToExtract, CancellationToken cancellationToken) { var buffer = new byte[bufferSize]; long totalRead = 0; + var lastPercentage = 0U; while (true) { @@ -37,8 +39,15 @@ public static async Task CopyToAsync(this Stream source, Stream destination, IPr await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken); totalRead += bytesRead; - // Report the progress of the operation. - progressProvider.Report(totalRead); + var progressPercentage = (uint)(totalRead / (double)totalBytesToExtract * 100D); + + // Only update progress when a whole percentage has been completed. + if (progressPercentage != lastPercentage) + { + // Report the progress of the operation. + progressProvider.Report(new ByteTransferProgress(totalRead, totalBytesToExtract)); + lastPercentage = progressPercentage; + } } } } diff --git a/HyperVExtension/src/HyperVExtension/Models/ByteTransferProgress.cs b/HyperVExtension/src/HyperVExtension/Models/ByteTransferProgress.cs new file mode 100644 index 0000000000..1cb1cf2b51 --- /dev/null +++ b/HyperVExtension/src/HyperVExtension/Models/ByteTransferProgress.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HyperVExtension.Models; + +/// +/// Represents progress of an operation that require transferring bytes from one place to another. +/// +public class ByteTransferProgress +{ + public long BytesReceived { get; set; } + + public long TotalBytesToReceive { get; set; } + + public uint PercentageComplete => (uint)((BytesReceived / (double)TotalBytesToReceive) * 100); + + public ByteTransferProgress(long bytesReceived, long totalBytesToReceive) + { + BytesReceived = bytesReceived; + TotalBytesToReceive = totalBytesToReceive; + } +} diff --git a/HyperVExtension/src/HyperVExtension/Models/VMGalleryCreationAdaptiveCardSession.cs b/HyperVExtension/src/HyperVExtension/Models/VMGalleryCreationAdaptiveCardSession.cs index 9ebeef4edb..7136b91b3f 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VMGalleryCreationAdaptiveCardSession.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VMGalleryCreationAdaptiveCardSession.cs @@ -90,7 +90,7 @@ public IAsyncOperation OnAction(string action, string i switch (_creationAdaptiveCard?.State) { case "initialCreationForm": - operationResult = await HandleActionWhenFormInInitalState(actionPayload, inputs); + operationResult = await HandleActionWhenFormInInitialState(actionPayload, inputs); break; case "reviewForm": (operationResult, shouldEndSession) = await HandleActionWhenFormInReviewState(actionPayload); @@ -158,16 +158,16 @@ private ProviderOperationResult GetInitialCreationFormAdaptiveCard() foreach (var image in _vMGalleryImageList.Images) { var dataJson = new JsonObject - { - { "ImageDescription", GetMergedDescription(image) }, - { "SubDescription", image.Publisher }, - { "Header", image.Name }, - { "HeaderIcon", image.Symbol.Base64Image }, - { "ActionButtonText", "More info" }, - { "ContentDialogInfo", SetupContentDialogInfo(image) }, - { "ButtonToLaunchContentDialogLabel", buttonToLaunchContentDialogLabel }, - { "SecondaryButtonForContentDialogText", secondaryButtonForContentDialogText }, - }; + { + { "ImageDescription", GetMergedDescription(image) }, + { "SubDescription", image.Publisher }, + { "Header", image.Name }, + { "HeaderIcon", image.Symbol.Base64Image }, + { "ActionButtonText", "More info" }, + { "ContentDialogInfo", SetupContentDialogInfo(image) }, + { "ButtonToLaunchContentDialogLabel", buttonToLaunchContentDialogLabel }, + { "SecondaryButtonForContentDialogText", secondaryButtonForContentDialogText }, + }; jsonArrayOfGalleryImages.Add(dataJson); } @@ -211,7 +211,7 @@ private async Task GetForReviewFormAdaptiveCardAsync(st } var galleryImage = _vMGalleryImageList.Images[inputForGalleryOperation.SelectedImageListIndex]; - var newVirtualMachineNameLabel = _stringResource.GetLocalized("NameLabelForNewVirtualMachine", ":"); + var newEnvironmentNameLabel = _stringResource.GetLocalized("NameLabelForNewVirtualMachine", ":"); var primaryButtonForCreationFlowText = _stringResource.GetLocalized("PrimaryButtonLabelForCreationFlow"); var secondaryButtonForCreationFlowText = _stringResource.GetLocalized("SecondaryButtonLabelForCreationFlow"); var storageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(Constants.ExtensionIconInternal)); @@ -227,8 +227,8 @@ private async Task GetForReviewFormAdaptiveCardAsync(st { "DiskImageSize", BytesHelper.ConvertBytesToString(galleryImage.Disk.SizeInBytes) }, { "VMGalleryImageName", galleryImage.Name }, { "Publisher", galleryImage.Publisher }, - { "NameOfNewVM", inputForGalleryOperation.NewVirtualMachineName }, - { "NameLabel", newVirtualMachineNameLabel }, + { "NameOfNewVM", inputForGalleryOperation.NewEnvironmentName }, + { "NameLabel", newEnvironmentNameLabel }, { "Base64ImageForProvider", providerBase64Image }, { "DiskImageUrl", galleryImage.Symbol.Uri }, { "PrimaryButtonLabelForCreationFlow", primaryButtonForCreationFlowText }, @@ -247,7 +247,7 @@ private async Task GetForReviewFormAdaptiveCardAsync(st } /// - /// The discription for VM gallery images is stored in a list of strings. This method merges the strings into one string. + /// The description for VM gallery images is stored in a list of strings. This method merges the strings into one string. /// /// The c# class that represents the gallery image /// A string that combines the original list of strings into one @@ -298,7 +298,7 @@ private JsonObject SetupContentDialogInfo(VMGalleryImage image) }; } - private async Task HandleActionWhenFormInInitalState(AdaptiveCardActionPayload actionPayload, string inputs) + private async Task HandleActionWhenFormInInitialState(AdaptiveCardActionPayload actionPayload, string inputs) { ProviderOperationResult operationResult; var actionButtonId = actionPayload.Id ?? string.Empty; diff --git a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/ArchiveExtractionReport.cs b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/ArchiveExtractionReport.cs index c5c5657e11..e606dacf64 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/ArchiveExtractionReport.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/ArchiveExtractionReport.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; + namespace HyperVExtension.Models.VirtualMachineCreation; /// @@ -10,15 +12,12 @@ public sealed class ArchiveExtractionReport : IOperationReport { public ReportKind ReportKind => ReportKind.ArchiveExtraction; - public string LocalizationKey => "ExtractingFile"; - - public ulong BytesReceived { get; private set; } + public string LocalizationKey => "ExtractionInProgress"; - public ulong TotalBytesToReceive { get; private set; } + public ByteTransferProgress ProgressObject { get; private set; } - public ArchiveExtractionReport(ulong bytesReceived, ulong totalBytesToReceive) + public ArchiveExtractionReport(ByteTransferProgress progressObj) { - BytesReceived = bytesReceived; - TotalBytesToReceive = totalBytesToReceive; + ProgressObject = progressObj; } } diff --git a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/DotNetZipArchiveProvider.cs b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/DotNetZipArchiveProvider.cs index 8c76087c49..073a2822ed 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/DotNetZipArchiveProvider.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/DotNetZipArchiveProvider.cs @@ -28,13 +28,13 @@ public async Task ExtractArchiveAsync(IProgress progressProvid using var outputFileStream = File.OpenWrite(destinationAbsoluteFilePath); using var zipArchiveEntryStream = zipArchiveEntry.Open(); - var fileExtractionProgress = new Progress(bytesCopied => + var fileExtractionProgress = new Progress(progressObj => { - progressProvider.Report(new ArchiveExtractionReport((ulong)bytesCopied, (ulong)totalBytesToExtract)); + progressProvider.Report(new ArchiveExtractionReport(progressObj)); }); outputFileStream.SetLength(totalBytesToExtract); - await zipArchiveEntryStream.CopyToAsync(outputFileStream, fileExtractionProgress, _transferBufferSize, cancellationToken); + await zipArchiveEntryStream.CopyToAsync(outputFileStream, fileExtractionProgress, _transferBufferSize, totalBytesToExtract, cancellationToken); File.SetLastWriteTime(destinationAbsoluteFilePath, zipArchiveEntry.LastWriteTime.DateTime); } } diff --git a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/DownloadOperationReport.cs b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/DownloadOperationReport.cs index abec4f6301..53fe906262 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/DownloadOperationReport.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/DownloadOperationReport.cs @@ -9,13 +9,10 @@ public class DownloadOperationReport : IOperationReport public string LocalizationKey => "DownloadInProgress"; - public ulong BytesReceived { get; private set; } + public ByteTransferProgress ProgressObject { get; private set; } - public ulong TotalBytesToReceive { get; private set; } - - public DownloadOperationReport(ulong bytesReceived, ulong totalBytesToReceive) + public DownloadOperationReport(ByteTransferProgress progressObj) { - BytesReceived = bytesReceived; - TotalBytesToReceive = totalBytesToReceive; + ProgressObject = progressObj; } } diff --git a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/IOperationReport.cs b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/IOperationReport.cs index bf6bd3eb17..e418d3266a 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/IOperationReport.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/IOperationReport.cs @@ -15,7 +15,5 @@ public interface IOperationReport public string LocalizationKey { get; } - public ulong BytesReceived { get; } - - public ulong TotalBytesToReceive { get; } + public ByteTransferProgress ProgressObject { get; } } diff --git a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryCreationUserInput.cs b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryCreationUserInput.cs index d43193e2b5..73ecb39d1e 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryCreationUserInput.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryCreationUserInput.cs @@ -10,7 +10,7 @@ namespace HyperVExtension.Models.VirtualMachineCreation; /// public sealed class VMGalleryCreationUserInput { - public string NewVirtualMachineName { get; set; } = string.Empty; + public string NewEnvironmentName { get; set; } = string.Empty; [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] public int SelectedImageListIndex { get; set; } diff --git a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryVMCreationOperation.cs b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryVMCreationOperation.cs index 64463af6dc..6e3202ff51 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryVMCreationOperation.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryVMCreationOperation.cs @@ -79,14 +79,7 @@ public VMGalleryVMCreationOperation( /// The archive extraction operation returned by the progress handler which extracts the archive file public void Report(IOperationReport value) { - var displayText = Image.Name; - - if (value.ReportKind == ReportKind.ArchiveExtraction) - { - displayText = $"{ArchivedFile!.Name} ({Image.Name})"; - } - - UpdateProgress(value, value.LocalizationKey, displayText); + UpdateProgress(value, value.LocalizationKey, $"({Image.Name})"); } /// @@ -114,6 +107,7 @@ public void Report(IOperationReport value) IsOperationInProgress = true; } + UpdateProgress(_stringResource.GetLocalized("CreationStarting", $"({_userInputParameters.NewEnvironmentName})")); var imageList = await _vmGalleryService.GetGalleryImagesAsync(); if (imageList.Images.Count == 0) { @@ -130,12 +124,12 @@ public void Report(IOperationReport value) var archiveProvider = _archiveProviderFactory.CreateArchiveProvider(ArchivedFile!.FileType); await archiveProvider.ExtractArchiveAsync(this, ArchivedFile!, absoluteFilePathForVhd, CancellationTokenSource.Token); - var virtualMachineName = MakeFileNameValid(_userInputParameters.NewVirtualMachineName); + var virtualMachineName = MakeFileNameValid(_userInputParameters.NewEnvironmentName); // Use the Hyper-V manager to create the VM. UpdateProgress(_stringResource.GetLocalized("CreationInProgress", virtualMachineName)); var creationParameters = new VirtualMachineCreationParameters( - _userInputParameters.NewVirtualMachineName, + _userInputParameters.NewEnvironmentName, GetVirtualMachineProcessorCount(), absoluteFilePathForVhd, Image.Config.SecureBoot, @@ -158,16 +152,29 @@ public void Report(IOperationReport value) private void UpdateProgress(IOperationReport report, string localizedKey, string fileName) { - var bytesReceivedSoFar = BytesHelper.ConvertBytesToString(report.BytesReceived); - var totalBytesToReceive = BytesHelper.ConvertBytesToString(report.TotalBytesToReceive); - var progressPercentage = (uint)((report.BytesReceived / (double)report.TotalBytesToReceive) * 100D); - var displayString = _stringResource.GetLocalized(localizedKey, fileName, $"{bytesReceivedSoFar}/{totalBytesToReceive}"); - Progress?.Invoke(this, new CreateComputeSystemProgressEventArgs(displayString, progressPercentage)); + var bytesReceivedSoFar = BytesHelper.ConvertBytesToString((ulong)report.ProgressObject.BytesReceived); + var totalBytesToReceive = BytesHelper.ConvertBytesToString((ulong)report.ProgressObject.TotalBytesToReceive); + var displayString = _stringResource.GetLocalized(localizedKey, fileName, $"{bytesReceivedSoFar} / {totalBytesToReceive}"); + try + { + Progress?.Invoke(this, new CreateComputeSystemProgressEventArgs(displayString, report.ProgressObject.PercentageComplete)); + } + catch (Exception ex) + { + _log.Error("Failed to update progress", ex); + } } private void UpdateProgress(string localizedString, uint percentage = 0u) { - Progress?.Invoke(this, new CreateComputeSystemProgressEventArgs(localizedString, percentage)); + try + { + Progress?.Invoke(this, new CreateComputeSystemProgressEventArgs(localizedString, percentage)); + } + catch (Exception ex) + { + _log.Error("Failed to update progress", ex); + } } /// @@ -227,7 +234,7 @@ private string MakeFileNameValid(string originalName) private string GetUniqueAbsoluteFilePath(string defaultVirtualDiskPath) { var extension = Path.GetExtension(Image.Disk.ArchiveRelativePath); - var expectedExtractedFileLocation = Path.Combine(defaultVirtualDiskPath, $"{_userInputParameters.NewVirtualMachineName}{extension}"); + var expectedExtractedFileLocation = Path.Combine(defaultVirtualDiskPath, $"{_userInputParameters.NewEnvironmentName}{extension}"); var appendedNumber = 1u; var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(expectedExtractedFileLocation); diff --git a/HyperVExtension/src/HyperVExtension/Services/DownloaderService.cs b/HyperVExtension/src/HyperVExtension/Services/DownloaderService.cs index 6593a7adfc..dda443863d 100644 --- a/HyperVExtension/src/HyperVExtension/Services/DownloaderService.cs +++ b/HyperVExtension/src/HyperVExtension/Services/DownloaderService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using HyperVExtension.Extensions; +using HyperVExtension.Models; using HyperVExtension.Models.VirtualMachineCreation; namespace HyperVExtension.Services; @@ -32,13 +33,12 @@ public async Task StartDownloadAsync(IProgress progressProvide using var outputFileStream = File.OpenWrite(destinationFile); outputFileStream.SetLength(totalBytesToReceive); - var downloadProgress = new Progress(bytesCopied => + var downloadProgress = new Progress(progressObj => { - var percentage = (uint)(bytesCopied / (double)totalBytesToReceive * 100D); - progressProvider.Report(new DownloadOperationReport((ulong)bytesCopied, (ulong)totalBytesToReceive)); + progressProvider.Report(new DownloadOperationReport(progressObj)); }); - await webFileStream.CopyToAsync(outputFileStream, downloadProgress, _transferBufferSize, cancellationToken); + await webFileStream.CopyToAsync(outputFileStream, downloadProgress, _transferBufferSize, totalBytesToReceive, cancellationToken); } /// diff --git a/HyperVExtension/src/HyperVExtension/Strings/en-US/Resources.resw b/HyperVExtension/src/HyperVExtension/Strings/en-US/Resources.resw index 2c0fd6dfa5..7f7aa770ac 100644 --- a/HyperVExtension/src/HyperVExtension/Strings/en-US/Resources.resw +++ b/HyperVExtension/src/HyperVExtension/Strings/en-US/Resources.resw @@ -118,8 +118,12 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Creating: {0} - Locked="{0}" text to tell the user that we're currently creating the virtual machine. {0} is the name of the virtual machine + Adding network switch, secure boot and enhanced session configuration for {0} + Locked="{0}" Text to tell the user that we're performing post creation actions like adding a network switch to the virtual machine. {0} is the name of the virtual machine + + + Starting the creation process for {0} + Locked="{0}" Text to tell the user that we're starting the process to create the virtual machine. {0} is the name of the virtual machine Current Checkpoint @@ -130,7 +134,7 @@ Locked="{0}" text to tell the user that a file exists and we do not need to download it again. {0} is a previously download file. We show the file name in {0}. - Downloading {0}. {1} + Downloading {0} {1} Locked="{0}" text to tell the user that we are downloading a file from the web. {0} is the file we're downloading. {1} the progress in the form of "bytes received / total bytes needed". E.g "10 Mb / 400 Mb" @@ -158,7 +162,7 @@ Attempt counter text for the dialog to enter Hyper-V VM admin credential ({CurrentAttempt}/{MaxAttempts}). - Extracting file {0}. {1} + Extracting file {0} {1} Locked="{0}" text to tell the user that we're extracting a zip file into a location on their computer. {0} is the zip file we're extracting. {1} the progress in the form of "bytes extracted / total bytes needed". E.g "10 Mb / 400 Mb" diff --git a/HyperVExtension/src/HyperVExtension/Templates/InitialVMGalleryCreationForm.json b/HyperVExtension/src/HyperVExtension/Templates/InitialVMGalleryCreationForm.json index 95e64d78ba..31a9d10b4a 100644 --- a/HyperVExtension/src/HyperVExtension/Templates/InitialVMGalleryCreationForm.json +++ b/HyperVExtension/src/HyperVExtension/Templates/InitialVMGalleryCreationForm.json @@ -5,7 +5,7 @@ "body": [ { "type": "Input.Text", - "id": "NewVirtualMachineName", + "id": "NewEnvironmentName", "label": "${EnterNewVMNameLabel}", "placeholder": "${EnterNewVMNamePlaceHolder}", "maxLength": 100, diff --git a/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVExtensionIntegrationTest.cs b/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVExtensionIntegrationTest.cs index 97a0aaf1ce..8de473bab9 100644 --- a/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVExtensionIntegrationTest.cs +++ b/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVExtensionIntegrationTest.cs @@ -393,7 +393,7 @@ public async Task TestVirtualMachineCreationFromVmGallery() var smallestImageIndex = await GetIndexOfImageWithSmallestRequiredSpace(imageList); var inputJson = JsonSerializer.Serialize(new VMGalleryCreationUserInput() { - NewVirtualMachineName = expectedVMName, + NewEnvironmentName = expectedVMName, // Get Image with the smallest size from gallery, we'll use it to create a VM. SelectedImageListIndex = smallestImageIndex, diff --git a/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVProviderTests.cs b/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVProviderTests.cs index 68ca35480d..68a62af01c 100644 --- a/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVProviderTests.cs +++ b/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVProviderTests.cs @@ -77,7 +77,7 @@ public async Task HyperVProvider_Can_Create_VirtualMachine() var hyperVProvider = TestHost!.GetService(); var inputJson = JsonSerializer.Serialize(new VMGalleryCreationUserInput() { - NewVirtualMachineName = _expectedVmName, + NewEnvironmentName = _expectedVmName, SelectedImageListIndex = 0, // Our test gallery image list Json only has one image }); diff --git a/HyperVExtension/test/HyperVExtension/Mocks/DownloaderServiceMock.cs b/HyperVExtension/test/HyperVExtension/Mocks/DownloaderServiceMock.cs index 712f7cf5a3..52c8134a33 100644 --- a/HyperVExtension/test/HyperVExtension/Mocks/DownloaderServiceMock.cs +++ b/HyperVExtension/test/HyperVExtension/Mocks/DownloaderServiceMock.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using HyperVExtension.Models; using HyperVExtension.Models.VirtualMachineCreation; using HyperVExtension.Services; using Windows.Storage; @@ -11,9 +12,9 @@ public class DownloaderServiceMock : IDownloaderService { private readonly int _totalIterations = 4; - private readonly ulong _totalBytesToReceive = 1000; + private readonly long _totalBytesToReceive = 1000; - private readonly ulong _bytesReceivedEachIteration = 250; + private readonly long _bytesReceivedEachIteration = 250; private readonly IHttpClientFactory _httpClientFactory; @@ -24,12 +25,12 @@ public DownloaderServiceMock(IHttpClientFactory httpClientFactory) public async Task StartDownloadAsync(IProgress progressProvider, Uri sourceWebUri, string destinationFile, CancellationToken cancellationToken) { - var bytesReceivedSoFar = 0UL; + var bytesReceivedSoFar = 0L; for (var i = 0; i < _totalIterations; i++) { await Task.Delay(100, cancellationToken); bytesReceivedSoFar += _bytesReceivedEachIteration; - progressProvider.Report(new DownloadOperationReport(bytesReceivedSoFar, _totalBytesToReceive)); + progressProvider.Report(new DownloadOperationReport(new ByteTransferProgress(bytesReceivedSoFar, _totalBytesToReceive))); } var zipFile = await GetTestZipFileInPackage(); diff --git a/common/Environments/Converters/CardStateColorToBrushConverter.cs b/common/Environments/Converters/CardStateColorToBrushConverter.cs index 0ac1303b9b..ca01704c09 100644 --- a/common/Environments/Converters/CardStateColorToBrushConverter.cs +++ b/common/Environments/Converters/CardStateColorToBrushConverter.cs @@ -25,6 +25,7 @@ public object Convert(object value, Type targetType, object parameter, string la CardStateColor.Success => (SolidColorBrush)Application.Current.Resources["SystemFillColorSuccessBrush"], CardStateColor.Neutral => (SolidColorBrush)Application.Current.Resources["SystemFillColorSolidNeutralBrush"], CardStateColor.Caution => (SolidColorBrush)Application.Current.Resources["SystemFillColorCautionBrush"], + CardStateColor.Failure => (SolidColorBrush)Application.Current.Resources["SystemFillColorCriticalBrush"], _ => (SolidColorBrush)Application.Current.Resources["SystemFillColorCautionBrush"], }; } diff --git a/common/Environments/CustomControls/CardBody.xaml b/common/Environments/CustomControls/CardBody.xaml index 5fa356c1ff..5c72231e56 100644 --- a/common/Environments/CustomControls/CardBody.xaml +++ b/common/Environments/CustomControls/CardBody.xaml @@ -7,10 +7,14 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:devEnvConverters="using:DevHome.Common.Environments.Converters" xmlns:controls="using:CommunityToolkit.WinUI.Controls" + xmlns:converters="using:CommunityToolkit.WinUI.Converters" mc:Ignorable="d"> + + + - + - - - - + + + + - + - + + @@ -81,6 +95,10 @@ HorizontalSpacing="15"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -118,8 +190,23 @@ + + @@ -142,11 +229,14 @@ + ItemsSource="{x:Bind ViewModel.Providers, Mode=OneWay}" + SelectedIndex="{x:Bind ViewModel.SelectedProviderIndex, Mode=TwoWay}"> - - + + @@ -191,44 +281,15 @@ + MaxWidth="{ThemeResource MaxPageContentWidth}" + ItemsSource="{x:Bind ViewModel.ComputeSystemCardsView}" SelectionMode="None" + ItemTemplateSelector="{StaticResource CardItemTemplateSelector}" + ItemContainerStyle="{StaticResource HorizontalCardListViewItemContainerForManagementPageStyle}"> - - - - - - - - - - - - - - - - - - - - + (Visibility)GetValue(HeaderVisibilityProperty); set => SetValue(HeaderVisibilityProperty, value); } + + public Visibility ContentVisibility + { + get => (Visibility)GetValue(ContentVisibilityProperty); + set => SetValue(ContentVisibilityProperty, value); + } public SetupShell() { @@ -66,4 +72,5 @@ public SetupShell() public static readonly DependencyProperty HeaderProperty = DependencyProperty.RegisterAttached(nameof(Header), typeof(object), typeof(SetupShell), new PropertyMetadata(null)); public static readonly DependencyProperty OrchestratorProperty = DependencyProperty.RegisterAttached(nameof(Orchestrator), typeof(SetupFlowOrchestrator), typeof(SetupShell), new PropertyMetadata(null)); public static readonly DependencyProperty HeaderVisibilityProperty = DependencyProperty.RegisterAttached(nameof(HeaderVisibility), typeof(Visibility), typeof(SetupShell), new PropertyMetadata(Visibility.Visible)); + public static readonly DependencyProperty ContentVisibilityProperty = DependencyProperty.RegisterAttached(nameof(ContentVisibility), typeof(Visibility), typeof(SetupShell), new PropertyMetadata(Visibility.Visible)); } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreateEnvironmentTask.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreateEnvironmentTask.cs new file mode 100644 index 0000000000..41d541f11e --- /dev/null +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/Environments/CreateEnvironmentTask.cs @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +extern alias Projection; + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Messaging; +using DevHome.Common.Environments.Models; +using DevHome.Common.Environments.Services; +using DevHome.Common.Models; +using DevHome.SetupFlow.Models.Environments; +using DevHome.SetupFlow.Services; +using DevHome.SetupFlow.ViewModels; +using Projection::DevHome.SetupFlow.ElevatedComponent; +using Serilog; +using Windows.Foundation; +using DevHomeSDK = Microsoft.Windows.DevHome.SDK; + +namespace DevHome.SetupFlow.Models; + +/// +/// Task that creates an environment using the user input from an adaptive card session. +/// +public sealed class CreateEnvironmentTask : ISetupTask, IDisposable, IRecipient +{ + private readonly ILogger _log = Log.ForContext("SourceContext", nameof(CreateEnvironmentTask)); + + private readonly IComputeSystemManager _computeSystemManager; + + private readonly TaskMessages _taskMessages; + + private readonly ActionCenterMessages _actionCenterMessages = new(); + + private readonly ISetupFlowStringResource _stringResource; + + // Used to signal the task to start the creation operation. This is used when the adaptive card session ends. + private readonly AutoResetEvent _autoResetEventToStartCreationOperation = new(false); + + private readonly SetupFlowViewModel _setupFlowViewModel; + + private bool _disposedValue; + + public event ISetupTask.ChangeMessageHandler AddMessage; + + public string UserJsonInput { get; set; } + + public ComputeSystemProviderDetails ProviderDetails { get; set; } + + public DeveloperIdWrapper DeveloperIdWrapper { get; set; } + + // The "#pragma warning disable 67" directive suppresses the CS0067 warning. + // CS0067 is a warning that occurs when a public event is declared but never used. +#pragma warning disable 67 + public event ISetupTask.ChangeActionCenterMessageHandler UpdateActionCenterMessage; +#pragma warning restore 67 + + public bool RequiresAdmin => false; + + public bool RequiresReboot => false; + + public bool DependsOnDevDriveToBeInstalled => false; + + public bool CreationOperationStarted { get; private set; } + + public ISummaryInformationViewModel SummaryScreenInformation { get; } + + public CreateEnvironmentTask(IComputeSystemManager computeSystemManager, ISetupFlowStringResource stringResource, SetupFlowViewModel setupFlowViewModel) + { + _computeSystemManager = computeSystemManager; + _stringResource = stringResource; + _taskMessages = new TaskMessages + { + Executing = _stringResource.GetLocalized(StringResourceKey.StartingEnvironmentCreation), + Finished = _stringResource.GetLocalized(StringResourceKey.EnvironmentCreationOperationInitializationFinished), + Error = _stringResource.GetLocalized(StringResourceKey.EnvironmentCreationError), + }; + _setupFlowViewModel = setupFlowViewModel; + _setupFlowViewModel.EndSetupFlow += OnEndSetupFlow; + + // Register for the adaptive card session ended message so we can use the session data to create the environment + WeakReferenceMessenger.Default.Register(this); + } + + public ActionCenterMessages GetErrorMessages() => _actionCenterMessages; + + public TaskMessages GetLoadingMessages() => _taskMessages; + + public ActionCenterMessages GetRebootMessage() => new(); + + /// + /// Receives the adaptive card session ended message from the he + /// once the extension sends the session ended event. + /// + /// + /// The message payload that contains the provider and the user input json that will be used to invoke the + /// + /// + public void Receive(CreationAdaptiveCardSessionEndedMessage message) + { + _log.Information("The extension sent the session ended event"); + ProviderDetails = message.Value.ProviderDetails; + + // Json input that the user entered in the adaptive card session + UserJsonInput = message.Value.UserInputResultJson; + + // In the future we'll add the specific developer ID to the task, but for now since we haven't + // add support for switching between developer Id's in the environments pages, we'll use the first one + // in the provider details list of developer IDs. If we get here, then there should be at least one. + DeveloperIdWrapper = message.Value.ProviderDetails.DeveloperIds.First(); + + _log.Information("Signaling to the waiting event handle to Continue the 'Execute' operation"); + _autoResetEventToStartCreationOperation.Set(); + } + + private void OnEndSetupFlow(object sender, EventArgs e) + { + WeakReferenceMessenger.Default.Unregister(this); + _setupFlowViewModel.EndSetupFlow -= OnEndSetupFlow; + } + + IAsyncOperation ISetupTask.Execute() + { + return Task.Run(() => + { + _log.Information("Executing the operation. Waiting to be signalled that the adaptive card session has ended"); + + // Either wait until we're signaled to continue execution or we times out after 1 minute. If this task is initiated + // then that means the user went past the review page. At this point the extension should be firing a session ended + // event. Since the call flow is disjointed an extension may not have sent the session ended event when this method is called. + _autoResetEventToStartCreationOperation.WaitOne(TimeSpan.FromMinutes(1)); + + if (string.IsNullOrWhiteSpace(UserJsonInput)) + { + // The extension's creation adaptive card may not need user input. In that case, the user input will be null or empty. + _log.Information("UserJsonInput is null or empty."); + } + + // If the provider details are null, then we can't proceed with the operation. This happens if the auto event times out. + if (ProviderDetails == null) + { + _log.Error("ProviderDetails is null so we cannot proceed with executing the task"); + AddMessage(_stringResource.GetLocalized(StringResourceKey.EnvironmentCreationFailedToGetProviderInformation), MessageSeverityKind.Error); + return TaskFinishedState.Failure; + } + + var sdkCreateEnvironmentOperation = ProviderDetails.ComputeSystemProvider.CreateCreateComputeSystemOperation(DeveloperIdWrapper.DeveloperId, UserJsonInput); + var createComputeSystemOperationWrapper = new CreateComputeSystemOperation(sdkCreateEnvironmentOperation, ProviderDetails, UserJsonInput); + + // Start the operation, which returns immediately and runs in the background. + createComputeSystemOperationWrapper.StartOperation(); + AddMessage(_stringResource.GetLocalized(StringResourceKey.EnvironmentCreationForProviderStarted), MessageSeverityKind.Info); + + _computeSystemManager.AddRunningOperationForCreation(createComputeSystemOperationWrapper); + CreationOperationStarted = true; + + _log.Information("Successfully started the creation operation"); + return TaskFinishedState.Success; + }).AsAsyncOperation(); + } + + IAsyncOperation ISetupTask.ExecuteAsAdmin(IElevatedComponentOperation elevatedComponentOperation) + { + return Task.Run(() => + { + // No admin rights required for this task. This shouldn't ever be invoked since the RequiresAdmin property is always false. + _log.Error("Admin execution is not required for the create environment task"); + return TaskFinishedState.Failure; + }).AsAsyncOperation(); + } + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _autoResetEventToStartCreationOperation.Dispose(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowOrchestrator.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowOrchestrator.cs index 955e524fe5..93469e207a 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowOrchestrator.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/SetupFlowOrchestrator.cs @@ -27,6 +27,7 @@ public enum SetupFlowKind { LocalMachine, SetupTarget, + CreateEnvironment, } /// diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs index 81050c7075..9535a5eaaa 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/StringResourceKey.cs @@ -266,5 +266,23 @@ public static class StringResourceKey public static readonly string SelectEnvironmentPageTitle = nameof(SelectEnvironmentPageTitle); public static readonly string ConfigureEnvironmentPageTitle = nameof(ConfigureEnvironmentPageTitle); public static readonly string EnvironmentCreationReviewPageTitle = nameof(EnvironmentCreationReviewPageTitle); + public static readonly string EnvironmentCreationReviewTabTitle = nameof(EnvironmentCreationReviewTabTitle); + public static readonly string EnvironmentCreationError = nameof(EnvironmentCreationError); + public static readonly string StartingEnvironmentCreation = nameof(StartingEnvironmentCreation); + public static readonly string EnvironmentCreationOperationInitializationFinished = nameof(EnvironmentCreationOperationInitializationFinished); + public static readonly string EnvironmentCreationForProviderStarted = nameof(EnvironmentCreationForProviderStarted); + public static readonly string EnvironmentCreationFailedToGetProviderInformation = nameof(EnvironmentCreationFailedToGetProviderInformation); + public static readonly string EnvironmentCreationReviewExpanderDescription = nameof(EnvironmentCreationReviewExpanderDescription); public static readonly string CreateEnvironmentButtonText = nameof(CreateEnvironmentButtonText); + public static readonly string SetupShellReviewPageDescriptionForEnvironmentCreation = nameof(SetupShellReviewPageDescriptionForEnvironmentCreation); + + // Summary page + public static readonly string SummaryPageOpenDashboard = nameof(SummaryPageOpenDashboard); + public static readonly string SummaryPageRedirectToEnvironmentPageButton = nameof(SummaryPageRedirectToEnvironmentPageButton); + public static readonly string SummaryPageHeader = nameof(SummaryPageHeader); + public static readonly string SummaryPageHeaderForEnvironmentCreationText = nameof(SummaryPageHeaderForEnvironmentCreationText); + + // Review page + public static readonly string ReviewExpanderDescription = nameof(ReviewExpanderDescription); + public static readonly string SetupShellReviewPageDescription = nameof(SetupShellReviewPageDescription); } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw index 92b4555a2e..6bee36d1ba 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw +++ b/tools/SetupFlow/DevHome.SetupFlow/Strings/en-us/Resources.resw @@ -649,7 +649,7 @@ Generate a WinGet Configuration file (.winget) to repeat this set up in the future or share it with others. {Locked="WinGet",".winget"}Tooltip text about the generated configuration file - + Set up details Header for a section detailing the set up steps to be performed. "Set up" is the noun @@ -713,10 +713,14 @@ Select a target machine to set up. Description for the setup target page - + Review the terms and setup details below before applying these changes to your computer. Description for the review page + + Review the details for your new environment + Description for the review page + Applications Header for a section showing a summary of applications to be installed @@ -1044,7 +1048,7 @@ Installed applications Header for the section that shows all the downloaded apps - + Here's what we set up for you Header text for the summary page @@ -1056,7 +1060,7 @@ Next steps Text for the "Next steps" section of the summary page - + Open Dashboard Button content to let user go to the dashboard @@ -1764,12 +1768,59 @@ There was an error retreiving the adaptive card session from the extension Error text display when we are unable to retrieve the adaptive card information from an extension + + Environment + Title for create environment review tab + + + Your environment's details + Title for create environment review tab + - Create Environment + Review your environment Title for create environment review page Create Environment - Text for button that starts creating a new virtual machine or cloud pc for the user + + + There was an error starting the creation operation + Text to tell the user that we couldn't start the operation to create their local or cloud virtual environment + + + The operation to create your environment has started successfully + Text to tell the user that we were able to start the operation that create their local or cloud virtual machine successfully + + + Starting the create environment operation + Text to tell the user that the operation to create their local or cloud virtual environment has started + + + We timed out waiting for the extension to provide us with information to create your environment + Error text to show the user that we timed out while waiting for a response from a Dev Home extension + + + The {0} provider is now creating your environment + {Locked="{0}"} Text that tells the user that a specific provider has started creating their environment. {0} is the name of a provider who Dev Home will send the request to. + + + We failed to start the creation operation for the {0} provider + {Locked="{0}"} Text that tells the user that Dev Home could not start the creation operation for a specific provider. {0} is the name of a provider who Dev Home will send the request to. + + + Environment details + Heading that will show the user the details of the environment they initiated the creation process for. Environments can be local or cloud virtual machines + + + Go to Environments page + Text for button that when clicked will redirect the user to the environments page in Dev Home + + + Environment being created + Text to tell the user that an environment is being created. Environments can be local or cloud virtual machines + + + We've started creating your environment + Text to tell the user that an environment is being created. Environments can be local or cloud virtual machines \ No newline at end of file diff --git a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/EnvironmentCreationOptionsTaskGroup.cs b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/EnvironmentCreationOptionsTaskGroup.cs index 7661f0361e..130377a53d 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/EnvironmentCreationOptionsTaskGroup.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/EnvironmentCreationOptionsTaskGroup.cs @@ -23,6 +23,8 @@ public class EnvironmentCreationOptionsTaskGroup : ISetupTaskGroup public ComputeSystemProviderDetails ProviderDetails { get; private set; } + public CreateEnvironmentTask CreateEnvironmentTask { get; private set; } + public EnvironmentCreationOptionsTaskGroup( SetupFlowViewModel setupFlowViewModel, IComputeSystemManager computeSystemManager, @@ -34,9 +36,10 @@ public EnvironmentCreationOptionsTaskGroup( _createEnvironmentReviewViewModel = createEnvironmentReviewViewModel; _setupFlowStringResource = setupFlowStringResource; _computeSystemManager = computeSystemManager; + CreateEnvironmentTask = new CreateEnvironmentTask(_computeSystemManager, _setupFlowStringResource, setupFlowViewModel); } - public IEnumerable SetupTasks => new List(); + public IEnumerable SetupTasks => new List() { CreateEnvironmentTask }; public IEnumerable DSCTasks => new List(); diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/CreateEnvironmentReviewViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/CreateEnvironmentReviewViewModel.cs index a735e3f15a..543ef7d544 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/CreateEnvironmentReviewViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/CreateEnvironmentReviewViewModel.cs @@ -16,6 +16,6 @@ public CreateEnvironmentReviewViewModel( ISetupFlowStringResource stringResource) { _stringResource = stringResource; - TabTitle = stringResource.GetLocalized(StringResourceKey.EnvironmentCreationReviewPageTitle); + TabTitle = stringResource.GetLocalized(StringResourceKey.EnvironmentCreationReviewTabTitle); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs index 11a62a5522..38fa6699a3 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs @@ -91,6 +91,7 @@ public EnvironmentCreationOptionsViewModel( _elementRegistration.Set(DevHomeLaunchContentDialogButton.AdaptiveElementType, new DevHomeLaunchContentDialogButtonParser()); _elementRegistration.Set(DevHomeContentDialogContent.AdaptiveElementType, new DevHomeContentDialogContentParser()); _adaptiveCardRenderingService = renderingService; + Orchestrator.CurrentSetupFlowKind = SetupFlowKind.CreateEnvironment; } /// @@ -109,6 +110,7 @@ private void OnEndSetupFlow(object sender, EventArgs e) ResetAdaptiveCardConfiguration(); WeakReferenceMessenger.Default.UnregisterAll(this); _setupFlowViewModel.EndSetupFlow -= OnEndSetupFlow; + Orchestrator.CurrentSetupFlowKind = SetupFlowKind.LocalMachine; } protected async override Task OnFirstNavigateToAsync() @@ -267,7 +269,7 @@ private void OnReviewPageViewRequest(EnvironmentCreationOptionsViewModel recipie { // Only send the adaptive card if the session has loaded. If the session hasn't loaded yet, we'll send an empty response. The review page should be sent the adaptive card // once the session has loaded in the OnAdaptiveCardUpdated method. - if (!IsAdaptiveCardSessionLoaded) + if (!IsAdaptiveCardSessionLoaded && Orchestrator?.CurrentPageViewModel is not SummaryViewModel) { return; } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs index 4ac0ecf9fb..dedd93a429 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs @@ -49,7 +49,6 @@ private async Task LoadProvidersAsync() Orchestrator.NotifyNavigationCanExecuteChanged(); var providerDetails = await Task.Run(_computeSystemService.GetComputeSystemProvidersAsync); - ProvidersViewModels = new(); foreach (var providerDetail in providerDetails) { diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs index 9e1f69ffdd..76a5319861 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs @@ -192,12 +192,12 @@ private void StartRepoConfig(string flowTitle) /// Starts the create environment flow. /// [RelayCommand] - private void StartCreateEnvironment(string flowTitle) + public void StartCreateEnvironment(string flowTitle) { _log.Information("Starting flow for environment creation"); StartSetupFlowForTaskGroups( flowTitle, - "RepoConfig", + "CreateEnvironment", _host.GetService(), _host.GetService()); } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs index 7f9ef63faa..1129c1b62f 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs @@ -41,6 +41,15 @@ public partial class ReviewViewModel : SetupPageViewModelBase [NotifyCanExecuteChangedFor(nameof(SetUpCommand))] private bool _canSetUp; + [ObservableProperty] + private string _reviewPageTitle; + + [ObservableProperty] + private string _reviewPageExpanderDescription; + + [ObservableProperty] + private string _reviewPageDescription; + public bool HasApplicationsToInstall => Orchestrator.GetTaskGroup()?.SetupTasks.Any() == true; public bool RequiresTermsAgreement => HasApplicationsToInstall; @@ -88,6 +97,8 @@ public ReviewViewModel( { NextPageButtonText = StringResource.GetLocalized(StringResourceKey.SetUpButton); PageTitle = StringResource.GetLocalized(StringResourceKey.ReviewPageTitle); + ReviewPageExpanderDescription = StringResource.GetLocalized(StringResourceKey.ReviewExpanderDescription); + ReviewPageDescription = StringResource.GetLocalized(StringResourceKey.SetupShellReviewPageDescription); _setupFlowOrchestrator = orchestrator; _configFileBuilder = configFileBuilder; @@ -104,7 +115,18 @@ protected async override Task OnEachNavigateToAsync() .ToList(); SelectedReviewTab = ReviewTabs.FirstOrDefault(); + // If the CreateEnvironmentTaskGroup is present, update the setup button text to "Create Environment" + // and page title to "Review your environment" + if (Orchestrator.GetTaskGroup() != null) + { + NextPageButtonText = StringResource.GetLocalized(StringResourceKey.CreateEnvironmentButtonText); + PageTitle = StringResource.GetLocalized(StringResourceKey.EnvironmentCreationReviewPageTitle); + ReviewPageExpanderDescription = StringResource.GetLocalized(StringResourceKey.EnvironmentCreationReviewExpanderDescription); + ReviewPageDescription = StringResource.GetLocalized(StringResourceKey.SetupShellReviewPageDescriptionForEnvironmentCreation); + } + NextPageButtonToolTipText = HasTasksToSetUp ? null : StringResource.GetLocalized(StringResourceKey.ReviewNothingToSetUpToolTip); + UpdateCanSetUp(); await Task.CompletedTask; diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs index f6f3b23546..feba7c037b 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs @@ -14,6 +14,7 @@ using DevHome.SetupFlow.Services; using DevHome.Telemetry; using Microsoft.Extensions.Hosting; +using Microsoft.UI.Xaml.Navigation; using Serilog; using Windows.Storage; @@ -26,6 +27,8 @@ public partial class SetupFlowViewModel : ObservableObject private readonly MainPageViewModel _mainPageViewModel; private readonly PackageProvider _packageProvider; + private readonly string _creationFlowNavigationParameter = "StartCreationFlow"; + public SetupFlowOrchestrator Orchestrator { get; } public event EventHandler EndSetupFlow = (s, e) => { }; @@ -119,4 +122,25 @@ public async Task StartFileActivationFlowAsync(StorageFile file) Orchestrator.FlowPages = [_mainPageViewModel]; await _mainPageViewModel.StartConfigurationFileAsync(file); } + + public void StartCreationFlowAsync() + { + Orchestrator.FlowPages = [_mainPageViewModel]; + _mainPageViewModel.StartCreateEnvironment(string.Empty); + } + + public void OnNavigatedTo(NavigationEventArgs args) + { + // The setup flow isn't setup to support using the navigation service to navigate to specific + // pages. Instead we need to navigate to the main page and then start the creation flow template manually. + var parameter = args.Parameter?.ToString(); + + if ((!string.IsNullOrEmpty(parameter)) && + _creationFlowNavigationParameter.Equals(parameter, StringComparison.OrdinalIgnoreCase) && + Orchestrator.CurrentSetupFlowKind != SetupFlowKind.CreateEnvironment) + { + Cancel(); + StartCreationFlowAsync(); + } + } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SummaryViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SummaryViewModel.cs index 884831843e..b6e45248dc 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SummaryViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SummaryViewModel.cs @@ -108,6 +108,20 @@ public ObservableCollection AppsDownloaded } } + public bool WasCreateEnvironmentOperationStarted + { + get + { + var taskGroup = Orchestrator.GetTaskGroup(); + if (taskGroup == null) + { + return false; + } + + return taskGroup.CreateEnvironmentTask.CreationOperationStarted; + } + } + public List AppsDownloadedInstallationNotes => AppsDownloaded.Where(p => !string.IsNullOrEmpty(p.InstallationNotes)).ToList(); public IList ConfigurationUnitResults => _configurationUnitResults.Value; @@ -134,6 +148,9 @@ public ObservableCollection AppsDownloaded [ObservableProperty] private string _applicationsClonedText; + [ObservableProperty] + private string _summaryPageEnvironmentCreatingText; + [RelayCommand] public void RemoveRestartGrid() { @@ -157,7 +174,6 @@ public void GoToMainPage() _setupFlowViewModel.TerminateCurrentFlow("Summary_GoToMainPage"); } - [RelayCommand] public void GoToDashboard() { TelemetryFactory.Get().Log("Summary_NavigateTo_Event", LogLevel.Critical, new NavigateFromSummaryEvent("Dashboard"), Orchestrator.ActivityId); @@ -165,6 +181,26 @@ public void GoToDashboard() _setupFlowViewModel.TerminateCurrentFlow("Summary_GoToDashboard"); } + [RelayCommand] + public void RedirectToNextPage() + { + if (WasCreateEnvironmentOperationStarted) + { + GoToEnvironmentsPage(); + return; + } + + // Default behavior is to go to the dashboard + GoToDashboard(); + } + + public void GoToEnvironmentsPage() + { + TelemetryFactory.Get().Log("Summary_NavigateTo_Event", LogLevel.Critical, new NavigateFromSummaryEvent("Environments"), Orchestrator.ActivityId); + _host.GetService().NavigateTo(KnownPageKeys.Environments); + _setupFlowViewModel.TerminateCurrentFlow("Summary_GoToEnvironments"); + } + [RelayCommand] public void GoToDevHomeSettings() { @@ -180,6 +216,12 @@ public void GoToForDevelopersSettingsPage() Task.Run(() => Launcher.LaunchUriAsync(new Uri("ms-settings:developers"))).Wait(); } + [ObservableProperty] + private string _pageRedirectButtonText; + + [ObservableProperty] + private string _pageHeaderText; + public SummaryViewModel( ISetupFlowStringResource stringResource, SetupFlowOrchestrator orchestrator, @@ -199,6 +241,8 @@ public SummaryViewModel( _showRestartNeeded = Visibility.Collapsed; _appManagementInitializer = appManagementInitializer; _cloneRepoNextSteps = new(); + PageRedirectButtonText = StringResource.GetLocalized(StringResourceKey.SummaryPageOpenDashboard); + PageHeaderText = StringResource.GetLocalized(StringResourceKey.SummaryPageHeader); IsNavigationBarVisible = true; IsStepPage = false; @@ -259,6 +303,12 @@ protected async override Task OnFirstNavigateToAsync() TelemetryFactory.Get().LogCritical("Summary_NavigatedTo_Event", false, Orchestrator.ActivityId); } + if (WasCreateEnvironmentOperationStarted) + { + PageRedirectButtonText = StringResource.GetLocalized(StringResourceKey.SummaryPageRedirectToEnvironmentPageButton); + PageHeaderText = StringResource.GetLocalized(StringResourceKey.SummaryPageHeaderForEnvironmentCreationText); + } + await ReloadCatalogsAsync(); } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml index d987ef1f54..fa967365f7 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml @@ -21,15 +21,17 @@ + Grid.Row="0" + ContentVisibility="Collapsed"> - + @@ -55,7 +57,7 @@ Height="25" Margin="0,150,0,20"/> - + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml index 659e66afa5..30efe7f7f5 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml @@ -38,10 +38,12 @@ + Grid.Row="0" + ContentVisibility="Collapsed"> - + + Style="{StaticResource BodyStrongTextBlockStyle}" + Text="{x:Bind ViewModel.ReviewPageExpanderDescription, Mode=OneWay}" /> From a4c182986e2265670d42c9177120e567d99afa84 Mon Sep 17 00:00:00 2001 From: David Bennett Date: Tue, 9 Apr 2024 11:05:44 -0700 Subject: [PATCH 12/22] Fix timestamp log format (#2568) --- .../src/DevSetupAgent/appsettings_hypervsetupagent.json | 4 ++-- .../src/DevSetupEngine/appsettings_hypervsetup.json | 4 ++-- .../src/HyperVExtensionServer/appsettings_hyperv.json | 4 ++-- extensions/CoreWidgetProvider/corewidgets_appsettings.json | 4 ++-- src/appsettings.json | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/HyperVExtension/src/DevSetupAgent/appsettings_hypervsetupagent.json b/HyperVExtension/src/DevSetupAgent/appsettings_hypervsetupagent.json index e5932df92c..b66927f704 100644 --- a/HyperVExtension/src/DevSetupAgent/appsettings_hypervsetupagent.json +++ b/HyperVExtension/src/DevSetupAgent/appsettings_hypervsetupagent.json @@ -12,7 +12,7 @@ { "Name": "Console", "Args": { - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Debug" } }, @@ -20,7 +20,7 @@ "Name": "File", "Args": { "path": "%DEVHOME_LOGS_ROOT%\\hyperv_setupagent.dhlog", - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Information", "rollingInterval": "Day" } diff --git a/HyperVExtension/src/DevSetupEngine/appsettings_hypervsetup.json b/HyperVExtension/src/DevSetupEngine/appsettings_hypervsetup.json index 9653f50282..ce815ea0b5 100644 --- a/HyperVExtension/src/DevSetupEngine/appsettings_hypervsetup.json +++ b/HyperVExtension/src/DevSetupEngine/appsettings_hypervsetup.json @@ -6,7 +6,7 @@ { "Name": "Console", "Args": { - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Debug" } }, @@ -14,7 +14,7 @@ "Name": "File", "Args": { "path": "%DEVHOME_LOGS_ROOT%\\hyperv_setup.dhlog", - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Information", "rollingInterval": "Day" } diff --git a/HyperVExtension/src/HyperVExtensionServer/appsettings_hyperv.json b/HyperVExtension/src/HyperVExtensionServer/appsettings_hyperv.json index 15a4f5fade..f79a3cf6d0 100644 --- a/HyperVExtension/src/HyperVExtensionServer/appsettings_hyperv.json +++ b/HyperVExtension/src/HyperVExtensionServer/appsettings_hyperv.json @@ -6,7 +6,7 @@ { "Name": "Console", "Args": { - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Debug" } }, @@ -14,7 +14,7 @@ "Name": "File", "Args": { "path": "%DEVHOME_LOGS_ROOT%\\hyperv.dhlog", - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Information", "rollingInterval": "Day" } diff --git a/extensions/CoreWidgetProvider/corewidgets_appsettings.json b/extensions/CoreWidgetProvider/corewidgets_appsettings.json index d598474f02..349755f1f9 100644 --- a/extensions/CoreWidgetProvider/corewidgets_appsettings.json +++ b/extensions/CoreWidgetProvider/corewidgets_appsettings.json @@ -6,7 +6,7 @@ { "Name": "Console", "Args": { - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Debug" } }, @@ -14,7 +14,7 @@ "Name": "File", "Args": { "path": "%DEVHOME_LOGS_ROOT%\\corewidgets.dhlog", - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Information", "rollingInterval": "Day" } diff --git a/src/appsettings.json b/src/appsettings.json index d118f3fcc9..b16cd33ca4 100644 --- a/src/appsettings.json +++ b/src/appsettings.json @@ -15,7 +15,7 @@ { "Name": "Console", "Args": { - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Debug" } }, @@ -23,7 +23,7 @@ "Name": "File", "Args": { "path": "%DEVHOME_LOGS_ROOT%\\devhome.dhlog", - "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", + "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}", "restrictedToMinimumLevel": "Information", "rollingInterval": "Day" } From 13fe40ee5b81bd436f819381f80300b2704a5f4b Mon Sep 17 00:00:00 2001 From: Felipe G Date: Tue, 9 Apr 2024 11:11:04 -0700 Subject: [PATCH 13/22] Changing Select to Invoke button action (#2555) Co-authored-by: Felipe da Conceicao Guimaraes --- .../DevHome.Dashboard/Controls/SelectableMenuFlyoutItem.cs | 2 +- .../Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/Dashboard/DevHome.Dashboard/Controls/SelectableMenuFlyoutItem.cs b/tools/Dashboard/DevHome.Dashboard/Controls/SelectableMenuFlyoutItem.cs index 10237c3949..9af21e4ed2 100644 --- a/tools/Dashboard/DevHome.Dashboard/Controls/SelectableMenuFlyoutItem.cs +++ b/tools/Dashboard/DevHome.Dashboard/Controls/SelectableMenuFlyoutItem.cs @@ -47,7 +47,7 @@ public void RemoveFromSelection() public void Select() { - IsSelected = true; + Invoke(); } protected override string GetClassNameCore() diff --git a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs index c559a4ee09..d1e012ffa8 100644 --- a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs @@ -204,7 +204,7 @@ private void MarkSize(SelectableMenuFlyoutItem menuSizeItem) }; menuSizeItem.Icon = fontIcon; var peer = FrameworkElementAutomationPeer.FromElement(menuSizeItem) as SelectableMenuFlyoutItemAutomationPeer; - peer.Select(); + peer.AddToSelection(); } private void AddCustomizeToWidgetMenu(MenuFlyout widgetMenuFlyout, WidgetViewModel widgetViewModel) From 690127e073915825848974845e97391464897b46 Mon Sep 17 00:00:00 2001 From: sshilov7 <51001703+sshilov7@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:56:20 -0700 Subject: [PATCH 14/22] Fix DevSetupAgent logging and some other fixes. (#2566) * Fix DevSetupAgent logging and some other fixes. * Address review comments. --- HyperVExtension/src/DevSetupAgent/Logging.cs | 21 ++ .../src/DevSetupAgent/NativeMethods.txt | 6 + HyperVExtension/src/DevSetupAgent/Program.cs | 108 +++++++-- .../DevSetupAgent/Requests/ErrorRequest.cs | 7 +- .../DevSetupAgent/Requests/RequestFactory.cs | 1 - .../DevSetupAgent/Responses/ErrorResponse.cs | 15 +- .../ErrorUnsupportedRequestResponse.cs | 2 +- HyperVExtension/src/DevSetupEngine/Logging.cs | 8 +- .../HResultException.cs | 4 +- .../ApplyConfigurationOperation.cs | 1 + .../Helpers/DevSetupAgentDeploymentHelper.cs | 208 +----------------- .../HyperVExtension/HyperVExtension.csproj | 4 + .../HyperVExtension/Scripts/DevSetupAgent.ps1 | 208 ++++++++++++++++++ 13 files changed, 361 insertions(+), 232 deletions(-) create mode 100644 HyperVExtension/src/DevSetupAgent/Logging.cs rename HyperVExtension/src/{HyperVExtension/CommunicationWithGuest => HyperVExtension.HostGuestCommunication}/HResultException.cs (66%) create mode 100644 HyperVExtension/src/HyperVExtension/Scripts/DevSetupAgent.ps1 diff --git a/HyperVExtension/src/DevSetupAgent/Logging.cs b/HyperVExtension/src/DevSetupAgent/Logging.cs new file mode 100644 index 0000000000..920111d914 --- /dev/null +++ b/HyperVExtension/src/DevSetupAgent/Logging.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Windows.Storage; + +namespace HyperVExtension.DevSetupAgent; + +public class Logging +{ + public static readonly string LogExtension = ".dhlog"; + + public static readonly string LogFolderName = "Logs"; + + public static readonly string AppName = "DevSetupAgent"; + + public static readonly string DefaultLogFileName = "hyperv_setup"; + + private static readonly Lazy _logFolderRoot = new(() => Path.Combine(Path.GetTempPath(), AppName, LogFolderName)); + + public static readonly string LogFolderRoot = _logFolderRoot.Value; +} diff --git a/HyperVExtension/src/DevSetupAgent/NativeMethods.txt b/HyperVExtension/src/DevSetupAgent/NativeMethods.txt index 13b6a24b02..82f030d248 100644 --- a/HyperVExtension/src/DevSetupAgent/NativeMethods.txt +++ b/HyperVExtension/src/DevSetupAgent/NativeMethods.txt @@ -6,8 +6,14 @@ CLSCTX WIN32_ERROR S_OK E_FAIL +E_UNEXPECTED LsaEnumerateLogonSessions LsaGetLogonSessionData Windows.Win32.Security.Authentication.Identity.LsaFreeReturnBuffer SECURITY_LOGON_TYPE STATUS_SUCCESS +MakeAbsoluteSD +ConvertStringSecurityDescriptorToSecurityDescriptor +LocalAlloc +LocalFree +SDDL_REVISION_1 diff --git a/HyperVExtension/src/DevSetupAgent/Program.cs b/HyperVExtension/src/DevSetupAgent/Program.cs index d8dbf882e2..dbc372a24c 100644 --- a/HyperVExtension/src/DevSetupAgent/Program.cs +++ b/HyperVExtension/src/DevSetupAgent/Program.cs @@ -1,34 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.ComponentModel; using System.Runtime.InteropServices; using HyperVExtension.DevSetupAgent; +using HyperVExtension.HostGuestCommunication; using Serilog; using Windows.Win32; +using Windows.Win32.Foundation; using Windows.Win32.Security; using Windows.Win32.System.Com; -unsafe -{ - // TODO: Set real security descriptor to allow access from System+Admns+Interactive Users - var hr = PInvoke.CoInitializeSecurity( - new(null), - -1, - null, - null, - RPC_C_AUTHN_LEVEL.RPC_C_AUTHN_LEVEL_DEFAULT, - RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IDENTIFY, - null, - EOLE_AUTHENTICATION_CAPABILITIES.EOAC_NONE); - - if (hr < 0) - { - Marshal.ThrowExceptionForHR(hr); - } -} - // Set up Logging -Environment.SetEnvironmentVariable("DEVHOME_LOGS_ROOT", Path.Join(HyperVExtension.DevSetupEngine.Logging.LogFolderRoot, "HyperV")); +Environment.SetEnvironmentVariable("DEVHOME_LOGS_ROOT", Path.Join(Logging.LogFolderRoot, "HyperV")); var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings_hypervsetupagent.json") .Build(); @@ -36,6 +20,90 @@ .ReadFrom.Configuration(configuration) .CreateLogger(); +unsafe +{ + PSECURITY_DESCRIPTOR absolutSd = new(null); + PSID ownerSid = new(null); + PSID groupSid = new(null); + ACL* dacl = default; + ACL* sacl = default; + + try + { + // O:PSG:BU Owner Principal Self, Group Built-in Users + // (A;;0x3;;;SY) Allow Local System + // (A;;0x3;;;IU) Allow Interactive User + var accessPermission = "O:PSG:BUD:(A;;0x3;;;SY)(A;;0x3;;;IU)"; + uint securityDescriptorSize; + PInvoke.ConvertStringSecurityDescriptorToSecurityDescriptor(accessPermission, PInvoke.SDDL_REVISION_1, out var securityDescriptor, &securityDescriptorSize); + + uint absoluteSdSize = default; + uint daclSize = default; + uint saclSize = default; + uint ownerSize = default; + uint groupSize = default; + + if (PInvoke.MakeAbsoluteSD(securityDescriptor, absolutSd, ref absoluteSdSize, null, ref daclSize, null, ref saclSize, ownerSid, ref ownerSize, groupSid, ref groupSize)) + { + throw new HResultException(HRESULT.E_UNEXPECTED); + } + + var error = Marshal.GetLastWin32Error(); + if (error != (int)WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER) + { + throw new Win32Exception(error); + } + + absolutSd = new(PInvoke.LocalAlloc(Windows.Win32.System.Memory.LOCAL_ALLOC_FLAGS.LPTR, absoluteSdSize)); + dacl = (ACL*)PInvoke.LocalAlloc(Windows.Win32.System.Memory.LOCAL_ALLOC_FLAGS.LPTR, daclSize); + sacl = (ACL*)PInvoke.LocalAlloc(Windows.Win32.System.Memory.LOCAL_ALLOC_FLAGS.LPTR, saclSize); + ownerSid = new(PInvoke.LocalAlloc(Windows.Win32.System.Memory.LOCAL_ALLOC_FLAGS.LPTR, ownerSize)); + groupSid = new(PInvoke.LocalAlloc(Windows.Win32.System.Memory.LOCAL_ALLOC_FLAGS.LPTR, groupSize)); + + if (!PInvoke.MakeAbsoluteSD(securityDescriptor, absolutSd, ref absoluteSdSize, dacl, ref daclSize, sacl, ref saclSize, ownerSid, ref ownerSize, groupSid, ref groupSize)) + { + throw new HResultException(Marshal.GetLastWin32Error()); + } + + var hr = PInvoke.CoInitializeSecurity( + absolutSd, + -1, + null, + null, + RPC_C_AUTHN_LEVEL.RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL.RPC_C_IMP_LEVEL_IDENTIFY, + null, + EOLE_AUTHENTICATION_CAPABILITIES.EOAC_NONE); + + if (hr < 0) + { + Marshal.ThrowExceptionForHR(hr); + } + } + finally + { + if (sacl != default) + { + PInvoke.LocalFree((HLOCAL)sacl); + } + + if (dacl != default) + { + PInvoke.LocalFree((HLOCAL)dacl); + } + + if (groupSid != default) + { + PInvoke.LocalFree((HLOCAL)groupSid.Value); + } + + if (ownerSid != default) + { + PInvoke.LocalFree((HLOCAL)ownerSid.Value); + } + } +} + var host = Host.CreateDefaultBuilder(args) .UseWindowsService(options => { diff --git a/HyperVExtension/src/DevSetupAgent/Requests/ErrorRequest.cs b/HyperVExtension/src/DevSetupAgent/Requests/ErrorRequest.cs index fabc9a2052..725f3dedf5 100644 --- a/HyperVExtension/src/DevSetupAgent/Requests/ErrorRequest.cs +++ b/HyperVExtension/src/DevSetupAgent/Requests/ErrorRequest.cs @@ -9,10 +9,11 @@ namespace HyperVExtension.DevSetupAgent; /// internal class ErrorRequest : IHostRequest { - public ErrorRequest(IRequestMessage requestMessage) + public ErrorRequest(IRequestMessage requestMessage, Exception? ex = null) { Timestamp = DateTime.UtcNow; RequestId = requestMessage.RequestId!; + Error = ex; } public virtual uint Version { get; set; } = 1; @@ -27,6 +28,8 @@ public ErrorRequest(IRequestMessage requestMessage) public virtual IHostResponse Execute(ProgressHandler progressHandler, CancellationToken stoppingToken) { - return new ErrorResponse(RequestId); + return new ErrorResponse(RequestId, Error); } + + private Exception? Error { get; } } diff --git a/HyperVExtension/src/DevSetupAgent/Requests/RequestFactory.cs b/HyperVExtension/src/DevSetupAgent/Requests/RequestFactory.cs index 7067634443..2323e714a4 100644 --- a/HyperVExtension/src/DevSetupAgent/Requests/RequestFactory.cs +++ b/HyperVExtension/src/DevSetupAgent/Requests/RequestFactory.cs @@ -42,7 +42,6 @@ public IHostRequest CreateRequest(IRequestContext requestContext) { if (_requestFactories.TryGetValue(requestType, out var createRequest)) { - // TODO: Try/catch error. requestContext.JsonData = requestJson!; return createRequest(requestContext); } diff --git a/HyperVExtension/src/DevSetupAgent/Responses/ErrorResponse.cs b/HyperVExtension/src/DevSetupAgent/Responses/ErrorResponse.cs index 4428ebf76e..da53faf194 100644 --- a/HyperVExtension/src/DevSetupAgent/Responses/ErrorResponse.cs +++ b/HyperVExtension/src/DevSetupAgent/Responses/ErrorResponse.cs @@ -9,11 +9,20 @@ namespace HyperVExtension.DevSetupAgent; /// internal sealed class ErrorResponse : ResponseBase { - public ErrorResponse(string requestId) + public ErrorResponse(string requestId, Exception? error) : base(requestId) { - Status = Windows.Win32.Foundation.HRESULT.E_FAIL; - ErrorDescription = "Missing Request data."; + if (error != null) + { + ErrorDescription = error.Message; + Status = (uint)error.HResult; + } + else + { + ErrorDescription = "Missing Request data."; + Status = Windows.Win32.Foundation.HRESULT.E_FAIL; + } + GenerateJsonData(); } } diff --git a/HyperVExtension/src/DevSetupAgent/Responses/ErrorUnsupportedRequestResponse.cs b/HyperVExtension/src/DevSetupAgent/Responses/ErrorUnsupportedRequestResponse.cs index c0e0331f13..e57dfd4a9a 100644 --- a/HyperVExtension/src/DevSetupAgent/Responses/ErrorUnsupportedRequestResponse.cs +++ b/HyperVExtension/src/DevSetupAgent/Responses/ErrorUnsupportedRequestResponse.cs @@ -13,7 +13,7 @@ public ErrorUnsupportedRequestResponse(string requestId, string requestType) : base(requestId, requestType) { Status = Windows.Win32.Foundation.HRESULT.E_FAIL; - ErrorDescription = "Missing Request type."; + ErrorDescription = "Unsupported Request type."; GenerateJsonData(); } } diff --git a/HyperVExtension/src/DevSetupEngine/Logging.cs b/HyperVExtension/src/DevSetupEngine/Logging.cs index e3e5e40435..a2cdefcdfe 100644 --- a/HyperVExtension/src/DevSetupEngine/Logging.cs +++ b/HyperVExtension/src/DevSetupEngine/Logging.cs @@ -9,11 +9,13 @@ public class Logging { public static readonly string LogExtension = ".dhlog"; - public static readonly string LogFolderName = "Logs"; + public static readonly string LogFolderName = "Logs"; + + public static readonly string AppName = "DevSetupEngine"; public static readonly string DefaultLogFileName = "hyperv_setup"; - - private static readonly Lazy _logFolderRoot = new(() => Path.Combine(ApplicationData.Current.TemporaryFolder.Path, LogFolderName)); + + private static readonly Lazy _logFolderRoot = new(() => Path.Combine(Path.GetTempPath(), AppName, LogFolderName)); public static readonly string LogFolderRoot = _logFolderRoot.Value; } diff --git a/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/HResultException.cs b/HyperVExtension/src/HyperVExtension.HostGuestCommunication/HResultException.cs similarity index 66% rename from HyperVExtension/src/HyperVExtension/CommunicationWithGuest/HResultException.cs rename to HyperVExtension/src/HyperVExtension.HostGuestCommunication/HResultException.cs index f812115e95..c64c211b30 100644 --- a/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/HResultException.cs +++ b/HyperVExtension/src/HyperVExtension.HostGuestCommunication/HResultException.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace HyperVExtension.CommunicationWithGuest; +namespace HyperVExtension.HostGuestCommunication; -internal sealed class HResultException : Exception +public sealed class HResultException : Exception { public HResultException(int resultCode, string? description = null) : base(description) diff --git a/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/ApplyConfigurationOperation.cs b/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/ApplyConfigurationOperation.cs index 08c70efa37..5b8c3165ed 100644 --- a/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/ApplyConfigurationOperation.cs +++ b/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/ApplyConfigurationOperation.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using HyperVExtension.HostGuestCommunication; using HyperVExtension.Models; using Microsoft.Windows.DevHome.SDK; using Windows.Foundation; diff --git a/HyperVExtension/src/HyperVExtension/Helpers/DevSetupAgentDeploymentHelper.cs b/HyperVExtension/src/HyperVExtension/Helpers/DevSetupAgentDeploymentHelper.cs index cb6ccaeaf6..b5796b7cc1 100644 --- a/HyperVExtension/src/HyperVExtension/Helpers/DevSetupAgentDeploymentHelper.cs +++ b/HyperVExtension/src/HyperVExtension/Helpers/DevSetupAgentDeploymentHelper.cs @@ -3,6 +3,7 @@ using System.Management.Automation; using System.Security; +using System.Text; using HyperVExtension.Exceptions; using HyperVExtension.Services; @@ -28,6 +29,7 @@ private enum ProcessorArchitecture : ushort private readonly IPowerShellService _powerShellService; private readonly string _vmId; + private readonly Lazy _script = new(() => LoadScript()); public DevSetupAgentDeploymentHelper(IPowerShellService powerShellService, string vmId) { @@ -43,7 +45,7 @@ public void DeployDevSetupAgent(string userName, SecureString password) var sourcePath = GetSourcePath(architecture); var deployDevSetupAgentStatement = new StatementBuilder() - .AddScript(_script, false) + .AddScript(_script.Value, false) .AddCommand("Install-DevSetupAgent") .AddParameter("VMId", _vmId) .AddParameter("Session", session) @@ -124,203 +126,9 @@ private ushort GetVMArchitechture(PSObject session) return (ushort)psObject.BaseObject; } - private readonly string _script = @" -function Install-DevSetupAgent -{ - Param( - [Parameter(Mandatory = $true)] - [Guid] $VMId, - - [Parameter(Mandatory = $true)] - [System.Management.Automation.Runspaces.PSSession] $Session, - - [Parameter(Mandatory = $true)] - [string] $Path - ) - - $ErrorActionPreference = ""Stop"" - $activity = ""Installing DevSetupAgent to VM $VMId"" - - # Validate input. Only .cab and .zip files are supported - # If $Path is a directory, it will be copied to the VM and installed as is - $isDirectory = $false - $isCab = $false - $inputFileName = $null - if (Test-Path -Path $Path -PathType 'Container') - { - $isDirectory = $true - } - elseif (Test-Path -Path $Path -PathType 'Leaf') - { - if ($Path -match '\.(cab)$') - { - $isCab = $true - } - elseif (-not $Path -match '\.(zip)$') - { - throw ""Only .cab and .zip files are supported"" - } - $inputFileName = Split-Path -Path $Path -Leaf - } - else - { - throw ""$Path does not exist"" - } - - - $DevSetupAgentConst = ""DevSetupAgent"" - $DevSetupEngineConst = ""DevSetupEngine"" - $session = $Session - - $guestTempDirectory = Invoke-Command -Session $session -ScriptBlock { $env:temp } - - [string] $guid = [System.Guid]::NewGuid() - $guestUnpackDirectory = Join-Path -Path $guestTempDirectory -ChildPath $guid - $guestDevSetupAgentTempDirectory = Join-Path -Path $guestUnpackDirectory -ChildPath $DevSetupAgentConst - - Write-Host ""Creating VM temporary folder $guestUnpackDirectory"" - Write-Progress -Activity $activity -Status ""Creating VM temporary folder $guestUnpackDirectory"" -PercentComplete 10 - Invoke-Command -Session $session -ScriptBlock { New-Item -Path ""$using:guestUnpackDirectory"" -ItemType ""directory"" } - - if ($isDirectory) - { - $destinationPath = $guestDevSetupAgentTempDirectory - } - else - { - $destinationPath = $guestUnpackDirectory - } - - Write-Host ""Copying $Path to VM $destinationPath"" - Write-Progress -Activity $activity -Status ""Copying DevSetupAgent to VM $destinationPath"" -PercentComplete 15 - Copy-Item -ToSession $session -Recurse -Path $Path -Destination $destinationPath - - - Invoke-Command -Session $session -ScriptBlock { - $ErrorActionPreference = ""Stop"" - - try - { - $guestDevSetupAgentPath = Join-Path -Path $Env:Programfiles -ChildPath $using:DevSetupAgentConst - - # Stop and remove previous version of DevSetupAgent service if it exists - $service = Get-Service -Name $using:DevSetupAgentConst -ErrorAction SilentlyContinue - if ($service) - { - $serviceWMI = Get-WmiObject -Class Win32_Service -Filter ""Name='$using:DevSetupAgentConst'"" - $existingServicePath = $serviceWMI.Properties[""PathName""].Value - if ($existingServicePath) - { - $guestDevSetupAgentPath = Split-Path $existingServicePath -Parent - } - - try - { - Write-Host ""Stopping DevSetupAgent service"" - Write-Progress -Activity $using:activity -Status ""Stopping DevSetupAgent service $destinationPath"" -PercentComplete 30 - $service.Stop() - } - catch - { - Write-Host ""Ignoring error: $PSItem"" - } - - Remove-Variable -Name service -ErrorAction SilentlyContinue - - # Remove-Service is only available in PowerShell 6.0 and later. Windows doesn't come with it preinstalled. - Write-Host ""Removing DevSetupAgent service"" - Write-Progress -Activity $using:activity -Status ""Removing DevSetupAgent service"" -PercentComplete 35 - $serviceWMI = Get-WmiObject -Class Win32_Service -Filter ""Name='$using:DevSetupAgentConst'"" - $serviceWMI.Delete() - Remove-Variable -Name serviceWMI -ErrorAction SilentlyContinue - } - - # Stop previous version of DevSetupEngine COM server if it exists - $devSetupEngineProcess = Get-Process -Name ""$using:DevSetupEngineConst"" -ErrorAction SilentlyContinue - if ($devSetupEngineProcess -ne $null) - { - Write-Host ""Stopping $using:DevSetupEngineConst process"" - Write-Progress -Activity $using:activity -Status ""Stopping $using:DevSetupEngineConst process"" -PercentComplete 40 - Stop-Process -Force -Name ""$using:DevSetupEngineConst"" - } - - # Unregister DevSetupEngine - $enginePath = Join-Path -Path $guestDevSetupAgentPath -ChildPath ""$using:DevSetupEngineConst.exe"" - if (Test-Path -Path $enginePath) - { - Write-Host ""Unregistering DevSetupEngine ($enginePath)"" - Write-Progress -Activity $using:activity -Status ""Registering DevSetupEngine ($enginePath)"" -PercentComplete 88 - &$enginePath ""-UnregisterComServer"" - } - - # Remove previous version of DevSetupAgent service files - if (Test-Path -Path $guestDevSetupAgentPath) - { - # Sleep a few seconds to make sure all handles released after shutting down previous DevSetupEngine - Start-Sleep -Seconds 7 - Write-Host ""Deleting old DevSetupAgent service files"" - Write-Progress -Activity $using:activity -Status ""Deleting old DevSetupAgent service files"" -PercentComplete 45 - Remove-Item -Recurse -Force -Path $guestDevSetupAgentPath - } - - if ($using:isDirectory) - { - Write-Host ""Copying DevSetupAgent to $guestDevSetupAgentPath"" - Write-Progress -Activity $using:activity -Status ""Deleting old DevSetupAgent service files"" -PercentComplete 50 - Copy-Item -Recurse -Path $using:guestDevSetupAgentTempDirectory -Destination $guestDevSetupAgentPath - } - elseif ($using:isCab) - { - $cabPath = Join-Path -Path $using:guestUnpackDirectory -ChildPath $using:inputFileName - Write-Host ""Unpacking $cabPath to $guestDevSetupAgentPath"" - Write-Progress -Activity $using:activity -Status ""Unpacking $cabPath to $guestDevSetupAgentPath"" -PercentComplete 60 - $expandOutput=&""$Env:SystemRoot\System32\expand.exe"" $cabPath /F:* $Env:Programfiles - if ($LastExitCode -ne 0) - { - throw ""Error unpacking $cabPath`:`n$LastExitCode`n$($expandOutput|Out-String)"" - } - } - else - { - $zipPath = Join-Path -Path $using:guestUnpackDirectory -ChildPath $using:inputFileName - Write-Host ""Unpacking $using:inputFileName to $guestDevSetupAgentPath"" - Write-Progress -Activity $using:activity -Status ""Unpacking $using:inputFileName to $guestDevSetupAgentPath"" -PercentComplete 60 - Expand-Archive -Path $zipPath -Destination $guestDevSetupAgentPath - } - - # Register DevSetupAgent service - $servicePath = Join-Path -Path $guestDevSetupAgentPath -ChildPath ""$using:DevSetupAgentConst.exe"" - Write-Host ""Registering DevSetupAgent service ($servicePath)"" - Write-Progress -Activity $using:activity -Status ""Registering DevSetupAgent service ($servicePath)"" -PercentComplete 85 - New-Service -Name $using:DevSetupAgentConst -BinaryPathName $servicePath -StartupType Automatic - - # Register DevSetupEngine - Write-Host ""Registering DevSetupEngine ($enginePath)"" - Write-Progress -Activity $using:activity -Status ""Registering DevSetupEngine ($enginePath)"" -PercentComplete 88 - - # Executing non-console apps using '&' does not set $LastExitCode. Using Start-Process here to get the returned error code. - $process = Start-Process -NoNewWindow -Wait $enginePath -ArgumentList ""-RegisterComServer"" -PassThru - if ($process.ExitCode -ne 0) - { - throw ""Error registering $enginePath`: $process.ExitCode"" - } - - Write-Host ""Starting DevSetupAgent service"" - Write-Progress -Activity $using:activity -Status ""Starting DevSetupAgent service"" -PercentComplete 92 - Start-Service $using:DevSetupAgentConst - } - catch - { - Write-Host ""Error on guest OS: $PSItem"" - } - finally - { - Write-Host ""Removing temporary directory $using:guestUnpackDirectory"" - Remove-Item -Recurse -Force -Path $using:guestUnpackDirectory -ErrorAction SilentlyContinue - } - } - - Remove-PSSession $session -} -"; + private static string LoadScript() + { + var path = Path.Combine(AppContext.BaseDirectory, "HyperVExtension", "Scripts", "DevSetupAgent.ps1"); + return File.ReadAllText(path, Encoding.Default) ?? throw new FileNotFoundException(path); + } } diff --git a/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj b/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj index 2e6b76865e..4e3f564cf0 100644 --- a/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj +++ b/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj @@ -8,10 +8,14 @@ win-x86;win-x64;win-arm64 + + + Always + Always diff --git a/HyperVExtension/src/HyperVExtension/Scripts/DevSetupAgent.ps1 b/HyperVExtension/src/HyperVExtension/Scripts/DevSetupAgent.ps1 new file mode 100644 index 0000000000..f81bce1a5d --- /dev/null +++ b/HyperVExtension/src/HyperVExtension/Scripts/DevSetupAgent.ps1 @@ -0,0 +1,208 @@ +<# +.SYNOPSIS + +Install DevSetupAgent Windows service and DevSetupEngine COM server on a VM + +.DESCRIPTION + +Install DevSetupAgent Windows service and DevSetupEngine COM server on a VM +through the provided PSSession. +#> + +function Install-DevSetupAgent +{ + Param( + [Parameter(Mandatory = $true)] + [Guid] $VMId, + + [Parameter(Mandatory = $true)] + [System.Management.Automation.Runspaces.PSSession] $Session, + + [Parameter(Mandatory = $true)] + [string] $Path + ) + + $ErrorActionPreference = "Stop" + $activity = "Installing DevSetupAgent to VM $VMId" + + # Validate input. Only .cab and .zip files are supported + # If $Path is a directory, it will be copied to the VM and installed as is + $isDirectory = $false + $isCab = $false + $inputFileName = $null + if (Test-Path -Path $Path -PathType 'Container') + { + $isDirectory = $true + } + elseif (Test-Path -Path $Path -PathType 'Leaf') + { + if ($Path -match '\.(cab)$') + { + $isCab = $true + } + elseif (-not $Path -match '\.(zip)$') + { + throw "Only .cab and .zip files are supported" + } + $inputFileName = Split-Path -Path $Path -Leaf + } + else + { + throw "$Path does not exist" + } + + + $DevSetupAgentConst = "DevSetupAgent" + $DevSetupEngineConst = "DevSetupEngine" + $session = $Session + + $guestTempDirectory = Invoke-Command -Session $session -ScriptBlock { $env:temp } + + [string] $guid = [System.Guid]::NewGuid() + $guestUnpackDirectory = Join-Path -Path $guestTempDirectory -ChildPath $guid + $guestDevSetupAgentTempDirectory = Join-Path -Path $guestUnpackDirectory -ChildPath $DevSetupAgentConst + + Write-Host "Creating VM temporary folder $guestUnpackDirectory" + Write-Progress -Activity $activity -Status "Creating VM temporary folder $guestUnpackDirectory" -PercentComplete 10 + Invoke-Command -Session $session -ScriptBlock { New-Item -Path "$using:guestUnpackDirectory" -ItemType "directory" } + + if ($isDirectory) + { + $destinationPath = $guestDevSetupAgentTempDirectory + } + else + { + $destinationPath = $guestUnpackDirectory + } + + Write-Host "Copying $Path to VM $destinationPath" + Write-Progress -Activity $activity -Status "Copying DevSetupAgent to VM $destinationPath" -PercentComplete 15 + Copy-Item -ToSession $session -Recurse -Path $Path -Destination $destinationPath + + + Invoke-Command -Session $session -ScriptBlock { + $ErrorActionPreference = "Stop" + + try + { + $guestDevSetupAgentPath = Join-Path -Path $Env:Programfiles -ChildPath $using:DevSetupAgentConst + + # Stop and remove previous version of DevSetupAgent service if it exists + $service = Get-Service -Name $using:DevSetupAgentConst -ErrorAction SilentlyContinue + if ($service) + { + $serviceWMI = Get-WmiObject -Class Win32_Service -Filter "Name='$using:DevSetupAgentConst'" + $existingServicePath = $serviceWMI.Properties["PathName"].Value + if ($existingServicePath) + { + $guestDevSetupAgentPath = Split-Path $existingServicePath -Parent + } + + try + { + Write-Host "Stopping DevSetupAgent service" + Write-Progress -Activity $using:activity -Status "Stopping DevSetupAgent service $destinationPath" -PercentComplete 30 + $service.Stop() + } + catch + { + Write-Host "Ignoring error: $PSItem" + } + + Remove-Variable -Name service -ErrorAction SilentlyContinue + + # Remove-Service is only available in PowerShell 6.0 and later. Windows doesn't come with it preinstalled. + Write-Host "Removing DevSetupAgent service" + Write-Progress -Activity $using:activity -Status "Removing DevSetupAgent service" -PercentComplete 35 + $serviceWMI = Get-WmiObject -Class Win32_Service -Filter "Name='$using:DevSetupAgentConst'" + $serviceWMI.Delete() + Remove-Variable -Name serviceWMI -ErrorAction SilentlyContinue + } + + # Stop previous version of DevSetupEngine COM server if it exists + $devSetupEngineProcess = Get-Process -Name "$using:DevSetupEngineConst" -ErrorAction SilentlyContinue + if ($devSetupEngineProcess -ne $null) + { + Write-Host "Stopping $using:DevSetupEngineConst process" + Write-Progress -Activity $using:activity -Status "Stopping $using:DevSetupEngineConst process" -PercentComplete 40 + Stop-Process -Force -Name "$using:DevSetupEngineConst" + } + + # Unregister DevSetupEngine + $enginePath = Join-Path -Path $guestDevSetupAgentPath -ChildPath "$using:DevSetupEngineConst.exe" + if (Test-Path -Path $enginePath) + { + Write-Host "Unregistering DevSetupEngine ($enginePath)" + Write-Progress -Activity $using:activity -Status "Registering DevSetupEngine ($enginePath)" -PercentComplete 88 + &$enginePath "-UnregisterComServer" + } + + # Remove previous version of DevSetupAgent service files + if (Test-Path -Path $guestDevSetupAgentPath) + { + # Sleep a few seconds to make sure all handles released after shutting down previous DevSetupEngine + Start-Sleep -Seconds 7 + Write-Host "Deleting old DevSetupAgent service files" + Write-Progress -Activity $using:activity -Status "Deleting old DevSetupAgent service files" -PercentComplete 45 + Remove-Item -Recurse -Force -Path $guestDevSetupAgentPath + } + + if ($using:isDirectory) + { + Write-Host "Copying DevSetupAgent to $guestDevSetupAgentPath" + Write-Progress -Activity $using:activity -Status "Deleting old DevSetupAgent service files" -PercentComplete 50 + Copy-Item -Recurse -Path $using:guestDevSetupAgentTempDirectory -Destination $guestDevSetupAgentPath + } + elseif ($using:isCab) + { + $cabPath = Join-Path -Path $using:guestUnpackDirectory -ChildPath $using:inputFileName + Write-Host "Unpacking $cabPath to $guestDevSetupAgentPath" + Write-Progress -Activity $using:activity -Status "Unpacking $cabPath to $guestDevSetupAgentPath" -PercentComplete 60 + $expandOutput=&"$Env:SystemRoot\System32\expand.exe" $cabPath /F:* $Env:Programfiles + if ($LastExitCode -ne 0) + { + throw "Error unpacking $cabPath`:`n$LastExitCode`n$($expandOutput|Out-String)" + } + } + else + { + $zipPath = Join-Path -Path $using:guestUnpackDirectory -ChildPath $using:inputFileName + Write-Host "Unpacking $using:inputFileName to $guestDevSetupAgentPath" + Write-Progress -Activity $using:activity -Status "Unpacking $using:inputFileName to $guestDevSetupAgentPath" -PercentComplete 60 + Expand-Archive -Path $zipPath -Destination $guestDevSetupAgentPath + } + + # Register DevSetupAgent service + $servicePath = Join-Path -Path $guestDevSetupAgentPath -ChildPath "$using:DevSetupAgentConst.exe" + Write-Host "Registering DevSetupAgent service ($servicePath)" + Write-Progress -Activity $using:activity -Status "Registering DevSetupAgent service ($servicePath)" -PercentComplete 85 + New-Service -Name $using:DevSetupAgentConst -BinaryPathName $servicePath -StartupType Automatic + + # Register DevSetupEngine + Write-Host "Registering DevSetupEngine ($enginePath)" + Write-Progress -Activity $using:activity -Status "Registering DevSetupEngine ($enginePath)" -PercentComplete 88 + + # Executing non-console apps using '&' does not set $LastExitCode. Using Start-Process here to get the returned error code. + $process = Start-Process -NoNewWindow -Wait $enginePath -ArgumentList "-RegisterComServer" -PassThru + if ($process.ExitCode -ne 0) + { + throw "Error registering $enginePath`: $process.ExitCode" + } + + Write-Host "Starting DevSetupAgent service" + Write-Progress -Activity $using:activity -Status "Starting DevSetupAgent service" -PercentComplete 92 + Start-Service $using:DevSetupAgentConst + } + catch + { + Write-Host "Error on guest OS: $PSItem" + } + finally + { + Write-Host "Removing temporary directory $using:guestUnpackDirectory" + Remove-Item -Recurse -Force -Path $using:guestUnpackDirectory -ErrorAction SilentlyContinue + } + } + + Remove-PSSession $session +} From b747810dd3f9f5294cb57196e957a58d2aec7918 Mon Sep 17 00:00:00 2001 From: Eric Johnson Date: Wed, 10 Apr 2024 10:03:19 -0700 Subject: [PATCH 15/22] Correctly show/hide experimental features in stable builds (#2572) --- docs/ExperimentalFeatures.md | 2 +- src/NavConfig.jsonc | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/ExperimentalFeatures.md b/docs/ExperimentalFeatures.md index 3bf567276e..3b42f557f7 100644 --- a/docs/ExperimentalFeatures.md +++ b/docs/ExperimentalFeatures.md @@ -24,7 +24,7 @@ This is useful for features that are not ready for general use, but can be teste "visible": true }, { - "buildType": "release", + "buildType": "stable", "enabledByDefault": false, "visible": false } diff --git a/src/NavConfig.jsonc b/src/NavConfig.jsonc index fb191dc10a..c65878b80c 100644 --- a/src/NavConfig.jsonc +++ b/src/NavConfig.jsonc @@ -45,7 +45,7 @@ { "buildType": "dev", "enabledByDefault": true, - "visible": true + "visible": false }, { "buildType": "canary", @@ -53,7 +53,7 @@ "visible": true }, { - "buildType": "release", + "buildType": "stable", "enabledByDefault": false, "visible": false } @@ -74,7 +74,7 @@ "visible": false }, { - "buildType": "release", + "buildType": "stable", "enabledByDefault": false, "visible": false } @@ -95,7 +95,7 @@ "visible": false }, { - "buildType": "release", + "buildType": "stable", "enabledByDefault": false, "visible": false } @@ -116,7 +116,7 @@ "visible": true }, { - "buildType": "release", + "buildType": "stable", "enabledByDefault": false, "visible": false } From 05ddcd86ab70b552b0061699f61bbf6bc8ede244 Mon Sep 17 00:00:00 2001 From: Darren Hoehna Date: Wed, 10 Apr 2024 12:34:22 -0700 Subject: [PATCH 16/22] Using Content in SecondaryWindow (#2571) * Removing the first item from tab stop * Removing an un-used using * Adding comments * Using content instead of WindowContent * Reverting DevDriveView. --------- Co-authored-by: Darren Hoehna --- common/Windows/SecondaryWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/Windows/SecondaryWindow.cs b/common/Windows/SecondaryWindow.cs index 32794dc0dc..845dbddfec 100644 --- a/common/Windows/SecondaryWindow.cs +++ b/common/Windows/SecondaryWindow.cs @@ -184,7 +184,7 @@ public SecondaryWindow() // Initialize window content template _windowTemplate = new(this); - WindowContent = _windowTemplate; + Content = _windowTemplate; // Register secondary window events handlers Activated += OnSecondaryWindowActivated; From 79ed0a94276acf7d7fc5ab49679b4e613dd362a6 Mon Sep 17 00:00:00 2001 From: David Bennett Date: Wed, 10 Apr 2024 12:36:10 -0700 Subject: [PATCH 17/22] Fix exception logging (#2578) --- .../src/DevSetupAgent/DevAgentService.cs | 4 +-- .../src/DevSetupAgent/HostRegistryChannel.cs | 8 ++--- .../src/DevSetupAgent/RegistryWatcher.cs | 4 +-- .../src/DevSetupAgent/RequestManager.cs | 2 +- .../DevSetupAgent/Requests/RequestFactory.cs | 2 +- .../DevSetupEngine/ConfigurationFileHelper.cs | 9 +++-- .../PackageOperationException.cs | 2 +- HyperVExtension/src/DevSetupEngine/Program.cs | 2 +- .../Providers/MessageHelper.cs | 2 +- .../Responses/ResponseFactory.cs | 2 +- .../HyperVExtension/Helpers/PsObjectHelper.cs | 2 +- .../src/HyperVExtension/Helpers/Resources.cs | 2 +- .../src/HyperVExtension/HyperVExtension.cs | 2 +- .../Models/HyperVVirtualMachine.cs | 28 +++++++-------- .../VMGalleryVMCreationOperation.cs | 8 ++--- .../Models/VmCredentialAdaptiveCardSession.cs | 2 +- .../Models/WaitForLoginAdaptiveCardSession.cs | 2 +- .../Providers/HyperVProvider.cs | 4 +-- .../Services/PowerShellService.cs | 2 +- .../Services/VMGalleryService.cs | 2 +- .../Helpers/ComputeSystemHelpers.cs | 4 +-- .../Helpers/StringResourceHelper.cs | 2 +- common/Environments/Models/CardProperty.cs | 4 +-- common/Environments/Models/ComputeSystem.cs | 36 +++++++++---------- .../Models/ComputeSystemProvider.cs | 8 ++--- .../Models/CreateComputeSystemOperation.cs | 6 ++-- .../Services/ComputeSystemManager.cs | 6 ++-- common/Helpers/AdaptiveCardHelpers.cs | 2 +- common/Helpers/Deployment.cs | 2 +- common/Models/ExtensionAdaptiveCardSession.cs | 8 ++--- .../Services/AdaptiveCardRenderingService.cs | 2 +- common/Services/AppInstallManagerService.cs | 2 +- common/Services/ComputeSystemService.cs | 2 +- common/Services/NotificationService.cs | 12 +++---- .../CoreWidgetProvider/Helpers/GPUStats.cs | 2 +- .../Helpers/NetworkStats.cs | 2 +- .../CoreWidgetProvider/Helpers/Resources.cs | 2 +- .../CoreWidgetProvider/Widgets/CoreWidget.cs | 2 +- .../Widgets/SSHWalletWidget.cs | 4 +-- .../Widgets/SystemCPUUsageWidget.cs | 2 +- .../Widgets/SystemGPUUsageWidget.cs | 2 +- .../Widgets/SystemMemoryWidget.cs | 2 +- .../Widgets/SystemNetworkUsageWidget.cs | 2 +- .../Widgets/WidgetProvider.cs | 2 +- .../DevHome.Settings/Views/AboutPage.xaml.cs | 2 +- .../Views/AccountsPage.xaml.cs | 4 +-- src/Services/AccountsService.cs | 2 +- src/Services/DSCFileActivationHandler.cs | 2 +- src/ViewModels/InitializationViewModel.cs | 4 +-- .../OptimizeDevDriveDialogViewModel.cs | 4 +-- .../ViewModels/DevDriveInsightsViewModel.cs | 6 ++-- .../QuietBackgroundProcessesViewModel.cs | 8 ++--- .../Controls/WidgetControl.xaml.cs | 2 +- .../WidgetAdaptiveCardRenderingService.cs | 2 +- .../Services/WidgetHostingService.cs | 4 +-- .../ViewModels/WidgetViewModel.cs | 4 +-- .../Views/DashboardView.xaml.cs | 12 +++---- .../ViewModels/LandingPageViewModel.cs | 4 +-- .../ViewModels/ExtensionLibraryViewModel.cs | 2 +- .../DevDriveFormatter/DevDriveFormatter.cs | 2 +- .../Elevation/IPCSetup.cs | 4 +-- .../ElevatedComponentOperation.cs | 4 +-- .../Tasks/ElevatedConfigurationTask.cs | 2 +- .../Tasks/ElevatedInstallTask.cs | 2 +- .../DevHome.SetupFlow/Models/CloneRepoTask.cs | 4 +-- .../Models/CloningInformation.cs | 4 +-- .../Models/ConfigureTargetTask.cs | 8 ++--- .../DevHome.SetupFlow/Models/ConfigureTask.cs | 2 +- .../Models/CreateDevDriveTask.cs | 2 +- .../Models/GenericRepository.cs | 8 ++--- .../Models/InstallPackageTask.cs | 4 +-- .../Models/RepositoryProvider.cs | 18 +++++----- .../DevHome.SetupFlow/Models/WinGetPackage.cs | 2 +- .../SDKOpenConfigurationSetResult.cs | 2 +- .../Services/AppManagementInitializer.cs | 2 +- .../Services/CatalogDataSourceLoader.cs | 4 +-- .../Services/ComputeSystemViewModelFactory.cs | 2 +- .../Services/ConfigurationFileBuilder.cs | 2 +- .../Services/DevDriveManager.cs | 8 ++--- .../Services/WinGet/WinGetCatalogConnector.cs | 8 ++--- .../Services/WinGet/WinGetDeployment.cs | 12 +++---- .../Services/WinGet/WinGetRecovery.cs | 4 +-- .../WinGetFeaturedApplicationsDataSource.cs | 6 ++-- .../Services/WinGetPackageJsonDataSource.cs | 4 +-- .../WinGetPackageRestoreDataSource.cs | 4 +-- .../Utilities/DevDriveUtil.cs | 2 +- .../ViewModels/AddRepoViewModel.cs | 6 ++-- .../ViewModels/ConfigurationFileViewModel.cs | 10 +++--- .../ViewModels/DevDriveViewModel.cs | 4 +-- .../ComputeSystemsListViewModel.cs | 2 +- .../EnvironmentCreationOptionsViewModel.cs | 2 +- .../ViewModels/FolderPickerViewModel.cs | 2 +- .../ViewModels/PackageCatalogListViewModel.cs | 2 +- .../ViewModels/ReviewViewModel.cs | 4 +-- .../ViewModels/SearchViewModel.cs | 2 +- .../ViewModels/SetupTargetViewModel.cs | 4 +-- .../CreateEnvironmentReviewView.xaml.cs | 2 +- .../EnvironmentCreationOptionsView.xaml.cs | 2 +- 98 files changed, 217 insertions(+), 222 deletions(-) diff --git a/HyperVExtension/src/DevSetupAgent/DevAgentService.cs b/HyperVExtension/src/DevSetupAgent/DevAgentService.cs index f0b5c958c8..9b5b89a18b 100644 --- a/HyperVExtension/src/DevSetupAgent/DevAgentService.cs +++ b/HyperVExtension/src/DevSetupAgent/DevAgentService.cs @@ -41,13 +41,13 @@ protected async override Task ExecuteAsync(CancellationToken stoppingToken) } catch (Exception ex) { - _log.Error($"Exception in DevAgentService.", ex); + _log.Error(ex, $"Exception in DevAgentService."); } } } catch (Exception ex) { - _log.Error($"Failed to run DevSetupAgent.", ex); + _log.Error(ex, $"Failed to run DevSetupAgent."); throw; } finally diff --git a/HyperVExtension/src/DevSetupAgent/HostRegistryChannel.cs b/HyperVExtension/src/DevSetupAgent/HostRegistryChannel.cs index 518dc93ed2..11e8f8a076 100644 --- a/HyperVExtension/src/DevSetupAgent/HostRegistryChannel.cs +++ b/HyperVExtension/src/DevSetupAgent/HostRegistryChannel.cs @@ -113,7 +113,7 @@ await Task.Run( } catch (Exception ex) { - _log.Error($"Could not write host message. Response ID: {responseMessage.ResponseId}", ex); + _log.Error(ex, $"Could not write host message. Response ID: {responseMessage.ResponseId}"); } }, stoppingToken); @@ -130,7 +130,7 @@ await Task.Run( } catch (Exception ex) { - _log.Error($"Could not delete host message. Response ID: {responseId}", ex); + _log.Error(ex, $"Could not delete host message. Response ID: {responseId}"); } }, stoppingToken); @@ -207,7 +207,7 @@ private RequestMessage TryReadMessage() } catch (Exception ex) { - _log.Error($"Could not read host message {valueName}", ex); + _log.Error(ex, $"Could not read host message {valueName}"); } MessageHelper.DeleteAllMessages(_registryHiveKey, _fromHostRegistryKeyPath, s[0]); @@ -218,7 +218,7 @@ private RequestMessage TryReadMessage() } catch (Exception ex) { - _log.Error("Could not read host message.", ex); + _log.Error(ex, "Could not read host message."); } return requestMessage; diff --git a/HyperVExtension/src/DevSetupAgent/RegistryWatcher.cs b/HyperVExtension/src/DevSetupAgent/RegistryWatcher.cs index 019407c889..023f46ca27 100644 --- a/HyperVExtension/src/DevSetupAgent/RegistryWatcher.cs +++ b/HyperVExtension/src/DevSetupAgent/RegistryWatcher.cs @@ -76,14 +76,14 @@ public void Start() } catch (Exception ex) { - _log.Error("RegistryChanged delegate failed.", ex); + _log.Error(ex, "RegistryChanged delegate failed."); } } } } catch (Exception ex) { - _log.Error("Registry Watcher thread failed.", ex); + _log.Error(ex, "Registry Watcher thread failed."); } }); _log.Information("Registry Watcher thread started."); diff --git a/HyperVExtension/src/DevSetupAgent/RequestManager.cs b/HyperVExtension/src/DevSetupAgent/RequestManager.cs index 9c799d77f6..07cde9cc46 100644 --- a/HyperVExtension/src/DevSetupAgent/RequestManager.cs +++ b/HyperVExtension/src/DevSetupAgent/RequestManager.cs @@ -102,7 +102,7 @@ private void ProcessRequestQueue(CancellationToken stoppingToken) } catch (Exception ex) { - _log.Error($"Failed to execute request.", ex); + _log.Error(ex, $"Failed to execute request."); } } } diff --git a/HyperVExtension/src/DevSetupAgent/Requests/RequestFactory.cs b/HyperVExtension/src/DevSetupAgent/Requests/RequestFactory.cs index 2323e714a4..bd15df8917 100644 --- a/HyperVExtension/src/DevSetupAgent/Requests/RequestFactory.cs +++ b/HyperVExtension/src/DevSetupAgent/Requests/RequestFactory.cs @@ -64,7 +64,7 @@ public IHostRequest CreateRequest(IRequestContext requestContext) { var messageId = requestContext.RequestMessage.RequestId ?? ""; var requestData = requestContext.RequestMessage.RequestData ?? ""; - _log.Error($"Error processing message. Message ID: {messageId}. Request data: {requestData}", ex); + _log.Error(ex, $"Error processing message. Message ID: {messageId}. Request data: {requestData}"); return new ErrorRequest(requestContext.RequestMessage); } } diff --git a/HyperVExtension/src/DevSetupEngine/ConfigurationFileHelper.cs b/HyperVExtension/src/DevSetupEngine/ConfigurationFileHelper.cs index c7d7862dd7..c8885f2d4c 100644 --- a/HyperVExtension/src/DevSetupEngine/ConfigurationFileHelper.cs +++ b/HyperVExtension/src/DevSetupEngine/ConfigurationFileHelper.cs @@ -279,22 +279,21 @@ private void LogConfigurationDiagnostics(WinGet.IDiagnosticInformation diagnosti { _log.Information($"WinGet: {diagnosticInformation.Message}"); - var sourceComponent = nameof(WinGet.ConfigurationProcessor); switch (diagnosticInformation.Level) { case WinGet.DiagnosticLevel.Warning: - _log.Warning(sourceComponent, diagnosticInformation.Message); + _log.Warning(diagnosticInformation.Message); return; case WinGet.DiagnosticLevel.Error: - _log.Error(sourceComponent, diagnosticInformation.Message); + _log.Error(diagnosticInformation.Message); return; case WinGet.DiagnosticLevel.Critical: - _log.Fatal(sourceComponent, diagnosticInformation.Message); + _log.Fatal(diagnosticInformation.Message); return; case WinGet.DiagnosticLevel.Verbose: case WinGet.DiagnosticLevel.Informational: default: - _log.Information(sourceComponent, diagnosticInformation.Message); + _log.Information(diagnosticInformation.Message); return; } } diff --git a/HyperVExtension/src/DevSetupEngine/PackageOperationException.cs b/HyperVExtension/src/DevSetupEngine/PackageOperationException.cs index 9b149bede0..eaca912ed9 100644 --- a/HyperVExtension/src/DevSetupEngine/PackageOperationException.cs +++ b/HyperVExtension/src/DevSetupEngine/PackageOperationException.cs @@ -19,6 +19,6 @@ public PackageOperationException(ErrorCode errorCode, string message) { HResult = (int)errorCode; var log = Log.ForContext("SourceContext", nameof(PackageOperationException)); - log.Error(message, this); + log.Error(this, message); } } diff --git a/HyperVExtension/src/DevSetupEngine/Program.cs b/HyperVExtension/src/DevSetupEngine/Program.cs index 6bff3af2d6..4c1c269ebf 100644 --- a/HyperVExtension/src/DevSetupEngine/Program.cs +++ b/HyperVExtension/src/DevSetupEngine/Program.cs @@ -58,7 +58,7 @@ public static int Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnlyAr } catch (Exception ex) { - Log.Error($"Exception: {ex}", ex); + Log.Error(ex, $"Exception: {ex}"); Log.CloseAndFlush(); return ex.HResult; } diff --git a/HyperVExtension/src/HyperVExtension.HostGuestCommunication/Providers/MessageHelper.cs b/HyperVExtension/src/HyperVExtension.HostGuestCommunication/Providers/MessageHelper.cs index 8ec2c1ee14..6d8401907c 100644 --- a/HyperVExtension/src/HyperVExtension.HostGuestCommunication/Providers/MessageHelper.cs +++ b/HyperVExtension/src/HyperVExtension.HostGuestCommunication/Providers/MessageHelper.cs @@ -110,7 +110,7 @@ public static Dictionary MergeMessageParts(Dictionary"; var responseData = message?.ResponseData ?? ""; - _log.Error($"Error processing message. Message ID: {messageId}. Request data: {responseData}", ex); + _log.Error(ex, $"Error processing message. Message ID: {messageId}. Request data: {responseData}"); return new ErrorResponse(message!); } } diff --git a/HyperVExtension/src/HyperVExtension/Helpers/PsObjectHelper.cs b/HyperVExtension/src/HyperVExtension/Helpers/PsObjectHelper.cs index 3c88fd8388..e3a1713603 100644 --- a/HyperVExtension/src/HyperVExtension/Helpers/PsObjectHelper.cs +++ b/HyperVExtension/src/HyperVExtension/Helpers/PsObjectHelper.cs @@ -71,7 +71,7 @@ public PsObjectHelper(in PSObject pSObject) catch (Exception ex) { var log = Log.ForContext("SourceContext", nameof(PsObjectHelper)); - log.Error($"Failed to get property value with name {propertyName} from object with type {type}.", ex); + log.Error(ex, $"Failed to get property value with name {propertyName} from object with type {type}."); } return default(T); diff --git a/HyperVExtension/src/HyperVExtension/Helpers/Resources.cs b/HyperVExtension/src/HyperVExtension/Helpers/Resources.cs index 570a98ecff..eeb03be0e0 100644 --- a/HyperVExtension/src/HyperVExtension/Helpers/Resources.cs +++ b/HyperVExtension/src/HyperVExtension/Helpers/Resources.cs @@ -23,7 +23,7 @@ public static string GetResource(string identifier, ILogger? log = null) } catch (Exception ex) { - log?.Error($"Failed loading resource: {identifier}", ex); + log?.Error(ex, $"Failed loading resource: {identifier}"); // If we fail, load the original identifier so it is obvious which resource is missing. return identifier; diff --git a/HyperVExtension/src/HyperVExtension/HyperVExtension.cs b/HyperVExtension/src/HyperVExtension/HyperVExtension.cs index 3bc6f6a097..e71c411242 100644 --- a/HyperVExtension/src/HyperVExtension/HyperVExtension.cs +++ b/HyperVExtension/src/HyperVExtension/HyperVExtension.cs @@ -57,7 +57,7 @@ public HyperVExtension(IHost host) } catch (Exception ex) { - log.Error($"Failed to get provider for provider type {providerType}", ex); + log.Error(ex, $"Failed to get provider for provider type {providerType}"); } return provider; diff --git a/HyperVExtension/src/HyperVExtension/Models/HyperVVirtualMachine.cs b/HyperVExtension/src/HyperVExtension/Models/HyperVVirtualMachine.cs index eccfa1458d..0e190d4bca 100644 --- a/HyperVExtension/src/HyperVExtension/Models/HyperVVirtualMachine.cs +++ b/HyperVExtension/src/HyperVExtension/Models/HyperVVirtualMachine.cs @@ -192,7 +192,7 @@ private ComputeSystemOperationResult Start(string options) catch (Exception ex) { StateChanged(this, ComputeSystemState.Unknown); - _log.Error(OperationErrorString(ComputeSystemOperations.Start), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.Start)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } } @@ -253,7 +253,7 @@ public IAsyncOperation TerminateAsync(string optio catch (Exception ex) { StateChanged(this, ComputeSystemState.Unknown); - _log.Error(OperationErrorString(ComputeSystemOperations.Terminate), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.Terminate)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -278,7 +278,7 @@ public IAsyncOperation DeleteAsync(string options) catch (Exception ex) { StateChanged(this, ComputeSystemState.Unknown); - _log.Error(OperationErrorString(ComputeSystemOperations.Delete), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.Delete)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -309,7 +309,7 @@ public IAsyncOperation SaveAsync(string options) catch (Exception ex) { StateChanged(this, ComputeSystemState.Unknown); - _log.Error(OperationErrorString(ComputeSystemOperations.Save), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.Save)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -340,7 +340,7 @@ public IAsyncOperation PauseAsync(string options) catch (Exception ex) { StateChanged(this, ComputeSystemState.Unknown); - _log.Error(OperationErrorString(ComputeSystemOperations.Pause), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.Pause)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -371,7 +371,7 @@ public IAsyncOperation ResumeAsync(string options) catch (Exception ex) { StateChanged(this, ComputeSystemState.Unknown); - _log.Error(OperationErrorString(ComputeSystemOperations.Resume), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.Resume)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -393,7 +393,7 @@ public IAsyncOperation CreateSnapshotAsync(string } catch (Exception ex) { - _log.Error(OperationErrorString(ComputeSystemOperations.CreateSnapshot), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.CreateSnapshot)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -416,7 +416,7 @@ public IAsyncOperation RevertSnapshotAsync(string } catch (Exception ex) { - _log.Error(OperationErrorString(ComputeSystemOperations.RevertSnapshot), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.RevertSnapshot)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -439,7 +439,7 @@ public IAsyncOperation DeleteSnapshotAsync(string } catch (Exception ex) { - _log.Error(OperationErrorString(ComputeSystemOperations.DeleteSnapshot), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.DeleteSnapshot)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -457,7 +457,7 @@ public IAsyncOperation ConnectAsync(string options } catch (Exception ex) { - _log.Error($"Failed to launch vmconnect on {DateTime.Now}: VM details: {this}", ex); + _log.Error(ex, $"Failed to launch vmconnect on {DateTime.Now}: VM details: {this}"); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -487,7 +487,7 @@ public IAsyncOperation RestartAsync(string options catch (Exception ex) { StateChanged(this, ComputeSystemState.Unknown); - _log.Error(OperationErrorString(ComputeSystemOperations.Restart), ex); + _log.Error(ex, OperationErrorString(ComputeSystemOperations.Restart)); return new ComputeSystemOperationResult(ex, OperationErrorUnknownString, ex.Message); } }).AsAsyncOperation(); @@ -536,7 +536,7 @@ public IAsyncOperation> GetComputeSystemPrope } catch (Exception ex) { - _log.Error($"Failed to GetComputeSystemPropertiesAsync on {DateTime.Now}: VM details: {this}", ex); + _log.Error(ex, $"Failed to GetComputeSystemPropertiesAsync on {DateTime.Now}: VM details: {this}"); return new List(); } }).AsAsyncOperation(); @@ -708,7 +708,7 @@ public SDK.ApplyConfigurationResult ApplyConfiguration(ApplyConfigurationOperati } catch (Exception ex) { - _log.Error($"Failed to apply configuration on {DateTime.Now}: VM details: {this}", ex); + _log.Error(ex, $"Failed to apply configuration on {DateTime.Now}: VM details: {this}"); return operation.CompleteOperation(new HostGuestCommunication.ApplyConfigurationResult(ex.HResult, ex.Message)); } } @@ -721,7 +721,7 @@ public SDK.ApplyConfigurationResult ApplyConfiguration(ApplyConfigurationOperati } catch (Exception ex) { - _log.Error($"Failed to apply configuration on {DateTime.Now}: VM details: {this}", ex); + _log.Error(ex, $"Failed to apply configuration on {DateTime.Now}: VM details: {this}"); return new ApplyConfigurationOperation(this, ex); } } diff --git a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryVMCreationOperation.cs b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryVMCreationOperation.cs index 6e3202ff51..8bf3fcb3bc 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryVMCreationOperation.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VirtualMachineCreation/VMGalleryVMCreationOperation.cs @@ -139,7 +139,7 @@ public void Report(IOperationReport value) } catch (Exception ex) { - _log.Error("Operation to create compute system failed", ex); + _log.Error(ex, "Operation to create compute system failed"); ComputeSystemResult = new CreateComputeSystemResult(ex, ex.Message, ex.Message); } @@ -161,7 +161,7 @@ private void UpdateProgress(IOperationReport report, string localizedKey, string } catch (Exception ex) { - _log.Error("Failed to update progress", ex); + _log.Error(ex, "Failed to update progress"); } } @@ -173,7 +173,7 @@ private void UpdateProgress(string localizedString, uint percentage = 0u) } catch (Exception ex) { - _log.Error("Failed to update progress", ex); + _log.Error(ex, "Failed to update progress"); } } @@ -221,7 +221,7 @@ private async Task DeleteFileIfExists(StorageFile file) } catch (Exception ex) { - _log.Error($"Failed to delete file {file.Path}", ex); + _log.Error(ex, $"Failed to delete file {file.Path}"); } } diff --git a/HyperVExtension/src/HyperVExtension/Models/VmCredentialAdaptiveCardSession.cs b/HyperVExtension/src/HyperVExtension/Models/VmCredentialAdaptiveCardSession.cs index 009dc78c95..b7be3a7ef1 100644 --- a/HyperVExtension/src/HyperVExtension/Models/VmCredentialAdaptiveCardSession.cs +++ b/HyperVExtension/src/HyperVExtension/Models/VmCredentialAdaptiveCardSession.cs @@ -163,7 +163,7 @@ public IAsyncOperation OnAction(string action, string i } catch (Exception ex) { - _log.Error($"Exception in OnAction: {ex}"); + _log.Error(ex, $"Exception in OnAction: {ex}"); operationResult = new ProviderOperationResult(ProviderOperationStatus.Failure, ex, "Something went wrong", ex.Message); } diff --git a/HyperVExtension/src/HyperVExtension/Models/WaitForLoginAdaptiveCardSession.cs b/HyperVExtension/src/HyperVExtension/Models/WaitForLoginAdaptiveCardSession.cs index 677aa14d11..c5baabb0f5 100644 --- a/HyperVExtension/src/HyperVExtension/Models/WaitForLoginAdaptiveCardSession.cs +++ b/HyperVExtension/src/HyperVExtension/Models/WaitForLoginAdaptiveCardSession.cs @@ -144,7 +144,7 @@ public IAsyncOperation OnAction(string action, string i } catch (Exception ex) { - _log.Error($"Exception in OnAction: {ex}"); + _log.Error(ex, $"Exception in OnAction: {ex}"); operationResult = new ProviderOperationResult(ProviderOperationStatus.Failure, ex, "Something went wrong", ex.Message); } diff --git a/HyperVExtension/src/HyperVExtension/Providers/HyperVProvider.cs b/HyperVExtension/src/HyperVExtension/Providers/HyperVProvider.cs index b9d5b778e1..dd00cd5376 100644 --- a/HyperVExtension/src/HyperVExtension/Providers/HyperVProvider.cs +++ b/HyperVExtension/src/HyperVExtension/Providers/HyperVProvider.cs @@ -75,7 +75,7 @@ public IAsyncOperation GetComputeSystemsAsync(IDeveloperId } catch (Exception ex) { - _log.Error($"Failed to retrieved all virtual machines on: {DateTime.Now}", ex); + _log.Error(ex, $"Failed to retrieved all virtual machines on: {DateTime.Now}"); return new ComputeSystemsResult(ex, OperationErrorString, ex.Message); } }).AsAsyncOperation(); @@ -105,7 +105,7 @@ public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForComputeSystem } catch (Exception ex) { - _log.Error($"Failed to create a new virtual machine on: {DateTime.Now}", ex); + _log.Error(ex, $"Failed to create a new virtual machine on: {DateTime.Now}"); // Dev Home will handle null values as failed operations. We can't throw because this is an out of proc // COM call, so we'll lose the error information. We'll log the error and return null. diff --git a/HyperVExtension/src/HyperVExtension/Services/PowerShellService.cs b/HyperVExtension/src/HyperVExtension/Services/PowerShellService.cs index dbfb896090..b665df70a7 100644 --- a/HyperVExtension/src/HyperVExtension/Services/PowerShellService.cs +++ b/HyperVExtension/src/HyperVExtension/Services/PowerShellService.cs @@ -60,7 +60,7 @@ public PowerShellResult Execute(IEnumerable comm catch (Exception ex) { var commandStrings = string.Join(Environment.NewLine, commandLineStatements.Select(cmd => cmd.ToString())); - _log.Error($"Error running PowerShell commands: {commandStrings}", ex); + _log.Error(ex, $"Error running PowerShell commands: {commandStrings}"); throw; } } diff --git a/HyperVExtension/src/HyperVExtension/Services/VMGalleryService.cs b/HyperVExtension/src/HyperVExtension/Services/VMGalleryService.cs index 6f1ba9c3f1..e09837ce43 100644 --- a/HyperVExtension/src/HyperVExtension/Services/VMGalleryService.cs +++ b/HyperVExtension/src/HyperVExtension/Services/VMGalleryService.cs @@ -87,7 +87,7 @@ public async Task GetGalleryImagesAsync() } catch (Exception ex) { - _log.Error($"Unable to retrieve VM gallery images", ex); + _log.Error(ex, $"Unable to retrieve VM gallery images"); } return _imageList; diff --git a/common/Environments/Helpers/ComputeSystemHelpers.cs b/common/Environments/Helpers/ComputeSystemHelpers.cs index f97ff096b7..779519de52 100644 --- a/common/Environments/Helpers/ComputeSystemHelpers.cs +++ b/common/Environments/Helpers/ComputeSystemHelpers.cs @@ -35,7 +35,7 @@ public static class ComputeSystemHelpers } catch (Exception ex) { - Log.Error($"Failed to get thumbnail for compute system {computeSystemWrapper}.", ex); + Log.Error(ex, $"Failed to get thumbnail for compute system {computeSystemWrapper}."); return null; } } @@ -56,7 +56,7 @@ public static async Task> GetComputeSystemPropertiesAsync(Com } catch (Exception ex) { - Log.Error($"Failed to get all properties for compute system {computeSystemWrapper}.", ex); + Log.Error(ex, $"Failed to get all properties for compute system {computeSystemWrapper}."); return propertyList; } } diff --git a/common/Environments/Helpers/StringResourceHelper.cs b/common/Environments/Helpers/StringResourceHelper.cs index 4281531028..3e1982327c 100644 --- a/common/Environments/Helpers/StringResourceHelper.cs +++ b/common/Environments/Helpers/StringResourceHelper.cs @@ -26,7 +26,7 @@ public static string GetResource(string key, params object[] args) } catch (Exception ex) { - Log.Error($"Failed to get resource for key {key}.", ex); + Log.Error(ex, $"Failed to get resource for key {key}."); return key; } } diff --git a/common/Environments/Models/CardProperty.cs b/common/Environments/Models/CardProperty.cs index 5b15d56153..2bf9f0c89b 100644 --- a/common/Environments/Models/CardProperty.cs +++ b/common/Environments/Models/CardProperty.cs @@ -142,7 +142,7 @@ public static unsafe BitmapImage ConvertMsResourceToIcon(Uri iconPathUri, string } catch (Exception ex) { - Log.Error($"Failed to load icon from ms-resource: {iconPathUri} for package: {packageFullName} due to error:", ex); + Log.Error(ex, $"Failed to load icon from ms-resource: {iconPathUri} for package: {packageFullName} due to error:"); } return new BitmapImage(); @@ -200,7 +200,7 @@ public string ConvertBytesToString(object? size) } catch (Exception ex) { - Log.Error($"Failed to convert size in bytes to ulong. Error: {ex}"); + Log.Error(ex, $"Failed to convert size in bytes to ulong. Error: {ex}"); return string.Empty; } } diff --git a/common/Environments/Models/ComputeSystem.cs b/common/Environments/Models/ComputeSystem.cs index 4cdf9d64e0..ae21a80073 100644 --- a/common/Environments/Models/ComputeSystem.cs +++ b/common/Environments/Models/ComputeSystem.cs @@ -63,7 +63,7 @@ public void OnComputeSystemStateChanged(object? sender, ComputeSystemState state } catch (Exception ex) { - _log.Error($"OnComputeSystemStateChanged for: {this} failed due to exception", ex); + _log.Error(ex, $"OnComputeSystemStateChanged for: {this} failed due to exception"); } } @@ -75,7 +75,7 @@ public async Task GetStateAsync() } catch (Exception ex) { - _log.Error($"GetStateAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"GetStateAsync for: {this} failed due to exception"); return new ComputeSystemStateResult(ex, errorString, ex.Message); } } @@ -88,7 +88,7 @@ public async Task StartAsync(string options) } catch (Exception ex) { - _log.Error($"StartAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"StartAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -101,7 +101,7 @@ public async Task ShutDownAsync(string options) } catch (Exception ex) { - _log.Error($"ShutDownAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"ShutDownAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -114,7 +114,7 @@ public async Task RestartAsync(string options) } catch (Exception ex) { - _log.Error($"RestartAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"RestartAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -127,7 +127,7 @@ public async Task TerminateAsync(string options) } catch (Exception ex) { - _log.Error($"TerminateAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"TerminateAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -140,7 +140,7 @@ public async Task DeleteAsync(string options) } catch (Exception ex) { - _log.Error($"DeleteAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"DeleteAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -153,7 +153,7 @@ public async Task SaveAsync(string options) } catch (Exception ex) { - _log.Error($"SaveAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"SaveAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -166,7 +166,7 @@ public async Task PauseAsync(string options) } catch (Exception ex) { - _log.Error($"PauseAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"PauseAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -179,7 +179,7 @@ public async Task ResumeAsync(string options) } catch (Exception ex) { - _log.Error($"ResumeAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"ResumeAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -192,7 +192,7 @@ public async Task CreateSnapshotAsync(string optio } catch (Exception ex) { - _log.Error($"CreateSnapshotAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"CreateSnapshotAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -205,7 +205,7 @@ public async Task RevertSnapshotAsync(string optio } catch (Exception ex) { - _log.Error($"RevertSnapshotAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"RevertSnapshotAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -218,7 +218,7 @@ public async Task DeleteSnapshotAsync(string optio } catch (Exception ex) { - _log.Error($"DeleteSnapshotAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"DeleteSnapshotAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -231,7 +231,7 @@ public async Task ModifyPropertiesAsync(string opt } catch (Exception ex) { - _log.Error($"ModifyPropertiesAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"ModifyPropertiesAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -244,7 +244,7 @@ public async Task GetComputeSystemThumbnailAsync(s } catch (Exception ex) { - _log.Error($"GetComputeSystemThumbnailAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"GetComputeSystemThumbnailAsync for: {this} failed due to exception"); return new ComputeSystemThumbnailResult(ex, errorString, ex.Message); } } @@ -257,7 +257,7 @@ public async Task> GetComputeSystemProperties } catch (Exception ex) { - _log.Error($"GetComputeSystemPropertiesAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"GetComputeSystemPropertiesAsync for: {this} failed due to exception"); return new List(); } } @@ -270,7 +270,7 @@ public async Task ConnectAsync(string options) } catch (Exception ex) { - _log.Error($"ConnectAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"ConnectAsync for: {this} failed due to exception"); return new ComputeSystemOperationResult(ex, errorString, ex.Message); } } @@ -283,7 +283,7 @@ public IApplyConfigurationOperation ApplyConfiguration(string configuration) } catch (Exception ex) { - _log.Error($"ApplyConfiguration for: {this} failed due to exception", ex); + _log.Error(ex, $"ApplyConfiguration for: {this} failed due to exception"); throw; } } diff --git a/common/Environments/Models/ComputeSystemProvider.cs b/common/Environments/Models/ComputeSystemProvider.cs index 6d239be5d8..ad64203245 100644 --- a/common/Environments/Models/ComputeSystemProvider.cs +++ b/common/Environments/Models/ComputeSystemProvider.cs @@ -52,7 +52,7 @@ public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForDeveloperId(I } catch (Exception ex) { - _log.Error($"CreateAdaptiveCardSessionForDeveloperId for: {this} failed due to exception", ex); + _log.Error(ex, $"CreateAdaptiveCardSessionForDeveloperId for: {this} failed due to exception"); return new ComputeSystemAdaptiveCardResult(ex, errorString, ex.Message); } } @@ -65,7 +65,7 @@ public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForComputeSystem } catch (Exception ex) { - _log.Error($"CreateAdaptiveCardSessionForComputeSystem for: {this} failed due to exception", ex); + _log.Error(ex, $"CreateAdaptiveCardSessionForComputeSystem for: {this} failed due to exception"); return new ComputeSystemAdaptiveCardResult(ex, errorString, ex.Message); } } @@ -78,7 +78,7 @@ public async Task GetComputeSystemsAsync(IDeveloperId deve } catch (Exception ex) { - _log.Error($"GetComputeSystemsAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"GetComputeSystemsAsync for: {this} failed due to exception"); return new ComputeSystemsResult(ex, errorString, ex.Message); } } @@ -92,7 +92,7 @@ public async Task GetComputeSystemsAsync(IDeveloperId deve } catch (Exception ex) { - _log.Error($"GetComputeSystemsAsync for: {this} failed due to exception", ex); + _log.Error(ex, $"GetComputeSystemsAsync for: {this} failed due to exception"); return new FailedCreateComputeSystemOperation(ex, StringResourceHelper.GetResource("CreationOperationStoppedUnexpectedly")); } } diff --git a/common/Environments/Models/CreateComputeSystemOperation.cs b/common/Environments/Models/CreateComputeSystemOperation.cs index 3f93b4a72f..ecf69adc04 100644 --- a/common/Environments/Models/CreateComputeSystemOperation.cs +++ b/common/Environments/Models/CreateComputeSystemOperation.cs @@ -112,7 +112,7 @@ public void StartOperation() } catch (Exception ex) { - _log.Error($"StartOperation failed for provider {ProviderDetails.ComputeSystemProvider}", ex); + _log.Error(ex, $"StartOperation failed for provider {ProviderDetails.ComputeSystemProvider}"); CreateComputeSystemResult = new CreateComputeSystemResult(ex, StringResourceHelper.GetResource("CreationOperationStoppedUnexpectedly"), ex.Message); Completed?.Invoke(this, CreateComputeSystemResult); } @@ -145,7 +145,7 @@ public void RemoveEventHandlers() } catch (Exception ex) { - _log.Error($"Failed to remove event handlers for {this}", ex); + _log.Error(ex, $"Failed to remove event handlers for {this}"); } } @@ -157,7 +157,7 @@ public void CancelOperation() } catch (Exception ex) { - _log.Error($"Failed to cancel operation for {this}", ex); + _log.Error(ex, $"Failed to cancel operation for {this}"); } } diff --git a/common/Environments/Services/ComputeSystemManager.cs b/common/Environments/Services/ComputeSystemManager.cs index 45861aad6a..616566deae 100644 --- a/common/Environments/Services/ComputeSystemManager.cs +++ b/common/Environments/Services/ComputeSystemManager.cs @@ -77,17 +77,17 @@ await Parallel.ForEachAsync(computeSystemsProviderDetails, async (providerDetail { if (innerEx is TaskCanceledException) { - _log.Error($"Failed to get retrieve all compute systems from all compute system providers due to cancellation", innerEx); + _log.Error(innerEx, $"Failed to get retrieve all compute systems from all compute system providers due to cancellation"); } else { - _log.Error($"Failed to get retrieve all compute systems from all compute system providers ", innerEx); + _log.Error(innerEx, $"Failed to get retrieve all compute systems from all compute system providers "); } } } catch (Exception ex) { - _log.Error($"Failed to get retrieve all compute systems from all compute system providers ", ex); + _log.Error(ex, $"Failed to get retrieve all compute systems from all compute system providers "); } } diff --git a/common/Helpers/AdaptiveCardHelpers.cs b/common/Helpers/AdaptiveCardHelpers.cs index a662b5e7f9..2332714e8d 100644 --- a/common/Helpers/AdaptiveCardHelpers.cs +++ b/common/Helpers/AdaptiveCardHelpers.cs @@ -32,7 +32,7 @@ public static ImageIcon ConvertBase64StringToImageIcon(string base64String) } catch (Exception ex) { - _log.Error($"Failed to load image icon", ex); + _log.Error(ex, $"Failed to load image icon"); return new ImageIcon(); } } diff --git a/common/Helpers/Deployment.cs b/common/Helpers/Deployment.cs index c62eef22f7..c25cb02e3f 100644 --- a/common/Helpers/Deployment.cs +++ b/common/Helpers/Deployment.cs @@ -37,7 +37,7 @@ public static Guid Identifier // We do not want this identifer's access to ever create a problem in the // application, so if we can't get it, return empty guid. An empty guid is also a // signal that the data is unknown for filtering purposes. - Log.Error($"Failed getting Deployment Identifier", ex); + Log.Error(ex, $"Failed getting Deployment Identifier"); return Guid.Empty; } } diff --git a/common/Models/ExtensionAdaptiveCardSession.cs b/common/Models/ExtensionAdaptiveCardSession.cs index 9494a1c99c..089308e6fb 100644 --- a/common/Models/ExtensionAdaptiveCardSession.cs +++ b/common/Models/ExtensionAdaptiveCardSession.cs @@ -21,8 +21,6 @@ public class ExtensionAdaptiveCardSession { private readonly ILogger _log = Log.ForContext("SourceContext", nameof(ExtensionAdaptiveCardSession)); - private readonly string _componentName = "ExtensionAdaptiveCardSession"; - public IExtensionAdaptiveCardSession Session { get; private set; } public event TypedEventHandler? Stopped; @@ -45,7 +43,7 @@ public ProviderOperationResult Initialize(IExtensionAdaptiveCard extensionUI) } catch (Exception ex) { - _log.Error(_componentName, $"Initialize failed due to exception", ex); + _log.Error(ex, $"Initialize failed due to exception"); return new ProviderOperationResult(ProviderOperationStatus.Failure, ex, ex.Message, ex.Message); } } @@ -63,7 +61,7 @@ public void Dispose() } catch (Exception ex) { - _log.Error(_componentName, $"Dispose failed due to exception", ex); + _log.Error(ex, $"Dispose failed due to exception"); } } @@ -75,7 +73,7 @@ public async Task OnAction(string action, string inputs } catch (Exception ex) { - _log.Error(_componentName, $"OnAction failed due to exception", ex); + _log.Error(ex, $"OnAction failed due to exception"); return new ProviderOperationResult(ProviderOperationStatus.Failure, ex, ex.Message, ex.Message); } } diff --git a/common/Services/AdaptiveCardRenderingService.cs b/common/Services/AdaptiveCardRenderingService.cs index 19923e01bd..3e712f476d 100644 --- a/common/Services/AdaptiveCardRenderingService.cs +++ b/common/Services/AdaptiveCardRenderingService.cs @@ -119,7 +119,7 @@ private async Task UpdateHostConfig() } catch (Exception ex) { - _log.Error("Error retrieving HostConfig", ex); + _log.Error(ex, "Error retrieving HostConfig"); } _windowEx.DispatcherQueue.TryEnqueue(() => diff --git a/common/Services/AppInstallManagerService.cs b/common/Services/AppInstallManagerService.cs index a16f63746b..4e8e017efa 100644 --- a/common/Services/AppInstallManagerService.cs +++ b/common/Services/AppInstallManagerService.cs @@ -97,7 +97,7 @@ public async Task TryInstallPackageAsync(string packageId) } catch (Exception ex) { - _log.Error("Package installation Failed", ex); + _log.Error(ex, "Package installation Failed"); } return false; diff --git a/common/Services/ComputeSystemService.cs b/common/Services/ComputeSystemService.cs index 42be48d4b5..395b68f1fa 100644 --- a/common/Services/ComputeSystemService.cs +++ b/common/Services/ComputeSystemService.cs @@ -83,7 +83,7 @@ public async Task> GetComputeSystemProvidersA } catch (Exception ex) { - Log.Error($"Failed to get {nameof(IComputeSystemProvider)} provider from '{extension.PackageFamilyName}/{extension.ExtensionDisplayName}'", ex); + Log.Error(ex, $"Failed to get {nameof(IComputeSystemProvider)} provider from '{extension.PackageFamilyName}/{extension.ExtensionDisplayName}'"); } } diff --git a/common/Services/NotificationService.cs b/common/Services/NotificationService.cs index 2da857e013..72ff9e577b 100644 --- a/common/Services/NotificationService.cs +++ b/common/Services/NotificationService.cs @@ -25,8 +25,6 @@ public class NotificationService private readonly IWindowsIdentityService _windowsIdentityService; - private readonly string _componentName = "NotificationService"; - private readonly string _hyperVText = "Hyper-V"; private readonly string _microsoftText = "Microsoft"; @@ -81,7 +79,7 @@ public void HandlerNotificationActions(AppActivationArguments args) } catch (Exception ex) { - _log.Error(_componentName, $"Unable to launch computer management due to exception", ex); + _log.Error(ex, $"Unable to launch computer management due to exception"); } } } @@ -124,7 +122,7 @@ public void ShowRestartNotification() } else { - _log.Error(_componentName, "Notification queue is not initialized"); + _log.Error("Notification queue is not initialized"); } } @@ -149,7 +147,7 @@ public void CheckIfUserIsAHyperVAdminAndShowNotification() var user = _windowsIdentityService.GetCurrentUserName(); if (user == null) { - _log.Error(_componentName, "Unable to get the current user name"); + _log.Error("Unable to get the current user name"); return; } @@ -185,7 +183,7 @@ public void CheckIfUserIsAHyperVAdminAndShowNotification() } catch (Exception ex) { - _log.Error(_componentName, "Unable to add the user to the Hyper-V Administrators group", ex); + _log.Error(ex, "Unable to add the user to the Hyper-V Administrators group"); ShowUnableToAddToHyperVAdminGroupNotification(); } }); @@ -209,7 +207,7 @@ public void CheckIfUserIsAHyperVAdminAndShowNotification() } else { - _log.Error(_componentName, "Notification queue is not initialized"); + _log.Error("Notification queue is not initialized"); } } } diff --git a/extensions/CoreWidgetProvider/Helpers/GPUStats.cs b/extensions/CoreWidgetProvider/Helpers/GPUStats.cs index 972c7320a7..6cfaf019e7 100644 --- a/extensions/CoreWidgetProvider/Helpers/GPUStats.cs +++ b/extensions/CoreWidgetProvider/Helpers/GPUStats.cs @@ -113,7 +113,7 @@ public void GetData() } catch (InvalidOperationException ex) { - Log.Warning("GPUStats", "Failed to get next value", ex); + Log.Warning(ex, "GPUStats", "Failed to get next value"); Log.Information("GPUStats", "Calling GetGPUPerfCounters again"); GetGPUPerfCounters(); } diff --git a/extensions/CoreWidgetProvider/Helpers/NetworkStats.cs b/extensions/CoreWidgetProvider/Helpers/NetworkStats.cs index 2b0f5b2795..1b56389faa 100644 --- a/extensions/CoreWidgetProvider/Helpers/NetworkStats.cs +++ b/extensions/CoreWidgetProvider/Helpers/NetworkStats.cs @@ -87,7 +87,7 @@ public void GetData() } catch (Exception ex) { - Log.Error("Error getting network data.", ex); + Log.Error(ex, "Error getting network data."); } } } diff --git a/extensions/CoreWidgetProvider/Helpers/Resources.cs b/extensions/CoreWidgetProvider/Helpers/Resources.cs index 90e2144d1c..85e5659de3 100644 --- a/extensions/CoreWidgetProvider/Helpers/Resources.cs +++ b/extensions/CoreWidgetProvider/Helpers/Resources.cs @@ -23,7 +23,7 @@ public static string GetResource(string identifier, ILogger? log = null) } catch (Exception ex) { - log?.Error($"Failed loading resource: {identifier}", ex); + log?.Error(ex, $"Failed loading resource: {identifier}"); // If we fail, load the original identifier so it is obvious which resource is missing. return identifier; diff --git a/extensions/CoreWidgetProvider/Widgets/CoreWidget.cs b/extensions/CoreWidgetProvider/Widgets/CoreWidget.cs index c223040362..9700800557 100644 --- a/extensions/CoreWidgetProvider/Widgets/CoreWidget.cs +++ b/extensions/CoreWidgetProvider/Widgets/CoreWidget.cs @@ -140,7 +140,7 @@ protected string GetTemplateForPage(WidgetPageState page) } catch (Exception e) { - Log.Error("Error getting template.", e); + Log.Error(e, "Error getting template."); return string.Empty; } } diff --git a/extensions/CoreWidgetProvider/Widgets/SSHWalletWidget.cs b/extensions/CoreWidgetProvider/Widgets/SSHWalletWidget.cs index f8623fd426..09818b1e0c 100644 --- a/extensions/CoreWidgetProvider/Widgets/SSHWalletWidget.cs +++ b/extensions/CoreWidgetProvider/Widgets/SSHWalletWidget.cs @@ -83,7 +83,7 @@ public override void LoadContentData() } catch (Exception e) { - Log.Error("Error retrieving data.", e); + Log.Error(e, "Error retrieving data."); var content = new JsonObject { { "errorMessage", e.Message }, @@ -309,7 +309,7 @@ public override string GetConfiguration(string data) } catch (Exception ex) { - Log.Error($"Failed getting configuration information for input config file path: {data}", ex); + Log.Error(ex, $"Failed getting configuration information for input config file path: {data}"); configurationData = FillConfigurationData(false, data, 0, Resources.GetResource(@"SSH_Widget_Template/ErrorProcessingConfigFile", Log)); diff --git a/extensions/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs b/extensions/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs index bc6f137a21..6f5052fea8 100644 --- a/extensions/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs +++ b/extensions/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs @@ -56,7 +56,7 @@ public override void LoadContentData() } catch (Exception e) { - Log.Error("Error retrieving stats.", e); + Log.Error(e, "Error retrieving stats."); var content = new JsonObject { { "errorMessage", e.Message }, diff --git a/extensions/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs b/extensions/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs index c3f24fddff..2bd44d80b5 100644 --- a/extensions/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs +++ b/extensions/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs @@ -58,7 +58,7 @@ public override void LoadContentData() } catch (Exception e) { - Log.Error("Error retrieving data.", e); + Log.Error(e, "Error retrieving data."); var content = new JsonObject { { "errorMessage", e.Message }, diff --git a/extensions/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs b/extensions/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs index a24210b9e8..88112f6ce9 100644 --- a/extensions/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs +++ b/extensions/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs @@ -75,7 +75,7 @@ public override void LoadContentData() } catch (Exception e) { - Log.Error("Error retrieving data.", e); + Log.Error(e, "Error retrieving data."); var content = new JsonObject { { "errorMessage", e.Message }, diff --git a/extensions/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs b/extensions/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs index 2e56c47444..1161487f48 100644 --- a/extensions/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs +++ b/extensions/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs @@ -76,7 +76,7 @@ public override void LoadContentData() } catch (Exception e) { - Log.Error("Error retrieving data.", e); + Log.Error(e, "Error retrieving data."); var content = new JsonObject { { "errorMessage", e.Message }, diff --git a/extensions/CoreWidgetProvider/Widgets/WidgetProvider.cs b/extensions/CoreWidgetProvider/Widgets/WidgetProvider.cs index 623bbdd3fd..7f55948f34 100644 --- a/extensions/CoreWidgetProvider/Widgets/WidgetProvider.cs +++ b/extensions/CoreWidgetProvider/Widgets/WidgetProvider.cs @@ -64,7 +64,7 @@ private void RecoverRunningWidgets() } catch (Exception e) { - Log.Error("Failed retrieving list of running widgets.", e); + Log.Error(e, "Failed retrieving list of running widgets."); return; } diff --git a/settings/DevHome.Settings/Views/AboutPage.xaml.cs b/settings/DevHome.Settings/Views/AboutPage.xaml.cs index ec2046d7a3..743dc23bb1 100644 --- a/settings/DevHome.Settings/Views/AboutPage.xaml.cs +++ b/settings/DevHome.Settings/Views/AboutPage.xaml.cs @@ -44,7 +44,7 @@ private void OpenLogsLocation() catch (Exception e) { var log = Log.ForContext("SourceContext", "AboutPage"); - log.Error($"Error opening log location", e); + log.Error(e, $"Error opening log location"); } } #endif diff --git a/settings/DevHome.Settings/Views/AccountsPage.xaml.cs b/settings/DevHome.Settings/Views/AccountsPage.xaml.cs index f37a358c69..427aa761bb 100644 --- a/settings/DevHome.Settings/Views/AccountsPage.xaml.cs +++ b/settings/DevHome.Settings/Views/AccountsPage.xaml.cs @@ -114,7 +114,7 @@ public async Task ShowLoginUIAsync(string loginEntryPoint, Page parentPage, Acco } catch (Exception ex) { - _log.Error($"ShowLoginUIAsync(): loginUIContentDialog failed.", ex); + _log.Error(ex, $"ShowLoginUIAsync(): loginUIContentDialog failed."); } accountProvider.RefreshLoggedInAccounts(); @@ -186,7 +186,7 @@ private async Task InitiateAddAccountUserExperienceAsync(Page parentPage, Accoun } catch (Exception ex) { - _log.Error($"Exception thrown while calling {nameof(accountProvider.DeveloperIdProvider)}.{nameof(accountProvider.DeveloperIdProvider.ShowLogonSession)}: ", ex); + _log.Error(ex, $"Exception thrown while calling {nameof(accountProvider.DeveloperIdProvider)}.{nameof(accountProvider.DeveloperIdProvider.ShowLogonSession)}: "); } accountProvider.RefreshLoggedInAccounts(); diff --git a/src/Services/AccountsService.cs b/src/Services/AccountsService.cs index 0c628f7a5e..9574232fbb 100644 --- a/src/Services/AccountsService.cs +++ b/src/Services/AccountsService.cs @@ -59,7 +59,7 @@ public async Task> GetDevIdProviders() } catch (Exception ex) { - _log.Error($"Failed to get {nameof(IDeveloperIdProvider)} provider from '{extension.PackageFamilyName}/{extension.ExtensionDisplayName}'", ex); + _log.Error(ex, $"Failed to get {nameof(IDeveloperIdProvider)} provider from '{extension.PackageFamilyName}/{extension.ExtensionDisplayName}'"); } } diff --git a/src/Services/DSCFileActivationHandler.cs b/src/Services/DSCFileActivationHandler.cs index 2c366c7ce6..1731684323 100644 --- a/src/Services/DSCFileActivationHandler.cs +++ b/src/Services/DSCFileActivationHandler.cs @@ -98,7 +98,7 @@ await _mainWindow.ShowErrorMessageDialogAsync( } catch (Exception ex) { - _log.Error("Error executing the DSC activation flow", ex); + _log.Error(ex, "Error executing the DSC activation flow"); } } } diff --git a/src/ViewModels/InitializationViewModel.cs b/src/ViewModels/InitializationViewModel.cs index 025c6c616c..7c0b33a0cd 100644 --- a/src/ViewModels/InitializationViewModel.cs +++ b/src/ViewModels/InitializationViewModel.cs @@ -65,7 +65,7 @@ public async void OnPageLoaded() } catch (Exception ex) { - _log.Information("Installing WidgetService failed: ", ex); + _log.Information(ex, "Installing WidgetService failed: "); } // Install the DevHomeGitHubExtension, unless it's already installed or a dev build is running. @@ -82,7 +82,7 @@ public async void OnPageLoaded() } catch (Exception ex) { - _log.Information("Installing DevHomeGitHubExtension failed: ", ex); + _log.Information(ex, "Installing DevHomeGitHubExtension failed: "); } } diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs index 2d0cedc5c4..46c5c7c000 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs @@ -113,7 +113,7 @@ private void MoveDirectory(string sourceDirectory, string targetDirectory) } catch (Exception ex) { - Log.Error($"Error in MoveDirectory. Error: {ex}"); + Log.Error(ex, $"Error in MoveDirectory. Error: {ex}"); } } @@ -125,7 +125,7 @@ private void SetEnvironmentVariable(string variableName, string value) } catch (Exception ex) { - Log.Error($"Error in SetEnvironmentVariable. Error: {ex}"); + Log.Error(ex, $"Error in SetEnvironmentVariable. Error: {ex}"); } } diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs index 5e3fa54bd4..368eb7c1c1 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs @@ -175,7 +175,7 @@ public void LoadAllDevDrivesInTheUI() } catch (Exception ex) { - Log.Error($"Error loading Dev Drives data. Error: {ex}"); + Log.Error(ex, $"Error loading Dev Drives data. Error: {ex}"); } } @@ -195,7 +195,7 @@ public void LoadAllDevDriveOptimizersInTheUI() } catch (Exception ex) { - Log.Error($"Error loading Dev Drive Optimizers data. Error: {ex}"); + Log.Error(ex, $"Error loading Dev Drive Optimizers data. Error: {ex}"); } } @@ -215,7 +215,7 @@ public void LoadAllDevDriveOptimizedsInTheUI() } catch (Exception ex) { - Log.Error($"Error loading Dev Drive Optimized data. Error: {ex}"); + Log.Error(ex, $"Error loading Dev Drive Optimized data. Error: {ex}"); } } diff --git a/tools/Customization/DevHome.Customization/ViewModels/QuietBackgroundProcessesViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/QuietBackgroundProcessesViewModel.cs index 11a9ac4b8e..c7d360562a 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/QuietBackgroundProcessesViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/QuietBackgroundProcessesViewModel.cs @@ -110,7 +110,7 @@ public void QuietButtonClicked() catch (Exception ex) { SessionStateText = GetStatusString("SessionError"); - _log.Error("QuietBackgroundProcessesSession::Start failed", ex); + _log.Error(ex, "QuietBackgroundProcessesSession::Start failed"); } } else @@ -124,7 +124,7 @@ public void QuietButtonClicked() catch (Exception ex) { SessionStateText = GetStatusString("UnableToCancelSession"); - _log.Error("QuietBackgroundProcessesSession::Stop failed", ex); + _log.Error(ex, "QuietBackgroundProcessesSession::Stop failed"); } } } @@ -142,7 +142,7 @@ private bool GetIsActive() catch (Exception ex) { SessionStateText = GetStatusString("SessionError"); - _log.Error("QuietBackgroundProcessesSession::IsActive failed", ex); + _log.Error(ex, "QuietBackgroundProcessesSession::IsActive failed"); } return false; @@ -157,7 +157,7 @@ private int GetTimeRemaining() catch (Exception ex) { SessionStateText = GetStatusString("SessionError"); - _log.Error("QuietBackgroundProcessesSession::TimeLeftInSeconds failed", ex); + _log.Error(ex, "QuietBackgroundProcessesSession::TimeLeftInSeconds failed"); return 0; } } diff --git a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs index d1e012ffa8..b4a2471d87 100644 --- a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs @@ -115,7 +115,7 @@ private async void OnRemoveWidgetClick(object sender, RoutedEventArgs e) } catch (Exception ex) { - _log.Error($"Didn't delete Widget {widgetIdToDelete}", ex); + _log.Error(ex, $"Didn't delete Widget {widgetIdToDelete}"); } } } diff --git a/tools/Dashboard/DevHome.Dashboard/Services/WidgetAdaptiveCardRenderingService.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetAdaptiveCardRenderingService.cs index 9295b65842..30d5e77417 100644 --- a/tools/Dashboard/DevHome.Dashboard/Services/WidgetAdaptiveCardRenderingService.cs +++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetAdaptiveCardRenderingService.cs @@ -115,7 +115,7 @@ private async Task UpdateHostConfig() } catch (Exception ex) { - _log.Error("Error retrieving HostConfig", ex); + _log.Error(ex, "Error retrieving HostConfig"); } _windowEx.DispatcherQueue.TryEnqueue(() => diff --git a/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs index 175e47f5a7..2099ed7bef 100644 --- a/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs +++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetHostingService.cs @@ -119,7 +119,7 @@ public async Task GetWidgetHostAsync() } catch (Exception ex) { - _log.Error("Exception in WidgetHost.Register:", ex); + _log.Error(ex, "Exception in WidgetHost.Register:"); } } @@ -136,7 +136,7 @@ public async Task GetWidgetCatalogAsync() } catch (Exception ex) { - _log.Error("Exception in WidgetCatalog.GetDefault:", ex); + _log.Error(ex, "Exception in WidgetCatalog.GetDefault:"); } } diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs index 5d48d0e1f9..a5197d6f9b 100644 --- a/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs +++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/WidgetViewModel.cs @@ -155,7 +155,7 @@ await Task.Run(async () => } catch (Exception ex) { - _log.Warning("There was an error expanding the Widget template with data: ", ex); + _log.Warning(ex, "There was an error expanding the Widget template with data: "); ShowErrorCard("WidgetErrorCardDisplayText"); return; } @@ -192,7 +192,7 @@ await Task.Run(async () => } catch (Exception ex) { - _log.Error("Error rendering widget card: ", ex); + _log.Error(ex, "Error rendering widget card: "); WidgetFrameworkElement = GetErrorCard("WidgetErrorCardDisplayText"); } }); diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs index 44c3bcafe0..b42a0effad 100644 --- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs +++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs @@ -94,7 +94,7 @@ private async Task SubscribeToWidgetCatalogEventsAsync() } catch (Exception ex) { - _log.Error("Exception in SubscribeToWidgetCatalogEvents:", ex); + _log.Error(ex, "Exception in SubscribeToWidgetCatalogEvents:"); return false; } @@ -287,7 +287,7 @@ private async Task RestorePinnedWidgetsAsync(Widget[] hostWidgets) } catch (Exception ex) { - _log.Error($"RestorePinnedWidgets(): ", ex); + _log.Error(ex, $"RestorePinnedWidgets(): "); } } @@ -374,7 +374,7 @@ private async Task PinDefaultWidgetAsync(WidgetDefinition defaultWidgetDefinitio } catch (Exception ex) { - _log.Error($"PinDefaultWidget failed: ", ex); + _log.Error(ex, $"PinDefaultWidget failed: "); } } @@ -424,7 +424,7 @@ public async Task AddWidgetClickAsync() } catch (Exception ex) { - _log.Warning($"Creating widget failed: ", ex); + _log.Warning(ex, $"Creating widget failed: "); var mainWindow = Application.Current.GetService(); var stringResource = new StringResource("DevHome.Dashboard.pri", "DevHome.Dashboard/Resources"); await mainWindow.ShowErrorMessageDialogAsync( @@ -464,7 +464,7 @@ await Task.Run(async () => { // TODO Support concurrency in dashboard. Today concurrent async execution can cause insertion errors. // https://github.com/microsoft/devhome/issues/1215 - _log.Warning($"Couldn't insert pinned widget", ex); + _log.Warning(ex, $"Couldn't insert pinned widget"); } }); } @@ -479,7 +479,7 @@ await Task.Run(async () => } catch (Exception ex) { - _log.Information($"Error deleting widget", ex); + _log.Information(ex, $"Error deleting widget"); } } }); diff --git a/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs index 1dfbc4f9e7..3b0d268cfe 100644 --- a/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs +++ b/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs @@ -261,7 +261,7 @@ private async Task AddAllComputeSystemsFromAProvider(ComputeSystemsLoadedData da var result = mapping.Value.Result; await _notificationService.ShowNotificationAsync(provider.DisplayName, result.DisplayMessage, InfoBarSeverity.Error); - _log.Error($"Error occurred while adding Compute systems to environments page for provider: {provider.Id}", result.DiagnosticText, result.ExtendedError); + _log.Error($"Error occurred while adding Compute systems to environments page for provider: {provider.Id}. {result.DiagnosticText}, {result.ExtendedError}"); data.DevIdToComputeSystemMap.Remove(mapping.Key); } @@ -296,7 +296,7 @@ await _windowEx.DispatcherQueue.EnqueueAsync(async () => } catch (Exception ex) { - _log.Error($"Exception occurred while adding Compute systems to environments page for provider: {provider.Id}", ex); + _log.Error(ex, $"Exception occurred while adding Compute systems to environments page for provider: {provider.Id}"); } }); } diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs index 01f434838a..f9a50252c7 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs @@ -124,7 +124,7 @@ private async Task GetStoreData() } catch (Exception ex) { - _log.Error("Error retrieving packages", ex); + _log.Error(ex, "Error retrieving packages"); ShouldShowStoreError = true; } diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/DevDriveFormatter/DevDriveFormatter.cs b/tools/SetupFlow/DevHome.SetupFlow.Common/DevDriveFormatter/DevDriveFormatter.cs index e8f8027d7e..f5d356cfb6 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.Common/DevDriveFormatter/DevDriveFormatter.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.Common/DevDriveFormatter/DevDriveFormatter.cs @@ -88,7 +88,7 @@ public int FormatPartitionAsDevDrive(char curDriveLetter, string driveLabel) } catch (CimException e) { - _log.Error($"A CimException occurred while formatting Dev Drive Error.", e); + _log.Error(e, $"A CimException occurred while formatting Dev Drive Error."); return e.HResult; } } diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/Elevation/IPCSetup.cs b/tools/SetupFlow/DevHome.SetupFlow.Common/Elevation/IPCSetup.cs index 602d3f250b..d7acd95fd4 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.Common/Elevation/IPCSetup.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.Common/Elevation/IPCSetup.cs @@ -296,7 +296,7 @@ public static (RemoteObject, Process) CreateOutOfProcessObjectAndGetProcess( } catch (Exception e) { - Log.Error($"Error occurred during setup.", e); + Log.Error(e, $"Error occurred during setup."); mappedMemory.HResult = e.HResult; } diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs index ff4a8fcd59..9e848df2dd 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs @@ -43,7 +43,7 @@ public ElevatedComponentOperation(IList tasksArgumentList) } catch (Exception e) { - _log.Error($"Failed to parse tasks arguments", e); + _log.Error(e, $"Failed to parse tasks arguments"); throw; } } @@ -182,7 +182,7 @@ private async Task ValidateAndExecuteAsync( } catch (Exception e) { - _log.Error($"Failed to validate or execute operation", e); + _log.Error(e, $"Failed to validate or execute operation"); EndOperation(taskArguments, false); throw; } diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedConfigurationTask.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedConfigurationTask.cs index 4238de1c5e..bad73c3373 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedConfigurationTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedConfigurationTask.cs @@ -56,7 +56,7 @@ public IAsyncOperation ApplyConfiguration(string fi } catch (Exception e) { - log.Error($"Failed to apply configuration.", e); + log.Error(e, $"Failed to apply configuration."); taskResult.TaskSucceeded = false; } diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedInstallTask.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedInstallTask.cs index 588cdab355..a90e148f7e 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedInstallTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedInstallTask.cs @@ -100,7 +100,7 @@ public IAsyncOperation InstallPackage(string packageI } catch (Exception e) { - _log.Error("Elevated app install failed.", e); + _log.Error(e, "Elevated app install failed."); result.TaskSucceeded = false; } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/CloneRepoTask.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/CloneRepoTask.cs index 0a9e2f0853..440dd217f7 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/CloneRepoTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/CloneRepoTask.cs @@ -232,7 +232,7 @@ IAsyncOperation ISetupTask.Execute() if (result.Status == ProviderOperationStatus.Failure) { - _log.Error($"Could not clone {RepositoryToClone.DisplayName} because {result.DisplayMessage}", result.ExtendedError); + _log.Error(result.ExtendedError, $"Could not clone {RepositoryToClone.DisplayName} because {result.DisplayMessage}"); TelemetryFactory.Get().LogError("CloneTask_CouldNotClone_Event", LogLevel.Critical, new ExceptionEvent(result.ExtendedError.HResult, result.DisplayMessage)); _actionCenterErrorMessage.PrimaryMessage = _stringResource.GetLocalized(StringResourceKey.CloneRepoErrorForActionCenter, RepositoryToClone.DisplayName, result.DisplayMessage); @@ -242,7 +242,7 @@ IAsyncOperation ISetupTask.Execute() } catch (Exception e) { - _log.Error($"Could not clone {RepositoryToClone.DisplayName}", e); + _log.Error(e, $"Could not clone {RepositoryToClone.DisplayName}"); _actionCenterErrorMessage.PrimaryMessage = _stringResource.GetLocalized(StringResourceKey.CloneRepoErrorForActionCenter, RepositoryToClone.DisplayName, e.HResult.ToString("X", CultureInfo.CurrentCulture)); TelemetryFactory.Get().LogError("CloneTask_CouldNotClone_Event", LogLevel.Critical, new ExceptionEvent(e.HResult)); return TaskFinishedState.Failure; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/CloningInformation.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/CloningInformation.cs index eebbf5f94c..a88c3c2d62 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/CloningInformation.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/CloningInformation.cs @@ -124,7 +124,7 @@ public void SetIcon(ElementTheme theme) } catch (Exception e) { - _log.Error(e.Message, e); + _log.Error(e, e.Message); RepositoryTypeIcon = GetGitIcon(theme); return; } @@ -259,7 +259,7 @@ public string RepositoryProviderDisplayName } catch (Exception e) { - _log.Error(e.Message, e); + _log.Error(e, e.Message); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTargetTask.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTargetTask.cs index a7fe05d628..c97ca72986 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTargetTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTargetTask.cs @@ -214,7 +214,7 @@ public void OnApplyConfigurationOperationChanged(object sender, SDK.Configuratio } catch (Exception ex) { - _log.Error($"Failed to process configuration progress data on target machine.'{ComputeSystemName}'", ex); + _log.Error(ex, $"Failed to process configuration progress data on target machine.'{ComputeSystemName}'"); } } @@ -254,7 +254,7 @@ public void HandleCompletedOperation(SDK.ApplyConfigurationResult applyConfigura if (resultStatus == ProviderOperationStatus.Failure) { - _log.Error($"Extension failed to configure config file with exception. Diagnostic text: {result.DiagnosticText}", result.ExtendedError); + _log.Error(result.ExtendedError, $"Extension failed to configure config file with exception. Diagnostic text: {result.DiagnosticText}"); throw new SDKApplyConfigurationSetResultException(applyConfigurationResult.Result.DiagnosticText); } @@ -288,7 +288,7 @@ public void HandleCompletedOperation(SDK.ApplyConfigurationResult applyConfigura } catch (Exception ex) { - _log.Error($"Failed to apply configuration on target machine. '{ComputeSystemName}'", ex); + _log.Error(ex, $"Failed to apply configuration on target machine. '{ComputeSystemName}'"); } var tempResultInfo = !string.IsNullOrEmpty(resultInformation) ? resultInformation : string.Empty; @@ -371,7 +371,7 @@ public IAsyncOperation Execute() } catch (Exception e) { - _log.Error($"Failed to apply configuration on target machine.", e); + _log.Error(e, $"Failed to apply configuration on target machine."); return TaskFinishedState.Failure; } }).AsAsyncOperation(); diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTask.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTask.cs index 2235ebeeac..353e74ba90 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/ConfigureTask.cs @@ -107,7 +107,7 @@ IAsyncOperation ISetupTask.Execute() } catch (Exception e) { - _log.Error($"Failed to apply configuration.", e); + _log.Error(e, $"Failed to apply configuration."); return TaskFinishedState.Failure; } }).AsAsyncOperation(); diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/CreateDevDriveTask.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/CreateDevDriveTask.cs index 19e6869af9..bd43532b02 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/CreateDevDriveTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/CreateDevDriveTask.cs @@ -129,7 +129,7 @@ IAsyncOperation ISetupTask.ExecuteAsAdmin(IElevatedComponentO catch (Exception ex) { result = ex.HResult; - _log.Error($"Failed to create Dev Drive.", ex); + _log.Error(ex, $"Failed to create Dev Drive."); _actionCenterMessages.PrimaryMessage = _stringResource.GetLocalized(StringResourceKey.DevDriveErrorWithReason, _stringResource.GetLocalizedErrorMsg(ex.HResult, Identity.Component.DevDrive)); TelemetryFactory.Get().LogException("CreatingDevDriveException", ex); return TaskFinishedState.Failure; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/GenericRepository.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/GenericRepository.cs index d37f37fe95..cfb9c611f6 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/GenericRepository.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/GenericRepository.cs @@ -51,22 +51,22 @@ public IAsyncAction CloneRepositoryAsync(string cloneDestination, IDeveloperId d } catch (RecurseSubmodulesException recurseException) { - _log.Error("Could not clone all sub modules", recurseException); + _log.Error(recurseException, "Could not clone all sub modules"); throw; } catch (UserCancelledException userCancelledException) { - _log.Error("The user stoped the clone operation", userCancelledException); + _log.Error(userCancelledException, "The user stoped the clone operation"); throw; } catch (NameConflictException nameConflictException) { - _log.Error(string.Empty, nameConflictException); + _log.Error(nameConflictException, nameConflictException.ToString()); throw; } catch (Exception e) { - _log.Error("Could not clone the repository", e); + _log.Error(e, "Could not clone the repository"); throw; } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/InstallPackageTask.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/InstallPackageTask.cs index 96052be67f..718ee3802b 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/InstallPackageTask.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/InstallPackageTask.cs @@ -153,7 +153,7 @@ IAsyncOperation ISetupTask.Execute() catch (Exception e) { ReportAppInstallFailedEvent(); - _log.Error($"Exception thrown while installing package.", e); + _log.Error(e, $"Exception thrown while installing package."); return TaskFinishedState.Failure; } }).AsAsyncOperation(); @@ -189,7 +189,7 @@ IAsyncOperation ISetupTask.ExecuteAsAdmin(IElevatedComponentO catch (Exception e) { ReportAppInstallFailedEvent(); - _log.Error($"Exception thrown while installing package.", e); + _log.Error(e, $"Exception thrown while installing package."); return TaskFinishedState.Failure; } }).AsAsyncOperation(); diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs index dc47ac7579..71a414dae1 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/RepositoryProvider.cs @@ -77,7 +77,7 @@ public void StartIfNotRunning() } catch (Exception ex) { - _log.Error($"Could not get repository provider from extension.", ex); + _log.Error(ex, $"Could not get repository provider from extension."); } } @@ -199,7 +199,7 @@ public async Task GetLoginUiAsync() } catch (Exception ex) { - _log.Error($"ShowLoginUIAsync(): loginUIContentDialog failed.", ex); + _log.Error(ex, $"ShowLoginUIAsync(): loginUIContentDialog failed."); } return null; @@ -224,7 +224,7 @@ public IEnumerable GetAllLoggedInAccounts() var developerIdsResult = _devIdProvider.GetLoggedInDeveloperIds(); if (developerIdsResult.Result.Status != ProviderOperationStatus.Success) { - _log.Error($"Could not get logged in accounts. Message: {developerIdsResult.Result.DisplayMessage}", developerIdsResult.Result.ExtendedError); + _log.Error(developerIdsResult.Result.ExtendedError, $"Could not get logged in accounts. Message: {developerIdsResult.Result.DisplayMessage}"); return new List(); } @@ -251,7 +251,7 @@ public RepositorySearchInformation SearchForRepositories(IDeveloperId developerI } else { - _log.Error($"Could not get repositories. Message: {result.Result.DisplayMessage}", result.Result.ExtendedError); + _log.Error(result.Result.ExtendedError, $"Could not get repositories. Message: {result.Result.DisplayMessage}"); } } else @@ -264,7 +264,7 @@ public RepositorySearchInformation SearchForRepositories(IDeveloperId developerI } else { - _log.Error($"Could not get repositories. Message: {result.Result.DisplayMessage}", result.Result.ExtendedError); + _log.Error(result.Result.ExtendedError, $"Could not get repositories. Message: {result.Result.DisplayMessage}"); } } } @@ -282,7 +282,7 @@ public RepositorySearchInformation SearchForRepositories(IDeveloperId developerI } catch (Exception ex) { - _log.Error($"Could not get repositories. Message: {ex}"); + _log.Error(ex, $"Could not get repositories. Message: {ex}"); } _repositories[developerId] = repoSearchInformation.Repositories; @@ -304,7 +304,7 @@ public RepositorySearchInformation GetAllRepositories(IDeveloperId developerId) } else { - _log.Error($"Could not get repositories. Message: {result.Result.DisplayMessage}", result.Result.ExtendedError); + _log.Error(result.Result.ExtendedError, $"Could not get repositories. Message: {result.Result.DisplayMessage}"); } } catch (AggregateException aggregateException) @@ -316,12 +316,12 @@ public RepositorySearchInformation GetAllRepositories(IDeveloperId developerId) } else { - _log.Error(aggregateException.Message, aggregateException); + _log.Error(aggregateException, aggregateException.Message); } } catch (Exception ex) { - _log.Error($"Could not get repositories. Message: {ex}", ex); + _log.Error(ex, $"Could not get repositories. Message: {ex}"); } _repositories[developerId] = repoSearchInformation.Repositories; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackage.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackage.cs index 9da80343a6..f74b337de5 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackage.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackage.cs @@ -133,7 +133,7 @@ private string FindVersion(IReadOnlyList availableVersions, Pa } catch (Exception e) { - _log.Error($"Unable to validate if the version {versionInfo.Version} is in the list of available versions", e); + _log.Error(e, $"Unable to validate if the version {versionInfo.Version} is in the list of available versions"); } return versionInfo.Version; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/WingetConfigure/SDKOpenConfigurationSetResult.cs b/tools/SetupFlow/DevHome.SetupFlow/Models/WingetConfigure/SDKOpenConfigurationSetResult.cs index 3b2de8ab81..cabbf87556 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Models/WingetConfigure/SDKOpenConfigurationSetResult.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Models/WingetConfigure/SDKOpenConfigurationSetResult.cs @@ -49,7 +49,7 @@ public SDKOpenConfigurationSetResult(SDK.OpenConfigurationSetResult result, ISet public string GetErrorMessage() { var log = Log.ForContext("SourceContext", nameof(SDKOpenConfigurationSetResult)); - log.Error($"Extension failed to open the configuration file provided by Dev Home: Field: {Field}, Value: {Value}, Line: {Line}, Column: {Column}", ResultCode); + log.Error(ResultCode, $"Extension failed to open the configuration file provided by Dev Home: Field: {Field}, Value: {Value}, Line: {Line}, Column: {Column}"); return _setupFlowStringResource.GetLocalized(StringResourceKey.SetupTargetConfigurationOpenConfigFailed); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/AppManagementInitializer.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/AppManagementInitializer.cs index ca3c3f70b3..690bfb7f3c 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/AppManagementInitializer.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/AppManagementInitializer.cs @@ -74,7 +74,7 @@ private async Task InitializeWindowsPackageManagerAsync() } catch (Exception e) { - _log.Error($"Unable to correctly initialize app management at the moment. Further attempts will be performed later.", e); + _log.Error(e, $"Unable to correctly initialize app management at the moment. Further attempts will be performed later."); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/CatalogDataSourceLoader.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/CatalogDataSourceLoader.cs index 6ab0f24ab8..26664c6fc2 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/CatalogDataSourceLoader.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/CatalogDataSourceLoader.cs @@ -72,7 +72,7 @@ private async Task InitializeDataSourceAsync(WinGetPackageDataSource dataSource) } catch (Exception e) { - _log.Error($"Exception thrown while initializing data source of type {dataSource.GetType().Name}", e); + _log.Error(e, $"Exception thrown while initializing data source of type {dataSource.GetType().Name}"); } } @@ -89,7 +89,7 @@ private async Task> LoadCatalogsFromDataSourceAsync(WinGet } catch (Exception e) { - _log.Error($"Exception thrown while loading data source of type {dataSource.GetType().Name}", e); + _log.Error(e, $"Exception thrown while loading data source of type {dataSource.GetType().Name}"); } return null; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/ComputeSystemViewModelFactory.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/ComputeSystemViewModelFactory.cs index 9efdd3a9e7..ab4936a111 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/ComputeSystemViewModelFactory.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/ComputeSystemViewModelFactory.cs @@ -37,7 +37,7 @@ public async Task CreateCardViewModelAsync( catch (Exception ex) { var log = Log.ForContext("SourceContext", nameof(ComputeSystemViewModelFactory)); - log.Error($"Failed to get initial properties for compute system {computeSystem}. Error: {ex.Message}"); + log.Error(ex, $"Failed to get initial properties for compute system {computeSystem}. Error: {ex.Message}"); } return cardViewModel; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/ConfigurationFileBuilder.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/ConfigurationFileBuilder.cs index 21c498c6d4..4059509583 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/ConfigurationFileBuilder.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/ConfigurationFileBuilder.cs @@ -138,7 +138,7 @@ private List GetResourcesForCloneTaskGroup(RepoConfigTaskG } catch (Exception e) { - _log.Error($"Error creating a repository resource entry", e); + _log.Error(e, $"Error creating a repository resource entry"); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/DevDriveManager.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/DevDriveManager.cs index 1663c249bc..d4b0dbeed3 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/DevDriveManager.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/DevDriveManager.cs @@ -251,7 +251,7 @@ volumeLetter is char newLetter && volumeSize is ulong newSize && catch (Exception ex) { // Log then return empty list, don't show the user their existing dev drive. Not catastrophic failure. - _log.Error($"Failed to get existing Dev Drives.", ex); + _log.Error(ex, $"Failed to get existing Dev Drives."); return new List(); } } @@ -280,7 +280,7 @@ private DevDrive GetDevDriveWithDefaultInfo() } catch (Exception ex) { - _log.Error($"Unable to get available Free Space for {root}.", ex); + _log.Error(ex, $"Unable to get available Free Space for {root}."); validationSuccessful = false; } @@ -372,7 +372,7 @@ public ISet GetDevDriveValidationResults(IDevDrive dev } catch (Exception ex) { - _log.Error($"Failed to validate selected Drive letter ({devDrive.DriveLocation.FirstOrDefault()}).", ex); + _log.Error(ex, $"Failed to validate selected Drive letter ({devDrive.DriveLocation.FirstOrDefault()})."); returnSet.Add(DevDriveValidationResult.DriveLetterNotAvailable); } @@ -405,7 +405,7 @@ public IList GetAvailableDriveLetters(char? usedLetterToKeepInList = null) } catch (Exception ex) { - _log.Error($"Failed to get Available Drive letters.", ex); + _log.Error(ex, $"Failed to get Available Drive letters."); } return driveLetterSet.ToList(); diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetCatalogConnector.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetCatalogConnector.cs index f195240367..d70f08539d 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetCatalogConnector.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetCatalogConnector.cs @@ -223,7 +223,7 @@ private async Task CreateAndConnectSearchCatalogAsync() } catch (Exception e) { - _log.Error($"Failed to create or connect to search catalog.", e); + _log.Error(e, $"Failed to create or connect to search catalog."); } } @@ -241,7 +241,7 @@ private async Task CreateAndConnectWinGetCatalogAsync() } catch (Exception e) { - _log.Error($"Failed to create or connect to 'winget' catalog source.", e); + _log.Error(e, $"Failed to create or connect to 'winget' catalog source."); } } @@ -259,7 +259,7 @@ private async Task CreateAndConnectMsStoreCatalogAsync() } catch (Exception e) { - _log.Error($"Failed to create or connect to 'msstore' catalog source.", e); + _log.Error(e, $"Failed to create or connect to 'msstore' catalog source."); } } @@ -278,7 +278,7 @@ private async Task CreateAndConnectCustomCatalogAsync(string cata } catch (Exception e) { - _log.Error($"Failed to create or connect to custom catalog with name {catalogName}", e); + _log.Error(e, $"Failed to create or connect to custom catalog with name {catalogName}"); return null; } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetDeployment.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetDeployment.cs index ca212e18b2..8511c9fa81 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetDeployment.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetDeployment.cs @@ -53,7 +53,7 @@ await Task.Run(() => } catch (Exception e) { - _log.Error($"Failed to create dummy {nameof(PackageManager)} COM object. WinGet COM Server is not available.", e); + _log.Error(e, $"Failed to create dummy {nameof(PackageManager)} COM object. WinGet COM Server is not available."); return false; } } @@ -70,7 +70,7 @@ public async Task IsUpdateAvailableAsync() } catch (Exception e) { - _log.Error("Failed to check if AppInstaller has an update, defaulting to false", e); + _log.Error(e, "Failed to check if AppInstaller has an update, defaulting to false"); return false; } } @@ -87,12 +87,12 @@ public async Task RegisterAppInstallerAsync() } catch (RegisterPackageException e) { - _log.Error($"Failed to register AppInstaller", e); + _log.Error(e, $"Failed to register AppInstaller"); return false; } catch (Exception e) { - _log.Error("An unexpected error occurred when registering AppInstaller", e); + _log.Error(e, "An unexpected error occurred when registering AppInstaller"); return false; } } @@ -106,7 +106,7 @@ public async Task IsConfigurationUnstubbedAsync() } catch (Exception e) { - _log.Error("An unexpected error occurred when checking if configuration is unstubbed", e); + _log.Error(e, "An unexpected error occurred when checking if configuration is unstubbed"); return false; } } @@ -123,7 +123,7 @@ public async Task UnstubConfigurationAsync() } catch (Exception e) { - _log.Error("An unexpected error occurred when unstubbing configuration", e); + _log.Error(e, "An unexpected error occurred when unstubbing configuration"); return false; } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetRecovery.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetRecovery.cs index c4a1006d6c..1212b1d7ec 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetRecovery.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetRecovery.cs @@ -45,12 +45,12 @@ public async Task DoWithRecoveryAsync(Func> actionFunc) } catch (CatalogNotInitializedException e) { - _log.Error($"Catalog used by the action is not initialized", e); + _log.Error(e, $"Catalog used by the action is not initialized"); await RecoveryAsync(attempt); } catch (COMException e) when (e.HResult == RpcServerUnavailable || e.HResult == RpcCallFailed || e.HResult == PackageUpdating) { - _log.Error($"Failed to operate on out-of-proc object with error code: 0x{e.HResult:x}", e); + _log.Error(e, $"Failed to operate on out-of-proc object with error code: 0x{e.HResult:x}"); await RecoveryAsync(attempt); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs index a4c8819ced..9209e267af 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetFeaturedApplicationsDataSource.cs @@ -90,7 +90,7 @@ await ForEachEnabledExtensionAsync(async (extensionGroups) => } catch (Exception e) { - _log.Error($"Error loading packages from featured applications group.", e); + _log.Error(e, $"Error loading packages from featured applications group."); } } }); @@ -197,7 +197,7 @@ private async Task ForEachEnabledExtensionAsync(Func LoadCatalogAsync(JsonWinGetPackageCatalog jso } catch (Exception e) { - _log.Error($"Error loading packages from winget catalog.", e); + _log.Error(e, $"Error loading packages from winget catalog."); } return null; @@ -184,7 +184,7 @@ private async Task GetJsonApplicationIconAsync(JsonWinGetPa } catch (Exception e) { - _log.Error($"Failed to get icon for JSON package {package.Uri}.", e); + _log.Error(e, $"Failed to get icon for JSON package {package.Uri}."); } _log.Warning($"No icon found for JSON package {package.Uri}. A default one will be provided."); diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetPackageRestoreDataSource.cs b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetPackageRestoreDataSource.cs index d87fe3126a..fa9bb0b060 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetPackageRestoreDataSource.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Services/WinGetPackageRestoreDataSource.cs @@ -97,7 +97,7 @@ public async override Task> LoadCatalogsAsync() } catch (Exception e) { - _log.Error($"Error loading packages from winget restore catalog.", e); + _log.Error(e, $"Error loading packages from winget restore catalog."); } return result; @@ -130,7 +130,7 @@ private async Task GetRestoreApplicationIconAsync(IRestoreA } catch (Exception e) { - _log.Error($"Failed to get icon for restore package {appInfo.Id}", e); + _log.Error(e, $"Failed to get icon for restore package {appInfo.Id}"); } _log.Warning($"No {theme} icon found for restore package {appInfo.Id}. A default one will be provided."); diff --git a/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs b/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs index 99f6c12de2..474a5c1c5c 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Utilities/DevDriveUtil.cs @@ -104,7 +104,7 @@ public static bool IsDevDriveFeatureEnabled } catch (Exception ex) { - Log.Error($"Unable to query for Dev Drive enablement: {ex.Message}"); + Log.Error(ex, $"Unable to query for Dev Drive enablement: {ex.Message}"); return false; } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs index b269b1be81..cd0a40d2ea 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs @@ -1051,7 +1051,7 @@ private void ValidateUriAndChangeUiIfBad(string url, out Uri uri) } catch (Exception e) { - _log.Error($"Invalid URL {uri.OriginalString}", e); + _log.Error(e, $"Invalid URL {uri.OriginalString}"); UrlParsingError = _stringResource.GetLocalized(StringResourceKey.UrlValidationBadUrl); ShouldShowUrlError = true; return; @@ -1248,7 +1248,7 @@ private async Task InitiateAddAccountUserExperienceAsync(RepositoryProvider prov } catch (Exception ex) { - _log.Error($"Exception thrown while calling show logon session", ex); + _log.Error(ex, $"Exception thrown while calling show logon session"); } } } @@ -1335,7 +1335,7 @@ private async Task CoordinateTasks(string loginId, Task PickConfigurationFileAsync() } catch (Exception e) { - _log.Error($"Failed to open file picker.", e); + _log.Error(e, $"Failed to open file picker."); return false; } } @@ -184,7 +184,7 @@ private async Task LoadConfigurationFileInternalAsync(StorageFile file) } catch (OpenConfigurationSetException e) { - _log.Error($"Opening configuration set failed.", e); + _log.Error(e, $"Opening configuration set failed."); await _mainWindow.ShowErrorMessageDialogAsync( StringResource.GetLocalized(StringResourceKey.ConfigurationViewTitle, file.Name), GetErrorMessage(e), @@ -192,7 +192,7 @@ await _mainWindow.ShowErrorMessageDialogAsync( } catch (Exception e) { - _log.Error($"Unknown error while opening configuration set.", e); + _log.Error(e, $"Unknown error while opening configuration set."); await _mainWindow.ShowErrorMessageDialogAsync( file.Name, diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/DevDriveViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/DevDriveViewModel.cs index 48cc57a038..8f2162075b 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/DevDriveViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/DevDriveViewModel.cs @@ -269,7 +269,7 @@ public async Task ChooseFolderLocationAsync() } catch (Exception e) { - _log.Error("Failed to open folder picker.", e); + _log.Error(e, "Failed to open folder picker."); } } @@ -509,7 +509,7 @@ private void RefreshDriveLetterToSizeMapping() } catch (Exception ex) { - _log.Error($"Failed to refresh the drive letter to size mapping.", ex); + _log.Error(ex, $"Failed to refresh the drive letter to size mapping."); // Clear the mapping since it can't be refreshed. This shouldn't happen unless DriveInfo.GetDrives() fails. In that case we won't know which drive // in the list is causing GetDrives()'s to throw. If there are values inside the dictionary at this point, they could be stale. Clearing the list diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemsListViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemsListViewModel.cs index a50b4f1391..123d843062 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemsListViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/ComputeSystemsListViewModel.cs @@ -146,7 +146,7 @@ public void FilterComputeSystemCards(string text) } catch (Exception ex) { - _log.Error($"Failed to filter Compute system cards. Error: {ex.Message}"); + _log.Error(ex, $"Failed to filter Compute system cards. Error: {ex.Message}"); } return true; diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs index 38fa6699a3..4a5f1a3115 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/EnvironmentCreationOptionsViewModel.cs @@ -181,7 +181,7 @@ public void UpdateExtensionAdaptiveCard(ComputeSystemAdaptiveCardResult adaptive } catch (Exception ex) { - _log.Error($"Failed to get creation options adaptive card from provider {_curProviderDetails.ComputeSystemProvider.Id}.", ex); + _log.Error(ex, $"Failed to get creation options adaptive card from provider {_curProviderDetails.ComputeSystemProvider.Id}."); SessionErrorMessage = ex.Message; } }); diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs index 9d7999dfd9..7300d8bc9c 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs @@ -146,7 +146,7 @@ private async Task PickCloneDirectoryAsync() } catch (Exception e) { - _log.Error("Failed to open folder picker", e); + _log.Error(e, "Failed to open folder picker"); return null; } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs index 693271087e..f8728f3722 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs @@ -93,7 +93,7 @@ private async Task LoadCatalogsAsync() } catch (Exception e) { - _log.Error($"Failed to load catalogs.", e); + _log.Error(e, $"Failed to load catalogs."); } finally { diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs index 1129c1b62f..8926e1b559 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs @@ -164,7 +164,7 @@ private async Task OnSetUpAsync() } catch (Exception e) { - _log.Error($"Failed to initialize elevated process.", e); + _log.Error(e, $"Failed to initialize elevated process."); } } @@ -188,7 +188,7 @@ private async Task DownloadConfigurationAsync() } catch (Exception e) { - _log.Error($"Failed to download configuration file.", e); + _log.Error(e, $"Failed to download configuration file."); } } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SearchViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SearchViewModel.cs index 5cb8bfd161..a80727ff66 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SearchViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SearchViewModel.cs @@ -128,7 +128,7 @@ public SearchViewModel(IWindowsPackageManager wpm, ISetupFlowStringResource stri } catch (Exception e) { - _log.Error($"Search error.", e); + _log.Error(e, $"Search error."); return (SearchResultStatus.ExceptionThrown, null); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs index 44ed8eabbe..80078db0f2 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs @@ -196,7 +196,7 @@ private bool ShouldShowListInUI(ComputeSystemsListViewModel listViewModel, strin } catch (Exception ex) { - _log.Error($"Error filtering ComputeSystemsListViewModel", ex); + _log.Error(ex, $"Error filtering ComputeSystemsListViewModel"); } return true; @@ -366,7 +366,7 @@ public async Task LoadAllComputeSystemsInTheUI() } catch (Exception ex) { - _log.Error($"Error loading ComputeSystemViewModels data", ex); + _log.Error(ex, $"Error loading ComputeSystemViewModels data"); } ShouldShowShimmerBelowList = false; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs index c1532b9b71..24ca943ebb 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs @@ -74,7 +74,7 @@ private void AddAdaptiveCardToUI(RenderedAdaptiveCard renderedAdaptiveCard) catch (Exception ex) { // Log the exception - _log.Error("Error adding adaptive card UI in CreateEnvironmentReviewView", ex); + _log.Error(ex, "Error adding adaptive card UI in CreateEnvironmentReviewView"); } } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs index 3bb7562cc0..7a438b6ae8 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs @@ -72,7 +72,7 @@ private void AddAdaptiveCardToUI(RenderedAdaptiveCard adaptiveCardData) catch (Exception ex) { // Log the exception - _log.Error("Error adding adaptive card UI in EnvironmentCreationOptionsView", ex); + _log.Error(ex, "Error adding adaptive card UI in EnvironmentCreationOptionsView"); } } } From f8e0dc407c1c9ea9b8b1b05f12c004956b409573 Mon Sep 17 00:00:00 2001 From: Kristen Schau <47155823+krschau@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:38:42 -0400 Subject: [PATCH 18/22] Handle a missing PropertySet gracefully (#2579) --- src/Package.appxmanifest | 2 +- src/Services/ExtensionService.cs | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Package.appxmanifest b/src/Package.appxmanifest index f6308beed4..3e44bb0bc3 100644 --- a/src/Package.appxmanifest +++ b/src/Package.appxmanifest @@ -86,7 +86,7 @@ - + diff --git a/src/Services/ExtensionService.cs b/src/Services/ExtensionService.cs index 0566a581bf..032d45c2cf 100644 --- a/src/Services/ExtensionService.cs +++ b/src/Services/ExtensionService.cs @@ -313,12 +313,12 @@ protected virtual void Dispose(bool disposing) private IPropertySet? GetSubPropertySet(IPropertySet propSet, string name) { - return propSet[name] as IPropertySet; + return propSet.TryGetValue(name, out var value) ? value as IPropertySet : null; } private object[]? GetSubPropertySetArray(IPropertySet propSet, string name) { - return propSet[name] as object[]; + return propSet.TryGetValue(name, out var value) ? value as object[] : null; } /// @@ -330,7 +330,6 @@ private List GetCreateInstanceList(IPropertySet activationPropSet) { var propSetList = new List(); var singlePropertySet = GetSubPropertySet(activationPropSet, CreateInstanceProperty); - var propertySetArray = GetSubPropertySetArray(activationPropSet, CreateInstanceProperty); if (singlePropertySet != null) { var classId = GetProperty(singlePropertySet, ClassIdProperty); @@ -341,19 +340,23 @@ private List GetCreateInstanceList(IPropertySet activationPropSet) propSetList.Add(classId); } } - else if (propertySetArray != null) + else { - foreach (var prop in propertySetArray) + var propertySetArray = GetSubPropertySetArray(activationPropSet, CreateInstanceProperty); + if (propertySetArray != null) { - if (prop is not IPropertySet propertySet) + foreach (var prop in propertySetArray) { - continue; - } + if (prop is not IPropertySet propertySet) + { + continue; + } - var classId = GetProperty(propertySet, ClassIdProperty); - if (classId != null) - { - propSetList.Add(classId); + var classId = GetProperty(propertySet, ClassIdProperty); + if (classId != null) + { + propSetList.Add(classId); + } } } } From ebfbe697de606c651b83af5852b6a0d9a7ea6b46 Mon Sep 17 00:00:00 2001 From: Darren Hoehna Date: Wed, 10 Apr 2024 12:44:45 -0700 Subject: [PATCH 19/22] Adding tooltip to config buttons (#2559) * Making the name better * Adding tooltip and moving code out of code-behind * Using path.join: * Removin unused usings --------- Co-authored-by: Darren Hoehna --- .../ViewModels/AddRepoViewModel.cs | 27 +++++++++++++++++-- .../ViewModels/FolderPickerViewModel.cs | 1 - .../Views/AddRepoDialog.xaml | 20 +++++++++++--- .../Views/AddRepoDialog.xaml.cs | 23 ---------------- .../Views/RepoConfigView.xaml | 16 +++++++++-- 5 files changed, 55 insertions(+), 32 deletions(-) diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs index cd0a40d2ea..636f80d46e 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs @@ -6,6 +6,7 @@ using System.Collections.ObjectModel; using System.IO; using System.Linq; +using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; @@ -339,6 +340,28 @@ public async Task ShowCustomizeDevDriveWindow() ToggleCloneButton(); } + [RelayCommand] + public void DevDriveCloneLocationChanged() + { + var location = (EditDevDriveViewModel.DevDrive != null) ? EditDevDriveViewModel.GetDriveDisplayName() : string.Empty; + + if (!string.IsNullOrEmpty(location)) + { + SaveCloneLocation(location); + } + } + + [RelayCommand] + public void SaveCloneLocation(string location) + { + // In cases where location is empty don't update the cloneLocation. Only update when there are actual values. + FolderPickerViewModel.CloneLocation = location; + + FolderPickerViewModel.ValidateCloneLocation(); + + ToggleCloneButton(); + } + /// /// Indicates if the ListView is currently filtering items. A result of manually filtering a list view /// is that the SelectionChanged is fired for any selected item that is removed and the item isn't "re-selected" @@ -1014,8 +1037,8 @@ public void AddOrRemoveRepository(string accountName, IList repositories cloningInformation.RepositoryProvider = _providers.GetSDKProvider(_selectedRepoProvider); cloningInformation.ProviderName = _providers.DisplayName(_selectedRepoProvider); cloningInformation.OwningAccount = developerId; - cloningInformation.EditClonePathAutomationName = _stringResource.GetLocalized(StringResourceKey.RepoPageEditClonePathAutomationProperties, $"{_selectedRepoProvider}/{repositoryToAdd}"); - cloningInformation.RemoveFromCloningAutomationName = _stringResource.GetLocalized(StringResourceKey.RepoPageRemoveRepoAutomationProperties, $"{_selectedRepoProvider}/{repositoryToAdd}"); + cloningInformation.EditClonePathAutomationName = _stringResource.GetLocalized(StringResourceKey.RepoPageEditClonePathAutomationProperties, Path.Join(_selectedRepoProvider, repositoryToAdd.RepoDisplayName)); + cloningInformation.RemoveFromCloningAutomationName = _stringResource.GetLocalized(StringResourceKey.RepoPageRemoveRepoAutomationProperties, Path.Join(_selectedRepoProvider, repositoryToAdd.RepoDisplayName)); EverythingToClone.Add(cloningInformation); } } diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs index 7300d8bc9c..f4e5b397c9 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs @@ -10,7 +10,6 @@ using DevHome.SetupFlow.Services; using Microsoft.UI.Xaml; using Serilog; -using Windows.Storage.Pickers; using WinUIEx; namespace DevHome.SetupFlow.ViewModels; diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml index 06639efbad..89422e4985 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml @@ -211,17 +211,29 @@ + x:Name="DevDriveCloneLocationAliasTextBox"> + + + + + + + Visibility="{x:Bind AddRepoViewModel.FolderPickerViewModel.InDevDriveScenario, Mode=OneWay, Converter={StaticResource NegatedBoolToVisibilityConverter}}"> + + + + + + diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs index b45115b4f1..428b5fddeb 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs @@ -110,29 +110,6 @@ public void DeveloperIdChangedEventHandler(object sender, IDeveloperId developer } } - /// - /// Validate the user put in a rooted, non-null path. - /// - private void CloneLocation_TextChanged(object sender, TextChangedEventArgs e) - { - // just in case something other than a text box calls this. - if (sender is TextBox cloneLocationTextBox) - { - var location = cloneLocationTextBox.Text; - if (string.Equals(cloneLocationTextBox.Name, "DevDriveCloneLocationAliasTextBox", StringComparison.Ordinal)) - { - location = (AddRepoViewModel.EditDevDriveViewModel.DevDrive != null) ? AddRepoViewModel.EditDevDriveViewModel.GetDriveDisplayName() : string.Empty; - } - - // In cases where location is empty don't update the cloneLocation. Only update when there are actual values. - AddRepoViewModel.FolderPickerViewModel.CloneLocation = string.IsNullOrEmpty(location) ? AddRepoViewModel.FolderPickerViewModel.CloneLocation : location; - } - - AddRepoViewModel.FolderPickerViewModel.ValidateCloneLocation(); - - AddRepoViewModel.ToggleCloneButton(); - } - /// /// If any items in reposToSelect exist in the UI, select them. /// An side-effect of SelectRange is SelectionChanged is fired for each item SelectRange is called on. diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml index bc2c11c908..a86acf64c6 100644 --- a/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml +++ b/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml @@ -146,14 +146,26 @@ VerticalAlignment="Center"/> - -