diff --git a/.editorconfig b/.editorconfig
index ecfa251206..c25d5a04a4 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -87,16 +87,13 @@ csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
#Style - language keyword and framework type options
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
-#Style - Language rules
-csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
-csharp_style_var_for_built_in_types = true:warning
-
#Style - modifier options
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
@@ -131,6 +128,9 @@ csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_prefer_primary_constructors = true:silent
[*.{cs,vb}]
dotnet_style_operator_placement_when_wrapping = beginning_of_line
@@ -146,7 +146,6 @@ dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
-[*.{cs,vb}]
#Style - Unnecessary code rules
csharp_style_unused_value_assignment_preference = discard_variable:warning
@@ -193,16 +192,37 @@ dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
-dotnet_naming_style.pascal_case.required_prefix =
-dotnet_naming_style.pascal_case.required_suffix =
-dotnet_naming_style.pascal_case.word_separator =
-dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_simplified_interpolation = true:suggestion
+# Define what we will treat as private fields.
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private
+
+dotnet_naming_symbols.const_private_fields.applicable_kinds = field
+dotnet_naming_symbols.const_private_fields.applicable_accessibilities = private
+dotnet_naming_symbols.const_private_fields.required_modifiers = const
+
+# Define rule that something must begin with an underscore and be in camel case.
+dotnet_naming_style.require_underscore_prefix_and_camel_case.required_prefix = _
+dotnet_naming_style.require_underscore_prefix_and_camel_case.capitalization = camel_case
+
+# Define rule that something must not begin with an underscore and be in pascal case
+dotnet_naming_style.require_no_prefix_and_pascal_case.required_prefix =
+dotnet_naming_style.require_no_prefix_and_pascal_case.capitalization = pascal_case
+
+# Appy our rule to private fields.
+dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case.symbols = private_fields
+dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case.style = require_underscore_prefix_and_camel_case
+dotnet_naming_rule.private_fields_must_begin_with_underscore_and_be_in_camel_case.severity = warning
+
+dotnet_naming_rule.const_fields_must_begin_with_no_prefix_and_be_in_pascal_case.symbols = const_private_fields
+dotnet_naming_rule.const_fields_must_begin_with_no_prefix_and_be_in_pascal_case.style = require_no_prefix_and_pascal_case
+dotnet_naming_rule.const_fields_must_begin_with_no_prefix_and_be_in_pascal_case.severity = warning
+
# Spelling
spelling_exclusion_path = .\exclusion.dic
diff --git a/tools/QuietBackgroundProcesses/.gitattributes b/.gitattributes
similarity index 100%
rename from tools/QuietBackgroundProcesses/.gitattributes
rename to .gitattributes
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index a0190f1c2f..2ad0d746c6 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -10,3 +10,4 @@
- [ ] Closes #xxx
- [ ] Tests added/passed
- [ ] Documentation updated
+- [ ] Telemetry [compliance tasks](https://aka.ms/devhome-telemetry) completed for added/updated events
diff --git a/DevHome.sln b/DevHome.sln
index f721edeb0a..7c9dfcd078 100644
--- a/DevHome.sln
+++ b/DevHome.sln
@@ -146,13 +146,28 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.EnvironmentVariable
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DevHome.Telemetry.Native", "telemetry\DevHome.Telemetry.Native\DevHome.Telemetry.Native.vcxproj", "{8EB52F7D-D216-49FF-BF16-DE06E4695950}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{E0A15760-487A-4CCB-8093-DE6FF3C4BC23}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.Services.WindowsPackageManager", "services\DevHome.Services.WindowsPackageManager\DevHome.Services.WindowsPackageManager.csproj", "{DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.Services.Core", "services\DevHome.Services.Core\DevHome.Services.Core.csproj", "{8FB1EF90-B693-4A2A-A7F2-44ECA499D769}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{E768781A-D1F7-4C03-B46D-E76354FAB587}"
ProjectSection(SolutionItems) = preProject
tools\scripts\CaptureDevHomeLogs.ps1 = tools\scripts\CaptureDevHomeLogs.ps1
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.Services.DesiredStateConfiguration", "services\DevHome.Services.DesiredStateConfiguration\DevHome.Services.DesiredStateConfiguration.csproj", "{D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WSLExtension", "WSLExtension", "{73D1E84F-56CC-412B-BF2B-FA692BF6B396}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WSLExtension", "extensions\WSLExtension\WSLExtension.csproj", "{B6153EEA-EADE-4BAA-B47D-6B48205C6696}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug_FailFast|arm64 = Debug_FailFast|arm64
+ Debug_FailFast|x64 = Debug_FailFast|x64
+ Debug_FailFast|x86 = Debug_FailFast|x86
Debug|arm64 = Debug|arm64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
@@ -161,6 +176,15 @@ Global
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug_FailFast|arm64.Deploy.0 = Debug_FailFast|arm64
+ {60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug_FailFast|x64.Deploy.0 = Debug_FailFast|x64
+ {60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
+ {60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug_FailFast|x86.Deploy.0 = Debug_FailFast|x86
{60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug|arm64.ActiveCfg = Debug|arm64
{60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug|arm64.Build.0 = Debug|arm64
{60E0FD98-5396-436D-BAB7-187A853A5DC6}.Debug|arm64.Deploy.0 = Debug|arm64
@@ -179,6 +203,12 @@ Global
{60E0FD98-5396-436D-BAB7-187A853A5DC6}.Release|x86.ActiveCfg = Release|x86
{60E0FD98-5396-436D-BAB7-187A853A5DC6}.Release|x86.Build.0 = Release|x86
{60E0FD98-5396-436D-BAB7-187A853A5DC6}.Release|x86.Deploy.0 = Release|x86
+ {8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Debug|arm64.ActiveCfg = Debug|arm64
{8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Debug|arm64.Build.0 = Debug|arm64
{8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Debug|x64.ActiveCfg = Debug|x64
@@ -191,6 +221,12 @@ Global
{8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Release|x64.Build.0 = Release|x64
{8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Release|x86.ActiveCfg = Release|x86
{8BE0016E-5BBD-459E-A382-B1CE56E7CA5D}.Release|x86.Build.0 = Release|x86
+ {3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Debug|arm64.ActiveCfg = Debug|arm64
{3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Debug|arm64.Build.0 = Debug|arm64
{3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Debug|x64.ActiveCfg = Debug|x64
@@ -203,6 +239,12 @@ Global
{3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Release|x64.Build.0 = Release|x64
{3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Release|x86.ActiveCfg = Release|x86
{3B409BF0-59D5-4AA3-8927-8E11476E6CEB}.Release|x86.Build.0 = Release|x86
+ {E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Debug|arm64.ActiveCfg = Debug|arm64
{E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Debug|arm64.Build.0 = Debug|arm64
{E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Debug|x64.ActiveCfg = Debug|x64
@@ -215,6 +257,12 @@ Global
{E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Release|x64.Build.0 = Release|x64
{E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Release|x86.ActiveCfg = Release|x86
{E9F49E1C-C15D-4B87-92CA-9003C1C31DB5}.Release|x86.Build.0 = Release|x86
+ {CD512D91-FDA6-4908-89D5-4106F090A7BE}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {CD512D91-FDA6-4908-89D5-4106F090A7BE}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {CD512D91-FDA6-4908-89D5-4106F090A7BE}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {CD512D91-FDA6-4908-89D5-4106F090A7BE}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {CD512D91-FDA6-4908-89D5-4106F090A7BE}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {CD512D91-FDA6-4908-89D5-4106F090A7BE}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{CD512D91-FDA6-4908-89D5-4106F090A7BE}.Debug|arm64.ActiveCfg = Debug|arm64
{CD512D91-FDA6-4908-89D5-4106F090A7BE}.Debug|arm64.Build.0 = Debug|arm64
{CD512D91-FDA6-4908-89D5-4106F090A7BE}.Debug|x64.ActiveCfg = Debug|x64
@@ -227,6 +275,12 @@ Global
{CD512D91-FDA6-4908-89D5-4106F090A7BE}.Release|x64.Build.0 = Release|x64
{CD512D91-FDA6-4908-89D5-4106F090A7BE}.Release|x86.ActiveCfg = Release|x86
{CD512D91-FDA6-4908-89D5-4106F090A7BE}.Release|x86.Build.0 = Release|x86
+ {C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Debug|arm64.ActiveCfg = Debug|arm64
{C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Debug|arm64.Build.0 = Debug|arm64
{C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Debug|x64.ActiveCfg = Debug|x64
@@ -239,6 +293,12 @@ Global
{C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Release|x64.Build.0 = Release|x64
{C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Release|x86.ActiveCfg = Release|x86
{C45241F7-8B4A-44F2-A78B-AFB6288F55B9}.Release|x86.Build.0 = Release|x86
+ {0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Debug|arm64.ActiveCfg = Debug|arm64
{0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Debug|arm64.Build.0 = Debug|arm64
{0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Debug|x64.ActiveCfg = Debug|x64
@@ -251,6 +311,12 @@ Global
{0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Release|x64.Build.0 = Release|x64
{0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Release|x86.ActiveCfg = Release|x86
{0901B260-1B88-4B99-A9F8-477ED0A74FBD}.Release|x86.Build.0 = Release|x86
+ {9A62BDDC-F33E-4EBE-B407-533263A92511}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {9A62BDDC-F33E-4EBE-B407-533263A92511}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {9A62BDDC-F33E-4EBE-B407-533263A92511}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {9A62BDDC-F33E-4EBE-B407-533263A92511}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {9A62BDDC-F33E-4EBE-B407-533263A92511}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {9A62BDDC-F33E-4EBE-B407-533263A92511}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{9A62BDDC-F33E-4EBE-B407-533263A92511}.Debug|arm64.ActiveCfg = Debug|arm64
{9A62BDDC-F33E-4EBE-B407-533263A92511}.Debug|arm64.Build.0 = Debug|arm64
{9A62BDDC-F33E-4EBE-B407-533263A92511}.Debug|x64.ActiveCfg = Debug|x64
@@ -263,6 +329,12 @@ Global
{9A62BDDC-F33E-4EBE-B407-533263A92511}.Release|x64.Build.0 = Release|x64
{9A62BDDC-F33E-4EBE-B407-533263A92511}.Release|x86.ActiveCfg = Release|x86
{9A62BDDC-F33E-4EBE-B407-533263A92511}.Release|x86.Build.0 = Release|x86
+ {F65759A2-AF44-4211-9817-76E6D02F37D0}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {F65759A2-AF44-4211-9817-76E6D02F37D0}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {F65759A2-AF44-4211-9817-76E6D02F37D0}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {F65759A2-AF44-4211-9817-76E6D02F37D0}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {F65759A2-AF44-4211-9817-76E6D02F37D0}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {F65759A2-AF44-4211-9817-76E6D02F37D0}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{F65759A2-AF44-4211-9817-76E6D02F37D0}.Debug|arm64.ActiveCfg = Debug|arm64
{F65759A2-AF44-4211-9817-76E6D02F37D0}.Debug|arm64.Build.0 = Debug|arm64
{F65759A2-AF44-4211-9817-76E6D02F37D0}.Debug|x64.ActiveCfg = Debug|x64
@@ -275,6 +347,12 @@ Global
{F65759A2-AF44-4211-9817-76E6D02F37D0}.Release|x64.Build.0 = Release|x64
{F65759A2-AF44-4211-9817-76E6D02F37D0}.Release|x86.ActiveCfg = Release|x86
{F65759A2-AF44-4211-9817-76E6D02F37D0}.Release|x86.Build.0 = Release|x86
+ {54082587-A435-423F-AE1B-01B906FFA7C5}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {54082587-A435-423F-AE1B-01B906FFA7C5}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {54082587-A435-423F-AE1B-01B906FFA7C5}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {54082587-A435-423F-AE1B-01B906FFA7C5}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {54082587-A435-423F-AE1B-01B906FFA7C5}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {54082587-A435-423F-AE1B-01B906FFA7C5}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{54082587-A435-423F-AE1B-01B906FFA7C5}.Debug|arm64.ActiveCfg = Debug|arm64
{54082587-A435-423F-AE1B-01B906FFA7C5}.Debug|arm64.Build.0 = Debug|arm64
{54082587-A435-423F-AE1B-01B906FFA7C5}.Debug|x64.ActiveCfg = Debug|x64
@@ -287,6 +365,12 @@ Global
{54082587-A435-423F-AE1B-01B906FFA7C5}.Release|x64.Build.0 = Release|x64
{54082587-A435-423F-AE1B-01B906FFA7C5}.Release|x86.ActiveCfg = Release|x86
{54082587-A435-423F-AE1B-01B906FFA7C5}.Release|x86.Build.0 = Release|x86
+ {D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Debug|arm64.ActiveCfg = Debug|arm64
{D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Debug|arm64.Build.0 = Debug|arm64
{D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Debug|x64.ActiveCfg = Debug|x64
@@ -299,6 +383,12 @@ Global
{D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Release|x64.Build.0 = Release|x64
{D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Release|x86.ActiveCfg = Release|x86
{D2303635-3DD9-4DCA-A38A-F5306D0BB8FE}.Release|x86.Build.0 = Release|x86
+ {6254ADB1-B6FD-4D74-AF13-40C997919178}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {6254ADB1-B6FD-4D74-AF13-40C997919178}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {6254ADB1-B6FD-4D74-AF13-40C997919178}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {6254ADB1-B6FD-4D74-AF13-40C997919178}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {6254ADB1-B6FD-4D74-AF13-40C997919178}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {6254ADB1-B6FD-4D74-AF13-40C997919178}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{6254ADB1-B6FD-4D74-AF13-40C997919178}.Debug|arm64.ActiveCfg = Debug|arm64
{6254ADB1-B6FD-4D74-AF13-40C997919178}.Debug|arm64.Build.0 = Debug|arm64
{6254ADB1-B6FD-4D74-AF13-40C997919178}.Debug|x64.ActiveCfg = Debug|x64
@@ -311,6 +401,12 @@ Global
{6254ADB1-B6FD-4D74-AF13-40C997919178}.Release|x64.Build.0 = Release|x64
{6254ADB1-B6FD-4D74-AF13-40C997919178}.Release|x86.ActiveCfg = Release|x86
{6254ADB1-B6FD-4D74-AF13-40C997919178}.Release|x86.Build.0 = Release|x86
+ {72CE876A-7272-4460-92EB-845A45B743FB}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {72CE876A-7272-4460-92EB-845A45B743FB}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {72CE876A-7272-4460-92EB-845A45B743FB}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {72CE876A-7272-4460-92EB-845A45B743FB}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {72CE876A-7272-4460-92EB-845A45B743FB}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {72CE876A-7272-4460-92EB-845A45B743FB}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{72CE876A-7272-4460-92EB-845A45B743FB}.Debug|arm64.ActiveCfg = Debug|arm64
{72CE876A-7272-4460-92EB-845A45B743FB}.Debug|arm64.Build.0 = Debug|arm64
{72CE876A-7272-4460-92EB-845A45B743FB}.Debug|x64.ActiveCfg = Debug|x64
@@ -323,6 +419,12 @@ Global
{72CE876A-7272-4460-92EB-845A45B743FB}.Release|x64.Build.0 = Release|x64
{72CE876A-7272-4460-92EB-845A45B743FB}.Release|x86.ActiveCfg = Release|x86
{72CE876A-7272-4460-92EB-845A45B743FB}.Release|x86.Build.0 = Release|x86
+ {940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Debug|arm64.ActiveCfg = Debug|ARM64
{940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Debug|arm64.Build.0 = Debug|ARM64
{940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Debug|x64.ActiveCfg = Debug|x64
@@ -335,6 +437,12 @@ Global
{940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Release|x64.Build.0 = Release|x64
{940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Release|x86.ActiveCfg = Release|x86
{940FD524-1AC0-4BBA-BBBE-1E4F2E797508}.Release|x86.Build.0 = Release|x86
+ {A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Debug|arm64.ActiveCfg = Debug|ARM64
{A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Debug|arm64.Build.0 = Debug|ARM64
{A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Debug|x64.ActiveCfg = Debug|x64
@@ -347,6 +455,12 @@ Global
{A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Release|x64.Build.0 = Release|x64
{A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Release|x86.ActiveCfg = Release|x86
{A7E5FD7B-B41A-4CAE-A45A-E686DFA8ACF1}.Release|x86.Build.0 = Release|x86
+ {053AF75C-5CD8-497F-BA25-47435BD86047}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {053AF75C-5CD8-497F-BA25-47435BD86047}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {053AF75C-5CD8-497F-BA25-47435BD86047}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {053AF75C-5CD8-497F-BA25-47435BD86047}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {053AF75C-5CD8-497F-BA25-47435BD86047}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {053AF75C-5CD8-497F-BA25-47435BD86047}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{053AF75C-5CD8-497F-BA25-47435BD86047}.Debug|arm64.ActiveCfg = Debug|ARM64
{053AF75C-5CD8-497F-BA25-47435BD86047}.Debug|arm64.Build.0 = Debug|ARM64
{053AF75C-5CD8-497F-BA25-47435BD86047}.Debug|x64.ActiveCfg = Debug|x64
@@ -359,6 +473,12 @@ Global
{053AF75C-5CD8-497F-BA25-47435BD86047}.Release|x64.Build.0 = Release|x64
{053AF75C-5CD8-497F-BA25-47435BD86047}.Release|x86.ActiveCfg = Release|x86
{053AF75C-5CD8-497F-BA25-47435BD86047}.Release|x86.Build.0 = Release|x86
+ {0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Debug|arm64.ActiveCfg = Debug|ARM64
{0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Debug|arm64.Build.0 = Debug|ARM64
{0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Debug|x64.ActiveCfg = Debug|x64
@@ -371,6 +491,12 @@ Global
{0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Release|x64.Build.0 = Release|x64
{0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Release|x86.ActiveCfg = Release|x86
{0D879E08-99AA-4019-9D04-DEA9F7C7BFC1}.Release|x86.Build.0 = Release|x86
+ {69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Debug|arm64.ActiveCfg = Debug|arm64
{69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Debug|arm64.Build.0 = Debug|arm64
{69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Debug|x64.ActiveCfg = Debug|x64
@@ -383,6 +509,12 @@ Global
{69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Release|x64.Build.0 = Release|x64
{69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Release|x86.ActiveCfg = Release|x86
{69F8B7DF-F52B-4B74-9A16-AB3241BB8912}.Release|x86.Build.0 = Release|x86
+ {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|arm64.ActiveCfg = Debug|arm64
{2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|arm64.Build.0 = Debug|arm64
{2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Debug|x64.ActiveCfg = Debug|x64
@@ -395,6 +527,12 @@ Global
{2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|x64.Build.0 = Release|x64
{2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|x86.ActiveCfg = Release|x86
{2F9AD5AF-EF3B-496A-8566-9E9539E3DF43}.Release|x86.Build.0 = Release|x86
+ {75945141-03AC-4C40-A586-16D463A0AC1B}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|ARM
+ {75945141-03AC-4C40-A586-16D463A0AC1B}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|ARM
+ {75945141-03AC-4C40-A586-16D463A0AC1B}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {75945141-03AC-4C40-A586-16D463A0AC1B}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {75945141-03AC-4C40-A586-16D463A0AC1B}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|Win32
+ {75945141-03AC-4C40-A586-16D463A0AC1B}.Debug_FailFast|x86.Build.0 = Debug_FailFast|Win32
{75945141-03AC-4C40-A586-16D463A0AC1B}.Debug|arm64.ActiveCfg = Debug|ARM64
{75945141-03AC-4C40-A586-16D463A0AC1B}.Debug|arm64.Build.0 = Debug|ARM64
{75945141-03AC-4C40-A586-16D463A0AC1B}.Debug|x64.ActiveCfg = Debug|x64
@@ -407,6 +545,12 @@ Global
{75945141-03AC-4C40-A586-16D463A0AC1B}.Release|x64.Build.0 = Release|x64
{75945141-03AC-4C40-A586-16D463A0AC1B}.Release|x86.ActiveCfg = Release|Win32
{75945141-03AC-4C40-A586-16D463A0AC1B}.Release|x86.Build.0 = Release|Win32
+ {092AC740-DA01-4872-8E93-B9557DAD6BE5}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {092AC740-DA01-4872-8E93-B9557DAD6BE5}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {092AC740-DA01-4872-8E93-B9557DAD6BE5}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {092AC740-DA01-4872-8E93-B9557DAD6BE5}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {092AC740-DA01-4872-8E93-B9557DAD6BE5}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {092AC740-DA01-4872-8E93-B9557DAD6BE5}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{092AC740-DA01-4872-8E93-B9557DAD6BE5}.Debug|arm64.ActiveCfg = Debug|ARM64
{092AC740-DA01-4872-8E93-B9557DAD6BE5}.Debug|arm64.Build.0 = Debug|ARM64
{092AC740-DA01-4872-8E93-B9557DAD6BE5}.Debug|x64.ActiveCfg = Debug|x64
@@ -419,6 +563,12 @@ Global
{092AC740-DA01-4872-8E93-B9557DAD6BE5}.Release|x64.Build.0 = Release|x64
{092AC740-DA01-4872-8E93-B9557DAD6BE5}.Release|x86.ActiveCfg = Release|x86
{092AC740-DA01-4872-8E93-B9557DAD6BE5}.Release|x86.Build.0 = Release|x86
+ {80805B43-CE75-4C6E-92F8-F385C1039E53}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|ARM
+ {80805B43-CE75-4C6E-92F8-F385C1039E53}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|ARM
+ {80805B43-CE75-4C6E-92F8-F385C1039E53}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {80805B43-CE75-4C6E-92F8-F385C1039E53}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {80805B43-CE75-4C6E-92F8-F385C1039E53}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|Win32
+ {80805B43-CE75-4C6E-92F8-F385C1039E53}.Debug_FailFast|x86.Build.0 = Debug_FailFast|Win32
{80805B43-CE75-4C6E-92F8-F385C1039E53}.Debug|arm64.ActiveCfg = Debug|ARM64
{80805B43-CE75-4C6E-92F8-F385C1039E53}.Debug|arm64.Build.0 = Debug|ARM64
{80805B43-CE75-4C6E-92F8-F385C1039E53}.Debug|x64.ActiveCfg = Debug|x64
@@ -431,6 +581,12 @@ Global
{80805B43-CE75-4C6E-92F8-F385C1039E53}.Release|x64.Build.0 = Release|x64
{80805B43-CE75-4C6E-92F8-F385C1039E53}.Release|x86.ActiveCfg = Release|Win32
{80805B43-CE75-4C6E-92F8-F385C1039E53}.Release|x86.Build.0 = Release|Win32
+ {4B370E2F-FB1D-4887-90BF-3B72517485CE}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|ARM
+ {4B370E2F-FB1D-4887-90BF-3B72517485CE}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|ARM
+ {4B370E2F-FB1D-4887-90BF-3B72517485CE}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {4B370E2F-FB1D-4887-90BF-3B72517485CE}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {4B370E2F-FB1D-4887-90BF-3B72517485CE}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|Win32
+ {4B370E2F-FB1D-4887-90BF-3B72517485CE}.Debug_FailFast|x86.Build.0 = Debug_FailFast|Win32
{4B370E2F-FB1D-4887-90BF-3B72517485CE}.Debug|arm64.ActiveCfg = Debug|ARM64
{4B370E2F-FB1D-4887-90BF-3B72517485CE}.Debug|arm64.Build.0 = Debug|ARM64
{4B370E2F-FB1D-4887-90BF-3B72517485CE}.Debug|x64.ActiveCfg = Debug|x64
@@ -443,6 +599,12 @@ Global
{4B370E2F-FB1D-4887-90BF-3B72517485CE}.Release|x64.Build.0 = Release|x64
{4B370E2F-FB1D-4887-90BF-3B72517485CE}.Release|x86.ActiveCfg = Release|Win32
{4B370E2F-FB1D-4887-90BF-3B72517485CE}.Release|x86.Build.0 = Release|Win32
+ {1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Debug|arm64.ActiveCfg = Debug|arm64
{1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Debug|arm64.Build.0 = Debug|arm64
{1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Debug|x64.ActiveCfg = Debug|x64
@@ -455,6 +617,12 @@ Global
{1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Release|x64.Build.0 = Release|x64
{1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Release|x86.ActiveCfg = Release|x86
{1477F3EA-A9F6-4B4F-82F4-C2427F57EBEE}.Release|x86.Build.0 = Release|x86
+ {AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|ARM64
+ {AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|ARM64
+ {AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|Win32
+ {AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Debug_FailFast|x86.Build.0 = Debug_FailFast|Win32
{AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Debug|arm64.ActiveCfg = Debug|ARM64
{AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Debug|arm64.Build.0 = Debug|ARM64
{AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Debug|x64.ActiveCfg = Debug|x64
@@ -467,6 +635,12 @@ Global
{AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Release|x64.Build.0 = Release|x64
{AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Release|x86.ActiveCfg = Release|Win32
{AF5A7FA0-E3E8-44C8-8830-31DD08F583E8}.Release|x86.Build.0 = Release|Win32
+ {CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Debug|arm64.ActiveCfg = Debug|ARM64
{CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Debug|arm64.Build.0 = Debug|ARM64
{CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Debug|x64.ActiveCfg = Debug|x64
@@ -479,6 +653,12 @@ Global
{CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Release|x64.Build.0 = Release|x64
{CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Release|x86.ActiveCfg = Release|x86
{CFD8A90D-8B6D-4ED6-BA35-FF894BEB46C0}.Release|x86.Build.0 = Release|x86
+ {0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Debug|arm64.ActiveCfg = Debug|ARM64
{0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Debug|arm64.Build.0 = Debug|ARM64
{0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Debug|x64.ActiveCfg = Debug|x64
@@ -491,6 +671,12 @@ Global
{0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Release|x64.Build.0 = Release|x64
{0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Release|x86.ActiveCfg = Release|x86
{0689521A-1686-46DB-81E1-3B84AA0EF1AC}.Release|x86.Build.0 = Release|x86
+ {A716481F-C1AF-4243-84F9-7B9399055E51}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {A716481F-C1AF-4243-84F9-7B9399055E51}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {A716481F-C1AF-4243-84F9-7B9399055E51}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {A716481F-C1AF-4243-84F9-7B9399055E51}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {A716481F-C1AF-4243-84F9-7B9399055E51}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {A716481F-C1AF-4243-84F9-7B9399055E51}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{A716481F-C1AF-4243-84F9-7B9399055E51}.Debug|arm64.ActiveCfg = Debug|arm64
{A716481F-C1AF-4243-84F9-7B9399055E51}.Debug|arm64.Build.0 = Debug|arm64
{A716481F-C1AF-4243-84F9-7B9399055E51}.Debug|x64.ActiveCfg = Debug|x64
@@ -503,6 +689,12 @@ Global
{A716481F-C1AF-4243-84F9-7B9399055E51}.Release|x64.Build.0 = Release|x64
{A716481F-C1AF-4243-84F9-7B9399055E51}.Release|x86.ActiveCfg = Release|x86
{A716481F-C1AF-4243-84F9-7B9399055E51}.Release|x86.Build.0 = Release|x86
+ {75976510-22B7-4910-96F2-3E1519C3FF35}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|ARM64
+ {75976510-22B7-4910-96F2-3E1519C3FF35}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|ARM64
+ {75976510-22B7-4910-96F2-3E1519C3FF35}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {75976510-22B7-4910-96F2-3E1519C3FF35}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {75976510-22B7-4910-96F2-3E1519C3FF35}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {75976510-22B7-4910-96F2-3E1519C3FF35}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{75976510-22B7-4910-96F2-3E1519C3FF35}.Debug|arm64.ActiveCfg = Debug|ARM64
{75976510-22B7-4910-96F2-3E1519C3FF35}.Debug|arm64.Build.0 = Debug|ARM64
{75976510-22B7-4910-96F2-3E1519C3FF35}.Debug|x64.ActiveCfg = Debug|x64
@@ -515,6 +707,12 @@ Global
{75976510-22B7-4910-96F2-3E1519C3FF35}.Release|x64.Build.0 = Release|x64
{75976510-22B7-4910-96F2-3E1519C3FF35}.Release|x86.ActiveCfg = Release|x86
{75976510-22B7-4910-96F2-3E1519C3FF35}.Release|x86.Build.0 = Release|x86
+ {D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Debug|arm64.ActiveCfg = Debug|arm64
{D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Debug|arm64.Build.0 = Debug|arm64
{D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Debug|x64.ActiveCfg = Debug|x64
@@ -527,6 +725,12 @@ Global
{D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Release|x64.Build.0 = Release|x64
{D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Release|x86.ActiveCfg = Release|x86
{D92BC45D-6D1B-4DE3-9303-4B3ED1971192}.Release|x86.Build.0 = Release|x86
+ {D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Debug|arm64.ActiveCfg = Debug|arm64
{D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Debug|arm64.Build.0 = Debug|arm64
{D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Debug|x64.ActiveCfg = Debug|x64
@@ -539,6 +743,12 @@ Global
{D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Release|x64.Build.0 = Release|x64
{D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Release|x86.ActiveCfg = Release|x86
{D8256951-EB23-45AA-8A0B-4573DF8E26F2}.Release|x86.Build.0 = Release|x86
+ {2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Debug|arm64.ActiveCfg = Debug|arm64
{2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Debug|arm64.Build.0 = Debug|arm64
{2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Debug|x64.ActiveCfg = Debug|x64
@@ -551,6 +761,12 @@ Global
{2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Release|x64.Build.0 = Release|x64
{2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Release|x86.ActiveCfg = Release|x86
{2C7522A3-DCE2-4ED0-889A-AD10F241EDF4}.Release|x86.Build.0 = Release|x86
+ {D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|ARM64
+ {D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|ARM64
+ {D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|Win32
+ {D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Debug_FailFast|x86.Build.0 = Debug_FailFast|Win32
{D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Debug|arm64.ActiveCfg = Debug|ARM64
{D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Debug|arm64.Build.0 = Debug|ARM64
{D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Debug|x64.ActiveCfg = Debug|x64
@@ -563,6 +779,12 @@ Global
{D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Release|x64.Build.0 = Release|x64
{D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Release|x86.ActiveCfg = Release|Win32
{D6B0C16D-858A-4C1B-99CF-D6F4CF5BCD5F}.Release|x86.Build.0 = Release|Win32
+ {AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Debug|arm64.ActiveCfg = Debug|arm64
{AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Debug|arm64.Build.0 = Debug|arm64
{AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Debug|x64.ActiveCfg = Debug|x64
@@ -575,6 +797,12 @@ Global
{AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Release|x64.Build.0 = Release|x64
{AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Release|x86.ActiveCfg = Release|x86
{AC872D0F-2F11-48C4-949C-2464EA1AC66F}.Release|x86.Build.0 = Release|x86
+ {F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Debug|arm64.ActiveCfg = Debug|arm64
{F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Debug|arm64.Build.0 = Debug|arm64
{F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Debug|x64.ActiveCfg = Debug|x64
@@ -587,6 +815,12 @@ Global
{F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Release|x64.Build.0 = Release|x64
{F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Release|x86.ActiveCfg = Release|x86
{F9121D0A-BB3A-4010-A982-CD8B77F47AA2}.Release|x86.Build.0 = Release|x86
+ {F4095FD3-6A3F-490B-966D-E63059612EE6}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {F4095FD3-6A3F-490B-966D-E63059612EE6}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {F4095FD3-6A3F-490B-966D-E63059612EE6}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {F4095FD3-6A3F-490B-966D-E63059612EE6}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {F4095FD3-6A3F-490B-966D-E63059612EE6}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {F4095FD3-6A3F-490B-966D-E63059612EE6}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{F4095FD3-6A3F-490B-966D-E63059612EE6}.Debug|arm64.ActiveCfg = Debug|ARM64
{F4095FD3-6A3F-490B-966D-E63059612EE6}.Debug|arm64.Build.0 = Debug|ARM64
{F4095FD3-6A3F-490B-966D-E63059612EE6}.Debug|x64.ActiveCfg = Debug|x64
@@ -599,6 +833,12 @@ Global
{F4095FD3-6A3F-490B-966D-E63059612EE6}.Release|x64.Build.0 = Release|x64
{F4095FD3-6A3F-490B-966D-E63059612EE6}.Release|x86.ActiveCfg = Release|x86
{F4095FD3-6A3F-490B-966D-E63059612EE6}.Release|x86.Build.0 = Release|x86
+ {0E05A442-BDC7-43D4-A000-F8C986826716}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {0E05A442-BDC7-43D4-A000-F8C986826716}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {0E05A442-BDC7-43D4-A000-F8C986826716}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {0E05A442-BDC7-43D4-A000-F8C986826716}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {0E05A442-BDC7-43D4-A000-F8C986826716}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {0E05A442-BDC7-43D4-A000-F8C986826716}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{0E05A442-BDC7-43D4-A000-F8C986826716}.Debug|arm64.ActiveCfg = Debug|ARM64
{0E05A442-BDC7-43D4-A000-F8C986826716}.Debug|arm64.Build.0 = Debug|ARM64
{0E05A442-BDC7-43D4-A000-F8C986826716}.Debug|x64.ActiveCfg = Debug|x64
@@ -611,6 +851,12 @@ Global
{0E05A442-BDC7-43D4-A000-F8C986826716}.Release|x64.Build.0 = Release|x64
{0E05A442-BDC7-43D4-A000-F8C986826716}.Release|x86.ActiveCfg = Release|x86
{0E05A442-BDC7-43D4-A000-F8C986826716}.Release|x86.Build.0 = Release|x86
+ {D759CD66-494C-4A00-8075-8B65A9891349}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {D759CD66-494C-4A00-8075-8B65A9891349}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {D759CD66-494C-4A00-8075-8B65A9891349}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {D759CD66-494C-4A00-8075-8B65A9891349}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {D759CD66-494C-4A00-8075-8B65A9891349}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {D759CD66-494C-4A00-8075-8B65A9891349}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{D759CD66-494C-4A00-8075-8B65A9891349}.Debug|arm64.ActiveCfg = Debug|arm64
{D759CD66-494C-4A00-8075-8B65A9891349}.Debug|arm64.Build.0 = Debug|arm64
{D759CD66-494C-4A00-8075-8B65A9891349}.Debug|x64.ActiveCfg = Debug|x64
@@ -623,6 +869,12 @@ Global
{D759CD66-494C-4A00-8075-8B65A9891349}.Release|x64.Build.0 = Release|x64
{D759CD66-494C-4A00-8075-8B65A9891349}.Release|x86.ActiveCfg = Release|x86
{D759CD66-494C-4A00-8075-8B65A9891349}.Release|x86.Build.0 = Release|x86
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|ARM64
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|ARM64
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|arm64.ActiveCfg = Debug|ARM64
{CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|arm64.Build.0 = Debug|ARM64
{CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|x64.ActiveCfg = Debug|x64
@@ -635,6 +887,12 @@ Global
{CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|x64.Build.0 = Release|x64
{CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|x86.ActiveCfg = Release|x86
{CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|x86.Build.0 = Release|x86
+ {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug|arm64.ActiveCfg = Debug|arm64
{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug|arm64.Build.0 = Debug|arm64
{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Debug|x64.ActiveCfg = Debug|x64
@@ -647,6 +905,12 @@ Global
{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Release|x64.Build.0 = Release|x64
{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Release|x86.ActiveCfg = Release|x86
{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}.Release|x86.Build.0 = Release|x86
+ {19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Debug|arm64.ActiveCfg = Debug|arm64
{19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Debug|arm64.Build.0 = Debug|arm64
{19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Debug|x64.ActiveCfg = Debug|x64
@@ -659,6 +923,12 @@ Global
{19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Release|x64.Build.0 = Release|x64
{19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Release|x86.ActiveCfg = Release|x86
{19C08C57-7C22-48C9-9B6C-FAAF1FCC65E7}.Release|x86.Build.0 = Release|x86
+ {1317314E-9BDD-4F1C-A76F-22121637A091}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {1317314E-9BDD-4F1C-A76F-22121637A091}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {1317314E-9BDD-4F1C-A76F-22121637A091}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {1317314E-9BDD-4F1C-A76F-22121637A091}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {1317314E-9BDD-4F1C-A76F-22121637A091}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {1317314E-9BDD-4F1C-A76F-22121637A091}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{1317314E-9BDD-4F1C-A76F-22121637A091}.Debug|arm64.ActiveCfg = Debug|ARM64
{1317314E-9BDD-4F1C-A76F-22121637A091}.Debug|arm64.Build.0 = Debug|ARM64
{1317314E-9BDD-4F1C-A76F-22121637A091}.Debug|arm64.Deploy.0 = Debug|ARM64
@@ -677,6 +947,12 @@ Global
{1317314E-9BDD-4F1C-A76F-22121637A091}.Release|x86.ActiveCfg = Release|x86
{1317314E-9BDD-4F1C-A76F-22121637A091}.Release|x86.Build.0 = Release|x86
{1317314E-9BDD-4F1C-A76F-22121637A091}.Release|x86.Deploy.0 = Release|x86
+ {2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Debug|arm64.ActiveCfg = Debug|ARM64
{2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Debug|arm64.Build.0 = Debug|ARM64
{2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Debug|arm64.Deploy.0 = Debug|ARM64
@@ -695,6 +971,12 @@ Global
{2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Release|x86.ActiveCfg = Release|x86
{2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Release|x86.Build.0 = Release|x86
{2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9}.Release|x86.Deploy.0 = Release|x86
+ {5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|arm64
+ {5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|arm64
+ {5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|x86
+ {5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Debug_FailFast|x86.Build.0 = Debug_FailFast|x86
{5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Debug|arm64.ActiveCfg = Debug|arm64
{5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Debug|arm64.Build.0 = Debug|arm64
{5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Debug|x64.ActiveCfg = Debug|x64
@@ -707,6 +989,12 @@ Global
{5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Release|x64.Build.0 = Release|x64
{5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Release|x86.ActiveCfg = Release|x86
{5F9749BC-F34E-4F45-933F-61E0F3ED521F}.Release|x86.Build.0 = Release|x86
+ {8EB52F7D-D216-49FF-BF16-DE06E4695950}.Debug_FailFast|arm64.ActiveCfg = Debug_FailFast|x64
+ {8EB52F7D-D216-49FF-BF16-DE06E4695950}.Debug_FailFast|arm64.Build.0 = Debug_FailFast|x64
+ {8EB52F7D-D216-49FF-BF16-DE06E4695950}.Debug_FailFast|x64.ActiveCfg = Debug_FailFast|x64
+ {8EB52F7D-D216-49FF-BF16-DE06E4695950}.Debug_FailFast|x64.Build.0 = Debug_FailFast|x64
+ {8EB52F7D-D216-49FF-BF16-DE06E4695950}.Debug_FailFast|x86.ActiveCfg = Debug_FailFast|Win32
+ {8EB52F7D-D216-49FF-BF16-DE06E4695950}.Debug_FailFast|x86.Build.0 = Debug_FailFast|Win32
{8EB52F7D-D216-49FF-BF16-DE06E4695950}.Debug|arm64.ActiveCfg = Debug|x64
{8EB52F7D-D216-49FF-BF16-DE06E4695950}.Debug|arm64.Build.0 = Debug|x64
{8EB52F7D-D216-49FF-BF16-DE06E4695950}.Debug|x64.ActiveCfg = Debug|x64
@@ -719,6 +1007,78 @@ Global
{8EB52F7D-D216-49FF-BF16-DE06E4695950}.Release|x64.Build.0 = Release|x64
{8EB52F7D-D216-49FF-BF16-DE06E4695950}.Release|x86.ActiveCfg = Release|Win32
{8EB52F7D-D216-49FF-BF16-DE06E4695950}.Release|x86.Build.0 = Release|Win32
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug_FailFast|arm64.ActiveCfg = Debug|ARM64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug_FailFast|arm64.Build.0 = Debug|ARM64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug_FailFast|x64.ActiveCfg = Debug|x64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug_FailFast|x64.Build.0 = Debug|x64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug_FailFast|x86.ActiveCfg = Debug|x86
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug_FailFast|x86.Build.0 = Debug|x86
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug|arm64.ActiveCfg = Debug|ARM64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug|arm64.Build.0 = Debug|ARM64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug|x64.ActiveCfg = Debug|x64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug|x64.Build.0 = Debug|x64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug|x86.ActiveCfg = Debug|x86
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Debug|x86.Build.0 = Debug|x86
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Release|arm64.ActiveCfg = Release|ARM64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Release|arm64.Build.0 = Release|ARM64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Release|x64.ActiveCfg = Release|x64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Release|x64.Build.0 = Release|x64
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Release|x86.ActiveCfg = Release|x86
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C}.Release|x86.Build.0 = Release|x86
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug_FailFast|arm64.ActiveCfg = Debug|ARM64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug_FailFast|arm64.Build.0 = Debug|ARM64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug_FailFast|x64.ActiveCfg = Debug|x64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug_FailFast|x64.Build.0 = Debug|x64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug_FailFast|x86.ActiveCfg = Debug|x86
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug_FailFast|x86.Build.0 = Debug|x86
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug|arm64.ActiveCfg = Debug|ARM64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug|arm64.Build.0 = Debug|ARM64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug|x64.ActiveCfg = Debug|x64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug|x64.Build.0 = Debug|x64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug|x86.ActiveCfg = Debug|x86
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Debug|x86.Build.0 = Debug|x86
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Release|arm64.ActiveCfg = Release|ARM64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Release|arm64.Build.0 = Release|ARM64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Release|x64.ActiveCfg = Release|x64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Release|x64.Build.0 = Release|x64
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Release|x86.ActiveCfg = Release|x86
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769}.Release|x86.Build.0 = Release|x86
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug_FailFast|arm64.ActiveCfg = Debug|ARM64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug_FailFast|arm64.Build.0 = Debug|ARM64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug_FailFast|x64.ActiveCfg = Debug|x64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug_FailFast|x64.Build.0 = Debug|x64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug_FailFast|x86.ActiveCfg = Debug|x86
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug_FailFast|x86.Build.0 = Debug|x86
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug|arm64.ActiveCfg = Debug|ARM64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug|arm64.Build.0 = Debug|ARM64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug|x64.ActiveCfg = Debug|x64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug|x64.Build.0 = Debug|x64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug|x86.ActiveCfg = Debug|x86
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Debug|x86.Build.0 = Debug|x86
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Release|arm64.ActiveCfg = Release|ARM64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Release|arm64.Build.0 = Release|ARM64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Release|x64.ActiveCfg = Release|x64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Release|x64.Build.0 = Release|x64
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Release|x86.ActiveCfg = Release|x86
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Release|x86.Build.0 = Release|x86
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|arm64.ActiveCfg = Debug|ARM64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|arm64.Build.0 = Debug|ARM64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|x64.ActiveCfg = Debug|x64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|x64.Build.0 = Debug|x64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|x86.ActiveCfg = Debug|x86
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|x86.Build.0 = Debug|x86
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|arm64.ActiveCfg = Debug|ARM64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|arm64.Build.0 = Debug|ARM64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|x64.ActiveCfg = Debug|x64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|x64.Build.0 = Debug|x64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|x86.ActiveCfg = Debug|x86
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|x86.Build.0 = Debug|x86
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|arm64.ActiveCfg = Release|ARM64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|arm64.Build.0 = Release|ARM64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|x64.ActiveCfg = Release|x64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|x64.Build.0 = Release|x64
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|x86.ActiveCfg = Release|x86
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -775,7 +1135,12 @@ Global
{1317314E-9BDD-4F1C-A76F-22121637A091} = {FAB6FAA7-ADF4-4B65-9831-0C819915E6E1}
{2E5629CA-0D1B-42B1-8D6E-934A6E1E18D9} = {FAB6FAA7-ADF4-4B65-9831-0C819915E6E1}
{5F9749BC-F34E-4F45-933F-61E0F3ED521F} = {FAB6FAA7-ADF4-4B65-9831-0C819915E6E1}
+ {DA5C4FC8-18AF-4446-807F-A2FFFE4DCF6C} = {E0A15760-487A-4CCB-8093-DE6FF3C4BC23}
+ {8FB1EF90-B693-4A2A-A7F2-44ECA499D769} = {E0A15760-487A-4CCB-8093-DE6FF3C4BC23}
{E768781A-D1F7-4C03-B46D-E76354FAB587} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD}
+ {D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F} = {E0A15760-487A-4CCB-8093-DE6FF3C4BC23}
+ {73D1E84F-56CC-412B-BF2B-FA692BF6B396} = {DCAF188B-60C3-4EDB-8049-BAA927FBCD7D}
+ {B6153EEA-EADE-4BAA-B47D-6B48205C6696} = {73D1E84F-56CC-412B-BF2B-FA692BF6B396}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {030B5641-B206-46BB-BF71-36FF009088FA}
diff --git a/Directory.Build.props b/Directory.Build.props
index 4f951f1b9f..70daf3ff7f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -40,4 +40,8 @@
+
+ False
+
+
\ No newline at end of file
diff --git a/TestingScenarios.md b/TestingScenarios.md
index c99bcbc31d..e4e4724ac9 100644
--- a/TestingScenarios.md
+++ b/TestingScenarios.md
@@ -13,3 +13,4 @@ These are the testing scenarios that need to be validated before shipping a new
1. [Creating Environment](tools/SetupFlow/DevHome.SetupFlow.UnitTest/TestingScenarios/CreateEnvironment.md)
1. [Setting up an Environment](tools/SetupFlow/DevHome.SetupFlow.UnitTest/TestingScenarios/SetupEnvironment.md)
1. [Quickstart Playground](tools/SetupFlow/DevHome.SetupFlow.UnitTest/TestingScenarios/QuickstartPlayground.md)
+1. [Utilities](tools/Utilities/UtilitiesTestingScenarios.md)
diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index 66ffde2521..7a27e5f7d8 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -21,8 +21,8 @@ parameters:
variables:
# MSIXVersion's second part should always be odd to account for stub app's version
- MSIXVersion: '0.1501'
- VersionOfSDK: '0.600'
+ MSIXVersion: '0.1601'
+ VersionOfSDK: '0.700'
solution: '**/DevHome.sln'
appxPackageDir: 'AppxPackages'
testOutputArtifactDir: 'TestResults'
@@ -81,7 +81,7 @@ extends:
symbolProject: 'DevHome'
indexSources: false
symbolsArtifactName: 'DevHomeSDK'
- symbolsVersion: '$(Build.BuildNumber)'
+ symbolsVersion: '$(BuildingBranch)$(Build.BuildNumber)'
searchPattern: |
**/bin/**/*.pdb
**/bin/**/*.exe
@@ -225,7 +225,7 @@ extends:
subscription: $(SymbolSubscription)
indexSources: true
symbolsArtifactName: 'DevHome_${{ platform }}_${{ configuration }}'
- symbolsVersion: $(Build.BuildNumber)
+ symbolsVersion: '$(BuildingBranch)$(Build.BuildNumber)'
searchPattern: >-
$(Build.SourcesDirectory)\**\bin\**\*.pdb
diff --git a/build/scripts/CreateBuildInfo.ps1 b/build/scripts/CreateBuildInfo.ps1
index 232711ad55..84342e5581 100644
--- a/build/scripts/CreateBuildInfo.ps1
+++ b/build/scripts/CreateBuildInfo.ps1
@@ -6,7 +6,7 @@ Param(
)
$Major = "0"
-$Minor = "15"
+$Minor = "16"
$Patch = "99" # default to 99 for local builds
$versionSplit = $Version.Split(".");
diff --git a/codeAnalysis/GlobalSuppressions.cs b/codeAnalysis/GlobalSuppressions.cs
index 19eeee6190..40d58d8500 100644
--- a/codeAnalysis/GlobalSuppressions.cs
+++ b/codeAnalysis/GlobalSuppressions.cs
@@ -55,3 +55,7 @@
// Code quality
[assembly: SuppressMessage("CodeQuality", "IDE0076:Invalid global 'SuppressMessageAttribute'", Justification = "Affect predefined suppressions.")]
+
+// Logger
+[assembly: SuppressMessage("Performance", "CA1848:Use the LoggerMessage delegates", Justification = "Allow using LoggerExtensions methods")]
+[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "Allow string concatenation or interpolation")]
diff --git a/common/.gitattributes b/common/.gitattributes
deleted file mode 100644
index c210a8dc46..0000000000
--- a/common/.gitattributes
+++ /dev/null
@@ -1,3 +0,0 @@
-# Set default behavior to automatically normalize line endings.
-
-* text=crlf
diff --git a/common/Contracts/IDevHomeActionSetRender.cs b/common/Contracts/IDevHomeActionSetRender.cs
index ee7424123c..9ce212afae 100644
--- a/common/Contracts/IDevHomeActionSetRender.cs
+++ b/common/Contracts/IDevHomeActionSetRender.cs
@@ -7,7 +7,7 @@ namespace DevHome.Common.Contracts;
///
/// Represents a renderer for an adaptive card action set that Dev Home can use to invoke actions from within
-/// the action set. This is useful for invoking an adaptive card action from an abitraty button within Dev Home's UI.
+/// the action set. This is useful for invoking an adaptive card action from an arbitrary button within Dev Home's UI.
///
public interface IDevHomeActionSetRender : IAdaptiveElementRenderer
{
diff --git a/common/DevHome.Common.csproj b/common/DevHome.Common.csproj
index d730ac47e8..b3865c90b5 100644
--- a/common/DevHome.Common.csproj
+++ b/common/DevHome.Common.csproj
@@ -7,6 +7,7 @@
enable
true
$(DevHomeSDKVersion)
+ Debug;Release;Debug_FailFast
@@ -38,8 +39,8 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -59,7 +60,7 @@
-
+
diff --git a/common/Environments/Exceptions/CreateCreateComputeSystemOperationException.cs b/common/Environments/Exceptions/CreateCreateComputeSystemOperationException.cs
index 9a2597ccbb..55af999534 100644
--- a/common/Environments/Exceptions/CreateCreateComputeSystemOperationException.cs
+++ b/common/Environments/Exceptions/CreateCreateComputeSystemOperationException.cs
@@ -2,10 +2,6 @@
// Licensed under the MIT License.
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace DevHome.Common.Environments.Exceptions;
diff --git a/common/Environments/Helpers/ComputeSystemHelpers.cs b/common/Environments/Helpers/ComputeSystemHelpers.cs
index ec305bbfee..db644b54fa 100644
--- a/common/Environments/Helpers/ComputeSystemHelpers.cs
+++ b/common/Environments/Helpers/ComputeSystemHelpers.cs
@@ -9,7 +9,6 @@
using System.Threading.Tasks;
using DevHome.Common.Environments.Models;
using DevHome.Common.TelemetryEvents.SetupFlow.Environments;
-using Microsoft.Extensions.Hosting;
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
diff --git a/common/Environments/Models/ComputeSystem.cs b/common/Environments/Models/ComputeSystem.cs
index 5d3f6722b0..4280cb9bac 100644
--- a/common/Environments/Models/ComputeSystem.cs
+++ b/common/Environments/Models/ComputeSystem.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
@@ -13,7 +12,6 @@
using Microsoft.Windows.DevHome.SDK;
using Serilog;
using Windows.Foundation;
-using Windows.Win32;
using WinRT;
namespace DevHome.Common.Environments.Models;
@@ -27,7 +25,7 @@ public class ComputeSystem
{
private readonly ILogger _log = Log.ForContext("SourceContext", nameof(ComputeSystem));
- private readonly string errorString;
+ private readonly string _errorString;
private readonly IComputeSystem _computeSystem;
@@ -66,7 +64,7 @@ public ComputeSystem(IComputeSystem computeSystem)
AssociatedDeveloperId = computeSystem.AssociatedDeveloperId;
AssociatedProviderId = new string(computeSystem.AssociatedProviderId);
_computeSystem.StateChanged += OnComputeSystemStateChanged;
- errorString = StringResourceHelper.GetResource("ComputeSystemUnexpectedError", DisplayName);
+ _errorString = StringResourceHelper.GetResource("ComputeSystemUnexpectedError", DisplayName);
}
public event TypedEventHandler StateChanged = (sender, state) => { };
@@ -93,7 +91,7 @@ public async Task GetStateAsync()
catch (Exception ex)
{
_log.Error(ex, $"GetStateAsync for: {this} failed due to exception");
- return new ComputeSystemStateResult(ex, errorString, ex.Message);
+ return new ComputeSystemStateResult(ex, _errorString, ex.Message);
}
}
@@ -106,7 +104,7 @@ public async Task StartAsync(string options)
catch (Exception ex)
{
_log.Error(ex, $"StartAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -119,7 +117,7 @@ public async Task ShutDownAsync(string options)
catch (Exception ex)
{
_log.Error(ex, $"ShutDownAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -132,7 +130,7 @@ public async Task RestartAsync(string options)
catch (Exception ex)
{
_log.Error(ex, $"RestartAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -145,7 +143,7 @@ public async Task TerminateAsync(string options)
catch (Exception ex)
{
_log.Error(ex, $"TerminateAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -158,7 +156,7 @@ public async Task DeleteAsync(string options)
catch (Exception ex)
{
_log.Error(ex, $"DeleteAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -171,7 +169,7 @@ public async Task SaveAsync(string options)
catch (Exception ex)
{
_log.Error(ex, $"SaveAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -184,7 +182,7 @@ public async Task PauseAsync(string options)
catch (Exception ex)
{
_log.Error(ex, $"PauseAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -197,7 +195,7 @@ public async Task ResumeAsync(string options)
catch (Exception ex)
{
_log.Error(ex, $"ResumeAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -210,7 +208,7 @@ public async Task CreateSnapshotAsync(string optio
catch (Exception ex)
{
_log.Error(ex, $"CreateSnapshotAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -223,7 +221,7 @@ public async Task RevertSnapshotAsync(string optio
catch (Exception ex)
{
_log.Error(ex, $"RevertSnapshotAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -236,7 +234,7 @@ public async Task DeleteSnapshotAsync(string optio
catch (Exception ex)
{
_log.Error(ex, $"DeleteSnapshotAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -249,7 +247,7 @@ public async Task ModifyPropertiesAsync(string opt
catch (Exception ex)
{
_log.Error(ex, $"ModifyPropertiesAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -262,7 +260,7 @@ public async Task GetComputeSystemThumbnailAsync(s
catch (Exception ex)
{
_log.Error(ex, $"GetComputeSystemThumbnailAsync for: {this} failed due to exception");
- return new ComputeSystemThumbnailResult(ex, errorString, ex.Message);
+ return new ComputeSystemThumbnailResult(ex, _errorString, ex.Message);
}
}
@@ -304,7 +302,7 @@ public async Task ConnectAsync(string options)
catch (Exception ex)
{
_log.Error(ex, $"ConnectAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -323,7 +321,7 @@ public async Task PinToStartMenuAsync(string optio
catch (Exception ex)
{
_log.Error(ex, $"PinToStartMenuAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -342,7 +340,7 @@ public async Task UnpinFromStartMenuAsync(string o
catch (Exception ex)
{
_log.Error(ex, $"UnpinFromStartMenuAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -361,7 +359,7 @@ public async Task PinToTaskbarAsync(string options
catch (Exception ex)
{
_log.Error(ex, $"PinToTaskbarAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -380,7 +378,7 @@ public async Task UnpinFromTaskbarAsync(string opt
catch (Exception ex)
{
_log.Error(ex, $"UnpinFromTaskbarAsync for: {this} failed due to exception");
- return new ComputeSystemOperationResult(ex, errorString, ex.Message);
+ return new ComputeSystemOperationResult(ex, _errorString, ex.Message);
}
}
@@ -399,7 +397,7 @@ public async Task GetIsPinnedToStartMenuAsync()
catch (Exception ex)
{
_log.Error(ex, $"GetIsPinnedToStartMenuAsync for: {this} failed due to exception");
- return new ComputeSystemPinnedResult(ex, errorString, ex.Message);
+ return new ComputeSystemPinnedResult(ex, _errorString, ex.Message);
}
}
@@ -418,7 +416,7 @@ public async Task GetIsPinnedToTaskbarAsync()
catch (Exception ex)
{
_log.Error(ex, $"GetIsPinnedToTaskbarAsync for: {this} failed due to exception");
- return new ComputeSystemPinnedResult(ex, errorString, ex.Message);
+ return new ComputeSystemPinnedResult(ex, _errorString, ex.Message);
}
}
diff --git a/common/Environments/Models/ComputeSystemProvider.cs b/common/Environments/Models/ComputeSystemProvider.cs
index ad64203245..3f8e5f9cdc 100644
--- a/common/Environments/Models/ComputeSystemProvider.cs
+++ b/common/Environments/Models/ComputeSystemProvider.cs
@@ -22,7 +22,7 @@ public class ComputeSystemProvider
{
private readonly ILogger _log = Log.ForContext("SourceContext", nameof(ComputeSystemProvider));
- private readonly string errorString;
+ private readonly string _errorString;
private readonly IComputeSystemProvider _computeSystemProvider;
@@ -41,7 +41,7 @@ public ComputeSystemProvider(IComputeSystemProvider computeSystemProvider)
DisplayName = computeSystemProvider.DisplayName;
SupportedOperations = computeSystemProvider.SupportedOperations;
Icon = computeSystemProvider.Icon;
- errorString = StringResourceHelper.GetResource("ComputeSystemUnexpectedError", DisplayName);
+ _errorString = StringResourceHelper.GetResource("ComputeSystemUnexpectedError", DisplayName);
}
public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForDeveloperId(IDeveloperId developerId, ComputeSystemAdaptiveCardKind sessionKind)
@@ -53,7 +53,7 @@ public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForDeveloperId(I
catch (Exception ex)
{
_log.Error(ex, $"CreateAdaptiveCardSessionForDeveloperId for: {this} failed due to exception");
- return new ComputeSystemAdaptiveCardResult(ex, errorString, ex.Message);
+ return new ComputeSystemAdaptiveCardResult(ex, _errorString, ex.Message);
}
}
@@ -66,7 +66,7 @@ public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForComputeSystem
catch (Exception ex)
{
_log.Error(ex, $"CreateAdaptiveCardSessionForComputeSystem for: {this} failed due to exception");
- return new ComputeSystemAdaptiveCardResult(ex, errorString, ex.Message);
+ return new ComputeSystemAdaptiveCardResult(ex, _errorString, ex.Message);
}
}
@@ -79,7 +79,7 @@ public async Task GetComputeSystemsAsync(IDeveloperId deve
catch (Exception ex)
{
_log.Error(ex, $"GetComputeSystemsAsync for: {this} failed due to exception");
- return new ComputeSystemsResult(ex, errorString, ex.Message);
+ return new ComputeSystemsResult(ex, _errorString, ex.Message);
}
}
diff --git a/common/Environments/Scripts/HyperVSetupScript.cs b/common/Environments/Scripts/HyperVSetupScript.cs
index 6274e083c6..01348c7909 100644
--- a/common/Environments/Scripts/HyperVSetupScript.cs
+++ b/common/Environments/Scripts/HyperVSetupScript.cs
@@ -1,12 +1,6 @@
// 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 DevHome.Common.Environments.Scripts;
public static class HyperVSetupScript
@@ -41,11 +35,12 @@ 6. The user is already in the Hyper-V Admin group and the Hyper-V Feature is alr
{
$featureEnablementResult = [OperationStatus]::OperationNotRun
$adminGroupResult = [OperationStatus]::OperationNotRun
+ $hyperVGroupSid = 'S-1-5-32-578'
# Check the security token the user logged on with contains the Hyper-V Administrators group SID (S-1-5-32-578). This can only be updated,
# once the user logs off and on again. Even if we add the user to the group later on in the script.
- $foundSecurityTokenString = [System.Security.Principal.WindowsIdentity]::GetCurrent().Groups.Value | Where-Object { $_ -eq 'S-1-5-32-578' }
- $doesUserSecurityTokenContainHyperAdminGroup = $foundSecurityTokenString -eq 'S-1-5-32-578'
+ $foundSecurityTokenString = [System.Security.Principal.WindowsIdentity]::GetCurrent().Groups.Value | Where-Object { $_ -eq $hyperVGroupSid }
+ $doesUserSecurityTokenContainHyperAdminGroup = $foundSecurityTokenString -eq $hyperVGroupSid
# Check if the Hyper-V feature is enabled
$featureState = Get-WindowsOptionalFeature -FeatureName 'Microsoft-Hyper-V' -Online | Select-Object -ExpandProperty State
@@ -76,13 +71,13 @@ exit 6
}
# Check the Hyper-V Administrators group to see if the user is inside the group
- $userGroupObject = Get-LocalGroupMember -Group 'Hyper-V Administrators' | Where-Object { $_.Name -eq ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) }
+ $userGroupObject = Get-LocalGroupMember -Sid $hyperVGroupSid | Where-Object { $_.Name -eq ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) }
$isUserInGroup = $null -ne $userGroupObject
# Add user to Hyper-v Administrators group if they aren't already in the group
if (-not $isUserInGroup)
{
- Add-LocalGroupMember -Group 'Hyper-V Administrators' -Member ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
+ Add-LocalGroupMember -Sid $hyperVGroupSid -Member ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
# Check if the last command succeeded
if ($?)
diff --git a/common/Environments/Services/ComputeSystemManager.cs b/common/Environments/Services/ComputeSystemManager.cs
index 7643daea17..6428015c94 100644
--- a/common/Environments/Services/ComputeSystemManager.cs
+++ b/common/Environments/Services/ComputeSystemManager.cs
@@ -9,7 +9,6 @@
using DevHome.Common.Contracts.Services;
using DevHome.Common.Environments.Models;
using DevHome.Common.Models;
-using DevHome.Common.Services;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
using Windows.Foundation;
@@ -17,7 +16,7 @@
namespace DevHome.Common.Environments.Services;
///
-/// Service thats used to get the ComputeSystems from the providers so they can be loaded into the UI.
+/// Service that's used to get the ComputeSystems from the providers so they can be loaded into the UI.
/// This class is also used to keep track of the ComputeSystem that a configuration file will be applied to.
///
public class ComputeSystemManager : IComputeSystemManager
@@ -33,7 +32,7 @@ public class ComputeSystemManager : IComputeSystemManager
private readonly object _creationOperationLock = new();
// Used in the setup flow to store the ComputeSystem needed to configure.
- public ComputeSystemReviewItem? ComputeSystemSetupItem { get; set; }
+ public ComputeSystemReviewItem? ComputeSystemSetupItem { get; set; }
public ComputeSystemManager(IComputeSystemService computeSystemService)
{
@@ -101,7 +100,7 @@ public List GetRunningOperationsForCreation()
{
lock (_creationOperationLock)
{
- return _createComputeSystemOperations.Values.ToList();
+ return _createComputeSystemOperations.Values.ToList();
}
}
@@ -117,7 +116,7 @@ public void RemoveOperation(CreateComputeSystemOperation operation)
{
lock (_creationOperationLock)
{
- _createComputeSystemOperations.Remove(operation.OperationId);
+ _createComputeSystemOperations.Remove(operation.OperationId);
}
}
diff --git a/common/Environments/Styles/HorizontalCardStyles.xaml b/common/Environments/Styles/HorizontalCardStyles.xaml
index 7a9e98c2b8..db6109d5e2 100644
--- a/common/Environments/Styles/HorizontalCardStyles.xaml
+++ b/common/Environments/Styles/HorizontalCardStyles.xaml
@@ -238,4 +238,12 @@
+
diff --git a/common/Helpers/AdaptiveCardHelpers.cs b/common/Helpers/AdaptiveCardHelpers.cs
index 2332714e8d..c17c360e4d 100644
--- a/common/Helpers/AdaptiveCardHelpers.cs
+++ b/common/Helpers/AdaptiveCardHelpers.cs
@@ -3,11 +3,6 @@
using System;
using System.IO;
-using System.Text;
-using System.Text.Json;
-using System.Threading.Tasks;
-using DevHome.Common.Helpers;
-using DevHome.Common.Models;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using Serilog;
diff --git a/common/Helpers/CommonConstants.cs b/common/Helpers/CommonConstants.cs
index 879748a45c..e1238da23e 100644
--- a/common/Helpers/CommonConstants.cs
+++ b/common/Helpers/CommonConstants.cs
@@ -1,17 +1,13 @@
// 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 DevHome.Common.Helpers;
public static class CommonConstants
{
- public const string HyperVExtensionClassId = "F8B26528-976A-488C-9B40-7198FB425C9E";
+ public const string HyperVExtensionClassId = "F8B26528-976A-488C-9B40-7198FB425C9E";
+
+ public const string WSLExtensionClassId = "121253AB-BA5D-4E73-99CF-25A2CB8BF173";
public const string HyperVWindowsOptionalFeatureName = "Microsoft-Hyper-V";
}
diff --git a/common/Helpers/Deployment.cs b/common/Helpers/Deployment.cs
index c25cb02e3f..36c79791f6 100644
--- a/common/Helpers/Deployment.cs
+++ b/common/Helpers/Deployment.cs
@@ -9,7 +9,7 @@ namespace DevHome.Common.Helpers;
public static class Deployment
{
- private static readonly string DeploymentIdentifierKeyName = "DevHomeDeploymentIdentifier";
+ private static readonly string _deploymentIdentifierKeyName = "DevHomeDeploymentIdentifier";
// This creates and returns a Guid associated with this deployment of DevHome. This uniquely
// identifies this deployment across multiple launches and will be different per Windows user.
@@ -23,18 +23,18 @@ public static Guid Identifier
try
{
var localSettings = ApplicationData.Current.LocalSettings;
- if (localSettings.Values.TryGetValue(DeploymentIdentifierKeyName, out var value))
+ if (localSettings.Values.TryGetValue(_deploymentIdentifierKeyName, out var value))
{
return (Guid)value;
}
var newGuid = Guid.NewGuid();
- localSettings.Values[DeploymentIdentifierKeyName] = newGuid;
+ localSettings.Values[_deploymentIdentifierKeyName] = newGuid;
return newGuid;
}
catch (Exception ex)
{
- // We do not want this identifer's access to ever create a problem in the
+ // We do not want this identifier'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(ex, $"Failed getting Deployment Identifier");
diff --git a/common/Helpers/ManagementInfrastructureHelper.cs b/common/Helpers/ManagementInfrastructureHelper.cs
index 5e734def24..39f20143a4 100644
--- a/common/Helpers/ManagementInfrastructureHelper.cs
+++ b/common/Helpers/ManagementInfrastructureHelper.cs
@@ -2,9 +2,9 @@
// Licensed under the MIT License.
using System;
-using System.Globalization;
using Microsoft.Management.Infrastructure;
using Serilog;
+using static DevHome.Common.Helpers.WindowsOptionalFeatures;
namespace DevHome.Common.Helpers;
@@ -23,6 +23,11 @@ public static class ManagementInfrastructureHelper
private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(ManagementInfrastructureHelper));
public static FeatureAvailabilityKind IsWindowsFeatureAvailable(string featureName)
+ {
+ return GetWindowsFeatureDetails(featureName)?.AvailabilityKind ?? FeatureAvailabilityKind.Unknown;
+ }
+
+ public static FeatureInfo? GetWindowsFeatureDetails(string featureName)
{
try
{
@@ -37,7 +42,19 @@ public static FeatureAvailabilityKind IsWindowsFeatureAvailable(string featureNa
var featureAvailability = GetAvailabilityKindFromState(installState);
_log.Information($"Found feature: '{featureName}' with enablement state: '{featureAvailability}'");
- return featureAvailability;
+
+ // Most optional features do not have a description, so we provide one for known features
+ var description = featureInstance.CimInstanceProperties["Description"]?.Value as string ?? string.Empty;
+ if (string.IsNullOrEmpty(description) && WindowsOptionalFeatures.FeatureDescriptions.TryGetValue(featureName, out var featureDescription))
+ {
+ description = featureDescription;
+ }
+
+ return new FeatureInfo(
+ featureName,
+ featureInstance.CimInstanceProperties["Caption"]?.Value as string ?? featureName,
+ description,
+ featureAvailability);
}
}
}
@@ -47,7 +64,7 @@ public static FeatureAvailabilityKind IsWindowsFeatureAvailable(string featureNa
}
_log.Information($"Unable to get state of {featureName} feature");
- return FeatureAvailabilityKind.Unknown;
+ return null;
}
private static FeatureAvailabilityKind GetAvailabilityKindFromState(uint state)
diff --git a/common/Helpers/RestartHelper.cs b/common/Helpers/RestartHelper.cs
new file mode 100644
index 0000000000..ac5fb7d5f5
--- /dev/null
+++ b/common/Helpers/RestartHelper.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using CommunityToolkit.Mvvm.Input;
+
+namespace DevHome.Common.Helpers;
+
+public partial class RestartHelper
+{
+ [RelayCommand]
+ public static void RestartComputer()
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ WindowStyle = ProcessWindowStyle.Hidden,
+ FileName = Environment.SystemDirectory + "\\shutdown.exe",
+ Arguments = "-r -t 0",
+ Verb = string.Empty,
+ };
+
+ var process = new Process
+ {
+ StartInfo = startInfo,
+ };
+ process.Start();
+ }
+}
diff --git a/common/Helpers/WindowsIdentityHelper.cs b/common/Helpers/WindowsIdentityHelper.cs
index 1c6a76b646..13be26cb7e 100644
--- a/common/Helpers/WindowsIdentityHelper.cs
+++ b/common/Helpers/WindowsIdentityHelper.cs
@@ -26,4 +26,14 @@ public virtual bool IsUserHyperVAdmin()
{
return _currentUserIdentity?.Name;
}
+
+ // Returns true if the current user has the built-in Administrators claim indicating that
+ // they could elevate to an administrator role via UAC if needed. Does not check if the process
+ // is running elevated.
+ public virtual bool IsUserAdministrator()
+ {
+ using WindowsIdentity identity = WindowsIdentity.GetCurrent();
+ var adminSid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null).Value;
+ return identity.Claims.Any(c => c.Value == adminSid);
+ }
}
diff --git a/common/Helpers/WindowsOptionalFeatures.cs b/common/Helpers/WindowsOptionalFeatures.cs
new file mode 100644
index 0000000000..aa75858124
--- /dev/null
+++ b/common/Helpers/WindowsOptionalFeatures.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+using DevHome.Common.Environments.Helpers;
+
+namespace DevHome.Common.Helpers;
+
+public static class WindowsOptionalFeatures
+{
+ // If changes are made to the set of features here, consider updating ModifyWindowsOptionalFeatures' valid feature set.
+ public const string Containers = "Containers";
+ public const string GuardedHost = "HostGuardian";
+ public const string HyperV = "Microsoft-Hyper-V-All";
+ public const string HyperVManagementTools = "Microsoft-Hyper-V-Tools-All";
+ public const string HyperVPlatform = "Microsoft-Hyper-V";
+ public const string VirtualMachinePlatform = "VirtualMachinePlatform";
+ public const string WindowsHypervisorPlatform = "HypervisorPlatform";
+ public const string WindowsSandbox = "Containers-DisposableClientVM";
+ public const string WindowsSubsystemForLinux = "Microsoft-Windows-Subsystem-Linux";
+
+ public static IEnumerable VirtualMachineFeatures => new[]
+ {
+ Containers,
+ GuardedHost,
+ HyperV,
+ HyperVManagementTools,
+ HyperVPlatform,
+ VirtualMachinePlatform,
+ WindowsHypervisorPlatform,
+ WindowsSandbox,
+ WindowsSubsystemForLinux,
+ };
+
+ public static readonly Dictionary FeatureDescriptions = new()
+ {
+ { Containers, GetFeatureDescription(nameof(Containers)) },
+ { GuardedHost, GetFeatureDescription(nameof(GuardedHost)) },
+ { HyperV, GetFeatureDescription(nameof(HyperV)) },
+ { HyperVManagementTools, GetFeatureDescription(nameof(HyperVManagementTools)) },
+ { HyperVPlatform, GetFeatureDescription(nameof(HyperVPlatform)) },
+ { VirtualMachinePlatform, GetFeatureDescription(nameof(VirtualMachinePlatform)) },
+ { WindowsHypervisorPlatform, GetFeatureDescription(nameof(WindowsHypervisorPlatform)) },
+ { WindowsSandbox, GetFeatureDescription(nameof(WindowsSandbox)) },
+ { WindowsSubsystemForLinux, GetFeatureDescription(nameof(WindowsSubsystemForLinux)) },
+ };
+
+ private static string GetFeatureDescription(string featureName)
+ {
+ return StringResourceHelper.GetResource(featureName + "Description");
+ }
+
+ public class FeatureInfo
+ {
+ public string FeatureName { get; set; }
+
+ public string DisplayName { get; set; }
+
+ public string Description { get; set; }
+
+ public bool IsEnabled { get; set; }
+
+ public bool IsAvailable { get; set; }
+
+ public FeatureAvailabilityKind AvailabilityKind { get; set; }
+
+ public FeatureInfo(string featureName, string displayName, string description, FeatureAvailabilityKind availabilityKind)
+ {
+ FeatureName = featureName;
+ DisplayName = displayName;
+ Description = description;
+ AvailabilityKind = availabilityKind;
+ IsEnabled = AvailabilityKind == FeatureAvailabilityKind.Enabled;
+ IsAvailable = AvailabilityKind == FeatureAvailabilityKind.Enabled || AvailabilityKind == FeatureAvailabilityKind.Disabled;
+ }
+ }
+}
diff --git a/common/Models/ExtensionAdaptiveCardSession.cs b/common/Models/ExtensionAdaptiveCardSession.cs
index 089308e6fb..1016b80073 100644
--- a/common/Models/ExtensionAdaptiveCardSession.cs
+++ b/common/Models/ExtensionAdaptiveCardSession.cs
@@ -2,11 +2,7 @@
// Licensed under the MIT License.
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using System.Threading.Tasks;
-using DevHome.Common.Helpers;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
@@ -61,7 +57,7 @@ public void Dispose()
}
catch (Exception ex)
{
- _log.Error(ex, $"Dispose failed due to exception");
+ _log.Error(ex, $"Dispose failed due to exception");
}
}
@@ -73,8 +69,8 @@ public async Task OnAction(string action, string inputs
}
catch (Exception ex)
{
- _log.Error(ex, $"OnAction failed due to exception");
- return new ProviderOperationResult(ProviderOperationStatus.Failure, ex, ex.Message, ex.Message);
+ _log.Error(ex, $"OnAction failed due to exception");
+ return new ProviderOperationResult(ProviderOperationStatus.Failure, ex, ex.Message, ex.Message);
}
}
diff --git a/common/Models/WindowsOptionalFeatureState.cs b/common/Models/WindowsOptionalFeatureState.cs
new file mode 100644
index 0000000000..cd9ef0bb36
--- /dev/null
+++ b/common/Models/WindowsOptionalFeatureState.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using CommunityToolkit.Mvvm.ComponentModel;
+using static DevHome.Common.Helpers.WindowsOptionalFeatures;
+
+namespace DevHome.Common.Models;
+
+public partial class WindowsOptionalFeatureState : ObservableObject
+{
+ public FeatureInfo Feature { get; }
+
+ [ObservableProperty]
+ private bool _isModifiable;
+
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(HasChanged))]
+ private bool _isEnabled;
+
+ public bool HasChanged => IsEnabled != Feature.IsEnabled;
+
+ public WindowsOptionalFeatureState(FeatureInfo feature, bool modifiable)
+ {
+ Feature = feature;
+ IsEnabled = feature.IsEnabled;
+ IsModifiable = modifiable;
+ }
+}
diff --git a/common/Renderers/ChooseFileAction.cs b/common/Renderers/ChooseFileAction.cs
index 9abc3dbe41..1354a3cde9 100644
--- a/common/Renderers/ChooseFileAction.cs
+++ b/common/Renderers/ChooseFileAction.cs
@@ -12,7 +12,6 @@
using Windows.Data.Json;
using Windows.Storage.Pickers;
using WinRT.Interop;
-using WinUIEx;
namespace DevHome.Common.Renderers;
diff --git a/common/Renderers/DevHomeChoiceSetWithDynamicRefresh.cs b/common/Renderers/DevHomeChoiceSetWithDynamicRefresh.cs
index afdb62137a..f320e84802 100644
--- a/common/Renderers/DevHomeChoiceSetWithDynamicRefresh.cs
+++ b/common/Renderers/DevHomeChoiceSetWithDynamicRefresh.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System;
using System.Collections.Generic;
using System.Text.Json;
using AdaptiveCards.ObjectModel.WinUI3;
diff --git a/common/Renderers/TextInputRenderer.cs b/common/Renderers/TextInputRenderer.cs
index 0352c49faf..6312534ce9 100644
--- a/common/Renderers/TextInputRenderer.cs
+++ b/common/Renderers/TextInputRenderer.cs
@@ -1,12 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Linq;
using AdaptiveCards.ObjectModel.WinUI3;
using AdaptiveCards.Rendering.WinUI3;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
namespace DevHome.Common.Renderers;
diff --git a/common/Scripts/ModifyLongPathsSetting.cs b/common/Scripts/ModifyLongPathsSetting.cs
new file mode 100644
index 0000000000..b09d9405cf
--- /dev/null
+++ b/common/Scripts/ModifyLongPathsSetting.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using Serilog;
+
+namespace DevHome.Common.Scripts;
+
+public static class ModifyLongPathsSetting
+{
+ public static ExitCode ModifyLongPaths(bool enabled, ILogger? log = null)
+ {
+ var scriptString = enabled ? EnableScript : DisableScript;
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ WindowStyle = ProcessWindowStyle.Hidden,
+ FileName = "powershell.exe",
+ Arguments = $"-ExecutionPolicy Bypass -Command {scriptString}",
+ UseShellExecute = true,
+ Verb = "runas",
+ },
+ };
+
+ try
+ {
+ process.Start();
+ process.WaitForExit();
+
+ return FromExitCode(process.ExitCode);
+ }
+ catch (Exception ex)
+ {
+ log?.Error(ex, "Failed to modify Long Paths setting");
+ return ExitCode.Failure;
+ }
+ }
+
+ public enum ExitCode
+ {
+ Success = 0,
+ Failure = 1,
+ }
+
+ private static ExitCode FromExitCode(int exitCode)
+ {
+ return exitCode switch
+ {
+ 0 => ExitCode.Success,
+ _ => ExitCode.Failure,
+ };
+ }
+
+ private const string EnableScript =
+@"
+$ErrorActionPreference='stop'
+Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
+if ($?) { exit 0 } else { exit 1 }
+";
+
+ private const string DisableScript =
+@"
+$ErrorActionPreference='stop'
+Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 0
+if ($?) { exit 0 } else { exit 1 }
+";
+}
diff --git a/common/Scripts/ModifyWindowsOptionalFeatures.cs b/common/Scripts/ModifyWindowsOptionalFeatures.cs
new file mode 100644
index 0000000000..dd0c340476
--- /dev/null
+++ b/common/Scripts/ModifyWindowsOptionalFeatures.cs
@@ -0,0 +1,220 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using DevHome.Common.Models;
+using DevHome.Common.TelemetryEvents;
+using Serilog;
+
+namespace DevHome.Common.Scripts;
+
+public static class ModifyWindowsOptionalFeatures
+{
+ public static async Task ModifyFeaturesAsync(
+ IEnumerable features,
+ ILogger? log = null,
+ CancellationToken cancellationToken = default)
+ {
+ if (!features.Any(f => f.HasChanged))
+ {
+ return ExitCode.Success;
+ }
+
+ // Format the argument for the PowerShell script using `n as a newline character since the list
+ // will be parsed with ConvertFrom-StringData.
+ // The format is FeatureName1=True|False`nFeatureName2=True|False`n...
+ var featuresString = string.Empty;
+ foreach (var featureState in features)
+ {
+ if (featureState.HasChanged)
+ {
+ featuresString += $"{featureState.Feature.FeatureName}={featureState.IsEnabled}`n";
+ }
+ }
+
+ var scriptString = Script.Replace("FEATURE_STRING_INPUT", featuresString);
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ WindowStyle = ProcessWindowStyle.Hidden,
+ FileName = "powershell.exe",
+ Arguments = $"-ExecutionPolicy Bypass -Command {scriptString}",
+ UseShellExecute = true,
+ Verb = "runas",
+ },
+ };
+
+ var exitCode = ExitCode.Failure;
+
+ Stopwatch stopwatch = Stopwatch.StartNew();
+
+ await Task.Run(
+ () =>
+ {
+ // Since a UAC prompt will be shown, we need to wait for the process to exit
+ // This can also be cancelled by the user which will result in an exception,
+ // which is handled as a failure.
+ try
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ log?.Information("Operation was cancelled.");
+ exitCode = ExitCode.Cancelled;
+ return;
+ }
+
+ process.Start();
+ while (!process.WaitForExit(1000))
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ // Attempt to kill the process if cancellation is requested
+ exitCode = ExitCode.Cancelled;
+ process.Kill();
+ log?.Information("Operation was cancelled.");
+ return;
+ }
+ }
+
+ exitCode = FromExitCode(process.ExitCode);
+ }
+ catch (Exception ex)
+ {
+ // This is most likely a case where the user cancelled the UAC prompt.
+ if (ex is System.ComponentModel.Win32Exception win32Exception)
+ {
+ if (win32Exception.NativeErrorCode == 1223)
+ {
+ log?.Information(ex, "UAC was cancelled by the user.");
+ exitCode = ExitCode.Cancelled;
+ }
+ }
+ else
+ {
+ log?.Error(ex, "Script failed");
+ exitCode = ExitCode.Failure;
+ }
+ }
+ },
+ cancellationToken);
+
+ stopwatch.Stop();
+
+ ModifyWindowsOptionalFeaturesEvent.Log(
+ featuresString,
+ exitCode,
+ stopwatch.ElapsedMilliseconds);
+
+ return exitCode;
+ }
+
+ public enum ExitCode
+ {
+ Success = 0,
+ Failure = 1,
+ Cancelled = 2,
+ }
+
+ private static ExitCode FromExitCode(int exitCode)
+ {
+ return exitCode switch
+ {
+ 0 => ExitCode.Success,
+ 1 => ExitCode.Failure,
+ _ => ExitCode.Cancelled,
+ };
+ }
+
+ public static string GetExitCodeDescription(ExitCode exitCode)
+ {
+ return exitCode switch
+ {
+ ExitCode.Success => "Success",
+ ExitCode.Failure => "Failure",
+ _ => "Cancelled",
+ };
+ }
+
+ ///
+ /// PowerShell script for modifying Windows optional features.
+ ///
+ /// This script takes a string argument representing feature names and their desired states (enabled or disabled).
+ /// It parses this string into a dictionary, iterates over each feature, and performs the necessary enable or disable operation based on the desired state.
+ ///
+ /// The script defines the following possible exit statuses:
+ /// - OperationSucceeded (0): All operations (enable or disable) succeeded.
+ /// - OperationFailed (1): At least one operation failed.
+ ///
+ /// Only features present in "validFeatures" are considered valid. If an invalid feature name is encountered, the script exits with OperationFailed.
+ /// This list should generally be kept consistent with the list of features in the WindowsOptionalFeatures class.
+ ///
+ ///
+ private const string Script =
+@"
+$ErrorActionPreference='stop'
+
+enum OperationStatus
+{
+ OperationSucceeded = 0
+ OperationFailed = 1
+}
+
+$validFeatures = @(
+ 'Containers',
+ 'HostGuardian',
+ 'Microsoft-Hyper-V-All',
+ 'Microsoft-Hyper-V-Tools-All',
+ 'Microsoft-Hyper-V',
+ 'VirtualMachinePlatform',
+ 'HypervisorPlatform',
+ 'Containers-DisposableClientVM',
+ 'Microsoft-Windows-Subsystem-Linux'
+)
+
+function ModifyFeatures($featuresString)
+{
+ $features = ConvertFrom-StringData $featuresString
+
+ foreach ($feature in $features.GetEnumerator())
+ {
+ $featureName = $feature.Key
+ if ($featureName -notin $validFeatures)
+ {
+ exit [OperationStatus]::OperationFailed
+ }
+
+ $isEnabled = [bool]::Parse($feature.Value);
+ $featureState = Get-WindowsOptionalFeature -FeatureName $featureName -Online | Select-Object -ExpandProperty State;
+ $currentEnabled = $featureState -eq 'Enabled';
+
+ if ($currentEnabled -ne $isEnabled)
+ {
+ $result = $null
+ if ($isEnabled)
+ {
+ $result = Enable-WindowsOptionalFeature -Online -FeatureName $featureName -All -NoRestart
+ }
+ else
+ {
+ $result = Disable-WindowsOptionalFeature -Online -FeatureName $featureName -NoRestart
+ }
+
+ if ($null -eq $result)
+ {
+ exit [OperationStatus]::OperationFailed;
+ }
+ }
+ }
+
+ exit [OperationStatus]::OperationSucceeded;
+}
+
+ModifyFeatures FEATURE_STRING_INPUT;
+";
+}
diff --git a/common/Services/IAppInfoService.cs b/common/Services/IAppInfoService.cs
index 35f332f87d..0df1d7acba 100644
--- a/common/Services/IAppInfoService.cs
+++ b/common/Services/IAppInfoService.cs
@@ -7,9 +7,27 @@ namespace DevHome.Common.Services;
public interface IAppInfoService
{
+ ///
+ /// Gets the localized name of the application.
+ ///
+ /// The localized name of the application.
public string GetAppNameLocalized();
+ ///
+ /// Gets the version of the application.
+ ///
+ /// The version of the application.
public Version GetAppVersion();
+ ///
+ /// Gets the path for the icon of the application.
+ ///
public string IconPath { get; }
+
+ ///
+ /// Gets the preferred language of the user.
+ /// If no preferred language is set, then default to
+ ///
+ /// Preferred language is set in the system settings under "Language & region"
+ public string UserPreferredLanguage { get; }
}
diff --git a/common/Services/IPackageDeploymentService.cs b/common/Services/IPackageDeploymentService.cs
deleted file mode 100644
index ccdeb4f7b9..0000000000
--- a/common/Services/IPackageDeploymentService.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using Windows.ApplicationModel;
-using Windows.Management.Deployment;
-
-namespace DevHome.Common.Services;
-
-///
-/// Interface for using the deployment API
-///
-///
-public interface IPackageDeploymentService
-{
- ///
- /// Register a package for the current user
- ///
- /// Package family name
- /// Register package options
- /// Exception thrown if registration failed
- public Task RegisterPackageForCurrentUserAsync(string packageFamilyName, RegisterPackageOptions? options = null);
-
- ///
- /// Find packages for the current user. If maxVersion is specified, package versions must be
- /// between minVersion and maxVersion. If maxVersion is null, packages must be above minVersion.
- /// If no minVersion is specified, returns packages of any version.
- ///
- /// An IEnumerable containing the installed packages that meet the version criteria.
- public IEnumerable FindPackagesForCurrentUser(string packageFamilyName, params (Version minVersion, Version? maxVersion)[] ranges);
-}
-
-///
-/// Parameter object for
-/// More details: https://learn.microsoft.com/uwp/api/windows.management.deployment.packagemanager.registerpackagebyfamilynameasync?view=winrt-22621
-///
-public sealed class RegisterPackageOptions
-{
- ///
- /// Gets or sets the family names of the dependency packages to be registered.
- ///
- public IEnumerable? DependencyPackageFamilyNames { get; set; }
-
- ///
- /// Gets or sets the package deployment option.
- ///
- public DeploymentOptions? DeploymentOptions { get; set; }
-
- ///
- /// Gets or sets the package volume to store that app data on.
- ///
- public PackageVolume? AppDataVolume { get; set; }
-
- ///
- /// Gets or sets the optional package family names from the main bundle to be registered.
- ///
- public IEnumerable? OptionalPackageFamilyNames { get; set; }
-}
diff --git a/common/Services/LocalSettingsService.cs b/common/Services/LocalSettingsService.cs
index 3bd1a6ca30..5da1340101 100644
--- a/common/Services/LocalSettingsService.cs
+++ b/common/Services/LocalSettingsService.cs
@@ -15,8 +15,8 @@ namespace DevHome.Common.Services;
public class LocalSettingsService : ILocalSettingsService
{
- private const string _defaultApplicationDataFolder = "DevHome/ApplicationData";
- private const string _defaultLocalSettingsFile = "LocalSettings.json";
+ private const string DefaultApplicationDataFolder = "DevHome/ApplicationData";
+ private const string DefaultLocalSettingsFile = "LocalSettings.json";
private readonly IFileService _fileService;
private readonly LocalSettingsOptions _options;
@@ -34,8 +34,8 @@ public LocalSettingsService(IFileService fileService, IOptions();
}
diff --git a/common/Services/QuickstartSetupService.cs b/common/Services/QuickstartSetupService.cs
index d379b060df..aeee99dd66 100644
--- a/common/Services/QuickstartSetupService.cs
+++ b/common/Services/QuickstartSetupService.cs
@@ -5,12 +5,12 @@
using System.Linq;
using System.Threading.Tasks;
using DevHome.Common.Contracts;
-using DevHome.Common.Services;
+using DevHome.Services.Core.Contracts;
using Serilog;
namespace DevHome.Services;
-public class QuickstartSetupService(IAppInstallManagerService appInstallManagerService, IPackageDeploymentService packageDeploymentService) : IQuickstartSetupService
+public class QuickstartSetupService(IMicrosoftStoreService msStoreService, IPackageDeploymentService packageDeploymentService) : IQuickstartSetupService
{
#if CANARY_BUILD
private const string AzureExtensionStorePackageId = "9NBVFRMSFXHW";
@@ -25,7 +25,7 @@ public class QuickstartSetupService(IAppInstallManagerService appInstallManagerS
private readonly ILogger _log = Log.ForContext("SourceContext", nameof(QuickstartSetupService));
- private readonly IAppInstallManagerService _appInstallManagerService = appInstallManagerService;
+ private readonly IMicrosoftStoreService _msStoreService = msStoreService;
private readonly IPackageDeploymentService _packageDeploymentService = packageDeploymentService;
public bool IsDevHomeAzureExtensionInstalled()
@@ -43,7 +43,7 @@ public async Task InstallDevHomeAzureExtensionAsync()
try
{
_log.Information("Installing DevHomeAzureExtension");
- await _appInstallManagerService.TryInstallPackageAsync(AzureExtensionStorePackageId);
+ await _msStoreService.TryInstallPackageAsync(AzureExtensionStorePackageId);
}
catch (Exception ex)
{
diff --git a/common/Strings/en-us/Resources.resw b/common/Strings/en-us/Resources.resw
index a2d1803b91..922cd4f409 100644
--- a/common/Strings/en-us/Resources.resw
+++ b/common/Strings/en-us/Resources.resw
@@ -131,7 +131,7 @@
vCPU:
- Title for the virtual cpu property of a compute system
+ Title for the virtual CPU property of a compute system
Created
@@ -287,14 +287,14 @@
Couldn't get title from the extension
- Error text to be displayed to the user when a dev home extension that wants a content dialog to be displayed does not provide Dev Home with a title for the content dialog
+ Error text to be displayed to the user when a Dev Home extension that wants a content dialog to be displayed does not provide Dev Home with a title for the content dialog
An item must be selected
Error text advising the user that they must select an item in the list before proceeding
- An unexpected error occured while attempting to create your new environment
+ An unexpected error occurred while attempting to create your new environment
Error text for when there was an unexpected error that we could not handle within Dev Home.
@@ -323,4 +323,40 @@
Choose file
+
+ Provides services and tools to create and manage Windows Server Containers and their resources.
+ Locked="{Windows Server Containers}" Description for the Containers optional feature.
+
+
+ Enables the device to create and run Shielded Virtual Machine using remote attestation.
+ Locked="{Shielded Virtual Machine}" Description for the HostGuardian optional feature.
+
+
+ Provides services and management tools for creating and running virtual machines and their resources.
+ Description for the Microsoft-Hyper-V-All optional feature.
+
+
+ Includes GUI and command-line tools for managing Hyper-V.
+ Description for the Microsoft-Hyper-V-Tools-All optional feature.
+
+
+ Provides the services that you can use to create and manage virtual machines and their resources.
+ Description for the Microsoft-Hyper-V optional feature.
+
+
+ Enables platform support for virtual machines.
+ Description for the VirtualMachinePlatform optional feature.
+
+
+ Enables virtualization software to run on the Windows hypervisor.
+ Description for the WindowsHypervisorPlatform optional feature.
+
+
+ Enables the dependencies required to run Windows Sandbox scenarios.
+ Locked="{Windows Sandbox}" Description for the Containers-DisposableClientVM optional feature.
+
+
+ Provides services and environments for running native user-mode Linux shells and tools on Windows.
+ Description for the Containers-Microsoft-Windows-Subsystem-Linux optional feature.
+
\ No newline at end of file
diff --git a/common/TelemetryEvents/DeveloperId/EntryPointEvent.cs b/common/TelemetryEvents/DeveloperId/EntryPointEvent.cs
index 3ea65d387f..1ff4c47618 100644
--- a/common/TelemetryEvents/DeveloperId/EntryPointEvent.cs
+++ b/common/TelemetryEvents/DeveloperId/EntryPointEvent.cs
@@ -28,7 +28,7 @@ public enum EntryPoint
Widget = 4,
}
- private readonly string[] entryPointNames =
+ private readonly string[] _entryPointNames =
[
string.Empty,
"Settings",
@@ -39,7 +39,7 @@ public enum EntryPoint
public EntryPointEvent(EntryPoint entryPoint)
{
- EntryPointName = entryPointNames[(int)entryPoint];
+ EntryPointName = _entryPointNames[(int)entryPoint];
}
public override void ReplaceSensitiveStrings(Func replaceSensitiveStrings)
diff --git a/common/TelemetryEvents/EnvironmentRedirectionUserEvent.cs b/common/TelemetryEvents/EnvironmentRedirectionUserEvent.cs
index 8e6af37312..0e2c3c0234 100644
--- a/common/TelemetryEvents/EnvironmentRedirectionUserEvent.cs
+++ b/common/TelemetryEvents/EnvironmentRedirectionUserEvent.cs
@@ -2,11 +2,7 @@
// Licensed under the MIT License.
using System;
-using System.Collections.Generic;
using System.Diagnostics.Tracing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using DevHome.Telemetry;
using Microsoft.Diagnostics.Telemetry;
using Microsoft.Diagnostics.Telemetry.Internal;
diff --git a/common/TelemetryEvents/Environments/EnvironmentRestartUserEvent.cs b/common/TelemetryEvents/Environments/EnvironmentRestartUserEvent.cs
index abfcbb5049..ad3344525f 100644
--- a/common/TelemetryEvents/Environments/EnvironmentRestartUserEvent.cs
+++ b/common/TelemetryEvents/Environments/EnvironmentRestartUserEvent.cs
@@ -3,7 +3,6 @@
using System;
using System.Diagnostics.Tracing;
-using DevHome.Common.TelemetryEvents.SetupFlow.Environments;
using DevHome.Telemetry;
using Microsoft.Diagnostics.Telemetry;
using Microsoft.Diagnostics.Telemetry.Internal;
diff --git a/common/TelemetryEvents/ModifyWindowsOptionalFeaturesEvent.cs b/common/TelemetryEvents/ModifyWindowsOptionalFeaturesEvent.cs
new file mode 100644
index 0000000000..e5ad8033fe
--- /dev/null
+++ b/common/TelemetryEvents/ModifyWindowsOptionalFeaturesEvent.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics.Tracing;
+using DevHome.Telemetry;
+using Microsoft.Diagnostics.Telemetry;
+using Microsoft.Diagnostics.Telemetry.Internal;
+using static DevHome.Common.Scripts.ModifyWindowsOptionalFeatures;
+
+namespace DevHome.Common.TelemetryEvents;
+
+[EventData]
+public class ModifyWindowsOptionalFeaturesEvent : EventBase
+{
+ public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServicePerformance;
+
+ public string FeaturesString
+ {
+ get;
+ }
+
+ public string ExitCode
+ {
+ get;
+ }
+
+ public long DurationMs
+ {
+ get;
+ }
+
+ public ModifyWindowsOptionalFeaturesEvent(string featureString, ExitCode result, long durationMs)
+ {
+ FeaturesString = featureString;
+ ExitCode = GetExitCodeDescription(result);
+ DurationMs = durationMs;
+ }
+
+ public override void ReplaceSensitiveStrings(Func replaceSensitiveStrings)
+ {
+ // No sensitive strings to replace.
+ }
+
+ public static void Log(string featureString, ExitCode result, long durationMs)
+ {
+ TelemetryFactory.Get().Log(
+ "ModifyVirtualizationFeatures_Event",
+ LogLevel.Measure,
+ new ModifyWindowsOptionalFeaturesEvent(featureString, result, durationMs));
+ }
+}
diff --git a/common/TelemetryEvents/SeekerEvent.cs b/common/TelemetryEvents/SeekerEvent.cs
index 0deb509625..4b8ebb11bb 100644
--- a/common/TelemetryEvents/SeekerEvent.cs
+++ b/common/TelemetryEvents/SeekerEvent.cs
@@ -9,7 +9,7 @@
namespace DevHome.Common.TelemetryEvents;
-// A seeker is someone who has sought out experimental features or experiements
+// A seeker is someone who has sought out experimental features or experiments
[EventData]
public class SeekerEvent : EventBase
{
diff --git a/common/TelemetryEvents/SetupFlow/SummaryPage/CloneRepoNextStepsEvent.cs b/common/TelemetryEvents/SetupFlow/SummaryPage/CloneRepoNextStepsEvent.cs
index 5882d8d488..efd4bc801e 100644
--- a/common/TelemetryEvents/SetupFlow/SummaryPage/CloneRepoNextStepsEvent.cs
+++ b/common/TelemetryEvents/SetupFlow/SummaryPage/CloneRepoNextStepsEvent.cs
@@ -2,11 +2,7 @@
// Licensed under the MIT License.
using System;
-using System.Collections.Generic;
using System.Diagnostics.Tracing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using DevHome.Telemetry;
using Microsoft.Diagnostics.Telemetry;
using Microsoft.Diagnostics.Telemetry.Internal;
diff --git a/common/Views/AdaptiveCardViews/AdaptiveCardResourceTemplates.xaml b/common/Views/AdaptiveCardViews/AdaptiveCardResourceTemplates.xaml
index d43ba0d760..90f6183f2c 100644
--- a/common/Views/AdaptiveCardViews/AdaptiveCardResourceTemplates.xaml
+++ b/common/Views/AdaptiveCardViews/AdaptiveCardResourceTemplates.xaml
@@ -4,19 +4,32 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cardModels="using:DevHome.Common.DevHomeAdaptiveCards.CardModels"
- xmlns:controls="using:CommunityToolkit.WinUI.Controls">
+ xmlns:controls="using:CommunityToolkit.WinUI.Controls"
+ xmlns:local="using:DevHome.Common.Views.AdaptiveCardViews"
+ xmlns:converters="using:CommunityToolkit.WinUI.Converters">
-
+
+
-
+
+ 40
+
+
diff --git a/common/Views/DevHomePage.cs b/common/Views/DevHomePage.cs
index 75681024a7..dfcf7d79f2 100644
--- a/common/Views/DevHomePage.cs
+++ b/common/Views/DevHomePage.cs
@@ -13,7 +13,6 @@ namespace DevHome.Common.Views;
/// the individual Page should handle that. Take a look at EnvironmentCreationOptionsView.xaml
/// for an example on using the autofocus behavior to focus when the element when it becomes visible.
///
-///
public class DevHomePage : Page
{
public DevHomePage()
diff --git a/common/ToolPage.cs b/common/Views/ToolPage.cs
similarity index 53%
rename from common/ToolPage.cs
rename to common/Views/ToolPage.cs
index 4ce07613f7..a34529858f 100644
--- a/common/ToolPage.cs
+++ b/common/Views/ToolPage.cs
@@ -1,11 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using DevHome.Common.Views;
-
-namespace DevHome.Common;
+namespace DevHome.Common.Views;
public abstract class ToolPage : DevHomePage
{
- public abstract string ShortName { get; }
}
diff --git a/common/Windows/FileDialog/WindowFileDialog.cs b/common/Windows/FileDialog/WindowFileDialog.cs
index b151d28126..27e5ea9453 100644
--- a/common/Windows/FileDialog/WindowFileDialog.cs
+++ b/common/Windows/FileDialog/WindowFileDialog.cs
@@ -35,7 +35,7 @@ public abstract class WindowFileDialog : IDisposable
private readonly IFileDialog _fileDialog;
private readonly uint _adviseCookie;
private readonly List _fileTypes = [];
- private bool disposedValue;
+ private bool _disposedValue;
// File dialog events
public event EventHandler? FileTypeChanged;
@@ -208,7 +208,7 @@ private int GetFileTypeIndex()
/// />
private void Dispose(bool disposing)
{
- if (!disposedValue)
+ if (!_disposedValue)
{
if (disposing)
{
@@ -216,7 +216,7 @@ private void Dispose(bool disposing)
_fileDialog.Unadvise(_adviseCookie);
}
- disposedValue = true;
+ _disposedValue = true;
}
}
diff --git a/common/Windows/FileDialog/WindowFileDialogFilter.cs b/common/Windows/FileDialog/WindowFileDialogFilter.cs
index 5803415999..e251b784fa 100644
--- a/common/Windows/FileDialog/WindowFileDialogFilter.cs
+++ b/common/Windows/FileDialog/WindowFileDialogFilter.cs
@@ -12,7 +12,7 @@ namespace DevHome.Common.Windows.FileDialog;
internal sealed class WindowFileDialogFilter : IWindowFileDialogFilter, IDisposable
{
private readonly COMDLG_FILTERSPEC _extension;
- private bool disposedValue;
+ private bool _disposedValue;
///
public unsafe string Name { get; }
@@ -56,7 +56,7 @@ public void Dispose()
///
private unsafe void Dispose(bool disposing)
{
- if (!disposedValue)
+ if (!_disposedValue)
{
if (disposing)
{
@@ -64,7 +64,7 @@ private unsafe void Dispose(bool disposing)
Marshal.FreeHGlobal((IntPtr)_extension.pszSpec.Value);
}
- disposedValue = true;
+ _disposedValue = true;
}
}
}
diff --git a/common/Windows/SecondaryWindow.cs b/common/Windows/SecondaryWindow.cs
index fce42968e1..c1cfacd9ef 100644
--- a/common/Windows/SecondaryWindow.cs
+++ b/common/Windows/SecondaryWindow.cs
@@ -58,7 +58,7 @@ public WindowTitleBar? WindowTitleBar
value.TitleChanged += OnSecondaryWindowTitleChanged;
}
}
- }
+ }
}
///
diff --git a/docs/architecture.md b/docs/architecture.md
index 1433f53717..4daf21308c 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -13,7 +13,7 @@ Dev Home has a modular architecture that consists of multiple components. These
A more detailed look at how the projects are related:
```mermaid
graph TD;
- DevHome.Telemetry-->DevHome.Common;
+ %%{init:{'flowchart':{"defaultRenderer": "elk"}}}%%
DevHome.Common-->DevHome.Customization;
DevHome.Common-->DevHome.Dashboard;
DevHome.Common-->DevHome.Experiments;
@@ -34,6 +34,14 @@ graph TD;
DevHome.ExtensionLibrary-->DevHome;
DevHome.Settings-->DevHome;
DevHome.SetupFlow-->DevHome;
+ DevHome.Services.Core-->DevHome.Services.WindowsPackageManager;
+ DevHome.Services.Core-->DevHome.Services.DesiredStateConfiguration;
+ DevHome.Telemetry-->DevHome.Services.Core;
+ DevHome.Services.Core-->DevHome.Common;
+ DevHome.Services.WindowsPackageManager-->DevHome.SetupFlow;
+ DevHome.Services.DesiredStateConfiguration-->DevHome.SetupFlow;
+ DevHome.Services.WindowsPackageManager-->DevHome.SetupFlow.ElevatedComponent;
+ DevHome.Services.DesiredStateConfiguration-->DevHome.SetupFlow.ElevatedComponent;
```
## Dev Home Core
@@ -70,6 +78,7 @@ Dev Home currently has the following tools:
- Setup flow
- Extensions Library
- [Windows customization](../tools/Customization/DevHome.Customization/Customization.md)
+- Utilities
## Extensions
diff --git a/docs/extensions/environments/readme.md b/docs/extensions/environments/readme.md
index 26a0582c35..64c36c7145 100644
--- a/docs/extensions/environments/readme.md
+++ b/docs/extensions/environments/readme.md
@@ -21,7 +21,7 @@ Dev Home uses the [IComputeSystem](https://github.com/microsoft/devhome/blob/3dc
### General Dev Home extension setup
If this is your first time building a Dev Home extension:
-1. Follow general Dev Home [extension documentation](https://github.com/microsoft/devhome/blob/main/docs/extensions.md) on how to get started with building an extension.
+1. Follow general Dev Home [extension documentation](https://github.com/microsoft/devhome/blob/main/docs/extensions/readme.md) on how to get started with building an extension.
2. A sample Dev Home extension can be found [here.](https://github.com/microsoft/devhome/tree/main/extensions/SampleExtension)
### Environment specific setup
diff --git a/docs/tools/creating-a-tool.md b/docs/tools/creating-a-tool.md
index 0a2ef7f75c..ea90fdf39e 100644
--- a/docs/tools/creating-a-tool.md
+++ b/docs/tools/creating-a-tool.md
@@ -21,13 +21,11 @@
## Tool requirements
-Each tool must define a custom page view extending from the [`ToolPage`](../common/ToolPage.cs) abstract class, and implement it like in this example:
+Each tool must define a custom page view extending from the [`ToolPage`](../../common/Views/ToolPage.cs) abstract class, and implement it like in this example:
```cs
public class SampleToolPage : ToolPage
{
- public override string ShortName => "SampleTool";
-
public SampleToolPage()
{
ViewModel = Application.Current.GetService();
@@ -36,24 +34,18 @@ public class SampleToolPage : ToolPage
}
```
-The Dev Home framework will look at all types in its assembly for any inheriting from `ToolPage`:
-
-On a found type, the framework will use:
- - ShortName property to get the name of the tool
+If a page is not part of a tool, it should extend from [`DevHomePage.cs`](../../common/Views/DevHomePage.cs).
-### Method definition
+
## Code organization
-[`toolpage.cs`](../common/ToolPage.cs)
+[`ToolPage.cs`](../../common/Views/ToolPage.cs)
Contains the interface definition for Dev Home tools.
+
+[`DevHomePage.cs`](../../common/Views/DevHomePage.cs)
+Contains the interface definition for all Dev Home pages.
diff --git a/exclusion.dic b/exclusion.dic
index c05f96737b..ce9fd395dd 100644
--- a/exclusion.dic
+++ b/exclusion.dic
@@ -29,3 +29,8 @@
jsonc
appsettings
awaitable
+winget
+Whats
+disksandvolumes
+Quickstart
+awaitable
diff --git a/extensions/CoreWidgetProvider/.gitattributes b/extensions/CoreWidgetProvider/.gitattributes
deleted file mode 100644
index c210a8dc46..0000000000
--- a/extensions/CoreWidgetProvider/.gitattributes
+++ /dev/null
@@ -1,3 +0,0 @@
-# Set default behavior to automatically normalize line endings.
-
-* text=crlf
diff --git a/extensions/CoreWidgetProvider/CoreWidgetProvider.csproj b/extensions/CoreWidgetProvider/CoreWidgetProvider.csproj
index 7eb8890a64..9f67c0df9d 100644
--- a/extensions/CoreWidgetProvider/CoreWidgetProvider.csproj
+++ b/extensions/CoreWidgetProvider/CoreWidgetProvider.csproj
@@ -2,6 +2,11 @@
Exe
+ Debug;Release;Debug_FailFast
+
+
+ Exe
+ Debug;Release;Debug_FailFast
WinExe
@@ -19,7 +24,7 @@
-
+
all
diff --git a/extensions/CoreWidgetProvider/Helpers/CPUStats.cs b/extensions/CoreWidgetProvider/Helpers/CPUStats.cs
index c579a84247..f701257e20 100644
--- a/extensions/CoreWidgetProvider/Helpers/CPUStats.cs
+++ b/extensions/CoreWidgetProvider/Helpers/CPUStats.cs
@@ -8,10 +8,10 @@ namespace CoreWidgetProvider.Helpers;
internal sealed class CPUStats : IDisposable
{
// CPU counters
- private readonly PerformanceCounter procPerf = new("Processor Information", "% Processor Utility", "_Total");
- private readonly PerformanceCounter procPerformance = new("Processor Information", "% Processor Performance", "_Total");
- private readonly PerformanceCounter procFrequency = new("Processor Information", "Processor Frequency", "_Total");
- private readonly Dictionary cpuCounters = new();
+ private readonly PerformanceCounter _procPerf = new("Processor Information", "% Processor Utility", "_Total");
+ private readonly PerformanceCounter _procPerformance = new("Processor Information", "% Processor Performance", "_Total");
+ private readonly PerformanceCounter _procFrequency = new("Processor Information", "Processor Frequency", "_Total");
+ private readonly Dictionary _cpuCounters = new();
internal sealed class ProcessStats
{
@@ -26,7 +26,7 @@ internal sealed class ProcessStats
public ProcessStats[] ProcessCPUStats { get; set; }
- public List CpuChartValues { get; set; } = new List();
+ public List CpuChartValues { get; set; } = new();
public CPUStats()
{
@@ -45,16 +45,16 @@ private void InitCPUPerfCounters()
{
var allProcesses = Process.GetProcesses().Where(p => (long)p.MainWindowHandle != 0);
- foreach (Process process in allProcesses)
+ foreach (var process in allProcesses)
{
- cpuCounters.Add(process, new PerformanceCounter("Process", "% Processor Time", process.ProcessName, true));
+ _cpuCounters.Add(process, new PerformanceCounter("Process", "% Processor Time", process.ProcessName, true));
}
}
public void GetData()
{
- CpuUsage = procPerf.NextValue() / 100;
- CpuSpeed = procFrequency.NextValue() * (procPerformance.NextValue() / 100);
+ CpuUsage = _procPerf.NextValue() / 100;
+ CpuSpeed = _procFrequency.NextValue() * (_procPerformance.NextValue() / 100);
lock (CpuChartValues)
{
@@ -63,7 +63,7 @@ public void GetData()
var processCPUUsages = new Dictionary();
- foreach (var processCounter in cpuCounters)
+ foreach (var processCounter in _cpuCounters)
{
try
{
@@ -111,11 +111,11 @@ internal void KillTopProcess(int cpuProcessIndex)
public void Dispose()
{
- procPerf.Dispose();
- procPerformance.Dispose();
- procFrequency.Dispose();
+ _procPerf.Dispose();
+ _procPerformance.Dispose();
+ _procFrequency.Dispose();
- foreach (var counter in cpuCounters.Values)
+ foreach (var counter in _cpuCounters.Values)
{
counter.Dispose();
}
diff --git a/extensions/CoreWidgetProvider/Helpers/DataManager.cs b/extensions/CoreWidgetProvider/Helpers/DataManager.cs
index 6a27f74586..e47c750117 100644
--- a/extensions/CoreWidgetProvider/Helpers/DataManager.cs
+++ b/extensions/CoreWidgetProvider/Helpers/DataManager.cs
@@ -7,23 +7,23 @@ namespace CoreWidgetProvider.Helpers;
internal sealed class DataManager : IDisposable
{
- private readonly SystemData systemData;
- private readonly DataType dataType;
- private readonly Timer updateTimer;
- private readonly Action updateAction;
+ private readonly SystemData _systemData;
+ private readonly DataType _dataType;
+ private readonly Timer _updateTimer;
+ private readonly Action _updateAction;
private const int OneSecondInMilliseconds = 1000;
public DataManager(DataType type, Action updateWidget)
{
- systemData = new SystemData();
- updateAction = updateWidget;
- dataType = type;
-
- updateTimer = new Timer(OneSecondInMilliseconds);
- updateTimer.Elapsed += UpdateTimer_Elapsed;
- updateTimer.AutoReset = true;
- updateTimer.Enabled = false;
+ _systemData = new SystemData();
+ _updateAction = updateWidget;
+ _dataType = type;
+
+ _updateTimer = new Timer(OneSecondInMilliseconds);
+ _updateTimer.Elapsed += UpdateTimer_Elapsed;
+ _updateTimer.AutoReset = true;
+ _updateTimer.Enabled = false;
}
private void GetMemoryData()
@@ -60,7 +60,7 @@ private void GetCPUData()
private void UpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
- switch (dataType)
+ switch (_dataType)
{
case DataType.CPU:
{
@@ -91,10 +91,7 @@ private void UpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs
}
}
- if (updateAction != null)
- {
- updateAction();
- }
+ _updateAction?.Invoke();
}
internal MemoryStats GetMemoryStats()
@@ -131,17 +128,17 @@ internal CPUStats GetCPUStats()
public void Start()
{
- updateTimer.Start();
+ _updateTimer.Start();
}
public void Stop()
{
- updateTimer.Stop();
+ _updateTimer.Stop();
}
public void Dispose()
{
- systemData.Dispose();
- updateTimer.Dispose();
+ _systemData.Dispose();
+ _updateTimer.Dispose();
}
}
diff --git a/extensions/CoreWidgetProvider/Helpers/GPUStats.cs b/extensions/CoreWidgetProvider/Helpers/GPUStats.cs
index 6cfaf019e7..943eb8495c 100644
--- a/extensions/CoreWidgetProvider/Helpers/GPUStats.cs
+++ b/extensions/CoreWidgetProvider/Helpers/GPUStats.cs
@@ -11,9 +11,9 @@ namespace CoreWidgetProvider.Helpers;
internal sealed class GPUStats : IDisposable
{
// GPU counters
- private readonly Dictionary> gpuCounters = new();
+ private readonly Dictionary> _gpuCounters = new();
- private readonly List stats = new();
+ private readonly List _stats = new();
public sealed class Data
{
@@ -25,7 +25,7 @@ public sealed class Data
public float Temperature { get; set; }
- public List GpuChartValues { get; set; } = new List();
+ public List GpuChartValues { get; set; } = new();
}
public GPUStats()
@@ -38,18 +38,18 @@ public void LoadGPUs()
{
using var session = CimSession.Create(null);
var i = 0;
- stats.Clear();
+ _stats.Clear();
foreach (CimInstance obj in session.QueryInstances("root/cimv2", "WQL", "select * from Win32_VideoController"))
{
var gpuName = (string)obj.CimInstanceProperties["name"].Value;
- stats.Add(new Data() { Name = gpuName, PhysId = i++ });
+ _stats.Add(new Data() { Name = gpuName, PhysId = i++ });
}
}
public void GetGPUPerfCounters()
{
- gpuCounters.Clear();
+ _gpuCounters.Clear();
var pcg = new PerformanceCounterCategory("GPU Engine");
var instanceNames = pcg.GetInstanceNames();
@@ -80,10 +80,10 @@ public void GetGPUPerfCounters()
continue;
}
- if (!gpuCounters.TryGetValue(phys, out var value))
+ if (!_gpuCounters.TryGetValue(phys, out var value))
{
value = new();
- gpuCounters.Add(phys, value);
+ _gpuCounters.Add(phys, value);
}
value.Add(counter);
@@ -94,10 +94,10 @@ public void GetGPUPerfCounters()
public void GetData()
{
- foreach (var gpu in stats)
+ foreach (var gpu in _stats)
{
List? counters;
- var success = gpuCounters.TryGetValue(gpu.PhysId, out counters);
+ var success = _gpuCounters.TryGetValue(gpu.PhysId, out counters);
if (success)
{
@@ -123,29 +123,29 @@ public void GetData()
internal string CreateGPUImageUrl(int gpuChartIndex)
{
- return ChartHelper.CreateImageUrl(stats.ElementAt(gpuChartIndex).GpuChartValues, ChartHelper.ChartType.GPU);
+ return ChartHelper.CreateImageUrl(_stats.ElementAt(gpuChartIndex).GpuChartValues, ChartHelper.ChartType.GPU);
}
internal string GetGPUName(int gpuActiveIndex)
{
- if (stats.Count <= gpuActiveIndex)
+ if (_stats.Count <= gpuActiveIndex)
{
return string.Empty;
}
- return stats[gpuActiveIndex].Name ?? string.Empty;
+ return _stats[gpuActiveIndex].Name ?? string.Empty;
}
internal int GetPrevGPUIndex(int gpuActiveIndex)
{
- if (stats.Count == 0)
+ if (_stats.Count == 0)
{
return 0;
}
if (gpuActiveIndex == 0)
{
- return stats.Count - 1;
+ return _stats.Count - 1;
}
return gpuActiveIndex - 1;
@@ -153,12 +153,12 @@ internal int GetPrevGPUIndex(int gpuActiveIndex)
internal int GetNextGPUIndex(int gpuActiveIndex)
{
- if (stats.Count == 0)
+ if (_stats.Count == 0)
{
return 0;
}
- if (gpuActiveIndex == stats.Count - 1)
+ if (gpuActiveIndex == _stats.Count - 1)
{
return 0;
}
@@ -168,22 +168,22 @@ internal int GetNextGPUIndex(int gpuActiveIndex)
internal float GetGPUUsage(int gpuActiveIndex, string gpuActiveEngType)
{
- if (stats.Count <= gpuActiveIndex)
+ if (_stats.Count <= gpuActiveIndex)
{
return 0;
}
- return stats[gpuActiveIndex].Usage;
+ return _stats[gpuActiveIndex].Usage;
}
internal string GetGPUTemperature(int gpuActiveIndex)
{
- if (stats.Count <= gpuActiveIndex)
+ if (_stats.Count <= gpuActiveIndex)
{
return "--";
}
- var temperature = stats[gpuActiveIndex].Temperature;
+ var temperature = _stats[gpuActiveIndex].Temperature;
if (temperature == 0)
{
return "--";
@@ -219,7 +219,7 @@ private string GetKeyValueFromCounterKey(string key, ref string counterKey)
public void Dispose()
{
- foreach (var counterPair in gpuCounters)
+ foreach (var counterPair in _gpuCounters)
{
foreach (var counter in counterPair.Value)
{
diff --git a/extensions/CoreWidgetProvider/Helpers/IconLoader.cs b/extensions/CoreWidgetProvider/Helpers/IconLoader.cs
index 44d11f4621..bc75a7e7d4 100644
--- a/extensions/CoreWidgetProvider/Helpers/IconLoader.cs
+++ b/extensions/CoreWidgetProvider/Helpers/IconLoader.cs
@@ -7,16 +7,16 @@ namespace CoreWidgetProvider.Helpers;
public class IconLoader
{
- private static readonly Dictionary Base64ImageRegistry = new();
+ private static readonly Dictionary _base64ImageRegistry = new();
public static string GetIconAsBase64(string filename)
{
var log = Log.ForContext("SourceContext", nameof(IconLoader));
log.Debug(nameof(IconLoader), $"Asking for icon: {filename}");
- if (!Base64ImageRegistry.TryGetValue(filename, out var value))
+ if (!_base64ImageRegistry.TryGetValue(filename, out var value))
{
value = ConvertIconToDataString(filename);
- Base64ImageRegistry.Add(filename, value);
+ _base64ImageRegistry.Add(filename, value);
log.Debug(nameof(IconLoader), $"The icon {filename} was converted and is now stored.");
}
diff --git a/extensions/CoreWidgetProvider/Helpers/MemoryStats.cs b/extensions/CoreWidgetProvider/Helpers/MemoryStats.cs
index 6f0c28a48c..cc54049d9b 100644
--- a/extensions/CoreWidgetProvider/Helpers/MemoryStats.cs
+++ b/extensions/CoreWidgetProvider/Helpers/MemoryStats.cs
@@ -9,11 +9,11 @@ namespace CoreWidgetProvider.Helpers;
internal sealed class MemoryStats : IDisposable
{
- private readonly PerformanceCounter memCommitted = new("Memory", "Committed Bytes", string.Empty);
- private readonly PerformanceCounter memCached = new("Memory", "Cache Bytes", string.Empty);
- private readonly PerformanceCounter memCommittedLimit = new("Memory", "Commit Limit", string.Empty);
- private readonly PerformanceCounter memPoolPaged = new("Memory", "Pool Paged Bytes", string.Empty);
- private readonly PerformanceCounter memPoolNonPaged = new("Memory", "Pool Nonpaged Bytes", string.Empty);
+ private readonly PerformanceCounter _memCommitted = new("Memory", "Committed Bytes", string.Empty);
+ private readonly PerformanceCounter _memCached = new("Memory", "Cache Bytes", string.Empty);
+ private readonly PerformanceCounter _memCommittedLimit = new("Memory", "Commit Limit", string.Empty);
+ private readonly PerformanceCounter _memPoolPaged = new("Memory", "Pool Paged Bytes", string.Empty);
+ private readonly PerformanceCounter _memPoolNonPaged = new("Memory", "Pool Nonpaged Bytes", string.Empty);
public float MemUsage
{
@@ -55,13 +55,13 @@ public ulong MemNonPagedPool
get; set;
}
- public List MemChartValues { get; set; } = new List();
+ public List MemChartValues { get; set; } = new();
public void GetData()
{
Windows.Win32.System.SystemInformation.MEMORYSTATUSEX memStatus = default;
memStatus.dwLength = (uint)Marshal.SizeOf(typeof(Windows.Win32.System.SystemInformation.MEMORYSTATUSEX));
- if (PInvoke.GlobalMemoryStatusEx(out memStatus))
+ if (PInvoke.GlobalMemoryStatusEx(ref memStatus))
{
AllMem = memStatus.ullTotalPhys;
var availableMem = memStatus.ullAvailPhys;
@@ -74,11 +74,11 @@ public void GetData()
}
}
- MemCached = (ulong)memCached.NextValue();
- MemCommitted = (ulong)memCommitted.NextValue();
- MemCommitLimit = (ulong)memCommittedLimit.NextValue();
- MemPagedPool = (ulong)memPoolPaged.NextValue();
- MemNonPagedPool = (ulong)memPoolNonPaged.NextValue();
+ MemCached = (ulong)_memCached.NextValue();
+ MemCommitted = (ulong)_memCommitted.NextValue();
+ MemCommitLimit = (ulong)_memCommittedLimit.NextValue();
+ MemPagedPool = (ulong)_memPoolPaged.NextValue();
+ MemNonPagedPool = (ulong)_memPoolNonPaged.NextValue();
}
public string CreateMemImageUrl()
@@ -88,10 +88,10 @@ public string CreateMemImageUrl()
public void Dispose()
{
- memCommitted.Dispose();
- memCached.Dispose();
- memCommittedLimit.Dispose();
- memPoolPaged.Dispose();
- memPoolNonPaged.Dispose();
+ _memCommitted.Dispose();
+ _memCached.Dispose();
+ _memCommittedLimit.Dispose();
+ _memPoolPaged.Dispose();
+ _memPoolNonPaged.Dispose();
}
}
diff --git a/extensions/CoreWidgetProvider/Helpers/NetworkStats.cs b/extensions/CoreWidgetProvider/Helpers/NetworkStats.cs
index 1b56389faa..e0c5135dce 100644
--- a/extensions/CoreWidgetProvider/Helpers/NetworkStats.cs
+++ b/extensions/CoreWidgetProvider/Helpers/NetworkStats.cs
@@ -8,11 +8,11 @@ namespace CoreWidgetProvider.Helpers;
internal sealed class NetworkStats : IDisposable
{
- private readonly Dictionary> networkCounters = new();
+ private readonly Dictionary> _networkCounters = new();
- private Dictionary NetworkUsages { get; set; } = new Dictionary();
+ private Dictionary NetworkUsages { get; set; } = new();
- private Dictionary> NetChartValues { get; set; } = new Dictionary>();
+ private Dictionary> NetChartValues { get; set; } = new();
public sealed class Data
{
@@ -39,15 +39,15 @@ public NetworkStats()
private void InitNetworkPerfCounters()
{
- PerformanceCounterCategory pcc = new PerformanceCounterCategory("Network Interface");
+ var pcc = new PerformanceCounterCategory("Network Interface");
var instanceNames = pcc.GetInstanceNames();
foreach (var instanceName in instanceNames)
{
- List instanceCounters = new List();
+ var instanceCounters = new List();
instanceCounters.Add(new PerformanceCounter("Network Interface", "Bytes Sent/sec", instanceName));
instanceCounters.Add(new PerformanceCounter("Network Interface", "Bytes Received/sec", instanceName));
instanceCounters.Add(new PerformanceCounter("Network Interface", "Current Bandwidth", instanceName));
- networkCounters.Add(instanceName, instanceCounters);
+ _networkCounters.Add(instanceName, instanceCounters);
NetChartValues.Add(instanceName, new List());
NetworkUsages.Add(instanceName, new Data());
}
@@ -56,7 +56,7 @@ private void InitNetworkPerfCounters()
public void GetData()
{
float maxUsage = 0;
- foreach (var networkCounterWithName in networkCounters)
+ foreach (var networkCounterWithName in _networkCounters)
{
try
{
@@ -155,7 +155,7 @@ public int GetNextNetworkIndex(int networkIndex)
public void Dispose()
{
- foreach (var counterPair in networkCounters)
+ foreach (var counterPair in _networkCounters)
{
foreach (var counter in counterPair.Value)
{
diff --git a/extensions/CoreWidgetProvider/Program.cs b/extensions/CoreWidgetProvider/Program.cs
index 58678ff4ba..d2da21e7c3 100644
--- a/extensions/CoreWidgetProvider/Program.cs
+++ b/extensions/CoreWidgetProvider/Program.cs
@@ -93,7 +93,7 @@ private static void HandleCOMServerActivation()
var widgetProviderInstance = new Widgets.WidgetProvider();
widgetServer.RegisterWidget(() => widgetProviderInstance);
- // This will make the main thread wait until the event is signalled by the extension class.
+ // This will make the main thread wait until the event is signaled by the extension class.
// Since we have single instance of the extension object, we exit as soon as it is disposed.
extensionDisposedEvent.WaitOne();
Log.Information($"Extension is disposed.");
diff --git a/extensions/CoreWidgetProvider/Widgets/CoreWidget.cs b/extensions/CoreWidgetProvider/Widgets/CoreWidget.cs
index 54051fda8a..8ee8716a97 100644
--- a/extensions/CoreWidgetProvider/Widgets/CoreWidget.cs
+++ b/extensions/CoreWidgetProvider/Widgets/CoreWidget.cs
@@ -109,7 +109,14 @@ public virtual void UpdateWidget()
};
Log.Debug($"Updating widget for {Page}");
- WidgetManager.GetDefault().UpdateWidget(updateOptions);
+ try
+ {
+ WidgetManager.GetDefault().UpdateWidget(updateOptions);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Exception updating widget via WidgetManager.");
+ }
}
public virtual string GetTemplatePath(WidgetPageState page)
diff --git a/extensions/CoreWidgetProvider/Widgets/SSHWalletWidget.cs b/extensions/CoreWidgetProvider/Widgets/SSHWalletWidget.cs
index 5bf8176f53..2e78000dd6 100644
--- a/extensions/CoreWidgetProvider/Widgets/SSHWalletWidget.cs
+++ b/extensions/CoreWidgetProvider/Widgets/SSHWalletWidget.cs
@@ -15,9 +15,9 @@ namespace CoreWidgetProvider.Widgets;
internal sealed class SSHWalletWidget : CoreWidget
{
- private static readonly string DefaultConfigFile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\.ssh\\config";
+ private static readonly string _defaultConfigFile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "\\.ssh\\config";
- private static readonly Regex HostRegex = new(@"^Host\s+(?:(\S*) ?)*?\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
+ private static readonly Regex _hostRegex = new(@"^Host\s+(?:(\S*) ?)*?\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
private FileSystemWatcher? FileWatcher { get; set; }
@@ -61,9 +61,7 @@ public override void LoadContentData()
var hostsArray = new JsonArray();
var hostEntries = GetHostEntries();
- if (hostEntries != null)
- {
- hostEntries.ToList().ForEach(hostEntry =>
+ hostEntries?.ToList().ForEach(hostEntry =>
{
var host = hostEntry.Groups[1].Value;
var hostJson = new JsonObject
@@ -73,7 +71,6 @@ public override void LoadContentData()
};
((IList)hostsArray).Add(hostJson);
});
- }
hostsData.Add("hosts", hostsArray);
hostsData.Add("selected_config_file", ConfigFile);
@@ -195,8 +192,14 @@ private void HandleCheckPath(WidgetActionInvokedArgs args)
CustomState = ConfigFile,
Template = GetTemplateForPage(Page),
};
-
- WidgetManager.GetDefault().UpdateWidget(updateRequestOptions);
+ try
+ {
+ WidgetManager.GetDefault().UpdateWidget(updateRequestOptions);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Exception updating widget via WidgetManager.");
+ }
}
}
@@ -213,7 +216,7 @@ private void HandleCheckPath(WidgetActionInvokedArgs args)
if (!string.IsNullOrEmpty(fileContent))
{
- return HostRegex.Matches(fileContent);
+ return _hostRegex.Matches(fileContent);
}
return null;
@@ -279,7 +282,7 @@ private JsonObject FillConfigurationData(bool hasConfiguration, string configFil
// is in the customize flow and we should show the _savedConfigFile.
// 3. Else, show the DefaultConfigFile.
var suggestedConfigFile = string.IsNullOrEmpty(configFile) ? _savedConfigFile : configFile;
- suggestedConfigFile = string.IsNullOrEmpty(suggestedConfigFile) ? DefaultConfigFile : suggestedConfigFile;
+ suggestedConfigFile = string.IsNullOrEmpty(suggestedConfigFile) ? _defaultConfigFile : suggestedConfigFile;
var sshConfigData = new JsonObject
{
@@ -367,7 +370,14 @@ public override void UpdateWidget()
};
Log.Debug($"Updating widget for {Page}");
- WidgetManager.GetDefault().UpdateWidget(updateOptions);
+ try
+ {
+ WidgetManager.GetDefault().UpdateWidget(updateOptions);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Exception updating widget via WidgetManager.");
+ }
}
public override string GetTemplatePath(WidgetPageState page)
diff --git a/extensions/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs b/extensions/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs
index 6f5052fea8..e61c506787 100644
--- a/extensions/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs
+++ b/extensions/CoreWidgetProvider/Widgets/SystemCPUUsageWidget.cs
@@ -13,12 +13,12 @@ internal sealed class SystemCPUUsageWidget : CoreWidget, IDisposable
{
private static Dictionary Templates { get; set; } = new();
- private readonly DataManager dataManager;
+ private readonly DataManager _dataManager;
public SystemCPUUsageWidget()
: base()
{
- dataManager = new(DataType.CPU, UpdateWidget);
+ _dataManager = new(DataType.CPU, UpdateWidget);
}
private string SpeedToString(float cpuSpeed)
@@ -39,7 +39,7 @@ public override void LoadContentData()
{
var cpuData = new JsonObject();
- var currentData = dataManager.GetCPUStats();
+ var currentData = _dataManager.GetCPUStats();
cpuData.Add("cpuUsage", FloatToPercentString(currentData.CpuUsage));
cpuData.Add("cpuSpeed", SpeedToString(currentData.CpuSpeed));
@@ -116,7 +116,7 @@ public override void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
if (processIndex != -1)
{
- dataManager.GetCPUStats().KillTopProcess(processIndex);
+ _dataManager.GetCPUStats().KillTopProcess(processIndex);
}
}
@@ -129,7 +129,7 @@ protected override void SetActive()
LoadContentData();
}
- dataManager.Start();
+ _dataManager.Start();
LogCurrentState();
UpdateWidget();
@@ -137,7 +137,7 @@ protected override void SetActive()
protected override void SetInactive()
{
- dataManager.Stop();
+ _dataManager.Stop();
ActivityState = WidgetActivityState.Inactive;
@@ -146,7 +146,7 @@ protected override void SetInactive()
protected override void SetDeleted()
{
- dataManager.Stop();
+ _dataManager.Stop();
SetState(string.Empty);
ActivityState = WidgetActivityState.Unknown;
@@ -155,6 +155,6 @@ protected override void SetDeleted()
public void Dispose()
{
- dataManager.Dispose();
+ _dataManager.Dispose();
}
}
diff --git a/extensions/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs b/extensions/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs
index 2bd44d80b5..5686347fed 100644
--- a/extensions/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs
+++ b/extensions/CoreWidgetProvider/Widgets/SystemGPUUsageWidget.cs
@@ -13,16 +13,16 @@ internal sealed class SystemGPUUsageWidget : CoreWidget, IDisposable
{
private static Dictionary Templates { get; set; } = new();
- private readonly DataManager dataManager;
+ private readonly DataManager _dataManager;
- private readonly string gpuActiveEngType = "3D";
+ private readonly string _gpuActiveEngType = "3D";
- private int gpuActiveIndex;
+ private int _gpuActiveIndex;
public SystemGPUUsageWidget()
: base()
{
- dataManager = new(DataType.GPU, UpdateWidget);
+ _dataManager = new(DataType.GPU, UpdateWidget);
}
private string SpeedToString(float cpuSpeed)
@@ -43,13 +43,13 @@ public override void LoadContentData()
{
var gpuData = new JsonObject();
- var stats = dataManager.GetGPUStats();
- var gpuName = stats.GetGPUName(gpuActiveIndex);
+ var stats = _dataManager.GetGPUStats();
+ var gpuName = stats.GetGPUName(_gpuActiveIndex);
- gpuData.Add("gpuUsage", FloatToPercentString(stats.GetGPUUsage(gpuActiveIndex, gpuActiveEngType)));
+ gpuData.Add("gpuUsage", FloatToPercentString(stats.GetGPUUsage(_gpuActiveIndex, _gpuActiveEngType)));
gpuData.Add("gpuName", gpuName);
- gpuData.Add("gpuTemp", stats.GetGPUTemperature(gpuActiveIndex));
- gpuData.Add("gpuGraphUrl", stats.CreateGPUImageUrl(gpuActiveIndex));
+ gpuData.Add("gpuTemp", stats.GetGPUTemperature(_gpuActiveIndex));
+ gpuData.Add("gpuGraphUrl", stats.CreateGPUImageUrl(_gpuActiveIndex));
gpuData.Add("chartHeight", ChartHelper.ChartHeight + "px");
gpuData.Add("chartWidth", ChartHelper.ChartWidth + "px");
@@ -93,13 +93,13 @@ public override string GetData(WidgetPageState page)
private void HandlePrevGPU(WidgetActionInvokedArgs args)
{
- gpuActiveIndex = dataManager.GetGPUStats().GetPrevGPUIndex(gpuActiveIndex);
+ _gpuActiveIndex = _dataManager.GetGPUStats().GetPrevGPUIndex(_gpuActiveIndex);
UpdateWidget();
}
private void HandleNextGPU(WidgetActionInvokedArgs args)
{
- gpuActiveIndex = dataManager.GetGPUStats().GetNextGPUIndex(gpuActiveIndex);
+ _gpuActiveIndex = _dataManager.GetGPUStats().GetNextGPUIndex(_gpuActiveIndex);
UpdateWidget();
}
@@ -133,7 +133,7 @@ protected override void SetActive()
LoadContentData();
}
- dataManager.Start();
+ _dataManager.Start();
LogCurrentState();
UpdateWidget();
@@ -141,7 +141,7 @@ protected override void SetActive()
protected override void SetInactive()
{
- dataManager.Stop();
+ _dataManager.Stop();
ActivityState = WidgetActivityState.Inactive;
@@ -150,7 +150,7 @@ protected override void SetInactive()
protected override void SetDeleted()
{
- dataManager.Stop();
+ _dataManager.Stop();
SetState(string.Empty);
ActivityState = WidgetActivityState.Unknown;
@@ -159,6 +159,6 @@ protected override void SetDeleted()
public void Dispose()
{
- dataManager.Dispose();
+ _dataManager.Dispose();
}
}
diff --git a/extensions/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs b/extensions/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs
index 88112f6ce9..1712539703 100644
--- a/extensions/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs
+++ b/extensions/CoreWidgetProvider/Widgets/SystemMemoryWidget.cs
@@ -12,12 +12,12 @@ internal sealed class SystemMemoryWidget : CoreWidget, IDisposable
{
private static Dictionary Templates { get; set; } = new();
- private readonly DataManager dataManager;
+ private readonly DataManager _dataManager;
public SystemMemoryWidget()
: base()
{
- dataManager = new(DataType.Memory, UpdateWidget);
+ _dataManager = new(DataType.Memory, UpdateWidget);
}
private string FloatToPercentString(float value)
@@ -56,7 +56,7 @@ public override void LoadContentData()
{
var memoryData = new JsonObject();
- var currentData = dataManager.GetMemoryStats();
+ var currentData = _dataManager.GetMemoryStats();
memoryData.Add("allMem", MemUlongToString(currentData.AllMem));
memoryData.Add("usedMem", MemUlongToString(currentData.UsedMem));
@@ -117,7 +117,7 @@ protected override void SetActive()
LoadContentData();
}
- dataManager.Start();
+ _dataManager.Start();
LogCurrentState();
UpdateWidget();
@@ -125,7 +125,7 @@ protected override void SetActive()
protected override void SetInactive()
{
- dataManager.Stop();
+ _dataManager.Stop();
ActivityState = WidgetActivityState.Inactive;
@@ -134,7 +134,7 @@ protected override void SetInactive()
protected override void SetDeleted()
{
- dataManager.Stop();
+ _dataManager.Stop();
SetState(string.Empty);
ActivityState = WidgetActivityState.Unknown;
@@ -143,6 +143,6 @@ protected override void SetDeleted()
public void Dispose()
{
- dataManager.Dispose();
+ _dataManager.Dispose();
}
}
diff --git a/extensions/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs b/extensions/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs
index 1161487f48..f0e55ee85a 100644
--- a/extensions/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs
+++ b/extensions/CoreWidgetProvider/Widgets/SystemNetworkUsageWidget.cs
@@ -11,20 +11,21 @@ namespace CoreWidgetProvider.Widgets;
internal sealed class SystemNetworkUsageWidget : CoreWidget, IDisposable
{
- private readonly DataManager dataManager;
+ private DataManager _dataManager;
private static Dictionary Templates { get; set; } = new();
- private int networkIndex;
+ private int _networkIndex;
public SystemNetworkUsageWidget()
: base()
{
- dataManager = new(DataType.Network, UpdateWidget);
+ _dataManager = new(DataType.Network, UpdateWidget);
}
private string SpeedToString(float cpuSpeed)
{
+ _dataManager = new(DataType.Network, UpdateWidget);
return string.Format(CultureInfo.InvariantCulture, "{0:0.00} GHz", cpuSpeed / 1000);
}
@@ -58,16 +59,16 @@ public override void LoadContentData()
{
var networkData = new JsonObject();
- var currentData = dataManager.GetNetworkStats();
+ var currentData = _dataManager.GetNetworkStats();
- var netName = currentData.GetNetworkName(networkIndex);
- var networkStats = currentData.GetNetworkUsage(networkIndex);
+ var netName = currentData.GetNetworkName(_networkIndex);
+ var networkStats = currentData.GetNetworkUsage(_networkIndex);
networkData.Add("networkUsage", FloatToPercentString(networkStats.Usage));
networkData.Add("netSent", BytesToBitsPerSecString(networkStats.Sent));
networkData.Add("netReceived", BytesToBitsPerSecString(networkStats.Received));
networkData.Add("networkName", netName);
- networkData.Add("netGraphUrl", currentData.CreateNetImageUrl(networkIndex));
+ networkData.Add("netGraphUrl", currentData.CreateNetImageUrl(_networkIndex));
networkData.Add("chartHeight", ChartHelper.ChartHeight + "px");
networkData.Add("chartWidth", ChartHelper.ChartWidth + "px");
@@ -111,13 +112,13 @@ public override string GetData(WidgetPageState page)
private void HandlePrevNetwork(WidgetActionInvokedArgs args)
{
- networkIndex = dataManager.GetNetworkStats().GetPrevNetworkIndex(networkIndex);
+ _networkIndex = _dataManager.GetNetworkStats().GetPrevNetworkIndex(_networkIndex);
UpdateWidget();
}
private void HandleNextNetwork(WidgetActionInvokedArgs args)
{
- networkIndex = dataManager.GetNetworkStats().GetNextNetworkIndex(networkIndex);
+ _networkIndex = _dataManager.GetNetworkStats().GetNextNetworkIndex(_networkIndex);
UpdateWidget();
}
@@ -151,7 +152,7 @@ protected override void SetActive()
LoadContentData();
}
- dataManager.Start();
+ _dataManager.Start();
LogCurrentState();
UpdateWidget();
@@ -159,7 +160,7 @@ protected override void SetActive()
protected override void SetInactive()
{
- dataManager.Stop();
+ _dataManager.Stop();
ActivityState = WidgetActivityState.Inactive;
@@ -168,7 +169,7 @@ protected override void SetInactive()
protected override void SetDeleted()
{
- dataManager.Stop();
+ _dataManager.Stop();
SetState(string.Empty);
ActivityState = WidgetActivityState.Unknown;
@@ -177,6 +178,6 @@ protected override void SetDeleted()
public void Dispose()
{
- dataManager.Dispose();
+ _dataManager.Dispose();
}
}
diff --git a/extensions/CoreWidgetProvider/Widgets/WidgetProvider.cs b/extensions/CoreWidgetProvider/Widgets/WidgetProvider.cs
index 7f55948f34..d5beb84977 100644
--- a/extensions/CoreWidgetProvider/Widgets/WidgetProvider.cs
+++ b/extensions/CoreWidgetProvider/Widgets/WidgetProvider.cs
@@ -12,17 +12,17 @@ namespace CoreWidgetProvider.Widgets;
[Guid("F8B2DBB9-3687-4C6E-99B2-B92C82905937")]
internal sealed class WidgetProvider : IWidgetProvider, IWidgetProvider2
{
- private readonly Dictionary widgetDefinitionRegistry = new();
- private readonly Dictionary runningWidgets = new();
+ private readonly Dictionary _widgetDefinitionRegistry = new();
+ private readonly Dictionary _runningWidgets = new();
public WidgetProvider()
{
Log.Debug("Provider Constructed");
- widgetDefinitionRegistry.Add("SSH_Wallet", new WidgetImplFactory());
- widgetDefinitionRegistry.Add("System_Memory", new WidgetImplFactory());
- widgetDefinitionRegistry.Add("System_NetworkUsage", new WidgetImplFactory());
- widgetDefinitionRegistry.Add("System_GPUUsage", new WidgetImplFactory());
- widgetDefinitionRegistry.Add("System_CPUUsage", new WidgetImplFactory());
+ _widgetDefinitionRegistry.Add("SSH_Wallet", new WidgetImplFactory());
+ _widgetDefinitionRegistry.Add("System_Memory", new WidgetImplFactory());
+ _widgetDefinitionRegistry.Add("System_NetworkUsage", new WidgetImplFactory());
+ _widgetDefinitionRegistry.Add("System_GPUUsage", new WidgetImplFactory());
+ _widgetDefinitionRegistry.Add("System_CPUUsage", new WidgetImplFactory());
RecoverRunningWidgets();
}
@@ -32,13 +32,13 @@ private void InitializeWidget(WidgetContext widgetContext, string state)
var widgetDefinitionId = widgetContext.DefinitionId;
Log.Debug($"Calling Initialize for Widget Id: {widgetId} - {widgetDefinitionId}");
- if (!widgetDefinitionRegistry.TryGetValue(widgetDefinitionId, out var value))
+ if (!_widgetDefinitionRegistry.TryGetValue(widgetDefinitionId, out var value))
{
Log.Error($"Unknown widget DefinitionId: {widgetDefinitionId}");
return;
}
- if (runningWidgets.ContainsKey(widgetId))
+ if (_runningWidgets.ContainsKey(widgetId))
{
Log.Warning($"Attempted to initialize a widget twice: {widgetDefinitionId} - {widgetId}");
return;
@@ -46,7 +46,7 @@ private void InitializeWidget(WidgetContext widgetContext, string state)
var factory = value;
var widgetImpl = factory.Create(widgetContext, state);
- runningWidgets.Add(widgetId, widgetImpl);
+ _runningWidgets.Add(widgetId, widgetImpl);
}
private void RecoverRunningWidgets()
@@ -70,7 +70,7 @@ private void RecoverRunningWidgets()
foreach (var widgetInfo in recoveredWidgets)
{
- if (!runningWidgets.ContainsKey(widgetInfo.WidgetContext.Id))
+ if (!_runningWidgets.ContainsKey(widgetInfo.WidgetContext.Id))
{
InitializeWidget(widgetInfo.WidgetContext, widgetInfo.CustomState);
}
@@ -89,7 +89,7 @@ public void Activate(WidgetContext widgetContext)
{
Log.Debug($"Activate id: {widgetContext.Id} definitionId: {widgetContext.DefinitionId}");
var widgetId = widgetContext.Id;
- if (runningWidgets.TryGetValue(widgetId, out var runningWidget))
+ if (_runningWidgets.TryGetValue(widgetId, out var runningWidget))
{
runningWidget.Activate(widgetContext);
}
@@ -98,7 +98,7 @@ public void Activate(WidgetContext widgetContext)
// Called to activate a widget that we don't know about, which is unexpected. Try to recover by creating it.
Log.Warning($"Found WidgetId that was not known: {widgetContext.Id}, attempting to recover by creating it.");
CreateWidget(widgetContext);
- if (runningWidgets.TryGetValue(widgetId, out var recoveredWidget))
+ if (_runningWidgets.TryGetValue(widgetId, out var recoveredWidget))
{
recoveredWidget.Activate(widgetContext);
}
@@ -108,7 +108,7 @@ public void Activate(WidgetContext widgetContext)
public void Deactivate(string widgetId)
{
Log.Debug($"Deactivate id: {widgetId}");
- if (runningWidgets.TryGetValue(widgetId, out var value))
+ if (_runningWidgets.TryGetValue(widgetId, out var value))
{
value.Deactivate(widgetId);
}
@@ -117,10 +117,10 @@ public void Deactivate(string widgetId)
public void DeleteWidget(string widgetId, string customState)
{
Log.Information($"DeleteWidget id: {widgetId}");
- if (runningWidgets.TryGetValue(widgetId, out var value))
+ if (_runningWidgets.TryGetValue(widgetId, out var value))
{
value.DeleteWidget(widgetId, customState);
- runningWidgets.Remove(widgetId);
+ _runningWidgets.Remove(widgetId);
}
}
@@ -129,7 +129,7 @@ public void OnActionInvoked(WidgetActionInvokedArgs actionInvokedArgs)
Log.Debug($"OnActionInvoked id: {actionInvokedArgs.WidgetContext.Id} definitionId: {actionInvokedArgs.WidgetContext.DefinitionId}");
var widgetContext = actionInvokedArgs.WidgetContext;
var widgetId = widgetContext.Id;
- if (runningWidgets.TryGetValue(widgetId, out var value))
+ if (_runningWidgets.TryGetValue(widgetId, out var value))
{
value.OnActionInvoked(actionInvokedArgs);
}
@@ -140,7 +140,7 @@ public void OnCustomizationRequested(WidgetCustomizationRequestedArgs customizat
Log.Debug($"OnCustomizationRequested id: {customizationRequestedArgs.WidgetContext.Id} definitionId: {customizationRequestedArgs.WidgetContext.DefinitionId}");
var widgetContext = customizationRequestedArgs.WidgetContext;
var widgetId = widgetContext.Id;
- if (runningWidgets.TryGetValue(widgetId, out var value))
+ if (_runningWidgets.TryGetValue(widgetId, out var value))
{
value.OnCustomizationRequested(customizationRequestedArgs);
}
@@ -151,7 +151,7 @@ public void OnWidgetContextChanged(WidgetContextChangedArgs contextChangedArgs)
Log.Debug($"OnWidgetContextChanged id: {contextChangedArgs.WidgetContext.Id} definitionId: {contextChangedArgs.WidgetContext.DefinitionId}");
var widgetContext = contextChangedArgs.WidgetContext;
var widgetId = widgetContext.Id;
- if (runningWidgets.TryGetValue(widgetId, out var value))
+ if (_runningWidgets.TryGetValue(widgetId, out var value))
{
value.OnWidgetContextChanged(contextChangedArgs);
}
diff --git a/extensions/CoreWidgetProvider/Widgets/WidgetServer.cs b/extensions/CoreWidgetProvider/Widgets/WidgetServer.cs
index 8aecdb8237..fcfbf44f1f 100644
--- a/extensions/CoreWidgetProvider/Widgets/WidgetServer.cs
+++ b/extensions/CoreWidgetProvider/Widgets/WidgetServer.cs
@@ -14,7 +14,7 @@ namespace CoreWidgetProvider.Widgets;
public sealed class WidgetServer : IDisposable
{
- private readonly HashSet registrationCookies = new();
+ private readonly HashSet _registrationCookies = new();
[UnconditionalSuppressMessage(
"ReflectionAnalysis",
@@ -41,7 +41,7 @@ public void RegisterWidget(Func createWidget)
Marshal.ThrowExceptionForHR(hr);
}
- registrationCookies.Add(cookie);
+ _registrationCookies.Add(cookie);
Log.Debug($"Cookie: {cookie}");
hr = PInvoke.CoResumeClassObjects();
if (hr < 0)
@@ -62,7 +62,7 @@ public void Run()
public void Dispose()
{
Log.Debug($"Revoking class object registrations:");
- foreach (var cookie in registrationCookies)
+ foreach (var cookie in _registrationCookies)
{
Log.Debug($"Cookie: {cookie}");
var hr = PInvoke.CoRevokeClassObject(cookie);
diff --git a/extensions/HyperVExtension/src/DevSetupAgent/DevSetupAgent.csproj b/extensions/HyperVExtension/src/DevSetupAgent/DevSetupAgent.csproj
index 5e9bebe632..8318ef70a6 100644
--- a/extensions/HyperVExtension/src/DevSetupAgent/DevSetupAgent.csproj
+++ b/extensions/HyperVExtension/src/DevSetupAgent/DevSetupAgent.csproj
@@ -9,6 +9,7 @@
win-x86;win-x64;win-arm64
Properties\PublishProfiles\win-$(Platform).pubxml
true
+ Debug;Release;Debug_FailFast
Exe
+ Debug;Release;Debug_FailFast
+
+
+ Exe
+ Debug;Release;Debug_FailFast
WinExe
@@ -45,7 +50,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/extensions/HyperVExtension/src/DevSetupEngine/Logging.cs b/extensions/HyperVExtension/src/DevSetupEngine/Logging.cs
index a2cdefcdfe..68791a87b7 100644
--- a/extensions/HyperVExtension/src/DevSetupEngine/Logging.cs
+++ b/extensions/HyperVExtension/src/DevSetupEngine/Logging.cs
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Windows.Storage;
-
namespace HyperVExtension.DevSetupEngine;
public class Logging
diff --git a/extensions/HyperVExtension/src/DevSetupEngine/PackageOperationException.cs b/extensions/HyperVExtension/src/DevSetupEngine/PackageOperationException.cs
index eaca912ed9..18837c217a 100644
--- a/extensions/HyperVExtension/src/DevSetupEngine/PackageOperationException.cs
+++ b/extensions/HyperVExtension/src/DevSetupEngine/PackageOperationException.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Microsoft.Management.Configuration;
using Serilog;
namespace HyperVExtension.DevSetupEngine;
diff --git a/extensions/HyperVExtension/src/DevSetupEngineIdl/DevSetupEngineIdl.vcxproj b/extensions/HyperVExtension/src/DevSetupEngineIdl/DevSetupEngineIdl.vcxproj
index 2b01279a1d..33be42d2fc 100644
--- a/extensions/HyperVExtension/src/DevSetupEngineIdl/DevSetupEngineIdl.vcxproj
+++ b/extensions/HyperVExtension/src/DevSetupEngineIdl/DevSetupEngineIdl.vcxproj
@@ -21,6 +21,18 @@
+
+ Debug_FailFast
+ ARM64
+
+
+ Debug_FailFast
+ Win32
+
+
+ Debug_FailFast
+ x64
+
Debug
ARM64
@@ -56,6 +68,10 @@
true
true
+
+ true
+ true
+
false
true
@@ -64,18 +80,27 @@
true
+
+ true
+
true
true
+
+ true
+
true
true
+
+ true
+
true
@@ -96,6 +121,10 @@
bin\x86\$(Configuration)\
obj\x86\$(Configuration)\
+
+ bin\x86\$(Configuration)\
+ obj\x86\$(Configuration)\
+
bin\$(Platform)\$(Configuration)\
obj\$(Platform)\$(Configuration)\
@@ -104,6 +133,10 @@
bin\$(Platform)\$(Configuration)\
obj\$(Platform)\$(Configuration)\
+
+ bin\$(Platform)\$(Configuration)\
+ obj\$(Platform)\$(Configuration)\
+
bin\$(Platform)\$(Configuration)\
obj\$(Platform)\$(Configuration)\
@@ -112,6 +145,10 @@
bin\$(Platform)\$(Configuration)\
obj\$(Platform)\$(Configuration)\
+
+ bin\$(Platform)\$(Configuration)\
+ obj\$(Platform)\$(Configuration)\
+
Use
@@ -132,6 +169,11 @@
_DEBUG;%(PreprocessorDefinitions)
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+
+
NDEBUG;%(PreprocessorDefinitions)
diff --git a/extensions/HyperVExtension/src/DevSetupEngineProjection/DevSetupEngineProjection.csproj b/extensions/HyperVExtension/src/DevSetupEngineProjection/DevSetupEngineProjection.csproj
index b610826fdf..32342a41d2 100644
--- a/extensions/HyperVExtension/src/DevSetupEngineProjection/DevSetupEngineProjection.csproj
+++ b/extensions/HyperVExtension/src/DevSetupEngineProjection/DevSetupEngineProjection.csproj
@@ -5,6 +5,7 @@
None
x86;x64;arm64
win-x86;win-x64;win-arm64
+ Debug;Release;Debug_FailFast
diff --git a/extensions/HyperVExtension/src/HyperVExtension.Common/HyperVExtension.Common.csproj b/extensions/HyperVExtension/src/HyperVExtension.Common/HyperVExtension.Common.csproj
index cc17ff4aad..630186d295 100644
--- a/extensions/HyperVExtension/src/HyperVExtension.Common/HyperVExtension.Common.csproj
+++ b/extensions/HyperVExtension/src/HyperVExtension.Common/HyperVExtension.Common.csproj
@@ -4,6 +4,7 @@
HyperVExtension.Common
x86;x64;arm64
win-x86;win-x64;win-arm64
+ Debug;Release;Debug_FailFast
diff --git a/extensions/HyperVExtension/src/HyperVExtension.HostGuestCommunication/HyperVExtension.HostGuestCommunication.csproj b/extensions/HyperVExtension/src/HyperVExtension.HostGuestCommunication/HyperVExtension.HostGuestCommunication.csproj
index 3c98d93a24..da9c656bfa 100644
--- a/extensions/HyperVExtension/src/HyperVExtension.HostGuestCommunication/HyperVExtension.HostGuestCommunication.csproj
+++ b/extensions/HyperVExtension/src/HyperVExtension.HostGuestCommunication/HyperVExtension.HostGuestCommunication.csproj
@@ -6,10 +6,11 @@
win-x86;win-x64;win-arm64
enable
enable
+ Debug;Release;Debug_FailFast
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/extensions/HyperVExtension/src/HyperVExtension.HostGuestCommunication/Providers/MessageHelper.cs b/extensions/HyperVExtension/src/HyperVExtension.HostGuestCommunication/Providers/MessageHelper.cs
index 2fdafbf8ac..50c10d1278 100644
--- a/extensions/HyperVExtension/src/HyperVExtension.HostGuestCommunication/Providers/MessageHelper.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension.HostGuestCommunication/Providers/MessageHelper.cs
@@ -3,9 +3,7 @@
using System.Globalization;
using System.Text;
-using System.Xml.Linq;
using Microsoft.Win32;
-using Serilog;
namespace HyperVExtension.HostGuestCommunication;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/GuestKvpChannel.cs b/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/GuestKvpChannel.cs
index 65c90294c5..75c0400a16 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/GuestKvpChannel.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/GuestKvpChannel.cs
@@ -6,7 +6,6 @@
using System.Xml.XPath;
using HyperVExtension.HostGuestCommunication;
using Serilog;
-using Windows.Devices.Sms;
namespace HyperVExtension.CommunicationWithGuest;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/GuestKvpSession.cs b/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/GuestKvpSession.cs
index 00f72f1a74..69ef7a1568 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/GuestKvpSession.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/GuestKvpSession.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Text.Json.Nodes;
using HyperVExtension.HostGuestCommunication;
namespace HyperVExtension.CommunicationWithGuest;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/Responses/ResponseBase.cs b/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/Responses/ResponseBase.cs
index 30062735c3..3ba6d8cab0 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/Responses/ResponseBase.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/Responses/ResponseBase.cs
@@ -3,7 +3,6 @@
using System.Text.Json;
using System.Text.Json.Nodes;
-using HyperVExtension.HostGuestCommunication;
namespace HyperVExtension.CommunicationWithGuest;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/WmiUtility.cs b/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/WmiUtility.cs
index 53d187ddcf..0e3ead50cd 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/WmiUtility.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/CommunicationWithGuest/WmiUtility.cs
@@ -1,9 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Globalization;
using System.Management;
-using Microsoft.Management.Infrastructure;
using Serilog;
using Windows.Win32.Foundation;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/Constants.cs b/extensions/HyperVExtension/src/HyperVExtension/Constants.cs
index 86771b8fcd..b60a4e42fb 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/Constants.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/Constants.cs
@@ -7,14 +7,14 @@ internal sealed class Constants
{
public const string WindowsThumbnail = "ms-appx:///HyperVExtension/Assets/hyper-v-windows-default-image.jpg";
-// We use different icon locations for different builds. Note these are ms-resource URIs, but are used by Dev Home to load the providers icon.
-// from the extension package. Extensions that implement the IComputeSystemProvider interface must provide a provider icon in this format.
-// Dev Home will use SHLoadIndirectString (https://learn.microsoft.com/windows/win32/api/shlwapi/nf-shlwapi-shloadindirectstring) to load the
-// location of the icon from the extension package.Once it gets this location, it will load the icon from the path and display it in the UI.
-// Icons should be located in an extension resource.pri file which is generated at build time.
-// See the MakePri.exe documentation for how you can view what is in the resource.pri file, so you can find the location of your icon.
-// https://learn.microsoft.com/en-us/windows/uwp/app-resources/makepri-exe-command-options. (use MakePri.exe in a VS Developer Command Prompt or
-// Powershell window)
+ // We use different icon locations for different builds. Note these are ms-resource URIs, but are used by Dev Home to load the providers icon.
+ // from the extension package. Extensions that implement the IComputeSystemProvider interface must provide a provider icon in this format.
+ // Dev Home will use SHLoadIndirectString (https://learn.microsoft.com/windows/win32/api/shlwapi/nf-shlwapi-shloadindirectstring) to load the
+ // location of the icon from the extension package.Once it gets this location, it will load the icon from the path and display it in the UI.
+ // Icons should be located in an extension resource.pri file which is generated at build time.
+ // See the MakePri.exe documentation for how you can view what is in the resource.pri file, so you can find the location of your icon.
+ // https://learn.microsoft.com/en-us/windows/uwp/app-resources/makepri-exe-command-options. (use MakePri.exe in a VS Developer Command Prompt or
+ // Powershell window)
#if CANARY_BUILD
public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome.Canary/Files/HyperVExtension/Assets/hyper-v-provider-icon.png";
#elif STABLE_BUILD
diff --git a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/AdaptiveCardInvalidActionException.cs b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/AdaptiveCardInvalidActionException.cs
index 38c73aeddc..3ddb59c3c6 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/AdaptiveCardInvalidActionException.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/AdaptiveCardInvalidActionException.cs
@@ -1,12 +1,6 @@
// 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
diff --git a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/DownloadOperationCancelledException.cs b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/DownloadOperationCancelledException.cs
index b37b310c14..1ee4ac8213 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/DownloadOperationCancelledException.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/DownloadOperationCancelledException.cs
@@ -1,11 +1,6 @@
// 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 HyperVExtension.Common;
namespace HyperVExtension.Exceptions;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/DownloadOperationFailedException.cs b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/DownloadOperationFailedException.cs
index 2c8f3ff265..6144524039 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/DownloadOperationFailedException.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/DownloadOperationFailedException.cs
@@ -1,11 +1,6 @@
// 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 HyperVExtension.Common;
namespace HyperVExtension.Exceptions;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/NoVMImagesAvailableException.cs b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/NoVMImagesAvailableException.cs
index 0ca74d2c0f..b0680f5c2a 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/NoVMImagesAvailableException.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/NoVMImagesAvailableException.cs
@@ -1,11 +1,6 @@
// 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 HyperVExtension.Common;
namespace HyperVExtension.Exceptions;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/OperationInProgressException.cs b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/OperationInProgressException.cs
index ba4947b2df..2347ad1c8e 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/OperationInProgressException.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/OperationInProgressException.cs
@@ -1,11 +1,6 @@
// 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 HyperVExtension.Common;
namespace HyperVExtension.Exceptions;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/VirtualMachineManagementServiceException.cs b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/VirtualMachineManagementServiceException.cs
index a9f3ad1720..3c5f0d3c1f 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/Exceptions/VirtualMachineManagementServiceException.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/Exceptions/VirtualMachineManagementServiceException.cs
@@ -1,12 +1,6 @@
// 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 VirtualMachineManagementServiceException : Exception
diff --git a/extensions/HyperVExtension/src/HyperVExtension/Extensions/StreamExtensions.cs b/extensions/HyperVExtension/src/HyperVExtension/Extensions/StreamExtensions.cs
index c44703db17..59f008d8ba 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/Extensions/StreamExtensions.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/Extensions/StreamExtensions.cs
@@ -1,11 +1,6 @@
// 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 HyperVExtension.Models;
namespace HyperVExtension.Extensions;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/Helpers/BytesHelper.cs b/extensions/HyperVExtension/src/HyperVExtension/Helpers/BytesHelper.cs
index 41621ae0cb..fd21d8f4a3 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/Helpers/BytesHelper.cs
+++ b/extensions/HyperVExtension/src/HyperVExtension/Helpers/BytesHelper.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System;
using System.Globalization;
using Windows.Win32;
using Windows.Win32.Foundation;
diff --git a/extensions/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj b/extensions/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj
index d6ec3808d5..7cc65e2016 100644
--- a/extensions/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj
+++ b/extensions/HyperVExtension/src/HyperVExtension/HyperVExtension.csproj
@@ -6,6 +6,7 @@
enable
Dev
win-x86;win-x64;win-arm64
+ Debug;Release;Debug_FailFast
@@ -36,7 +37,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -60,13 +61,6 @@
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
Exe
+ Debug;Release;Debug_FailFast
+
+
+ Exe
+ Debug;Release;Debug_FailFast
WinExe
@@ -25,10 +30,6 @@
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
diff --git a/extensions/HyperVExtension/src/HyperVExtensionServer/Program.cs b/extensions/HyperVExtension/src/HyperVExtensionServer/Program.cs
index 9d8e4a6e73..452474885b 100644
--- a/extensions/HyperVExtension/src/HyperVExtensionServer/Program.cs
+++ b/extensions/HyperVExtension/src/HyperVExtensionServer/Program.cs
@@ -9,7 +9,6 @@
using Microsoft.Windows.AppLifecycle;
using Serilog;
using Windows.ApplicationModel.Activation;
-using Windows.Storage;
namespace HyperVExtension;
@@ -112,7 +111,7 @@ private static void HandleCOMServerActivation()
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
extensionServer.RegisterExtension(() => hyperVExtension, true);
- // This will make the main thread wait until the event is signalled by the extension class.
+ // This will make the main thread wait until the event is signaled by the extension class.
// Since we have single instance of the extension object, we exit as soon as it is disposed.
hyperVExtension.ExtensionDisposedEvent.WaitOne();
Log.Information($"Extension is disposed.");
diff --git a/extensions/HyperVExtension/src/Telemetry/HyperVExtension.Telemetry.csproj b/extensions/HyperVExtension/src/Telemetry/HyperVExtension.Telemetry.csproj
index d096236e4e..eb7769426b 100644
--- a/extensions/HyperVExtension/src/Telemetry/HyperVExtension.Telemetry.csproj
+++ b/extensions/HyperVExtension/src/Telemetry/HyperVExtension.Telemetry.csproj
@@ -5,6 +5,7 @@
x86;x64;arm64
win-x86;win-x64;win-arm64
true
+ Debug;Release;Debug_FailFast
@@ -18,11 +19,4 @@
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
diff --git a/extensions/HyperVExtension/test/DevSetupAgent.Test/DevSetupAgent.Test.csproj b/extensions/HyperVExtension/test/DevSetupAgent.Test/DevSetupAgent.Test.csproj
index 569a042b29..ff43ef9e4f 100644
--- a/extensions/HyperVExtension/test/DevSetupAgent.Test/DevSetupAgent.Test.csproj
+++ b/extensions/HyperVExtension/test/DevSetupAgent.Test/DevSetupAgent.Test.csproj
@@ -7,6 +7,7 @@
x86;x64;arm64
false
true
+ Debug;Release;Debug_FailFast
diff --git a/extensions/HyperVExtension/test/DevSetupEngine.Test/DevSetupEngine.Test.csproj b/extensions/HyperVExtension/test/DevSetupEngine.Test/DevSetupEngine.Test.csproj
index caeb673ffe..2f6e2dcc2e 100644
--- a/extensions/HyperVExtension/test/DevSetupEngine.Test/DevSetupEngine.Test.csproj
+++ b/extensions/HyperVExtension/test/DevSetupEngine.Test/DevSetupEngine.Test.csproj
@@ -7,6 +7,7 @@
x86;x64;arm64
false
true
+ Debug;Release;Debug_FailFast
@@ -16,7 +17,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/extensions/HyperVExtension/test/DevSetupEngine.Test/DevSetupEngineIntegrationTest.cs b/extensions/HyperVExtension/test/DevSetupEngine.Test/DevSetupEngineIntegrationTest.cs
index a7aa327f34..82cbff95be 100644
--- a/extensions/HyperVExtension/test/DevSetupEngine.Test/DevSetupEngineIntegrationTest.cs
+++ b/extensions/HyperVExtension/test/DevSetupEngine.Test/DevSetupEngineIntegrationTest.cs
@@ -3,7 +3,6 @@
using System.Runtime.InteropServices;
using Microsoft.Extensions.Hosting;
-using Microsoft.Win32;
using Microsoft.Windows.DevHome.DevSetupEngine;
using Windows.Win32;
using Windows.Win32.System.Com;
diff --git a/extensions/HyperVExtension/test/HyperVExtension/HyperVExtension.UnitTest.csproj b/extensions/HyperVExtension/test/HyperVExtension/HyperVExtension.UnitTest.csproj
index 40bae818a2..d9d9970e95 100644
--- a/extensions/HyperVExtension/test/HyperVExtension/HyperVExtension.UnitTest.csproj
+++ b/extensions/HyperVExtension/test/HyperVExtension/HyperVExtension.UnitTest.csproj
@@ -10,6 +10,7 @@
true
resources.pri
true
+ Debug;Release;Debug_FailFast
@@ -26,12 +27,6 @@
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
diff --git a/extensions/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVExtensionIntegrationTest.cs b/extensions/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVExtensionIntegrationTest.cs
index 8de473bab9..c71b2508fd 100644
--- a/extensions/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVExtensionIntegrationTest.cs
+++ b/extensions/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVExtensionIntegrationTest.cs
@@ -3,12 +3,10 @@
using System.Diagnostics;
using System.Globalization;
-using System.IO;
using System.Management.Automation;
using System.Net;
using System.Text;
using System.Text.Json;
-using System.Threading;
using HyperVExtension.Common;
using HyperVExtension.Common.Extensions;
using HyperVExtension.Helpers;
@@ -22,7 +20,6 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Windows.DevHome.SDK;
using Moq;
-using Windows.Storage;
namespace HyperVExtension.UnitTest.HyperVExtensionTests.Services;
@@ -160,18 +157,18 @@ public async Task TestConfigureRequest()
{
if (actionRequired.CorrectiveActionCardSession is VmCredentialAdaptiveCardSession credentialsCardSession)
{
- var extensionAdaptiveCard = new Mock();
- extensionAdaptiveCard
- .Setup(x => x.Update(It.IsAny(), It.IsAny(), It.IsAny()))
- .Returns((string templateJson, string dataJson, string state) => new ProviderOperationResult(ProviderOperationStatus.Success, null, null, null));
+ var extensionAdaptiveCard = new Mock();
+ extensionAdaptiveCard
+ .Setup(x => x.Update(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns((string templateJson, string dataJson, string state) => new ProviderOperationResult(ProviderOperationStatus.Success, null, null, null));
- extensionAdaptiveCard
- .Setup(x => x.State)
- .Returns("VmCredential");
+ extensionAdaptiveCard
+ .Setup(x => x.State)
+ .Returns("VmCredential");
- credentialsCardSession.Initialize(extensionAdaptiveCard.Object);
- var op = credentialsCardSession.OnAction(@"{ ""Type"": ""Action.Execute"", ""Id"": ""okAction"" }", @"{ ""id"": ""okAction"", ""UserVal"": """", ""PassVal"": """" }");
- await op.AsTask();
+ credentialsCardSession.Initialize(extensionAdaptiveCard.Object);
+ var op = credentialsCardSession.OnAction(@"{ ""Type"": ""Action.Execute"", ""Id"": ""okAction"" }", @"{ ""id"": ""okAction"", ""UserVal"": """", ""PassVal"": """" }");
+ await op.AsTask();
}
else if (actionRequired.CorrectiveActionCardSession is WaitForLoginAdaptiveCardSession waitForLoginCardSession)
{
diff --git a/extensions/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVProviderTests.cs b/extensions/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVProviderTests.cs
index 68a62af01c..0ef13e3883 100644
--- a/extensions/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVProviderTests.cs
+++ b/extensions/HyperVExtension/test/HyperVExtension/HyperVExtensionTests/Services/HyperVProviderTests.cs
@@ -7,11 +7,9 @@
using HyperVExtension.Common.Extensions;
using HyperVExtension.Helpers;
using HyperVExtension.Models.VirtualMachineCreation;
-using HyperVExtension.Providers;
using HyperVExtension.UnitTest.Mocks;
using Microsoft.Windows.DevHome.SDK;
using Windows.Storage;
-using static System.Net.Mime.MediaTypeNames;
namespace HyperVExtension.UnitTest.HyperVExtensionTests.Services;
diff --git a/extensions/HyperVExtension/test/HyperVExtension/Mocks/PSCustomObjectMock.cs b/extensions/HyperVExtension/test/HyperVExtension/Mocks/PSCustomObjectMock.cs
index 3c0298ad69..22e4b35513 100644
--- a/extensions/HyperVExtension/test/HyperVExtension/Mocks/PSCustomObjectMock.cs
+++ b/extensions/HyperVExtension/test/HyperVExtension/Mocks/PSCustomObjectMock.cs
@@ -1,9 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.ServiceProcess;
-using HyperVExtension.Helpers;
-
namespace HyperVExtension.UnitTest.Mocks;
public enum HyperVState
diff --git a/extensions/WSLExtension/ClassExtensions/IHostExtensions.cs b/extensions/WSLExtension/ClassExtensions/IHostExtensions.cs
new file mode 100644
index 0000000000..9cc1fe9e41
--- /dev/null
+++ b/extensions/WSLExtension/ClassExtensions/IHostExtensions.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace WSLExtension.ClassExtensions;
+
+public static class IHostExtensions
+{
+ ///
+ public static T CreateInstance(this IHost host, params object[] parameters)
+ {
+ return ActivatorUtilities.CreateInstance(host.Services, parameters);
+ }
+
+ ///
+ /// Gets the service object for the specified type, or throws an exception
+ /// if type was not registered.
+ ///
+ /// Service type
+ /// Host object
+ /// Service object
+ /// Throw an exception if the specified
+ /// type is not registered
+ public static T GetService(this IHost host)
+ where T : class
+ {
+ if (host.Services.GetService(typeof(T)) is not T service)
+ {
+ throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices.");
+ }
+
+ return service;
+ }
+}
diff --git a/extensions/WSLExtension/ClassExtensions/ResourceExtensions.cs b/extensions/WSLExtension/ClassExtensions/ResourceExtensions.cs
new file mode 100644
index 0000000000..4ae87ea97e
--- /dev/null
+++ b/extensions/WSLExtension/ClassExtensions/ResourceExtensions.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Windows.ApplicationModel.Resources;
+
+namespace WSLExtension.ClassExtensions;
+
+public static class ResourceExtensions
+{
+ private static readonly ResourceLoader _resourceLoader = new();
+
+ public static string GetLocalized(this string resourceKey) => _resourceLoader.GetString(resourceKey);
+}
diff --git a/extensions/WSLExtension/ClassExtensions/ServiceExtensions.cs b/extensions/WSLExtension/ClassExtensions/ServiceExtensions.cs
new file mode 100644
index 0000000000..942f86613d
--- /dev/null
+++ b/extensions/WSLExtension/ClassExtensions/ServiceExtensions.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Windows.DevHome.SDK;
+using WSLExtension.Contracts;
+using WSLExtension.DevHomeProviders;
+using WSLExtension.DistributionDefinitions;
+using WSLExtension.Models;
+using WSLExtension.Services;
+
+namespace WSLExtension.ClassExtensions;
+
+public static class ServiceExtensions
+{
+ public static IServiceCollection AddWslExtensionServices(this IServiceCollection services)
+ {
+ // Services
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ // Factory delegate to create WslComputeSystems
+ services.AddSingleton(
+ serviceProvider => wslDistribution => ActivatorUtilities.CreateInstance(serviceProvider, wslDistribution));
+
+ return services;
+ }
+}
diff --git a/extensions/WSLExtension/ClassExtensions/StringExtensions.cs b/extensions/WSLExtension/ClassExtensions/StringExtensions.cs
new file mode 100644
index 0000000000..a233ab45cd
--- /dev/null
+++ b/extensions/WSLExtension/ClassExtensions/StringExtensions.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Globalization;
+
+namespace WSLExtension.ClassExtensions;
+
+public static class StringExtensions
+{
+ public static string FormatArgs(this string source, params object[] args)
+ {
+ return string.Format(CultureInfo.InvariantCulture, source, args);
+ }
+}
diff --git a/extensions/WSLExtension/Constants.cs b/extensions/WSLExtension/Constants.cs
new file mode 100644
index 0000000000..cd07902e9b
--- /dev/null
+++ b/extensions/WSLExtension/Constants.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace WSLExtension;
+
+public static class Constants
+{
+#if CANARY_BUILD
+ public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome.Canary/Files/WslAssets/wslLinux.png";
+#elif STABLE_BUILD
+ public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome/Files/WslAssets/wslLinux.png";
+#else
+ public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome.Dev/Files/WslAssets/wslLinux.png";
+#endif
+
+ // Common unlocalized strings used for WSL extension
+ public const string WslProviderDisplayName = "Microsoft WSL";
+ public const string WslProviderId = "Microsoft.WSL";
+ public const string WindowsTerminalShimExe = "wt.exe";
+ public const string WindowsTerminalPackageFamilyName = "Microsoft.WindowsTerminal_8wekyb3d8bbwe";
+ public const string WslExe = "wsl.exe";
+ public const string WslTemplateSubfolderName = "WslTemplates";
+
+ public const string DefaultWslLogoPath = @"ms-appx:///WslAssets/wslLinux.png";
+ public const string WslLogoPathFormat = @"ms-appx:///WslAssets/{0}";
+ public const string KnownDistributionsLocalYamlLocation = @"ms-appx:///DistributionDefinitions/DistributionDefinition.yaml";
+ public const string KnownDistributionsWebJsonLocation = @"https://aka.ms/wsldistributionsjson";
+
+ // Wsl registry location for registered distributions.
+ public const string WslRegistryLocation = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss";
+
+ // Wsl registry data names within a distribution location.
+ public const string PackageFamilyRegistryName = "PackageFamilyName";
+ public const string DistributionRegistryName = "DistributionName";
+ public const string DefaultDistributionRegistryName = "DefaultDistribution";
+ public const string WslVersion = "Version";
+ public const string WslState = "State";
+ public const int WslVersion1 = 1;
+ public const int WslVersion2 = 2;
+ public const int WslExeExitSuccess = 0;
+
+ // Launch terminal with specific profile and log the user into their home directory in the login shell
+ // Note: this opens a new terminal window in the UI
+ public static string LaunchDistributionInTerminalWithProfile { get; } = "--profile {0} -- wsl --shell-type login --cd ~ --distribution {1}";
+
+ // Launch without using a Terminal profile and log the user into their home directory using the login shell
+ // Note: this opens a new terminal window in the UI
+ public static string LaunchDistributionInTerminalWithNoProfile { get; } = "wsl --shell-type login --cd ~ --distribution {0}";
+
+ // Launch into the wsl process without terminal and log the user into their home directory using the login shell
+ // Note: this opens a new terminal window in the UI
+ public static string LaunchDistributionWithoutTerminal { get; } = "--shell-type login --cd ~ --distribution {0}";
+
+ // Arguments to unregister a wsl distribution from a machine using wsl.exe
+ public const string UnregisterDistributionArgs = "--unregister {0}";
+
+ // Arguments to terminate all wsl sessions for a specific distribution using wsl.exe
+ public const string TerminateDistributionArgs = "--terminate {0}";
+
+ // Arguments to download, install and register a wsl distribution using Terminal
+ // Note: this opens a new terminal window in the UI
+ public const string InstallDistributionWithTerminal = "wsl --install --distribution {0}";
+
+ // Arguments to download, install and register a wsl distribution without using terminal
+ // Note: this opens a cmd window in the UI
+ public const string InstallDistributionWithoutTerminal = "--install --distribution {0}";
+
+ // Arguments to list of all running distributions on a machine using wsl.exe
+ public const string ListAllRunningDistributions = "--list --running";
+}
diff --git a/extensions/WSLExtension/Contracts/IDistributionDefinitionHelper.cs b/extensions/WSLExtension/Contracts/IDistributionDefinitionHelper.cs
new file mode 100644
index 0000000000..562c971074
--- /dev/null
+++ b/extensions/WSLExtension/Contracts/IDistributionDefinitionHelper.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using WSLExtension.DistributionDefinitions;
+
+namespace WSLExtension.Contracts;
+
+///
+/// Provides definition information about all the WSL distributions that can be found at
+/// .
+///
+public interface IDistributionDefinitionHelper
+{
+ ///
+ /// Retrieves a list of objects that contain metadata about WSL distributions that can be
+ /// installed from the wsl.exe executable.
+ ///
+ ///
+ /// A Dictionary where the key is the name of the distribution and the value is its
+ /// metadata.
+ ///
+ public Task> GetDistributionDefinitionsAsync();
+}
diff --git a/extensions/WSLExtension/Contracts/IProcessCreator.cs b/extensions/WSLExtension/Contracts/IProcessCreator.cs
new file mode 100644
index 0000000000..a18bf217f6
--- /dev/null
+++ b/extensions/WSLExtension/Contracts/IProcessCreator.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using WSLExtension.Models;
+
+namespace WSLExtension.Contracts;
+
+///
+/// Interface used to create processes throughout the WSL extension.
+///
+public interface IProcessCreator
+{
+ ///
+ /// Creates and starts a new process that opens in a new Window. Note:
+ /// The process is started and the method does not wait until the process
+ /// has exited before returning.
+ ///
+ /// The name of the executable to start
+ /// The arguments that will be passed to the executable at process startup
+ public void CreateProcessWithWindow(string fileName, string arguments);
+
+ ///
+ /// Creates and starts a new process without opening a window. Note: execution is blocked
+ /// until the process exits.
+ ///
+ /// The name of the executable to start
+ /// The arguments that will be passed to the executable at process startup
+ /// The meta data associated with the exited process.
+ /// E.g StdOutput, StdError and its exit code.
+ ///
+ public WslProcessData CreateProcessWithoutWindowAndWaitForExit(string fileName, string arguments);
+}
diff --git a/extensions/WSLExtension/Contracts/IStringResource.cs b/extensions/WSLExtension/Contracts/IStringResource.cs
new file mode 100644
index 0000000000..7feec156dd
--- /dev/null
+++ b/extensions/WSLExtension/Contracts/IStringResource.cs
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace WSLExtension.Contracts;
+
+public interface IStringResource
+{
+ public string GetLocalized(string key, params object[] args);
+}
diff --git a/extensions/WSLExtension/Contracts/IWslManager.cs b/extensions/WSLExtension/Contracts/IWslManager.cs
new file mode 100644
index 0000000000..eadaf4b014
--- /dev/null
+++ b/extensions/WSLExtension/Contracts/IWslManager.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using WSLExtension.DistributionDefinitions;
+using WSLExtension.Models;
+
+namespace WSLExtension.Contracts;
+
+///
+/// Used to interact between the WSL dev environment compute systems and the WSL mediator.
+///
+public interface IWslManager
+{
+ public event EventHandler>? DistributionStateSyncEventHandler;
+
+ /// Gets a list of all registered WSL distributions on the machine.
+ public Task> GetAllRegisteredDistributionsAsync();
+
+ ///
+ /// Gets a list of objects that each contain metadata about a wsl distribution
+ /// that is not currently registered on the machine and is available to install.
+ ///
+ public Task> GetAllDistributionsAvailableToInstallAsync();
+
+ ///
+ /// Gets a list of objects that each contain information about a WSL distribution that is already
+ /// registered on the machine.
+ ///
+ public Task GetInformationOnRegisteredDistributionAsync(string distributionName);
+
+ ///
+ /// Unregisters a WSL distribution. This is a wrapper for
+ ///
+ void UnregisterDistribution(string distributionName);
+
+ /// Launches a new WSL distribution.
+ /// This is a wrapper for
+ ///
+ void LaunchDistribution(string distributionName, string? windowsTerminalProfile);
+
+ /// Installs a new WSL distribution.
+ /// This is a wrapper for
+ ///
+ void InstallDistribution(string distributionName);
+
+ /// Terminates all sessions for a new WSL distribution.
+ /// This is a wrapper for
+ ///
+ void TerminateDistribution(string distributionName);
+
+ /// Gets a boolean indicating whether the WSL distribution is currently running.
+ /// This is a wrapper for
+ ///
+ public bool IsDistributionRunning(string distributionName);
+}
diff --git a/extensions/WSLExtension/Contracts/IWslServicesMediator.cs b/extensions/WSLExtension/Contracts/IWslServicesMediator.cs
new file mode 100644
index 0000000000..2915dea08f
--- /dev/null
+++ b/extensions/WSLExtension/Contracts/IWslServicesMediator.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using WSLExtension.Models;
+
+namespace WSLExtension.Contracts;
+
+///
+/// Used to interact between the WSL service on the machine and
+/// the wsl extension itself.
+///
+public interface IWslServicesMediator
+{
+ ///
+ /// Gets a set of all currently running distributions. Note: each WSL distribution
+ /// name is unique.
+ ///
+ public HashSet GetAllNamesOfRunningDistributions();
+
+ ///
+ /// Gets a list of the registered distributions on the machine.
+ ///
+ public List GetAllRegisteredDistributions();
+
+ ///
+ /// Unregisters a WSL distribution from the WSL service. Note: This is the same as deleting the
+ /// distribution and any of its associated data.
+ ///
+ void UnregisterDistribution(string distributionName);
+
+ /// Launches a new WSL process with the provided distribution.
+ void LaunchDistribution(string distributionName, string? windowsTerminalProfile);
+
+ /// Installs and registers a new distribution on the machine.
+ void InstallDistribution(string distributionName);
+
+ /// Terminates all running WSL sessions for the provided distribution on the machine.
+ void TerminateDistribution(string distributionName);
+
+ /// Checks whether the provided WSL distribution is currently running
+ /// True only if the distribution is running. False otherwise.
+ public bool IsDistributionRunning(string distributionName);
+}
diff --git a/extensions/WSLExtension/DevHomeProviders/WslProvider.cs b/extensions/WSLExtension/DevHomeProviders/WslProvider.cs
new file mode 100644
index 0000000000..1762a8269e
--- /dev/null
+++ b/extensions/WSLExtension/DevHomeProviders/WslProvider.cs
@@ -0,0 +1,100 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using Microsoft.Windows.DevHome.SDK;
+using Serilog;
+using Windows.Foundation;
+using WSLExtension.Contracts;
+using WSLExtension.Models;
+using static WSLExtension.Constants;
+
+namespace WSLExtension.DevHomeProviders;
+
+/// Provides functionality to enumerate and install WSL distributions
+public class WslProvider : IComputeSystemProvider
+{
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WslProvider));
+
+ private readonly IStringResource _stringResource;
+
+ private readonly IWslManager _wslManager;
+
+ public string DisplayName => WslProviderDisplayName;
+
+ public Uri Icon { get; }
+
+ public string Id => WslProviderId;
+
+ public ComputeSystemProviderOperations SupportedOperations => ComputeSystemProviderOperations.CreateComputeSystem;
+
+ public WslProvider(IStringResource stringResource, IWslManager wslManager)
+ {
+ _stringResource = stringResource;
+ _wslManager = wslManager;
+ Icon = new(ExtensionIcon);
+ }
+
+ ///
+ /// Creates and returns the adaptive card session that will appear in the create environment creation UX in Dev Home.
+ ///
+ public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForDeveloperId(IDeveloperId developerId, ComputeSystemAdaptiveCardKind sessionKind)
+ {
+ var definitions = _wslManager.GetAllDistributionsAvailableToInstallAsync().GetAwaiter().GetResult();
+ return new ComputeSystemAdaptiveCardResult(new RegisterAndInstallDistributionSession(definitions, _stringResource));
+ }
+
+ ///
+ /// Creates the operation that when started will install and register the WSL distribution.
+ ///
+ public ICreateComputeSystemOperation? CreateCreateComputeSystemOperation(IDeveloperId? developerId, string inputJson)
+ {
+ try
+ {
+ var deserializedObject = JsonSerializer.Deserialize(inputJson, typeof(WslInstallationUserInput));
+ var wslInstallationUserInput =
+ deserializedObject as WslInstallationUserInput ?? throw new InvalidOperationException($"Json deserialization failed for input Json: {inputJson}");
+
+ var definitions = _wslManager.GetAllDistributionsAvailableToInstallAsync().GetAwaiter().GetResult();
+ return new WslInstallDistributionOperation(
+ definitions[wslInstallationUserInput.SelectedDistributionIndex],
+ _stringResource,
+ _wslManager);
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Failed to create the compute system creation operation. InputJson: {inputJson}");
+
+ // 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.
+ return null;
+ }
+ }
+
+ public IAsyncOperation GetComputeSystemsAsync(IDeveloperId developerId)
+ {
+ return Task.Run(async () =>
+ {
+ try
+ {
+ var computeSystems = await _wslManager.GetAllRegisteredDistributionsAsync();
+
+ _log.Information($"Successfully retrieved all wsl distributions");
+ return new ComputeSystemsResult(computeSystems);
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Failed to retrieve all wsl distributions");
+ return new ComputeSystemsResult(ex, ex.Message, ex.Message);
+ }
+ }).AsAsyncOperation();
+ }
+
+ public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForComputeSystem(
+ IComputeSystem computeSystem,
+ ComputeSystemAdaptiveCardKind sessionKind)
+ {
+ var notImplementedException = new NotImplementedException($"Method not implemented by WSL Compute System Provider");
+ return new ComputeSystemAdaptiveCardResult(notImplementedException, notImplementedException.Message, notImplementedException.Message);
+ }
+}
diff --git a/extensions/WSLExtension/DistributionDefinitions/DistributionDefinition.cs b/extensions/WSLExtension/DistributionDefinitions/DistributionDefinition.cs
new file mode 100644
index 0000000000..956743dd1d
--- /dev/null
+++ b/extensions/WSLExtension/DistributionDefinitions/DistributionDefinition.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace WSLExtension.DistributionDefinitions;
+
+///
+/// Represents a definition of a WSL distribution. This metadata is used
+/// to deserialize the WSL DistributionInfo.json file located in
+/// . It is also
+/// used to deserialize the WSL definitions located in the local
+/// file.
+///
+public class DistributionDefinition
+{
+ public string FriendlyName { get; set; } = string.Empty;
+
+ public string Name { get; set; } = string.Empty;
+
+ public string LogoFile { get; set; } = string.Empty;
+
+ public string Base64StringLogo { get; set; } = string.Empty;
+
+ public string? WindowsTerminalProfileGuid { get; set; }
+
+ public string? StoreAppId { get; set; }
+
+ [JsonPropertyName("Amd64")]
+ public bool IsAmd64Supported { get; set; }
+
+ [JsonPropertyName("Arm64")]
+ public bool IsArm64Supported { get; set; }
+
+ public string PackageFamilyName { get; set; } = string.Empty;
+
+ public string Publisher { get; set; } = string.Empty;
+}
diff --git a/extensions/WSLExtension/DistributionDefinitions/DistributionDefinition.yaml b/extensions/WSLExtension/DistributionDefinitions/DistributionDefinition.yaml
new file mode 100644
index 0000000000..8824e88616
--- /dev/null
+++ b/extensions/WSLExtension/DistributionDefinitions/DistributionDefinition.yaml
@@ -0,0 +1,74 @@
+# This is used to map a distributions name to it logo file in the WslAssets folder. These distribution names
+# should be kept in sync with the distributionInfo.json file here: https://aka.ms/wsldistributionsjson.
+# Note: Cela has approved the usage of these image assets in Dev Home as they are used for non-marketing purposes.
+---
+# See: https://www.debian.org/logos/ for trademark information.
+- Name: Debian
+ LogoFile: debian.png
+ Publisher: The Debian Project
+
+# See: https://www.kali.org/docs/policy/trademark/ for trademark information.
+- Name: kali-linux
+ LogoFile: kali.png
+ Publisher: Kali Linux
+
+# See: https://en.opensuse.org/openSUSE:Trademark_guidelines#Redistributing_openSUSE_Without_Modifications for trademark information.
+- Name: openSUSE-Leap-15.5
+ LogoFile: opensuse.png
+ Publisher: SUSE
+
+# See: https://en.opensuse.org/openSUSE:Trademark_guidelines#Redistributing_openSUSE_Without_Modifications for trademark information.
+- Name: openSUSE-Tumbleweed
+ LogoFile: opensuse.png
+ Publisher: SUSE
+
+ # We don't store oracle logos in Dev Home.
+- Name: OracleLinux_7_9
+ Publisher: Oracle America, Inc.
+
+- Name: OracleLinux_8_7
+ Publisher: Oracle America, Inc.
+
+- Name: OracleLinux_9_1
+ Publisher: Oracle America, Inc.
+
+# See: https://en.opensuse.org/openSUSE:Trademark_guidelines#Redistributing_openSUSE_Without_Modifications for trademark information.
+- Name: SUSE-Linux-Enterprise-Server-15-SP4
+ LogoFile: openuse-enterprise.png
+ Publisher: SUSE
+
+# logo can be found: https://github.com/openSUSE/artwork/blob/master/logos/buttons/button-colour.png
+# See: https://en.opensuse.org/openSUSE:Trademark_guidelines#Redistributing_openSUSE_Without_Modifications for trademark information.
+- Name: SUSE-Linux-Enterprise-15-SP5
+ LogoFile: openuse-enterprise.png
+ Publisher: SUSE
+
+# See heading 6 at: https://ubuntu.com/legal/intellectual-property-policy for trademark information.
+- Name: Ubuntu
+ WindowsTerminalProfileGuid: "{51855cb2-8cce-5362-8f54-464b92b32386}"
+ LogoFile: ubuntu.png
+ Publisher: Canonical Group Limited
+
+# See heading 6 at: https://ubuntu.com/legal/intellectual-property-policy for trademark information.
+- Name: Ubuntu-18.04
+ WindowsTerminalProfileGuid: "{24a0533e-913b-5f8c-a5cb-6be85a4c9e70}"
+ LogoFile: ubuntu.png
+ Publisher: Canonical Group Limited
+
+# See heading 6 at: https://ubuntu.com/legal/intellectual-property-policy for trademark information.
+- Name: Ubuntu-20.04
+ WindowsTerminalProfileGuid: "{4dd1e689-b517-5f39-947d-78e8a8bdf958}"
+ LogoFile: ubuntu.png
+ Publisher: Canonical Group Limited
+
+# See heading 6 at: https://ubuntu.com/legal/intellectual-property-policy for trademark information.
+- Name: Ubuntu-22.04
+ WindowsTerminalProfileGuid: "{e5a83caa-4c73-52b3-ae6b-bc438d721ef9}"
+ LogoFile: ubuntu.png
+ Publisher: Canonical Group Limited
+
+# See heading 6 at: https://ubuntu.com/legal/intellectual-property-policy for trademark information.
+- Name: Ubuntu-24.04
+ WindowsTerminalProfileGuid: "{acbafd15-cbbb-5bb3-8a61-bed446ff4b83}"
+ LogoFile: ubuntu.png
+ Publisher: Canonical Group Limited
diff --git a/extensions/WSLExtension/DistributionDefinitions/DistributionDefinitionHelper.cs b/extensions/WSLExtension/DistributionDefinitions/DistributionDefinitionHelper.cs
new file mode 100644
index 0000000000..9b31040add
--- /dev/null
+++ b/extensions/WSLExtension/DistributionDefinitions/DistributionDefinitionHelper.cs
@@ -0,0 +1,142 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Runtime.InteropServices;
+using System.Text.Json;
+using Serilog;
+using Windows.Storage;
+using WSLExtension.ClassExtensions;
+using WSLExtension.Contracts;
+using WSLExtension.Helpers;
+using YamlDotNet.Serialization;
+using static WSLExtension.Constants;
+
+namespace WSLExtension.DistributionDefinitions;
+
+///
+/// Provides definition information about all the WSL distributions that can be found at
+/// .
+///
+public class DistributionDefinitionHelper : IDistributionDefinitionHelper, IDisposable
+{
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(DistributionDefinitionHelper));
+
+ private readonly IHttpClientFactory _httpClientFactory;
+
+ private readonly PackageHelper _packageHelper = new();
+
+ private readonly Architecture _osArchitecture;
+
+ private readonly SemaphoreSlim _definitionsLock = new(1, 1);
+
+ private readonly JsonSerializerOptions _jsonOptions = new()
+ {
+ PropertyNameCaseInsensitive = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ };
+
+ private readonly Dictionary _distributionDefinitionsMap = new();
+
+ private bool _disposedValue;
+
+ public DistributionDefinitionHelper(IHttpClientFactory httpClientFactory)
+ {
+ _httpClientFactory = httpClientFactory;
+ _osArchitecture = RuntimeInformation.OSArchitecture;
+ }
+
+ ///
+ public async Task> GetDistributionDefinitionsAsync()
+ {
+ await _definitionsLock.WaitAsync();
+
+ try
+ {
+ // Get the update to date distribution definitions from WSL GitHub repository.
+ // We use definitions from the web as our single source of truth, these web definitions are the
+ // same that are used in the command wsl.exe --list --online.
+ var client = _httpClientFactory.CreateClient();
+ var distributionDefinitionsJson = await client.GetStringAsync(KnownDistributionsWebJsonLocation);
+ var webDefinitions = JsonSerializer.Deserialize(distributionDefinitionsJson, _jsonOptions);
+
+ foreach (var definition in webDefinitions!.Values)
+ {
+ // Only supported distributions for this machine.
+ if (ShouldAddDistribution(definition))
+ {
+ _distributionDefinitionsMap[definition.Name] = definition;
+ }
+ }
+
+ // Merge the local distribution information we have stored in DistributionDefinition.yaml with the one above.
+ var uri = new Uri(KnownDistributionsLocalYamlLocation);
+ var storageFile = await StorageFile.GetFileFromApplicationUriAsync(uri);
+ var localYamlDefinitionsFile = await FileIO.ReadTextAsync(storageFile);
+ var localYamlDefinitions = BuildYamlDeserializer().Deserialize>(localYamlDefinitionsFile);
+ foreach (var localYamlDefinition in localYamlDefinitions)
+ {
+ // Ignore distributions that we have in the local yaml file but are no longer present in the web file.
+ if (!_distributionDefinitionsMap.TryGetValue(localYamlDefinition.Name, out var definitionFromWeb))
+ {
+ continue;
+ }
+
+ definitionFromWeb.Publisher = localYamlDefinition.Publisher;
+ definitionFromWeb.WindowsTerminalProfileGuid = localYamlDefinition.WindowsTerminalProfileGuid;
+
+ // Only add a logo to the definition we got from the web if the definition in the local yaml file
+ // has one.
+ if (!string.IsNullOrEmpty(localYamlDefinition.LogoFile))
+ {
+ // Update the logo with the base64 string representation so we can show it as a thumbnail.
+ var logoFilePath = WslLogoPathFormat.FormatArgs(localYamlDefinition.LogoFile);
+ definitionFromWeb.Base64StringLogo = await _packageHelper.GetBase64StringFromLogoPathAsync(logoFilePath);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Unable to retrieve all definitions for known distributions");
+ }
+
+ return _distributionDefinitionsMap;
+ }
+
+ private bool ShouldAddDistribution(DistributionDefinition distribution)
+ {
+ if (_osArchitecture == Architecture.Arm64)
+ {
+ return distribution.IsArm64Supported;
+ }
+ else if (_osArchitecture == Architecture.X64)
+ {
+ return distribution.IsAmd64Supported;
+ }
+
+ return false;
+ }
+
+ private IDeserializer BuildYamlDeserializer()
+ {
+ return new DeserializerBuilder().IgnoreUnmatchedProperties().Build();
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ _definitionsLock.Dispose();
+ }
+
+ _disposedValue = true;
+ }
+ }
+}
diff --git a/extensions/WSLExtension/DistributionDefinitions/DistributionDefinitions.cs b/extensions/WSLExtension/DistributionDefinitions/DistributionDefinitions.cs
new file mode 100644
index 0000000000..7568bd3a3c
--- /dev/null
+++ b/extensions/WSLExtension/DistributionDefinitions/DistributionDefinitions.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace WSLExtension.DistributionDefinitions;
+
+///
+/// Used when deserializing json file at
+///
+public class DistributionDefinitions
+{
+ [JsonPropertyName("Distributions")]
+ public List Values { get; set; } = new();
+}
diff --git a/extensions/WSLExtension/Exceptions/AdaptiveCardInvalidActionException.cs b/extensions/WSLExtension/Exceptions/AdaptiveCardInvalidActionException.cs
new file mode 100644
index 0000000000..d48f20d2d2
--- /dev/null
+++ b/extensions/WSLExtension/Exceptions/AdaptiveCardInvalidActionException.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace WSLExtension.Exceptions;
+
+public class AdaptiveCardInvalidActionException : Exception
+{
+ public AdaptiveCardInvalidActionException(string message)
+ : base(message)
+ {
+ }
+}
diff --git a/extensions/WSLExtension/Exceptions/WslServicesMediatorException.cs b/extensions/WSLExtension/Exceptions/WslServicesMediatorException.cs
new file mode 100644
index 0000000000..4b64da0b66
--- /dev/null
+++ b/extensions/WSLExtension/Exceptions/WslServicesMediatorException.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace WSLExtension.Exceptions;
+
+public class WslServicesMediatorException : Exception
+{
+ public WslServicesMediatorException(string? message)
+ : base(message)
+ {
+ }
+}
diff --git a/extensions/WSLExtension/Helpers/AdaptiveCardActionPayload.cs b/extensions/WSLExtension/Helpers/AdaptiveCardActionPayload.cs
new file mode 100644
index 0000000000..fcd5ea4b88
--- /dev/null
+++ b/extensions/WSLExtension/Helpers/AdaptiveCardActionPayload.cs
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace WSLExtension.Helpers;
+
+internal sealed class AdaptiveCardActionPayload
+{
+ public string? Id { get; set; }
+}
diff --git a/extensions/WSLExtension/Helpers/Json.cs b/extensions/WSLExtension/Helpers/Json.cs
new file mode 100644
index 0000000000..3e42ee7b87
--- /dev/null
+++ b/extensions/WSLExtension/Helpers/Json.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace WSLExtension.Helpers;
+
+public static class Json
+{
+ private static readonly JsonSerializerOptions _options = new()
+ {
+ PropertyNameCaseInsensitive = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.Never,
+ IncludeFields = true,
+ };
+
+ public static T? ToObject(string value)
+ {
+ if (typeof(T) == typeof(bool))
+ {
+ return (T)(object)bool.Parse(value);
+ }
+
+ return JsonSerializer.Deserialize(value, _options);
+ }
+}
diff --git a/extensions/WSLExtension/Helpers/Logging.cs b/extensions/WSLExtension/Helpers/Logging.cs
new file mode 100644
index 0000000000..7a91a1aea7
--- /dev/null
+++ b/extensions/WSLExtension/Helpers/Logging.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Windows.Storage;
+
+namespace WSLExtension.Helpers;
+
+public static class Logging
+{
+ public static readonly string LogFolderName = "Logs";
+
+ public static readonly string WslSubFolderName = "WSL";
+
+ private static readonly Lazy _logFolderRoot = new(() => Path.Combine(ApplicationData.Current.TemporaryFolder.Path, LogFolderName));
+
+ public static readonly string RootDevHomeLogFolder = _logFolderRoot.Value;
+
+ public static readonly string PathToWslLogFolder = Path.Combine(RootDevHomeLogFolder, WslSubFolderName);
+}
diff --git a/extensions/WSLExtension/Helpers/PackageHelper.cs b/extensions/WSLExtension/Helpers/PackageHelper.cs
new file mode 100644
index 0000000000..d3cc418b01
--- /dev/null
+++ b/extensions/WSLExtension/Helpers/PackageHelper.cs
@@ -0,0 +1,86 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Runtime.InteropServices.WindowsRuntime;
+using Serilog;
+using Windows.ApplicationModel;
+using Windows.Foundation;
+using Windows.Management.Deployment;
+using Windows.Storage;
+using Windows.Storage.Streams;
+
+namespace WSLExtension.Helpers;
+
+public class PackageHelper
+{
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(PackageHelper));
+
+ // Using 256 because the thumbnail in Dev Home's environments page
+ // is 64x64 but Windows packaged app logo sizes go up to 256.
+ // Getting the 256px image helps as it will mean the image will be rounded down instead
+ // of up, if there is a scaling change.
+ // See: https://learn.microsoft.com/windows/apps/design/style/iconography/app-icon-construction
+ private readonly Size _logoDimensions = new(256, 256);
+
+ private readonly PackageManager _packageManager = new();
+
+ public virtual bool IsPackageInstalled(string packageName)
+ {
+ var currentPackage = _packageManager.FindPackagesForUser(string.Empty, packageName).FirstOrDefault();
+ return currentPackage != null;
+ }
+
+ public async virtual Task GetPackageIconAsByteArrayAsync(string packageName)
+ {
+ // We'll use the first installed distribution for the package family to get the icon.
+ if (!(GetPackageFromPackageFamilyName(packageName) is Package package))
+ {
+ return [];
+ }
+
+ var stream = package.GetLogoAsRandomAccessStreamReference(_logoDimensions);
+ var logoStream = await stream.OpenReadAsync();
+
+ // Convert the stream to a byte array
+ var bytesArray = new byte[logoStream.Size];
+ await logoStream.ReadAsync(bytesArray.AsBuffer(), (uint)logoStream.Size, InputStreamOptions.None);
+ return bytesArray;
+ }
+
+ ///
+ /// Converts a path to a distributions logo in the DistributionDefinition.yaml file to its
+ /// base64 string representation.
+ ///
+ /// path to the logo file using the ms-appx:// schema
+ /// A base64 string that represents the logo.
+ public async virtual Task GetBase64StringFromLogoPathAsync(string logoFilePath)
+ {
+ try
+ {
+ var uri = new Uri(logoFilePath);
+ var storageFile = await StorageFile.GetFileFromApplicationUriAsync(uri);
+ 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);
+
+ return Convert.ToBase64String(bytes);
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Unable to get base64 string from logo file path: {logoFilePath}");
+ return string.Empty;
+ }
+ }
+
+ public virtual Package? GetPackageFromPackageFamilyName(string packageFamilyName)
+ {
+ if (string.IsNullOrEmpty(packageFamilyName))
+ {
+ return null;
+ }
+
+ return _packageManager.FindPackagesForUser(string.Empty, packageFamilyName).FirstOrDefault();
+ }
+}
diff --git a/extensions/WSLExtension/Models/RegisterAndInstallDistributionSession.cs b/extensions/WSLExtension/Models/RegisterAndInstallDistributionSession.cs
new file mode 100644
index 0000000000..c51a05dc17
--- /dev/null
+++ b/extensions/WSLExtension/Models/RegisterAndInstallDistributionSession.cs
@@ -0,0 +1,304 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using Microsoft.Windows.DevHome.SDK;
+using Serilog;
+using Windows.ApplicationModel;
+using Windows.Foundation;
+using WSLExtension.Contracts;
+using WSLExtension.DistributionDefinitions;
+using WSLExtension.Exceptions;
+using WSLExtension.Helpers;
+using static WSLExtension.Constants;
+
+namespace WSLExtension.Models;
+
+public enum SessionState
+{
+ WslInstallationForm,
+ ReviewForm,
+}
+
+///
+/// Class used to send adaptive cards to Dev Home's machine create environment flow. It sends a list of available
+/// wsl distributions on the first page, and then the name and logo of the selected distribution on the review page.
+///
+public class RegisterAndInstallDistributionSession : IExtensionAdaptiveCardSession2
+{
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(RegisterAndInstallDistributionSession));
+
+ private readonly PackageHelper _packageHelper = new();
+
+ private readonly string _adaptiveCardNextButtonId = "DevHomeMachineConfigurationNextButton";
+
+ private readonly string _pathToInitialCreationFormTemplate =
+ Path.Combine(Package.Current.EffectivePath, $@"{WslTemplateSubfolderName}\WslInstallationForm.json");
+
+ private readonly string _pathToReviewFormTemplate =
+ Path.Combine(Package.Current.EffectivePath, $@"{WslTemplateSubfolderName}\ReviewFormForWslInstallation.json");
+
+ private readonly List _availableDistributionsToInstall;
+
+ private readonly IStringResource _stringResource;
+
+ private string? _defaultWslLogo;
+
+ private IExtensionAdaptiveCard? _availableDistributionsAdaptiveCard;
+
+ public event TypedEventHandler? Stopped;
+
+ public string UserInputJson { get; private set; } = string.Empty;
+
+ public RegisterAndInstallDistributionSession(List availableDistributions, IStringResource stringResource)
+ {
+ _availableDistributionsToInstall = availableDistributions;
+ _stringResource = stringResource;
+ }
+
+ public ProviderOperationResult Initialize(IExtensionAdaptiveCard extensionUI)
+ {
+ _availableDistributionsAdaptiveCard = extensionUI;
+ _defaultWslLogo ??= _packageHelper.GetBase64StringFromLogoPathAsync(DefaultWslLogoPath).GetAwaiter().GetResult();
+
+ return GetWslInstallationFormAdaptiveCard();
+ }
+
+ public IAsyncOperation OnAction(string action, string inputs)
+ {
+ return Task.Run(() =>
+ {
+ try
+ {
+ ProviderOperationResult operationResult;
+ var shouldEndSession = false;
+ var adaptiveCardStateNotRecognizedError = _stringResource.GetLocalized("AdaptiveCardStateNotRecognizedError");
+
+ var actionPayload = Json.ToObject(action);
+ if (actionPayload == null)
+ {
+ _log.Error($"Actions in Adaptive card action Json not recognized: {action}");
+ var creationFormGenerationError = _stringResource.GetLocalized("AdaptiveCardUnRecognizedAction");
+ throw new AdaptiveCardInvalidActionException(creationFormGenerationError);
+ }
+
+ switch (_availableDistributionsAdaptiveCard?.State)
+ {
+ case "wslInstallationForm":
+ operationResult = HandleActionWhenFormInInitialState(actionPayload, inputs);
+ break;
+ case "reviewForm":
+ (operationResult, shouldEndSession) = HandleActionWhenFormInReviewState(actionPayload);
+ break;
+ default:
+ _log.Error($"No matching state found for: '{_availableDistributionsAdaptiveCard?.State}'." +
+ $"resetting state of adaptive card back to default.");
+ operationResult = GetWslInstallationFormAdaptiveCard();
+ 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, UserInputJson));
+ }
+
+ return operationResult;
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Unable to process action request from Dev Home");
+ return new ProviderOperationResult(ProviderOperationStatus.Failure, ex, ex.Message, ex.Message);
+ }
+ }).AsAsyncOperation();
+ }
+
+ ///
+ /// Loads the adaptive card template based on the session state.
+ ///
+ /// State of the adaptive card session
+ /// A Json string representing the adaptive card
+ public string LoadTemplate(SessionState state)
+ {
+ var pathToTemplate = state switch
+ {
+ SessionState.WslInstallationForm => _pathToInitialCreationFormTemplate,
+ SessionState.ReviewForm => _pathToReviewFormTemplate,
+ _ => _pathToInitialCreationFormTemplate,
+ };
+
+ return File.ReadAllText(pathToTemplate, Encoding.Default);
+ }
+
+ private ProviderOperationResult HandleActionWhenFormInInitialState(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.
+ UserInputJson = inputs;
+ operationResult = GetWslReviewFormAdaptiveCardAsync(inputs);
+ }
+ else
+ {
+ operationResult = GetWslInstallationFormAdaptiveCard();
+ }
+
+ return operationResult;
+ }
+
+ private (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 = GetWslReviewFormAdaptiveCardAsync(UserInputJson);
+ }
+ else
+ {
+ operationResult = GetWslInstallationFormAdaptiveCard();
+ }
+
+ return (operationResult, shouldEndSession);
+ }
+
+ ///
+ /// Creates the initial form that will be displayed to the user. It will be a list of settings cards from the
+ /// Windows community toolkit that can be displayed in Dev Homes UI. This will be display on the initial
+ /// create environments page once the user selects the wsl extension and clicks the next button.
+ ///
+ /// Result of the operation
+ private ProviderOperationResult GetWslInstallationFormAdaptiveCard()
+ {
+ try
+ {
+ // Create the JSON array for the available wsl distributions that can be installed and
+ // add the data for each one. These will be display in the initial creation form.
+ var jsonArrayOfAvailableDistributions = new JsonArray();
+ var primaryButtonForCreationFlowText = _stringResource.GetLocalized("PrimaryButtonLabelForCreationFlow");
+ var secondaryButtonForCreationFlowText = _stringResource.GetLocalized("SecondaryButtonLabelForCreationFlow");
+ var settingsCardLabel = _stringResource.GetLocalized("SettingsCardLabel", _availableDistributionsToInstall.Count);
+ var noDistributionsFound = _stringResource.GetLocalized("NoDistributionsFoundAvailable");
+
+ // Add information about all found distributions so we can use them in the settings cards
+ foreach (var distribution in _availableDistributionsToInstall)
+ {
+ var base64Logo =
+ string.IsNullOrEmpty(distribution.Base64StringLogo) ? _defaultWslLogo : distribution.Base64StringLogo;
+
+ var dataJson = new JsonObject
+ {
+ { "Header", distribution.FriendlyName },
+ { "HeaderIcon", base64Logo },
+ { "PublisherName", distribution.Publisher },
+ };
+
+ jsonArrayOfAvailableDistributions.Add(dataJson);
+ }
+
+ // Make sure we show the error message for when there are no distributions available to install
+ var noDistributionErrorData = new JsonArray
+ {
+ new JsonObject
+ {
+ { "NoDistributionsFoundError", noDistributionsFound },
+ { "NoDistributionsFoundErrorVisibility", _availableDistributionsToInstall.Count == 0 },
+ },
+ };
+
+ var templateData =
+ $"{{\"PrimaryButtonLabelForCreationFlow\" : \"{primaryButtonForCreationFlowText}\"," +
+ $"\"SecondaryButtonLabelForCreationFlow\" : \"{secondaryButtonForCreationFlowText}\"," +
+ $"\"SettingsCardLabel\": \"{settingsCardLabel}\"," +
+ $"\"NoDistributionErrorData\": {noDistributionErrorData.ToJsonString()}," +
+ $"\"AvailableDistributions\" : {jsonArrayOfAvailableDistributions.ToJsonString()}" +
+ $"}}";
+
+ var template = LoadTemplate(SessionState.WslInstallationForm);
+
+ return _availableDistributionsAdaptiveCard!.Update(template, templateData, "wslInstallationForm");
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Unable to create wsl creation form due to error");
+ return new ProviderOperationResult(ProviderOperationStatus.Failure, ex, ex.Message, 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 ProviderOperationResult GetWslReviewFormAdaptiveCardAsync(string inputJson)
+ {
+ try
+ {
+ var deserializedObject = JsonSerializer.Deserialize(inputJson, typeof(WslInstallationUserInput));
+
+ if (!(deserializedObject is WslInstallationUserInput inputForWslInstallation))
+ {
+ throw new InvalidOperationException($"Json deserialization failed for input Json: {inputJson}");
+ }
+
+ var distribution = _availableDistributionsToInstall[inputForWslInstallation.SelectedDistributionIndex];
+ var distributionLabel = _stringResource.GetLocalized("DistributionNameLabel");
+ var publisherLabel = _stringResource.GetLocalized("ReviewPagePublisherLabel");
+ var primaryButtonForCreationFlowText = _stringResource.GetLocalized("PrimaryButtonLabelForCreationFlow");
+ var secondaryButtonForCreationFlowText = _stringResource.GetLocalized("SecondaryButtonLabelForCreationFlow");
+ var extensionLabel = _stringResource.GetLocalized("ExtensionLabel");
+ var base64Logo = string.IsNullOrEmpty(distribution.Base64StringLogo) ? _defaultWslLogo : distribution.Base64StringLogo;
+
+ var reviewFormData = new JsonObject
+ {
+ { "ProviderName", WslProviderDisplayName },
+ { "NewEnvironmentName", distribution.FriendlyName },
+ { "DistributionName", distribution.Name },
+ { "DistributionNameLabel", distributionLabel },
+ { "PublisherName", distribution.Publisher },
+ { "ReviewPagePublisherLabel", publisherLabel },
+ { "ExtensionLabel", extensionLabel },
+ { "DistributionImage", $"data:image/png;base64,{base64Logo}" },
+ { "PrimaryButtonLabelForCreationFlow", primaryButtonForCreationFlowText },
+ { "SecondaryButtonLabelForCreationFlow", secondaryButtonForCreationFlowText },
+ { "DistributionImageLogoAltText", _stringResource.GetLocalized("DistributionLogoAltText", distribution.FriendlyName) },
+ };
+
+ // Add friendly name of the selected distribution to the original user input so Dev Home
+ // can show its name on the Environments page when the environment is being created.
+ UserInputJson = new JsonObject
+ {
+ { "NewEnvironmentName", distribution.FriendlyName },
+ { "SelectedDistributionIndex", $"{inputForWslInstallation.SelectedDistributionIndex}" },
+ }.ToJsonString();
+
+ return _availableDistributionsAdaptiveCard!.Update(
+ LoadTemplate(SessionState.ReviewForm),
+ reviewFormData.ToJsonString(),
+ "reviewForm");
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Unable to create wsl review form due to error");
+ return new ProviderOperationResult(ProviderOperationStatus.Failure, ex, ex.Message, ex.Message);
+ }
+ }
+
+ public void Dispose()
+ {
+ }
+}
diff --git a/extensions/WSLExtension/Models/WslComputeSystem.cs b/extensions/WSLExtension/Models/WslComputeSystem.cs
new file mode 100644
index 0000000000..2d55e2344f
--- /dev/null
+++ b/extensions/WSLExtension/Models/WslComputeSystem.cs
@@ -0,0 +1,351 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Data;
+using Microsoft.Windows.DevHome.SDK;
+using Serilog;
+using Windows.ApplicationModel;
+using Windows.Foundation;
+using WSLExtension.Contracts;
+using WSLExtension.Helpers;
+using static System.Convert;
+using static WSLExtension.Constants;
+
+namespace WSLExtension.Models;
+
+public delegate WslComputeSystem WslRegisteredDistributionFactory(WslRegisteredDistribution distribution);
+
+public class WslComputeSystem : IComputeSystem
+{
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WslComputeSystem));
+
+ private readonly PackageHelper _packageHelper = new();
+
+ private readonly string _versionLabel;
+
+ private readonly string _defaultDistributionLabel;
+
+ private readonly string _defaultDistributionValue;
+
+ private readonly string _packageVersionLabel;
+
+ private readonly string _publisherLabel;
+
+ private readonly object _lock = new();
+
+ private readonly IStringResource _stringResource;
+
+ private readonly IWslManager _wslManager;
+
+ private readonly WslRegisteredDistribution _distribution;
+
+ private readonly Package? _curPackage;
+
+ public IDeveloperId? AssociatedDeveloperId { get; set; }
+
+ public string AssociatedProviderId { get; set; } = WslProviderId;
+
+ public string DisplayName { get; set; }
+
+ private ComputeSystemState _curState;
+
+ public event TypedEventHandler? StateChanged;
+
+ ///
+ /// Gets or sets the Id of the compute system.All WSL distribution names are unique
+ /// so we will use them as the Id for the compute system.
+ ///
+ public string Id { get; set; }
+
+ public string SupplementalDisplayName { get; set; } = string.Empty;
+
+ public ComputeSystemOperations SupportedOperations
+ {
+ get
+ {
+ if (GetState() == ComputeSystemState.Stopped)
+ {
+ return ComputeSystemOperations.Delete;
+ }
+
+ return ComputeSystemOperations.Delete | ComputeSystemOperations.Terminate;
+ }
+ }
+
+ public WslComputeSystem(
+ IStringResource stringResource,
+ WslRegisteredDistribution distribution,
+ IWslManager wslManager)
+ {
+ _stringResource = stringResource;
+ _distribution = distribution;
+ _wslManager = wslManager;
+ DisplayName = distribution.FriendlyName;
+
+ // Only get the package information if it has a package family name
+ if (_distribution.PackageFamilyName != null)
+ {
+ _curPackage = _packageHelper.GetPackageFromPackageFamilyName(_distribution.PackageFamilyName);
+ }
+
+ // Use display name of package if there is no friendly name for this distribution
+ if (string.IsNullOrEmpty(DisplayName) && _curPackage != null)
+ {
+ DisplayName = _curPackage.DisplayName;
+ }
+
+ // Use the unique name of the distribution as its supplemental name as long as its not the same
+ // as the display name above.
+ if (!DisplayName.Equals(distribution.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ SupplementalDisplayName = distribution.Name;
+ }
+
+ Id = distribution.Name;
+ _wslManager.DistributionStateSyncEventHandler += OnStateSyncRequested;
+ _versionLabel = _stringResource.GetLocalized("WSLVersionLabel");
+ _defaultDistributionLabel = _stringResource.GetLocalized("WSLDefaultDistributionLabel");
+ _defaultDistributionValue = _stringResource.GetLocalized("WSLDefaultDistributionValue");
+ _publisherLabel = _stringResource.GetLocalized("WSLPublisherLabel");
+ _packageVersionLabel = _stringResource.GetLocalized("WSLPackageVersionLabel");
+ _curState = _wslManager.IsDistributionRunning(Id) ? ComputeSystemState.Running : ComputeSystemState.Stopped;
+ }
+
+ private void OnStateSyncRequested(object? sender, HashSet runningDistributions)
+ {
+ var newState = runningDistributions.Contains(Id)
+ ? ComputeSystemState.Running
+ : ComputeSystemState.Stopped;
+
+ UpdateState(newState);
+ }
+
+ private void UpdateState(ComputeSystemState newState)
+ {
+ lock (_lock)
+ {
+ if (_curState != newState)
+ {
+ _curState = newState;
+ StateChanged?.Invoke(this, newState);
+ }
+ }
+ }
+
+ private ComputeSystemState GetState()
+ {
+ lock (_lock)
+ {
+ return _curState;
+ }
+ }
+
+ public void RemoveSubscriptions()
+ {
+ _wslManager.DistributionStateSyncEventHandler -= OnStateSyncRequested;
+ }
+
+ public IAsyncOperation GetStateAsync()
+ {
+ return Task.Run(() =>
+ {
+ try
+ {
+ return new ComputeSystemStateResult(GetState());
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Unable to get state for Distribution: {Id}");
+ return new ComputeSystemStateResult(ex, ex.Message, ex.Message);
+ }
+ }).AsAsyncOperation();
+ }
+
+ public IAsyncOperation TerminateAsync(string options)
+ {
+ return Task.Run(() =>
+ {
+ try
+ {
+ UpdateState(ComputeSystemState.Stopping);
+ _wslManager.TerminateDistribution(Id);
+ UpdateState(ComputeSystemState.Stopped);
+ return new ComputeSystemOperationResult();
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Unable to terminate all sessions for Distribution: {Id}");
+ UpdateState(ComputeSystemState.Unknown);
+ return GetErrorResult(ex, "WSLTerminateError");
+ }
+ }).AsAsyncOperation();
+ }
+
+ public IAsyncOperation DeleteAsync(string options)
+ {
+ return Task.Run(() =>
+ {
+ try
+ {
+ UpdateState(ComputeSystemState.Deleting);
+ _wslManager.UnregisterDistribution(Id);
+ UpdateState(ComputeSystemState.Deleted);
+ return new ComputeSystemOperationResult();
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Unable to unregister Distribution: {Id}");
+ UpdateState(ComputeSystemState.Unknown);
+ return GetErrorResult(ex, "WSLUnregisterError");
+ }
+ }).AsAsyncOperation();
+ }
+
+ public IAsyncOperation GetComputeSystemThumbnailAsync(string options)
+ {
+ return Task.Run(async () =>
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(_distribution.Base64StringLogo))
+ {
+ byte[]? packageFamilyLogo = default;
+ var familyName = _distribution.PackageFamilyName;
+
+ // No logo for this distribution so we'll use its PackageFamily logo
+ if (familyName != null)
+ {
+ packageFamilyLogo = await _packageHelper.GetPackageIconAsByteArrayAsync(familyName);
+ }
+
+ if (packageFamilyLogo == null)
+ {
+ // Couldn't find package family logo, so instead use the default WSL logo.
+ var defaultLogoBase64 = await _packageHelper.GetBase64StringFromLogoPathAsync(DefaultWslLogoPath);
+ return new ComputeSystemThumbnailResult(FromBase64String(defaultLogoBase64));
+ }
+
+ return new ComputeSystemThumbnailResult(packageFamilyLogo);
+ }
+
+ // This is a known distribution. Use the logo we have for this distribution defined in
+ // the DistributionDefinitions.yaml
+ return new ComputeSystemThumbnailResult(FromBase64String(_distribution.Base64StringLogo!));
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Unable to get thumbnail for Distribution: {Id}");
+ return new ComputeSystemThumbnailResult(ex, ex.Message, ex.Message);
+ }
+ }).AsAsyncOperation();
+ }
+
+ public IAsyncOperation> GetComputeSystemPropertiesAsync(string options)
+ {
+ return Task.Run(() =>
+ {
+ try
+ {
+ var properties = new List();
+ if (_distribution.Version != null)
+ {
+ properties.Add(ComputeSystemProperty.CreateCustom(_distribution.Version.Value, _versionLabel, null));
+ }
+
+ if (_distribution.IsDefaultDistribution)
+ {
+ properties.Add(ComputeSystemProperty.CreateCustom(_defaultDistributionValue, _defaultDistributionLabel, null));
+ }
+
+ if (_curPackage != null)
+ {
+ if (!string.IsNullOrEmpty(_curPackage.PublisherDisplayName))
+ {
+ properties.Add(ComputeSystemProperty.CreateCustom(_curPackage.PublisherDisplayName, _publisherLabel, null));
+ }
+
+ var version = _curPackage.Id.Version;
+ var versionInfo = $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
+ properties.Add(ComputeSystemProperty.CreateCustom(versionInfo, _packageVersionLabel, null));
+ }
+
+ return properties.AsEnumerable();
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Unable to get compute system properties for Distribution: {Id}");
+ return new List();
+ }
+ }).AsAsyncOperation();
+ }
+
+ public IAsyncOperation ConnectAsync(string options)
+ {
+ return Task.Run(() =>
+ {
+ try
+ {
+ UpdateState(ComputeSystemState.Starting);
+ _wslManager.LaunchDistribution(Id, _distribution.AssociatedTerminalProfileGuid);
+ UpdateState(ComputeSystemState.Running);
+ return new ComputeSystemOperationResult();
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Unable to launch Distribution: {Id}");
+ UpdateState(ComputeSystemState.Unknown);
+ return GetErrorResult(ex, "WSLLaunchError");
+ }
+ }).AsAsyncOperation();
+ }
+
+ private ComputeSystemOperationResult GetUnSupportedResult()
+ {
+ var notImplementedException = new NotImplementedException($"Method not implemented by WSL Compute Systems");
+ return new ComputeSystemOperationResult(notImplementedException, notImplementedException.Message, notImplementedException.Message);
+ }
+
+ public IApplyConfigurationOperation? CreateApplyConfigurationOperation(string configuration)
+ {
+ // 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.
+ return null;
+ }
+
+ private ComputeSystemOperationResult GetErrorResult(Exception ex, string resourceKey)
+ {
+ var displayMsg = _stringResource.GetLocalized(resourceKey, ex.Message);
+ return new ComputeSystemOperationResult(ex, displayMsg, ex.Message);
+ }
+
+ // Unsupported IComputeSystem methods
+ public IAsyncOperation SaveAsync(string options) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+
+ public IAsyncOperation PauseAsync(string options) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+
+ public IAsyncOperation ResumeAsync(string options) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+
+ public IAsyncOperation CreateSnapshotAsync(string options) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+
+ public IAsyncOperation RevertSnapshotAsync(string options) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+
+ public IAsyncOperation DeleteSnapshotAsync(string options) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+
+ public IAsyncOperation ModifyPropertiesAsync(string inputJson) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+
+ public IAsyncOperation StartAsync(string options) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+
+ public IAsyncOperation ShutDownAsync(string options) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+
+ public IAsyncOperation RestartAsync(string options) =>
+ Task.FromResult(GetUnSupportedResult()).AsAsyncOperation();
+}
diff --git a/extensions/WSLExtension/Models/WslInstallDistributionOperation.cs b/extensions/WSLExtension/Models/WslInstallDistributionOperation.cs
new file mode 100644
index 0000000000..858de3ca55
--- /dev/null
+++ b/extensions/WSLExtension/Models/WslInstallDistributionOperation.cs
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Windows.DevHome.SDK;
+using Serilog;
+using Windows.Foundation;
+using WSLExtension.Contracts;
+using WSLExtension.DistributionDefinitions;
+
+namespace WSLExtension.Models;
+
+public class WslInstallDistributionOperation : ICreateComputeSystemOperation
+{
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WslInstallDistributionOperation));
+
+ private readonly string _preparingToInstall;
+
+ private readonly string _waitingToComplete;
+
+ private readonly string _installationFailedTimeout;
+
+ private readonly string _installationSuccessful;
+
+ private readonly TimeSpan _threeSecondDelayInSeconds = TimeSpan.FromSeconds(3);
+
+ private readonly DistributionDefinition _definition;
+
+ private readonly IStringResource _stringResource;
+
+ private readonly IWslManager _wslManager;
+
+ public WslInstallDistributionOperation(
+ DistributionDefinition distributionDefinition,
+ IStringResource stringResource,
+ IWslManager wslManager)
+ {
+ _definition = distributionDefinition;
+ _stringResource = stringResource;
+ _wslManager = wslManager;
+ _preparingToInstall = GetLocalizedString("WSLPrepareInstall", _definition.FriendlyName);
+ _waitingToComplete = GetLocalizedString("WSLWaitingToCompleteInstallation", _definition.FriendlyName);
+
+ _installationFailedTimeout = GetLocalizedString("WSLInstallationFailedTimeOut", _definition.FriendlyName);
+
+ _installationSuccessful = GetLocalizedString("WSLInstallationCompletedSuccessfully", _definition.FriendlyName);
+ }
+
+ private string GetLocalizedString(string resourcekey, string value)
+ {
+ return _stringResource.GetLocalized(resourcekey, value);
+ }
+
+ public IAsyncOperation StartAsync()
+ {
+ return Task.Run(async () =>
+ {
+ try
+ {
+ var startTime = DateTime.UtcNow;
+ _log.Information($"Starting installation for {_definition.Name}");
+ Progress?.Invoke(this, new CreateComputeSystemProgressEventArgs(_preparingToInstall, 0));
+ _wslManager.InstallDistribution(_definition.Name);
+
+ // Cancel waiting for install if the distribution hasn't been installed after 10 minutes.
+ CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
+ cancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(10));
+ WslRegisteredDistribution? registeredDistribution = null;
+ var distributionInstalledSuccessfully = false;
+
+ Progress?.Invoke(this, new CreateComputeSystemProgressEventArgs(_waitingToComplete, 0));
+ while (!cancellationTokenSource.IsCancellationRequested)
+ {
+ // Wait in 3 second intervals before checking. Unfortunately there are no APIs to check for
+ // installation so we need to keep checking for its completion.
+ await Task.Delay(_threeSecondDelayInSeconds);
+ registeredDistribution = await _wslManager.GetInformationOnRegisteredDistributionAsync(_definition.Name);
+
+ if ((registeredDistribution != null) &&
+ (distributionInstalledSuccessfully = registeredDistribution.IsDistributionFullyRegistered()))
+ {
+ break;
+ }
+ }
+
+ _log.Information($"Ending installation for {_definition.Name}. Operation took: {DateTime.UtcNow - startTime}");
+ if (distributionInstalledSuccessfully)
+ {
+ Progress?.Invoke(this, new CreateComputeSystemProgressEventArgs(_installationSuccessful, 100));
+ return new CreateComputeSystemResult(new WslComputeSystem(_stringResource, registeredDistribution!, _wslManager));
+ }
+
+ throw new TimeoutException(_installationFailedTimeout);
+ }
+ catch (Exception ex)
+ {
+ var errorMsg = _stringResource.GetLocalized("WSLInstallationFailedWithException", _definition.FriendlyName, ex.Message);
+ return new CreateComputeSystemResult(ex, errorMsg, ex.Message);
+ }
+ }).AsAsyncOperation();
+ }
+
+ public event TypedEventHandler? ActionRequired
+ {
+ add { }
+ remove { }
+ }
+
+ public event TypedEventHandler? Progress;
+}
diff --git a/extensions/WSLExtension/Models/WslInstallationUserInput.cs b/extensions/WSLExtension/Models/WslInstallationUserInput.cs
new file mode 100644
index 0000000000..4d5bbf19ff
--- /dev/null
+++ b/extensions/WSLExtension/Models/WslInstallationUserInput.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Text.Json.Serialization;
+
+namespace WSLExtension.Models;
+
+///
+/// Represents the user input for a Wsl install and registration operation.
+///
+public sealed class WslInstallationUserInput
+{
+ ///
+ /// Gets or sets the WSL distribution name for the creation operation.
+ /// Note: Do not rename this variable, as Dev Home looks for the name
+ /// 'NewEnvironmentName' in the user input Json in order to show the
+ /// name of the environment in the UI while its being created in the
+ /// CreateComputeSystemOperation object.
+ ///
+ public string NewEnvironmentName { get; set; } = string.Empty;
+
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
+ public int SelectedDistributionIndex { get; set; }
+}
diff --git a/extensions/WSLExtension/Models/WslProcessData.cs b/extensions/WSLExtension/Models/WslProcessData.cs
new file mode 100644
index 0000000000..d3914de84a
--- /dev/null
+++ b/extensions/WSLExtension/Models/WslProcessData.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Globalization;
+using System.Text;
+using static WSLExtension.Constants;
+
+namespace WSLExtension.Models;
+
+///
+/// Represents metadata about a process that has exited.
+///
+public class WslProcessData
+{
+ public int ExitCode { get; }
+
+ public string StdOutput { get; } = string.Empty;
+
+ public string StdError { get; } = string.Empty;
+
+ public WslProcessData(int exitCode)
+ {
+ ExitCode = exitCode;
+ }
+
+ public WslProcessData(int exitCode, string stdOutput, string stdError)
+ {
+ ExitCode = exitCode;
+ StdOutput = stdOutput;
+ StdError = stdError;
+ }
+
+ public bool ExitedSuccessfully()
+ {
+ return ExitCode == WslExeExitSuccess && string.IsNullOrEmpty(StdError);
+ }
+
+ public override string ToString()
+ {
+ StringBuilder builder = new();
+ builder.AppendLine(CultureInfo.InvariantCulture, $"ExitCode: {ExitCode} ");
+ builder.AppendLine(CultureInfo.InvariantCulture, $"StdOutput: {StdOutput} ");
+ builder.AppendLine(CultureInfo.InvariantCulture, $"StdError: {StdError} ");
+ return builder.ToString();
+ }
+}
diff --git a/extensions/WSLExtension/Models/WslRegisteredDistribution.cs b/extensions/WSLExtension/Models/WslRegisteredDistribution.cs
new file mode 100644
index 0000000000..15141e82d1
--- /dev/null
+++ b/extensions/WSLExtension/Models/WslRegisteredDistribution.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using WSLExtension.DistributionDefinitions;
+using static Microsoft.Win32.Registry;
+using static WSLExtension.Constants;
+
+namespace WSLExtension.Models;
+
+///
+/// Represents information about a registered WSL distribution
+///
+public class WslRegisteredDistribution
+{
+ private const int InstalledState = 1;
+
+ public string FriendlyName { get; set; } = string.Empty;
+
+ public string Name { get; set; } = string.Empty;
+
+ public int? Version { get; set; }
+
+ public string? SubKeyName { get; set; }
+
+ public string? PackageFamilyName { get; set; }
+
+ public string? Base64StringLogo { get; set; }
+
+ public bool IsDefaultDistribution { get; set; }
+
+ public string Publisher { get; set; } = string.Empty;
+
+ public string? AssociatedTerminalProfileGuid { get; set; } = string.Empty;
+
+ public WslRegisteredDistribution(string distributionName)
+ {
+ Name = distributionName;
+ }
+
+ public WslRegisteredDistribution(DistributionDefinition distributionDistribution)
+ {
+ Name = distributionDistribution.Name;
+ FriendlyName = distributionDistribution.FriendlyName;
+ Base64StringLogo = distributionDistribution.Base64StringLogo;
+ AssociatedTerminalProfileGuid = distributionDistribution.WindowsTerminalProfileGuid;
+ }
+
+ public WslRegisteredDistribution(string distributionName, string? subKeyName, string? packageFamilyName, int? version)
+ {
+ Name = distributionName;
+ FriendlyName = Name;
+ SubKeyName = subKeyName;
+ Version = version;
+ PackageFamilyName = packageFamilyName;
+ }
+
+ ///
+ /// Uses the registry information about the distribution to determine if its fully registered or not.
+ ///
+ /// True only when distribution is fully registered. False otherwise.
+ public virtual bool IsDistributionFullyRegistered()
+ {
+ var distributionKey = CurrentUser.OpenSubKey($@"{WslRegistryLocation}\{SubKeyName}", false);
+
+ if (distributionKey == null)
+ {
+ return false;
+ }
+
+ var state = distributionKey?.GetValue(WslState) as int?;
+
+ // Any other state other than a 1 means the distribution is not fully installed yet.
+ return state == InstalledState;
+ }
+}
diff --git a/extensions/WSLExtension/Program.cs b/extensions/WSLExtension/Program.cs
new file mode 100644
index 0000000000..fe43049bee
--- /dev/null
+++ b/extensions/WSLExtension/Program.cs
@@ -0,0 +1,120 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Windows.AppLifecycle;
+using Microsoft.Windows.DevHome.SDK;
+using Serilog;
+using Windows.ApplicationModel.Activation;
+using WSLExtension.ClassExtensions;
+using WSLExtension.Services;
+using static WSLExtension.Helpers.Logging;
+
+namespace WSLExtension;
+
+public sealed class Program
+{
+ public static IHost? Host { get; set; }
+
+ [MTAThread]
+ public static void Main([System.Runtime.InteropServices.WindowsRuntime.ReadOnlyArray] string[] args)
+ {
+ // Set up Logging
+ Environment.SetEnvironmentVariable("WSL_LOGS_ROOT", PathToWslLogFolder);
+ var configuration = new ConfigurationBuilder().AddJsonFile("wsl_appsettings.json").Build();
+ Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
+
+ Log.Information($"Launched with args: {string.Join(' ', args.ToArray())}");
+
+ // Force the app to be single instanced.
+ // Get or register the main instance.
+ var mainInstance = AppInstance.FindOrRegisterForKey("mainInstance");
+ var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
+ if (!mainInstance.IsCurrent)
+ {
+ Log.Information("Not main instance, redirecting.");
+ mainInstance.RedirectActivationToAsync(activationArgs).AsTask().Wait();
+ Log.CloseAndFlush();
+ return;
+ }
+
+ // Build the host container before handling activation.
+ BuildHostContainer();
+
+ // Register for activation redirection.
+ AppInstance.GetCurrent().Activated += AppActivationRedirected;
+
+ if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
+ {
+ HandleCOMServerActivation();
+ }
+ else
+ {
+ Log.Warning("Not being launched as a ComServer... exiting.");
+ }
+
+ Log.CloseAndFlush();
+ }
+
+ private static void AppActivationRedirected(object? sender, AppActivationArguments activationArgs)
+ {
+ Log.Information($"Redirected with kind: {activationArgs.Kind}");
+
+ // Handle COM server.
+ if (activationArgs.Kind == ExtendedActivationKind.Launch)
+ {
+ var launchActivatedEventArgs = activationArgs.Data as ILaunchActivatedEventArgs;
+ var args = launchActivatedEventArgs?.Arguments.Split();
+
+ if (args?.Length > 0 && args[1] == "-RegisterProcessAsComServer")
+ {
+ Log.Information($"Activation COM Registration Redirect: {string.Join(' ', args.ToList())}");
+ HandleCOMServerActivation();
+ }
+ }
+ }
+
+ ///
+ /// Creates the host container for the WSLExtension server application. This can be used to register
+ /// services and other dependencies throughout the application.
+ ///
+ private static void BuildHostContainer()
+ {
+ Host = Microsoft.Extensions.Hosting.Host.
+ CreateDefaultBuilder().
+ UseContentRoot(AppContext.BaseDirectory).
+ UseDefaultServiceProvider((context, options) =>
+ {
+ options.ValidateOnBuild = true;
+ }).
+ ConfigureServices((context, services) =>
+ {
+ // Services
+ services.AddHttpClient();
+ services.AddWslExtensionServices();
+ }).
+ Build();
+ }
+
+ private static void HandleCOMServerActivation()
+ {
+ Log.Information("Activating COM Server");
+
+ // Register and run COM server.
+ // This could be called by either of the COM registrations, we will do them all to avoid deadlock and bind all on the extension's lifetime.
+ using var extensionServer = new ExtensionServer();
+ var wslExtension = Host!.GetService();
+
+ // We are instantiating extension instance once above, and returning it every time the callback in RegisterExtension below is called.
+ // This makes sure that only one instance of the extension is alive, which is returned every time the host asks for the IExtension object.
+ // If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
+ extensionServer.RegisterExtension(() => wslExtension, true);
+
+ // This will make the main thread wait until the event is signaled by the extension class.
+ // Since we have single instance of the extension object, we exit as soon as it is disposed.
+ wslExtension.ExtensionDisposedEvent.WaitOne();
+ Log.Information("Extension is disposed.");
+ }
+}
diff --git a/extensions/WSLExtension/Services/ProcessCreator.cs b/extensions/WSLExtension/Services/ProcessCreator.cs
new file mode 100644
index 0000000000..d9d743743a
--- /dev/null
+++ b/extensions/WSLExtension/Services/ProcessCreator.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics;
+using System.Text;
+using WSLExtension.Contracts;
+using WSLExtension.Models;
+
+namespace WSLExtension.Services;
+
+public class ProcessCreator : IProcessCreator
+{
+ ///
+ public void CreateProcessWithWindow(string fileName, string arguments)
+ {
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = fileName,
+ Arguments = arguments,
+ UseShellExecute = true,
+ },
+ };
+
+ process.Start();
+ }
+
+ ///
+ public WslProcessData CreateProcessWithoutWindowAndWaitForExit(string fileName, string arguments)
+ {
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = fileName,
+ Arguments = arguments,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true,
+ StandardOutputEncoding = Encoding.Unicode,
+ },
+ };
+
+ process.Start();
+ var output = process.StandardOutput.ReadToEnd();
+ var errors = process.StandardError.ReadToEnd();
+
+ process.WaitForExit();
+ var exitCode = process.ExitCode;
+ return new WslProcessData(exitCode, output, errors);
+ }
+}
diff --git a/extensions/WSLExtension/Services/StringResource.cs b/extensions/WSLExtension/Services/StringResource.cs
new file mode 100644
index 0000000000..52abbd1869
--- /dev/null
+++ b/extensions/WSLExtension/Services/StringResource.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Globalization;
+using Microsoft.Windows.ApplicationModel.Resources;
+using WSLExtension.Contracts;
+
+namespace WSLExtension.Services;
+
+public class StringResource : IStringResource
+{
+ private readonly ResourceLoader _resourceLoader;
+
+ public StringResource()
+ {
+ _resourceLoader = new ResourceLoader("WSLExtensionServer.pri", "Resources");
+ }
+
+ public StringResource(string name, string path)
+ {
+ _resourceLoader = new ResourceLoader(name, path);
+ }
+
+ /// Gets the localized string of a resource key.
+ /// Resource key.
+ /// Placeholder arguments.
+ /// Localized value, or resource key if the value is empty or an exception occurred.
+ public string GetLocalized(string key, params object[] args)
+ {
+ string value;
+
+ try
+ {
+ value = _resourceLoader.GetString(key);
+ value = string.Format(CultureInfo.CurrentCulture, value, args);
+ }
+ catch
+ {
+ value = string.Empty;
+ }
+
+ return string.IsNullOrEmpty(value) ? key : value;
+ }
+}
diff --git a/extensions/WSLExtension/Services/WslExtension.cs b/extensions/WSLExtension/Services/WslExtension.cs
new file mode 100644
index 0000000000..6d443220c5
--- /dev/null
+++ b/extensions/WSLExtension/Services/WslExtension.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Runtime.InteropServices;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Windows.DevHome.SDK;
+using Serilog;
+using WSLExtension.ClassExtensions;
+
+namespace WSLExtension.Services;
+
+[ComVisible(true)]
+[Guid("121253AB-BA5D-4E73-99CF-25A2CB8BF173")]
+[ComDefaultInterface(typeof(IExtension))]
+public sealed class WslExtension : IExtension, IDisposable
+{
+ private readonly IComputeSystemProvider _computeSystemProvider;
+
+ private bool _disposed;
+
+ public WslExtension(IComputeSystemProvider computeSystemProvider)
+ {
+ _computeSystemProvider = computeSystemProvider;
+ }
+
+ ///
+ /// Gets the synchronization object that is used to prevent the main program from exiting
+ /// until the extension is disposed.
+ ///
+ public ManualResetEvent ExtensionDisposedEvent { get; } = new(false);
+
+ ///
+ /// Gets provider object for the specified provider type.
+ ///
+ ///
+ /// The provider type that the WSL extension may support. This is used to query the WSL
+ /// extension for whether it supports the provider type.
+ ///
+ ///
+ /// When the extension supports the ProviderType the object returned will not be null. However,
+ /// when the extension does not support the ProviderType the returned object will be null.
+ ///
+ public object? GetProvider(ProviderType providerType)
+ {
+ var log = Log.ForContext("SourceContext", nameof(WslExtension));
+ object? provider = null;
+ try
+ {
+ switch (providerType)
+ {
+ case ProviderType.ComputeSystem:
+ provider = _computeSystemProvider;
+ break;
+ default:
+ log.Information($"Unsupported provider: {providerType}");
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ log.Error(ex, $"Failed to get provider for provider type {providerType}");
+ }
+
+ return provider;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ ExtensionDisposedEvent.Set();
+ }
+
+ _disposed = true;
+ }
+}
diff --git a/extensions/WSLExtension/Services/WslManager.cs b/extensions/WSLExtension/Services/WslManager.cs
new file mode 100644
index 0000000000..257bd97780
--- /dev/null
+++ b/extensions/WSLExtension/Services/WslManager.cs
@@ -0,0 +1,182 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Serilog;
+using Windows.System.Threading;
+using WSLExtension.ClassExtensions;
+using WSLExtension.Contracts;
+using WSLExtension.DistributionDefinitions;
+using WSLExtension.Helpers;
+using WSLExtension.Models;
+using static WSLExtension.Constants;
+
+namespace WSLExtension.Services;
+
+public class WslManager : IWslManager
+{
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WslManager));
+
+ private readonly PackageHelper _packageHelper = new();
+
+ private readonly TimeSpan _oneMinutePollingInterval = TimeSpan.FromMinutes(1);
+
+ private readonly WslRegisteredDistributionFactory _wslRegisteredDistributionFactory;
+
+ private readonly IWslServicesMediator _wslServicesMediator;
+
+ private readonly IDistributionDefinitionHelper _definitionHelper;
+
+ private readonly List _registeredWslDistributions = new();
+
+ public event EventHandler>? DistributionStateSyncEventHandler;
+
+ private Dictionary? _distributionDefinitionsMap;
+
+ private ThreadPoolTimer? _timerForUpdatingDistributionStates;
+
+ public WslManager(
+ IWslServicesMediator wslServicesMediator,
+ WslRegisteredDistributionFactory wslDistributionFactory,
+ IDistributionDefinitionHelper distributionDefinitionHelper)
+ {
+ _wslRegisteredDistributionFactory = wslDistributionFactory;
+ _wslServicesMediator = wslServicesMediator;
+ _definitionHelper = distributionDefinitionHelper;
+ StartDistributionStatePolling();
+ }
+
+ ///
+ public async Task> GetAllRegisteredDistributionsAsync()
+ {
+ // The list of compute systems in Dev Home is being refreshed, so remove any old
+ // subscriptions
+ _registeredWslDistributions.ForEach(distribution => distribution.RemoveSubscriptions());
+ _registeredWslDistributions.Clear();
+
+ foreach (var distribution in await GetInformationOnAllRegisteredDistributionsAsync())
+ {
+ try
+ {
+ _registeredWslDistributions.Add(_wslRegisteredDistributionFactory(distribution.Value));
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Unable to add the distribution: {distribution.Key}");
+ }
+ }
+
+ return _registeredWslDistributions;
+ }
+
+ ///
+ public async Task> GetAllDistributionsAvailableToInstallAsync()
+ {
+ var registeredDistributionsMap = await GetInformationOnAllRegisteredDistributionsAsync();
+ var distributionsToListOnCreationPage = new List();
+ _distributionDefinitionsMap ??= await _definitionHelper.GetDistributionDefinitionsAsync();
+ foreach (var distributionDefinition in _distributionDefinitionsMap.Values)
+ {
+ // filter out distribution definitions already registered on machine.
+ if (registeredDistributionsMap.TryGetValue(distributionDefinition.Name, out var _))
+ {
+ continue;
+ }
+
+ distributionsToListOnCreationPage.Add(distributionDefinition);
+ }
+
+ // Sort the list by distribution name in ascending order before sending it.
+ distributionsToListOnCreationPage.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
+ return distributionsToListOnCreationPage;
+ }
+
+ ///
+ public async Task GetInformationOnRegisteredDistributionAsync(string distributionName)
+ {
+ foreach (var registeredDistribution in (await GetInformationOnAllRegisteredDistributionsAsync()).Values)
+ {
+ if (distributionName.Equals(registeredDistribution.Name, StringComparison.Ordinal))
+ {
+ return registeredDistribution;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ public bool IsDistributionRunning(string distributionName)
+ {
+ return _wslServicesMediator.IsDistributionRunning(distributionName);
+ }
+
+ ///
+ public void UnregisterDistribution(string distributionName)
+ {
+ _wslServicesMediator.UnregisterDistribution(distributionName);
+ }
+
+ ///
+ public void LaunchDistribution(string distributionName, string? windowsTerminalProfile = null)
+ {
+ _wslServicesMediator.LaunchDistribution(distributionName, windowsTerminalProfile);
+ }
+
+ ///
+ public void InstallDistribution(string distributionName)
+ {
+ _wslServicesMediator.InstallDistribution(distributionName);
+ }
+
+ ///
+ public void TerminateDistribution(string distributionName)
+ {
+ _wslServicesMediator.TerminateDistribution(distributionName);
+ }
+
+ ///
+ /// Retrieves information about all registered distributions on the machine and fills in any missing data
+ /// that is needed for them to be shown in Dev Home's UI. E.g logo images.
+ ///
+ private async Task> GetInformationOnAllRegisteredDistributionsAsync()
+ {
+ _distributionDefinitionsMap ??= await _definitionHelper.GetDistributionDefinitionsAsync();
+ var distributions = new Dictionary();
+ foreach (var distribution in _wslServicesMediator.GetAllRegisteredDistributions())
+ {
+ // If this is a distribution we know about in DistributionDefinition.yaml add its friendly name and logo.
+ if (_distributionDefinitionsMap.TryGetValue(distribution.Name, out var knownDistributionInfo))
+ {
+ distribution.FriendlyName = knownDistributionInfo.FriendlyName;
+ distribution.Base64StringLogo = knownDistributionInfo.Base64StringLogo;
+ distribution.AssociatedTerminalProfileGuid = knownDistributionInfo.WindowsTerminalProfileGuid;
+ }
+
+ distributions.Add(distribution.Name, distribution);
+ }
+
+ return distributions;
+ }
+
+ ///
+ /// Raises an event once every minute so that the wsl compute systems state can be updated. Unfortunately there
+ /// are no WSL APIs to achieve this. Once an API is created that fires an event for state changes this can be
+ /// updated/removed.
+ ///
+ private void StartDistributionStatePolling()
+ {
+ _timerForUpdatingDistributionStates = ThreadPoolTimer.CreatePeriodicTimer(
+ (ThreadPoolTimer timer) =>
+ {
+ try
+ {
+ DistributionStateSyncEventHandler?.Invoke(this, _wslServicesMediator.GetAllNamesOfRunningDistributions());
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Unable to raise distribution sync event due to an error");
+ }
+ },
+ _oneMinutePollingInterval);
+ }
+}
diff --git a/extensions/WSLExtension/Services/WslServicesMediator.cs b/extensions/WSLExtension/Services/WslServicesMediator.cs
new file mode 100644
index 0000000000..3d83056b23
--- /dev/null
+++ b/extensions/WSLExtension/Services/WslServicesMediator.cs
@@ -0,0 +1,214 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics;
+using Microsoft.Win32;
+using WSLExtension.ClassExtensions;
+using WSLExtension.Contracts;
+using WSLExtension.Exceptions;
+using WSLExtension.Helpers;
+using WSLExtension.Models;
+using static Microsoft.Win32.Registry;
+using static WSLExtension.Constants;
+
+namespace WSLExtension.Services;
+
+///
+/// Class to interact with WSL services either through the wsl.exe process
+/// or the registry.
+///
+public class WslServicesMediator : IWslServicesMediator
+{
+ private const int FirstIndex = 0;
+
+ private readonly PackageHelper _packageHelper = new();
+
+ private readonly IProcessCreator _processCreator;
+
+ public WslServicesMediator(IProcessCreator creator)
+ {
+ _processCreator = creator;
+ }
+
+ ///
+ public HashSet GetAllNamesOfRunningDistributions()
+ {
+ var processData = _processCreator.CreateProcessWithoutWindowAndWaitForExit(WslExe, ListAllRunningDistributions);
+
+ // wsl.exe returns an error code when there are no distributions running. But in that case
+ // it will send at least one line to standard output e.g "There are no running distributions."
+ if (!processData.ExitedSuccessfully() && string.IsNullOrEmpty(processData.StdOutput))
+ {
+ throw new WslServicesMediatorException($"Unable to get all running distribution data" +
+ $" process data: {processData}");
+ }
+
+ var distributions = new HashSet();
+ using var reader = new StringReader(processData.StdOutput);
+
+ // Results for executing wsl.exe --list --running.
+ // When no distributions are running the first line is localized: "There are no running distributions."
+ // So we can skip it.
+ // When there are distributions the first line is: "Windows Subsystem for Linux Distributions"
+ // The rest of the lines are the running distribution names e.g
+ // Debian (Default)
+ // OracleLinux_7_9
+ //
+ // Note: the distribution that's set up as the default, will contain (default) next to it. But for our purposes
+ // we don't need to read that part. We only need to first word of the space separated line. Distribution
+ // names cannot have spaces so we don't need to worry about that either.
+ reader.ReadLine();
+ while (reader.ReadLine() is { } line)
+ {
+ var spaceSeparatedArr = line.Split(" ");
+ distributions.Add(spaceSeparatedArr[FirstIndex]);
+ }
+
+ return distributions;
+ }
+
+ ///
+ public bool IsDistributionRunning(string distributionName)
+ {
+ return GetAllNamesOfRunningDistributions().Contains(distributionName);
+ }
+
+ ///
+ ///
+ /// Method enumerates through the WSL registry location subkey location
+ /// to retrieve information about each registered distribution.
+ ///
+ public List GetAllRegisteredDistributions()
+ {
+ var distributions = new List();
+ var linuxSubSystemKey = CurrentUser.OpenSubKey(WslRegistryLocation, false);
+
+ if (linuxSubSystemKey == null)
+ {
+ return new();
+ }
+
+ var defaultDistribution = linuxSubSystemKey.GetValue(DefaultDistributionRegistryName) as string;
+
+ foreach (var subKeyName in linuxSubSystemKey.GetSubKeyNames())
+ {
+ var subKey = linuxSubSystemKey.OpenSubKey(subKeyName);
+
+ if (subKey == null)
+ {
+ continue;
+ }
+
+ var distribution = BuildDistributionInfoFromRegistry(subKey);
+ if (string.IsNullOrEmpty(distribution.Name))
+ {
+ // distribution doesn't have a name. This would happen only if the users registry info
+ // was messed up. WSL would likely not function properly either in these cases.
+ continue;
+ }
+
+ // the last part of the registry subkey is the registered guid of the wsl distribution.
+ var distributionGuid = subKey.Name.Split('\\').LastOrDefault() ?? string.Empty;
+ if (!string.IsNullOrEmpty(defaultDistribution) &&
+ defaultDistribution.Equals(distributionGuid, StringComparison.OrdinalIgnoreCase))
+ {
+ distribution.IsDefaultDistribution = true;
+ }
+
+ distributions.Add(distribution);
+ }
+
+ return distributions;
+ }
+
+ private WslRegisteredDistribution BuildDistributionInfoFromRegistry(RegistryKey registryKey)
+ {
+ var regDistributionName = registryKey.GetValue(DistributionRegistryName) as string ?? string.Empty;
+
+ // The registry key name will be in the form of e.g:
+ // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss\{387ef692-f68f-4f83-a1f1-30c891257bb6}
+ // We need the last segment {387ef692-f68f-4f83-a1f1-30c891257bb6}. Each guid subkey under Lxss is a
+ // registered WSL distribution and the WSL service uses these subkeys to store information about the
+ // distribution.
+ var lastSegmentOfRegistryKeyName = registryKey.Name.Split('\\').LastOrDefault();
+ var version = registryKey.GetValue(WslVersion) as int?;
+ var packageFamilyName = registryKey.GetValue(PackageFamilyRegistryName) as string;
+ return new WslRegisteredDistribution(regDistributionName, lastSegmentOfRegistryKeyName, packageFamilyName, version);
+ }
+
+ ///
+ public void UnregisterDistribution(string distributionName)
+ {
+ var processData = _processCreator.CreateProcessWithoutWindowAndWaitForExit(WslExe, UnregisterDistributionArgs.FormatArgs(distributionName));
+
+ if (!processData.ExitedSuccessfully())
+ {
+ throw new WslServicesMediatorException($"Unable to launch distribution {distributionName} : {processData}");
+ }
+ }
+
+ ///
+ public void LaunchDistribution(string distributionName, string? windowsTerminalProfile)
+ {
+ var executable = GetFileNameForProcessLaunch();
+
+ // Only launch with terminal if its installed
+ if (executable.Equals(WindowsTerminalShimExe, StringComparison.OrdinalIgnoreCase))
+ {
+ LaunchDistributionUsingTerminal(distributionName, windowsTerminalProfile);
+ return;
+ }
+
+ // Default to starting the wsl process directly and passing in its command line args
+ _processCreator.CreateProcessWithWindow(executable, LaunchDistributionWithoutTerminal.FormatArgs(distributionName));
+ }
+
+ private void LaunchDistributionUsingTerminal(string distributionName, string? windowsTerminalProfile)
+ {
+ var terminalArgs = LaunchDistributionInTerminalWithNoProfile.FormatArgs(distributionName);
+
+ if (!string.IsNullOrEmpty(windowsTerminalProfile))
+ {
+ // Launch into terminal with the specified profile and run wsl.exe in the console window
+ terminalArgs = LaunchDistributionInTerminalWithProfile.FormatArgs(windowsTerminalProfile, distributionName);
+ _processCreator.CreateProcessWithWindow(WindowsTerminalShimExe, terminalArgs);
+ }
+ else
+ {
+ // Launch into terminal and run wsl.exe in the console window without a profile
+ _processCreator.CreateProcessWithWindow(WindowsTerminalShimExe, terminalArgs);
+ }
+ }
+
+ ///
+ public void TerminateDistribution(string distributionName)
+ {
+ var processData = _processCreator.CreateProcessWithoutWindowAndWaitForExit(WslExe, TerminateDistributionArgs.FormatArgs(distributionName));
+
+ if (!processData.ExitedSuccessfully())
+ {
+ throw new WslServicesMediatorException($"Unable to terminate distribution {distributionName} : {processData}");
+ }
+ }
+
+ ///
+ public void InstallDistribution(string distributionName)
+ {
+ var executable = GetFileNameForProcessLaunch();
+
+ // Launch into terminal if its installed and run wsl.exe in the console window
+ if (executable.Equals(WindowsTerminalShimExe, StringComparison.OrdinalIgnoreCase))
+ {
+ _processCreator.CreateProcessWithWindow(executable, InstallDistributionWithTerminal.FormatArgs(distributionName));
+ return;
+ }
+
+ // Default to starting the wsl process directly and passing in its command line args
+ _processCreator.CreateProcessWithWindow(executable, InstallDistributionWithoutTerminal.FormatArgs(distributionName));
+ }
+
+ private string GetFileNameForProcessLaunch()
+ {
+ return _packageHelper.IsPackageInstalled(WindowsTerminalPackageFamilyName) ? WindowsTerminalShimExe : WslExe;
+ }
+}
diff --git a/extensions/WSLExtension/Strings/en-US/Resources.resw b/extensions/WSLExtension/Strings/en-US/Resources.resw
new file mode 100644
index 0000000000..09cc76c0f3
--- /dev/null
+++ b/extensions/WSLExtension/Strings/en-US/Resources.resw
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 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
+
+
+ Next
+ Text to display to the user about what the primary button does in the UI
+
+
+ Previous
+ Text to display to the user about what the secondary button does in the UI.
+
+
+ Choose a WSL distribution to install ({0} Available):
+ {Locked="WSL", "{0}"} Label text for a list of cards that appear in the UI. {0} is the number of distributions available
+
+
+ Distribution name:
+ label text for distribution name
+
+
+ Extension:
+ label text for the extensions name
+
+
+ Publisher:
+ text for publisher name label.
+
+
+ {0} distribution logo
+ {Locked="{0}"} The alt text for a logo in Dev Homes review page. {0} is the name of the distribution the user selected.
+
+
+ There are no distributions available to install at this time
+ Error text when there aren't any distributions to display in Dev Homes UX
+
+
+ Default distribution
+ label text for when a wsl distribution is the default distribution
+
+
+ Yes
+ text for the value that will appear in the UI when a wsl distribution is the default distribution
+
+
+ WSL Version
+ {Locked="WSL"} label text for the wsl version number of the distribution
+
+
+ Publisher
+ label text for the publisher of the wsl distribution
+
+
+ Package version
+ label text for the version of the package the wsl distribution was installed from
+
+
+ Unable to terminate distribution due to error: {0}
+ {Locked="{0}"} Error text for when we couldn't terminate a distribution. {0} is the error message
+
+
+ Unable to unregister distribution due to error: {0}
+ {Locked="{0}"} Error text for when we couldn't unregister a distribution. {0} is the error message
+
+
+ Unable to launch a session into the distribution due to error: {0}
+ {Locked="{0}"} Error text for when we couldn't launch into the distribution. {0} is the error message
+
+
+ Preparing to install and register {0}
+ {Locked="{0}"} Text message for when an installation of a wsl distribution start. {0} is the name of the distribution
+
+
+ Waiting for {0}'s installation to complete
+ {Locked="{0}"} Text message to display when a wsl distribution is installing. {0} is the name of the distribution
+
+
+ Unable to install and register {0} due to error: {1}. Try using {wsl.exe} to install it manually. See {aka.ms/wslinstall}
+ {Locked="{0}", "{1}", "{wsl.exe}", {aka.ms/wslinstall}} Text message to display when we failed to install a wsl distribution. {0} is the name of the distribution and {1} is the error message.
+
+
+ Installation timed out, unable to install and register {0}. Try using {wsl.exe} to install manually. See {aka.ms/wslinstall}
+ {Locked="{0}", "{wsl.exe}", {aka.ms/wslinstall}} Text message to display when we failed to install a wsl distribution. {0} is the name of the distribution
+
+
+ Successfully installed and registered {0}
+ {Locked="{0}"} Text message to display when we successfully installed a wsl distribution. {0} is the name of the distribution
+
+
\ No newline at end of file
diff --git a/extensions/WSLExtension/WSLExtension.csproj b/extensions/WSLExtension/WSLExtension.csproj
new file mode 100644
index 0000000000..befbfca2d6
--- /dev/null
+++ b/extensions/WSLExtension/WSLExtension.csproj
@@ -0,0 +1,104 @@
+
+
+
+
+ Exe
+ Debug;Release;Debug_FailFast
+
+
+ Exe
+ Debug;Release;Debug_FailFast
+
+
+ WinExe
+
+
+
+ enable
+ enable
+ x86;x64;arm64
+ win-x86;win-x64;win-arm64
+ WSLExtension.Program
+ WSLExtensionServer
+ $(SolutionDir)\src\Properties\PublishProfiles\win-$(Platform).pubxml
+
+
+ Dev
+ $(DefineConstants);CANARY_BUILD
+ $(DefineConstants);STABLE_BUILD
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
+
+ Always
+
+
+
+
+ portable
+
+
+
+ portable
+
+
+
+ portable
+
+
+
+ portable
+
+
+
+ portable
+
+
+
+ portable
+
+
+
+ portable
+
+
+
+ portable
+
+
+
+ portable
+
+
diff --git a/extensions/WSLExtension/WslAssets/debian.png b/extensions/WSLExtension/WslAssets/debian.png
new file mode 100644
index 0000000000..059e1142a7
Binary files /dev/null and b/extensions/WSLExtension/WslAssets/debian.png differ
diff --git a/extensions/WSLExtension/WslAssets/kali.png b/extensions/WSLExtension/WslAssets/kali.png
new file mode 100644
index 0000000000..ebe30712aa
Binary files /dev/null and b/extensions/WSLExtension/WslAssets/kali.png differ
diff --git a/extensions/WSLExtension/WslAssets/opensuse.png b/extensions/WSLExtension/WslAssets/opensuse.png
new file mode 100644
index 0000000000..98469158ac
Binary files /dev/null and b/extensions/WSLExtension/WslAssets/opensuse.png differ
diff --git a/extensions/WSLExtension/WslAssets/openuse-enterprise.png b/extensions/WSLExtension/WslAssets/openuse-enterprise.png
new file mode 100644
index 0000000000..6435b68903
Binary files /dev/null and b/extensions/WSLExtension/WslAssets/openuse-enterprise.png differ
diff --git a/extensions/WSLExtension/WslAssets/ubuntu.png b/extensions/WSLExtension/WslAssets/ubuntu.png
new file mode 100644
index 0000000000..3e21ef2298
Binary files /dev/null and b/extensions/WSLExtension/WslAssets/ubuntu.png differ
diff --git a/extensions/WSLExtension/WslAssets/wslLinux.png b/extensions/WSLExtension/WslAssets/wslLinux.png
new file mode 100644
index 0000000000..780ef02f6d
Binary files /dev/null and b/extensions/WSLExtension/WslAssets/wslLinux.png differ
diff --git a/extensions/WSLExtension/WslTemplates/ReviewFormForWslInstallation.json b/extensions/WSLExtension/WslTemplates/ReviewFormForWslInstallation.json
new file mode 100644
index 0000000000..4df8d58c1a
--- /dev/null
+++ b/extensions/WSLExtension/WslTemplates/ReviewFormForWslInstallation.json
@@ -0,0 +1,102 @@
+{
+ "type": "AdaptiveCard",
+ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+ "version": "1.6",
+ "body": [
+ {
+ "type": "ColumnSet",
+ "columns": [
+ {
+ "type": "Column",
+ "width": "auto",
+ "spacing": "medium",
+ "verticalContentAlignment": "center",
+ "items": [
+ {
+ "type": "TextBlock",
+ "text": "${ExtensionLabel}",
+ "wrap": true,
+ "size": "medium"
+ },
+ {
+ "type": "TextBlock",
+ "text": "${ProviderName}",
+ "wrap": true,
+ "size": "medium"
+ }
+ ]
+ },
+ {
+ "type": "Column",
+ "width": "auto",
+ "spacing": "extraLarge",
+ "verticalContentAlignment": "center",
+ "items": [
+ {
+ "type": "Image",
+ "url": "${DistributionImage}",
+ "altText": "${DistributionImageLogoAltText}",
+ "height": "48px"
+ }
+ ]
+ },
+ {
+ "type": "Column",
+ "width": "auto",
+ "spacing": "medium",
+ "verticalContentAlignment": "center",
+ "items": [
+ {
+ "type": "TextBlock",
+ "text": "${ReviewPagePublisherLabel}",
+ "wrap": true,
+ "size": "medium"
+ },
+ {
+ "type": "TextBlock",
+ "text": "${PublisherName}",
+ "wrap": true,
+ "size": "medium"
+ }
+ ]
+ },
+ {
+ "type": "Column",
+ "width": "auto",
+ "spacing": "extraLarge",
+ "verticalContentAlignment": "center",
+ "items": [
+ {
+ "type": "TextBlock",
+ "text": "${DistributionNameLabel}",
+ "wrap": true,
+ "size": "medium"
+ },
+ {
+ "type": "TextBlock",
+ "text": "${NewEnvironmentName}",
+ "wrap": true,
+ "size": "medium"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "ActionSet",
+ "id": "DevHomeTopLevelActionSet",
+ "actions": [
+ {
+ "id": "DevHomeMachineConfigurationNextButton",
+ "type": "Action.Submit",
+ "title": "${PrimaryButtonLabelForCreationFlow}"
+ },
+ {
+ "id": "DevHomeMachineConfigurationPreviousButton",
+ "type": "Action.Submit",
+ "title": "${SecondaryButtonLabelForCreationFlow}"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/extensions/WSLExtension/WslTemplates/WslInstallationForm.json b/extensions/WSLExtension/WslTemplates/WslInstallationForm.json
new file mode 100644
index 0000000000..1cd52f7ecb
--- /dev/null
+++ b/extensions/WSLExtension/WslTemplates/WslInstallationForm.json
@@ -0,0 +1,62 @@
+{
+ "type": "AdaptiveCard",
+ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+ "version": "1.5",
+ "body": [
+ {
+ "type": "Container",
+ "items": [
+ {
+ "type": "DevHome.SettingsCardChoiceSet",
+ "id": "SelectedDistributionIndex",
+ "label": "${SettingsCardLabel}",
+ "isRequired": true,
+ "devHomeSettingsCards": [
+ {
+ "type": "DevHome.SettingsCard",
+ "$data": "${AvailableDistributions}",
+ "devHomeSettingsCardDescription": "${PublisherName}",
+ "devHomeSettingsCardHeader": "${Header}",
+ "devHomeSettingsCardHeaderIcon": "${HeaderIcon}"
+ }
+ ]
+ },
+ {
+ "type": "Container",
+ "$data": "${NoDistributionErrorData}",
+ "isVisible": "${NoDistributionsFoundErrorVisibility}",
+ "verticalContentAlignment": "center",
+ "spacing": "extraLarge",
+ "height": "stretch",
+ "items": [
+ {
+ "type": "TextBlock",
+ "text": "${NoDistributionsFoundError}",
+ "size": "large",
+ "weight": "bolder",
+ "wrap": true,
+ "horizontalAlignment": "center",
+ "height": "stretch"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "ActionSet",
+ "id": "DevHomeTopLevelActionSet",
+ "actions": [
+ {
+ "id": "DevHomeMachineConfigurationNextButton",
+ "type": "Action.Submit",
+ "title": "${PrimaryButtonLabelForCreationFlow}"
+ },
+ {
+ "id": "DevHomeMachineConfigurationPreviousButton",
+ "type": "Action.Submit",
+ "title": "${SecondaryButtonLabelForCreationFlow}"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/extensions/WSLExtension/wsl_appsettings.json b/extensions/WSLExtension/wsl_appsettings.json
new file mode 100644
index 0000000000..03e5779060
--- /dev/null
+++ b/extensions/WSLExtension/wsl_appsettings.json
@@ -0,0 +1,31 @@
+{
+ "Serilog": {
+ "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Debug" ],
+ "MinimumLevel": "Debug",
+ "WriteTo": [
+ {
+ "Name": "Console",
+ "Args": {
+ "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
+ "restrictedToMinimumLevel": "Debug"
+ }
+ },
+ {
+ "Name": "File",
+ "Args": {
+ "path": "%WSL_LOGS_ROOT%\\wsl.dhlog",
+ "outputTemplate": "[{Timestamp:yyyy/mm/dd HH:mm:ss.fff} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
+ "restrictedToMinimumLevel": "Information",
+ "rollingInterval": "Day"
+ }
+ },
+ {
+ "Name": "Debug"
+ }
+ ],
+ "Enrich": [ "FromLogContext" ],
+ "Properties": {
+ "SourceContext": "WSLExtension"
+ }
+ }
+}
\ No newline at end of file
diff --git a/extensionsdk/Microsoft.Windows.DevHome.SDK/Microsoft.Windows.DevHome.SDK.idl b/extensionsdk/Microsoft.Windows.DevHome.SDK/Microsoft.Windows.DevHome.SDK.idl
index 3f112c4e8d..9bbae0873c 100644
--- a/extensionsdk/Microsoft.Windows.DevHome.SDK/Microsoft.Windows.DevHome.SDK.idl
+++ b/extensionsdk/Microsoft.Windows.DevHome.SDK/Microsoft.Windows.DevHome.SDK.idl
@@ -1,6 +1,6 @@
namespace Microsoft.Windows.DevHome.SDK
{
- [contractversion(6)]
+ [contractversion(7)]
apicontract DevHomeContract {}
[contract(Microsoft.Windows.DevHome.SDK.DevHomeContract, 1)]
@@ -24,6 +24,10 @@ namespace Microsoft.Windows.DevHome.SDK
[experimental]
[contract(Microsoft.Windows.DevHome.SDK.DevHomeContract, 6)]
QuickStartProject = 5,
+
+ [experimental]
+ [contract(Microsoft.Windows.DevHome.SDK.DevHomeContract, 7)]
+ LocalRepository = 6,
};
// Definitions for exceptions.
diff --git a/common/Services/IAppInstallManagerService.cs b/services/DevHome.Services.Core/Contracts/IMicrosoftStoreService.cs
similarity index 91%
rename from common/Services/IAppInstallManagerService.cs
rename to services/DevHome.Services.Core/Contracts/IMicrosoftStoreService.cs
index 86c69a591e..d3871a2a12 100644
--- a/common/Services/IAppInstallManagerService.cs
+++ b/services/DevHome.Services.Core/Contracts/IMicrosoftStoreService.cs
@@ -1,14 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Windows.ApplicationModel.Store.Preview.InstallControl;
using Windows.Foundation;
-namespace DevHome.Services;
+namespace DevHome.Services.Core.Contracts;
-public interface IAppInstallManagerService
+public interface IMicrosoftStoreService
{
///
public event TypedEventHandler ItemCompleted;
diff --git a/services/DevHome.Services.Core/Contracts/IPackageDeploymentService.cs b/services/DevHome.Services.Core/Contracts/IPackageDeploymentService.cs
new file mode 100644
index 0000000000..7436607dea
--- /dev/null
+++ b/services/DevHome.Services.Core/Contracts/IPackageDeploymentService.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using DevHome.Services.Core.Models;
+using Windows.ApplicationModel;
+
+namespace DevHome.Services.Core.Contracts;
+
+///
+/// Interface for using the deployment API
+///
+///
+public interface IPackageDeploymentService
+{
+ ///
+ /// Register a package for the current user
+ ///
+ /// Package family name
+ /// Register package options
+ /// Exception thrown if registration failed
+ public Task RegisterPackageForCurrentUserAsync(string packageFamilyName, RegisterPackageOptions options = null);
+
+ ///
+ /// Find packages for the current user. If maxVersion is specified, package versions must be
+ /// between minVersion and maxVersion. If maxVersion is null, packages must be above minVersion.
+ /// If no minVersion is specified, returns packages of any version.
+ ///
+ /// An IEnumerable containing the installed packages that meet the version criteria.
+ public IEnumerable FindPackagesForCurrentUser(string packageFamilyName, params (Version minVersion, Version maxVersion)[] ranges);
+}
diff --git a/services/DevHome.Services.Core/DevHome.Services.Core.csproj b/services/DevHome.Services.Core/DevHome.Services.Core.csproj
new file mode 100644
index 0000000000..0505cf72c4
--- /dev/null
+++ b/services/DevHome.Services.Core/DevHome.Services.Core.csproj
@@ -0,0 +1,15 @@
+
+
+
+ DevHome.Services.Core
+ x86;x64;arm64
+ win-x86;win-x64;win-arm64
+
+
+
+
+
+
+
+
+
diff --git a/common/Exceptions/RegisterPackageException.cs b/services/DevHome.Services.Core/Exceptions/RegisterPackageException.cs
similarity index 67%
rename from common/Exceptions/RegisterPackageException.cs
rename to services/DevHome.Services.Core/Exceptions/RegisterPackageException.cs
index 0c2d78955e..b027a20f49 100644
--- a/common/Exceptions/RegisterPackageException.cs
+++ b/services/DevHome.Services.Core/Exceptions/RegisterPackageException.cs
@@ -3,14 +3,14 @@
using System;
-namespace DevHome.Common.Exceptions;
+namespace DevHome.Services.Core.Exceptions;
///
/// Exception thrown if a package registration failed
///
public class RegisterPackageException : Exception
{
- public RegisterPackageException(string? message, Exception? innerException)
+ public RegisterPackageException(string message, Exception innerException)
: base(message, innerException)
{
}
diff --git a/services/DevHome.Services.Core/Extensions/ServiceExtensions.cs b/services/DevHome.Services.Core/Extensions/ServiceExtensions.cs
new file mode 100644
index 0000000000..3e660d1bfe
--- /dev/null
+++ b/services/DevHome.Services.Core/Extensions/ServiceExtensions.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using DevHome.Services.Core.Contracts;
+using DevHome.Services.Core.Services;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace DevHome.Services.Core.Extensions;
+
+public static class ServiceExtensions
+{
+ public static IServiceCollection AddCore(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ return services;
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/Extensions/WinRTObjectExtensions.cs b/services/DevHome.Services.Core/Extensions/WinRTObjectExtensions.cs
similarity index 92%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/Extensions/WinRTObjectExtensions.cs
rename to services/DevHome.Services.Core/Extensions/WinRTObjectExtensions.cs
index 0afb824e3c..f39a61a6b8 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/Extensions/WinRTObjectExtensions.cs
+++ b/services/DevHome.Services.Core/Extensions/WinRTObjectExtensions.cs
@@ -4,7 +4,7 @@
using System;
using WinRT;
-namespace DevHome.SetupFlow.Common.Extensions;
+namespace DevHome.Services.Core.Extensions;
public static class WinRTObjectExtensions
{
diff --git a/services/DevHome.Services.Core/Models/RegisterPackageOptions.cs b/services/DevHome.Services.Core/Models/RegisterPackageOptions.cs
new file mode 100644
index 0000000000..6c36cdb418
--- /dev/null
+++ b/services/DevHome.Services.Core/Models/RegisterPackageOptions.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+using Windows.Management.Deployment;
+
+namespace DevHome.Services.Core.Models;
+
+///
+/// Parameter object for
+/// More details: https://learn.microsoft.com/uwp/api/windows.management.deployment.packagemanager.registerpackagebyfamilynameasync?view=winrt-22621
+///
+public sealed class RegisterPackageOptions
+{
+ ///
+ /// Gets or sets the family names of the dependency packages to be registered.
+ ///
+ public IEnumerable DependencyPackageFamilyNames { get; set; }
+
+ ///
+ /// Gets or sets the package deployment option.
+ ///
+ public DeploymentOptions DeploymentOptions { get; set; }
+
+ ///
+ /// Gets or sets the package volume to store that app data on.
+ ///
+ public PackageVolume AppDataVolume { get; set; }
+
+ ///
+ /// Gets or sets the optional package family names from the main bundle to be registered.
+ ///
+ public IEnumerable OptionalPackageFamilyNames { get; set; }
+}
diff --git a/common/Services/AppInstallManagerService.cs b/services/DevHome.Services.Core/Services/MicrosoftStoreService.cs
similarity index 72%
rename from common/Services/AppInstallManagerService.cs
rename to services/DevHome.Services.Core/Services/MicrosoftStoreService.cs
index 419217ebcb..67fb233f68 100644
--- a/common/Services/AppInstallManagerService.cs
+++ b/services/DevHome.Services.Core/Services/MicrosoftStoreService.cs
@@ -2,24 +2,25 @@
// Licensed under the MIT License.
using System;
+using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
-using Serilog;
+using DevHome.Services.Core.Contracts;
+using Microsoft.Extensions.Logging;
using Windows.ApplicationModel.Store.Preview.InstallControl;
using Windows.Foundation;
-namespace DevHome.Services;
+namespace DevHome.Services.Core.Services;
///
-/// Service class for using the Store API
+/// Service class for using the Microsoft Store API
+/// https://learn.microsoft.com/uwp/api/windows.applicationmodel.store.preview?view=winrt-22621
///
-public class AppInstallManagerService : IAppInstallManagerService
+public class MicrosoftStoreService : IMicrosoftStoreService
{
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(AppInstallManagerService));
-
private readonly AppInstallManager _appInstallManager;
-
- private static readonly TimeSpan StoreInstallTimeout = new(0, 0, 60);
+ private readonly TimeSpan _storeInstallTimeout = TimeSpan.FromMinutes(1);
+ private readonly ILogger _logger;
public event TypedEventHandler ItemCompleted
{
@@ -33,9 +34,10 @@ public event TypedEventHandler _appInstallManager.ItemStatusChanged -= value;
}
- public AppInstallManagerService()
+ public MicrosoftStoreService(ILogger logger)
{
- _appInstallManager = new AppInstallManager();
+ _logger = logger;
+ _appInstallManager = new();
}
public async Task IsAppUpdateAvailableAsync(string productId)
@@ -81,7 +83,7 @@ public async Task TryInstallPackageAsync(string packageId)
var installTask = InstallPackageAsync(packageId);
// Wait for a maximum of StoreInstallTimeout (60 seconds).
- var completedTask = await Task.WhenAny(installTask, Task.Delay(StoreInstallTimeout));
+ var completedTask = await Task.WhenAny(installTask, Task.Delay(_storeInstallTimeout));
if (completedTask.Exception != null)
{
@@ -97,7 +99,7 @@ public async Task TryInstallPackageAsync(string packageId)
}
catch (Exception ex)
{
- _log.Error(ex, "Package installation Failed");
+ _logger.LogError(ex, "Package installation failed");
}
return false;
@@ -111,12 +113,12 @@ await Task.Run(() =>
AppInstallItem installItem;
try
{
- _log.Information($"Starting {packageId} install");
+ _logger.LogInformation($"Starting {packageId} install");
installItem = _appInstallManager.StartAppInstallAsync(packageId, null, true, false).GetAwaiter().GetResult();
}
catch (Exception ex)
{
- _log.Error($"{packageId} install failure");
+ _logger.LogError($"{packageId} install failure");
tcs.SetException(ex);
return tcs.Task;
}
@@ -125,11 +127,11 @@ await Task.Run(() =>
{
if (!tcs.TrySetResult(true))
{
- _log.Information("WidgetHostingService", $"{packageId} In Completed handler, RanToCompleted already set.");
+ _logger.LogInformation($"{packageId} In Completed handler, RanToCompleted already set.");
}
else
{
- _log.Information("WidgetHostingService", $"{packageId} In Completed handler, RanToCompleted set.");
+ _logger.LogInformation($"{packageId} In Completed handler, RanToCompleted set.");
}
};
@@ -138,17 +140,17 @@ await Task.Run(() =>
if (installItem.GetCurrentStatus().InstallState == AppInstallState.Canceled
|| installItem.GetCurrentStatus().InstallState == AppInstallState.Error)
{
- tcs.TrySetException(new System.Management.Automation.JobFailedException(installItem.GetCurrentStatus().ErrorCode.ToString()));
+ tcs.TrySetException(new JobFailedException(installItem.GetCurrentStatus().ErrorCode.ToString()));
}
else if (installItem.GetCurrentStatus().InstallState == AppInstallState.Completed)
{
if (!tcs.TrySetResult(true))
{
- _log.Information("WidgetHostingService", $"{packageId} In StatusChanged handler, RanToCompleted already set.");
+ _logger.LogInformation($"{packageId} In StatusChanged handler, RanToCompleted already set.");
}
else
{
- _log.Information("WidgetHostingService", $"{packageId} In StatusChanged handler, RanToCompleted set.");
+ _logger.LogInformation($"{packageId} In StatusChanged handler, RanToCompleted set.");
}
}
};
diff --git a/common/Services/PackageDeploymentService.cs b/services/DevHome.Services.Core/Services/PackageDeploymentService.cs
similarity index 76%
rename from common/Services/PackageDeploymentService.cs
rename to services/DevHome.Services.Core/Services/PackageDeploymentService.cs
index b03055bc95..afcbe64bb0 100644
--- a/common/Services/PackageDeploymentService.cs
+++ b/services/DevHome.Services.Core/Services/PackageDeploymentService.cs
@@ -5,26 +5,32 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using DevHome.Common.Exceptions;
-using Serilog;
+using DevHome.Services.Core.Contracts;
+using DevHome.Services.Core.Exceptions;
+using DevHome.Services.Core.Models;
+using Microsoft.Extensions.Logging;
using Windows.ApplicationModel;
-using Windows.Management.Deployment;
-namespace DevHome.Common.Services;
+namespace DevHome.Services.Core.Services;
public class PackageDeploymentService : IPackageDeploymentService
{
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(PackageDeploymentService));
+ private readonly ILogger _logger;
+ private readonly Windows.Management.Deployment.PackageManager _packageManager;
- private readonly PackageManager _packageManager = new();
+ public PackageDeploymentService(ILogger logger)
+ {
+ _logger = logger;
+ _packageManager = new();
+ }
///
- public async Task RegisterPackageForCurrentUserAsync(string packageFamilyName, RegisterPackageOptions? options = null)
+ public async Task RegisterPackageForCurrentUserAsync(string packageFamilyName, RegisterPackageOptions options = null)
{
var result = await _packageManager.RegisterPackageByFamilyNameAsync(
packageFamilyName,
options?.DependencyPackageFamilyNames ?? new List(),
- options?.DeploymentOptions ?? DeploymentOptions.None,
+ options?.DeploymentOptions ?? Windows.Management.Deployment.DeploymentOptions.None,
options?.AppDataVolume,
options?.OptionalPackageFamilyNames ?? new List());
@@ -38,7 +44,7 @@ public async Task RegisterPackageForCurrentUserAsync(string packageFamilyName, R
}
///
- public IEnumerable FindPackagesForCurrentUser(string packageFamilyName, params (Version minVersion, Version? maxVersion)[] ranges)
+ public IEnumerable FindPackagesForCurrentUser(string packageFamilyName, params (Version minVersion, Version maxVersion)[] ranges)
{
var packages = _packageManager.FindPackagesForUser(string.Empty, packageFamilyName);
if (packages.Any())
@@ -52,7 +58,7 @@ public IEnumerable FindPackagesForCurrentUser(string packageFamilyName,
var build = version.Build;
var revision = version.Revision;
- _log.Information($"Found package {package.Id.FullName}");
+ _logger.LogInformation($"Found package {package.Id.FullName}");
// Create System.Version type from PackageVersion to test. System.Version supports CompareTo() for easy comparisons.
if (IsVersionSupported(new(major, minor, build, revision), ranges))
@@ -66,7 +72,7 @@ public IEnumerable FindPackagesForCurrentUser(string packageFamilyName,
else
{
// If there is no version installed at all, return the empty enumerable.
- _log.Information($"Found no installed version of {packageFamilyName}");
+ _logger.LogInformation($"Found no installed version of {packageFamilyName}");
return packages;
}
}
@@ -81,7 +87,7 @@ public IEnumerable FindPackagesForCurrentUser(string packageFamilyName,
///
private bool IsVersionAtOrAbove(Version target, Version min) => target.CompareTo(min) >= 0;
- private bool IsVersionSupported(Version target, params (Version minVersion, Version? maxVersion)[] ranges)
+ private bool IsVersionSupported(Version target, params (Version minVersion, Version maxVersion)[] ranges)
{
// If a min version wasn't specified, any version is fine.
if (ranges.Length == 0)
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSC.cs b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSC.cs
new file mode 100644
index 0000000000..49671fdfd2
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSC.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace DevHome.Services.DesiredStateConfiguration.Contracts;
+
+public interface IDSC
+{
+ ///
+ public Task IsUnstubbedAsync();
+
+ ///
+ public Task UnstubAsync();
+
+ ///
+ public Task ApplyConfigurationAsync(IDSCFile file, Guid activityId);
+
+ ///
+ public Task GetConfigurationUnitDetailsAsync(IDSCFile file);
+
+ ///
+ public Task ValidateConfigurationAsync(IDSCFile file);
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCApplicationResult.cs b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCApplicationResult.cs
new file mode 100644
index 0000000000..77b0a7ecf7
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCApplicationResult.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+
+namespace DevHome.Services.DesiredStateConfiguration.Contracts;
+
+public interface IDSCApplicationResult
+{
+ ///
+ /// Gets the configuration set that was applied.
+ ///
+ IDSCSet AppliedSet { get; }
+
+ ///
+ /// Gets a value indicating whether the application of the configuration
+ /// file succeeded.
+ ///
+ bool Succeeded { get; }
+
+ ///
+ /// Gets a value indicating whether a reboot is required to complete the
+ /// application of the configuration file.
+ ///
+ bool RequiresReboot { get; }
+
+ ///
+ /// Gets the exception that occurred during the application of the
+ /// configuration file.
+ ///
+ Exception ResultException { get; }
+
+ ///
+ /// Gets the results of the individual units in the configuration file.
+ ///
+ public IReadOnlyList UnitResults { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCApplicationUnitResult.cs b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCApplicationUnitResult.cs
new file mode 100644
index 0000000000..3a341c5751
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCApplicationUnitResult.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.Management.Configuration;
+
+namespace DevHome.Services.DesiredStateConfiguration.Contracts;
+
+public interface IDSCApplicationUnitResult
+{
+ ///
+ /// Gets the applied configuration unit.
+ ///
+ public IDSCUnit AppliedUnit { get; }
+
+ ///
+ /// Gets the configuration unit error description.
+ ///
+ public string ErrorDescription { get; }
+
+ ///
+ /// Gets a value indicating whether the configuration unit requires a reboot.
+ ///
+ public bool RebootRequired { get; }
+
+ ///
+ /// Gets a value indicating whether the configuration unit is skipped.
+ ///
+ public bool IsSkipped { get; }
+
+ ///
+ /// Gets the HResult of the configuration unit result.
+ ///
+ public int HResult { get; }
+
+ ///
+ /// Gets the result source of the configuration unit result.
+ ///
+ public ConfigurationUnitResultSource ResultSource { get; }
+
+ ///
+ /// Gets a more detailed error message appropriate for diagnosing the root
+ /// cause of an error.
+ ///
+ public string Details { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCDeployment.cs b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCDeployment.cs
new file mode 100644
index 0000000000..e79503dd70
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCDeployment.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Threading.Tasks;
+
+namespace DevHome.Services.DesiredStateConfiguration.Contracts;
+
+internal interface IDSCDeployment
+{
+ ///
+ /// Check if configuration is unstubbed
+ ///
+ /// True if configuration is unstubbed, false otherwise
+ public Task IsUnstubbedAsync();
+
+ ///
+ /// Unstub configuration
+ ///
+ /// True if configuration was unstubbed, false otherwise
+ public Task UnstubAsync();
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCFile.cs b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCFile.cs
new file mode 100644
index 0000000000..415e7e05cf
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCFile.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace DevHome.Services.DesiredStateConfiguration.Contracts;
+
+public interface IDSCFile
+{
+ ///
+ /// Gets the configuration file name
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the configuration file path
+ ///
+ public string Path { get; }
+
+ ///
+ /// Gets the configuration file directory path
+ ///
+ public string DirectoryPath { get; }
+
+ ///
+ /// Gets the configuration file content
+ ///
+ public string Content { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCOperations.cs b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCOperations.cs
new file mode 100644
index 0000000000..369336e04f
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCOperations.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace DevHome.Services.DesiredStateConfiguration.Contracts;
+
+internal interface IDSCOperations
+{
+ ///
+ /// Apply DSC configuration from a file
+ ///
+ /// File containing the DSC configuration
+ /// Activity ID for telemetry
+ /// Result of applying the configuration
+ public Task ApplyConfigurationAsync(IDSCFile file, Guid activityId);
+
+ ///
+ /// Get details of configuration units in a file
+ ///
+ /// File containing the DSC configuration
+ /// Details of configuration units
+ public Task GetConfigurationUnitDetailsAsync(IDSCFile file);
+
+ ///
+ /// Validate the configuration in a file
+ ///
+ /// File containing the DSC configuration
+ public Task ValidateConfigurationAsync(IDSCFile file);
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCSet.cs b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCSet.cs
new file mode 100644
index 0000000000..e5f39a1bc1
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCSet.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+
+namespace DevHome.Services.DesiredStateConfiguration.Contracts;
+
+///
+/// Represents a set of Desired State Configuration (DSC) resources.
+///
+public interface IDSCSet
+{
+ ///
+ /// Gets the identifier used to uniquely identify the instance of a
+ /// configuration set on the system.
+ ///
+ public Guid InstanceIdentifier { get; }
+
+ ///
+ /// Gets the name of the set; if from a file this could be the file name.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the configuration units that are part of this set.
+ ///
+ public IReadOnlyList Units { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCUnit.cs b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCUnit.cs
new file mode 100644
index 0000000000..016fde1d4a
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCUnit.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+
+namespace DevHome.Services.DesiredStateConfiguration.Contracts;
+
+public interface IDSCUnit
+{
+ ///
+ /// Gets the type of the unit being configured
+ ///
+ public string Type { get; }
+
+ ///
+ /// Gets an identifier used to uniquely identify the instance of a configuration unit on the system.
+ ///
+ public string Id { get; }
+
+ ///
+ /// Gets a description of the configuration unit.
+ ///
+ public string Description { get; }
+
+ ///
+ /// Gets the intent of how this configuration unit will be used.
+ ///
+ public string Intent { get; }
+
+ ///
+ /// Gets the values of the configuration units that this unit depends on.
+ ///
+ public IList Dependencies { get; }
+
+ ///
+ /// Gets the values that are for use by the configuration unit itself.
+ ///
+ public IList> Settings { get; }
+
+ ///
+ /// Gets the metadata properties associated with the configuration unit.
+ ///
+ public IList> Metadata { get; }
+
+ ///
+ /// Gets the information on the origin of the configuration unit if available.
+ ///
+ public IDSCUnitDetails Details { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCUnitDetails.cs b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCUnitDetails.cs
new file mode 100644
index 0000000000..4f12e81c5c
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Contracts/IDSCUnitDetails.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace DevHome.Services.DesiredStateConfiguration.Contracts;
+
+public interface IDSCUnitDetails
+{
+ ///
+ /// Gets the type of the configuration unit.
+ ///
+ public string UnitType { get; }
+
+ ///
+ /// Gets a description of the configuration unit.
+ ///
+ public string UnitDescription { get; }
+
+ ///
+ /// Gets the URI of the documentation for the unit of configuration.
+ ///
+ public string UnitDocumentationUri { get; }
+
+ ///
+ /// Gets the name of the module containing the unit of configuration.
+ ///
+ public string ModuleName { get; }
+
+ ///
+ /// Gets the type of the module containing the unit of configuration.
+ ///
+ public string ModuleType { get; }
+
+ ///
+ /// Gets the source of the module containing the unit of configuration.
+ ///
+ public string ModuleSource { get; }
+
+ ///
+ /// Gets the description of the module containing the unit of configuration.
+ ///
+ public string ModuleDescription { get; }
+
+ ///
+ /// Gets the URI of the documentation for the module containing the unit of configuration.
+ ///
+ public string ModuleDocumentationUri { get; }
+
+ ///
+ /// Gets the URI for the published module containing the unit of configuration.
+ ///
+ public string PublishedModuleUri { get; }
+
+ ///
+ /// Gets the version of the module containing the unit of configuration.
+ ///
+ public string Version { get; }
+
+ ///
+ /// Gets a value indicating whether the module is already present on the system.
+ ///
+ public bool IsLocal { get; }
+
+ ///
+ /// Gets the author of the module containing the unit of configuration.
+ ///
+ public string Author { get; }
+
+ ///
+ /// Gets the publisher of the module containing the unit of configuration.
+ ///
+ public string Publisher { get; }
+
+ ///
+ /// Gets a value indicating whether the module comes from a public repository.
+ ///
+ public bool IsPublic { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/DevHome.Services.DesiredStateConfiguration.csproj b/services/DevHome.Services.DesiredStateConfiguration/DevHome.Services.DesiredStateConfiguration.csproj
new file mode 100644
index 0000000000..8e887415bc
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/DevHome.Services.DesiredStateConfiguration.csproj
@@ -0,0 +1,28 @@
+
+
+
+ DevHome.Services.DesiredStateConfiguration
+ x86;x64;arm64
+ win-x86;win-x64;win-arm64
+
+
+
+
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/Exceptions/WinGetConfigurationException.cs b/services/DevHome.Services.DesiredStateConfiguration/Exceptions/ConfigurationException.cs
similarity index 94%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/Exceptions/WinGetConfigurationException.cs
rename to services/DevHome.Services.DesiredStateConfiguration/Exceptions/ConfigurationException.cs
index ad39e7d48f..0576d2d528 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/Exceptions/WinGetConfigurationException.cs
+++ b/services/DevHome.Services.DesiredStateConfiguration/Exceptions/ConfigurationException.cs
@@ -3,9 +3,9 @@
using System;
-namespace DevHome.SetupFlow.Common.Exceptions;
+namespace DevHome.Services.DesiredStateConfiguration.Exceptions;
-public class WinGetConfigurationException : Exception
+public class ConfigurationException : Exception
{
// WinGet Configuration error codes:
// https://github.com/microsoft/winget-cli/blob/master/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorCodes.cs
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/Exceptions/OpenConfigurationSetException.cs b/services/DevHome.Services.DesiredStateConfiguration/Exceptions/OpenConfigurationSetException.cs
similarity index 82%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/Exceptions/OpenConfigurationSetException.cs
rename to services/DevHome.Services.DesiredStateConfiguration/Exceptions/OpenConfigurationSetException.cs
index 63f03ec67b..18eda31c83 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/Exceptions/OpenConfigurationSetException.cs
+++ b/services/DevHome.Services.DesiredStateConfiguration/Exceptions/OpenConfigurationSetException.cs
@@ -4,9 +4,9 @@
using System;
using Microsoft.Management.Configuration;
-namespace DevHome.SetupFlow.Common.Exceptions;
+namespace DevHome.Services.DesiredStateConfiguration.Exceptions;
-public class OpenConfigurationSetException : WinGetConfigurationException
+public class OpenConfigurationSetException : ConfigurationException
{
///
/// Gets the
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Extensions/ServiceExtensions.cs b/services/DevHome.Services.DesiredStateConfiguration/Extensions/ServiceExtensions.cs
new file mode 100644
index 0000000000..6ed38d68a9
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Extensions/ServiceExtensions.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using DevHome.Services.Core.Extensions;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using DevHome.Services.DesiredStateConfiguration.Services;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace DevHome.Services.DesiredStateConfiguration.Extensions;
+
+public static class ServiceExtensions
+{
+ public static IServiceCollection AddDSC(this IServiceCollection services)
+ {
+ services.AddCore();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ return services;
+ }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Models/DSCApplicationResult.cs b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCApplicationResult.cs
new file mode 100644
index 0000000000..144bced123
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCApplicationResult.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using Microsoft.Management.Configuration;
+
+namespace DevHome.Services.DesiredStateConfiguration.Models;
+
+internal sealed class DSCApplicationResult : IDSCApplicationResult
+{
+ public DSCApplicationResult(ConfigurationSet appliedSet, ApplyConfigurationSetResult result)
+ {
+ // Constructor copies all the required data from the out-of-proc COM
+ // objects over to the current process. This ensures that we have this
+ // information available even if the out-of-proc COM objects are no
+ // longer available (e.g. AppInstaller service is no longer running).
+ AppliedSet = new DSCSet(appliedSet);
+ Succeeded = result.ResultCode == null;
+ RequiresReboot = result.UnitResults.Any(result => result.RebootRequired);
+ ResultException = result.ResultCode;
+ UnitResults = result.UnitResults.Select(unitResult => new DSCApplicationUnitResult(unitResult)).ToList();
+ }
+
+ public IDSCSet AppliedSet { get; }
+
+ public bool Succeeded { get; }
+
+ public bool RequiresReboot { get; }
+
+ public Exception ResultException { get; }
+
+ public IReadOnlyList UnitResults { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Models/DSCApplicationUnitResult.cs b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCApplicationUnitResult.cs
new file mode 100644
index 0000000000..b4bd933bd7
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCApplicationUnitResult.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using Microsoft.Management.Configuration;
+using Windows.Win32.Foundation;
+
+namespace DevHome.Services.DesiredStateConfiguration.Models;
+
+internal sealed class DSCApplicationUnitResult : IDSCApplicationUnitResult
+{
+ public DSCApplicationUnitResult(ApplyConfigurationUnitResult unitResult)
+ {
+ // Constructor copies all the required data from the out-of-proc COM
+ // objects over to the current process. This ensures that we have this
+ // information available even if the out-of-proc COM objects are no
+ // longer available (e.g. AppInstaller service is no longer running).
+ AppliedUnit = new DSCUnit(unitResult.Unit);
+ IsSkipped = unitResult.State == ConfigurationUnitState.Skipped;
+ HResult = unitResult.ResultInformation?.ResultCode?.HResult ?? HRESULT.S_OK;
+ ResultSource = unitResult.ResultInformation?.ResultSource ?? ConfigurationUnitResultSource.None;
+ ErrorDescription = unitResult.ResultInformation?.Description;
+ Details = unitResult.ResultInformation?.Details;
+ RebootRequired = unitResult.RebootRequired;
+ }
+
+ public IDSCUnit AppliedUnit { get; }
+
+ public string ErrorDescription { get; }
+
+ public bool RebootRequired { get; }
+
+ public bool IsSkipped { get; }
+
+ public int HResult { get; }
+
+ public ConfigurationUnitResultSource ResultSource { get; }
+
+ public string Details { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Models/DSCFile.cs b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCFile.cs
new file mode 100644
index 0000000000..d3d1be03ba
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCFile.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics;
+using System.IO;
+using System.Threading.Tasks;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+
+namespace DevHome.Services.DesiredStateConfiguration.Models;
+
+///
+/// Model class for a YAML configuration file
+///
+public sealed class DSCFile : IDSCFile
+{
+ private readonly FileInfo _fileInfo;
+
+ private DSCFile(string filePath, string content = null)
+ {
+ _fileInfo = new FileInfo(filePath);
+ Content = content;
+ }
+
+ ///
+ /// Load a configuration file from a path.
+ ///
+ /// The path to the file.
+ /// The configuration file.
+ public static async Task LoadAsync(string filePath)
+ {
+ var file = new DSCFile(filePath);
+ await file.LoadContentAsync();
+ return file;
+ }
+
+ ///
+ /// Create a virtual file with the specified content without writing to disk.
+ ///
+ /// The path to the file.
+ /// Content of the file
+ /// The configuration file.
+ public static IDSCFile CreateVirtual(string filePath, string content)
+ {
+ Debug.Assert(content != null, "Content must not be null");
+ return new DSCFile(filePath, content);
+ }
+
+ ///
+ public string Name => _fileInfo.Name;
+
+ ///
+ public string Path => _fileInfo.FullName;
+
+ ///
+ public string DirectoryPath => _fileInfo.Directory.FullName;
+
+ ///
+ public string Content { get; private set; }
+
+ ///
+ /// Load configuration file content
+ ///
+ private async Task LoadContentAsync()
+ {
+ using var text = _fileInfo.OpenText();
+ Content = await text.ReadToEndAsync();
+ }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Models/DSCSet.cs b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCSet.cs
new file mode 100644
index 0000000000..39580427cd
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCSet.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using Microsoft.Management.Configuration;
+
+namespace DevHome.Services.DesiredStateConfiguration.Models;
+
+internal sealed class DSCSet : IDSCSet
+{
+ public DSCSet(ConfigurationSet configSet)
+ {
+ // Constructor copies all the required data from the out-of-proc COM
+ // objects over to the current process. This ensures that we have this
+ // information available even if the out-of-proc COM objects are no
+ // longer available (e.g. AppInstaller service is no longer running).
+ InstanceIdentifier = configSet.InstanceIdentifier;
+ Name = configSet.Name;
+ Units = configSet.Units.Select(unit => new DSCUnit(unit)).ToList();
+ }
+
+ public Guid InstanceIdentifier { get; }
+
+ public string Name { get; }
+
+ public IReadOnlyList Units { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Models/DSCUnit.cs b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCUnit.cs
new file mode 100644
index 0000000000..3fb608d3bf
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCUnit.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+using System.Linq;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using Microsoft.Management.Configuration;
+
+namespace DevHome.Services.DesiredStateConfiguration.Models;
+
+internal sealed class DSCUnit : IDSCUnit
+{
+ private const string DescriptionMetadataKey = "description";
+ private const string ModuleMetadataKey = "module";
+
+ public DSCUnit(ConfigurationUnit unit)
+ {
+ // Constructor copies all the required data from the out-of-proc COM
+ // objects over to the current process. This ensures that we have this
+ // information available even if the out-of-proc COM objects are no
+ // longer available (e.g. AppInstaller service is no longer running).
+ Type = unit.Type;
+ Id = unit.Identifier;
+ Intent = unit.Intent.ToString();
+ Dependencies = [.. unit.Dependencies];
+
+ // Get description from settings
+ unit.Metadata.TryGetValue(DescriptionMetadataKey, out var descriptionObj);
+ Description = descriptionObj?.ToString() ?? string.Empty;
+
+ // Load dictionary values into list of key value pairs
+ Settings = unit.Settings.Select(s => new KeyValuePair(s.Key, s.Value.ToString())).ToList();
+ Metadata = unit.Metadata.Select(m => new KeyValuePair(m.Key, m.Value.ToString())).ToList();
+
+ // Load details if available, otherwise create empty details with just
+ // the module name if available
+ if (unit.Details == null)
+ {
+ // Get module name from metadata
+ unit.Metadata.TryGetValue(ModuleMetadataKey, out var moduleObj);
+ Details = new DSCUnitDetails(moduleObj?.ToString() ?? string.Empty);
+ }
+ else
+ {
+ Details = new DSCUnitDetails(unit.Details);
+ }
+ }
+
+ public string Type { get; }
+
+ public string Id { get; }
+
+ public string Description { get; }
+
+ public string Intent { get; }
+
+ public IList Dependencies { get; }
+
+ public IList> Settings { get; }
+
+ public IList> Metadata { get; }
+
+ public IDSCUnitDetails Details { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Models/DSCUnitDetails.cs b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCUnitDetails.cs
new file mode 100644
index 0000000000..b63d9c7ec6
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Models/DSCUnitDetails.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using Microsoft.Management.Configuration;
+
+namespace DevHome.Services.DesiredStateConfiguration.Models;
+
+internal sealed class DSCUnitDetails : IDSCUnitDetails
+{
+ public DSCUnitDetails(string moduleName)
+ {
+ // In case the details are not available, we can still create an
+ // instance with the unknown module name.
+ ModuleName = moduleName;
+ }
+
+ public DSCUnitDetails(IConfigurationUnitProcessorDetails details)
+ {
+ // Constructor copies all the required data from the out-of-proc COM
+ // objects over to the current process. This ensures that we have this
+ // information available even if the out-of-proc COM objects are no
+ // longer available (e.g. AppInstaller service is no longer running).
+ UnitType = details.UnitType;
+ UnitDescription = details.UnitDescription;
+ UnitDocumentationUri = details.UnitDocumentationUri?.ToString();
+ ModuleName = details.ModuleName;
+ ModuleType = details.ModuleType;
+ ModuleSource = details.ModuleSource;
+ ModuleDescription = details.ModuleDescription;
+ ModuleDocumentationUri = details.ModuleDocumentationUri?.ToString();
+ PublishedModuleUri = details.PublishedModuleUri?.ToString();
+ Version = details.Version;
+ IsLocal = details.IsLocal;
+ Author = details.Author;
+ Publisher = details.Publisher;
+ IsPublic = details.IsPublic;
+ }
+
+ public string UnitType { get; }
+
+ public string UnitDescription { get; }
+
+ public string UnitDocumentationUri { get; }
+
+ public string ModuleName { get; }
+
+ public string ModuleType { get; }
+
+ public string ModuleSource { get; }
+
+ public string ModuleDescription { get; }
+
+ public string ModuleDocumentationUri { get; }
+
+ public string PublishedModuleUri { get; }
+
+ public string Version { get; }
+
+ public bool IsLocal { get; }
+
+ public string Author { get; }
+
+ public string Publisher { get; }
+
+ public bool IsPublic { get; }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/NativeMethods.txt b/services/DevHome.Services.DesiredStateConfiguration/NativeMethods.txt
new file mode 100644
index 0000000000..cbe0bd787b
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/NativeMethods.txt
@@ -0,0 +1 @@
+S_OK
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Services/DSC.cs b/services/DevHome.Services.DesiredStateConfiguration/Services/DSC.cs
new file mode 100644
index 0000000000..a891558dc3
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Services/DSC.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+
+namespace DevHome.Services.DesiredStateConfiguration.Services;
+
+internal sealed class DSC : IDSC
+{
+ private readonly IDSCDeployment _dscDeployment;
+ private readonly IDSCOperations _dscOperations;
+
+ public DSC(IDSCDeployment dscDeployment, IDSCOperations dscOperations)
+ {
+ _dscDeployment = dscDeployment;
+ _dscOperations = dscOperations;
+ }
+
+ ///
+ public async Task IsUnstubbedAsync() => await _dscDeployment.IsUnstubbedAsync();
+
+ ///
+ public async Task UnstubAsync() => await _dscDeployment.UnstubAsync();
+
+ ///
+ public async Task ApplyConfigurationAsync(IDSCFile file, Guid activityId) => await _dscOperations.ApplyConfigurationAsync(file, activityId);
+
+ ///
+ public async Task GetConfigurationUnitDetailsAsync(IDSCFile file) => await _dscOperations.GetConfigurationUnitDetailsAsync(file);
+
+ ///
+ public async Task ValidateConfigurationAsync(IDSCFile file) => await _dscOperations.ValidateConfigurationAsync(file);
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Services/DSCDeployment.cs b/services/DevHome.Services.DesiredStateConfiguration/Services/DSCDeployment.cs
new file mode 100644
index 0000000000..44cc1f0c3e
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Services/DSCDeployment.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading.Tasks;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using Microsoft.Extensions.Logging;
+using Microsoft.Management.Configuration;
+
+namespace DevHome.Services.DesiredStateConfiguration.Services;
+
+internal sealed class DSCDeployment : IDSCDeployment
+{
+ private readonly ILogger _logger;
+
+ public DSCDeployment(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public async Task IsUnstubbedAsync()
+ {
+ try
+ {
+ return await Task.Run(() => new ConfigurationStaticFunctions().IsConfigurationAvailable);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "An unexpected error occurred when checking if configuration is unstubbed");
+ return false;
+ }
+ }
+
+ public async Task UnstubAsync()
+ {
+ try
+ {
+ _logger.LogInformation("Starting to unstub configuration ...");
+ await new ConfigurationStaticFunctions().EnsureConfigurationAvailableAsync();
+ _logger.LogInformation("Configuration unstubbed successfully");
+ return true;
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "An unexpected error occurred when unstubbing configuration");
+ return false;
+ }
+ }
+}
diff --git a/services/DevHome.Services.DesiredStateConfiguration/Services/DSCOperations.cs b/services/DevHome.Services.DesiredStateConfiguration/Services/DSCOperations.cs
new file mode 100644
index 0000000000..048dc300b3
--- /dev/null
+++ b/services/DevHome.Services.DesiredStateConfiguration/Services/DSCOperations.cs
@@ -0,0 +1,160 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading.Tasks;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using DevHome.Services.DesiredStateConfiguration.Exceptions;
+using DevHome.Services.DesiredStateConfiguration.Models;
+using DevHome.Telemetry;
+using Microsoft.Extensions.Logging;
+using Microsoft.Management.Configuration;
+using Windows.Storage.Streams;
+
+namespace DevHome.Services.DesiredStateConfiguration.Services;
+
+internal sealed class DSCOperations : IDSCOperations
+{
+ private readonly ILogger _logger;
+ private const string PowerShellHandlerIdentifier = "pwsh";
+
+ public DSCOperations(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ public async Task ApplyConfigurationAsync(IDSCFile file, Guid activityId)
+ {
+ var processor = await CreateConfigurationProcessorAsync();
+ var configSet = await OpenConfigurationSetAsync(file, processor);
+
+ _logger.LogInformation("Starting to apply configuration set");
+ var outOfProcResult = await processor.ApplySetAsync(configSet, ApplyConfigurationSetFlags.None);
+ var inProcResult = new DSCApplicationResult(configSet, outOfProcResult);
+ _logger.LogInformation($"Apply configuration finished. HResult: {inProcResult.ResultException?.HResult}");
+ ReportApplyConfigurationResult(inProcResult, activityId);
+ return inProcResult;
+ }
+
+ ///
+ public async Task GetConfigurationUnitDetailsAsync(IDSCFile file)
+ {
+ var processor = await CreateConfigurationProcessorAsync();
+ var configSet = await OpenConfigurationSetAsync(file, processor);
+
+ _logger.LogInformation("Getting configuration unit details");
+ await processor.GetSetDetailsAsync(configSet, ConfigurationUnitDetailFlags.ReadOnly);
+ return new DSCSet(configSet);
+ }
+
+ ///
+ public async Task ValidateConfigurationAsync(IDSCFile file)
+ {
+ // Try to open the configuration file to validate it.
+ _logger.LogInformation("Validating configuration file");
+ var processor = await CreateConfigurationProcessorAsync();
+ await OpenConfigurationSetAsync(file, processor);
+ }
+
+ ///
+ /// Report the result of applying a configuration set
+ ///
+ /// Result of applying the configuration set
+ /// Activity ID
+ private void ReportApplyConfigurationResult(IDSCApplicationResult result, Guid activityId)
+ {
+ foreach (var unitResult in result.UnitResults)
+ {
+ TelemetryFactory.Get().Log("ConfigurationFile_UnitResult", Telemetry.LogLevel.Critical, new ConfigurationUnitResultEvent(unitResult), activityId);
+ }
+
+ TelemetryFactory.Get().Log("ConfigurationFile_Result", Telemetry.LogLevel.Critical, new ConfigurationSetResultEvent(result), activityId);
+ }
+
+ ///
+ /// Create a configuration processor using DSC configuration API
+ ///
+ /// Configuration processor
+ private async Task CreateConfigurationProcessorAsync()
+ {
+ ConfigurationStaticFunctions config = new();
+ var factory = await config.CreateConfigurationSetProcessorFactoryAsync(PowerShellHandlerIdentifier);
+
+ // Create and configure the configuration processor.
+ var processor = config.CreateConfigurationProcessor(factory);
+ processor.Caller = nameof(DevHome);
+ processor.Diagnostics += LogConfigurationDiagnostics;
+ processor.MinimumLevel = DiagnosticLevel.Verbose;
+ return processor;
+ }
+
+ ///
+ /// Open a configuration set using DSC configuration API
+ ///
+ /// Configuration file
+ /// Configuration set
+ /// Thrown when the configuration set cannot be opened
+ private async Task OpenConfigurationSetAsync(IDSCFile file, ConfigurationProcessor processor)
+ {
+ var inputStream = await StringToStreamAsync(file.Content);
+ var openConfigResult = processor.OpenConfigurationSet(inputStream);
+ var configSet = openConfigResult.Set ?? throw new OpenConfigurationSetException(openConfigResult.ResultCode, openConfigResult.Field, openConfigResult.Value);
+
+ // Set input file path in the configuration set to inform the
+ // processor about the working directory when applying the
+ // configuration
+ configSet.Name = file.Name;
+ configSet.Origin = file.DirectoryPath;
+ configSet.Path = file.Path;
+ return configSet;
+ }
+
+ ///
+ /// Map configuration diagnostics to logger
+ ///
+ /// The event sender
+ /// Diagnostic information
+ private void LogConfigurationDiagnostics(object sender, IDiagnosticInformation diagnosticInformation)
+ {
+ switch (diagnosticInformation.Level)
+ {
+ case DiagnosticLevel.Warning:
+ _logger.LogWarning(diagnosticInformation.Message);
+ return;
+ case DiagnosticLevel.Error:
+ _logger.LogError(diagnosticInformation.Message);
+ return;
+ case DiagnosticLevel.Critical:
+ _logger.LogCritical(diagnosticInformation.Message);
+ return;
+ case DiagnosticLevel.Verbose:
+ _logger.LogTrace(diagnosticInformation.Message);
+ return;
+ case DiagnosticLevel.Informational:
+ default:
+ _logger.LogInformation(diagnosticInformation.Message);
+ return;
+ }
+ }
+
+ ///
+ /// Convert a string to an input stream
+ ///
+ /// Target string
+ /// Input stream
+ private static async Task StringToStreamAsync(string str)
+ {
+ InMemoryRandomAccessStream result = new();
+ using (DataWriter writer = new(result))
+ {
+ writer.UnicodeEncoding = UnicodeEncoding.Utf8;
+ writer.WriteString(str);
+ await writer.StoreAsync();
+ writer.DetachStream();
+ }
+
+ result.Seek(0);
+ return result;
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationSetResultEvent.cs b/services/DevHome.Services.DesiredStateConfiguration/TelemetryEvents/ConfigurationSetResultEvent.cs
similarity index 51%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationSetResultEvent.cs
rename to services/DevHome.Services.DesiredStateConfiguration/TelemetryEvents/ConfigurationSetResultEvent.cs
index 2a50cd58ed..511e6869b6 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationSetResultEvent.cs
+++ b/services/DevHome.Services.DesiredStateConfiguration/TelemetryEvents/ConfigurationSetResultEvent.cs
@@ -3,31 +3,28 @@
using System;
using System.Diagnostics.Tracing;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
using DevHome.Telemetry;
using Microsoft.Diagnostics.Telemetry;
using Microsoft.Diagnostics.Telemetry.Internal;
-using Microsoft.Management.Configuration;
-namespace DevHome.SetupFlow.Common.TelemetryEvents;
+namespace DevHome.Services.DesiredStateConfiguration.Services;
[EventData]
internal sealed class ConfigurationSetResultEvent : EventBase
{
- private readonly ConfigurationSet _configSet;
+ private readonly IDSCApplicationResult _setResult;
- private readonly ApplyConfigurationSetResult _setResult;
+ public string SetName => _setResult.AppliedSet.Name;
- public string SetName => _configSet.Name;
+ public string SetInstanceIdentifier => _setResult.AppliedSet.InstanceIdentifier.ToString();
- public string SetInstanceIdentifier => _configSet.InstanceIdentifier.ToString();
-
- public int ExceptionHResult => _setResult.ResultCode?.HResult ?? 0;
+ public int ExceptionHResult => _setResult.ResultException?.HResult ?? 0;
public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServicePerformance;
- public ConfigurationSetResultEvent(ConfigurationSet configSet, ApplyConfigurationSetResult setResult)
+ public ConfigurationSetResultEvent(IDSCApplicationResult setResult)
{
- _configSet = configSet;
_setResult = setResult;
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationUnitResultEvent.cs b/services/DevHome.Services.DesiredStateConfiguration/TelemetryEvents/ConfigurationUnitResultEvent.cs
similarity index 61%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationUnitResultEvent.cs
rename to services/DevHome.Services.DesiredStateConfiguration/TelemetryEvents/ConfigurationUnitResultEvent.cs
index ea906e962d..f865def3d0 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/TelemetryEvents/ConfigurationUnitResultEvent.cs
+++ b/services/DevHome.Services.DesiredStateConfiguration/TelemetryEvents/ConfigurationUnitResultEvent.cs
@@ -3,21 +3,21 @@
using System;
using System.Diagnostics.Tracing;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
using DevHome.Telemetry;
using Microsoft.Diagnostics.Telemetry;
using Microsoft.Diagnostics.Telemetry.Internal;
-using Microsoft.Management.Configuration;
-namespace DevHome.SetupFlow.Common.TelemetryEvents;
+namespace DevHome.Services.DesiredStateConfiguration.Services;
[EventData]
internal sealed class ConfigurationUnitResultEvent : EventBase
{
- private readonly ApplyConfigurationUnitResult _unitResult;
+ private readonly IDSCApplicationUnitResult _unitResult;
- public string Type => _unitResult.Unit.Type;
+ public string Type => _unitResult.AppliedUnit.Type;
- public int ExceptionHResult => _unitResult.ResultInformation.ResultCode?.HResult ?? 0;
+ public int ExceptionHResult => _unitResult.HResult;
public string ResultDescription { get; private set; }
@@ -27,11 +27,11 @@ internal sealed class ConfigurationUnitResultEvent : EventBase
public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServicePerformance;
- public ConfigurationUnitResultEvent(ApplyConfigurationUnitResult unitResult)
+ public ConfigurationUnitResultEvent(IDSCApplicationUnitResult unitResult)
{
_unitResult = unitResult;
- ResultDescription = _unitResult.ResultInformation.Description;
- ResultDetails = _unitResult.ResultInformation.Details;
+ ResultDescription = _unitResult.ErrorDescription;
+ ResultDetails = _unitResult.Details;
}
public override void ReplaceSensitiveStrings(Func replaceSensitiveStrings)
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassModel.cs b/services/DevHome.Services.WindowsPackageManager/COM/ClassModel.cs
similarity index 96%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassModel.cs
rename to services/DevHome.Services.WindowsPackageManager/COM/ClassModel.cs
index baa92b5353..ebbbc34a98 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassModel.cs
+++ b/services/DevHome.Services.WindowsPackageManager/COM/ClassModel.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
-namespace DevHome.SetupFlow.Common.WindowsPackageManager;
+namespace DevHome.Services.WindowsPackageManager.COM;
internal sealed class ClassModel
{
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassesDefinition.cs b/services/DevHome.Services.WindowsPackageManager/COM/ClassesDefinition.cs
similarity index 98%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassesDefinition.cs
rename to services/DevHome.Services.WindowsPackageManager/COM/ClassesDefinition.cs
index 1037c54a5a..d31503cf68 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClassesDefinition.cs
+++ b/services/DevHome.Services.WindowsPackageManager/COM/ClassesDefinition.cs
@@ -5,7 +5,7 @@
using System.Collections.Generic;
using Microsoft.Management.Deployment;
-namespace DevHome.SetupFlow.Common.WindowsPackageManager;
+namespace DevHome.Services.WindowsPackageManager.COM;
internal static class ClassesDefinition
{
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClsidContext.cs b/services/DevHome.Services.WindowsPackageManager/COM/ClsidContext.cs
similarity index 66%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClsidContext.cs
rename to services/DevHome.Services.WindowsPackageManager/COM/ClsidContext.cs
index ab3f82014c..8c0f580d75 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/ClsidContext.cs
+++ b/services/DevHome.Services.WindowsPackageManager/COM/ClsidContext.cs
@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-namespace DevHome.SetupFlow.Common.WindowsPackageManager;
+namespace DevHome.Services.WindowsPackageManager.COM;
-public enum ClsidContext
+internal enum ClsidContext
{
// Production CLSID Guids
Prod,
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/WindowsPackageManagerDefaultFactory.cs b/services/DevHome.Services.WindowsPackageManager/COM/WindowsPackageManagerDefaultFactory.cs
similarity index 87%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/WindowsPackageManagerDefaultFactory.cs
rename to services/DevHome.Services.WindowsPackageManager/COM/WindowsPackageManagerDefaultFactory.cs
index a5f941d7e0..c32eb32311 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/WindowsPackageManagerDefaultFactory.cs
+++ b/services/DevHome.Services.WindowsPackageManager/COM/WindowsPackageManagerDefaultFactory.cs
@@ -7,9 +7,9 @@
using Windows.Win32.System.Com;
using WinRT;
-namespace DevHome.SetupFlow.Common.WindowsPackageManager;
+namespace DevHome.Services.WindowsPackageManager.COM;
-public class WindowsPackageManagerDefaultFactory : WindowsPackageManagerFactory
+internal sealed class WindowsPackageManagerDefaultFactory : WindowsPackageManagerFactory
{
public WindowsPackageManagerDefaultFactory(ClsidContext clsidContext = ClsidContext.Prod)
: base(clsidContext)
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/WindowsPackageManagerFactory.cs b/services/DevHome.Services.WindowsPackageManager/COM/WindowsPackageManagerFactory.cs
similarity index 92%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/WindowsPackageManagerFactory.cs
rename to services/DevHome.Services.WindowsPackageManager/COM/WindowsPackageManagerFactory.cs
index 2c39ef543c..7c18962853 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/WindowsPackageManagerFactory.cs
+++ b/services/DevHome.Services.WindowsPackageManager/COM/WindowsPackageManagerFactory.cs
@@ -4,14 +4,14 @@
using System;
using Microsoft.Management.Deployment;
-namespace DevHome.SetupFlow.Common.WindowsPackageManager;
+namespace DevHome.Services.WindowsPackageManager.COM;
///
/// Factory class for creating WinGet COM objects.
/// Details about each method can be found in the source IDL:
/// https://github.com/microsoft/winget-cli/blob/master/src/Microsoft.Management.Deployment/PackageManager.idl
///
-public abstract class WindowsPackageManagerFactory
+internal abstract class WindowsPackageManagerFactory
{
private readonly ClsidContext _clsidContext;
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/WindowsPackageManagerManualActivationFactory.cs b/services/DevHome.Services.WindowsPackageManager/COM/WindowsPackageManagerManualActivationFactory.cs
similarity index 90%
rename from tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/WindowsPackageManagerManualActivationFactory.cs
rename to services/DevHome.Services.WindowsPackageManager/COM/WindowsPackageManagerManualActivationFactory.cs
index 6225431263..edea9c8d74 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/WindowsPackageManager/WindowsPackageManagerManualActivationFactory.cs
+++ b/services/DevHome.Services.WindowsPackageManager/COM/WindowsPackageManagerManualActivationFactory.cs
@@ -5,7 +5,7 @@
using System.Runtime.InteropServices;
using WinRT;
-namespace DevHome.SetupFlow.Common.WindowsPackageManager;
+namespace DevHome.Services.WindowsPackageManager.COM;
///
/// Factory for creating winget COM objects using manual activation
@@ -21,7 +21,7 @@ namespace DevHome.SetupFlow.Common.WindowsPackageManager;
/// This class is based on what the winget cmdlets do. See
/// https://github.com/microsoft/winget-cli/blob/master/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs
///
-public class WindowsPackageManagerManualActivationFactory : WindowsPackageManagerFactory
+internal sealed class WindowsPackageManagerManualActivationFactory : WindowsPackageManagerFactory
{
// The only CLSID context supported by the DLL we call is Prod.
// If we want to use Dev classes we have to use a Dev version of the DLL.
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/IWindowsPackageManager.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGet.cs
similarity index 78%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/IWindowsPackageManager.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGet.cs
index 78c211ccc9..885b669a86 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/IWindowsPackageManager.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGet.cs
@@ -1,27 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
-using DevHome.SetupFlow.Services.WinGet;
-using DevHome.SetupFlow.Services.WinGet.Operations;
+using DevHome.Services.WindowsPackageManager.Models;
+using DevHome.Services.WindowsPackageManager.Services;
-namespace DevHome.SetupFlow.Services;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
///
/// Interface for interacting with the WinGet package manager.
/// More details: https://github.com/microsoft/winget-cli/blob/master/src/Microsoft.Management.Deployment/PackageManager.idl
///
-public interface IWindowsPackageManager
+public interface IWinGet
{
+ public const string AppInstallerProductId = WinGetDeployment.AppInstallerProductId;
+ public const int AppInstallerErrorFacility = WinGetDeployment.AppInstallerErrorFacility;
+
///
/// Initialize the winget package manager.
///
public Task InitializeAsync();
///
- public Task InstallPackageAsync(WinGetPackageUri packageUri);
+ public Task InstallPackageAsync(WinGetPackageUri packageUri, Guid activityId);
///
public Task> GetPackagesAsync(IList packageUris);
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetCatalogConnector.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetCatalogConnector.cs
similarity index 92%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetCatalogConnector.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetCatalogConnector.cs
index a83f71cdf6..c25c4fad79 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetCatalogConnector.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetCatalogConnector.cs
@@ -2,9 +2,9 @@
// Licensed under the MIT License.
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
internal interface IWinGetCatalogConnector
{
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetDeployment.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetDeployment.cs
similarity index 62%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetDeployment.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetDeployment.cs
index 8fcf4cd98d..33daedcce9 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetDeployment.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetDeployment.cs
@@ -3,7 +3,7 @@
using System.Threading.Tasks;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
internal interface IWinGetDeployment
{
@@ -25,16 +25,4 @@ internal interface IWinGetDeployment
///
/// True if AppInstaller was registered, false otherwise.
public Task RegisterAppInstallerAsync();
-
- ///
- /// Check if configuration is unstubbed
- ///
- /// True if configuration is unstubbed, false otherwise
- public Task IsConfigurationUnstubbedAsync();
-
- ///
- /// Unstub configuration
- ///
- /// True if configuration was unstubbed, false otherwise
- public Task UnstubConfigurationAsync();
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/InstallPackageResult.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetInstallPackageResult.cs
similarity index 69%
rename from tools/SetupFlow/DevHome.SetupFlow/Models/InstallPackageResult.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetInstallPackageResult.cs
index cc8333765e..4bea293e4f 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Models/InstallPackageResult.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetInstallPackageResult.cs
@@ -1,27 +1,21 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-namespace DevHome.SetupFlow.Models;
-
-public class InstallPackageResult
-{
- ///
- /// Gets a value indicating whether a restart is required to complete the
- /// installation
- ///
- public bool RebootRequired
- {
- get; init;
- }
-
- ///
- /// Gets the error code of the overall operation.
- ///
- ///
- /// Reference: https://github.com/msftrubengu/winget-cli/blob/demo/src/Microsoft.Management.Deployment/PackageManager.idl
- ///
- public int ExtendedErrorCode
- {
- get; init;
- }
-}
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace DevHome.Services.WindowsPackageManager.Contracts;
+
+public interface IWinGetInstallPackageResult
+{
+ ///
+ /// Gets a value indicating whether a restart is required to complete the
+ /// installation
+ ///
+ public bool RebootRequired { get; }
+
+ ///
+ /// Gets the error code of the overall operation.
+ ///
+ ///
+ /// Reference: https://github.com/msftrubengu/winget-cli/blob/demo/src/Microsoft.Management.Deployment/PackageManager.idl
+ ///
+ public int ExtendedErrorCode { get; }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetOperations.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetOperations.cs
similarity index 67%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetOperations.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetOperations.cs
index b2442d87d9..918ae44a5e 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetOperations.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetOperations.cs
@@ -1,16 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet.Operations;
+namespace DevHome.Services.WindowsPackageManager.Contracts.Operations;
internal interface IWinGetOperations
{
/// "
- public Task InstallPackageAsync(WinGetPackageUri packageUri);
+ public Task InstallPackageAsync(WinGetPackageUri packageUri, Guid activityId);
/// "
public Task> GetPackagesAsync(IList packageUris);
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/IWinGetPackage.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackage.cs
similarity index 79%
rename from tools/SetupFlow/DevHome.SetupFlow/Models/IWinGetPackage.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackage.cs
index 04ae8c6d00..26efcd4932 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Models/IWinGetPackage.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackage.cs
@@ -3,10 +3,10 @@
using System;
using System.Collections.Generic;
-using DevHome.SetupFlow.Services;
+using DevHome.Services.WindowsPackageManager.Models;
using Windows.Storage.Streams;
-namespace DevHome.SetupFlow.Models;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
///
/// Interface for a winget package.
@@ -137,18 +137,19 @@ public string InstallationNotes
}
///
- /// Create an install task for this package
+ /// Gets a value indicating whether the package requires elevation
///
- /// Windows package manager service
- /// String resource service
- /// Version to install
- /// Activity id
- /// Task object for installing this package
- InstallPackageTask CreateInstallTask(
- IWindowsPackageManager wpm,
- ISetupFlowStringResource stringResource,
- string installVersion,
- Guid activityId);
+ public bool IsElevationRequired
+ {
+ get;
+ }
+
+ ///
+ /// Gets the package uri of this package
+ ///
+ /// The version to install
+ /// The package uri
+ public WinGetPackageUri GetUri(string installVersion = null);
}
///
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetPackageCache.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackageCache.cs
similarity index 90%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetPackageCache.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackageCache.cs
index 402f02f3c7..ee604181da 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetPackageCache.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackageCache.cs
@@ -2,9 +2,9 @@
// Licensed under the MIT License.
using System.Collections.Generic;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
///
/// Thread -safe cache for packages
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetPackageFinder.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackageFinder.cs
similarity index 91%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetPackageFinder.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackageFinder.cs
index 89056aceb2..60415a7657 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetPackageFinder.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackageFinder.cs
@@ -3,10 +3,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Models;
using Microsoft.Management.Deployment;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
internal interface IWinGetPackageFinder
{
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetPackageInstaller.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackageInstaller.cs
similarity index 58%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetPackageInstaller.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackageInstaller.cs
index f4c8cc6a56..bd5dc154bf 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetPackageInstaller.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackageInstaller.cs
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
internal interface IWinGetPackageInstaller
{
@@ -14,6 +15,7 @@ internal interface IWinGetPackageInstaller
/// Catalog from which to install the package
/// Package id to install
/// Version of the package to install
+ /// Activity id for telemetry
/// Result of the installation
- public Task InstallPackageAsync(WinGetCatalog catalog, string packageId, string version = null);
+ public Task InstallPackageAsync(WinGetCatalog catalog, string packageId, string version, Guid activityId);
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetProtocolParser.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetProtocolParser.cs
similarity index 91%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetProtocolParser.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetProtocolParser.cs
index c26026c934..28f0abdbeb 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetProtocolParser.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetProtocolParser.cs
@@ -2,9 +2,9 @@
// Licensed under the MIT License.
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
internal interface IWinGetProtocolParser
{
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetRecovery.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetRecovery.cs
similarity index 85%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetRecovery.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetRecovery.cs
index fc7ca3811e..8b979cad16 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/IWinGetRecovery.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetRecovery.cs
@@ -4,7 +4,7 @@
using System;
using System.Threading.Tasks;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
internal interface IWinGetRecovery
{
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/IWinGetGetPackageOperation.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/Operations/IWinGetGetPackageOperation.cs
similarity index 77%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/IWinGetGetPackageOperation.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/Operations/IWinGetGetPackageOperation.cs
index 9ce3b7eaa9..90904a0f5e 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/IWinGetGetPackageOperation.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/Operations/IWinGetGetPackageOperation.cs
@@ -3,9 +3,9 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet.Operations;
+namespace DevHome.Services.WindowsPackageManager.Contracts.Operations;
internal interface IWinGetGetPackageOperation
{
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/IWinGetInstallOperation.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/Operations/IWinGetInstallOperation.cs
similarity index 52%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/IWinGetInstallOperation.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/Operations/IWinGetInstallOperation.cs
index 5c0c51fa1a..03fafb55b6 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/IWinGetInstallOperation.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/Operations/IWinGetInstallOperation.cs
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet.Operations;
+namespace DevHome.Services.WindowsPackageManager.Contracts;
internal interface IWinGetInstallOperation
{
@@ -12,6 +13,7 @@ internal interface IWinGetInstallOperation
/// Installs a package from a URI.
///
/// Uri of the package to install.
+ /// Activity id for telemetry.
/// Result of the installation.
- public Task InstallPackageAsync(WinGetPackageUri packageUri);
+ public Task InstallPackageAsync(WinGetPackageUri packageUri, Guid activityId);
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/IWinGetSearchOperation.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/Operations/IWinGetSearchOperation.cs
similarity index 85%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/IWinGetSearchOperation.cs
rename to services/DevHome.Services.WindowsPackageManager/Contracts/Operations/IWinGetSearchOperation.cs
index 37735dc6be..423aec64bc 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/IWinGetSearchOperation.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Contracts/Operations/IWinGetSearchOperation.cs
@@ -3,9 +3,8 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
-namespace DevHome.SetupFlow.Services.WinGet.Operations;
+namespace DevHome.Services.WindowsPackageManager.Contracts.Operations;
internal interface IWinGetSearchOperation
{
diff --git a/services/DevHome.Services.WindowsPackageManager/DevHome.Services.WindowsPackageManager.csproj b/services/DevHome.Services.WindowsPackageManager/DevHome.Services.WindowsPackageManager.csproj
new file mode 100644
index 0000000000..34238a0df0
--- /dev/null
+++ b/services/DevHome.Services.WindowsPackageManager/DevHome.Services.WindowsPackageManager.csproj
@@ -0,0 +1,53 @@
+
+
+
+ DevHome.Services.WindowsPackageManager
+ x86;x64;arm64
+ win-x86;win-x64;win-arm64
+
+
+
+
+ 10.0.19041.0
+ Microsoft.Management.Deployment
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+
+
+
+
+ NU1701
+ true
+ none
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Exceptions/CatalogNotInitializedException.cs b/services/DevHome.Services.WindowsPackageManager/Exceptions/CatalogNotInitializedException.cs
similarity index 61%
rename from tools/SetupFlow/DevHome.SetupFlow/Exceptions/CatalogNotInitializedException.cs
rename to services/DevHome.Services.WindowsPackageManager/Exceptions/CatalogNotInitializedException.cs
index 73666e3cbf..c0674c814b 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Exceptions/CatalogNotInitializedException.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Exceptions/CatalogNotInitializedException.cs
@@ -3,12 +3,12 @@
using System;
-namespace DevHome.SetupFlow.Exceptions;
+namespace DevHome.Services.WindowsPackageManager.Exceptions;
///
/// Exception thrown when the catalog is not initialized, likely because of a
/// failure to connect to the catalog.
///
-public class CatalogNotInitializedException : ArgumentNullException
+internal sealed class CatalogNotInitializedException : ArgumentNullException
{
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Exceptions/FindPackagesException.cs b/services/DevHome.Services.WindowsPackageManager/Exceptions/FindPackagesException.cs
similarity index 88%
rename from tools/SetupFlow/DevHome.SetupFlow/Exceptions/FindPackagesException.cs
rename to services/DevHome.Services.WindowsPackageManager/Exceptions/FindPackagesException.cs
index ab8d1ee389..3d96c8566d 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Exceptions/FindPackagesException.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Exceptions/FindPackagesException.cs
@@ -4,7 +4,7 @@
using System;
using Microsoft.Management.Deployment;
-namespace DevHome.SetupFlow.Exceptions;
+namespace DevHome.Services.WindowsPackageManager.Exceptions;
///
/// Exception thrown if a find package operation failed
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Exceptions/InstallPackageException.cs b/services/DevHome.Services.WindowsPackageManager/Exceptions/InstallPackageException.cs
similarity index 97%
rename from tools/SetupFlow/DevHome.SetupFlow/Exceptions/InstallPackageException.cs
rename to services/DevHome.Services.WindowsPackageManager/Exceptions/InstallPackageException.cs
index 8b6277ed54..2b354d8f98 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Exceptions/InstallPackageException.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Exceptions/InstallPackageException.cs
@@ -4,7 +4,7 @@
using System;
using Microsoft.Management.Deployment;
-namespace DevHome.SetupFlow.Exceptions;
+namespace DevHome.Services.WindowsPackageManager.Exceptions;
///
/// Exception thrown if package installation failed
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Exceptions/WindowsPackageManagerRecoveryException.cs b/services/DevHome.Services.WindowsPackageManager/Exceptions/WindowsPackageManagerRecoveryException.cs
similarity index 77%
rename from tools/SetupFlow/DevHome.SetupFlow/Exceptions/WindowsPackageManagerRecoveryException.cs
rename to services/DevHome.Services.WindowsPackageManager/Exceptions/WindowsPackageManagerRecoveryException.cs
index 4844a757e3..1eef582c43 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Exceptions/WindowsPackageManagerRecoveryException.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Exceptions/WindowsPackageManagerRecoveryException.cs
@@ -3,7 +3,7 @@
using System;
-namespace DevHome.SetupFlow.Exceptions;
+namespace DevHome.Services.WindowsPackageManager.Exceptions;
///
/// Exception thrown when the Windows Package Manager recovery fails.
diff --git a/services/DevHome.Services.WindowsPackageManager/Extensions/ServiceExtensions.cs b/services/DevHome.Services.WindowsPackageManager/Extensions/ServiceExtensions.cs
new file mode 100644
index 0000000000..8ccfda289b
--- /dev/null
+++ b/services/DevHome.Services.WindowsPackageManager/Extensions/ServiceExtensions.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using DevHome.Services.Core.Extensions;
+using DevHome.Services.WindowsPackageManager.COM;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Contracts.Operations;
+using DevHome.Services.WindowsPackageManager.Services;
+using DevHome.Services.WindowsPackageManager.Services.Operations;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace DevHome.Services.WindowsPackageManager.Extensions;
+
+public static class ServiceExtensions
+{
+ public static IServiceCollection AddWinGet(this IServiceCollection services, bool useDev = false)
+ {
+ var context = useDev ? ClsidContext.Dev : ClsidContext.Prod;
+ services.AddSingleton(new WindowsPackageManagerDefaultFactory(context));
+ services.AddWinGetCommon();
+ return services;
+ }
+
+ public static IServiceCollection AddWinGetElevated(this IServiceCollection services)
+ {
+ services.AddSingleton(new WindowsPackageManagerManualActivationFactory());
+ services.AddWinGetCommon();
+ return services;
+ }
+
+ private static void AddWinGetCommon(this IServiceCollection services)
+ {
+ services.AddCore();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Extensions/WindowsPackageManagerFactoryExtensions.cs b/services/DevHome.Services.WindowsPackageManager/Extensions/WindowsPackageManagerFactoryExtensions.cs
similarity index 90%
rename from tools/SetupFlow/DevHome.SetupFlow/Extensions/WindowsPackageManagerFactoryExtensions.cs
rename to services/DevHome.Services.WindowsPackageManager/Extensions/WindowsPackageManagerFactoryExtensions.cs
index 2814ef4008..bb1a855894 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Extensions/WindowsPackageManagerFactoryExtensions.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Extensions/WindowsPackageManagerFactoryExtensions.cs
@@ -2,12 +2,12 @@
// Licensed under the MIT License.
using System.Collections.Generic;
-using DevHome.SetupFlow.Common.WindowsPackageManager;
+using DevHome.Services.WindowsPackageManager.COM;
using Microsoft.Management.Deployment;
-namespace DevHome.SetupFlow.Extensions;
+namespace DevHome.Services.WindowsPackageManager.Extensions;
-public static class WindowsPackageManagerFactoryExtensions
+internal static class WindowsPackageManagerFactoryExtensions
{
///
/// Creates a new instance of with the specified values.
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetCatalog.cs b/services/DevHome.Services.WindowsPackageManager/Models/WinGetCatalog.cs
similarity index 84%
rename from tools/SetupFlow/DevHome.SetupFlow/Models/WinGetCatalog.cs
rename to services/DevHome.Services.WindowsPackageManager/Models/WinGetCatalog.cs
index d9b57a48f5..046b7d3c60 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetCatalog.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Models/WinGetCatalog.cs
@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using WPMPackageCatalog = Microsoft.Management.Deployment.PackageCatalog;
+using Microsoft.Management.Deployment;
-namespace DevHome.SetupFlow.Models;
+namespace DevHome.Services.WindowsPackageManager.Models;
///
/// Internal model for a WinGet catalog.
@@ -28,7 +28,7 @@ public enum CatalogType
///
/// Gets the catalog object.
///
- public WPMPackageCatalog Catalog { get; }
+ public PackageCatalog Catalog { get; }
///
/// Gets the type of the catalog.
@@ -43,7 +43,7 @@ public enum CatalogType
///
public string UnknownCatalogName { get; }
- public WinGetCatalog(WPMPackageCatalog catalog, CatalogType type, string unknownCatalogName = null)
+ public WinGetCatalog(PackageCatalog catalog, CatalogType type, string unknownCatalogName = null)
{
Catalog = catalog;
Type = type;
diff --git a/services/DevHome.Services.WindowsPackageManager/Models/WinGetInstallPackageResult.cs b/services/DevHome.Services.WindowsPackageManager/Models/WinGetInstallPackageResult.cs
new file mode 100644
index 0000000000..08a420c5d9
--- /dev/null
+++ b/services/DevHome.Services.WindowsPackageManager/Models/WinGetInstallPackageResult.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using DevHome.Services.WindowsPackageManager.Contracts;
+
+namespace DevHome.Services.WindowsPackageManager.Models;
+
+internal sealed class WinGetInstallPackageResult : IWinGetInstallPackageResult
+{
+ ///
+ public bool RebootRequired { get; init; }
+
+ ///
+ public int ExtendedErrorCode { get; init; }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackage.cs b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackage.cs
similarity index 81%
rename from tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackage.cs
rename to services/DevHome.Services.WindowsPackageManager/Models/WinGetPackage.cs
index f74b337de5..02f28bd616 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackage.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackage.cs
@@ -5,28 +5,28 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
-using DevHome.SetupFlow.Common.Helpers;
-using DevHome.SetupFlow.Services;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using Microsoft.Extensions.Logging;
using Microsoft.Management.Deployment;
-using Serilog;
using Windows.Storage.Streams;
-namespace DevHome.SetupFlow.Models;
+namespace DevHome.Services.WindowsPackageManager.Models;
///
/// Model class for a Windows Package Manager package.
///
-public class WinGetPackage : IWinGetPackage
+internal sealed class WinGetPackage : IWinGetPackage
{
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WinGetPackage));
+ private readonly ILogger _logger;
- public WinGetPackage(CatalogPackage package, bool requiresElevated)
+ public WinGetPackage(ILogger logger, CatalogPackage package, bool requiresElevated)
{
// WinGetPackage constructor copies all the required data from the
// out-of-proc COM objects over to the current process. This ensures
// that we have this information available even if the out-of-proc COM
// objects are no longer available (e.g. AppInstaller service is no
// longer running).
+ _logger = logger;
Id = package.Id;
CatalogId = package.DefaultInstallVersion.PackageCatalog.Info.Id;
CatalogName = package.DefaultInstallVersion.PackageCatalog.Info.Name;
@@ -75,13 +75,11 @@ public WinGetPackage(CatalogPackage package, bool requiresElevated)
public bool IsElevationRequired { get; }
- public InstallPackageTask CreateInstallTask(
- IWindowsPackageManager wpm,
- ISetupFlowStringResource stringResource,
- string installVersion,
- Guid activityId) => new(wpm, stringResource, this, installVersion, activityId);
-
- public WinGetPackageUri GetUri(string installVersion) => new(CatalogName, Id, new(installVersion));
+ public WinGetPackageUri GetUri(string installVersion = null)
+ {
+ var uriOptions = string.IsNullOrEmpty(installVersion) ? null : new WinGetPackageUriOptions(installVersion);
+ return new(CatalogName, Id, uriOptions);
+ }
///
/// Gets the package metadata from the current culture name (e.g. 'en-US')
@@ -101,7 +99,7 @@ private T GetMetadataValue(CatalogPackage package, Func availableVersions, Pa
}
catch (Exception e)
{
- _log.Error(e, $"Unable to validate if the version {versionInfo.Version} is in the list of available versions");
+ _logger.LogError(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/WinGetPackageUri.cs b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackageUri.cs
similarity index 95%
rename from tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackageUri.cs
rename to services/DevHome.Services.WindowsPackageManager/Models/WinGetPackageUri.cs
index 5e6eb40cf1..d3f1f57df2 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackageUri.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackageUri.cs
@@ -4,12 +4,12 @@
using System;
using System.Diagnostics;
-namespace DevHome.SetupFlow.Models;
+namespace DevHome.Services.WindowsPackageManager.Models;
///
/// Windows package manager (winget) package Uri
///
-public class WinGetPackageUri
+public sealed class WinGetPackageUri
{
///
/// Windows package manager custom protocol scheme
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackageUriOptions.cs b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackageUriOptions.cs
similarity index 94%
rename from tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackageUriOptions.cs
rename to services/DevHome.Services.WindowsPackageManager/Models/WinGetPackageUriOptions.cs
index dceee49b63..3b89801c84 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackageUriOptions.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackageUriOptions.cs
@@ -4,7 +4,7 @@
using System;
using System.Web;
-namespace DevHome.SetupFlow.Models;
+namespace DevHome.Services.WindowsPackageManager.Models;
///
/// Windows package manager (winget) package Uri options
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackageUriParameters.cs b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackageUriParameters.cs
similarity index 76%
rename from tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackageUriParameters.cs
rename to services/DevHome.Services.WindowsPackageManager/Models/WinGetPackageUriParameters.cs
index 806c00abb4..183dda801f 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Models/WinGetPackageUriParameters.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackageUriParameters.cs
@@ -3,7 +3,7 @@
using System;
-namespace DevHome.SetupFlow.Models;
+namespace DevHome.Services.WindowsPackageManager.Models;
[Flags]
public enum WinGetPackageUriParameters
diff --git a/services/DevHome.Services.WindowsPackageManager/NativeMethods.txt b/services/DevHome.Services.WindowsPackageManager/NativeMethods.txt
new file mode 100644
index 0000000000..42060e7960
--- /dev/null
+++ b/services/DevHome.Services.WindowsPackageManager/NativeMethods.txt
@@ -0,0 +1,2 @@
+S_OK
+CoCreateInstance
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/WinGetGetPackageOperation.cs b/services/DevHome.Services.WindowsPackageManager/Services/Operations/WinGetGetPackageOperation.cs
similarity index 76%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/WinGetGetPackageOperation.cs
rename to services/DevHome.Services.WindowsPackageManager/Services/Operations/WinGetGetPackageOperation.cs
index 270402008e..9e065e784d 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/WinGetGetPackageOperation.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Services/Operations/WinGetGetPackageOperation.cs
@@ -4,28 +4,32 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
-using Serilog;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Contracts.Operations;
+using DevHome.Services.WindowsPackageManager.Models;
+using Microsoft.Extensions.Logging;
-namespace DevHome.SetupFlow.Services.WinGet.Operations;
+namespace DevHome.Services.WindowsPackageManager.Services.Operations;
///
/// Get packages using WinGet with recovery
///
internal sealed class WinGetGetPackageOperation : IWinGetGetPackageOperation
{
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WinGetGetPackageOperation));
+ private readonly ILogger _logger;
private readonly IWinGetPackageCache _packageCache;
private readonly IWinGetProtocolParser _protocolParser;
private readonly IWinGetPackageFinder _packageFinder;
private readonly IWinGetRecovery _recovery;
public WinGetGetPackageOperation(
+ ILogger logger,
IWinGetPackageCache packageCache,
IWinGetProtocolParser protocolParser,
IWinGetPackageFinder packageFinder,
IWinGetRecovery recovery)
{
+ _logger = logger;
_packageCache = packageCache;
_protocolParser = protocolParser;
_packageFinder = packageFinder;
@@ -40,8 +44,8 @@ public async Task> GetPackagesAsync(IList $"({p.Id}, {p.CatalogName})"))}]");
- _log.Information($"Package URIs not found in cache [{string.Join(", ", packageUrisToQuery)}]");
+ _logger.LogInformation($"Packages loaded from cache [{string.Join(", ", cachedPackages.Select(p => $"({p.Id}, {p.CatalogName})"))}]");
+ _logger.LogInformation($"Package URIs not found in cache [{string.Join(", ", packageUrisToQuery)}]");
// Get packages grouped by catalog
var getPackagesTasks = new List>>();
@@ -56,13 +60,13 @@ public async Task> GetPackagesAsync(IList p.PackageId).ToHashSet();
- _log.Information($"Getting packages [{string.Join(", ", packageIds)}] from parsed uri catalog name: {firstParsedUri.CatalogName}");
+ _logger.LogInformation($"Getting packages [{string.Join(", ", packageIds)}] from parsed uri catalog name: {firstParsedUri.CatalogName}");
// Get packages from the catalog
var catalog = await _protocolParser.ResolveCatalogAsync(firstParsedUri);
var packagesOutOfProc = await _packageFinder.GetPackagesAsync(catalog, packageIds);
var packagesInProc = packagesOutOfProc
- .Select(p => new WinGetPackage(p, _packageFinder.IsElevationRequired(p)))
+ .Select(p => new WinGetPackage(_logger, p, _packageFinder.IsElevationRequired(p)))
.ToList();
packagesInProc
.ForEach(p => _packageCache.TryAddPackage(_protocolParser.CreatePackageUri(p), p));
@@ -88,7 +92,7 @@ public async Task> GetPackagesAsync(IList
/// Install packages using WinGet with recovery
@@ -29,12 +32,12 @@ public WinGetInstallOperation(
}
///
- public async Task InstallPackageAsync(WinGetPackageUri packageUri)
+ public async Task InstallPackageAsync(WinGetPackageUri packageUri, Guid activityId)
{
return await _recovery.DoWithRecoveryAsync(async () =>
{
var catalog = await _protocolParser.ResolveCatalogAsync(packageUri);
- return await _packageInstaller.InstallPackageAsync(catalog, packageUri.PackageId, packageUri.Options.Version);
+ return await _packageInstaller.InstallPackageAsync(catalog, packageUri.PackageId, packageUri.Options.Version, activityId);
});
}
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/WinGetSearchOperation.cs b/services/DevHome.Services.WindowsPackageManager/Services/Operations/WinGetSearchOperation.cs
similarity index 69%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/WinGetSearchOperation.cs
rename to services/DevHome.Services.WindowsPackageManager/Services/Operations/WinGetSearchOperation.cs
index a3f9fc946a..b011d8bfa7 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/Operations/WinGetSearchOperation.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Services/Operations/WinGetSearchOperation.cs
@@ -4,24 +4,30 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Contracts.Operations;
+using DevHome.Services.WindowsPackageManager.Models;
+using Microsoft.Extensions.Logging;
-namespace DevHome.SetupFlow.Services.WinGet.Operations;
+namespace DevHome.Services.WindowsPackageManager.Services.Operations;
///
/// Search for packages using WinGet with recovery
///
internal sealed class WinGetSearchOperation : IWinGetSearchOperation
{
+ private readonly ILogger _logger;
private readonly IWinGetCatalogConnector _catalogConnector;
private readonly IWinGetPackageFinder _packageFinder;
private readonly IWinGetRecovery _recovery;
public WinGetSearchOperation(
+ ILogger logger,
IWinGetCatalogConnector catalogConnector,
IWinGetPackageFinder packageFinder,
IWinGetRecovery recovery)
{
+ _logger = logger;
_catalogConnector = catalogConnector;
_packageFinder = packageFinder;
_recovery = recovery;
@@ -35,7 +41,7 @@ public async Task> SearchAsync(string query, uint limit)
var searchCatalog = await _catalogConnector.GetCustomSearchCatalogAsync();
var results = await _packageFinder.SearchAsync(searchCatalog, query, limit);
return results
- .Select(p => new WinGetPackage(p, _packageFinder.IsElevationRequired(p)))
+ .Select(p => new WinGetPackage(_logger, p, _packageFinder.IsElevationRequired(p)))
.ToList();
});
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WindowsPackageManager.cs b/services/DevHome.Services.WindowsPackageManager/Services/WinGet.cs
similarity index 83%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WindowsPackageManager.cs
rename to services/DevHome.Services.WindowsPackageManager/Services/WinGet.cs
index 3945c48371..17f2128467 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WindowsPackageManager.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Services/WinGet.cs
@@ -1,18 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
-using DevHome.SetupFlow.Services.WinGet;
-using DevHome.SetupFlow.Services.WinGet.Operations;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Contracts.Operations;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services;
+namespace DevHome.Services.WindowsPackageManager.Services;
///
/// Windows package manager class is an entry point for using the WinGet COM API.
///
-internal sealed class WindowsPackageManager : IWindowsPackageManager
+internal sealed class WinGet : IWinGet
{
// WinGet services
private readonly IWinGetCatalogConnector _catalogConnector;
@@ -24,7 +25,7 @@ internal sealed class WindowsPackageManager : IWindowsPackageManager
public static int AppInstallerErrorFacility => WinGetDeployment.AppInstallerErrorFacility;
- public WindowsPackageManager(
+ public WinGet(
IWinGetCatalogConnector catalogConnector,
IWinGetDeployment deployment,
IWinGetOperations operations,
@@ -45,7 +46,7 @@ public async Task InitializeAsync()
}
///
- public async Task InstallPackageAsync(WinGetPackageUri packageUri) => await _operations.InstallPackageAsync(packageUri);
+ public async Task InstallPackageAsync(WinGetPackageUri packageUri, Guid activityId) => await _operations.InstallPackageAsync(packageUri, activityId);
///
public async Task> GetPackagesAsync(IList packageUris) => await _operations.GetPackagesAsync(packageUris);
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetCatalogConnector.cs b/services/DevHome.Services.WindowsPackageManager/Services/WinGetCatalogConnector.cs
similarity index 87%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetCatalogConnector.cs
rename to services/DevHome.Services.WindowsPackageManager/Services/WinGetCatalogConnector.cs
index d70f08539d..4dd70c7b6d 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetCatalogConnector.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Services/WinGetCatalogConnector.cs
@@ -5,18 +5,18 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Common.WindowsPackageManager;
-using DevHome.SetupFlow.Extensions;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.COM;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Extensions;
+using DevHome.Services.WindowsPackageManager.Models;
+using Microsoft.Extensions.Logging;
using Microsoft.Management.Deployment;
-using Serilog;
-using WPMPackageCatalog = Microsoft.Management.Deployment.PackageCatalog;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Services;
internal sealed class WinGetCatalogConnector : IWinGetCatalogConnector, IDisposable
{
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WinGetCatalogConnector));
+ private readonly ILogger _logger;
private readonly IWinGetPackageCache _packageCache;
private readonly WindowsPackageManagerFactory _wingetFactory;
private readonly Dictionary _customCatalogs = new();
@@ -32,9 +32,11 @@ internal sealed class WinGetCatalogConnector : IWinGetCatalogConnector, IDisposa
private bool _disposedValue;
public WinGetCatalogConnector(
+ ILogger logger,
WindowsPackageManagerFactory wingetFactory,
IWinGetPackageCache packageCache)
{
+ _logger = logger;
_packageCache = packageCache;
_wingetFactory = wingetFactory;
}
@@ -179,7 +181,7 @@ public async Task RecoverDisconnectedCatalogsAsync()
var recoverSearchCatalog = !IsCatalogAlive(_customSearchCatalog);
var recoverWinGetCatalog = !IsCatalogAlive(_predefinedWingetCatalog);
var recoverMsStoreCatalog = !IsCatalogAlive(_predefinedMsStoreCatalog);
- _log.Information($"Recovering disconnected catalogs [should recover?]: Search [{recoverSearchCatalog}], WinGet [{recoverWinGetCatalog}], MsStore [{recoverMsStoreCatalog}]");
+ _logger.LogInformation($"Recovering disconnected catalogs [should recover?]: Search [{recoverSearchCatalog}], WinGet [{recoverWinGetCatalog}], MsStore [{recoverMsStoreCatalog}]");
await Task.WhenAll(
recoverSearchCatalog ? CreateAndConnectSearchCatalogAsync() : Task.CompletedTask,
recoverWinGetCatalog ? CreateAndConnectWinGetCatalogAsync() : Task.CompletedTask,
@@ -223,7 +225,7 @@ private async Task CreateAndConnectSearchCatalogAsync()
}
catch (Exception e)
{
- _log.Error(e, $"Failed to create or connect to search catalog.");
+ _logger.LogError(e, $"Failed to create or connect to search catalog.");
}
}
@@ -241,7 +243,7 @@ private async Task CreateAndConnectWinGetCatalogAsync()
}
catch (Exception e)
{
- _log.Error(e, $"Failed to create or connect to 'winget' catalog source.");
+ _logger.LogError(e, $"Failed to create or connect to 'winget' catalog source.");
}
}
@@ -259,7 +261,7 @@ private async Task CreateAndConnectMsStoreCatalogAsync()
}
catch (Exception e)
{
- _log.Error(e, $"Failed to create or connect to 'msstore' catalog source.");
+ _logger.LogError(e, $"Failed to create or connect to 'msstore' catalog source.");
}
}
@@ -278,7 +280,7 @@ private async Task CreateAndConnectCustomCatalogAsync(string cata
}
catch (Exception e)
{
- _log.Error(e, $"Failed to create or connect to custom catalog with name {catalogName}");
+ _logger.LogError(e, $"Failed to create or connect to custom catalog with name {catalogName}");
return null;
}
}
@@ -288,7 +290,7 @@ private async Task CreateAndConnectCustomCatalogAsync(string cata
///
/// Catalog references
/// Connected catalog or null if an error occurred
- private async Task CreateAndConnectCatalogInternalAsync(IReadOnlyList catalogReferences)
+ private async Task CreateAndConnectCatalogInternalAsync(IReadOnlyList catalogReferences)
{
// Search in all catalogs including the local catalog which allows detecting if a package is installed
var disconnectedCatalog = _wingetFactory.CreateCompositePackageCatalog(CompositeSearchBehavior.RemotePackagesFromAllCatalogs, catalogReferences);
@@ -298,7 +300,7 @@ private async Task CreateAndConnectCatalogInternalAsync(IRead
return connectResult.PackageCatalog;
}
- _log.Error($"Failed to connect to catalog with status {connectResult.Status}");
+ _logger.LogError($"Failed to connect to catalog with status {connectResult.Status}");
return null;
}
diff --git a/services/DevHome.Services.WindowsPackageManager/Services/WinGetDeployment.cs b/services/DevHome.Services.WindowsPackageManager/Services/WinGetDeployment.cs
new file mode 100644
index 0000000000..0064edc169
--- /dev/null
+++ b/services/DevHome.Services.WindowsPackageManager/Services/WinGetDeployment.cs
@@ -0,0 +1,100 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading.Tasks;
+using DevHome.Services.Core.Contracts;
+using DevHome.Services.Core.Exceptions;
+using DevHome.Services.WindowsPackageManager.COM;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using Microsoft.Extensions.Logging;
+using Microsoft.Management.Deployment;
+
+namespace DevHome.Services.WindowsPackageManager.Services;
+
+internal sealed class WinGetDeployment : IWinGetDeployment
+{
+ private readonly ILogger _logger;
+ private readonly WindowsPackageManagerFactory _wingetFactory;
+ private readonly IMicrosoftStoreService _msStoreService;
+ private readonly IPackageDeploymentService _packageDeploymentService;
+
+ // App installer constants
+ public const int AppInstallerErrorFacility = 0xA15;
+ public const string AppInstallerProductId = "9NBLGGH4NNS1";
+ public const string AppInstallerPackageFamilyName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe";
+
+ public WinGetDeployment(
+ ILogger logger,
+ WindowsPackageManagerFactory wingetFactory,
+ IMicrosoftStoreService msStoreService,
+ IPackageDeploymentService packageDeploymentService)
+ {
+ _logger = logger;
+ _wingetFactory = wingetFactory;
+ _msStoreService = msStoreService;
+ _packageDeploymentService = packageDeploymentService;
+ }
+
+ ///
+ public async Task IsAvailableAsync()
+ {
+ try
+ {
+ // Quick check (without recovery) if the COM server is available by
+ // creating a dummy out-of-proc object
+ await Task.Run(() =>
+ {
+ _logger.LogInformation($"Attempting to create a dummy out-of-proc {nameof(PackageManager)} COM object to test if the COM server is available");
+ _wingetFactory.CreatePackageManager();
+ _logger.LogInformation($"WinGet COM Server is available");
+ });
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, $"Failed to create dummy {nameof(PackageManager)} COM object. WinGet COM Server is not available.");
+ return false;
+ }
+ }
+
+ ///
+ public async Task IsUpdateAvailableAsync()
+ {
+ try
+ {
+ _logger.LogInformation("Checking if AppInstaller has an update ...");
+ var appInstallerUpdateAvailable = await _msStoreService.IsAppUpdateAvailableAsync(AppInstallerProductId);
+ _logger.LogInformation($"AppInstaller update available = {appInstallerUpdateAvailable}");
+ return appInstallerUpdateAvailable;
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Failed to check if AppInstaller has an update, defaulting to false");
+ return false;
+ }
+ }
+
+ ///
+ public async Task RegisterAppInstallerAsync()
+ {
+ try
+ {
+ _logger.LogInformation("Starting AppInstaller registration ...");
+ await _packageDeploymentService.RegisterPackageForCurrentUserAsync(AppInstallerPackageFamilyName);
+ _logger.LogInformation($"AppInstaller registered successfully");
+ return true;
+ }
+ catch (RegisterPackageException e)
+ {
+ _logger.LogError(e, $"Failed to register AppInstaller");
+ return false;
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "An unexpected error occurred when registering AppInstaller");
+ return false;
+ }
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetOperations.cs b/services/DevHome.Services.WindowsPackageManager/Services/WinGetOperations.cs
similarity index 70%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetOperations.cs
rename to services/DevHome.Services.WindowsPackageManager/Services/WinGetOperations.cs
index 8ddc086943..dd8b1e14bd 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetOperations.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Services/WinGetOperations.cs
@@ -1,11 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Contracts.Operations;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet.Operations;
+namespace DevHome.Services.WindowsPackageManager.Services;
internal sealed class WinGetOperations : IWinGetOperations
{
@@ -24,7 +27,7 @@ public WinGetOperations(
}
///
- public async Task InstallPackageAsync(WinGetPackageUri packageUri) => await _installOperation.InstallPackageAsync(packageUri);
+ public async Task InstallPackageAsync(WinGetPackageUri packageUri, Guid activityId) => await _installOperation.InstallPackageAsync(packageUri, activityId);
///
public async Task> GetPackagesAsync(IList packageUris) => await _getPackageOperation.GetPackagesAsync(packageUris);
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetPackageCache.cs b/services/DevHome.Services.WindowsPackageManager/Services/WinGetPackageCache.cs
similarity index 90%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetPackageCache.cs
rename to services/DevHome.Services.WindowsPackageManager/Services/WinGetPackageCache.cs
index 7e21237f27..9b09ba6ecb 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetPackageCache.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Services/WinGetPackageCache.cs
@@ -2,9 +2,10 @@
// Licensed under the MIT License.
using System.Collections.Generic;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Services;
///
/// Thread-safe cache for packages
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetPackageFinder.cs b/services/DevHome.Services.WindowsPackageManager/Services/WinGetPackageFinder.cs
similarity index 74%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetPackageFinder.cs
rename to services/DevHome.Services.WindowsPackageManager/Services/WinGetPackageFinder.cs
index 95acebd080..684971e89f 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetPackageFinder.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Services/WinGetPackageFinder.cs
@@ -5,25 +5,27 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Common.WindowsPackageManager;
-using DevHome.SetupFlow.Exceptions;
-using DevHome.SetupFlow.Extensions;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.COM;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Exceptions;
+using DevHome.Services.WindowsPackageManager.Extensions;
+using DevHome.Services.WindowsPackageManager.Models;
+using Microsoft.Extensions.Logging;
using Microsoft.Management.Deployment;
-using Serilog;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Services;
///
/// Finds packages using the Windows Package Manager (WinGet).
///
internal sealed class WinGetPackageFinder : IWinGetPackageFinder
{
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WinGetPackageFinder));
+ private readonly ILogger _logger;
private readonly WindowsPackageManagerFactory _wingetFactory;
- public WinGetPackageFinder(WindowsPackageManagerFactory wingetFactory)
+ public WinGetPackageFinder(ILogger logger, WindowsPackageManagerFactory wingetFactory)
{
+ _logger = logger;
_wingetFactory = wingetFactory;
}
@@ -36,7 +38,7 @@ public async Task> SearchAsync(WinGetCatalog catalog, stri
}
// Use default filter criteria for searching ('winget search {query}')
- _log.Information($"Searching for '{query}'. Result limit: {limit}");
+ _logger.LogInformation($"Searching for '{query}'. Result limit: {limit}");
var filter = _wingetFactory.CreatePackageMatchFilter(PackageMatchField.CatalogDefault, PackageFieldMatchOption.ContainsCaseInsensitive, query);
var options = _wingetFactory.CreateFindPackagesOptions();
options.Selectors.Add(filter);
@@ -64,12 +66,12 @@ public async Task> GetPackagesAsync(WinGetCatalog catalog,
throw new CatalogNotInitializedException();
}
- _log.Information($"Getting packages: [{string.Join(", ", packageIds)}] from {catalog.GetDescriptiveName()}");
+ _logger.LogInformation($"Getting packages: [{string.Join(", ", packageIds)}] from {catalog.GetDescriptiveName()}");
// Skip search if set is empty
if (!packageIds.Any())
{
- _log.Warning($"{nameof(GetPackagesAsync)} received an empty set of package id. Skipping operation.");
+ _logger.LogWarning($"{nameof(GetPackagesAsync)} received an empty set of package id. Skipping operation.");
return new List();
}
@@ -84,22 +86,22 @@ public bool IsElevationRequired(CatalogPackage package)
var packageId = package.Id;
try
{
- _log.Information($"Getting applicable installer info for package {packageId}");
+ _logger.LogInformation($"Getting applicable installer info for package {packageId}");
var installOptions = _wingetFactory.CreateInstallOptions();
installOptions.PackageInstallScope = PackageInstallScope.Any;
var applicableInstaller = package.DefaultInstallVersion.GetApplicableInstaller(installOptions);
if (applicableInstaller != null)
{
- _log.Information($"Elevation requirement = {applicableInstaller.ElevationRequirement} for package {packageId}");
+ _logger.LogInformation($"Elevation requirement = {applicableInstaller.ElevationRequirement} for package {packageId}");
return applicableInstaller.ElevationRequirement == ElevationRequirement.ElevationRequired || applicableInstaller.ElevationRequirement == ElevationRequirement.ElevatesSelf;
}
- _log.Warning($"No applicable installer info found for package {packageId}; defaulting to not requiring elevation");
+ _logger.LogWarning($"No applicable installer info found for package {packageId}; defaulting to not requiring elevation");
return false;
}
catch
{
- _log.Warning($"Failed to get elevation requirement for package {packageId}; defaulting to not requiring elevation");
+ _logger.LogWarning($"Failed to get elevation requirement for package {packageId}; defaulting to not requiring elevation");
return false;
}
}
@@ -151,11 +153,11 @@ private async Task> GetPackagesMultiQueriesAsync(WinGetCat
/// List of packages
private async Task> GetPackagesInternalAsync(WinGetCatalog catalog, FindPackagesOptions options)
{
- _log.Information($"Performing search on catalog {catalog.GetDescriptiveName()}");
+ _logger.LogInformation($"Performing search on catalog {catalog.GetDescriptiveName()}");
var findResult = await catalog.Catalog.FindPackagesAsync(options);
if (findResult.Status != FindPackagesResultStatus.Ok)
{
- _log.Error($"Failed to find packages with status {findResult.Status}");
+ _logger.LogError($"Failed to find packages with status {findResult.Status}");
throw new FindPackagesException(findResult.Status);
}
@@ -167,7 +169,7 @@ private async Task> GetPackagesInternalAsync(WinGetCatalog
result.Add(findResult.Matches[i].CatalogPackage);
}
- _log.Information($"Found {result.Count} results from catalog {catalog.GetDescriptiveName()} [{string.Join(", ", result.Select(p => p.Id))}]");
+ _logger.LogInformation($"Found {result.Count} results from catalog {catalog.GetDescriptiveName()} [{string.Join(", ", result.Select(p => p.Id))}]");
return result;
}
diff --git a/services/DevHome.Services.WindowsPackageManager/Services/WinGetPackageInstaller.cs b/services/DevHome.Services.WindowsPackageManager/Services/WinGetPackageInstaller.cs
new file mode 100644
index 0000000000..f3af13e98b
--- /dev/null
+++ b/services/DevHome.Services.WindowsPackageManager/Services/WinGetPackageInstaller.cs
@@ -0,0 +1,178 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using DevHome.Services.Core.Extensions;
+using DevHome.Services.WindowsPackageManager.COM;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Exceptions;
+using DevHome.Services.WindowsPackageManager.Models;
+using DevHome.Services.WindowsPackageManager.TelemetryEvents;
+using DevHome.Telemetry;
+using Microsoft.Extensions.Logging;
+using Microsoft.Management.Deployment;
+using Windows.Win32.Foundation;
+
+namespace DevHome.Services.WindowsPackageManager.Services;
+
+///
+/// Installs a package using the Windows Package Manager (WinGet).
+///
+internal sealed class WinGetPackageInstaller : IWinGetPackageInstaller, IDisposable
+{
+ private readonly ILogger _logger;
+ private readonly WindowsPackageManagerFactory _wingetFactory;
+ private readonly IWinGetPackageFinder _packageFinder;
+ private readonly SemaphoreSlim _findLock = new(1, 1);
+ private bool _disposedValue;
+
+ public WinGetPackageInstaller(
+ ILogger logger,
+ WindowsPackageManagerFactory wingetFactory,
+ IWinGetPackageFinder packageFinder)
+ {
+ _logger = logger;
+ _wingetFactory = wingetFactory;
+ _packageFinder = packageFinder;
+ }
+
+ ///
+ public async Task InstallPackageAsync(WinGetCatalog catalog, string packageId, string version, Guid activityId)
+ {
+ if (catalog == null)
+ {
+ throw new CatalogNotInitializedException();
+ }
+
+ // Report telemetry for attempting app install
+ TelemetryFactory.Get().Log("AppInstall_AppSelected", Telemetry.LogLevel.Critical, new AppInstallUserEvent(packageId, catalog.Catalog.Info.Id), activityId);
+
+ try
+ {
+ // 1. Find package
+ var package = await FindPackageOrThrowAsync(catalog, packageId);
+
+ // 2. Install package
+ _logger.LogInformation($"Starting package installation for {packageId} from catalog {catalog.GetDescriptiveName()}");
+ var installResult = await InstallPackageInternalAsync(package, version, activityId);
+ var extendedErrorCode = installResult.ExtendedErrorCode?.HResult ?? HRESULT.S_OK;
+ var installErrorCode = installResult.GetValueOrDefault(res => res.InstallerErrorCode, HRESULT.S_OK); // WPM API V4
+
+ // 3. Report install result
+ _logger.LogInformation($"Install result: Status={installResult.Status}, InstallerErrorCode={installErrorCode}, ExtendedErrorCode={extendedErrorCode}, RebootRequired={installResult.RebootRequired}");
+ if (installResult.Status != InstallResultStatus.Ok)
+ {
+ throw new InstallPackageException(installResult.Status, extendedErrorCode, installErrorCode);
+ }
+
+ _logger.LogInformation($"Completed package installation for {packageId} from catalog {catalog.GetDescriptiveName()}");
+ TelemetryFactory.Get().Log("AppInstall_InstallSucceeded", Telemetry.LogLevel.Critical, new AppInstallResultEvent(package.Id, catalog.Catalog.Info.Id), activityId);
+ return new WinGetInstallPackageResult()
+ {
+ ExtendedErrorCode = extendedErrorCode,
+ RebootRequired = installResult.RebootRequired,
+ };
+ }
+ catch
+ {
+ // Report telemetry for failed install and rethrow
+ TelemetryFactory.Get().LogError("AppInstall_InstallFailed", Telemetry.LogLevel.Critical, new AppInstallResultEvent(packageId, catalog.Catalog.Info.Id), activityId);
+ throw;
+ }
+ }
+
+ ///
+ /// Install a package from a catalog.
+ ///
+ /// Package to install
+ /// Install result
+ private async Task InstallPackageInternalAsync(CatalogPackage package, string version, Guid activityId)
+ {
+ var installOptions = _wingetFactory.CreateInstallOptions();
+ installOptions.PackageInstallMode = PackageInstallMode.Silent;
+ if (!string.IsNullOrWhiteSpace(version))
+ {
+ installOptions.PackageVersionId = FindVersionOrThrow(package, version);
+ if (installOptions.PackageVersionId.Version != package.DefaultInstallVersion.Version)
+ {
+ TelemetryFactory.Get().Log("AppInstall_VersionSpecified", Telemetry.LogLevel.Critical, new AppInstallUserEvent(package.Id, package.DefaultInstallVersion.PackageCatalog.Info.Id), activityId);
+ }
+ }
+ else
+ {
+ _logger.LogInformation($"Install version not specified. Falling back to default install version {package.DefaultInstallVersion.Version}");
+ }
+
+ var packageManager = _wingetFactory.CreatePackageManager();
+ return await packageManager.InstallPackageAsync(package, installOptions).AsTask();
+ }
+
+ ///
+ /// Find a specific version in the list of available versions for a package.
+ ///
+ /// Target package
+ /// Version to find
+ /// Specified version
+ /// Exception thrown if the specified version was not found
+ private PackageVersionId FindVersionOrThrow(CatalogPackage package, string version)
+ {
+ // Find the version in the list of available versions
+ for (var i = 0; i < package.AvailableVersions.Count; i++)
+ {
+ if (package.AvailableVersions[i].Version == version)
+ {
+ return package.AvailableVersions[i];
+ }
+ }
+
+ _logger.LogError($"Specified install version was not found {version}.");
+ throw new InstallPackageException(InstallResultStatus.InvalidOptions, InstallPackageException.InstallErrorInvalidParameter, HRESULT.S_OK);
+ }
+
+ private async Task FindPackageOrThrowAsync(WinGetCatalog catalog, string packageId)
+ {
+ // During installation, ensure that only one package is being found at
+ // a time. This is mainly to prevent the elevated process from
+ // encountering a racing condition the first time attempting to access
+ // a catalog.
+ // Related issue: https://github.com/microsoft/winget-cli/issues/4587
+ await _findLock.WaitAsync();
+ try
+ {
+ var package = await _packageFinder.GetPackageAsync(catalog, packageId);
+ if (package == null)
+ {
+ _logger.LogError($"Install aborted for package {packageId} because it was not found in the provided catalog {catalog.GetDescriptiveName()}");
+ throw new FindPackagesException(FindPackagesResultStatus.CatalogError);
+ }
+
+ return package;
+ }
+ finally
+ {
+ _findLock.Release();
+ }
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ _findLock.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/WinGet/WinGetProtocolParser.cs b/services/DevHome.Services.WindowsPackageManager/Services/WinGetProtocolParser.cs
similarity index 91%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetProtocolParser.cs
rename to services/DevHome.Services.WindowsPackageManager/Services/WinGetProtocolParser.cs
index f8502743dc..b94f18b1d5 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetProtocolParser.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Services/WinGetProtocolParser.cs
@@ -3,9 +3,10 @@
using System;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Models;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Models;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Services;
///
/// Winget protocol parser
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetRecovery.cs b/services/DevHome.Services.WindowsPackageManager/Services/WinGetRecovery.cs
similarity index 69%
rename from tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetRecovery.cs
rename to services/DevHome.Services.WindowsPackageManager/Services/WinGetRecovery.cs
index 1212b1d7ec..e4981f8bd0 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Services/WinGet/WinGetRecovery.cs
+++ b/services/DevHome.Services.WindowsPackageManager/Services/WinGetRecovery.cs
@@ -4,14 +4,15 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
-using DevHome.SetupFlow.Exceptions;
-using Serilog;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Exceptions;
+using Microsoft.Extensions.Logging;
-namespace DevHome.SetupFlow.Services.WinGet;
+namespace DevHome.Services.WindowsPackageManager.Services;
internal sealed class WinGetRecovery : IWinGetRecovery
{
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WinGetRecovery));
+ private readonly ILogger _logger;
// Recovery configuration
private const int MaxAttempts = 5;
@@ -24,8 +25,9 @@ internal sealed class WinGetRecovery : IWinGetRecovery
private readonly IWinGetCatalogConnector _catalogConnector;
- public WinGetRecovery(IWinGetCatalogConnector catalogConnector)
+ public WinGetRecovery(ILogger logger, IWinGetCatalogConnector catalogConnector)
{
+ _logger = logger;
_catalogConnector = catalogConnector;
}
@@ -45,17 +47,17 @@ public async Task DoWithRecoveryAsync(Func> actionFunc)
}
catch (CatalogNotInitializedException e)
{
- _log.Error(e, $"Catalog used by the action is not initialized");
+ _logger.LogError(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(e, $"Failed to operate on out-of-proc object with error code: 0x{e.HResult:x}");
+ _logger.LogError(e, $"Failed to operate on out-of-proc object with error code: 0x{e.HResult:x}");
await RecoveryAsync(attempt);
}
}
- _log.Error($"Unable to recover windows package manager after {MaxAttempts} attempts");
+ _logger.LogError($"Unable to recover windows package manager after {MaxAttempts} attempts");
throw new WindowsPackageManagerRecoveryException();
});
}
@@ -70,15 +72,15 @@ private async Task RecoveryAsync(int attempt)
{
// Retry with exponential backoff
var backoffMs = DelayMs * (int)Math.Pow(2, attempt);
- _log.Error($"Attempting to recover attempt number {attempt} in {backoffMs} ms");
+ _logger.LogError($"Attempting to recover attempt number {attempt} in {backoffMs} ms");
// Wait for the backoff period
await Task.Delay(TimeSpan.FromMilliseconds(backoffMs));
// Recover catalogs
- _log.Information($"Starting recovery ...");
+ _logger.LogInformation($"Starting recovery ...");
await _catalogConnector.RecoverDisconnectedCatalogsAsync();
- _log.Information($"Recovery complete");
+ _logger.LogInformation($"Recovery complete");
}
}
}
diff --git a/common/TelemetryEvents/SetupFlow/AppInstallEvent.cs b/services/DevHome.Services.WindowsPackageManager/TelemetryEvents/AppInstallEvent.cs
similarity index 82%
rename from common/TelemetryEvents/SetupFlow/AppInstallEvent.cs
rename to services/DevHome.Services.WindowsPackageManager/TelemetryEvents/AppInstallEvent.cs
index 0f4426a790..255eafad9b 100644
--- a/common/TelemetryEvents/SetupFlow/AppInstallEvent.cs
+++ b/services/DevHome.Services.WindowsPackageManager/TelemetryEvents/AppInstallEvent.cs
@@ -7,10 +7,10 @@
using Microsoft.Diagnostics.Telemetry;
using Microsoft.Diagnostics.Telemetry.Internal;
-namespace DevHome.Common.TelemetryEvents.SetupFlow;
+namespace DevHome.Services.WindowsPackageManager.TelemetryEvents;
[EventData]
-public class AppInstallEvent : EventBase
+internal sealed class AppInstallEvent : EventBase
{
public string PackageId { get; }
diff --git a/common/TelemetryEvents/SetupFlow/AppInstallResultEvent.cs b/services/DevHome.Services.WindowsPackageManager/TelemetryEvents/AppInstallResultEvent.cs
similarity index 82%
rename from common/TelemetryEvents/SetupFlow/AppInstallResultEvent.cs
rename to services/DevHome.Services.WindowsPackageManager/TelemetryEvents/AppInstallResultEvent.cs
index 4c36d25746..91b848d8b0 100644
--- a/common/TelemetryEvents/SetupFlow/AppInstallResultEvent.cs
+++ b/services/DevHome.Services.WindowsPackageManager/TelemetryEvents/AppInstallResultEvent.cs
@@ -7,10 +7,10 @@
using Microsoft.Diagnostics.Telemetry;
using Microsoft.Diagnostics.Telemetry.Internal;
-namespace DevHome.Common.TelemetryEvents.SetupFlow;
+namespace DevHome.Services.WindowsPackageManager.TelemetryEvents;
[EventData]
-public class AppInstallResultEvent : EventBase
+internal sealed class AppInstallResultEvent : EventBase
{
public string PackageId { get; }
diff --git a/common/TelemetryEvents/SetupFlow/AppInstallUserEvent.cs b/services/DevHome.Services.WindowsPackageManager/TelemetryEvents/AppInstallUserEvent.cs
similarity index 85%
rename from common/TelemetryEvents/SetupFlow/AppInstallUserEvent.cs
rename to services/DevHome.Services.WindowsPackageManager/TelemetryEvents/AppInstallUserEvent.cs
index afd773a6a3..96742c6ce9 100644
--- a/common/TelemetryEvents/SetupFlow/AppInstallUserEvent.cs
+++ b/services/DevHome.Services.WindowsPackageManager/TelemetryEvents/AppInstallUserEvent.cs
@@ -7,10 +7,10 @@
using Microsoft.Diagnostics.Telemetry;
using Microsoft.Diagnostics.Telemetry.Internal;
-namespace DevHome.Common.TelemetryEvents.SetupFlow;
+namespace DevHome.Services.WindowsPackageManager.TelemetryEvents;
[EventData]
-public class AppInstallUserEvent : EventBase
+internal sealed class AppInstallUserEvent : EventBase
{
public string PackageId { get; }
diff --git a/settings/.gitattributes b/settings/.gitattributes
deleted file mode 100644
index c210a8dc46..0000000000
--- a/settings/.gitattributes
+++ /dev/null
@@ -1,3 +0,0 @@
-# Set default behavior to automatically normalize line endings.
-
-* text=crlf
diff --git a/settings/DevHome.Settings/DevHome.Settings.csproj b/settings/DevHome.Settings/DevHome.Settings.csproj
index 728106ee76..21f4187df5 100644
--- a/settings/DevHome.Settings/DevHome.Settings.csproj
+++ b/settings/DevHome.Settings/DevHome.Settings.csproj
@@ -6,6 +6,7 @@
win-x86;win-x64;win-arm64
enable
true
+ Debug;Release;Debug_FailFast
@@ -28,7 +29,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -65,4 +66,8 @@
$(DefineConstants);DEBUG
+
+
+ $(DefineConstants);DEBUG;DEBUG_FAILFAST
+
diff --git a/settings/DevHome.Settings/Strings/en-us/Resources.resw b/settings/DevHome.Settings/Strings/en-us/Resources.resw
index f13444365a..f505ded0d8 100644
--- a/settings/DevHome.Settings/Strings/en-us/Resources.resw
+++ b/settings/DevHome.Settings/Strings/en-us/Resources.resw
@@ -468,11 +468,11 @@
Title
- SubjectTitle topic of the feature that the user wants to reccomend creating
+ SubjectTitle topic of the feature that the user wants to recommend creating
Enter title
- The topic of the feature that the user wants to reccomend creating
+ The topic of the feature that the user wants to recommend creating
Scenario
@@ -488,7 +488,7 @@
Enter more info or links to give us more details.
- What are some additional resoures about this feature
+ What are some additional resources about this feature
Learn more about how we use your Settings_Feedback
@@ -558,7 +558,7 @@
Name of experimental feature 'Project Ironsides' on the 'Settings -> Experiments' page where you enable it.
- Project Ironsides is a utlity to provide deeper insights into your applications
+ Project Ironsides is a utility to provide deeper insights into your applications
Inline description of the Project Ironsides experimental feature on the 'Settings -> Experiments' page where you enable it.
diff --git a/settings/DevHome.Settings/ViewModels/AccountsProviderViewModel.cs b/settings/DevHome.Settings/ViewModels/AccountsProviderViewModel.cs
index 417638db56..33a454974c 100644
--- a/settings/DevHome.Settings/ViewModels/AccountsProviderViewModel.cs
+++ b/settings/DevHome.Settings/ViewModels/AccountsProviderViewModel.cs
@@ -51,7 +51,6 @@ public void RemoveAccount(string loginId)
if (providerOperationResult.Status == ProviderOperationStatus.Failure)
{
_log.Error($"{providerOperationResult.DisplayMessage} - {providerOperationResult.DiagnosticText}");
- return;
}
}
diff --git a/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs b/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs
index b088de1de3..2d736f266a 100644
--- a/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs
+++ b/settings/DevHome.Settings/Views/FeedbackPage.xaml.cs
@@ -11,6 +11,7 @@
using DevHome.Common.Extensions;
using DevHome.Common.Services;
using DevHome.Common.Views;
+using DevHome.Services.Core.Contracts;
using DevHome.Settings.ViewModels;
using Microsoft.Management.Infrastructure;
using Microsoft.UI.Xaml;
@@ -248,7 +249,7 @@ private string GetPhysicalMemory()
MEMORYSTATUSEX memStatus = default;
memStatus.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
- PInvoke.GlobalMemoryStatusEx(out memStatus);
+ PInvoke.GlobalMemoryStatusEx(ref memStatus);
var availMemKbToGb = Math.Round(memStatus.ullAvailPhys / ByteSizeGB, 2);
var totalMemKbToGb = Math.Round(memStatus.ullTotalPhys / ByteSizeGB, 2);
diff --git a/settings/DevHome.Settings/Views/LoginUIDialog.xaml.cs b/settings/DevHome.Settings/Views/LoginUIDialog.xaml.cs
index 1cbc790be6..c027d34558 100644
--- a/settings/DevHome.Settings/Views/LoginUIDialog.xaml.cs
+++ b/settings/DevHome.Settings/Views/LoginUIDialog.xaml.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using DevHome.Common.Extensions;
+using DevHome.Contracts.Services;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
@@ -12,6 +14,7 @@ public LoginUIDialog(StackPanel extensionAdaptiveCardPanel)
{
this.InitializeComponent();
LoginUIContent.Content = extensionAdaptiveCardPanel;
+ RequestedTheme = Application.Current.GetService().Theme;
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
diff --git a/settings/DevHome.Settings/Views/SettingsPage.xaml.cs b/settings/DevHome.Settings/Views/SettingsPage.xaml.cs
index c0c8444ec3..7905389353 100644
--- a/settings/DevHome.Settings/Views/SettingsPage.xaml.cs
+++ b/settings/DevHome.Settings/Views/SettingsPage.xaml.cs
@@ -3,8 +3,6 @@
using DevHome.Common.Views;
using DevHome.Settings.ViewModels;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
namespace DevHome.Settings.Views;
diff --git a/src/.gitattributes b/src/.gitattributes
deleted file mode 100644
index c210a8dc46..0000000000
--- a/src/.gitattributes
+++ /dev/null
@@ -1,3 +0,0 @@
-# Set default behavior to automatically normalize line endings.
-
-* text=crlf
diff --git a/src/App.xaml.cs b/src/App.xaml.cs
index 83c4640db9..751bc53c75 100644
--- a/src/App.xaml.cs
+++ b/src/App.xaml.cs
@@ -15,6 +15,9 @@
using DevHome.ExtensionLibrary.Extensions;
using DevHome.Helpers;
using DevHome.Services;
+using DevHome.Services.Core.Extensions;
+using DevHome.Services.DesiredStateConfiguration.Extensions;
+using DevHome.Services.WindowsPackageManager.Extensions;
using DevHome.Settings.Extensions;
using DevHome.SetupFlow.Extensions;
using DevHome.SetupFlow.Services;
@@ -79,17 +82,11 @@ private static string RemoveComments(string text)
public App()
{
InitializeComponent();
+#if DEBUG_FAILFAST
+ DebugSettings.FailFastOnErrors = true;
+#endif
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
- // Set up Logging
- Environment.SetEnvironmentVariable("DEVHOME_LOGS_ROOT", Path.Join(Common.Logging.LogFolderRoot, "DevHome"));
- var configuration = new ConfigurationBuilder()
- .AddJsonFile("appsettings.json")
- .Build();
- Log.Logger = new LoggerConfiguration()
- .ReadFrom.Configuration(configuration)
- .CreateLogger();
-
Host = Microsoft.Extensions.Hosting.Host.
CreateDefaultBuilder().
UseContentRoot(AppContext.BaseDirectory).
@@ -110,6 +107,11 @@ public App()
services.AddTransient();
services.AddTransient();
+ // Service projects
+ services.AddCore();
+ services.AddWinGet();
+ services.AddDSC();
+
// Services
services.AddSingleton();
services.AddSingleton();
@@ -125,8 +127,6 @@ public App()
services.AddSingleton();
services.AddSingleton(TelemetryFactory.Get());
services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
@@ -199,10 +199,15 @@ public void ShowMainWindow()
private async void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
- // TODO: Log and handle exceptions as appropriate.
// https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
- // https://github.com/microsoft/devhome/issues/613
+ Log.Fatal(e.Exception, $"Unhandled exception: {e.Message}");
+
+ // We are about to crash, so signal the extensions to stop.
await GetService().SignalStopExtensionsAsync();
+ Log.CloseAndFlush();
+
+ // We are very likely in a bad and unrecoverable state, so ensure Dev Home crashes w/ the exception info.
+ Environment.FailFast(e.Message, e.Exception);
}
protected async override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
@@ -225,9 +230,4 @@ private async void OnActivated(object? sender, AppActivationArguments args)
// Activate the app and ensure the appropriate handlers are called.
await _dispatcherQueue.EnqueueAsync(async () => await GetService().ActivateAsync(localArgsDataReference));
}
-
- private void Window_Closed(object sender, EventArgs e)
- {
- Log.CloseAndFlush();
- }
}
diff --git a/src/Assets/InitializationPage/AppList.scale-400.png b/src/Assets/InitializationPage/AppList.scale-400.png
new file mode 100644
index 0000000000..fcdf841bad
Binary files /dev/null and b/src/Assets/InitializationPage/AppList.scale-400.png differ
diff --git a/src/DevHome.csproj b/src/DevHome.csproj
index 56138d6919..2a7ea02123 100644
--- a/src/DevHome.csproj
+++ b/src/DevHome.csproj
@@ -22,6 +22,7 @@
true
true
$(DefineConstants);DISABLE_XAML_GENERATED_MAIN
+ Debug;Release;Debug_FailFast
-
+
-
+
all
runtime; build; native; contentfiles; analyzers
@@ -83,6 +84,7 @@
+
@@ -105,6 +107,9 @@
Always
+
+ PreserveNewest
+
Always
@@ -153,6 +158,14 @@
$(DefineConstants);STABLE_BUILD
+
+ $(DefineConstants);DEBUG
+
+
+
+ $(DefineConstants);DEBUG;DEBUG_FAILFAST
+
+
diff --git a/src/MainWindow.xaml.cs b/src/MainWindow.xaml.cs
index a2b590d2a1..e9c2db07dc 100644
--- a/src/MainWindow.xaml.cs
+++ b/src/MainWindow.xaml.cs
@@ -6,12 +6,13 @@
using DevHome.Telemetry;
using DevHome.TelemetryEvents;
using Microsoft.UI.Xaml;
+using Serilog;
namespace DevHome;
public sealed partial class MainWindow : WinUIEx.WindowEx
{
- private readonly DateTime mainWindowCreated;
+ private readonly DateTime _mainWindowCreated;
public MainWindow()
{
@@ -20,12 +21,17 @@ public MainWindow()
AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "Assets/DevHome.ico"));
Content = null;
Title = Application.Current.GetService().GetAppNameLocalized();
- mainWindowCreated = DateTime.UtcNow;
+ _mainWindowCreated = DateTime.UtcNow;
}
private void MainWindow_Closed(object sender, WindowEventArgs args)
{
Application.Current.GetService().SignalStopExtensionsAsync();
- TelemetryFactory.Get().Log("DevHome_MainWindow_Closed_Event", LogLevel.Critical, new DevHomeClosedEvent(mainWindowCreated));
+ TelemetryFactory.Get().Log("DevHome_MainWindow_Closed_Event", LogLevel.Critical, new DevHomeClosedEvent(_mainWindowCreated));
+ Log.Information("Terminating via MainWindow_Closed.");
+
+ // WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592).
+ // Workaround by turning it off before shutdown.
+ App.Current.DebugSettings.FailFastOnErrors = false;
}
}
diff --git a/src/Models/WhatsNewCard.cs b/src/Models/WhatsNewCard.cs
index a9159a04fd..0a24671b9b 100644
--- a/src/Models/WhatsNewCard.cs
+++ b/src/Models/WhatsNewCard.cs
@@ -42,7 +42,7 @@ public string? DarkThemeImageBig
get; set;
}
- public string? Button
+ public string? ButtonText
{
get; set;
}
diff --git a/src/Package.appxmanifest b/src/Package.appxmanifest
index 66de1e44c5..a23db92a59 100644
--- a/src/Package.appxmanifest
+++ b/src/Package.appxmanifest
@@ -66,6 +66,13 @@
+
+
+
+
+
+
+
@@ -107,6 +114,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Program.cs b/src/Program.cs
index ed3b9ffed3..ded9d803cf 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System.Diagnostics;
-using DevHome.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.UI.Dispatching;
using Serilog;
@@ -18,6 +17,17 @@ public static class Program
[STAThread]
public static void Main(string[] args)
{
+ // Set up Logging
+ Environment.SetEnvironmentVariable("DEVHOME_LOGS_ROOT", Path.Join(Common.Logging.LogFolderRoot, "DevHome"));
+ var configuration = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json")
+ .Build();
+ Log.Logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(configuration)
+ .CreateLogger();
+
+ Log.Information($"Launched with args: {string.Join(' ', [.. args])}");
+
// Be sure to parse these args in this instance of the exe... don't redirect this to another instance for parsing which
// may be running in a different security context.
ParseCommandLine(args);
@@ -36,6 +46,9 @@ public static void Main(string[] args)
_app = new App();
});
}
+
+ Log.Information("Terminating Dev Home");
+ Log.CloseAndFlush();
}
private static async Task DecideRedirection()
diff --git a/src/Services/AppInfoService.cs b/src/Services/AppInfoService.cs
index be6409f72e..92b72ebca6 100644
--- a/src/Services/AppInfoService.cs
+++ b/src/Services/AppInfoService.cs
@@ -1,12 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Globalization;
using System.Reflection;
using System.Security.Principal;
using DevHome.Common.Helpers;
using DevHome.Common.Services;
using DevHome.Helpers;
using Windows.ApplicationModel;
+using Windows.System.UserProfile;
namespace DevHome.Services;
@@ -38,6 +40,8 @@ public Version GetAppVersion()
}
}
+ public string UserPreferredLanguage { get; } = GlobalizationPreferences.Languages.Count > 0 ? GlobalizationPreferences.Languages[0] : CultureInfo.CurrentCulture.Name;
+
private static bool RunningAsAdmin
{
get
diff --git a/src/Services/AppInstallActivationHandler.cs b/src/Services/AppInstallActivationHandler.cs
index c74675e12a..4f2ccb3388 100644
--- a/src/Services/AppInstallActivationHandler.cs
+++ b/src/Services/AppInstallActivationHandler.cs
@@ -1,74 +1,72 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Diagnostics;
-using System.Web;
-using DevHome.Activation;
-using DevHome.Common.Extensions;
-using DevHome.Common.Services;
-using DevHome.Settings.ViewModels;
-using DevHome.SetupFlow.Models;
-using DevHome.SetupFlow.Services;
-using DevHome.SetupFlow.ViewModels;
-using Microsoft.UI.Xaml;
-using Serilog;
-using Windows.ApplicationModel.Activation;
-using Windows.Storage;
-
-namespace DevHome.Services;
-
-///
-/// Class that handles the activation of the application when an add-apps-to-cart URI protocol is used.
-///
-public class AppInstallActivationHandler : ActivationHandler
-{
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(AppInstallActivationHandler));
- private const string AppSearchUri = "add-apps-to-cart";
- private readonly INavigationService _navigationService;
- private readonly SetupFlowViewModel _setupFlowViewModel;
- private readonly IWindowsPackageManager _windowsPackageManager;
- private readonly PackageProvider _packageProvider;
- private readonly SetupFlowOrchestrator _setupFlowOrchestrator;
- private readonly Window _mainWindow;
- private readonly ISetupFlowStringResource _setupFlowStringResource;
- private static readonly char[] Separator = [','];
-
- public enum ActivationQueryType
- {
- Search,
- WingetURIs,
- }
-
- public AppInstallActivationHandler(
- INavigationService navigationService,
- SetupFlowViewModel setupFlowViewModel,
- PackageProvider packageProvider,
- IWindowsPackageManager wpm,
- SetupFlowOrchestrator setupFlowOrchestrator,
- ISetupFlowStringResource setupFlowStringResource,
- Window mainWindow)
- {
- _navigationService = navigationService;
- _setupFlowViewModel = setupFlowViewModel;
- _packageProvider = packageProvider;
- _windowsPackageManager = wpm;
- _setupFlowOrchestrator = setupFlowOrchestrator;
- _setupFlowStringResource = setupFlowStringResource;
- _mainWindow = mainWindow;
- }
-
- protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
- {
- return args.Uri != null && args.Uri.AbsolutePath.Equals(AppSearchUri, StringComparison.OrdinalIgnoreCase);
- }
-
- protected async override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Web;
+using DevHome.Activation;
+using DevHome.Common.Extensions;
+using DevHome.Common.Services;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Models;
+using DevHome.SetupFlow.Services;
+using DevHome.SetupFlow.ViewModels;
+using Microsoft.UI.Xaml;
+using Serilog;
+using Windows.ApplicationModel.Activation;
+
+namespace DevHome.Services;
+
+///
+/// Class that handles the activation of the application when an add-apps-to-cart URI protocol is used.
+///
+public class AppInstallActivationHandler : ActivationHandler
+{
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(AppInstallActivationHandler));
+ private const string AppSearchUri = "add-apps-to-cart";
+ private readonly INavigationService _navigationService;
+ private readonly SetupFlowViewModel _setupFlowViewModel;
+ private readonly IWinGet _winget;
+ private readonly PackageProvider _packageProvider;
+ private readonly SetupFlowOrchestrator _setupFlowOrchestrator;
+ private readonly Window _mainWindow;
+ private readonly ISetupFlowStringResource _setupFlowStringResource;
+ private static readonly char[] _separator = [','];
+
+ public enum ActivationQueryType
+ {
+ Search,
+ WingetURIs,
+ }
+
+ public AppInstallActivationHandler(
+ INavigationService navigationService,
+ SetupFlowViewModel setupFlowViewModel,
+ PackageProvider packageProvider,
+ IWinGet winget,
+ SetupFlowOrchestrator setupFlowOrchestrator,
+ ISetupFlowStringResource setupFlowStringResource,
+ Window mainWindow)
+ {
+ _navigationService = navigationService;
+ _setupFlowViewModel = setupFlowViewModel;
+ _packageProvider = packageProvider;
+ _winget = winget;
+ _setupFlowOrchestrator = setupFlowOrchestrator;
+ _setupFlowStringResource = setupFlowStringResource;
+ _mainWindow = mainWindow;
+ }
+
+ protected override bool CanHandleInternal(ProtocolActivatedEventArgs args)
+ {
+ return args.Uri != null && args.Uri.AbsolutePath.Equals(AppSearchUri, StringComparison.OrdinalIgnoreCase);
+ }
+
+ protected async override Task HandleInternalAsync(ProtocolActivatedEventArgs args)
{
var uri = args.Uri;
- var parameters = HttpUtility.ParseQueryString(uri.Query);
-
- if (parameters != null)
- {
+ var parameters = HttpUtility.ParseQueryString(uri.Query);
+
+ if (parameters != null)
+ {
foreach (ActivationQueryType queryType in Enum.GetValues(typeof(ActivationQueryType)))
{
var query = parameters.Get(queryType.ToString());
@@ -78,45 +76,45 @@ protected async override Task HandleInternalAsync(ProtocolActivatedEventArgs arg
await AppActivationFlowAsync(query, queryType);
return; // Exit after handling the first non-null query
}
- }
- }
- }
-
- private async Task AppActivationFlowAsync(string query, ActivationQueryType queryType)
- {
- if (_setupFlowOrchestrator.IsMachineConfigurationInProgress)
- {
- _log.Warning($"Cannot activate the {AppSearchUri} flow because the machine configuration is in progress");
- await _mainWindow.ShowErrorMessageDialogAsync(
- _setupFlowStringResource.GetLocalized(StringResourceKey.AppInstallActivationTitle),
- _setupFlowStringResource.GetLocalized(StringResourceKey.URIActivationFailedBusy),
- _setupFlowStringResource.GetLocalized(StringResourceKey.Close));
- return;
- }
-
- var identifiers = SplitAndTrimIdentifiers(query);
- if (identifiers.Length == 0)
- {
- _log.Warning("No valid identifiers provided in the query.");
- return;
- }
-
- _log.Information($"Starting {AppSearchUri} activation");
- _navigationService.NavigateTo(typeof(SetupFlowViewModel).FullName!);
- _setupFlowViewModel.StartAppManagementFlow(queryType == ActivationQueryType.Search ? identifiers[0] : null);
- await HandleAppSelectionAsync(identifiers, queryType);
+ }
+ }
+ }
+
+ private async Task AppActivationFlowAsync(string query, ActivationQueryType queryType)
+ {
+ if (_setupFlowOrchestrator.IsMachineConfigurationInProgress)
+ {
+ _log.Warning($"Cannot activate the {AppSearchUri} flow because the machine configuration is in progress");
+ await _mainWindow.ShowErrorMessageDialogAsync(
+ _setupFlowStringResource.GetLocalized(StringResourceKey.AppInstallActivationTitle),
+ _setupFlowStringResource.GetLocalized(StringResourceKey.URIActivationFailedBusy),
+ _setupFlowStringResource.GetLocalized(StringResourceKey.Close));
+ return;
+ }
+
+ var identifiers = SplitAndTrimIdentifiers(query);
+ if (identifiers.Length == 0)
+ {
+ _log.Warning("No valid identifiers provided in the query.");
+ return;
+ }
+
+ _log.Information($"Starting {AppSearchUri} activation");
+ _navigationService.NavigateTo(typeof(SetupFlowViewModel).FullName!);
+ _setupFlowViewModel.StartAppManagementFlow(queryType == ActivationQueryType.Search ? identifiers[0] : null);
+ await HandleAppSelectionAsync(identifiers, queryType);
}
private string[] SplitAndTrimIdentifiers(string query)
{
- return query.Split(Separator, StringSplitOptions.RemoveEmptyEntries)
- .Select(id => id.Trim(' ', '"'))
+ return query.Split(_separator, StringSplitOptions.RemoveEmptyEntries)
+ .Select(id => id.Trim(' ', '"', '\''))
.ToArray();
}
-
- private async Task HandleAppSelectionAsync(string[] identifiers, ActivationQueryType queryType)
- {
- try
+
+ private async Task HandleAppSelectionAsync(string[] identifiers, ActivationQueryType queryType)
+ {
+ try
{
switch (queryType)
{
@@ -128,25 +126,25 @@ private async Task HandleAppSelectionAsync(string[] identifiers, ActivationQuery
await PackageSearchAsync(identifiers);
return;
}
- }
- catch (Exception ex)
- {
- _log.Error(ex, $"Error executing the {AppSearchUri} activation flow");
- }
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, $"Error executing the {AppSearchUri} activation flow");
+ }
}
private async Task PackageSearchAsync(string[] identifiers)
{
- List uris = [];
-
- foreach (var identifier in identifiers)
- {
- uris.Add(new WinGetPackageUri(identifier));
+ List uris = [];
+
+ foreach (var identifier in identifiers)
+ {
+ uris.Add(new WinGetPackageUri(identifier));
}
try
{
- var list = await _windowsPackageManager.GetPackagesAsync(uris);
+ var list = await _winget.GetPackagesAsync(uris);
foreach (var item in list)
{
var package = _packageProvider.CreateOrGet(item);
@@ -158,20 +156,20 @@ private async Task PackageSearchAsync(string[] identifiers)
{
_log.Error(ex, $"Error occurred during package search for URIs: {uris}.");
}
- }
-
- private async Task SearchAndSelectAsync(string identifier)
- {
- var searchResults = await _windowsPackageManager.SearchAsync(identifier, 1);
- if (searchResults.Count == 0)
- {
- _log.Warning($"No results found for the identifier: {identifier}");
+ }
+
+ private async Task SearchAndSelectAsync(string identifier)
+ {
+ var searchResults = await _winget.SearchAsync(identifier, 1);
+ if (searchResults.Count == 0)
+ {
+ _log.Warning($"No results found for the identifier: {identifier}");
}
else
{
var package = _packageProvider.CreateOrGet(searchResults[0]);
package.IsSelected = true;
_log.Information($"Selected package: {package} for addition to cart.");
- }
- }
-}
+ }
+ }
+}
diff --git a/src/Services/GitWatcher.cs b/src/Services/GitWatcher.cs
index e8b74b2a83..2590af01b0 100644
--- a/src/Services/GitWatcher.cs
+++ b/src/Services/GitWatcher.cs
@@ -10,37 +10,37 @@ namespace DevHome.Services;
public class GitWatcher : IGitWatcher
{
- private static readonly Lazy LazyInstance = new(() => new());
+ private static readonly Lazy _lazyInstance = new(() => new());
- public static GitWatcher Instance => LazyInstance.Value;
+ public static GitWatcher Instance => _lazyInstance.Value;
public event EventHandler? GitRepositoryCreated;
public event EventHandler? GitRepositoryDeleted;
- private readonly Dictionary newRepoWatchers;
- private readonly Dictionary existingRepoWatchers;
+ private readonly Dictionary _newRepoWatchers;
+ private readonly Dictionary _existingRepoWatchers;
// Used to protect two dictionaries above from simultaneous modification
- private readonly object modificationLock = new();
+ private readonly object _modificationLock = new();
// Checks for one of the following formats of drive roots (must be a full string match):
// c:
// D:\
// \\?\X:
- private static readonly Regex RootIsDrive = new(@"^(([a-z]:\\?)|(\\\\\?\\[a-z]:))$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private static readonly Regex _rootIsDrive = new(@"^(([a-z]:\\?)|(\\\\\?\\[a-z]:))$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private GitWatcher()
{
// TODO: Add rehydration logic either here or in core app initialization code
// https://github.com/microsoft/devhome/issues/618
- newRepoWatchers = new();
- existingRepoWatchers = new();
+ _newRepoWatchers = new();
+ _existingRepoWatchers = new();
}
public void AddTrackedRepositories(Collection repositoryPaths)
{
- lock (modificationLock)
+ lock (_modificationLock)
{
repositoryPaths.ToList().ForEach(
(repositoryPath) =>
@@ -48,7 +48,7 @@ public void AddTrackedRepositories(Collection repositoryPaths)
// Path must be fully qualified to a root drive, i.e. not a UNC path
if (!Path.IsPathFullyQualified(repositoryPath) ||
(repositoryPath.IndexOfAny(Path.GetInvalidPathChars()) != -1) ||
- !RootIsDrive.IsMatch(Path.GetPathRoot(repositoryPath)!))
+ !_rootIsDrive.IsMatch(Path.GetPathRoot(repositoryPath)!))
{
throw new ArgumentException("Path is not fully qualified or is a UNC path.");
}
@@ -61,26 +61,26 @@ public void AddTrackedRepositories(Collection repositoryPaths)
public void RemoveTrackedRepositories(Collection repositoryPaths)
{
- lock (modificationLock)
+ lock (_modificationLock)
{
repositoryPaths.ToList().ForEach(
(repositoryPath) =>
{
var path = repositoryPath.ToLower(CultureInfo.InvariantCulture);
- existingRepoWatchers.Remove(path);
+ _existingRepoWatchers.Remove(path);
});
}
}
- public List GetTrackedRepositories() => existingRepoWatchers.Keys.ToList();
+ public List GetTrackedRepositories() => _existingRepoWatchers.Keys.ToList();
public void SetMonitoredSources(Collection? sources, bool append = false)
{
- lock (modificationLock)
+ lock (_modificationLock)
{
if (!append)
{
- newRepoWatchers.Clear();
+ _newRepoWatchers.Clear();
}
sources?.ToList().ForEach(
@@ -104,20 +104,20 @@ public void SetMonitoredSources(Collection? sources, bool append = false
watcher.EnableRaisingEvents = true;
- newRepoWatchers.Add(source, watcher);
+ _newRepoWatchers.Add(source, watcher);
});
}
}
public bool RemoveMonitoredSource(string source)
{
- lock (modificationLock)
+ lock (_modificationLock)
{
- return newRepoWatchers.Remove(source);
+ return _newRepoWatchers.Remove(source);
}
}
- public List GetMonitoredSources() => newRepoWatchers.Keys.ToList();
+ public List GetMonitoredSources() => _newRepoWatchers.Keys.ToList();
public IGitFileWatcher CreateFileWatcher(string filePattern)
{
@@ -126,7 +126,7 @@ public IGitFileWatcher CreateFileWatcher(string filePattern)
private void AddNewRepoWatcher(string path)
{
- lock (modificationLock)
+ lock (_modificationLock)
{
FileSystemWatcher deletionWatcher = new(path + @"\.git")
{
@@ -143,7 +143,7 @@ private void AddNewRepoWatcher(string path)
deletionWatcher.EnableRaisingEvents = true;
- existingRepoWatchers.Add(path, deletionWatcher);
+ _existingRepoWatchers.Add(path, deletionWatcher);
}
}
@@ -168,7 +168,7 @@ private static string GetRepoRootFromFileInGitFolder(string path)
private void RepoSentinelFileCreated(object sender, FileSystemEventArgs e)
{
- lock (modificationLock)
+ lock (_modificationLock)
{
if (!e.FullPath.Contains(@"\.git\"))
{
@@ -180,7 +180,7 @@ private void RepoSentinelFileCreated(object sender, FileSystemEventArgs e)
// TODO: Linux filesystems may recognize upper- and lowercase paths as distinct
// https://github.com/microsoft/devhome/issues/620
path = path.ToLower(CultureInfo.InvariantCulture);
- if (!existingRepoWatchers.ContainsKey(path))
+ if (!_existingRepoWatchers.ContainsKey(path))
{
AddNewRepoWatcher(path);
GitRepositoryCreated?.Invoke(this, new GitRepositoryChangedEventArgs(GitRepositoryChangeType.Created, path));
@@ -190,12 +190,12 @@ private void RepoSentinelFileCreated(object sender, FileSystemEventArgs e)
private void RepoSentinelFileDeleted(object sender, FileSystemEventArgs e)
{
- lock (modificationLock)
+ lock (_modificationLock)
{
var path = GetRepoRootFromFileInGitFolder(e.FullPath);
path = path.ToLower(CultureInfo.InvariantCulture);
- if (existingRepoWatchers.Remove(path))
+ if (_existingRepoWatchers.Remove(path))
{
GitRepositoryDeleted?.Invoke(this, new GitRepositoryChangedEventArgs(GitRepositoryChangeType.Deleted, path));
}
@@ -205,11 +205,11 @@ private void RepoSentinelFileDeleted(object sender, FileSystemEventArgs e)
public class GitFileWatcher : IGitFileWatcher
{
- private readonly Dictionary watchers;
- private readonly GitWatcher owner;
+ private readonly Dictionary _watchers;
+ private readonly GitWatcher _owner;
// Used to protect "watchers" from simultaneous modification
- private readonly object modificationLock = new();
+ private readonly object _modificationLock = new();
public bool IsOpen { get; private set; }
@@ -223,10 +223,10 @@ public class GitFileWatcher : IGitFileWatcher
public GitFileWatcher(GitWatcher owner, string filePattern)
{
- this.owner = owner;
+ this._owner = owner;
Filter = filePattern;
- watchers = new();
+ _watchers = new();
owner.GitRepositoryCreated += OnRepoCreated;
owner.GitRepositoryDeleted += OnRepoDeleted;
@@ -252,10 +252,10 @@ private void CreateWatcher(string filePattern, string repository)
watcher.Changed += WatchedFileChanged;
watcher.Deleted += WatchedFileDeleted;
- lock (modificationLock)
+ lock (_modificationLock)
{
var key = repository.ToLower(CultureInfo.InvariantCulture);
- if (watchers.TryAdd(key, watcher))
+ if (_watchers.TryAdd(key, watcher))
{
watcher.EnableRaisingEvents = true;
}
@@ -264,7 +264,7 @@ private void CreateWatcher(string filePattern, string repository)
private void WatchedFileChanged(object sender, FileSystemEventArgs e)
{
- foreach (var watcher in watchers)
+ foreach (var watcher in _watchers)
{
if (e.FullPath.StartsWith(watcher.Key, StringComparison.InvariantCultureIgnoreCase))
{
@@ -278,7 +278,7 @@ private void WatchedFileChanged(object sender, FileSystemEventArgs e)
private void WatchedFileCreated(object sender, FileSystemEventArgs e)
{
- foreach (var watcher in watchers)
+ foreach (var watcher in _watchers)
{
if (e.FullPath.StartsWith(watcher.Key, StringComparison.InvariantCultureIgnoreCase))
{
@@ -292,7 +292,7 @@ private void WatchedFileCreated(object sender, FileSystemEventArgs e)
private void WatchedFileDeleted(object sender, FileSystemEventArgs e)
{
- foreach (var watcher in watchers)
+ foreach (var watcher in _watchers)
{
if (e.FullPath.StartsWith(watcher.Key, StringComparison.InvariantCultureIgnoreCase))
{
@@ -308,10 +308,10 @@ public void Close()
{
IsOpen = false;
- owner.GitRepositoryCreated -= OnRepoCreated;
- owner.GitRepositoryDeleted -= OnRepoDeleted;
+ _owner.GitRepositoryCreated -= OnRepoCreated;
+ _owner.GitRepositoryDeleted -= OnRepoDeleted;
- watchers.Clear();
+ _watchers.Clear();
}
private void OnRepoCreated(object? sender, GitRepositoryChangedEventArgs e)
@@ -321,9 +321,9 @@ private void OnRepoCreated(object? sender, GitRepositoryChangedEventArgs e)
private void OnRepoDeleted(object? sender, GitRepositoryChangedEventArgs e)
{
- lock (modificationLock)
+ lock (_modificationLock)
{
- watchers.Remove(Filter);
+ _watchers.Remove(Filter);
}
}
@@ -331,8 +331,8 @@ private void OnRepoDeleted(object? sender, GitRepositoryChangedEventArgs e)
{
if (IsOpen)
{
- owner.GitRepositoryCreated -= OnRepoCreated;
- owner.GitRepositoryDeleted -= OnRepoDeleted;
+ _owner.GitRepositoryCreated -= OnRepoCreated;
+ _owner.GitRepositoryDeleted -= OnRepoDeleted;
}
}
}
diff --git a/src/Services/PageService.cs b/src/Services/PageService.cs
index 00c704e4b2..e320e7557a 100644
--- a/src/Services/PageService.cs
+++ b/src/Services/PageService.cs
@@ -42,11 +42,13 @@ where assembly.GetName().Name == tool.Assembly
}
}
+ // Configure nested pages from tools
+ this.ConfigureCustomizationPages();
+
// Configure footer pages
Configure();
this.ConfigureExtensionLibraryPages();
this.ConfigureSettingsPages();
- this.ConfigureCustomizationPages();
// Configure Experimental Feature pages
ExperimentalFeature.LocalSettingsService = localSettingsService;
diff --git a/src/Strings/en-us/Resources.resw b/src/Strings/en-us/Resources.resw
index a03ca81571..caac4a8a8b 100644
--- a/src/Strings/en-us/Resources.resw
+++ b/src/Strings/en-us/Resources.resw
@@ -1,17 +1,17 @@
-
@@ -192,16 +192,16 @@
Dev Home (Preview)
-
+
Pin widgets
-
+
Create Dev Drive
Learn more
-
+
Connect accounts
@@ -234,9 +234,9 @@
Administrator: Dev Home (Preview)
Name of app when run as administrator
-
+
Explore extensions
- Button on extensions card on "whats new" page
+ Button text on extensions card on "whats new" page
Extensions help you get more out of Dev Home. For example, get recommendations for other repositories to add when setting up your machine in Dev Home and add other widgets to your dashboard.
@@ -310,4 +310,12 @@
Hyper-V Extension
{Locked="Hyper-V"} Extension Display Name
+
+ Windows Subsystem for Linux Extension
+ {Locked="Windows", "Linux"} Extension Display Name
+
+
+ Windows Subsystem for Linux
+ {Locked="Windows", "Linux"} Extension Description
+
\ No newline at end of file
diff --git a/src/ViewModels/InitializationViewModel.cs b/src/ViewModels/InitializationViewModel.cs
index 3dbe7e9ed7..6279a3a012 100644
--- a/src/ViewModels/InitializationViewModel.cs
+++ b/src/ViewModels/InitializationViewModel.cs
@@ -3,10 +3,9 @@
using CommunityToolkit.Mvvm.ComponentModel;
using DevHome.Common.Extensions;
-using DevHome.Common.Services;
using DevHome.Contracts.Services;
using DevHome.Dashboard.Services;
-using DevHome.Services;
+using DevHome.Services.Core.Contracts;
using DevHome.Views;
using Microsoft.UI.Xaml;
using Serilog;
@@ -19,7 +18,7 @@ public class InitializationViewModel : ObservableObject
private readonly IThemeSelectorService _themeSelector;
private readonly IWidgetServiceService _widgetServiceService;
- private readonly IAppInstallManagerService _appInstallManagerService;
+ private readonly IMicrosoftStoreService _msStoreService;
private readonly IPackageDeploymentService _packageDeploymentService;
#if CANARY_BUILD
@@ -36,12 +35,12 @@ public class InitializationViewModel : ObservableObject
public InitializationViewModel(
IThemeSelectorService themeSelector,
IWidgetServiceService widgetServiceService,
- IAppInstallManagerService appInstallManagerService,
+ IMicrosoftStoreService msStoreService,
IPackageDeploymentService packageDeploymentService)
{
_themeSelector = themeSelector;
_widgetServiceService = widgetServiceService;
- _appInstallManagerService = appInstallManagerService;
+ _msStoreService = msStoreService;
_packageDeploymentService = packageDeploymentService;
}
@@ -78,7 +77,7 @@ public async void OnPageLoaded()
try
{
_log.Information("Installing DevHomeGitHubExtension...");
- await _appInstallManagerService.TryInstallPackageAsync(GitHubExtensionStorePackageId);
+ await _msStoreService.TryInstallPackageAsync(GitHubExtensionStorePackageId);
}
catch (Exception ex)
{
diff --git a/src/ViewModels/WhatsNewViewModel.cs b/src/ViewModels/WhatsNewViewModel.cs
index 5d1f72988e..f9ec7211e6 100644
--- a/src/ViewModels/WhatsNewViewModel.cs
+++ b/src/ViewModels/WhatsNewViewModel.cs
@@ -3,19 +3,25 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using DevHome.Common.Services;
using DevHome.Models;
namespace DevHome.ViewModels;
-public class WhatsNewViewModel : ObservableObject
+public partial class WhatsNewViewModel : ObservableObject
{
public ObservableCollection Source { get; } = new ObservableCollection();
public ObservableCollection BigSource { get; } = new ObservableCollection();
- public int NumberOfBigCards
+ public int NumberOfBigCards { get; set; }
+
+ private readonly INavigationService _navigationService;
+
+ public WhatsNewViewModel(INavigationService navigationService)
{
- get; set;
+ _navigationService = navigationService;
}
public void AddCard(WhatsNewCard card)
@@ -105,13 +111,9 @@ public void SwitchToLargerView()
}
}
- public void OnNavigatedTo(object parameter)
- {
- Source.Clear();
- BigSource.Clear();
- }
-
- public void OnNavigatedFrom()
+ [RelayCommand]
+ private void HeaderButtonNavigate()
{
+ _navigationService.NavigateTo(typeof(SetupFlow.ViewModels.SetupFlowViewModel).FullName!);
}
}
diff --git a/src/Views/InitializationPage.xaml b/src/Views/InitializationPage.xaml
index 5f2319c549..704767a76d 100644
--- a/src/Views/InitializationPage.xaml
+++ b/src/Views/InitializationPage.xaml
@@ -21,7 +21,7 @@
-
+
diff --git a/src/Views/ShellPage.xaml.cs b/src/Views/ShellPage.xaml.cs
index 0cd6df6b78..b9832fb07c 100644
--- a/src/Views/ShellPage.xaml.cs
+++ b/src/Views/ShellPage.xaml.cs
@@ -168,7 +168,7 @@ private void UpdateNavigationMenuItems()
";
- NavigationViewItem navigationViewItem = (NavigationViewItem)XamlReader.Load(navigationViewItemString);
+ var navigationViewItem = (NavigationViewItem)XamlReader.Load(navigationViewItemString);
if (expFeature != null)
{
diff --git a/src/Views/WhatsNewPage.xaml b/src/Views/WhatsNewPage.xaml
index 29db27088c..ca3b4b4767 100644
--- a/src/Views/WhatsNewPage.xaml
+++ b/src/Views/WhatsNewPage.xaml
@@ -1,14 +1,13 @@
-
+ Command="{x:Bind ViewModel.HeaderButtonNavigateCommand}" />
-
-
+
-
+
-
-
+
-
-
+
+
-
+
-
-
-
-
+ Text="{x:Bind Title, Mode=OneTime}" />
+ Text="{x:Bind Description, Mode=OneTime}" />
+ Visibility="{x:Bind HasLinkAndShouldShowIt(Link, ShouldShowLink), Mode=OneTime}" />
+ Content="{x:Bind ButtonText, Mode=OneTime}" />
-
-
+
+
-
-
+
-
+
-
-
+
-
+
-
+
+ Text="{x:Bind Title, Mode=OneTime}" />
+ Text="{x:Bind Description, Mode=OneTime}" />
+ Visibility="{x:Bind HasLinkAndShouldShowIt(Link, ShouldShowLink), Mode=OneTime}" />
@@ -300,28 +294,28 @@
-
-
+
+
-
+
diff --git a/src/Views/WhatsNewPage.xaml.cs b/src/Views/WhatsNewPage.xaml.cs
index cc1cba1a80..6451818f7a 100644
--- a/src/Views/WhatsNewPage.xaml.cs
+++ b/src/Views/WhatsNewPage.xaml.cs
@@ -7,6 +7,7 @@
using DevHome.Common.Services;
using DevHome.Common.TelemetryEvents;
using DevHome.Common.TelemetryEvents.DeveloperId;
+using DevHome.Common.Views;
using DevHome.Models;
using DevHome.SetupFlow.Utilities;
using DevHome.Telemetry;
@@ -17,17 +18,13 @@
namespace DevHome.Views;
-public sealed partial class WhatsNewPage : Page
+public sealed partial class WhatsNewPage : DevHomePage
{
private readonly Uri _devDrivePageKeyUri = new("ms-settings:disksandvolumes");
private readonly Uri _devDriveLearnMoreLinkUri = new("https://go.microsoft.com/fwlink/?linkid=2236041");
- private const string _devDriveLinkResourceKey = "WhatsNewPage_DevDriveCard/Link";
- private const string _accountsPageNavigationLink = "DevHome.Settings.ViewModels.AccountsViewModel";
+ private const string DevDriveLinkResourceKey = "WhatsNewPage_DevDriveCard/Link";
- public WhatsNewViewModel ViewModel
- {
- get;
- }
+ public WhatsNewViewModel ViewModel { get; }
public WhatsNewPage()
{
@@ -56,7 +53,7 @@ private async void OnLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
if (!DevDriveUtil.IsDevDriveFeatureEnabled)
{
- card.Button = Application.Current.GetService().GetLocalized(_devDriveLinkResourceKey);
+ card.ButtonText = Application.Current.GetService().GetLocalized(DevDriveLinkResourceKey);
card.ShouldShowLink = false;
}
}
@@ -88,12 +85,6 @@ private async void OnLoaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
MoveBigCardsIfNeeded(this.ActualWidth);
}
- private void MachineConfigButton_Click(object sender, RoutedEventArgs e)
- {
- var navigationService = Application.Current.GetService();
- navigationService.NavigateTo(typeof(DevHome.SetupFlow.ViewModels.SetupFlowViewModel).FullName!);
- }
-
private async void Button_ClickAsync(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
@@ -116,12 +107,12 @@ private async void Button_ClickAsync(object sender, RoutedEventArgs e)
}
else
{
- if (pageKey.Equals(_accountsPageNavigationLink, StringComparison.OrdinalIgnoreCase))
+ if (pageKey.Equals(typeof(Settings.ViewModels.AccountsViewModel).FullName, StringComparison.OrdinalIgnoreCase))
{
TelemetryFactory.Get().Log(
- "EntryPoint_DevId_Event",
- LogLevel.Critical,
- new EntryPointEvent(EntryPointEvent.EntryPoint.WhatsNewPage));
+ "EntryPoint_DevId_Event",
+ LogLevel.Critical,
+ new EntryPointEvent(EntryPointEvent.EntryPoint.WhatsNewPage));
}
var navigationService = Application.Current.GetService();
@@ -131,7 +122,7 @@ private async void Button_ClickAsync(object sender, RoutedEventArgs e)
public void OnSizeChanged(object sender, SizeChangedEventArgs args)
{
- if ((Page)sender == this)
+ if (sender as Page == this)
{
MoveBigCardsIfNeeded(args.NewSize.Width);
}
@@ -148,12 +139,4 @@ private void MoveBigCardsIfNeeded(double newWidth)
ViewModel.SwitchToLargerView();
}
}
-
- public static class MyHelpers
- {
- public static Type GetType(object ele)
- {
- return ele.GetType();
- }
- }
}
diff --git a/src/appsettings.json b/src/appsettings.json
index 5cd6d71323..fdf37c44c6 100644
--- a/src/appsettings.json
+++ b/src/appsettings.json
@@ -34,7 +34,7 @@
],
"Enrich": [ "FromLogContext" ],
"Properties": {
- "SourceContext": "CoreWidgetExtension"
+ "SourceContext": "DevHome"
}
}
}
diff --git a/telemetry/DevHome.Telemetry.Native/DevHome.Telemetry.Native.vcxproj b/telemetry/DevHome.Telemetry.Native/DevHome.Telemetry.Native.vcxproj
index d53e9ab336..3c1f4df143 100644
--- a/telemetry/DevHome.Telemetry.Native/DevHome.Telemetry.Native.vcxproj
+++ b/telemetry/DevHome.Telemetry.Native/DevHome.Telemetry.Native.vcxproj
@@ -6,6 +6,14 @@
+
+ Debug_FailFast
+ Win32
+
+
+ Debug_FailFast
+ x64
+
Debug
Win32
@@ -45,6 +53,12 @@
v143
Unicode
+
+ StaticLibrary
+ true
+ v143
+ Unicode
+
StaticLibrary
false
@@ -58,6 +72,12 @@
v143
Unicode
+
+ StaticLibrary
+ true
+ v143
+ Unicode
+
StaticLibrary
false
@@ -73,12 +93,18 @@
+
+
+
+
+
+
@@ -95,6 +121,18 @@
true
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
Level3
@@ -123,6 +161,18 @@
true
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
Level3
diff --git a/telemetry/DevHome.Telemetry.Native/inc/DevHomeTelemetryProvider.h b/telemetry/DevHome.Telemetry.Native/inc/DevHomeTelemetryProvider.h
index 541d6cd107..26ef921fbe 100644
--- a/telemetry/DevHome.Telemetry.Native/inc/DevHomeTelemetryProvider.h
+++ b/telemetry/DevHome.Telemetry.Native/inc/DevHomeTelemetryProvider.h
@@ -10,6 +10,32 @@
#define __WIL_TRACELOGGING_CONFIG_H
#include
+#define DEFINE_CRITICAL_DATA_EVENT_PARAM10( \
+ EventId, VarType1, varName1, VarType2, varName2, VarType3, varName3, VarType4, varName4, VarType5, varName5, VarType6, varName6, VarType7, varName7, VarType8, varName8, VarType9, varName9, VarType10, varName10) \
+ DEFINE_TRACELOGGING_EVENT_PARAM10( \
+ EventId, \
+ VarType1, \
+ varName1, \
+ VarType2, \
+ varName2, \
+ VarType3, \
+ varName3, \
+ VarType4, \
+ varName4, \
+ VarType5, \
+ varName5, \
+ VarType6, \
+ varName6, \
+ VarType7, \
+ varName7, \
+ VarType8, \
+ varName8, \
+ VarType9, \
+ varName9, \
+ VarType10, \
+ varName10, \
+ TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA))
+
// [uuid(2e74ff65-bbda-5e80-4c0a-bd8320d4223b)]
class DevHomeTelemetryProvider : public wil::TraceLoggingProvider
{
@@ -55,15 +81,15 @@ class DevHomeTelemetryProvider : public wil::TraceLoggingProvider
);
}
- DEFINE_TRACELOGGING_EVENT_PARAM4(
- ComputerInfo,
+ DEFINE_CRITICAL_DATA_EVENT_PARAM4(
+ QuietBackgroundProcesses_ComputerInfo,
DWORD, processorCount,
PCWSTR, processor,
PCWSTR, motherboard,
DWORD, ram);
- DEFINE_TRACELOGGING_EVENT_PARAM10(
- SessionCategoryMetrics,
+ DEFINE_CRITICAL_DATA_EVENT_PARAM10(
+ QuietBackgroundProcesses_SessionCategoryMetrics,
int, numProcesses_unknown,
int, numProcesses_user,
int, numProcesses_system,
@@ -75,8 +101,8 @@ class DevHomeTelemetryProvider : public wil::TraceLoggingProvider
int, totalCpuTimesByCategory_developer,
int, totalCpuTimesByCategory_background);
- DEFINE_TRACELOGGING_EVENT_PARAM10(
- ProcessInfo,
+ DEFINE_CRITICAL_DATA_EVENT_PARAM10(
+ QuietBackgroundProcesses_ProcessInfo,
int, reason,
bool, isInSystem32,
PCWSTR, processName,
diff --git a/telemetry/DevHome.Telemetry/DevHome.Telemetry.csproj b/telemetry/DevHome.Telemetry/DevHome.Telemetry.csproj
index bd827b8dcd..79f8844c9b 100644
--- a/telemetry/DevHome.Telemetry/DevHome.Telemetry.csproj
+++ b/telemetry/DevHome.Telemetry/DevHome.Telemetry.csproj
@@ -7,6 +7,7 @@
true
TELEMETRYEVENTSOURCE_PUBLIC
+ Debug;Release;Debug_FailFast
diff --git a/telemetry/DevHome.Telemetry/EmptyEvent.cs b/telemetry/DevHome.Telemetry/EmptyEvent.cs
index a5f78cb5f7..373c42d800 100644
--- a/telemetry/DevHome.Telemetry/EmptyEvent.cs
+++ b/telemetry/DevHome.Telemetry/EmptyEvent.cs
@@ -3,7 +3,6 @@
using System;
using System.Diagnostics.Tracing;
-using Microsoft.Diagnostics.Telemetry;
using Microsoft.Diagnostics.Telemetry.Internal;
namespace DevHome.Telemetry;
diff --git a/test/DevHome.Test.csproj b/test/DevHome.Test.csproj
index cd32b9cfa3..6ab5a4a683 100644
--- a/test/DevHome.Test.csproj
+++ b/test/DevHome.Test.csproj
@@ -10,6 +10,7 @@
true
true
resources.pri
+ Debug;Release;Debug_FailFast
diff --git a/test/TestClass.cs b/test/TestClass.cs
index a67af0e1fb..14ddf9a67c 100644
--- a/test/TestClass.cs
+++ b/test/TestClass.cs
@@ -54,9 +54,9 @@ public void TestHelpersIngestion()
}
[TestMethod]
- public void TestExperimentHelpers()
+ public void TestExperimentHelpers_UnrecognizedKey_ReturnFalse()
{
Microsoft.Internal.Windows.DevHome.Helpers.Experimentation.Experiment experiment = new();
- Assert.IsTrue(experiment.IsEnabled("Sample_FeatureStaging"));
+ Assert.IsFalse(experiment.IsEnabled("unknown_key"));
}
}
diff --git a/tools/Customization/DevHome.Customization/DevHome.Customization.csproj b/tools/Customization/DevHome.Customization/DevHome.Customization.csproj
index 993ba640cd..2e1d8e6bea 100644
--- a/tools/Customization/DevHome.Customization/DevHome.Customization.csproj
+++ b/tools/Customization/DevHome.Customization/DevHome.Customization.csproj
@@ -6,11 +6,12 @@
win-x86;win-x64;win-arm64
enable
true
+ Debug;Release;Debug_FailFast
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -32,6 +33,8 @@
+
+
@@ -45,6 +48,10 @@
MSBuild:Compile
$(DefaultXamlRuntime)
+
+ MSBuild:Compile
+ $(DefaultXamlRuntime)
+
MSBuild:Compile
$(DefaultXamlRuntime)
@@ -53,9 +60,17 @@
MSBuild:Compile
$(DefaultXamlRuntime)
+
+ MSBuild:Compile
+ $(DefaultXamlRuntime)
+
$(DefineConstants);DEBUG
+
+
+ $(DefineConstants);DEBUG;DEBUG_FAILFAST
+
diff --git a/tools/Customization/DevHome.Customization/Extensions/PageExtensions.cs b/tools/Customization/DevHome.Customization/Extensions/PageExtensions.cs
index 0973a53f63..579f16cdf2 100644
--- a/tools/Customization/DevHome.Customization/Extensions/PageExtensions.cs
+++ b/tools/Customization/DevHome.Customization/Extensions/PageExtensions.cs
@@ -13,5 +13,7 @@ public static void ConfigureCustomizationPages(this IPageService pageService)
{
pageService.Configure();
pageService.Configure();
+ pageService.Configure();
+ pageService.Configure();
}
}
diff --git a/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs b/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs
index 1372f2b018..598cbbeaf7 100644
--- a/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs
+++ b/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs
@@ -28,6 +28,12 @@ public static IServiceCollection AddWindowsCustomization(this IServiceCollection
services.AddTransient();
+ services.AddSingleton();
+ services.AddTransient();
+
+ services.AddSingleton();
+ services.AddTransient();
+
return services;
}
}
diff --git a/tools/Customization/DevHome.Customization/Helpers/DevDriveCacheData.cs b/tools/Customization/DevHome.Customization/Helpers/DevDriveCacheData.cs
index 8b9223c3cc..13654f754f 100644
--- a/tools/Customization/DevHome.Customization/Helpers/DevDriveCacheData.cs
+++ b/tools/Customization/DevHome.Customization/Helpers/DevDriveCacheData.cs
@@ -1,11 +1,7 @@
// 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 DevHome.Customization.Helpers;
diff --git a/tools/Customization/DevHome.Customization/Models/DevDriveOptimizedData.cs b/tools/Customization/DevHome.Customization/Models/DevDriveOptimizedData.cs
index bee3296c4b..6c9a56929f 100644
--- a/tools/Customization/DevHome.Customization/Models/DevDriveOptimizedData.cs
+++ b/tools/Customization/DevHome.Customization/Models/DevDriveOptimizedData.cs
@@ -1,12 +1,6 @@
// 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 DevHome.Customization.Models;
public class DevDriveOptimizedData
diff --git a/tools/Customization/DevHome.Customization/Models/DevDriveTrustedData.cs b/tools/Customization/DevHome.Customization/Models/DevDriveTrustedData.cs
index b3b147dc60..9c24438dc1 100644
--- a/tools/Customization/DevHome.Customization/Models/DevDriveTrustedData.cs
+++ b/tools/Customization/DevHome.Customization/Models/DevDriveTrustedData.cs
@@ -1,12 +1,6 @@
// 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 DevHome.Customization.Models;
public class DevDriveTrustedData
diff --git a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw
index 7485d5d508..53354d6cc6 100644
--- a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw
+++ b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw
@@ -1,4 +1,4 @@
-
+
-
@@ -22,4 +22,4 @@
-
+
diff --git a/tools/Customization/DevHome.Customization/Views/MainPage.xaml.cs b/tools/Customization/DevHome.Customization/Views/MainPage.xaml.cs
index f598e173ec..9900d419c3 100644
--- a/tools/Customization/DevHome.Customization/Views/MainPage.xaml.cs
+++ b/tools/Customization/DevHome.Customization/Views/MainPage.xaml.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using DevHome.Common;
using DevHome.Common.Extensions;
+using DevHome.Common.Views;
using DevHome.Customization.ViewModels;
using Microsoft.UI.Xaml;
@@ -10,8 +10,6 @@ namespace DevHome.Customization.Views;
public sealed partial class MainPage : ToolPage
{
- public override string ShortName => "Windows customization";
-
public MainPageViewModel ViewModel
{
get;
diff --git a/tools/Customization/DevHome.Customization/Views/MainPageView.xaml b/tools/Customization/DevHome.Customization/Views/MainPageView.xaml
index 9e957a96f2..1b8e3a7097 100644
--- a/tools/Customization/DevHome.Customization/Views/MainPageView.xaml
+++ b/tools/Customization/DevHome.Customization/Views/MainPageView.xaml
@@ -5,12 +5,15 @@
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
+ xmlns:i="using:Microsoft.Xaml.Interactivity"
+ xmlns:ic="using:Microsoft.Xaml.Interactions.Core"
xmlns:views="using:DevHome.Customization.Views"
- xmlns:quietviews="using:DevHome.QuietBackgroundProcesses.UI.Views"
- Loaded="UserControl_Loaded">
-
-
-
+ xmlns:quietviews="using:DevHome.QuietBackgroundProcesses.UI.Views">
+
+
+
+
+
@@ -25,19 +28,39 @@
IsClickEnabled="True"
Margin="{ThemeResource SettingsCardMargin}"/>
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -54,7 +77,7 @@
AutomationProperties.AutomationId="LaunchWindowsDeveloperSettingsButton"
Command="{x:Bind ViewModel.LaunchWindowsDeveloperSettingsCommand}"
HeaderIcon="{ui:FontIcon Glyph=}"
- IsClickEnabled="True"
+ IsClickEnabled="True"
Margin="{ThemeResource SettingsCardMargin}"/>
diff --git a/tools/Customization/DevHome.Customization/Views/MainPageView.xaml.cs b/tools/Customization/DevHome.Customization/Views/MainPageView.xaml.cs
index 953dd3aaa8..515674315d 100644
--- a/tools/Customization/DevHome.Customization/Views/MainPageView.xaml.cs
+++ b/tools/Customization/DevHome.Customization/Views/MainPageView.xaml.cs
@@ -22,9 +22,4 @@ public MainPageView()
ViewModel = Application.Current.GetService();
this.DataContext = ViewModel;
}
-
- private async void UserControl_Loaded(object sender, RoutedEventArgs e)
- {
- await ViewModel.LoadViewModelContentAsync();
- }
}
diff --git a/tools/Customization/DevHome.Customization/Views/ModifyFeaturesDialog.xaml b/tools/Customization/DevHome.Customization/Views/ModifyFeaturesDialog.xaml
new file mode 100644
index 0000000000..780a05af9e
--- /dev/null
+++ b/tools/Customization/DevHome.Customization/Views/ModifyFeaturesDialog.xaml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
diff --git a/tools/Customization/DevHome.Customization/Views/ModifyFeaturesDialog.xaml.cs b/tools/Customization/DevHome.Customization/Views/ModifyFeaturesDialog.xaml.cs
new file mode 100644
index 0000000000..a66770dd27
--- /dev/null
+++ b/tools/Customization/DevHome.Customization/Views/ModifyFeaturesDialog.xaml.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using CommunityToolkit.Mvvm.Input;
+using DevHome.Customization.ViewModels;
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.Customization.Views;
+
+public sealed partial class ModifyFeaturesDialog : ContentDialog
+{
+ public ModifyFeaturesDialogViewModel ViewModel { get; }
+
+ public ModifyFeaturesDialog(IAsyncRelayCommand applyChangedCommand)
+ {
+ ViewModel = new ModifyFeaturesDialogViewModel(applyChangedCommand);
+ this.InitializeComponent();
+ this.DataContext = ViewModel;
+ }
+
+ private void OnPrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
+ {
+ ViewModel.HandlePrimaryButton();
+ }
+
+ private void OnSecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
+ {
+ ViewModel.HandleSecondaryButton();
+ }
+}
diff --git a/tools/Customization/DevHome.Customization/Views/VirtualizationFeatureManagementPage.xaml b/tools/Customization/DevHome.Customization/Views/VirtualizationFeatureManagementPage.xaml
new file mode 100644
index 0000000000..5f704ae0c2
--- /dev/null
+++ b/tools/Customization/DevHome.Customization/Views/VirtualizationFeatureManagementPage.xaml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/Customization/DevHome.Customization/Views/VirtualizationFeatureManagementPage.xaml.cs b/tools/Customization/DevHome.Customization/Views/VirtualizationFeatureManagementPage.xaml.cs
new file mode 100644
index 0000000000..0f4f8af619
--- /dev/null
+++ b/tools/Customization/DevHome.Customization/Views/VirtualizationFeatureManagementPage.xaml.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using DevHome.Common.Extensions;
+using DevHome.Customization.ViewModels;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.Customization.Views;
+
+public sealed partial class VirtualizationFeatureManagementPage : Page
+{
+ public VirtualizationFeatureManagementViewModel ViewModel
+ {
+ get;
+ }
+
+ public VirtualizationFeatureManagementPage()
+ {
+ ViewModel = Application.Current.GetService();
+ this.InitializeComponent();
+ }
+
+ private async void OnLoaded(object sender, RoutedEventArgs e)
+ {
+ await ViewModel.Initialize(NotificationQueue);
+ }
+
+ private void OnUnloaded(object sender, RoutedEventArgs e)
+ {
+ ViewModel.Uninitialize();
+ }
+}
diff --git a/tools/Dashboard/DevHome.Dashboard.UnitTest/DevHome.Dashboard.UnitTest.csproj b/tools/Dashboard/DevHome.Dashboard.UnitTest/DevHome.Dashboard.UnitTest.csproj
index 5f94624003..7387de2c60 100644
--- a/tools/Dashboard/DevHome.Dashboard.UnitTest/DevHome.Dashboard.UnitTest.csproj
+++ b/tools/Dashboard/DevHome.Dashboard.UnitTest/DevHome.Dashboard.UnitTest.csproj
@@ -10,6 +10,7 @@
true
true
resources.pri
+ Debug;Release;Debug_FailFast
diff --git a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs
index 1b71ad13de..c84274e591 100644
--- a/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs
+++ b/tools/Dashboard/DevHome.Dashboard/Controls/WidgetControl.xaml.cs
@@ -14,7 +14,6 @@
using DevHome.Dashboard.Helpers;
using DevHome.Dashboard.Services;
using DevHome.Dashboard.ViewModels;
-using DevHome.Dashboard.Views;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
@@ -39,7 +38,7 @@ public sealed partial class WidgetControl : UserControl
// https://learn.microsoft.com/en-us/windows/apps/design/widgets/widgets-design-fundamentals
// Adaptive cards render with 8px padding on each side, so we subtract that from the header height,
// as well as 1px for the border.
- private const double _headerHeightUnscaled = 39;
+ private const double HeaderHeightUnscaled = 39;
private SelectableMenuFlyoutItem _currentSelectedSize;
@@ -90,6 +89,7 @@ private void OnLoaded()
private void OnUnloaded()
{
_uiSettings.TextScaleFactorChanged -= HandleTextScaleFactorChangedAsync;
+ WidgetSource = null;
}
private async void HandleTextScaleFactorChangedAsync(UISettings sender, object args)
@@ -118,7 +118,7 @@ private static double GetPixelHeightFromWidgetSize(WidgetSize size)
private void SetScaledWidthAndHeight(double textScale)
{
- HeaderHeight = new GridLength(_headerHeightUnscaled * textScale);
+ HeaderHeight = new GridLength(HeaderHeightUnscaled * textScale);
WidgetHeight = GetPixelHeightFromWidgetSize(WidgetSource.WidgetSize) * textScale;
WidgetWidth = WidgetHelpers.WidgetPxWidth * textScale;
}
@@ -177,7 +177,10 @@ private async void OnRemoveWidgetClick(object sender, RoutedEventArgs e)
_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);
+ Application.Current.GetService().TryEnqueue(() =>
+ {
+ Application.Current.GetService().PinnedWidgets.Remove(widgetViewModel);
+ });
try
{
await widgetToDelete.DeleteAsync();
diff --git a/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj b/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj
index b747e1bcc4..bbd8dee365 100644
--- a/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj
+++ b/tools/Dashboard/DevHome.Dashboard/DevHome.Dashboard.csproj
@@ -6,6 +6,7 @@
win-x86;win-x64;win-arm64
true
Microsoft.Windows.Widgets.Hosts
+ Debug;Release;Debug_FailFast
@@ -67,6 +68,10 @@
$(DefineConstants);DEBUG
+
+ $(DefineConstants);DEBUG;DEBUG_FAILFAST
+
+
$(DefineConstants);CANARY_BUILD
$(DefineConstants);STABLE_BUILD
diff --git a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs b/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs
index f63149e90b..8fe8646984 100644
--- a/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs
+++ b/tools/Dashboard/DevHome.Dashboard/Helpers/WidgetHelpers.cs
@@ -93,9 +93,9 @@ public static async Task IsIncludedWidgetProviderAsync(WidgetProviderDefin
var endOfPfnIndex = providerId.IndexOf('!', StringComparison.Ordinal);
var familyNamePartOfProviderId = providerId[..endOfPfnIndex];
- // Get the list of packages that contain Dev Home widgets.
+ // Get the list of packages that contain Dev Home widgets and are enabled.
var extensionService = Application.Current.GetService();
- var enabledWidgetProviderIds = await extensionService.GetInstalledDevHomeWidgetPackageFamilyNamesAsync(true);
+ var enabledWidgetProviderIds = await extensionService.GetInstalledDevHomeWidgetPackageFamilyNamesAsync(includeDisabledExtensions: false);
// Check if the specified widget provider is in the list.
var include = enabledWidgetProviderIds.ToList().Contains(familyNamePartOfProviderId);
diff --git a/tools/Dashboard/DevHome.Dashboard/Services/WidgetServiceService.cs b/tools/Dashboard/DevHome.Dashboard/Services/WidgetServiceService.cs
index 893b181f7d..5d0ea06e47 100644
--- a/tools/Dashboard/DevHome.Dashboard/Services/WidgetServiceService.cs
+++ b/tools/Dashboard/DevHome.Dashboard/Services/WidgetServiceService.cs
@@ -5,9 +5,8 @@
using System.Linq;
using System.Threading.Tasks;
using DevHome.Common.Helpers;
-using DevHome.Common.Services;
using DevHome.Dashboard.Helpers;
-using DevHome.Services;
+using DevHome.Services.Core.Contracts;
using Serilog;
namespace DevHome.Dashboard.Services;
@@ -17,7 +16,7 @@ public class WidgetServiceService : IWidgetServiceService
private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WidgetServiceService));
private readonly IPackageDeploymentService _packageDeploymentService;
- private readonly IAppInstallManagerService _appInstallManagerService;
+ private readonly IMicrosoftStoreService _msStoreService;
private WidgetServiceStates _widgetServiceState = WidgetServiceStates.Unknown;
@@ -32,10 +31,10 @@ public enum WidgetServiceStates
Unknown,
}
- public WidgetServiceService(IPackageDeploymentService packageDeploymentService, IAppInstallManagerService appInstallManagerService)
+ public WidgetServiceService(IPackageDeploymentService packageDeploymentService, IMicrosoftStoreService msStoreService)
{
_packageDeploymentService = packageDeploymentService;
- _appInstallManagerService = appInstallManagerService;
+ _msStoreService = msStoreService;
}
public bool CheckForWidgetServiceAsync()
@@ -77,7 +76,7 @@ public bool CheckForWidgetServiceAsync()
public async Task TryInstallingWidgetService()
{
_log.Information("Try installing widget service...");
- var installedSuccessfully = await _appInstallManagerService.TryInstallPackageAsync(WidgetHelpers.WidgetServiceStorePackageId);
+ var installedSuccessfully = await _msStoreService.TryInstallPackageAsync(WidgetHelpers.WidgetServiceStorePackageId);
_widgetServiceState = installedSuccessfully ? WidgetServiceStates.HasStoreWidgetServiceGoodVersion : WidgetServiceStates.HasStoreWidgetServiceNoOrBadVersion;
_log.Information($"InstalledSuccessfully == {installedSuccessfully}, {_widgetServiceState}");
return installedSuccessfully;
diff --git a/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw b/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw
index b5f3f923cc..304bd754b8 100644
--- a/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw
+++ b/tools/Dashboard/DevHome.Dashboard/Strings/en-us/Resources.resw
@@ -189,6 +189,10 @@
Add new widget
The hyperlink to bring the user to the Add Widget dialog. Shows if the user hasn't added any widgets.
+
+ Dashboard cannot be displayed while running as Administrator.
+ Message shown when the user ran as admin and we can't show the dashboard.
+
We're having trouble displaying widgets. Restarting Dev Home may help.
Message shown when there's no widget service and the user should restart Dev Home.
@@ -207,15 +211,15 @@
This widget cannot be created
- Message shown when the user tries to create a widet but the widget provider is not installed.
+ Message shown when the user tries to create a widget but the widget provider is not installed.
Please ensure the app that provides this widget is installed.
- Second part of message shown when the user tries to create a widet but the widget provider is not installed.
+ Second part of message shown when the user tries to create a widget but the widget provider is not installed.
There are no widgets available to add
- Message shown when the user tries to create a widet but there are no installed widgets.
+ Message shown when the user tries to create a widget but there are no installed widgets.
Please ensure you have the latest version of the Windows Web Experience Pack installed.
@@ -239,7 +243,7 @@
Selected
- State of selected size for automatiom purposes
+ State of selected size for automation purposes
Dev Home encountered an error. The widget could not be created.
diff --git a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs
index d2bab8fa2c..155b6c8198 100644
--- a/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs
+++ b/tools/Dashboard/DevHome.Dashboard/ViewModels/DashboardViewModel.cs
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
+using DevHome.Common.Helpers;
using DevHome.Dashboard.Services;
using Microsoft.UI.Xaml;
@@ -17,6 +19,8 @@ public partial class DashboardViewModel : ObservableObject
public IWidgetScreenshotService WidgetScreenshotService { get; }
+ public ObservableCollection PinnedWidgets { get; set; }
+
[ObservableProperty]
private bool _isLoading;
@@ -33,10 +37,17 @@ public DashboardViewModel(
WidgetHostingService = widgetHostingService;
WidgetIconService = widgetIconService;
WidgetScreenshotService = widgetScreenshotService;
+
+ PinnedWidgets = new ObservableCollection();
}
public Visibility GetNoWidgetMessageVisibility(int widgetCount, bool isLoading)
{
return (widgetCount == 0 && !isLoading && HasWidgetService) ? Visibility.Visible : Visibility.Collapsed;
}
+
+ public bool IsRunningAsAdmin()
+ {
+ return RuntimeHelper.IsCurrentProcessRunningAsAdmin();
+ }
}
diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml
index 5640e34d03..4fe5c73adc 100644
--- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml
+++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml
@@ -1,17 +1,15 @@
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs
index 5bb3f65455..59e5b1bb37 100644
--- a/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs
+++ b/tools/Dashboard/DevHome.Dashboard/Views/DashboardView.xaml.cs
@@ -3,17 +3,16 @@
using System;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
-using DevHome.Common;
using DevHome.Common.Contracts;
using DevHome.Common.Extensions;
using DevHome.Common.Helpers;
using DevHome.Common.Services;
+using DevHome.Common.Views;
using DevHome.Dashboard.ComSafeWidgetObjects;
using DevHome.Dashboard.Controls;
using DevHome.Dashboard.Helpers;
@@ -35,16 +34,12 @@ public partial class DashboardView : ToolPage, IDisposable
{
private readonly ILogger _log = Log.ForContext("SourceContext", nameof(DashboardView));
- public override string ShortName => "Dashboard";
-
public DashboardViewModel ViewModel { get; }
internal DashboardBannerViewModel BannerViewModel { get; }
private readonly WidgetViewModelFactory _widgetViewModelFactory;
- public static ObservableCollection PinnedWidgets { get; set; }
-
private readonly SemaphoreSlim _pinnedWidgetsLock = new(1, 1);
private static DispatcherQueue _dispatcherQueue;
@@ -62,8 +57,7 @@ public DashboardView()
this.InitializeComponent();
- PinnedWidgets = new ObservableCollection();
- PinnedWidgets.CollectionChanged += OnPinnedWidgetsCollectionChangedAsync;
+ ViewModel.PinnedWidgets.CollectionChanged += OnPinnedWidgetsCollectionChangedAsync;
_dispatcherQueue = Application.Current.GetService();
_localSettingsService = Application.Current.GetService();
@@ -121,7 +115,7 @@ private async Task UnsubscribeFromWidgetCatalogEventsAsync()
private async void HandleRendererUpdated(object sender, object args)
{
// Re-render the widgets with the new theme and renderer.
- foreach (var widget in PinnedWidgets)
+ foreach (var widget in ViewModel.PinnedWidgets)
{
await widget.RenderAsync();
}
@@ -136,6 +130,9 @@ private async Task OnLoadedAsync()
[RelayCommand]
private async Task OnUnloadedAsync()
{
+ ViewModel.PinnedWidgets.CollectionChanged -= OnPinnedWidgetsCollectionChangedAsync;
+ Bindings.StopTracking();
+
Application.Current.GetService().RendererUpdated -= HandleRendererUpdated;
_log.Debug($"Leaving Dashboard, deactivating widgets.");
@@ -149,6 +146,7 @@ private async Task OnUnloadedAsync()
_log.Error(ex, "Exception in UnsubscribeFromWidgets:");
}
+ ViewModel.PinnedWidgets.Clear();
await UnsubscribeFromWidgetCatalogEventsAsync();
}
@@ -156,7 +154,7 @@ private void UnsubscribeFromWidgets()
{
try
{
- foreach (var widget in PinnedWidgets)
+ foreach (var widget in ViewModel.PinnedWidgets)
{
widget.UnsubscribeFromWidgetUpdates();
}
@@ -172,7 +170,12 @@ private async Task InitializeDashboard()
LoadingWidgetsProgressRing.Visibility = Visibility.Visible;
ViewModel.IsLoading = true;
- if (ViewModel.WidgetServiceService.CheckForWidgetServiceAsync())
+ if (ViewModel.IsRunningAsAdmin())
+ {
+ _log.Error($"Dev Home is running as admin, can't show Dashboard");
+ RunningAsAdminMessageStackPanel.Visibility = Visibility.Visible;
+ }
+ else if (ViewModel.WidgetServiceService.CheckForWidgetServiceAsync())
{
ViewModel.HasWidgetService = true;
if (await SubscribeToWidgetCatalogEventsAsync())
@@ -181,7 +184,7 @@ private async Task InitializeDashboard()
_log.Information($"Is first dashboard run = {isFirstDashboardRun}");
if (isFirstDashboardRun)
{
- await Application.Current.GetService().SaveSettingAsync(WellKnownSettingsKeys.IsNotFirstDashboardRun, true);
+ await _localSettingsService.SaveSettingAsync(WellKnownSettingsKeys.IsNotFirstDashboardRun, true);
}
await InitializePinnedWidgetListAsync(isFirstDashboardRun);
@@ -302,7 +305,6 @@ private async Task RestorePinnedWidgetsAsync(ComSafeWidget[] hostWidgets)
continue;
}
- // Ensure only one copy of a widget is pinned if that widget's definition only allows for one instance.
var comSafeWidgetDefinition = new ComSafeWidgetDefinition(widgetDefinitionId);
if (!await comSafeWidgetDefinition.PopulateAsync())
{
@@ -311,6 +313,14 @@ private async Task RestorePinnedWidgetsAsync(ComSafeWidget[] hostWidgets)
continue;
}
+ // If the widget's extension was disabled, hide the widget (don't add it to the list), but don't delete it.
+ if (!await WidgetHelpers.IsIncludedWidgetProviderAsync(comSafeWidgetDefinition.ProviderDefinition))
+ {
+ _log.Information($"Not adding widget from disabled extension {comSafeWidgetDefinition.ProviderDefinitionId}");
+ continue;
+ }
+
+ // Ensure only one copy of a widget is pinned if that widget's definition only allows for one instance.
if (comSafeWidgetDefinition.AllowMultiple == false)
{
if (pinnedSingleInstanceWidgets.Contains(widgetDefinitionId))
@@ -365,10 +375,12 @@ private async Task RestorePinnedWidgetsAsync(ComSafeWidget[] hostWidgets)
// For example, if the provider for the widget at position 0 was deleted, the widget at position 1
// should be updated to have position 0, etc.
var updatedPlace = 0;
- foreach (var widget in PinnedWidgets)
+ foreach (var widget in ViewModel.PinnedWidgets)
{
await WidgetHelpers.SetPositionCustomStateAsync(widget.Widget, updatedPlace++);
}
+
+ _log.Information($"Done restoring pinned widgets");
}
private async Task DeleteAbandonedWidgetAsync(ComSafeWidget widget)
@@ -436,7 +448,7 @@ private async Task PinDefaultWidgetAsync(ComSafeWidgetDefinition defaultWidgetDe
_log.Information($"Created default widget {unsafeWidgetId}");
// Set custom state on new widget.
- var position = PinnedWidgets.Count;
+ var position = ViewModel.PinnedWidgets.Count;
var newCustomState = WidgetHelpers.CreateWidgetCustomState(position);
_log.Debug($"SetCustomState: {newCustomState}");
await comSafeWidget.SetCustomStateAsync(newCustomState);
@@ -517,7 +529,7 @@ public async Task AddWidgetClickAsync()
}
// Set custom state on new widget.
- var position = PinnedWidgets.Count;
+ var position = ViewModel.PinnedWidgets.Count;
var newCustomState = WidgetHelpers.CreateWidgetCustomState(position);
_log.Debug($"SetCustomState: {newCustomState}");
await comSafeWidget.SetCustomStateAsync(newCustomState);
@@ -584,7 +596,7 @@ await Task.Run(async () =>
{
try
{
- PinnedWidgets.Insert(index, wvm);
+ ViewModel.PinnedWidgets.Insert(index, wvm);
}
catch (Exception ex)
{
@@ -648,7 +660,7 @@ private async void WidgetCatalog_WidgetDefinitionUpdated(WidgetCatalog sender, W
var matchingWidgetsFound = 0;
- foreach (var widgetToUpdate in PinnedWidgets.Where(x => x.Widget.DefinitionId == updatedDefinitionId).ToList())
+ foreach (var widgetToUpdate in ViewModel.PinnedWidgets.Where(x => x.Widget.DefinitionId == updatedDefinitionId).ToList())
{
// Things in the definition that we need to update to if they have changed:
// AllowMultiple, DisplayTitle, Capabilities (size), ThemeResource (icons)
@@ -661,7 +673,7 @@ private async void WidgetCatalog_WidgetDefinitionUpdated(WidgetCatalog sender, W
{
_log.Information($"No longer allowed to have multiple of widget {updatedDefinitionId}");
_log.Information($"Delete widget {widgetToUpdate.Widget.Id}");
- PinnedWidgets.Remove(widgetToUpdate);
+ ViewModel.PinnedWidgets.Remove(widgetToUpdate);
await widgetToUpdate.Widget.DeleteAsync();
_log.Information($"Deleted Widget {widgetToUpdate.Widget.Id}");
});
@@ -700,10 +712,10 @@ private void WidgetCatalog_WidgetDefinitionDeleted(WidgetCatalog sender, WidgetD
_dispatcherQueue.TryEnqueue(async () =>
{
_log.Information($"WidgetDefinitionDeleted {definitionId}");
- foreach (var widgetToRemove in PinnedWidgets.Where(x => x.Widget.DefinitionId == definitionId).ToList())
+ foreach (var widgetToRemove in ViewModel.PinnedWidgets.Where(x => x.Widget.DefinitionId == definitionId).ToList())
{
_log.Information($"Remove widget {widgetToRemove.Widget.Id}");
- PinnedWidgets.Remove(widgetToRemove);
+ ViewModel.PinnedWidgets.Remove(widgetToRemove);
// The widget definition is gone, so delete widgets with that definition.
await widgetToRemove.Widget.DeleteAsync();
@@ -727,10 +739,10 @@ private async void OnPinnedWidgetsCollectionChangedAsync(object sender, NotifyCo
{
var removedIndex = e.OldStartingIndex;
_log.Debug($"Removed widget at index {removedIndex}");
- for (var i = removedIndex; i < PinnedWidgets.Count; i++)
+ for (var i = removedIndex; i < ViewModel.PinnedWidgets.Count; i++)
{
_log.Debug($"Updating widget position for widget now at {i}");
- var widgetToUpdate = PinnedWidgets.ElementAt(i);
+ var widgetToUpdate = ViewModel.PinnedWidgets.ElementAt(i);
await WidgetHelpers.SetPositionCustomStateAsync(widgetToUpdate.Widget, i);
}
}
@@ -749,7 +761,7 @@ private void WidgetGridView_DragItemsStarting(object sender, DragItemsStartingEv
var draggedObject = e.Items.FirstOrDefault();
var draggedWidgetViewModel = draggedObject as WidgetViewModel;
e.Data.Properties.Add(DraggedWidget, draggedWidgetViewModel);
- e.Data.Properties.Add(DraggedIndex, PinnedWidgets.IndexOf(draggedWidgetViewModel));
+ e.Data.Properties.Add(DraggedIndex, ViewModel.PinnedWidgets.IndexOf(draggedWidgetViewModel));
}
private void WidgetControl_DragOver(object sender, DragEventArgs e)
@@ -809,9 +821,9 @@ private async void WidgetControl_Drop(object sender, DragEventArgs e)
// moved from a lower index to a higher one, removing the moved widget before inserting it will ensure that any
// widgets between the starting and ending indices move up to replace the removed widget. If the widget was
// moved from a higher index to a lower one, then the order of removal and insertion doesn't matter.
- PinnedWidgets.CollectionChanged -= OnPinnedWidgetsCollectionChangedAsync;
+ ViewModel.PinnedWidgets.CollectionChanged -= OnPinnedWidgetsCollectionChangedAsync;
- PinnedWidgets.RemoveAt(draggedIndex);
+ ViewModel.PinnedWidgets.RemoveAt(draggedIndex);
var size = await draggedWidgetViewModel.Widget.GetSizeAsync();
await InsertWidgetInPinnedWidgetsAsync(draggedWidgetViewModel.Widget, size, droppedIndex);
await WidgetHelpers.SetPositionCustomStateAsync(draggedWidgetViewModel.Widget, droppedIndex);
@@ -822,11 +834,11 @@ private async void WidgetControl_Drop(object sender, DragEventArgs e)
var endIndex = draggedIndex < droppedIndex ? droppedIndex : draggedIndex + 1;
for (var i = startIndex; i < endIndex; i++)
{
- var widgetToUpdate = PinnedWidgets.ElementAt(i);
+ var widgetToUpdate = ViewModel.PinnedWidgets.ElementAt(i);
await WidgetHelpers.SetPositionCustomStateAsync(widgetToUpdate.Widget, i);
}
- PinnedWidgets.CollectionChanged += OnPinnedWidgetsCollectionChangedAsync;
+ ViewModel.PinnedWidgets.CollectionChanged += OnPinnedWidgetsCollectionChangedAsync;
_log.Debug($"Drop ended");
}
diff --git a/tools/Environments/DevHome.Environments/DevHome.Environments.csproj b/tools/Environments/DevHome.Environments/DevHome.Environments.csproj
index 26dda148a8..3d87af116d 100644
--- a/tools/Environments/DevHome.Environments/DevHome.Environments.csproj
+++ b/tools/Environments/DevHome.Environments/DevHome.Environments.csproj
@@ -6,6 +6,7 @@
win-x86;win-x64;win-arm64
enable
true
+ Debug;Release;Debug_FailFast
diff --git a/tools/Environments/DevHome.Environments/Extensions/ServiceExtensions.cs b/tools/Environments/DevHome.Environments/Extensions/ServiceExtensions.cs
index 3e73cf9425..8ee6f420b4 100644
--- a/tools/Environments/DevHome.Environments/Extensions/ServiceExtensions.cs
+++ b/tools/Environments/DevHome.Environments/Extensions/ServiceExtensions.cs
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using DevHome.Common.Contracts;
-using DevHome.Common.Services;
using DevHome.Environments.Helpers;
using DevHome.Environments.Models;
using DevHome.Environments.ViewModels;
diff --git a/tools/Environments/DevHome.Environments/Models/ComputeSystemOperationCompletedMessage.cs b/tools/Environments/DevHome.Environments/Models/ComputeSystemOperationCompletedMessage.cs
index d90c337757..945ef38e04 100644
--- a/tools/Environments/DevHome.Environments/Models/ComputeSystemOperationCompletedMessage.cs
+++ b/tools/Environments/DevHome.Environments/Models/ComputeSystemOperationCompletedMessage.cs
@@ -1,13 +1,7 @@
// 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.Messaging.Messages;
-using DevHome.Common.Environments.Models;
namespace DevHome.Environments.Models;
diff --git a/tools/Environments/DevHome.Environments/Models/ComputeSystemOperationStartedMessage.cs b/tools/Environments/DevHome.Environments/Models/ComputeSystemOperationStartedMessage.cs
index 076ce6160c..78cc0a7684 100644
--- a/tools/Environments/DevHome.Environments/Models/ComputeSystemOperationStartedMessage.cs
+++ b/tools/Environments/DevHome.Environments/Models/ComputeSystemOperationStartedMessage.cs
@@ -1,11 +1,6 @@
// 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.Messaging.Messages;
namespace DevHome.Environments.Models;
diff --git a/tools/Environments/DevHome.Environments/Models/PinOperationData.cs b/tools/Environments/DevHome.Environments/Models/PinOperationData.cs
index 1b1c4f69ef..1f277fb5d2 100644
--- a/tools/Environments/DevHome.Environments/Models/PinOperationData.cs
+++ b/tools/Environments/DevHome.Environments/Models/PinOperationData.cs
@@ -1,11 +1,6 @@
// 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.Environments.ViewModels;
using Microsoft.Windows.DevHome.SDK;
diff --git a/tools/Environments/DevHome.Environments/Selectors/CardItemTemplateSelector.cs b/tools/Environments/DevHome.Environments/Selectors/CardItemTemplateSelector.cs
index f138e1f8a0..6637ba782f 100644
--- a/tools/Environments/DevHome.Environments/Selectors/CardItemTemplateSelector.cs
+++ b/tools/Environments/DevHome.Environments/Selectors/CardItemTemplateSelector.cs
@@ -4,7 +4,6 @@
using DevHome.Environments.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using Microsoft.Windows.DevHome.SDK;
namespace DevHome.Environments.Selectors;
diff --git a/tools/Environments/DevHome.Environments/Strings/en-us/Resources.resw b/tools/Environments/DevHome.Environments/Strings/en-us/Resources.resw
index 2a755f758a..81f8abbe83 100644
--- a/tools/Environments/DevHome.Environments/Strings/en-us/Resources.resw
+++ b/tools/Environments/DevHome.Environments/Strings/en-us/Resources.resw
@@ -179,7 +179,7 @@
Sort:
- Text labelling sort by text block
+ Text labeling sort by text block
Sync
@@ -211,7 +211,7 @@
Provider:
- Text labelling provider text block
+ Text labeling provider text block
Provider
@@ -259,7 +259,7 @@
Failed to create environment due to error: {0}
- Locked="{0}" Text to show the the user when Dev Home receives the notification that the creation of the new environment has failed. {0} is the error that is displayed to the user
+ Locked="{0}" Text to show the user when Dev Home receives the notification that the creation of the new environment has failed. {0} is the error that is displayed to the user
Status: {0}
diff --git a/tools/Environments/DevHome.Environments/TestModels/TestSystems.cs b/tools/Environments/DevHome.Environments/TestModels/TestSystems.cs
index 567b4a9417..1b52a621b9 100644
--- a/tools/Environments/DevHome.Environments/TestModels/TestSystems.cs
+++ b/tools/Environments/DevHome.Environments/TestModels/TestSystems.cs
@@ -87,7 +87,7 @@ private async Task TestStateChangesAsync()
StateChanged?.Invoke(this, ComputeSystemState.Starting);
await Task.Delay(2500);
StateChanged?.Invoke(this, ComputeSystemState.Running);
- }
+ }
private IAsyncOperation TestOperation(string options)
{
diff --git a/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs
index b813900e34..8dfe018f26 100644
--- a/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs
+++ b/tools/Environments/DevHome.Environments/ViewModels/ComputeSystemViewModel.cs
@@ -22,7 +22,6 @@
using Microsoft.UI.Xaml;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
-using static System.Runtime.InteropServices.JavaScript.JSType;
namespace DevHome.Environments.ViewModels;
@@ -54,6 +53,9 @@ public partial class ComputeSystemViewModel : ComputeSystemCardBase, IRecipient<
[ObservableProperty]
private bool _shouldShowDotOperations;
+ [ObservableProperty]
+ private bool _shouldShowSplitButton;
+
private bool _disposedValue;
///
@@ -129,6 +131,8 @@ private async Task InitializeOperationDataAsync()
try
{
ShouldShowDotOperations = false;
+ ShouldShowSplitButton = false;
+
RegisterForAllOperationMessages(DataExtractor.FillDotButtonOperations(ComputeSystem, _mainWindow), DataExtractor.FillLaunchButtonOperations(ComputeSystem));
_ = Task.Run(async () =>
@@ -158,7 +162,11 @@ private async Task InitializeOperationDataAsync()
DotOperations.Add(data);
}
- ShouldShowDotOperations = true;
+ // Only show dot operations when there are items in the list.
+ ShouldShowDotOperations = DotOperations.Count > 0;
+
+ // Only show Launch split button with operations when there are items in the list.
+ ShouldShowSplitButton = LaunchOperations.Count > 0;
});
});
diff --git a/tools/Environments/DevHome.Environments/ViewModels/CreateComputeSystemOperationViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/CreateComputeSystemOperationViewModel.cs
index 117289ff47..b64cf11db0 100644
--- a/tools/Environments/DevHome.Environments/ViewModels/CreateComputeSystemOperationViewModel.cs
+++ b/tools/Environments/DevHome.Environments/ViewModels/CreateComputeSystemOperationViewModel.cs
@@ -6,7 +6,6 @@
using DevHome.Common.Environments.Models;
using DevHome.Common.Environments.Services;
using DevHome.Common.Services;
-using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
diff --git a/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs b/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs
index 816fc6fe5e..3b6cf80e45 100644
--- a/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs
+++ b/tools/Environments/DevHome.Environments/ViewModels/LandingPageViewModel.cs
@@ -16,7 +16,6 @@
using DevHome.Common.Environments.Models;
using DevHome.Common.Environments.Services;
using DevHome.Common.Services;
-using DevHome.Common.Views;
using DevHome.Environments.Helpers;
using Microsoft.UI.Xaml;
using Serilog;
diff --git a/tools/Environments/DevHome.Environments/Views/LandingPage.xaml b/tools/Environments/DevHome.Environments/Views/LandingPage.xaml
index 825530112a..1fb634c345 100644
--- a/tools/Environments/DevHome.Environments/Views/LandingPage.xaml
+++ b/tools/Environments/DevHome.Environments/Views/LandingPage.xaml
@@ -1,8 +1,8 @@
-
-
+
-
+
@@ -35,17 +35,30 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -350,4 +363,4 @@
-
+
diff --git a/tools/Environments/DevHome.Environments/Views/LandingPage.xaml.cs b/tools/Environments/DevHome.Environments/Views/LandingPage.xaml.cs
index cc3ffb86b0..538087f93d 100644
--- a/tools/Environments/DevHome.Environments/Views/LandingPage.xaml.cs
+++ b/tools/Environments/DevHome.Environments/Views/LandingPage.xaml.cs
@@ -3,7 +3,6 @@
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
-using DevHome.Common;
using DevHome.Common.Extensions;
using DevHome.Common.Views;
using DevHome.Environments.ViewModels;
@@ -14,8 +13,6 @@ namespace DevHome.Environments.Views;
public sealed partial class LandingPage : ToolPage
{
- public override string ShortName => "Environments";
-
public LandingPageViewModel ViewModel { get; }
public LandingPage()
diff --git a/tools/Experiments/src/DevHome.Experiments.csproj b/tools/Experiments/src/DevHome.Experiments.csproj
index 282fc72016..e136086506 100644
--- a/tools/Experiments/src/DevHome.Experiments.csproj
+++ b/tools/Experiments/src/DevHome.Experiments.csproj
@@ -5,6 +5,7 @@
x86;x64;arm64
win-x86;win-x64;win-arm64
true
+ Debug;Release;Debug_FailFast
diff --git a/tools/Experiments/src/Views/TestExperimentPage.xaml b/tools/Experiments/src/Views/TestExperimentPage.xaml
index f486b1bd64..0ff0f699f3 100644
--- a/tools/Experiments/src/Views/TestExperimentPage.xaml
+++ b/tools/Experiments/src/Views/TestExperimentPage.xaml
@@ -1,15 +1,15 @@
-
-
+
diff --git a/tools/Experiments/src/Views/TestExperimentPage.xaml.cs b/tools/Experiments/src/Views/TestExperimentPage.xaml.cs
index 7957848107..2f336fa0f4 100644
--- a/tools/Experiments/src/Views/TestExperimentPage.xaml.cs
+++ b/tools/Experiments/src/Views/TestExperimentPage.xaml.cs
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using DevHome.Common;
+using DevHome.Common.Views;
using DevHome.Experiments.ViewModels;
namespace DevHome.Experiments.Views;
@@ -11,8 +11,6 @@ namespace DevHome.Experiments.Views;
///
public sealed partial class TestExperimentPage : ToolPage
{
- public override string ShortName => "TestExperiment1";
-
public TestExperimentViewModel ViewModel
{
get;
diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/DevHome.ExtensionLibrary.csproj b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/DevHome.ExtensionLibrary.csproj
index c830fb85fa..365ec7f5cc 100644
--- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/DevHome.ExtensionLibrary.csproj
+++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/DevHome.ExtensionLibrary.csproj
@@ -6,6 +6,7 @@
win-x86;win-x64;win-arm64
enable
true
+ Debug;Release;Debug_FailFast
@@ -45,4 +46,8 @@
$(DefineConstants);DEBUG
+
+
+ $(DefineConstants);DEBUG;DEBUG_FAILFAST
+
diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs
index 02e22967ad..5efa729773 100644
--- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs
+++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs
@@ -19,6 +19,7 @@
using Windows.ApplicationModel;
using Windows.Data.Json;
using Windows.Storage;
+using static DevHome.Common.Helpers.CommonConstants;
namespace DevHome.ExtensionLibrary.ViewModels;
@@ -35,7 +36,8 @@ public partial class ExtensionLibraryViewModel : ObservableObject
// their class Ids to this set.
private readonly HashSet _internalClassIdsToBeShownInExtensionsPage = new()
{
- CommonConstants.HyperVExtensionClassId,
+ HyperVExtensionClassId,
+ WSLExtensionClassId,
};
public ObservableCollection StorePackagesList { get; set; }
diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml
index e5fb69179e..347f7089b3 100644
--- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml
+++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml
@@ -1,11 +1,10 @@
-
-
+
diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml.cs
index 8340f56877..1310e400a4 100644
--- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml.cs
+++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using DevHome.Common;
using DevHome.Common.Extensions;
+using DevHome.Common.Views;
using DevHome.ExtensionLibrary.ViewModels;
using Microsoft.UI.Xaml;
@@ -10,8 +10,6 @@ namespace DevHome.ExtensionLibrary.Views;
public partial class ExtensionLibraryView : ToolPage
{
- public override string ShortName => "Extensions";
-
public ExtensionLibraryViewModel ViewModel { get; }
internal ExtensionLibraryBannerViewModel BannerViewModel { get; }
diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionSettingsPage.xaml b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionSettingsPage.xaml
index 21ad5e887d..cdffafd7a7 100644
--- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionSettingsPage.xaml
+++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionSettingsPage.xaml
@@ -1,11 +1,11 @@
-
-
+
-
+
-
+
diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionSettingsPage.xaml.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionSettingsPage.xaml.cs
index 416c81e33e..d165703230 100644
--- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionSettingsPage.xaml.cs
+++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionSettingsPage.xaml.cs
@@ -2,13 +2,13 @@
// Licensed under the MIT License.
using DevHome.Common.Extensions;
+using DevHome.Common.Views;
using DevHome.ExtensionLibrary.ViewModels;
using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
namespace DevHome.ExtensionLibrary.Views;
-public sealed partial class ExtensionSettingsPage : Page
+public sealed partial class ExtensionSettingsPage : DevHomePage
{
public ExtensionSettingsViewModel ViewModel { get; }
diff --git a/tools/PI/DevHome.PI.UnitTest/TestingScenarios/ProjectIronSides.md b/tools/PI/DevHome.PI.UnitTest/TestingScenarios/ProjectIronSides.md
new file mode 100644
index 0000000000..d3333413e3
--- /dev/null
+++ b/tools/PI/DevHome.PI.UnitTest/TestingScenarios/ProjectIronSides.md
@@ -0,0 +1,28 @@
+# Project Ironsides Tests
+If your code affects Project Ironsides, please manually verify these scenarios.
+
+## Scenarios
+Please make sure to verify all of these scenarios. These apply to both Windows 10 and Windows 11.
+
+### First Run and First Setup Experience
+1. PI: On Dev/Canary/Preview toggle Dev Home PI experimental feature on/off/on again to enable startup task
+1. PI: Reboot and invoking PI with Win-F12 should launch it immediately.
+
+### Core Feature (Feature is Enabled and Configured)
+1. PI: Attach to foreground app - Launch any app and Hit Win-F12, Validate app that is in foreground was selected in PI
+1. PI: Close Bar Window, Hit Win-F12. Validate app that is in foreground was selected in PI.
+1. PI Dock - Undock: Dock button should make PI vertical and dock to right side of the window
+1. PI Dock - Undock: Hitting Dock button again should undock PI
+1. PI Dock - Undock: Dragging PI window in vertival mode to right hand side of attached app should make PI dock to right side.
+1. PI WinLogs: Remove yourself from Performance Log Users from lsusrmgr.ms, Log out and log back in to Windows
+1. PI WinLogs: Validate that ETW logs are unchecked by default and checking them triggers a UAC prompt
+1. PI WinLogs: Accept UAC prompt, Log Out and log back in, ETW logs should be checked by default.
+1. PI WinLogs: Attach to an application and validate logs are working
+1. PI WinLogs: Crash the app/end task from taskmanager and validate logs
+1. PI WinLogs: ETW logs check box works. Example scenario - attach to notepad and close it.
+1. PI WinLogs: Debug output check-box works. Example scenario - Attach to DevHome, go to Widgets page and add widgets like CPU and GPU
+1. PI ProcessList Page: Filter text box works both for PIDs and process name
+1. PI ProcessList Page: Click on any process and confirm PI attaches to that process.
+1. PI ProcessList Page: After attaching to new process, verify that other pages have relevant info related to newly attached process.
+1. PI System Process/Admin Process: Attach PI to Task Manager or other system process, verify that AppDetails and Modules page show "Run As Admin" button
+1. PI System Process/Admin Process: Clicking on "Run As Admin" button should work.
\ No newline at end of file
diff --git a/tools/PI/DevHome.PI/Contracts/ViewModels/INavigationAware.cs b/tools/PI/DevHome.PI/Contracts/ViewModels/INavigationAware.cs
index ee1eb1c7ec..e031f4957b 100644
--- a/tools/PI/DevHome.PI/Contracts/ViewModels/INavigationAware.cs
+++ b/tools/PI/DevHome.PI/Contracts/ViewModels/INavigationAware.cs
@@ -1,12 +1,6 @@
// 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 DevHome.PI.Contracts.ViewModels;
// Similar to DevHome.Contracts.ViewModels.INavigationAware
diff --git a/tools/PI/DevHome.PI/Controls/AddToolControl.xaml b/tools/PI/DevHome.PI/Controls/AddToolControl.xaml
index 672b6c1083..4d59b92304 100644
--- a/tools/PI/DevHome.PI/Controls/AddToolControl.xaml
+++ b/tools/PI/DevHome.PI/Controls/AddToolControl.xaml
@@ -5,6 +5,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
+ xmlns:models="using:DevHome.PI.Models"
+ xmlns:local="using:DevHome.PI.Controls"
mc:Ignorable="d">
@@ -17,21 +20,64 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ x:Name="ToolBrowseButton" x:Uid="ToolBrowseButton" HorizontalAlignment="Left" MinWidth="100" Click="ToolBrowseButton_Click"/>
+ x:Name="ToolPathTextBox" Grid.Column="1" MinWidth="800" Margin="8,0,0,0" IsReadOnly="True" HorizontalAlignment="Stretch"/>
-
+
+
@@ -39,67 +85,43 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Grid.Row="5" HorizontalAlignment="Left" IsOn="True" Margin="0,-6,0,0"/>
-
+
diff --git a/tools/PI/DevHome.PI/Controls/AddToolControl.xaml.cs b/tools/PI/DevHome.PI/Controls/AddToolControl.xaml.cs
index 41aa6fccfc..28c8a1abd6 100644
--- a/tools/PI/DevHome.PI/Controls/AddToolControl.xaml.cs
+++ b/tools/PI/DevHome.PI/Controls/AddToolControl.xaml.cs
@@ -2,25 +2,77 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
-using DevHome.Common;
+using System.Threading.Tasks;
using DevHome.Common.Extensions;
using DevHome.PI.Helpers;
+using DevHome.PI.Models;
+using IWshRuntimeLibrary;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media.Imaging;
+using Serilog;
+using Windows.ApplicationModel;
+using Windows.Management.Deployment;
using Windows.Win32;
using Windows.Win32.UI.Controls.Dialogs;
namespace DevHome.PI.Controls;
-public sealed partial class AddToolControl : UserControl
+public sealed partial class AddToolControl : UserControl, INotifyPropertyChanged
{
- private readonly string invalidToolInfo = CommonHelper.GetLocalizedString("InvalidToolInfoMessage");
- private readonly string messageCloseText = CommonHelper.GetLocalizedString("MessageCloseText");
+ private static readonly ILogger _log = Log.ForContext("SourceContext", nameof(AddToolControl));
+
+ private readonly string _invalidToolInfo = CommonHelper.GetLocalizedString("InvalidToolInfoMessage");
+ private readonly string _messageCloseText = CommonHelper.GetLocalizedString("MessageCloseText");
+
+ // We have 3 sets of operations, and we arbitrarily divide the progress timing into 3 equal segments.
+ private const int ShortcutProcessingEndIndex = 33;
+ private const int PackageProcessingEndIndex = 67;
+
+ private InstalledAppInfo? _selectedApp;
+ private List? _shortcuts;
+ private List? _packages;
+ private List _allApps = [];
+ private int _itemCount;
+
+ public ObservableCollection SortedApps { get; set; } = [];
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ private bool _isLoading;
+
+ public bool IsLoading
+ {
+ get => _isLoading;
+ set
+ {
+ _isLoading = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLoading)));
+ }
+ }
+
+ private int _progressPercentage;
+
+ public int ProgressPercentage
+ {
+ get => _progressPercentage;
+ set
+ {
+ _progressPercentage = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ProgressPercentage)));
+ }
+ }
public AddToolControl()
{
InitializeComponent();
+ LoadingProgressTextRing.DataContext = this;
}
private void ToolBrowseButton_Click(object sender, RoutedEventArgs e)
@@ -47,8 +99,6 @@ private void HandleBrowseButton()
lpstrFilter = pFilter,
nFilterIndex = 1,
nMaxFile = 255,
-
- // TODO - This should be the Settings window, not the bar window
hwndOwner = barWindow?.CurrentHwnd ?? Windows.Win32.Foundation.HWND.Null,
};
@@ -59,13 +109,10 @@ private void HandleBrowseButton()
}
}
- if (fileName != string.Empty)
+ if (!string.IsNullOrEmpty(fileName))
{
ToolPathTextBox.Text = fileName;
- if (ToolNameTextBox.Text == string.Empty)
- {
- ToolNameTextBox.Text = System.IO.Path.GetFileNameWithoutExtension(fileName);
- }
+ ToolNameTextBox.Text = Path.GetFileNameWithoutExtension(fileName);
}
return;
@@ -81,7 +128,7 @@ private void OkButton_Click(object sender, RoutedEventArgs e)
ExternalToolsHelper.Instance.AddExternalTool(tool);
var toolRegisteredMessage = CommonHelper.GetLocalizedString("ToolRegisteredMessage", ToolNameTextBox.Text);
- WindowHelper.ShowTimedMessageDialog(this, toolRegisteredMessage, messageCloseText);
+ WindowHelper.ShowTimedMessageDialog(this, toolRegisteredMessage, _messageCloseText);
ClearValues();
}
@@ -89,69 +136,285 @@ private void ClearValues()
{
ToolNameTextBox.Text = string.Empty;
ToolPathTextBox.Text = string.Empty;
- NoneRadio.IsChecked = true;
- PrefixTextBox.Text = string.Empty;
- OtherArgsTextBox.Text = string.Empty;
+ LaunchRadio.IsChecked = true;
+ ArgumentsTextBox.Text = string.Empty;
IsPinnedToggleSwitch.IsOn = true;
+ _selectedApp = null;
}
private ExternalTool? GetCurrentToolDefinition()
{
if (string.IsNullOrEmpty(ToolNameTextBox.Text) || string.IsNullOrEmpty(ToolPathTextBox.Text))
{
- WindowHelper.ShowTimedMessageDialog(this, invalidToolInfo, messageCloseText);
+ WindowHelper.ShowTimedMessageDialog(this, _invalidToolInfo, _messageCloseText);
return null;
}
- var argType = ExternalToolArgType.None;
-
- if (HwndRadio.IsChecked ?? false)
+ var activationType = ToolActivationType.Launch;
+ if (ProtocolRadio.IsChecked ?? false)
{
- argType = ExternalToolArgType.Hwnd;
+ activationType = ToolActivationType.Protocol;
}
- else if (ProcessIdRadio.IsChecked ?? false)
+ else if (_selectedApp is not null && _selectedApp.IsMsix)
{
- argType = ExternalToolArgType.ProcessId;
+ activationType = ToolActivationType.Msix;
}
return new(
ToolNameTextBox.Text,
ToolPathTextBox.Text,
- argType,
- PrefixTextBox.Text ?? string.Empty,
- OtherArgsTextBox.Text ?? string.Empty,
+ activationType,
+ ArgumentsTextBox.Text,
+ _selectedApp?.AppUserModelId ?? string.Empty,
+ _selectedApp?.IconFilePath ?? string.Empty,
IsPinnedToggleSwitch.IsOn);
}
- private void UpdateSampleCommandline(object sender, TextChangedEventArgs e)
+ private void CancelButton_Click(object sender, RoutedEventArgs e)
+ {
+ ClearValues();
+ }
+
+ private async void AppListButton_Click(object sender, RoutedEventArgs e)
+ {
+ _allApps.Clear();
+ SortedApps.Clear();
+
+ _itemCount = GetShortcuts();
+ if (_itemCount == 0)
+ {
+ _log.Error("Error getting _shortcuts");
+ }
+
+ var packageCount = GetPackages();
+ if (packageCount == 0)
+ {
+ _log.Error("Error getting _packages");
+ }
+
+ _itemCount += packageCount;
+ if (_itemCount == 0)
+ {
+ _log.Error("Error getting list of installed apps");
+ return;
+ }
+
+ // We get most of the data on a background thread, which
+ // reports intermittent progress.
+ IsLoading = true;
+ var progress = new Progress(percent =>
+ {
+ // Update the progress report.
+ LoadingProgressTextRing.Value = percent;
+ });
+
+ await ProcessItemsAsync(progress);
+
+ foreach (var app in _allApps)
+ {
+ SortedApps.Add(app);
+ }
+
+ IsLoading = false;
+ }
+
+ private int GetShortcuts()
+ {
+ int count;
+
+ // Search for .lnk files in the per-user and all-users Start Menu Programs directories.
+ // %APPDATA%\Microsoft\Windows\Start Menu\Programs
+ var startMenuProgramsPath =
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Programs");
+
+ // %ProgramData%\Microsoft\Windows\Start Menu\Programs
+ var commonStartMenuProgramsPath =
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu), "Programs");
+
+ _shortcuts = [];
+ if (Directory.Exists(startMenuProgramsPath))
+ {
+ _shortcuts.AddRange(Directory.GetFiles(startMenuProgramsPath, "*.lnk", SearchOption.AllDirectories));
+ }
+
+ if (Directory.Exists(commonStartMenuProgramsPath))
+ {
+ _shortcuts.AddRange(Directory.GetFiles(commonStartMenuProgramsPath, "*.lnk", SearchOption.AllDirectories));
+ }
+
+ count = _shortcuts?.Count ?? 0;
+ return count;
+ }
+
+ private int GetPackages()
{
- UpdateSampleCommandline();
+ var count = 0;
+ var packageManager = new PackageManager();
+ _packages = packageManager.FindPackagesForUserWithPackageTypes(string.Empty, PackageTypes.Main).ToList();
+ if (_packages is not null)
+ {
+ count = _packages.Count;
+ }
+
+ return count;
}
- private void UpdateSampleCommandline(object sender, RoutedEventArgs e)
+ private async Task ProcessItemsAsync(IProgress progress)
{
- UpdateSampleCommandline();
+ // Process all the shortcut files.
+ var currentCount = 0;
+ for (var i = 0; i < _shortcuts?.Count; i++)
+ {
+ await Task.Run(() => ProcessShortcut(_shortcuts[i]));
+
+ // Report progress.
+ currentCount++;
+ var percentComplete = (i + 1) * ShortcutProcessingEndIndex / _itemCount;
+ progress.Report(percentComplete);
+ }
+
+ for (var j = 0; j < _packages?.Count; j++)
+ {
+ await Task.Run(() => ProcessPackage(_packages[j]));
+
+ // Report progress.
+ currentCount++;
+ var percentComplete = ShortcutProcessingEndIndex + ((j + 1) * ShortcutProcessingEndIndex / _itemCount);
+ progress.Report(percentComplete);
+ }
+
+ _allApps = _allApps.OrderBy(app => app.Name).ToList();
+
+ // We get the icon data on the UI thread, because BitmapImages must be created on the UI thread.
+ for (var k = 0; k < _allApps.Count; k++)
+ {
+ var app = _allApps[k];
+ if (app.IsMsix)
+ {
+ if (app.AppPackage is not null)
+ {
+ try
+ {
+ // The package might be in a bad state, and accessing its
+ // properties might throw an exception.
+ app.Icon = new BitmapImage(app.AppPackage.Logo);
+ app.IconFilePath = app.AppPackage.Logo.LocalPath;
+ }
+ catch
+ {
+ _log.Error("Error getting icon from package");
+ }
+ }
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(app.TargetPath))
+ {
+ app.Icon = WindowHelper.GetBitmapImageFromFile(app.TargetPath);
+ }
+ }
+
+ currentCount++;
+ var percentComplete = PackageProcessingEndIndex + ((k + 1) * ShortcutProcessingEndIndex / _itemCount);
+ progress.Report(percentComplete);
+
+ // Yield to make sure the UI thread can update the progress output.
+ await Task.Delay(1);
+ }
}
- private void UpdateSampleCommandline()
+ public void ProcessShortcut(string filePath)
{
- if (SampleCommandTextBox is null)
+ var appName = Path.GetFileNameWithoutExtension(filePath);
+
+ // Exclude Microsoft Virtual Desktop _shortcuts.
+ if (appName.Contains("Microsoft Virtual Desktop", StringComparison.OrdinalIgnoreCase))
{
- // The window is still initializing.
return;
}
- var tool = GetCurrentToolDefinition();
- if (tool is null)
+ var wshShell = new WshShell();
+ if (wshShell.CreateShortcut(filePath) is not IWshShortcut shortcut)
{
+ _log.Error("Error getting shortcut");
return;
}
- SampleCommandTextBox.Text = tool.CreateFullCommandLine(123, (Windows.Win32.Foundation.HWND)123);
+ // Proceed with using the shortcut object.
+ var targetPath = shortcut.TargetPath;
+
+ // Exclude _shortcuts that point to empty targets or filesystem folders.
+ if (string.IsNullOrEmpty(targetPath) || Directory.Exists(targetPath))
+ {
+ return;
+ }
+
+ // Exclude *.chm, *.url, *.html, *.ico targets.
+ var extension = Path.GetExtension(targetPath);
+ if (extension.Equals(".chm", StringComparison.OrdinalIgnoreCase)
+ || extension.Equals(".url", StringComparison.OrdinalIgnoreCase)
+ || extension.Equals(".html", StringComparison.OrdinalIgnoreCase)
+ || extension.Equals(".ico", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+
+ // Add the app for this shortcut to the list.
+ _allApps.Add(new InstalledAppInfo
+ {
+ Name = appName,
+ ShortcutFilePath = filePath,
+ TargetPath = targetPath,
+ });
}
- private void CancelButton_Click(object sender, RoutedEventArgs e)
+ public void ProcessPackage(Package package)
{
- ClearValues();
+ var op = package.GetAppListEntriesAsync();
+ var task = op.AsTask();
+ task.Wait();
+ var entries = task.Result;
+
+ // We only get the icon for apps that have an AppListEntry,
+ // because the others are not likely to be activatable from the UI.
+ if (entries.Count > 0)
+ {
+ // We only get the icon for the first AppListEntry, ignoring MAPs.
+ // Note we use Package.Logo, not AppListEntry.DisplayInfo.GetLogo
+ // because the latter doesn't get consistently-sized icons.
+ var appListEntry = entries[0];
+
+ // Add the app for this package to the list.
+ _allApps.Add(new InstalledAppInfo
+ {
+ Name = package.DisplayName,
+ AppUserModelId = appListEntry.AppUserModelId,
+ AppPackage = package,
+ });
+ }
+ }
+
+ private void AppsListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (AppsListView.SelectedItem is InstalledAppInfo app)
+ {
+ _selectedApp = app;
+ if (!string.IsNullOrEmpty(app.TargetPath))
+ {
+ ToolPathTextBox.Text = app.TargetPath;
+ }
+ else if (!string.IsNullOrEmpty(app.AppUserModelId))
+ {
+ ToolPathTextBox.Text = app.AppUserModelId;
+ }
+
+ ToolNameTextBox.Text = app.Name;
+ }
+ else
+ {
+ _selectedApp = null;
+ ToolPathTextBox.Text = string.Empty;
+ ToolNameTextBox.Text = string.Empty;
+ }
}
}
diff --git a/tools/PI/DevHome.PI/Controls/ExpandedViewControl.xaml b/tools/PI/DevHome.PI/Controls/ExpandedViewControl.xaml
index dae17ff08f..74c3774745 100644
--- a/tools/PI/DevHome.PI/Controls/ExpandedViewControl.xaml
+++ b/tools/PI/DevHome.PI/Controls/ExpandedViewControl.xaml
@@ -11,15 +11,11 @@
-
+
-
-
-
+
@@ -39,7 +35,7 @@
@@ -61,23 +57,7 @@
Visibility="{x:Bind viewModel.AppSettingsVisibility, Mode=OneWay}"
Command="{x:Bind viewModel.DetachFromProcessCommand}"/>
-
+
-
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/tools/PI/DevHome.PI/Controls/ProgressTextRing.xaml b/tools/PI/DevHome.PI/Controls/ProgressTextRing.xaml
new file mode 100644
index 0000000000..20b3df7c18
--- /dev/null
+++ b/tools/PI/DevHome.PI/Controls/ProgressTextRing.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/PI/DevHome.PI/Controls/ProgressTextRing.xaml.cs b/tools/PI/DevHome.PI/Controls/ProgressTextRing.xaml.cs
new file mode 100644
index 0000000000..f9c3f6e7de
--- /dev/null
+++ b/tools/PI/DevHome.PI/Controls/ProgressTextRing.xaml.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.ComponentModel;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.PI.Controls;
+
+public sealed partial class ProgressTextRing : UserControl, INotifyPropertyChanged
+{
+ public ProgressTextRing()
+ {
+ InitializeComponent();
+ }
+
+ public static readonly DependencyProperty IsActiveProperty =
+ DependencyProperty.Register(nameof(IsActive), typeof(bool), typeof(ProgressTextRing), new PropertyMetadata(false, OnIsActivePropertyChanged));
+
+ public bool IsActive
+ {
+ get => (bool)GetValue(IsActiveProperty);
+ set => SetValue(IsActiveProperty, value);
+ }
+
+ private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var control = (ProgressTextRing)d;
+ control.OnPropertyChanged(nameof(TextBlockVisibility));
+ }
+
+ public static readonly DependencyProperty DiameterProperty =
+ DependencyProperty.Register(nameof(Diameter), typeof(double), typeof(ProgressTextRing), new PropertyMetadata(0.0, OnDiameterPropertyChanged));
+
+ public double Diameter
+ {
+ get => (double)GetValue(DiameterProperty);
+ set => SetValue(DiameterProperty, value);
+ }
+
+ private static void OnDiameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var control = (ProgressTextRing)d;
+ control.OnPropertyChanged(nameof(TextBlockFontSize));
+ }
+
+ public static readonly DependencyProperty ValueProperty =
+ DependencyProperty.Register(nameof(Value), typeof(double), typeof(ProgressTextRing), new PropertyMetadata(0.0, OnValuePropertyChanged));
+
+ public double Value
+ {
+ get => (double)GetValue(ValueProperty);
+ set => SetValue(ValueProperty, value);
+ }
+
+ private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var control = (ProgressTextRing)d;
+ control.OnPropertyChanged(nameof(PercentageText));
+ }
+
+ private Visibility TextBlockVisibility => IsActive ? Visibility.Visible : Visibility.Collapsed;
+
+ private double TextBlockFontSize => FontSize > 0 ? FontSize : Diameter / 3.6;
+
+ public string PercentageText => $"{Value}%";
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ private void OnPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
diff --git a/tools/PI/DevHome.PI/DevHome.PI.csproj b/tools/PI/DevHome.PI/DevHome.PI.csproj
index d14a91b700..71c261c81a 100644
--- a/tools/PI/DevHome.PI/DevHome.PI.csproj
+++ b/tools/PI/DevHome.PI/DevHome.PI.csproj
@@ -16,6 +16,7 @@
true
false
$(DefineConstants);DISABLE_XAML_GENERATED_MAIN
+ Debug;Release;Debug_FailFast
@@ -28,6 +29,7 @@
+
@@ -80,7 +82,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -98,6 +100,18 @@
+
+
+ tlbimp
+ 0
+ 1
+ f935dc20-1cf0-11d0-adb9-00c04fd58a0b
+ 0
+ false
+ true
+
+
+
-
+
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+
+
+ x:Name="LargeContentPanel" Visibility="Collapsed" Grid.Row="2"
+ BorderThickness="0"
+ VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
diff --git a/tools/PI/DevHome.PI/Views/BarWindowHorizontal.xaml.cs b/tools/PI/DevHome.PI/Views/BarWindowHorizontal.xaml.cs
index 3b2b4946ab..7994d7d28d 100644
--- a/tools/PI/DevHome.PI/Views/BarWindowHorizontal.xaml.cs
+++ b/tools/PI/DevHome.PI/Views/BarWindowHorizontal.xaml.cs
@@ -2,9 +2,10 @@
// Licensed under the MIT License.
using System;
+using System.Collections.Specialized;
using System.ComponentModel;
+using System.Diagnostics;
using System.Linq;
-using System.Runtime.InteropServices;
using DevHome.Common.Extensions;
using DevHome.PI.Controls;
using DevHome.PI.Helpers;
@@ -16,6 +17,7 @@
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
using Windows.Foundation;
using Windows.UI.ViewManagement;
using Windows.UI.WindowManagement;
@@ -25,52 +27,65 @@
using Windows.Win32.UI.Shell.Common;
using WinRT.Interop;
using WinUIEx;
+using static DevHome.PI.Helpers.CommonHelper;
using static DevHome.PI.Helpers.WindowHelper;
namespace DevHome.PI;
public partial class BarWindowHorizontal : WindowEx
{
+ private const string ExpandButtonText = "\ue70d"; // ChevronDown
+ private const string CollapseButtonText = "\ue70e"; // ChevronUp
+ private const string ManageToolsButtonText = "\uec7a"; // ChevronUp
+
+ private readonly string _pinMenuItemText = CommonHelper.GetLocalizedString("PinMenuItemText");
+ private readonly string _unpinMenuItemText = CommonHelper.GetLocalizedString("UnpinMenuItemRawText");
+ private readonly string _unregisterMenuItemText = CommonHelper.GetLocalizedString("UnregisterMenuItemRawText");
+ private readonly string _manageToolsMenuItemText = CommonHelper.GetLocalizedString("ManageExternalToolsMenuText");
+
private readonly Settings _settings = Settings.Default;
private readonly BarWindowViewModel _viewModel;
private readonly UISettings _uiSettings = new();
- private bool isClosing;
+ private readonly SolidColorBrush _darkModeActiveCaptionBrush;
+ private readonly SolidColorBrush _darkModeDeactiveCaptionBrush;
+ private readonly SolidColorBrush _nonDarkModeActiveCaptionBrush;
+ private readonly SolidColorBrush _nonDarkModeDeactiveCaptionBrush;
+
+ private bool _isClosing;
+ private WindowActivationState _currentActivationState = WindowActivationState.Deactivated;
// Constants that control window sizes
- private const int _WindowPositionOffsetY = 30;
- private const int _FloatingHorizontalBarHeight = 70;
- private const int _DefaultExpandedViewTop = 30;
- private const int _DefaultExpandedViewLeft = 100;
- private const int _RightSideGap = 10;
+ private const int WindowPositionOffsetY = 30;
+ private const int FloatingHorizontalBarHeight = 90;
+ private const int FloatingHorizontalBarHeightWithExpandedCommandBar = 130;
+ private const int DefaultExpandedViewTop = 30;
+ private const int DefaultExpandedViewLeft = 100;
+ private const int RightSideGap = 10;
private RECT _monitorRect;
private RestoreState _restoreState = new()
{
- Top = _DefaultExpandedViewTop,
- Left = _DefaultExpandedViewLeft,
+ Top = DefaultExpandedViewTop,
+ Left = DefaultExpandedViewLeft,
BarOrientation = Orientation.Horizontal,
IsLargePanelVisible = true,
};
- private const int _UnsnapGap = 9;
private double _dpiScale = 1.0;
internal HWND ThisHwnd { get; private set; }
internal ClipboardMonitor? ClipboardMonitor { get; private set; }
- public Microsoft.UI.Dispatching.DispatcherQueue TheDispatcher
- {
- get; set;
- }
+ private readonly Microsoft.UI.Dispatching.DispatcherQueue _dispatcher;
public BarWindowHorizontal(BarWindowViewModel model)
{
_viewModel = model;
- TheDispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
+ _dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
InitializeComponent();
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
@@ -84,10 +99,24 @@ public BarWindowHorizontal(BarWindowViewModel model)
var settingSize = Settings.Default.ExpandedLargeSize;
_restoreState.Height = settingSize.Height;
_restoreState.Width = settingSize.Width;
+ ExpandCollapseLayoutButtonText.Text = _viewModel.ShowingExpandedContent ? CollapseButtonText : ExpandButtonText;
+
+ // Precreate the brushes for the caption buttons
+ // In Dark Mode, the active state is white, and the deactive state is translucent white
+ // In Light Mode, the active state is black, and the deactive state is translucent black
+ Windows.UI.Color color = Colors.White;
+ _darkModeActiveCaptionBrush = new SolidColorBrush(color);
+ color.A = 0x66;
+ _darkModeDeactiveCaptionBrush = new SolidColorBrush(color);
+
+ color = Colors.Black;
+ _nonDarkModeActiveCaptionBrush = new SolidColorBrush(color);
+ color.A = 0x66;
+ _nonDarkModeDeactiveCaptionBrush = new SolidColorBrush(color);
_uiSettings.ColorValuesChanged += (sender, args) =>
{
- TheDispatcher.TryEnqueue(() =>
+ _dispatcher.TryEnqueue(() =>
{
ApplySystemThemeToCaptionButtons();
});
@@ -101,10 +130,12 @@ private void ViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs
if (_viewModel.ShowingExpandedContent)
{
ExpandLargeContentPanel();
+ ExpandCollapseLayoutButtonText.Text = CollapseButtonText;
}
else
{
CollapseLargeContentPanel();
+ ExpandCollapseLayoutButtonText.Text = ExpandButtonText;
}
}
}
@@ -125,13 +156,220 @@ private void MainPanel_Loaded(object sender, RoutedEventArgs e)
SetRequestedTheme(t.Theme);
// Calculate the DPI scale.
- var monitor = PInvoke.MonitorFromWindow(ThisHwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
- PInvoke.GetScaleFactorForMonitor(monitor, out DEVICE_SCALE_FACTOR scaleFactor).ThrowOnFailure();
- _dpiScale = (double)scaleFactor / 100;
+ _dpiScale = GetDpiScaleForWindow(ThisHwnd);
SetDefaultPosition();
SetRegionsForTitleBar();
+
+ PopulateCommandBar();
+ ((INotifyCollectionChanged)ExternalToolsHelper.Instance.AllExternalTools).CollectionChanged += AllExternalTools_CollectionChanged;
+
+ // Now that the position is set correctly show the window
+ this.Show();
+ }
+
+ public void PopulateCommandBar()
+ {
+ AddManageToolsOptionToCommandBar();
+
+ foreach (ExternalTool tool in ExternalToolsHelper.Instance.AllExternalTools)
+ {
+ AddToolToCommandBar(tool);
+ }
+ }
+
+ private void AddToolToCommandBar(ExternalTool tool)
+ {
+ AppBarButton button = new AppBarButton
+ {
+ Label = tool.Name,
+ Tag = tool,
+ };
+
+ button.Icon = tool.MenuIcon;
+
+ button.Click += _viewModel.ExternalToolButton_Click;
+
+ MenuFlyout menu = new MenuFlyout();
+ menu.Items.Add(tool.IsPinned ? CreateUnPinMenuItem(tool) : CreatePinMenuItem(tool));
+ menu.Items.Add(CreateUnregisterMenuItem(tool));
+ button.ContextFlyout = menu;
+
+ // If a tool is pinned, we'll add it to the primary commands list, otherwise the secondary commands list
+ if (tool.IsPinned)
+ {
+ MyCommandBar.PrimaryCommands.Add(button);
+ }
+ else
+ {
+ MyCommandBar.SecondaryCommands.Add(button);
+ }
+
+ tool.PropertyChanged += (sender, args) =>
+ {
+ if (args.PropertyName == nameof(ExternalTool.MenuIcon))
+ {
+ button.Icon = tool.MenuIcon;
+ }
+ else if (args.PropertyName == nameof(ExternalTool.IsPinned))
+ {
+ // The command bar does not like to be updated when the overflow menu is open, so be sure to close it before manipulating elements.
+ MyCommandBar.IsOpen = false;
+
+ // Flip the menu item from pin to unpin (or vice versa)
+ MenuFlyout menu = new MenuFlyout();
+ menu.Items.Add(tool.IsPinned ? CreateUnPinMenuItem(tool) : CreatePinMenuItem(tool));
+ menu.Items.Add(CreateUnregisterMenuItem(tool));
+ button.ContextFlyout = menu;
+
+ // If a tool is pinned, we'll add it to the primary commands list, otherwise the secondary commands list
+ if (tool.IsPinned)
+ {
+ MyCommandBar.SecondaryCommands.Remove(button);
+ MyCommandBar.PrimaryCommands.Add(button);
+ }
+ else
+ {
+ MyCommandBar.PrimaryCommands.Remove(button);
+ MyCommandBar.SecondaryCommands.Add(button);
+ }
+
+ EvaluateLocationOfManageToolsButton();
+ }
+ };
+ }
+
+ private void EvaluateLocationOfManageToolsButton()
+ {
+ // If there are pinned tools (registered as primary commands), then move the Manage Tools button to the secondary command list
+ if (MyCommandBar.PrimaryCommands.Count > 1 &&
+ MyCommandBar.PrimaryCommands[0] is AppBarButton button &&
+ button.Command == _viewModel.ManageExternalToolsButtonCommand)
+ {
+ MyCommandBar.PrimaryCommands.Remove(button);
+ AddManageToolsOptionToCommandBar();
+ }
+
+ // If we don't have any more primary commands, move the Manage Tools Option from the secondary commands to the primary commands
+ else if (MyCommandBar.PrimaryCommands.Count == 0)
+ {
+ // The first two items in the secondary commands list should be the tool management button and a separator
+ Debug.Assert(MyCommandBar.SecondaryCommands.Count >= 2 && MyCommandBar.SecondaryCommands[0] is AppBarButton toolsBtn && toolsBtn.Command == _viewModel.ManageExternalToolsButtonCommand, "Where did tools button go?");
+ Debug.Assert(MyCommandBar.SecondaryCommands.Count >= 2 && MyCommandBar.SecondaryCommands[1] is AppBarSeparator, "Where did the separator go?");
+
+ MyCommandBar.SecondaryCommands.RemoveAt(1);
+ MyCommandBar.SecondaryCommands.RemoveAt(0);
+ AddManageToolsOptionToCommandBar();
+ }
+ }
+
+ private void AddManageToolsOptionToCommandBar()
+ {
+ // Put in the "manage tools" button
+ AppBarButton manageToolsButton = new AppBarButton
+ {
+ Label = _manageToolsMenuItemText,
+ Icon = new FontIcon() { Glyph = ManageToolsButtonText },
+ Command = _viewModel.ManageExternalToolsButtonCommand,
+ };
+
+ // If there aren't any pinned tools, then put this in as a primary command
+ if (ExternalToolsHelper.Instance.FilteredExternalTools.Count == 0)
+ {
+ MyCommandBar.PrimaryCommands.Add(manageToolsButton);
+ }
+ else
+ {
+ // Otherwise, put this at the at the top of the secondary command list
+ MyCommandBar.SecondaryCommands.Insert(0, manageToolsButton);
+ MyCommandBar.SecondaryCommands.Insert(1, new AppBarSeparator());
+ }
+ }
+
+ private MenuFlyoutItem CreatePinMenuItem(ExternalTool tool)
+ {
+ MenuFlyoutItem pin = new MenuFlyoutItem
+ {
+ Text = _pinMenuItemText,
+ Command = tool.TogglePinnedStateCommand,
+ Icon = new FontIcon() { Glyph = tool.PinGlyph },
+ };
+
+ return pin;
+ }
+
+ private MenuFlyoutItem CreateUnPinMenuItem(ExternalTool tool)
+ {
+ MenuFlyoutItem unpin = new MenuFlyoutItem
+ {
+ Text = _unpinMenuItemText,
+ Command = tool.TogglePinnedStateCommand,
+ Icon = new FontIcon() { Glyph = tool.PinGlyph },
+ };
+
+ return unpin;
+ }
+
+ private MenuFlyoutItem CreateUnregisterMenuItem(ExternalTool tool)
+ {
+ MenuFlyoutItem unRegister = new MenuFlyoutItem
+ {
+ Text = _unregisterMenuItemText,
+ Command = tool.UnregisterToolCommand,
+ Icon = new FontIcon() { Glyph = "\uECC9" },
+ };
+
+ return unRegister;
+ }
+
+ private void AllExternalTools_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ {
+ if (e.NewItems is not null)
+ {
+ foreach (ExternalTool newItem in e.NewItems)
+ {
+ AddToolToCommandBar(newItem);
+ }
+ }
+
+ break;
+ }
+
+ case NotifyCollectionChangedAction.Remove:
+ {
+ Debug.Assert(e.OldItems is not null, "Why is old items null");
+ foreach (ExternalTool oldItem in e.OldItems)
+ {
+ // Find this item in the command bar
+ AppBarButton? button = MyCommandBar.PrimaryCommands.OfType().FirstOrDefault(b => b.Tag == oldItem);
+ if (button is not null)
+ {
+ MyCommandBar.PrimaryCommands.Remove(button);
+ }
+ else
+ {
+ button = MyCommandBar.SecondaryCommands.OfType().FirstOrDefault(b => b.Tag == oldItem);
+ if (button is not null)
+ {
+ MyCommandBar.SecondaryCommands.Remove(button);
+ }
+ else
+ {
+ Debug.Assert(false, "Could not find button for tool");
+ }
+ }
+ }
+
+ break;
+ }
+ }
+
+ EvaluateLocationOfManageToolsButton();
}
public void SetRegionsForTitleBar()
@@ -155,11 +393,11 @@ public void SetRegionsForTitleBar()
private void SetDefaultPosition()
{
// If attached to an app it should show up on the monitor that the app is on
- _monitorRect = GetMonitorRectForWindow(_viewModel.ApplicationHwnd ?? ThisHwnd);
+ _monitorRect = GetMonitorRectForWindow(_viewModel.ApplicationHwnd ?? TryGetParentProcessHWND() ?? ThisHwnd);
var screenWidth = _monitorRect.right - _monitorRect.left;
this.Move(
(int)((screenWidth - (Width * _dpiScale)) / 2) + _monitorRect.left,
- (int)_WindowPositionOffsetY);
+ (int)WindowPositionOffsetY + _monitorRect.top);
// Get the saved settings for the ExpandedView size. On first run, this will be
// the default 0,0, so we'll set the size proportional to the monitor size.
@@ -183,6 +421,13 @@ private void SetDefaultPosition()
_restoreState.Width = settingSize.Width;
}
+ internal void UpdatePositionFromHwnd(HWND hwnd)
+ {
+ RECT rect;
+ PInvoke.GetWindowRect(hwnd, out rect);
+ this.Move(rect.left, rect.top);
+ }
+
private void WindowEx_Closed(object sender, WindowEventArgs args)
{
ClipboardMonitor.Instance.Stop();
@@ -194,13 +439,16 @@ private void WindowEx_Closed(object sender, WindowEventArgs args)
CacheRestoreState();
}
- if (!isClosing)
+ if (!_isClosing)
{
- isClosing = true;
+ _isClosing = true;
var barWindow = Application.Current.GetService().DBarWindow;
barWindow?.Close();
- isClosing = false;
+ _isClosing = false;
}
+
+ // Unsubscribe from the activation handler
+ Activated -= Window_Activated;
}
private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e)
@@ -261,33 +509,22 @@ private void ExpandLargeContentPanel()
LargeContentPanel.Visibility = Visibility.Visible;
MaxHeight = double.NaN;
- // If they expand to ExpandedView and they're not snapped, we can use the
- // RestoreState size & position.
- if (!_viewModel.IsSnapped)
- {
- this.MoveAndResize(
- _restoreState.Left, _restoreState.Top, _restoreState.Width, _restoreState.Height);
- }
- else
- {
- // Conversely if they're snapped, the position is determined by the snap,
- // and we potentially adjust the size to ensure it doesn't extend beyond the screen.
- var availableWidth = _monitorRect.Width - Math.Abs(AppWindow.Position.X) - _RightSideGap;
- if (availableWidth < _restoreState.Width)
- {
- _restoreState.Width = availableWidth;
- }
+ var monitorRect = GetMonitorRectForWindow(ThisHwnd);
+ var dpiScale = GetDpiScaleForWindow(ThisHwnd);
- Width = _restoreState.Width;
+ // Expand the window but keep the x,y coordinates of top-left most corner of the window the same so it doesn't
+ // jump around the screen.
+ var availableWidth = monitorRect.Width - Math.Abs(AppWindow.Position.X - monitorRect.left) - RightSideGap;
+ _restoreState.Width = (int)((double)availableWidth / dpiScale);
- var availableHeight = _monitorRect.Height - Math.Abs(AppWindow.Position.Y);
- if (availableHeight < _restoreState.Height)
- {
- _restoreState.Height = availableHeight;
- }
+ Width = _restoreState.Width;
- Height = _restoreState.Height;
- }
+ var availableHeight = monitorRect.Height - Math.Abs(AppWindow.Position.Y - monitorRect.top);
+
+ _restoreState.Height = (int)((double)availableHeight / dpiScale);
+
+ this.MoveAndResize(
+ AppWindow.Position.X, AppWindow.Position.Y, _restoreState.Width, _restoreState.Height);
}
private void CollapseLargeContentPanel()
@@ -295,7 +532,7 @@ private void CollapseLargeContentPanel()
// Make sure we cache the state before switching to collapsed bar.
CacheRestoreState();
LargeContentPanel.Visibility = Visibility.Collapsed;
- MaxHeight = _FloatingHorizontalBarHeight;
+ SetBarToDefaultHeight();
}
internal void NavigateTo(Type viewModelType)
@@ -320,7 +557,7 @@ private void MainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
SetRegionsForTitleBar();
}
- // workaround as Appwindow titlebar doesn't update caption button colors correctly when changed while app is running
+ // workaround as AppWindow TitleBar doesn't update caption button colors correctly when changed while app is running
// https://task.ms/44172495
public void ApplySystemThemeToCaptionButtons()
{
@@ -344,8 +581,68 @@ public void ApplySystemThemeToCaptionButtons()
public void SetCaptionButtonColors(Windows.UI.Color color)
{
- var res = Application.Current.Resources;
- res["WindowCaptionForeground"] = color;
AppWindow.TitleBar.ButtonForegroundColor = color;
+ UpdateCustomTitleBarButtonsTextColor();
+ }
+
+ private void Window_Activated(object sender, WindowActivatedEventArgs args)
+ {
+ // This follows the design guidance of dimming our title bar elements when the window isn't activated
+ // https://learn.microsoft.com/en-us/windows/apps/develop/title-bar#dim-the-title-bar-when-the-window-is-inactive
+ _currentActivationState = args.WindowActivationState;
+ UpdateCustomTitleBarButtonsTextColor();
+ }
+
+ private void UpdateCustomTitleBarButtonsTextColor()
+ {
+ FrameworkElement? rootElement = Content as FrameworkElement;
+ Debug.Assert(rootElement != null, "Expected Content to be a FrameworkElement");
+
+ if (_currentActivationState == WindowActivationState.Deactivated)
+ {
+ SolidColorBrush brush = (rootElement.ActualTheme == ElementTheme.Dark) ? _darkModeDeactiveCaptionBrush : _nonDarkModeDeactiveCaptionBrush;
+
+ SnapButtonText.Foreground = brush;
+ ExpandCollapseLayoutButtonText.Foreground = brush;
+ RotateLayoutButtonText.Foreground = brush;
+ }
+ else
+ {
+ SolidColorBrush brush = (rootElement.ActualTheme == ElementTheme.Dark) ? _darkModeActiveCaptionBrush : _nonDarkModeActiveCaptionBrush;
+
+ SnapButtonText.Foreground = brush;
+ ExpandCollapseLayoutButtonText.Foreground = brush;
+ RotateLayoutButtonText.Foreground = brush;
+ }
+ }
+
+ private void SetBarToDefaultHeight()
+ {
+ if (!_viewModel.ShowingExpandedContent)
+ {
+ this.Height = FloatingHorizontalBarHeight;
+ this.MaxHeight = FloatingHorizontalBarHeight;
+ this.MinHeight = FloatingHorizontalBarHeight;
+ this.AppWindow.Resize(new Windows.Graphics.SizeInt32(this.AppWindow.Size.Width, FloatingHorizontalBarHeight));
+ }
+ }
+
+ // CommandBar has an issue where, if the overflow menu opens and the app's window size is too small,
+ // the overflow menu will always appear above the app window and will go off screen. Bug has been filed,
+ // but in the meantime, we'll adjust our window's size to when the overflow menu opens, and set it back
+ // to default after it is closed in order to work around this issue.
+ private void MyCommandBar_Opening(object sender, object e)
+ {
+ if (!_viewModel.ShowingExpandedContent)
+ {
+ this.Height = FloatingHorizontalBarHeightWithExpandedCommandBar;
+ this.MaxHeight = FloatingHorizontalBarHeightWithExpandedCommandBar;
+ this.MinHeight = FloatingHorizontalBarHeightWithExpandedCommandBar;
+ }
+ }
+
+ private void MyCommandBar_Closing(object sender, object e)
+ {
+ SetBarToDefaultHeight();
}
}
diff --git a/tools/PI/DevHome.PI/Views/BarWindowVertical.xaml b/tools/PI/DevHome.PI/Views/BarWindowVertical.xaml
index 4ae1cfb67e..84dac185a8 100644
--- a/tools/PI/DevHome.PI/Views/BarWindowVertical.xaml
+++ b/tools/PI/DevHome.PI/Views/BarWindowVertical.xaml
@@ -9,52 +9,25 @@
xmlns:controls="using:DevHome.PI.Controls"
xmlns:helpers="using:DevHome.PI.Helpers"
mc:Ignorable="d"
- Title="" MinHeight="700" MinWidth="70" MaxWidth="70" Width="70" Height="700"
+ Title="" MinHeight="500" MinWidth="72" MaxWidth="72" Width="72" Height="700"
TaskBarIcon="Images/pi.ico" IsTitleBarVisible="False"
IsAlwaysOnTop="{x:Bind _viewModel.IsAlwaysOnTop, Mode=OneWay}"
Closed="WindowEx_Closed">
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -88,19 +96,13 @@
-
-
-
+
+
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
diff --git a/tools/PI/DevHome.PI/Views/BarWindowVertical.xaml.cs b/tools/PI/DevHome.PI/Views/BarWindowVertical.xaml.cs
index f7f12219ba..f867cf0ea8 100644
--- a/tools/PI/DevHome.PI/Views/BarWindowVertical.xaml.cs
+++ b/tools/PI/DevHome.PI/Views/BarWindowVertical.xaml.cs
@@ -3,9 +3,8 @@
using System;
using System.ComponentModel;
-using System.Diagnostics;
using System.Linq;
-using DevHome.Common.Extensions;
+using DevHome.Common.Extensions;
using DevHome.PI.Helpers;
using DevHome.PI.Models;
using DevHome.PI.Properties;
@@ -15,10 +14,14 @@
using Microsoft.UI.Xaml.Input;
using Windows.UI.WindowManagement;
using Windows.Win32;
-using Windows.Win32.Foundation;
+using Windows.Win32.Foundation;
+using Windows.Win32.Graphics.Gdi;
+using Windows.Win32.UI.Shell.Common;
using Windows.Win32.UI.WindowsAndMessaging;
using WinRT.Interop;
-using WinUIEx;
+using WinUIEx;
+
+using static DevHome.PI.Helpers.CommonHelper;
using static DevHome.PI.Helpers.WindowHelper;
namespace DevHome.PI;
@@ -26,30 +29,27 @@ namespace DevHome.PI;
public partial class BarWindowVertical : WindowEx
{
private readonly Settings _settings = Settings.Default;
- private readonly string _errorTitleText = CommonHelper.GetLocalizedString("ToolLaunchErrorTitle");
- private readonly string _errorMessageText = CommonHelper.GetLocalizedString("ToolLaunchErrorMessage");
private readonly BarWindowViewModel _viewModel;
private int _cursorPosX; // = 0;
private int _cursorPosY; // = 0;
private int _appWindowPosX; // = 0;
private int _appWindowPosY; // = 0;
- private bool isWindowMoving; // = false;
- private bool isClosing;
+ private bool _isWindowMoving; // = false;
+ private bool _isClosing;
+
+ private double _dpiScale = 1.0;
internal HWND ThisHwnd { get; private set; }
- public Microsoft.UI.Dispatching.DispatcherQueue TheDispatcher
- {
- get; set;
- }
+ private readonly Microsoft.UI.Dispatching.DispatcherQueue _dispatcher;
public BarWindowVertical(BarWindowViewModel model)
{
_viewModel = model;
// The main constructor is used in all cases, including when there's no target window.
- TheDispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
+ _dispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
InitializeComponent();
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
@@ -66,11 +66,25 @@ private void MainPanel_Loaded(object sender, RoutedEventArgs e)
RemoveThickFrameAttribute();
// Regardless of what is set in the XAML, our initial window width is too big. Setting this to 70 (same as the XAML file)
- Width = 70;
- }
-
+ Width = 70;
+
+ // Calculate the DPI scale.
+ _dpiScale = GetDpiScaleForWindow(ThisHwnd);
+
+ SetDefaultPosition();
+ }
+
+ private void SetDefaultPosition()
+ {
+ // If attached to an app it should show up on the monitor that the app is on
+ var monitorRect = GetMonitorRectForWindow(_viewModel.ApplicationHwnd ?? TryGetParentProcessHWND() ?? ThisHwnd);
+ this.Move(
+ monitorRect.left + (int)(70 * _dpiScale),
+ monitorRect.top + (int)(70 * _dpiScale));
+ }
+
private void RemoveThickFrameAttribute()
- {
+ {
// This is a workaround for this issue
// https://github.com/microsoft/microsoft-ui-xaml/issues/8947
//
@@ -78,7 +92,7 @@ private void RemoveThickFrameAttribute()
var style = PInvoke.GetWindowLong(ThisHwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
style &= ~(int)WINDOW_STYLE.WS_THICKFRAME;
_ = PInvoke.SetWindowLong(ThisHwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, style);
- PInvoke.SetWindowPos(ThisHwnd, HWND.Null, 0, 0, 0, 0, Windows.Win32.UI.WindowsAndMessaging.SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOOWNERZORDER);
+ PInvoke.SetWindowPos(ThisHwnd, HWND.Null, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOOWNERZORDER);
}
private void ViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
@@ -87,17 +101,28 @@ private void ViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs
{
this.Move(_viewModel.WindowPosition.X, _viewModel.WindowPosition.Y);
}
+ else if (string.Equals(e.PropertyName, nameof(BarWindowViewModel.RequestedWindowSize), StringComparison.OrdinalIgnoreCase))
+ {
+ Height = _viewModel.RequestedWindowSize.Height - 6; // 6 is a magic number that we lose due to removing the Thick Frame attribute
+ }
}
private void WindowEx_Closed(object sender, WindowEventArgs args)
{
- if (!isClosing)
+ if (!_isClosing)
{
- isClosing = true;
+ _isClosing = true;
var barWindow = Application.Current.GetService().DBarWindow;
barWindow?.Close();
- isClosing = false;
+ _isClosing = false;
}
+ }
+
+ internal void UpdatePositionFromHwnd(HWND hwnd)
+ {
+ RECT rect;
+ PInvoke.GetWindowRect(hwnd, out rect);
+ this.Move(rect.left, rect.top);
}
internal void SetRequestedTheme(ElementTheme theme)
@@ -118,7 +143,7 @@ private void CloseButton_Click(object sender, RoutedEventArgs e)
private void Window_PointerReleased(object sender, PointerRoutedEventArgs e)
{
((UIElement)sender).ReleasePointerCaptures();
- isWindowMoving = false;
+ _isWindowMoving = false;
// If we're occupying the same space as the target window, and we're not in medium/large mode, snap to the app
if (!_viewModel.IsSnapped && TargetAppData.Instance.HWnd != HWND.Null)
@@ -141,7 +166,7 @@ private void Window_PointerPressed(object sender, PointerRoutedEventArgs e)
_viewModel.UnsnapBarWindow();
}
- isWindowMoving = true;
+ _isWindowMoving = true;
((UIElement)sender).CapturePointer(e.Pointer);
_appWindowPosX = AppWindow.Position.X;
_appWindowPosY = AppWindow.Position.Y;
@@ -153,7 +178,7 @@ private void Window_PointerPressed(object sender, PointerRoutedEventArgs e)
private void Window_PointerMoved(object sender, PointerRoutedEventArgs e)
{
- if (isWindowMoving)
+ if (_isWindowMoving)
{
var properties = e.GetCurrentPoint((UIElement)sender).Properties;
if (properties.IsLeftButtonPressed)
diff --git a/tools/PI/DevHome.PI/Views/PrimaryWindow.xaml.cs b/tools/PI/DevHome.PI/Views/PrimaryWindow.xaml.cs
index d314669466..88d57f65f0 100644
--- a/tools/PI/DevHome.PI/Views/PrimaryWindow.xaml.cs
+++ b/tools/PI/DevHome.PI/Views/PrimaryWindow.xaml.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System;
-using System.ComponentModel;
using System.Diagnostics;
using DevHome.PI.Helpers;
using DevHome.PI.Models;
@@ -12,7 +11,6 @@
using Microsoft.UI.Xaml;
using Windows.System;
using Windows.Win32;
-using Windows.Win32.Foundation;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using WinUIEx;
using static DevHome.PI.Helpers.WindowHelper;
@@ -24,7 +22,7 @@ public sealed partial class PrimaryWindow : WindowEx
private const VirtualKey HotKey = VirtualKey.F12;
private const HOT_KEY_MODIFIERS KeyModifier = HOT_KEY_MODIFIERS.MOD_WIN;
- private HotKeyHelper? hotKeyHelper;
+ private HotKeyHelper? _hotKeyHelper;
public BarWindow? DBarWindow { get; private set; }
@@ -54,16 +52,15 @@ public void ClearBarWindow()
private void Window_Loaded(object sender, RoutedEventArgs e)
{
- hotKeyHelper = new(this, HandleHotKey);
- hotKeyHelper.RegisterHotKey(HotKey, KeyModifier);
-
App.Log("DevHome.PI_MainWindows_Loaded", LogLevel.Measure);
+ _hotKeyHelper = new(this, HandleHotKey);
+ _hotKeyHelper.RegisterHotKey(HotKey, KeyModifier);
}
private void WindowEx_Closed(object sender, WindowEventArgs args)
{
DBarWindow?.Close();
- hotKeyHelper?.UnregisterHotKey();
+ _hotKeyHelper?.UnregisterHotKey();
}
public void HandleHotKey(int keyId)
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Common/DevHome.QuietBackgroundProcesses.Common.vcxproj b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Common/DevHome.QuietBackgroundProcesses.Common.vcxproj
index 1ee96ed452..bbdd8af811 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Common/DevHome.QuietBackgroundProcesses.Common.vcxproj
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Common/DevHome.QuietBackgroundProcesses.Common.vcxproj
@@ -7,7 +7,6 @@
Utlity
-
true
true
true
@@ -27,6 +26,22 @@
+
+ Debug_FailFast
+ ARM
+
+
+ Debug_FailFast
+ ARM64
+
+
+ Debug_FailFast
+ Win32
+
+
+ Debug_FailFast
+ x64
+
Debug
ARM
@@ -73,6 +88,10 @@
true
true
+
+ true
+ true
+
false
true
@@ -133,6 +152,12 @@
Disabled
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+ Disabled
+
+
NDEBUG;%(PreprocessorDefinitions)
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer.Projection/DevHome.QuietBackgroundProcesses.ElevatedServer.Projection.csproj b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer.Projection/DevHome.QuietBackgroundProcesses.ElevatedServer.Projection.csproj
index b792576ee9..7293c0f8af 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer.Projection/DevHome.QuietBackgroundProcesses.ElevatedServer.Projection.csproj
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer.Projection/DevHome.QuietBackgroundProcesses.ElevatedServer.Projection.csproj
@@ -9,6 +9,7 @@
x86;x64;arm64
win-x86;win-x64;win-arm64
$(CppBaseOutDir)\DevHome.QuietBackgroundProcesses.Common\
+ Debug;Release;Debug_FailFast
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/DevHome.QuietBackgroundProcesses.ElevatedServer.vcxproj b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/DevHome.QuietBackgroundProcesses.ElevatedServer.vcxproj
index 388c55fdb7..d92583dca3 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/DevHome.QuietBackgroundProcesses.ElevatedServer.vcxproj
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/DevHome.QuietBackgroundProcesses.ElevatedServer.vcxproj
@@ -5,7 +5,7 @@
-
+
true
true
@@ -27,6 +27,22 @@
+
+ Debug_FailFast
+ ARM
+
+
+ Debug_FailFast
+ ARM64
+
+
+ Debug_FailFast
+ Win32
+
+
+ Debug_FailFast
+ x64
+
Debug
ARM
@@ -76,6 +92,10 @@
true
true
+
+ true
+ true
+
false
true
@@ -125,6 +145,12 @@
Disabled
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+ Disabled
+
+
NDEBUG;%(PreprocessorDefinitions)
@@ -178,6 +204,6 @@
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
+
\ No newline at end of file
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/Helpers.cpp b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/Helpers.cpp
index dcd89805df..0af73613af 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/Helpers.cpp
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/Helpers.cpp
@@ -131,7 +131,7 @@ void UploadPerformanceDataTelemetry(std::chrono::milliseconds samplingPeriod, co
// Upload computer information
auto computerInformation = GetComputerInformation();
- activity.ComputerInfo(
+ activity.QuietBackgroundProcesses_ComputerInfo(
computerInformation.processorCount,
computerInformation.processor.c_str(),
computerInformation.motherboard.c_str(),
@@ -147,7 +147,7 @@ void UploadPerformanceDataTelemetry(std::chrono::milliseconds samplingPeriod, co
}
// Upload category metrics
- activity.SessionCategoryMetrics(
+ activity.QuietBackgroundProcesses_SessionCategoryMetrics(
numProcesses[0],
numProcesses[1],
numProcesses[2],
@@ -198,10 +198,17 @@ void UploadPerformanceDataTelemetry(std::chrono::milliseconds samplingPeriod, co
continue;
}
- activity.ProcessInfo(
+ // Report process name with service name in telemetry
+ auto processNameTelemetry = std::wstring(item.name);
+ if (item.serviceName)
+ {
+ processNameTelemetry += std::wstring{} + L" (" + item.serviceName + L")";
+ }
+
+ activity.QuietBackgroundProcesses_ProcessInfo(
reason,
wil::compare_string_ordinal(item.path, system32Path, true) == 0,
- item.name,
+ processNameTelemetry.c_str(),
item.category,
item.packageFullName,
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/packages.config b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/packages.config
index b8f17060fc..4d87f2a61e 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/packages.config
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.ElevatedServer/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.PerformanceRecorderEngine/DevHome.QuietBackgroundProcesses.PerformanceRecorderEngine.vcxproj b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.PerformanceRecorderEngine/DevHome.QuietBackgroundProcesses.PerformanceRecorderEngine.vcxproj
index d9807465b4..e34d2dd142 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.PerformanceRecorderEngine/DevHome.QuietBackgroundProcesses.PerformanceRecorderEngine.vcxproj
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.PerformanceRecorderEngine/DevHome.QuietBackgroundProcesses.PerformanceRecorderEngine.vcxproj
@@ -6,6 +6,18 @@
+
+ Debug_FailFast
+ ARM64
+
+
+ Debug_FailFast
+ Win32
+
+
+ Debug_FailFast
+ x64
+
Debug
ARM64
@@ -50,16 +62,31 @@
true
v143
+
+ DynamicLibrary
+ true
+ v143
+
DynamicLibrary
true
v143
+
+ DynamicLibrary
+ true
+ v143
+
DynamicLibrary
true
v143
+
+ DynamicLibrary
+ true
+ v143
+
DynamicLibrary
false
@@ -87,18 +114,27 @@
+
+
+
+
+
+
+
+
+
@@ -108,6 +144,10 @@
false
false
+
+ false
+ false
+
false
false
@@ -116,6 +156,10 @@
false
false
+
+ false
+ false
+
false
false
@@ -124,6 +168,10 @@
false
false
+
+ false
+ false
+
false
false
@@ -147,6 +195,12 @@
Disabled
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+ Disabled
+
+
NDEBUG;%(PreprocessorDefinitions)
@@ -165,10 +219,13 @@
Create
+ Create
Create
Create
+ Create
Create
Create
+ Create
Create
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Server/DevHome.QuietBackgroundProcesses.Server.vcxproj b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Server/DevHome.QuietBackgroundProcesses.Server.vcxproj
index 18921b5ff6..6089f01bd0 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Server/DevHome.QuietBackgroundProcesses.Server.vcxproj
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Server/DevHome.QuietBackgroundProcesses.Server.vcxproj
@@ -3,7 +3,7 @@
C++
-
+
@@ -26,6 +26,22 @@
+
+ Debug_FailFast
+ ARM
+
+
+ Debug_FailFast
+ ARM64
+
+
+ Debug_FailFast
+ Win32
+
+
+ Debug_FailFast
+ x64
+
Debug
ARM
@@ -72,6 +88,10 @@
true
true
+
+ true
+ true
+
false
true
@@ -131,6 +151,12 @@
Disabled
+
+
+ _DEBUG;%(PreprocessorDefinitions)
+ Disabled
+
+
NDEBUG;%(PreprocessorDefinitions)
@@ -170,6 +196,6 @@
This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
-
+
\ No newline at end of file
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Server/packages.config b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Server/packages.config
index 3541db9593..41b2cb59a0 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Server/packages.config
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.Server/packages.config
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.UI/DevHome.QuietBackgroundProcesses.UI.csproj b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.UI/DevHome.QuietBackgroundProcesses.UI.csproj
index 74f170c199..de04195dc4 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.UI/DevHome.QuietBackgroundProcesses.UI.csproj
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.UI/DevHome.QuietBackgroundProcesses.UI.csproj
@@ -7,6 +7,7 @@
win-x86;win-x64;win-arm64
true
enable
+ Debug;Release;Debug_FailFast
diff --git a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.UI/ProcessData.cs b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.UI/ProcessData.cs
index 188d506072..01e898712e 100644
--- a/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.UI/ProcessData.cs
+++ b/tools/QuietBackgroundProcesses/DevHome.QuietBackgroundProcesses.UI/ProcessData.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System;
-using Microsoft.UI.Xaml.Automation;
namespace DevHome.QuietBackgroundProcesses.UI;
diff --git a/tools/SampleTool/src/SampleTool.csproj b/tools/SampleTool/src/SampleTool.csproj
index 76f91517f6..5f40d28d61 100644
--- a/tools/SampleTool/src/SampleTool.csproj
+++ b/tools/SampleTool/src/SampleTool.csproj
@@ -5,6 +5,7 @@
x86;x64;arm64
win-x86;win-x64;win-arm64
true
+ Debug;Release;Debug_FailFast
diff --git a/tools/SampleTool/src/Views/SampleToolPage.xaml b/tools/SampleTool/src/Views/SampleToolPage.xaml
index c2e0ccbba8..09d974d603 100644
--- a/tools/SampleTool/src/Views/SampleToolPage.xaml
+++ b/tools/SampleTool/src/Views/SampleToolPage.xaml
@@ -1,11 +1,11 @@
-
-
+
diff --git a/tools/SampleTool/src/Views/SampleToolPage.xaml.cs b/tools/SampleTool/src/Views/SampleToolPage.xaml.cs
index e1de9e3b45..7b8dbefa66 100644
--- a/tools/SampleTool/src/Views/SampleToolPage.xaml.cs
+++ b/tools/SampleTool/src/Views/SampleToolPage.xaml.cs
@@ -1,15 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using DevHome.Common;
+using DevHome.Common.Views;
using Tools.SampleTool.ViewModels;
namespace Tools.SampleTool.Views;
public partial class SampleToolPage : ToolPage
{
- public override string ShortName => "SampleTool";
-
public SampleToolViewModel ViewModel
{
get;
diff --git a/tools/SampleTool/unittest/SampleTool.UnitTest.csproj b/tools/SampleTool/unittest/SampleTool.UnitTest.csproj
index 49961984f3..390eb3df07 100644
--- a/tools/SampleTool/unittest/SampleTool.UnitTest.csproj
+++ b/tools/SampleTool/unittest/SampleTool.UnitTest.csproj
@@ -10,6 +10,7 @@
true
true
resources.pri
+ Debug;Release;Debug_FailFast
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/Configuration/ConfigurationFileHelper.cs b/tools/SetupFlow/DevHome.SetupFlow.Common/Configuration/ConfigurationFileHelper.cs
deleted file mode 100644
index 4c50229b2a..0000000000
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/Configuration/ConfigurationFileHelper.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using DevHome.SetupFlow.Common.Exceptions;
-using DevHome.SetupFlow.Common.Helpers;
-using DevHome.SetupFlow.Common.TelemetryEvents;
-using DevHome.Telemetry;
-using Microsoft.Management.Configuration;
-using Serilog;
-using Windows.Storage;
-using Windows.Storage.Streams;
-
-namespace DevHome.SetupFlow.Common.Configuration;
-
-///
-/// Helper for applying a configuration file. This exists so that we can
-/// use it in an elevated or non-elevated context.
-///
-public class ConfigurationFileHelper
-{
- public class ApplicationResult
- {
- public ApplyConfigurationSetResult Result
- {
- get;
- }
-
- public bool Succeeded => Result.ResultCode == null;
-
- public bool RequiresReboot => Result.UnitResults.Any(result => result.RebootRequired);
-
- public Exception ResultException => Result.ResultCode;
-
- public ApplicationResult(ApplyConfigurationSetResult result)
- {
- Result = result;
- }
- }
-
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(ConfigurationFileHelper));
- private const string PowerShellHandlerIdentifier = "pwsh";
- private readonly Guid _activityId;
- private ConfigurationProcessor _processor;
- private ConfigurationSet _configSet;
-
- public ConfigurationFileHelper(Guid activityId)
- {
- _activityId = activityId;
- }
-
- public IList Units => _configSet?.Units;
-
- ///
- /// Open configuration set from the provided .
- ///
- /// DSC configuration file path
- /// DSC configuration file content
- public async Task OpenConfigurationSetAsync(string filePath, string content)
- {
- try
- {
- _processor = await CreateConfigurationProcessorAsync();
- _configSet = await OpenConfigurationSetInternalAsync(_processor, filePath, content);
- }
- catch
- {
- _processor = null;
- _configSet = null;
- throw;
- }
- }
-
- public async Task ResolveConfigurationUnitDetails()
- {
- if (_processor == null || _configSet == null)
- {
- throw new InvalidOperationException();
- }
-
- await _processor.GetSetDetailsAsync(_configSet, ConfigurationUnitDetailFlags.ReadOnly);
- }
-
- private async Task OpenConfigurationSetInternalAsync(ConfigurationProcessor processor, string filePath, string content)
- {
- var file = await StorageFile.GetFileFromPathAsync(filePath);
- var parentDir = await file.GetParentAsync();
- var inputStream = StringToStream(content);
-
- var openConfigResult = processor.OpenConfigurationSet(inputStream);
- var configSet = openConfigResult.Set ?? throw new OpenConfigurationSetException(openConfigResult.ResultCode, openConfigResult.Field, openConfigResult.Value);
-
- // Set input file path in the configuration set to inform the
- // processor about the working directory when applying the
- // configuration
- configSet.Name = file.Name;
- configSet.Origin = parentDir.Path;
- configSet.Path = file.Path;
- return configSet;
- }
-
- public async Task ApplyConfigurationAsync()
- {
- if (_processor == null || _configSet == null)
- {
- throw new InvalidOperationException();
- }
-
- _log.Information("Starting to apply configuration set");
- var result = await _processor.ApplySetAsync(_configSet, ApplyConfigurationSetFlags.None);
-
- foreach (var unitResult in result.UnitResults)
- {
- TelemetryFactory.Get().Log("ConfigurationFile_UnitResult", LogLevel.Critical, new ConfigurationUnitResultEvent(unitResult), _activityId);
- }
-
- TelemetryFactory.Get().Log("ConfigurationFile_Result", LogLevel.Critical, new ConfigurationSetResultEvent(_configSet, result), _activityId);
-
- _log.Information($"Apply configuration finished. HResult: {result.ResultCode?.HResult}");
- return new ApplicationResult(result);
- }
-
- ///
- /// Create and configure the configuration processor.
- ///
- /// Configuration processor
- private async Task CreateConfigurationProcessorAsync()
- {
- ConfigurationStaticFunctions config = new();
- var factory = await config.CreateConfigurationSetProcessorFactoryAsync(PowerShellHandlerIdentifier).AsTask();
-
- // Create and configure the configuration processor.
- var processor = config.CreateConfigurationProcessor(factory);
- processor.Caller = nameof(DevHome);
- processor.Diagnostics += LogConfigurationDiagnostics;
- processor.MinimumLevel = DiagnosticLevel.Verbose;
- return processor;
- }
-
- ///
- /// Log configuration diagnostics event handler
- ///
- /// Event sender
- /// Diagnostic information
- private void LogConfigurationDiagnostics(object sender, IDiagnosticInformation diagnosticInformation)
- {
- var log = _log.ForContext("SourceContext", nameof(ConfigurationProcessor));
- switch (diagnosticInformation.Level)
- {
- case DiagnosticLevel.Warning:
- log.Warning(diagnosticInformation.Message);
- return;
- case DiagnosticLevel.Error:
- log.Error(diagnosticInformation.Message);
- return;
- case DiagnosticLevel.Critical:
- log.Fatal(diagnosticInformation.Message);
- return;
- case DiagnosticLevel.Verbose:
- case DiagnosticLevel.Informational:
- default:
- log.Information(diagnosticInformation.Message);
- return;
- }
- }
-
- ///
- /// Convert a string to an input stream
- ///
- /// Target string
- /// Input stream
- private InMemoryRandomAccessStream StringToStream(string str)
- {
- InMemoryRandomAccessStream result = new();
- using (DataWriter writer = new(result))
- {
- writer.UnicodeEncoding = UnicodeEncoding.Utf8;
- writer.WriteString(str);
- writer.StoreAsync().AsTask().Wait();
- writer.DetachStream();
- }
-
- result.Seek(0);
- return result;
- }
-}
diff --git a/tools/SetupFlow/DevHome.SetupFlow.Common/DevHome.SetupFlow.Common.csproj b/tools/SetupFlow/DevHome.SetupFlow.Common/DevHome.SetupFlow.Common.csproj
index 33ab51ab0b..3b83b5168c 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.Common/DevHome.SetupFlow.Common.csproj
+++ b/tools/SetupFlow/DevHome.SetupFlow.Common/DevHome.SetupFlow.Common.csproj
@@ -6,54 +6,16 @@
win-x86;win-x64;win-arm64
disable
false
-
-
-
-
- 10.0.19041.0
- Microsoft.Management.Deployment
+ Debug;Release;Debug_FailFast
-
-
-
-
-
-
-
-
-
-
-
+
all
runtime; build; native; contentfiles; analyzers
-
-
-
- NU1701
- true
- none
-
-
- True
-
@@ -65,11 +27,4 @@
-
-
-
-
-
-
-
diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent.Projection/DevHome.SetupFlow.ElevatedComponent.Projection.csproj b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent.Projection/DevHome.SetupFlow.ElevatedComponent.Projection.csproj
index 609b9dc59d..cd1159b441 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent.Projection/DevHome.SetupFlow.ElevatedComponent.Projection.csproj
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent.Projection/DevHome.SetupFlow.ElevatedComponent.Projection.csproj
@@ -9,6 +9,7 @@
enable
x86;x64;arm64
win-x86;win-x64;win-arm64
+ Debug;Release;Debug_FailFast
@@ -20,7 +21,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/DevHome.SetupFlow.ElevatedComponent.csproj b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/DevHome.SetupFlow.ElevatedComponent.csproj
index e7723fa286..cc0662551c 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/DevHome.SetupFlow.ElevatedComponent.csproj
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/DevHome.SetupFlow.ElevatedComponent.csproj
@@ -8,6 +8,7 @@
enable
x86;x64;arm64
win-x86;win-x64;win-arm64
+ Debug;Release;Debug_FailFast
@@ -20,7 +21,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -28,6 +29,8 @@
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Directory.build.targets b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Directory.build.targets
new file mode 100644
index 0000000000..e709f3f981
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Directory.build.targets
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs
index 9e848df2dd..97bb5486c6 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/ElevatedComponentOperation.cs
@@ -1,9 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using DevHome.Services.Core.Extensions;
+using DevHome.Services.DesiredStateConfiguration.Extensions;
+using DevHome.Services.WindowsPackageManager.Extensions;
using DevHome.SetupFlow.Common.Contracts;
using DevHome.SetupFlow.ElevatedComponent.Helpers;
using DevHome.SetupFlow.ElevatedComponent.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
using Serilog;
using Windows.Foundation;
@@ -13,8 +19,10 @@ namespace DevHome.SetupFlow.ElevatedComponent;
/// Class for executing operations in the elevated background process.
///
public sealed class ElevatedComponentOperation : IElevatedComponentOperation
-{
- private readonly ILogger _log = Log.ForContext("SourceContext", nameof(ElevatedComponentOperation));
+{
+ private readonly Microsoft.Extensions.Logging.ILogger _logger;
+
+ internal static IHost Host { get; } = BuildHost();
///
/// Tasks arguments are passed to the elevated process as input at launch-time.
@@ -37,13 +45,15 @@ public sealed class ElevatedComponentOperation : IElevatedComponentOperation
public ElevatedComponentOperation(IList tasksArgumentList)
{
+ _logger = LoggerFactory.Create(lb => lb.AddSerilog(dispose: false)).CreateLogger();
+
try
{
_tasksArguments = TasksArguments.FromArgumentList(tasksArgumentList);
}
catch (Exception e)
{
- _log.Error(e, $"Failed to parse tasks arguments");
+ _logger.LogError(e, $"Failed to parse tasks arguments");
throw;
}
}
@@ -53,16 +63,16 @@ public void WriteToStdOut(string value)
Console.WriteLine(value);
}
- public IAsyncOperation InstallPackageAsync(string packageId, string catalogName, string version)
+ public IAsyncOperation InstallPackageAsync(string packageId, string catalogName, string version, Guid activityId)
{
var taskArguments = GetInstallPackageTaskArguments(packageId, catalogName, version);
return ValidateAndExecuteAsync(
taskArguments,
async () =>
{
- _log.Information($"Installing package elevated: '{packageId}' from '{catalogName}'");
+ _logger.LogInformation($"Installing package elevated: '{packageId}' from '{catalogName}'");
var task = new ElevatedInstallTask();
- return await task.InstallPackage(taskArguments.PackageId, taskArguments.CatalogName, version);
+ return await task.InstallPackage(taskArguments.PackageId, taskArguments.CatalogName, version, activityId);
},
result => result.TaskSucceeded).AsAsyncOperation();
}
@@ -77,7 +87,7 @@ public IAsyncOperation CreateDevDriveAsync()
taskArguments,
async () =>
{
- _log.Information("Creating elevated Dev Drive storage operator");
+ _logger.LogInformation("Creating elevated Dev Drive storage operator");
var task = new DevDriveStorageOperator();
var result = task.CreateDevDrive(taskArguments.VirtDiskPath, taskArguments.SizeInBytes, taskArguments.NewDriveLetter, taskArguments.DriveLabel);
return await Task.FromResult(result);
@@ -92,7 +102,7 @@ public IAsyncOperation ApplyConfigurationAsync(Guid
taskArguments,
async () =>
{
- _log.Information("Applying DSC configuration elevated");
+ _logger.LogInformation("Applying DSC configuration elevated");
var task = new ElevatedConfigurationTask();
return await task.ApplyConfiguration(taskArguments.FilePath, taskArguments.Content, activityId);
},
@@ -107,7 +117,7 @@ public void Terminate()
var allTasksArguments = _tasksArguments.GetAllTasksArguments();
if (allTasksArguments.Count == _operationsState.Count)
{
- _log.Information($"All operations for the tasks arguments provided to the elevated process were executed.");
+ _logger.LogInformation($"All operations for the tasks arguments provided to the elevated process were executed.");
}
else
{
@@ -116,7 +126,7 @@ public void Terminate()
{
if (!_operationsState.ContainsKey(taskArguments))
{
- _log.Warning($"Operation for task arguments {string.Join(' ', taskArguments.ToArgumentList())} was provided to the elevated process but was never executed.");
+ _logger.LogWarning($"Operation for task arguments {string.Join(' ', taskArguments.ToArgumentList())} was provided to the elevated process but was never executed.");
}
}
}
@@ -128,7 +138,7 @@ private InstallPackageTaskArguments GetInstallPackageTaskArguments(string packag
var taskArguments = _tasksArguments.InstallPackages?.FirstOrDefault(def => def.PackageId == packageId && def.CatalogName == catalogName && def.Version == version);
if (taskArguments == null)
{
- _log.Error($"No match found for PackageId={packageId}, CatalogId={catalogName} and Version={version} in the process tasks arguments.");
+ _logger.LogError($"No match found for PackageId={packageId}, CatalogId={catalogName} and Version={version} in the process tasks arguments.");
throw new ArgumentException($"Failed to install '{packageId}' ({version}) from '{catalogName}' because it was not in the pre-approved tasks arguments");
}
@@ -140,7 +150,7 @@ private ConfigureTaskArguments GetConfigureTaskArguments()
var taskArguments = _tasksArguments.Configure;
if (taskArguments == null)
{
- _log.Error($"No configuration task was found in the process tasks arguments ");
+ _logger.LogError($"No configuration task was found in the process tasks arguments ");
throw new ArgumentException($"Failed to apply configuration because it was not in the pre-approved tasks arguments");
}
@@ -152,7 +162,7 @@ private CreateDevDriveTaskArguments GetDevDriveTaskArguments()
var taskArguments = _tasksArguments.CreateDevDrive;
if (taskArguments == null)
{
- _log.Error($"No 'create dev drive' task was found in the process tasks arguments ");
+ _logger.LogError($"No 'create dev drive' task was found in the process tasks arguments ");
throw new ArgumentException($"Failed to create a dev drive because it was not in the pre-approved tasks arguments");
}
@@ -182,7 +192,7 @@ private async Task ValidateAndExecuteAsync(
}
catch (Exception e)
{
- _log.Error(e, $"Failed to validate or execute operation");
+ _logger.LogError(e, $"Failed to validate or execute operation");
EndOperation(taskArguments, false);
throw;
}
@@ -266,4 +276,26 @@ private sealed class OperationState
///
public bool InProgress { get; set; }
}
+
+ private static IHost BuildHost()
+ {
+ return Microsoft.Extensions.Hosting.Host
+ .CreateDefaultBuilder()
+ .UseContentRoot(AppContext.BaseDirectory)
+ .UseDefaultServiceProvider((context, options) =>
+ {
+ options.ValidateOnBuild = true;
+ })
+ .ConfigureServices((context, services) =>
+ {
+ // Add Serilog logging for ILogger.
+ services.AddLogging(lb => lb.AddSerilog(dispose: true));
+
+ // Service projects
+ services.AddCore();
+ services.AddWinGetElevated();
+ services.AddDSC();
+ })
+ .Build();
+ }
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Helpers/ElevatedConfigureUnitTaskResult.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Helpers/ElevatedConfigureUnitTaskResult.cs
index cf56009d34..2d7cbf8cb8 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Helpers/ElevatedConfigureUnitTaskResult.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Helpers/ElevatedConfigureUnitTaskResult.cs
@@ -1,28 +1,44 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using Microsoft.Management.Configuration;
+using Windows.Win32.Foundation;
+
namespace DevHome.SetupFlow.ElevatedComponent.Helpers;
///
/// Class for the result of an individual unit/resource inside
///
public sealed class ElevatedConfigureUnitTaskResult
-{
- public string? Type { get; set; }
+{
+ private readonly IDSCApplicationUnitResult? _unitResult;
+
+ public ElevatedConfigureUnitTaskResult()
+ {
+ // This constructor is required for CsWinRT projection.
+ }
+
+ internal ElevatedConfigureUnitTaskResult(IDSCApplicationUnitResult unitResult)
+ {
+ _unitResult = unitResult;
+ }
+
+ public string? Type => _unitResult?.AppliedUnit.Type;
- public string? Id { get; set; }
+ public string? Id => _unitResult?.AppliedUnit.Id;
- public string? UnitDescription { get; set; }
+ public string? UnitDescription => _unitResult?.AppliedUnit.Description;
- public string? Intent { get; set; }
+ public string? Intent => _unitResult?.AppliedUnit.Intent;
- public bool IsSkipped { get; set; }
+ public bool IsSkipped => _unitResult?.IsSkipped ?? false;
- public int HResult { get; set; }
+ public int HResult => _unitResult?.HResult ?? HRESULT.S_OK;
- public int ResultSource { get; set; }
+ public int ResultSource => (int)(_unitResult?.ResultSource ?? ConfigurationUnitResultSource.None);
- public string? Details { get; set; }
+ public string? Details => _unitResult?.Details;
- public string? ErrorDescription { get; set; }
+ public string? ErrorDescription => _unitResult?.ErrorDescription;
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/IElevatedComponentOperation.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/IElevatedComponentOperation.cs
index 8bcd7cbba8..2661988866 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/IElevatedComponentOperation.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/IElevatedComponentOperation.cs
@@ -36,7 +36,7 @@ public interface IElevatedComponentOperation
/// Package catalog name
/// Package version
/// Install package operation result
- public IAsyncOperation InstallPackageAsync(string packageId, string catalogName, string version);
+ public IAsyncOperation InstallPackageAsync(string packageId, string catalogName, string version, Guid activityId);
///
/// Create a dev drive
diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/DevDriveStorageOperator.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/DevDriveStorageOperator.cs
index d284ab138a..f3b806fbe2 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/DevDriveStorageOperator.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/DevDriveStorageOperator.cs
@@ -284,7 +284,7 @@ private HRESULT CreatePartition(string virtDiskPhysicalPath, out uint diskNumber
unsafe
{
partitionLayout.Info.PartitionCount = 1;
- var partitionInfo = &partitionLayout.Info.PartitionEntry._0;
+ var partitionInfo = &partitionLayout.Info.PartitionEntry.e0;
partitionInfo->PartitionStyle = PARTITION_STYLE.PARTITION_STYLE_GPT;
// There are currently no partitions on the disk. Start off the
diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedConfigurationTask.cs b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedConfigurationTask.cs
index bad73c3373..e67df5ed5e 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedConfigurationTask.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedConfigurationTask.cs
@@ -1,12 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using DevHome.SetupFlow.Common.Configuration;
+using DevHome.Services.DesiredStateConfiguration.Contracts;
+using DevHome.Services.DesiredStateConfiguration.Models;
using DevHome.SetupFlow.ElevatedComponent.Helpers;
-using Microsoft.Management.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Serilog;
using Windows.Foundation;
-using Windows.Win32.Foundation;
namespace DevHome.SetupFlow.ElevatedComponent.Tasks;
@@ -14,40 +15,21 @@ public sealed class ElevatedConfigurationTask
{
public IAsyncOperation ApplyConfiguration(string filePath, string content, Guid activityId)
{
+ var logger = LoggerFactory.Create(lb => lb.AddSerilog(dispose: false)).CreateLogger();
return Task.Run(async () =>
{
var taskResult = new ElevatedConfigureTaskResult();
- var log = Log.ForContext("SourceContext", nameof(ElevatedConfigurationTask));
try
{
- var configurationFileHelper = new ConfigurationFileHelper(activityId);
-
- log.Information($"Opening configuration set from file: {filePath}");
- await configurationFileHelper.OpenConfigurationSetAsync(filePath, content);
-
- log.Information("Starting configuration set application");
- var result = await configurationFileHelper.ApplyConfigurationAsync();
- log.Information("Configuration application finished");
+ var dsc = ElevatedComponentOperation.Host.Services.GetRequiredService();
+ var file = DSCFile.CreateVirtual(filePath, content);
+ var result = await dsc.ApplyConfigurationAsync(file, activityId);
taskResult.TaskAttempted = true;
taskResult.TaskSucceeded = result.Succeeded;
taskResult.RebootRequired = result.RequiresReboot;
- taskResult.UnitResults = result.Result.UnitResults.Select(unitResult =>
- {
- unitResult.Unit.Settings.TryGetValue("description", out var descriptionObj);
- return new ElevatedConfigureUnitTaskResult
- {
- Type = unitResult.Unit.Type,
- Id = unitResult.Unit.Identifier,
- UnitDescription = descriptionObj?.ToString() ?? string.Empty,
- Intent = unitResult.Unit.Intent.ToString(),
- IsSkipped = unitResult.State == ConfigurationUnitState.Skipped,
- HResult = unitResult.ResultInformation?.ResultCode?.HResult ?? HRESULT.S_OK,
- ResultSource = (int)(unitResult.ResultInformation?.ResultSource ?? ConfigurationUnitResultSource.None),
- ErrorDescription = unitResult.ResultInformation?.Description,
- };
- }).ToList();
+ taskResult.UnitResults = result.UnitResults.Select(unitResult => new ElevatedConfigureUnitTaskResult(unitResult)).ToList();
if (result.ResultException != null)
{
@@ -56,7 +38,7 @@ public IAsyncOperation ApplyConfiguration(string fi
}
catch (Exception e)
{
- log.Error(e, $"Failed to apply configuration.");
+ logger.LogError(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 a90e148f7e..8b97a3a9d0 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedInstallTask.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedComponent/Tasks/ElevatedInstallTask.cs
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using DevHome.SetupFlow.Common.Extensions;
-using DevHome.SetupFlow.Common.WindowsPackageManager;
+using DevHome.Services.WindowsPackageManager.Contracts;
+using DevHome.Services.WindowsPackageManager.Exceptions;
+using DevHome.Services.WindowsPackageManager.Models;
using DevHome.SetupFlow.ElevatedComponent.Helpers;
-using Microsoft.Management.Deployment;
+using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Windows.Foundation;
-using Windows.Win32.Foundation;
namespace DevHome.SetupFlow.ElevatedComponent.Tasks;
@@ -30,125 +30,41 @@ namespace DevHome.SetupFlow.ElevatedComponent.Tasks;
public sealed class ElevatedInstallTask
{
private readonly ILogger _log = Log.ForContext("SourceContext", nameof(ElevatedInstallTask));
- private readonly WindowsPackageManagerFactory _wingetFactory = new WindowsPackageManagerManualActivationFactory();
///
/// Installs a package given its ID and the ID of the catalog it comes from.
///
- public IAsyncOperation InstallPackage(string packageId, string catalogName, string version)
+ public IAsyncOperation InstallPackage(string packageId, string catalogName, string version, Guid activityId)
{
return Task.Run(async () =>
{
var result = new ElevatedInstallTaskResult();
try
{
- _log.Information($"Elevated install requested for package [{packageId}] from catalog [{catalogName}]");
-
- var packageManager = _wingetFactory.CreatePackageManager();
-
- _log.Information($"Connecting to catalog [{catalogName}]");
- var catalogReference = packageManager.GetPackageCatalogByName(catalogName);
- var connectResult = await catalogReference.ConnectAsync();
- if (connectResult.Status != ConnectResultStatus.Ok)
- {
- _log.Error($"Failed to connect to the catalog [{catalogName}] with status {connectResult.Status}");
- result.TaskAttempted = false;
- return result;
- }
-
- _log.Information($"Finding package [{packageId}] in catalog");
- var findOptions = CreateFindOptionsForPackageId(packageId);
- var findResult = connectResult.PackageCatalog.FindPackages(findOptions);
- if (findResult.Status != FindPackagesResultStatus.Ok
- || findResult.Matches.Count < 1
- || findResult.WasLimitExceeded)
- {
- _log.Error($"Failed to find package. Status={findResult.Status}, Matches Count={findResult.Matches.Count}, LimitReached={findResult.WasLimitExceeded}");
- result.TaskAttempted = false;
- return result;
- }
-
- var packageToInstall = findResult.Matches[0].CatalogPackage;
-
- var installOptions = _wingetFactory.CreateInstallOptions();
- installOptions.PackageInstallMode = PackageInstallMode.Silent;
- if (!string.IsNullOrWhiteSpace(version))
- {
- installOptions.PackageVersionId = FindVersionOrThrow(result, packageToInstall, version);
- }
- else
- {
- _log.Information($"Install version not specified. Falling back to default install version {packageToInstall.DefaultInstallVersion.Version}");
- }
-
- _log.Information($"Initiating install of package {packageId}");
- var installResult = await packageManager.InstallPackageAsync(packageToInstall, installOptions);
- var extendedErrorCode = installResult.ExtendedErrorCode?.HResult ?? HRESULT.S_OK;
-
- // Contract version 4
- var installErrorCode = installResult.GetValueOrDefault(res => res.InstallerErrorCode, HRESULT.S_OK);
-
- _log.Information($"Install finished. Status={installResult.Status}, InstallerErrorCode={installErrorCode}, ExtendedErrorCode={extendedErrorCode}, RebootRequired={installResult.RebootRequired}");
+ var winget = ElevatedComponentOperation.Host.Services.GetRequiredService();
+ var installResult = await winget.InstallPackageAsync(new WinGetPackageUri(catalogName, packageId, new(version)), activityId);
result.TaskAttempted = true;
- result.TaskSucceeded = installResult.Status == InstallResultStatus.Ok;
result.RebootRequired = installResult.RebootRequired;
- result.Status = (int)installResult.Status;
- result.ExtendedErrorCode = extendedErrorCode;
- result.InstallerErrorCode = installErrorCode;
- return result;
+ // Set the extended error code in case a reboot is required
+ result.ExtendedErrorCode = installResult.ExtendedErrorCode;
+ result.TaskSucceeded = true;
+ }
+ catch (InstallPackageException e)
+ {
+ result.Status = (int)e.Status;
+ result.ExtendedErrorCode = e.ExtendedErrorCode;
+ result.InstallerErrorCode = e.InstallerErrorCode;
+ result.TaskSucceeded = false;
+ _log.Error($"Failed to install package with status {e.Status} and installer error code {e.InstallerErrorCode}");
}
catch (Exception e)
{
- _log.Error(e, "Elevated app install failed.");
result.TaskSucceeded = false;
+ _log.Error(e, $"Exception thrown while installing package.");
}
return result;
}).AsAsyncOperation();
}
-
- ///
- /// Creates a that can be used to find
- /// the package with the given ID.
- ///
- private FindPackagesOptions CreateFindOptionsForPackageId(string packageId)
- {
- var matchFilter = _wingetFactory.CreatePackageMatchFilter();
- matchFilter.Field = PackageMatchField.Id;
- matchFilter.Option = PackageFieldMatchOption.Equals;
- matchFilter.Value = packageId;
-
- var findOptions = _wingetFactory.CreateFindPackagesOptions();
- findOptions.Selectors.Add(matchFilter);
- findOptions.ResultLimit = 1;
-
- return findOptions;
- }
-
- ///
- /// Find a specific version in the list of available versions for a package.
- ///
- /// Target package
- /// Version to find
- /// Specified version
- /// Exception thrown if the specified version was not found
- private PackageVersionId FindVersionOrThrow(ElevatedInstallTaskResult result, CatalogPackage package, string version)
- {
- // Find the version in the list of available versions
- for (var i = 0; i < package.AvailableVersions.Count; i++)
- {
- if (package.AvailableVersions[i].Version == version)
- {
- return package.AvailableVersions[i];
- }
- }
-
- var installErrorInvalidParameter = unchecked((int)0x8A150112);
- result.Status = (int)InstallResultStatus.InvalidOptions;
- result.ExtendedErrorCode = installErrorInvalidParameter;
- var message = $"Specified install version was not found {version}.";
- _log.Error(message);
- throw new ArgumentException(message);
- }
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow.ElevatedServer/DevHome.SetupFlow.ElevatedServer.csproj b/tools/SetupFlow/DevHome.SetupFlow.ElevatedServer/DevHome.SetupFlow.ElevatedServer.csproj
index 0102280101..5d6f4903f2 100644
--- a/tools/SetupFlow/DevHome.SetupFlow.ElevatedServer/DevHome.SetupFlow.ElevatedServer.csproj
+++ b/tools/SetupFlow/DevHome.SetupFlow.ElevatedServer/DevHome.SetupFlow.ElevatedServer.csproj
@@ -15,11 +15,12 @@
enable
enable
DevHome.SetupFlow.ElevatedServer.Program
+ Debug;Release;Debug_FailFast
-
+
-
+
+
-
+
-
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
-
+
+
-
-
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
+ Text="{x:Bind AddRepoViewModel.FolderPickerViewModel.CloneLocationAlias, Mode=TwoWay}"
+ Visibility="{x:Bind AddRepoViewModel.FolderPickerViewModel.InDevDriveScenario, Mode=OneWay}">
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
+
+ Grid.Row="2"
+ Margin="0,10,0,10"
+ ColumnSpacing="10"
+ Visibility="{x:Bind AddRepoViewModel.EditDevDriveViewModel.ShowDevDriveInformation, Mode=OneWay}">
-
-
+
+
-
-
+
+
+ x:Name="NewDevDriveComboBox"
+ x:Uid="NewDevDriveComboBox"
+ Grid.Row="0"
+ Grid.Column="0"
+ IsChecked="{x:Bind AddRepoViewModel.EditDevDriveViewModel.IsDevDriveCheckboxChecked, Mode=TwoWay}"
+ IsEnabled="{x:Bind AddRepoViewModel.EditDevDriveViewModel.IsDevDriveCheckboxEnabled}">
-
+
-
+
+ x:Uid="DevDriveDefaultDriveCheckBoxError"
+ Grid.Row="1"
+ Grid.Column="0"
+ Grid.ColumnSpan="2"
+ Margin="0,5,0,0"
+ HorizontalAlignment="Stretch"
+ IsClosable="False"
+ IsOpen="True"
+ Severity="Error"
+ Visibility="{x:Bind AddRepoViewModel.EditDevDriveViewModel.DevDriveValidationError, Mode=OneWay}" />
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs
index df3aa6379c..3cee29eaa7 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml.cs
@@ -195,8 +195,10 @@ private void FilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
private void FilterSuggestions(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
- sender.ItemsSource = _searchFieldsAndValues[sender.Header.ToString()].Where(x => x.Contains(sender.Text));
- return;
+ if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
+ {
+ sender.ItemsSource = _searchFieldsAndValues[sender.Header.ToString()].Where(x => x.Contains(sender.Text));
+ }
}
private async void SwitchToSearchPage(object sender, RoutedEventArgs e)
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/CloneRepoSummaryInformationView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/CloneRepoSummaryInformationView.xaml.cs
index 03a69cd717..82a9ed8539 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/CloneRepoSummaryInformationView.xaml.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/CloneRepoSummaryInformationView.xaml.cs
@@ -1,21 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices.WindowsRuntime;
using DevHome.SetupFlow.ViewModels;
-using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Controls.Primitives;
-using Microsoft.UI.Xaml.Data;
-using Microsoft.UI.Xaml.Input;
-using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Navigation;
-using Windows.Foundation;
-using Windows.Foundation.Collections;
namespace DevHome.SetupFlow.Views;
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/ConfigurationFileView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/ConfigurationFileView.xaml
index 471a3e987f..a63ddfd4b1 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/ConfigurationFileView.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/ConfigurationFileView.xaml
@@ -50,7 +50,6 @@
+
10
0,0,0,5
@@ -146,7 +148,7 @@
-
+
@@ -155,7 +157,7 @@
-
+
@@ -176,19 +178,19 @@
-
+
-
+
-
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs
index 523c65be4a..9adcf6d6e2 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/CreateEnvironmentReviewView.xaml.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
using AdaptiveCards.Rendering.WinUI3;
using CommunityToolkit.Mvvm.Messaging;
using DevHome.SetupFlow.Models.Environments;
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs
index 7a24c5c1d2..1370e3fc68 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/EnvironmentCreationOptionsView.xaml.cs
@@ -3,10 +3,8 @@
using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
using AdaptiveCards.Rendering.WinUI3;
using CommunityToolkit.Mvvm.Messaging;
-using CommunityToolkit.WinUI;
using DevHome.SetupFlow.Models.Environments;
using DevHome.SetupFlow.ViewModels.Environments;
using Microsoft.UI.Xaml;
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml
index 1fb724c57c..f558c3d559 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Environments/SelectEnvironmentProviderView.xaml
@@ -18,6 +18,14 @@
+
+ 40
+
-
+
+
@@ -19,11 +19,14 @@
-
-
@@ -34,184 +37,253 @@
-
-
+
+
-
+
-
-
-
+
+
+
+ RowSpacing="20">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
-
+ Margin="0,0,0,10">
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
+ VerticalScrollBarVisibility="Auto">
-
-
+
+
-
+
-
+
-
-
+
+
-
+
+ IsTextSelectionEnabled="True"
+ Text="{x:Bind MessageToShow, Mode=OneWay}"
+ TextWrapping="Wrap" />
-
+
-
+
-
-
+
+
-
-
+
+
-
+
+ IsTextSelectionEnabled="True"
+ Text="{x:Bind MessageToShow, Mode=OneWay}"
+ TextWrapping="Wrap" />
-
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+ Margin="0,0,0,10"
+ Padding="20"
+ Background="{ThemeResource CardBackgroundFillColorDefault}"
+ CornerRadius="10"
+ Visibility="{x:Bind PrimaryMessage, Converter={StaticResource EmptyObjectToObjectConverter}}">
+
-
+ Content="{x:Bind ExtensionAdaptiveCardPanel}"
+ CornerRadius="10"
+ Unloaded="ContentControl_Unloaded" />
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/QuickstartPlaygroundView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/QuickstartPlaygroundView.xaml.cs
index 1d714d31e4..fce41c3142 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/QuickstartPlaygroundView.xaml.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/QuickstartPlaygroundView.xaml.cs
@@ -188,7 +188,7 @@ public async Task ShowExtensionInitializationUI()
{
if (ViewModel.ActiveQuickstartSelection is not null)
{
- var adaptiveCardSessionResult = ViewModel.ActiveQuickstartSelection.CreateAdaptiveCardSessionForExtensionInitialization();
+ var adaptiveCardSessionResult = ViewModel.ActiveQuickstartSelection.CreateAdaptiveCardSessionForExtensionInitialization(ViewModel.ActivityId);
await ShowAdaptiveCardOnContentDialog(adaptiveCardSessionResult);
}
}
@@ -212,7 +212,7 @@ public async Task ShowProgressAdaptiveCard()
{
DispatcherQueue.TryEnqueue(() =>
{
- ProgressOutputScrollViewer.ScrollToVerticalOffset(ProgressOutputScrollViewer.ScrollableHeight);
+ ProgressOutputScrollViewer.ScrollToVerticalOffset(ProgressOutputScrollViewer.ScrollableHeight);
});
};
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml
index 9d2d664709..e61ba5780d 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml
@@ -1,12 +1,12 @@
-
-
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml.cs
index 18b3b778a8..ed27fdb0f5 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupFlowPage.xaml.cs
@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using DevHome.Common;
using DevHome.Common.Extensions;
+using DevHome.Common.Views;
using DevHome.SetupFlow.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -12,8 +12,6 @@ namespace DevHome.SetupFlow.Views;
public partial class SetupFlowPage : ToolPage
{
- public override string ShortName => "SetupFlow";
-
public SetupFlowViewModel ViewModel { get; }
public SetupFlowPage()
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppInstallationNotes.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppInstallationNotes.xaml
new file mode 100644
index 0000000000..0b0cf8df65
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppInstallationNotes.xaml
@@ -0,0 +1,75 @@
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppInstallationNotes.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppInstallationNotes.xaml.cs
new file mode 100644
index 0000000000..d49adffe3a
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppInstallationNotes.xaml.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using DevHome.SetupFlow.ViewModels;
+using DevHome.SetupFlow.Windows;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryAppInstallationNotes : UserControl
+{
+ public SummaryAppInstallationNotes()
+ {
+ this.InitializeComponent();
+ }
+
+ private void ViewAllButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ // When installation notes' "view all" button is clicked, open a new window with the full text
+ if (sender is Button viewAllButton && viewAllButton.Tag is PackageViewModel package)
+ {
+ var window = new InstallationNotesWindow(package.PackageTitle, package.InstallationNotes);
+ window.CenterOnWindow();
+ window.Activate();
+ }
+ }
+
+ private void InstallationNotes_IsTextTrimmedChanged(TextBlock sender, IsTextTrimmedChangedEventArgs args)
+ {
+ // Show 'view all' button if installation notes text is trimmed, otherwise hide it.
+ if (sender?.Tag is Button viewAllButton)
+ {
+ viewAllButton.Visibility = sender.IsTextTrimmed ? Visibility.Visible : Visibility.Collapsed;
+ }
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppsDownloadedReposCloned.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppsDownloadedReposCloned.xaml
new file mode 100644
index 0000000000..0e09e5e3b8
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppsDownloadedReposCloned.xaml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppsDownloadedReposCloned.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppsDownloadedReposCloned.xaml.cs
new file mode 100644
index 0000000000..851451b071
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryAppsDownloadedReposCloned.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryAppsDownloadedReposCloned : UserControl
+{
+ public SummaryAppsDownloadedReposCloned()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryConfigurationFileResults.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryConfigurationFileResults.xaml
new file mode 100644
index 0000000000..47bd70a11b
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryConfigurationFileResults.xaml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryConfigurationFileResults.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryConfigurationFileResults.xaml.cs
new file mode 100644
index 0000000000..6221b6f470
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryConfigurationFileResults.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryConfigurationFileResults : UserControl
+{
+ public SummaryConfigurationFileResults()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryFailedTasks.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryFailedTasks.xaml
new file mode 100644
index 0000000000..9954347b46
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryFailedTasks.xaml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryFailedTasks.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryFailedTasks.xaml.cs
new file mode 100644
index 0000000000..86b7e77f44
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryFailedTasks.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryFailedTasks : UserControl
+{
+ public SummaryFailedTasks()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaConfiguration.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaConfiguration.xaml
new file mode 100644
index 0000000000..d308aa8707
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaConfiguration.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaConfiguration.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaConfiguration.xaml.cs
new file mode 100644
index 0000000000..9de5e51c12
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaConfiguration.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryIntroViaConfiguration : UserControl
+{
+ public SummaryIntroViaConfiguration()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaNonConfigurationFlow.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaNonConfigurationFlow.xaml
new file mode 100644
index 0000000000..fd2d558caa
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaNonConfigurationFlow.xaml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaNonConfigurationFlow.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaNonConfigurationFlow.xaml.cs
new file mode 100644
index 0000000000..ec581e3ba2
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryIntroViaNonConfigurationFlow.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryIntroViaNonConfigurationFlow : UserControl
+{
+ public SummaryIntroViaNonConfigurationFlow()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNeedsRestart.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNeedsRestart.xaml
new file mode 100644
index 0000000000..8db69e860f
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNeedsRestart.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNeedsRestart.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNeedsRestart.xaml.cs
new file mode 100644
index 0000000000..1dac0beb6a
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNeedsRestart.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryNeedsRestart : UserControl
+{
+ public SummaryNeedsRestart()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNextSteps.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNextSteps.xaml
new file mode 100644
index 0000000000..4f6ccd335f
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNextSteps.xaml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNextSteps.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNextSteps.xaml.cs
new file mode 100644
index 0000000000..724c09aaf4
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryNextSteps.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryNextSteps : UserControl
+{
+ public SummaryNextSteps()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryShowAppsAndRepos.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryShowAppsAndRepos.xaml
new file mode 100644
index 0000000000..288d0a8a66
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryShowAppsAndRepos.xaml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryShowAppsAndRepos.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryShowAppsAndRepos.xaml.cs
new file mode 100644
index 0000000000..7ffee33720
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryShowAppsAndRepos.xaml.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using AdaptiveCards.Rendering.WinUI3;
+using CommunityToolkit.Mvvm.Messaging;
+using DevHome.SetupFlow.Models.Environments;
+using DevHome.SetupFlow.ViewModels;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryShowAppsAndRepos : UserControl, IRecipient
+{
+ public SummaryShowAppsAndRepos()
+ {
+ this.InitializeComponent();
+ WeakReferenceMessenger.Default.Register(this);
+ }
+
+ ///
+ /// 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 SummaryViewModel
+ if (message.Value.CurrentSetupFlowViewModel is SummaryViewModel)
+ {
+ AddAdaptiveCardToUI(message.Value.RenderedAdaptiveCard);
+ }
+ }
+
+ ///
+ /// Request the adaptive cad from the EnvironmentCreationOptionsViewModel object when we're in the environment
+ /// creation flow.
+ ///
+ private void ViewLoaded(object sender, RoutedEventArgs e)
+ {
+ var message = WeakReferenceMessenger.Default.Send();
+ if (!message.HasReceivedResponse)
+ {
+ return;
+ }
+
+ AddAdaptiveCardToUI(message.Response);
+ }
+
+ private void ViewUnloaded(object sender, RoutedEventArgs e)
+ {
+ AdaptiveCardGrid.Children.Clear();
+ WeakReferenceMessenger.Default.UnregisterAll(this);
+ }
+
+ private void AddAdaptiveCardToUI(RenderedAdaptiveCard renderedAdaptiveCard)
+ {
+ var frameworkElement = renderedAdaptiveCard?.FrameworkElement;
+ if (frameworkElement == null)
+ {
+ return;
+ }
+
+ AdaptiveCardGrid.Children.Clear();
+ AdaptiveCardGrid.Children.Add(frameworkElement);
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineAppsDownloadedReposCloned.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineAppsDownloadedReposCloned.xaml
new file mode 100644
index 0000000000..9305e522ec
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineAppsDownloadedReposCloned.xaml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineAppsDownloadedReposCloned.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineAppsDownloadedReposCloned.xaml.cs
new file mode 100644
index 0000000000..21e0e61bf0
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineAppsDownloadedReposCloned.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryTargetMachineAppsDownloadedReposCloned : UserControl
+{
+ public SummaryTargetMachineAppsDownloadedReposCloned()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineShowAppsAndRepos.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineShowAppsAndRepos.xaml
new file mode 100644
index 0000000000..d085b09382
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineShowAppsAndRepos.xaml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineShowAppsAndRepos.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineShowAppsAndRepos.xaml.cs
new file mode 100644
index 0000000000..c26057ccef
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineShowAppsAndRepos.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryTargetMachineShowAppsAndRepos : UserControl
+{
+ public SummaryTargetMachineShowAppsAndRepos()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineWithErrors.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineWithErrors.xaml
new file mode 100644
index 0000000000..38cda74b2d
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineWithErrors.xaml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineWithErrors.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineWithErrors.xaml.cs
new file mode 100644
index 0000000000..118e8b30d6
--- /dev/null
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/Summary/SummaryTargetMachineWithErrors.xaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.SetupFlow.Views.Summary;
+
+public sealed partial class SummaryTargetMachineWithErrors : UserControl
+{
+ public SummaryTargetMachineWithErrors()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml
index fb2f0c74e8..b87ed7ba67 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml
@@ -1,735 +1,178 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
0, 1, 0, 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ VerticalScrollMode="Enabled">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Installation Notes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml.cs
index 095cfd0557..1ff582b39c 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml.cs
@@ -1,90 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using AdaptiveCards.Rendering.WinUI3;
-using CommunityToolkit.Mvvm.Messaging;
-using DevHome.SetupFlow.Models.Environments;
+using CommunityToolkit.WinUI;
+using DevHome.Common.Extensions;
using DevHome.SetupFlow.ViewModels;
-using DevHome.SetupFlow.Windows;
+using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Windows.UI.ViewManagement;
namespace DevHome.SetupFlow.Views;
-public sealed partial class SummaryView : UserControl, IRecipient
+public sealed partial class SummaryView : UserControl
{
- public SummaryView()
- {
- this.InitializeComponent();
- WeakReferenceMessenger.Default.Register(this);
- }
+ private readonly UISettings _uiSettings = new();
- public SummaryViewModel ViewModel => (SummaryViewModel)this.DataContext;
+ private const double BaseWidth = 550;
- private void ViewAllButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
- {
- // When installation notes' 'view all' button is clicked, open a new window with the full text
- if (sender is Button viewAllButton && viewAllButton.Tag is PackageViewModel package)
- {
- var window = new InstallationNotesWindow(package.PackageTitle, package.InstallationNotes);
- window.CenterOnWindow();
- window.Activate();
- }
- }
-
- private void InstallationNotes_IsTextTrimmedChanged(TextBlock sender, IsTextTrimmedChangedEventArgs args)
+ public SummaryView()
{
- // Show 'view all' button if installation notes text is trimmed, otherwise hide it.
- if (sender?.Tag is Button viewAllButton)
- {
- viewAllButton.Visibility = sender.IsTextTrimmed ? Visibility.Visible : Visibility.Collapsed;
- }
- }
+ this.InitializeComponent();
- private void ViewUnloaded(object sender, RoutedEventArgs e)
- {
- AdaptiveCardGrid.Children.Clear();
- WeakReferenceMessenger.Default.UnregisterAll(this);
+ // Setting MinWidth on the UniformGrid prevents text clipping in "Next Steps".
+ // Setting MinWidth on the "Left Side" stack panel keeps the MinWidth but still clips
+ // when the window becomes too small.
+ var textScale = _uiSettings.TextScaleFactor;
+ _uiSettings.TextScaleFactorChanged += HandleTextScaleFactorChanged;
+ ParentUniformGrid.MinWidth = BaseWidth * textScale;
}
- ///
- /// 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 SummaryViewModel
- if (message.Value.CurrentSetupFlowViewModel is SummaryViewModel)
- {
- AddAdaptiveCardToUI(message.Value.RenderedAdaptiveCard);
- }
- }
+ public SummaryViewModel ViewModel => (SummaryViewModel)this.DataContext;
- ///
- /// Request the adaptive cad from the EnvironmentCreationOptionsViewModel object when we're in the environment
- /// creation flow.
- ///
- private void ViewLoaded(object sender, RoutedEventArgs e)
+ private void HandleTextScaleFactorChanged(UISettings sender, object args)
{
- var message = WeakReferenceMessenger.Default.Send();
- if (!message.HasReceivedResponse)
+ Application.Current.GetService().EnqueueAsync(() =>
{
- return;
- }
-
- AddAdaptiveCardToUI(message.Response);
+ var textScale = sender.TextScaleFactor;
+ ParentUniformGrid.MinWidth = BaseWidth * textScale;
+ InvalidateMeasure();
+ });
}
- private void AddAdaptiveCardToUI(RenderedAdaptiveCard renderedAdaptiveCard)
+ private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
- var frameworkElement = renderedAdaptiveCard?.FrameworkElement;
- if (frameworkElement == null)
- {
- return;
- }
-
- AdaptiveCardGrid.Children.Clear();
- AdaptiveCardGrid.Children.Add(frameworkElement);
+ _uiSettings.TextScaleFactorChanged -= HandleTextScaleFactorChanged;
}
}
diff --git a/tools/Utilities/EnvVariablesUtility/DevHome.EnvironmentVariables.csproj b/tools/Utilities/EnvVariablesUtility/DevHome.EnvironmentVariables.csproj
index 3b333597f6..dad3388b29 100644
--- a/tools/Utilities/EnvVariablesUtility/DevHome.EnvironmentVariables.csproj
+++ b/tools/Utilities/EnvVariablesUtility/DevHome.EnvironmentVariables.csproj
@@ -13,6 +13,7 @@
false
DevHome.EnvironmentVariables.pri
false
+ Debug;Release;Debug_FailFast
diff --git a/tools/Utilities/HostsUtility/DevHome.HostsFileEditor.csproj b/tools/Utilities/HostsUtility/DevHome.HostsFileEditor.csproj
index 916c6c1406..37cb174d30 100644
--- a/tools/Utilities/HostsUtility/DevHome.HostsFileEditor.csproj
+++ b/tools/Utilities/HostsUtility/DevHome.HostsFileEditor.csproj
@@ -13,6 +13,7 @@
false
DevHome.HostsFileEditor.pri
false
+ Debug;Release;Debug_FailFast
diff --git a/tools/Utilities/HostsUtility/Helpers/LoggerWrapper.cs b/tools/Utilities/HostsUtility/Helpers/LoggerWrapper.cs
index 8b42fe6ac5..967f37e156 100644
--- a/tools/Utilities/HostsUtility/Helpers/LoggerWrapper.cs
+++ b/tools/Utilities/HostsUtility/Helpers/LoggerWrapper.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System;
-using HostsUILib.Helpers;
using Serilog;
namespace DevHome.HostsFileEditor.Helpers;
diff --git a/tools/Utilities/RegPreviewUtility/DevHome.RegistryPreview.csproj b/tools/Utilities/RegPreviewUtility/DevHome.RegistryPreview.csproj
index f423a1f78d..89337a73d3 100644
--- a/tools/Utilities/RegPreviewUtility/DevHome.RegistryPreview.csproj
+++ b/tools/Utilities/RegPreviewUtility/DevHome.RegistryPreview.csproj
@@ -13,6 +13,7 @@
false
DevHome.RegistryPreview.pri
false
+ Debug;Release;Debug_FailFast
diff --git a/tools/Utilities/UtilitiesTestingScenarios.md b/tools/Utilities/UtilitiesTestingScenarios.md
new file mode 100644
index 0000000000..c84b1f2102
--- /dev/null
+++ b/tools/Utilities/UtilitiesTestingScenarios.md
@@ -0,0 +1,26 @@
+# Utilities Tests
+If your code affects Utilities, please manually verify these scenarios.
+
+## Scenarios
+Please make sure to verify all of these scenarios. These apply to both Windows 10 and Windows 11.
+
+#### Core
+1. User can launch utilities from Utilities page
+1. User can launch utilities from Utilities page as admin
+1. Strings should be localized in all three utilities
+
+#### Hosts File Editor
+1. Hosts File Editor: User should not be allowed to add entry, enable/disable toggles on existing entry in Hosts File Editor if not running as admin
+1. Hosts File Editor: User should be allowed to add entry, enable/disable toggles on existing entry in Hosts File Editor if running as admin
+1. Hosts File Editor: Its Settings should persist and work
+
+#### Registry Preview
+1. Registry Preview: User should be able to open registry file via Open button and visualize registry file
+1. Registry Preview: Notepad should open whenc "edit" button is clicked
+1. Registry Preview: RegEdit should open when "Open Registry Editor" is clicked
+1. Registry Preview: Selecting key from the visualized tree and clicking on "open key" should open that key in RegEdit
+
+#### Environment Variables
+1. Environment Variables: User should not be able to add "system" variable if not running as admin
+1. Environment Variables: User should be able to add/edit user/system environment variable
+1. Environment Variables: Adding/Editing env variable from Windows should show "variabled have been modified, please reload" banner and button in Environment Variables Editor
\ No newline at end of file
diff --git a/tools/Utilities/src/DevHome.Utilities.csproj b/tools/Utilities/src/DevHome.Utilities.csproj
index 2422c288a0..a0527370bb 100644
--- a/tools/Utilities/src/DevHome.Utilities.csproj
+++ b/tools/Utilities/src/DevHome.Utilities.csproj
@@ -5,6 +5,7 @@
x86;x64;arm64
win-x86;win-x64;win-arm64
true
+ Debug;Release;Debug_FailFast
@@ -16,10 +17,6 @@
-
-
-
-
MSBuild:Compile
diff --git a/tools/Utilities/src/Strings/en-us/Resources.resw b/tools/Utilities/src/Strings/en-us/Resources.resw
index c204afd7d7..94e6564af2 100644
--- a/tools/Utilities/src/Strings/en-us/Resources.resw
+++ b/tools/Utilities/src/Strings/en-us/Resources.resw
@@ -1,17 +1,17 @@
-
@@ -131,10 +131,13 @@
Hosts File Editor
;Locked={"Hosts"}
-
+
+ Learn more
+
+
Launch
-
+
Launch
@@ -143,9 +146,11 @@
An application for gaining deeper insights into your applications.
+ A description of the Project Ironsides utility
Project Ironsides
+ {Locked="IronSides"}
Edit and visualize Windows Registry files.
diff --git a/tools/Utilities/src/TelemetryEvents/UtilitiesLaunchEvent.cs b/tools/Utilities/src/TelemetryEvents/UtilitiesLaunchEvent.cs
index 96a18a0bdc..3348618bb6 100644
--- a/tools/Utilities/src/TelemetryEvents/UtilitiesLaunchEvent.cs
+++ b/tools/Utilities/src/TelemetryEvents/UtilitiesLaunchEvent.cs
@@ -29,14 +29,17 @@ public enum Phase
public Phase LaunchPhase { get; }
+ public int ErrorCode { get; }
+
public string ErrorString { get; }
- public UtilitiesLaunchEvent(Guid activityId, string utilityName, bool launchedAsAdmin, Phase phase, string errorString = "")
+ public UtilitiesLaunchEvent(Guid activityId, string utilityName, bool launchedAsAdmin, Phase phase, int errorCode = 0, string errorString = "")
{
ActivityId = activityId.ToString();
UtilityName = utilityName;
LaunchedAsAdmin = launchedAsAdmin;
LaunchPhase = phase;
+ ErrorCode = errorCode;
ErrorString = errorString;
}
diff --git a/tools/Utilities/src/ViewModels/UtilityViewModel.cs b/tools/Utilities/src/ViewModels/UtilityViewModel.cs
index 3819deca5f..079ee3a6c4 100644
--- a/tools/Utilities/src/ViewModels/UtilityViewModel.cs
+++ b/tools/Utilities/src/ViewModels/UtilityViewModel.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -17,10 +18,9 @@ namespace DevHome.Utilities.ViewModels;
public partial class UtilityViewModel : ObservableObject
{
#nullable enable
-
private readonly ILogger _log = Log.ForContext("SourceContext", nameof(UtilityViewModel));
- private readonly IExperimentationService? experimentationService;
- private readonly string? experimentalFeature;
+ private readonly IExperimentationService? _experimentationService;
+ private readonly string? _experimentalFeature;
private readonly string _exeName;
#nullable disable
@@ -29,9 +29,9 @@ public bool Visible
get
{
// Query if there is an experimental feature and return its enabled value
- if (experimentalFeature is not null)
+ if (_experimentalFeature is not null)
{
- var isExperimentalFeatureEnabled = experimentationService?.IsFeatureEnabled(experimentalFeature) ?? true;
+ var isExperimentalFeatureEnabled = _experimentationService?.IsFeatureEnabled(_experimentalFeature) ?? true;
return isExperimentalFeatureEnabled;
}
@@ -60,9 +60,9 @@ public bool Visible
#nullable enable
public UtilityViewModel(string exeName, IExperimentationService? experimentationService = null, string? experimentalFeature = null)
{
- this._exeName = exeName;
- this.experimentationService = experimentationService;
- this.experimentalFeature = experimentalFeature;
+ _exeName = exeName;
+ _experimentationService = experimentationService;
+ _experimentalFeature = experimentalFeature;
LaunchCommand = new RelayCommand(Launch);
_log.Information($"UtilityViewModel created for Title: {Title}, exe: {exeName}");
}
@@ -89,14 +89,20 @@ private void Launch()
if (process is null)
{
_log.Error($"Failed to start process {_exeName}");
- TelemetryFactory.Get().Log("Utilities_UtilitiesLaunchEvent", LogLevel.Critical, new UtilitiesLaunchEvent(activityId, Title, LaunchAsAdmin, UtilitiesLaunchEvent.Phase.Error));
+ TelemetryFactory.Get().Log("Utilities_UtilitiesLaunchEvent", LogLevel.Critical, new UtilitiesLaunchEvent(activityId, Title, LaunchAsAdmin, UtilitiesLaunchEvent.Phase.Error, -2147467259 /* E_FAIL */));
throw new InvalidOperationException("Failed to start process");
}
}
catch (Exception ex)
{
+ int errorCode = ex.HResult;
+ if (ex.GetType() == typeof(Win32Exception))
+ {
+ errorCode = ((Win32Exception)ex).NativeErrorCode;
+ }
+
_log.Error(ex, $"Failed to start process {_exeName}");
- TelemetryFactory.Get().Log("Utilities_UtilitiesLaunchEvent", LogLevel.Critical, new UtilitiesLaunchEvent(activityId, Title, LaunchAsAdmin, UtilitiesLaunchEvent.Phase.Error, ex.ToString()));
+ TelemetryFactory.Get().Log("Utilities_UtilitiesLaunchEvent", LogLevel.Critical, new UtilitiesLaunchEvent(activityId, Title, LaunchAsAdmin, UtilitiesLaunchEvent.Phase.Error, errorCode, ex.ToString()));
}
TelemetryFactory.Get().Log("Utilities_UtilitiesLaunchEvent", LogLevel.Critical, new UtilitiesLaunchEvent(activityId, Title, LaunchAsAdmin, UtilitiesLaunchEvent.Phase.Complete), null);
diff --git a/tools/Utilities/src/Views/UtilitiesMainPageView.xaml b/tools/Utilities/src/Views/UtilitiesMainPageView.xaml
index f08da95042..8d8b32bed9 100644
--- a/tools/Utilities/src/Views/UtilitiesMainPageView.xaml
+++ b/tools/Utilities/src/Views/UtilitiesMainPageView.xaml
@@ -1,5 +1,5 @@
-
@@ -41,4 +41,4 @@
-
+
diff --git a/tools/Utilities/src/Views/UtilitiesMainPageView.xaml.cs b/tools/Utilities/src/Views/UtilitiesMainPageView.xaml.cs
index b861e85dea..78d6dc03ef 100644
--- a/tools/Utilities/src/Views/UtilitiesMainPageView.xaml.cs
+++ b/tools/Utilities/src/Views/UtilitiesMainPageView.xaml.cs
@@ -2,8 +2,8 @@
// Licensed under the MIT License.
using System.Windows.Input;
-using DevHome.Common;
using DevHome.Common.Extensions;
+using DevHome.Common.Views;
using DevHome.Utilities.ViewModels;
using Microsoft.UI.Xaml;
@@ -11,8 +11,6 @@ namespace DevHome.Utilities.Views;
public sealed partial class UtilitiesMainPageView : ToolPage
{
- public override string ShortName => "Utilities";
-
public UtilitiesMainPageViewModel ViewModel { get; }
public ICommand OpenNewWindowCommand { get; private set; }
diff --git a/tools/Utilities/src/Views/UtilityView.xaml b/tools/Utilities/src/Views/UtilityView.xaml
index d0a6950289..e047e589ed 100644
--- a/tools/Utilities/src/Views/UtilityView.xaml
+++ b/tools/Utilities/src/Views/UtilityView.xaml
@@ -3,18 +3,18 @@
x:Class="DevHome.Utilities.Views.UtilityView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- mc:Ignorable="d"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
+ Height="64" />
+ Grid.Row="1">
+ Text="{x:Bind ViewModel.Description}" />
+ HorizontalAlignment="Left" />
+ AutomationProperties.AutomationId="AdminToggleAutomationId" />
-
-
-
-
diff --git a/uitest/DevHome.UITest.csproj b/uitest/DevHome.UITest.csproj
index 1c0fd0ffa9..af2829706c 100644
--- a/uitest/DevHome.UITest.csproj
+++ b/uitest/DevHome.UITest.csproj
@@ -10,6 +10,7 @@
true
resources.pri
$(MSBuildProjectDirectory)\Test.runsettings
+ Debug;Release;Debug_FailFast
diff --git a/uitest/Dialogs/AddWidgetDialog.cs b/uitest/Dialogs/AddWidgetDialog.cs
index eaf4a44513..47f546c849 100644
--- a/uitest/Dialogs/AddWidgetDialog.cs
+++ b/uitest/Dialogs/AddWidgetDialog.cs
@@ -4,7 +4,6 @@
using System.Diagnostics;
using DevHome.UITest.Extensions;
using DevHome.UITest.Pages;
-using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
namespace DevHome.UITest.Dialogs;
diff --git a/uitest/IntroducingTest.cs b/uitest/IntroducingTest.cs
index f60554a5ae..33ca2e8462 100644
--- a/uitest/IntroducingTest.cs
+++ b/uitest/IntroducingTest.cs
@@ -2,8 +2,6 @@
// Licensed under the MIT License.
using DevHome.UITest.Common;
-using DevHome.UITest.Dialogs;
-using DevHome.UITest.Pages;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DevHome.Tests.UITest;
diff --git a/uitest/Pages/ExtensionsPage.cs b/uitest/Pages/ExtensionsPage.cs
index 82d213df97..4cad3120a6 100644
--- a/uitest/Pages/ExtensionsPage.cs
+++ b/uitest/Pages/ExtensionsPage.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Diagnostics;
using OpenQA.Selenium.Appium.Windows;
namespace DevHome.UITest.Pages;
diff --git a/uitest/Pages/PreferencesPage.cs b/uitest/Pages/PreferencesPage.cs
index 95673b0247..793ff614f3 100644
--- a/uitest/Pages/PreferencesPage.cs
+++ b/uitest/Pages/PreferencesPage.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System.Diagnostics;
-using DevHome.UITest.Common;
using DevHome.UITest.Extensions;
using OpenQA.Selenium.Appium.Windows;
diff --git a/uitest/Pages/SettingsAccountsPage.cs b/uitest/Pages/SettingsAccountsPage.cs
index cb8af1be0c..642e67fcf3 100644
--- a/uitest/Pages/SettingsAccountsPage.cs
+++ b/uitest/Pages/SettingsAccountsPage.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Diagnostics;
using OpenQA.Selenium.Appium.Windows;
namespace DevHome.UITest.Pages;
diff --git a/uitest/Pages/SettingsPage.cs b/uitest/Pages/SettingsPage.cs
index bd37632617..56ccaea34e 100644
--- a/uitest/Pages/SettingsPage.cs
+++ b/uitest/Pages/SettingsPage.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System.Diagnostics;
-using DevHome.UITest.Common;
using OpenQA.Selenium.Appium.Windows;
namespace DevHome.UITest.Pages;
diff --git a/uitest/Pages/UtilitiesPage.cs b/uitest/Pages/UtilitiesPage.cs
index a7ae2170d4..b321d890b2 100644
--- a/uitest/Pages/UtilitiesPage.cs
+++ b/uitest/Pages/UtilitiesPage.cs
@@ -6,7 +6,7 @@
using System.Security.Principal;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Win32.SafeHandles;
-using OpenQA.Selenium.Appium.Windows;
+using OpenQA.Selenium.Appium.Windows;
using Windows.Win32;
namespace DevHome.UITest.Pages;
diff --git a/uitest/SettingsTest.cs b/uitest/SettingsTest.cs
index b77d059fbb..3ec266c57f 100644
--- a/uitest/SettingsTest.cs
+++ b/uitest/SettingsTest.cs
@@ -2,10 +2,7 @@
// Licensed under the MIT License.
using System.Drawing;
-using System.Formats.Tar;
using DevHome.UITest.Common;
-using DevHome.UITest.Dialogs;
-using DevHome.UITest.Pages;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace DevHome.Tests.UITest;