diff --git a/Build.ps1 b/Build.ps1
index 78f6de3eca..50c6e43d6d 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -49,6 +49,23 @@ $env:sdk_version = build\Scripts\CreateBuildInfo.ps1 -Version $VersionOfSDK -IsS
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')
+function Write-XmlDocumentToFile {
+ param (
+ [System.Xml.XmlDocument]$xmlDocument,
+ [string]$filePath
+ )
+
+ $settings = New-Object System.Xml.XmlWriterSettings
+ $settings.Indent = $true
+ $settings.CheckCharacters = $false
+ $settings.NewLineChars = "`r`n"
+
+ $writer = [System.Xml.XmlWriter]::Create($filePath, $settings)
+ $xmlDocument.WriteTo($writer)
+ $writer.Flush()
+ $writer.Close()
+}
+
if ($IsAzurePipelineBuild) {
Copy-Item (Join-Path $env:Build_RootDirectory "build\nuget.config.internal") -Destination (Join-Path $env:Build_RootDirectory "nuget.config")
}
@@ -127,6 +144,11 @@ Try {
$uapExtension = [System.Xml.Linq.XName]::Get("{http://schemas.microsoft.com/appx/manifest/uap/windows10/3}Extension");
$uapAppExtension = [System.Xml.Linq.XName]::Get("{http://schemas.microsoft.com/appx/manifest/uap/windows10/3}AppExtension");
+ # Update C++ version resources and header
+ $cppHeader = (Join-Path $env:Build_RootDirectory "build\cppversion\version.h")
+ $updatebinverpath = (Join-Path $env:Build_RootDirectory "build\scripts\update-binver.ps1")
+ & $updatebinverpath -TargetFile $cppHeader -BuildVersion $env:msix_version
+
# Update the appxmanifest
$appxmanifestPath = (Join-Path $env:Build_RootDirectory "src\Package.appxmanifest")
$appxmanifest = [System.Xml.Linq.XDocument]::Load($appxmanifestPath)
@@ -151,7 +173,7 @@ Try {
}
}
}
- $appxmanifest.Save($appxmanifestPath)
+ Write-XmlDocumentToFile -xmlDocument $appxmanifest -filePath $appxmanifestPath
# This is needed for vcxproj
& $nugetPath restore
@@ -164,6 +186,7 @@ Try {
("DevHome.sln"),
("/p:Platform="+$platform),
("/p:Configuration="+$configuration),
+ ("/p:Version="+$env:msix_version),
("/restore"),
("/binaryLogger:DevHome.$platform.$configuration.binlog"),
("/p:AppxPackageOutput=$appxPackageDir\DevHome-$platform.msix"),
@@ -186,6 +209,11 @@ Try {
}
}
+ # reset version file back to original values
+ $cppHeader = (Join-Path $env:Build_RootDirectory "build\cppversion\version.h")
+ $updatebinverpath = (Join-Path $env:Build_RootDirectory "build\scripts\update-binver.ps1")
+ & $updatebinverpath -TargetFile $cppHeader -BuildVersion "1.0.0.0"
+
# Reset the appxmanifest to prevent unnecessary code changes
$appxmanifest = [System.Xml.Linq.XDocument]::Load($appxmanifestPath)
$appxmanifest.Root.Element($xIdentity).Attribute("Version").Value = "0.0.0.0"
@@ -203,7 +231,7 @@ Try {
}
}
}
- $appxmanifest.Save($appxmanifestPath)
+ Write-XmlDocumentToFile -xmlDocument $appxmanifest -filePath $appxmanifestPath
}
if (($BuildStep -ieq "stubpackages")) {
diff --git a/DevHome.sln b/DevHome.sln
index dac61696af..07620f7594 100644
--- a/DevHome.sln
+++ b/DevHome.sln
@@ -122,6 +122,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevSetupAgent.Test", "exten
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HyperVExtension.HostGuestCommunication", "extensions\HyperVExtension\src\HyperVExtension.HostGuestCommunication\HyperVExtension.HostGuestCommunication.csproj", "{D759CD66-494C-4A00-8075-8B65A9891349}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.PI", "tools\PI\DevHome.PI\DevHome.PI.csproj", "{CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PI", "PI", "{DB3D0F2C-1A7F-44B4-B408-B21A56212985}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Customization", "Customization", "{623998FD-B0A6-4980-95D5-A5072301CA10}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.Customization", "tools\Customization\DevHome.Customization\DevHome.Customization.csproj", "{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C}"
@@ -142,6 +146,10 @@ 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}") = "WindowsSandboxExtension", "WindowsSandboxExtension", "{4ACF917D-B2CC-4CF2-8EE1-0EBBB52A69F0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsSandboxExtension", "extensions\WindowsSandboxExtension\WindowsSandboxExtension.csproj", "{118E20E8-FD8A-40CF-83A5-F912B9187787}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|arm64 = Debug|arm64
@@ -614,6 +622,18 @@ 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|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
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|x64.Build.0 = Debug|x64
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|x86.ActiveCfg = Debug|x86
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Debug|x86.Build.0 = Debug|x86
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|arm64.ActiveCfg = Release|ARM64
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|arm64.Build.0 = Release|ARM64
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725}.Release|x64.ActiveCfg = Release|x64
+ {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|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
@@ -698,6 +718,18 @@ 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
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|arm64.ActiveCfg = Debug|arm64
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|arm64.Build.0 = Debug|arm64
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|x64.ActiveCfg = Debug|x64
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|x64.Build.0 = Debug|x64
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|x86.ActiveCfg = Debug|x86
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Debug|x86.Build.0 = Debug|x86
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|arm64.ActiveCfg = Release|arm64
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|arm64.Build.0 = Release|arm64
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|x64.ActiveCfg = Release|x64
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|x64.Build.0 = Release|x64
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|x86.ActiveCfg = Release|x86
+ {118E20E8-FD8A-40CF-83A5-F912B9187787}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -745,6 +777,8 @@ Global
{F4095FD3-6A3F-490B-966D-E63059612EE6} = {3E3791DF-070D-4ADE-96E8-93D6FBD53953}
{0E05A442-BDC7-43D4-A000-F8C986826716} = {3E3791DF-070D-4ADE-96E8-93D6FBD53953}
{D759CD66-494C-4A00-8075-8B65A9891349} = {81AACED5-CFB5-47A6-AFD6-4625AADCFFA3}
+ {CAAC0CDF-9AB8-4F43-A3EB-38D785AF5725} = {DB3D0F2C-1A7F-44B4-B408-B21A56212985}
+ {DB3D0F2C-1A7F-44B4-B408-B21A56212985} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD}
{623998FD-B0A6-4980-95D5-A5072301CA10} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD}
{AF527EA4-6A24-4BD6-BC6E-A5863DC3489C} = {623998FD-B0A6-4980-95D5-A5072301CA10}
{FAB6FAA7-ADF4-4B65-9831-0C819915E6E1} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD}
@@ -752,8 +786,14 @@ 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}
+ {4ACF917D-B2CC-4CF2-8EE1-0EBBB52A69F0} = {DCAF188B-60C3-4EDB-8049-BAA927FBCD7D}
+ {118E20E8-FD8A-40CF-83A5-F912B9187787} = {4ACF917D-B2CC-4CF2-8EE1-0EBBB52A69F0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {030B5641-B206-46BB-BF71-36FF009088FA}
EndGlobalSection
+ GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ build\cppversion\version.vcxitems*{092ac740-da01-4872-8e93-b9557dad6be5}*SharedItemsImports = 5
+ build\cppversion\version.vcxitems*{60e0fd98-5396-436d-bab7-187a853a5dc6}*SharedItemsImports = 5
+ EndGlobalSection
EndGlobal
diff --git a/Directory.CppBuild.props b/Directory.CppBuild.props
index 280febbe6d..59efed82bd 100644
--- a/Directory.CppBuild.props
+++ b/Directory.CppBuild.props
@@ -5,4 +5,8 @@
$(SolutionDir)tools\bin\$(CppPlatformTarget)\$(Configuration)\
$(CppBaseOutDir)$(MSBuildProjectName)\
+
+
+
+
\ No newline at end of file
diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml
index 7e4a24542c..e55c277094 100644
--- a/build/azure-pipelines.yml
+++ b/build/azure-pipelines.yml
@@ -21,7 +21,7 @@ parameters:
variables:
# MSIXVersion's second part should always be odd to account for stub app's version
- MSIXVersion: '0.1401'
+ MSIXVersion: '0.1501'
VersionOfSDK: '0.600'
solution: '**/DevHome.sln'
appxPackageDir: 'AppxPackages'
@@ -241,46 +241,45 @@ extends:
$(Build.SourcesDirectory)\**\obj\**\*.r2r.ni.pdb
- - task: EsrpCodeSigning@2
- inputs:
- ConnectedServiceName: 'Xlang Code Signing'
- FolderPath: '$(appxPackageDir)\${{ configuration }}'
- Pattern: '*.msix'
- signConfigType: 'inlineSignParams'
- inlineOperation: |
- [
- {
- "keycode": "CP-230012",
- "operationSetCode": "SigntoolvNextSign",
- "parameters": [
- {
- "parameterName": "OpusName",
- "parameterValue": "Microsoft"
- },
- {
- "parameterName": "OpusInfo",
- "parameterValue": "http://www.microsoft.com"
- },
- {
- "parameterName": "PageHash",
- "parameterValue": "/NPH"
- },
- {
- "parameterName": "FileDigest",
- "parameterValue": "/fd sha256"
- },
- {
- "parameterName": "TimeStamp",
- "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
- }
- ],
- "toolName": "signtool.exe",
- "toolVersion": "6.2.9304.0"
- }
- ]
- SessionTimeout: '60'
- MaxConcurrency: '50'
- MaxRetryAttempts: '5'
+ - template: ./build/templates/EsrpSigning-Steps.yml@self
+ parameters:
+ displayName: Submit *.msix to ESRP for code signing
+ inputs:
+ FolderPath: '$(appxPackageDir)\${{ configuration }}'
+ Pattern: '*.msix'
+ UseMinimatch: true
+ signConfigType: inlineSignParams
+ inlineOperation: >-
+ [
+ {
+ "keycode": "CP-230012",
+ "operationSetCode": "SigntoolvNextSign",
+ "parameters": [
+ {
+ "parameterName": "OpusName",
+ "parameterValue": "Microsoft"
+ },
+ {
+ "parameterName": "OpusInfo",
+ "parameterValue": "http://www.microsoft.com"
+ },
+ {
+ "parameterName": "PageHash",
+ "parameterValue": "/NPH"
+ },
+ {
+ "parameterName": "FileDigest",
+ "parameterValue": "/fd sha256"
+ },
+ {
+ "parameterName": "TimeStamp",
+ "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
+ }
+ ],
+ "toolName": "signtool.exe",
+ "toolVersion": "6.2.9304.0"
+ }
+ ]
# Commented out until our implementation is fixed
# - task: AzureKeyVault@1
@@ -394,47 +393,46 @@ extends:
filePath: 'build/scripts/UnbundleStubPackage.ps1'
arguments: -InputPath $(appxPackageDir)\Staging -OutputLocation $(appxPackageDir)\${{ configuration }}\AppxMetadata\Stub
- - task: EsrpCodeSigning@2
- condition: and(eq(variables['BuildingBranch'], 'release'), eq('${{ configuration }}', 'Release'))
- inputs:
- ConnectedServiceName: 'Xlang Code Signing'
- FolderPath: '$(appxPackageDir)\${{ configuration }}\AppxMetadata\Stub'
- Pattern: '*.msix'
- signConfigType: 'inlineSignParams'
- inlineOperation: |
- [
- {
- "keycode": "CP-230012",
- "operationSetCode": "SigntoolvNextSign",
- "parameters": [
- {
- "parameterName": "OpusName",
- "parameterValue": "Microsoft"
- },
- {
- "parameterName": "OpusInfo",
- "parameterValue": "http://www.microsoft.com"
- },
- {
- "parameterName": "PageHash",
- "parameterValue": "/NPH"
- },
- {
- "parameterName": "FileDigest",
- "parameterValue": "/fd sha256"
- },
+ - ${{ if and(eq(variables['BuildingBranch'], 'release'), eq('${{ configuration }}', 'Release')) }}:
+ - template: ./build/templates/EsrpSigning-Steps.yml@self
+ parameters:
+ displayName: Submit *.msix to ESRP for code signing
+ inputs:
+ FolderPath: '$(appxPackageDir)\${{ configuration }}\AppxMetadata\Stub'
+ Pattern: '*.msix'
+ UseMinimatch: true
+ signConfigType: inlineSignParams
+ inlineOperation: |
+ [
{
- "parameterName": "TimeStamp",
- "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
+ "keycode": "CP-230012",
+ "operationSetCode": "SigntoolvNextSign",
+ "parameters": [
+ {
+ "parameterName": "OpusName",
+ "parameterValue": "Microsoft"
+ },
+ {
+ "parameterName": "OpusInfo",
+ "parameterValue": "http://www.microsoft.com"
+ },
+ {
+ "parameterName": "PageHash",
+ "parameterValue": "/NPH"
+ },
+ {
+ "parameterName": "FileDigest",
+ "parameterValue": "/fd sha256"
+ },
+ {
+ "parameterName": "TimeStamp",
+ "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
+ }
+ ],
+ "toolName": "signtool.exe",
+ "toolVersion": "6.2.9304.0"
}
- ],
- "toolName": "signtool.exe",
- "toolVersion": "6.2.9304.0"
- }
- ]
- SessionTimeout: '60'
- MaxConcurrency: '50'
- MaxRetryAttempts: '5'
+ ]
- task: PowerShell@2
displayName: Build MsixBundle
@@ -442,46 +440,45 @@ extends:
filePath: 'Build.ps1'
arguments: -Configuration "${{ configuration }}" -Version $(MSIXVersion) -BuildStep "msixbundle" -IsAzurePipelineBuild
- - task: EsrpCodeSigning@2
- inputs:
- ConnectedServiceName: 'Xlang Code Signing'
- FolderPath: 'AppxBundles\${{ configuration }}'
- Pattern: '*.msixbundle'
- signConfigType: 'inlineSignParams'
- inlineOperation: |
- [
- {
- "keycode": "CP-230012",
- "operationSetCode": "SigntoolvNextSign",
- "parameters": [
- {
- "parameterName": "OpusName",
- "parameterValue": "Microsoft"
- },
- {
- "parameterName": "OpusInfo",
- "parameterValue": "http://www.microsoft.com"
- },
- {
- "parameterName": "PageHash",
- "parameterValue": "/NPH"
- },
- {
- "parameterName": "FileDigest",
- "parameterValue": "/fd sha256"
- },
- {
- "parameterName": "TimeStamp",
- "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
- }
- ],
- "toolName": "signtool.exe",
- "toolVersion": "6.2.9304.0"
- }
- ]
- SessionTimeout: '60'
- MaxConcurrency: '50'
- MaxRetryAttempts: '5'
+ - template: ./build/templates/EsrpSigning-Steps.yml@self
+ parameters:
+ displayName: Submit *.msixbundle to ESRP for code signing
+ inputs:
+ FolderPath: 'AppxBundles\${{ configuration }}'
+ Pattern: '*.msixbundle'
+ UseMinimatch: true
+ signConfigType: inlineSignParams
+ inlineOperation: |
+ [
+ {
+ "keycode": "CP-230012",
+ "operationSetCode": "SigntoolvNextSign",
+ "parameters": [
+ {
+ "parameterName": "OpusName",
+ "parameterValue": "Microsoft"
+ },
+ {
+ "parameterName": "OpusInfo",
+ "parameterValue": "http://www.microsoft.com"
+ },
+ {
+ "parameterName": "PageHash",
+ "parameterValue": "/NPH"
+ },
+ {
+ "parameterName": "FileDigest",
+ "parameterValue": "/fd sha256"
+ },
+ {
+ "parameterName": "TimeStamp",
+ "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
+ }
+ ],
+ "toolName": "signtool.exe",
+ "toolVersion": "6.2.9304.0"
+ }
+ ]
templateContext:
outputs:
diff --git a/build/cppversion/version.h b/build/cppversion/version.h
new file mode 100644
index 0000000000..86885d7378
--- /dev/null
+++ b/build/cppversion/version.h
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#define STRINGIZE2(s) #s
+#define STRINGIZE(s) STRINGIZE2(s)
+
+#define VERSION_MAJOR 1
+#define VERSION_MINOR 0
+#define VERSION_BUILD 0
+#define VERSION_REVISION 0
+
+#define VER_FILE_DESCRIPTION_STR "Dev Home"
+#define VER_ORIGINAL_FILENAME_STR "DevHome"
+#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_REVISION
+#define VER_FILE_VERSION_STR STRINGIZE(VERSION_MAJOR) \
+ "." STRINGIZE(VERSION_MINOR) \
+ "." STRINGIZE(VERSION_BUILD) \
+ "." STRINGIZE(VERSION_REVISION) \
+
+#define VER_PRODUCTNAME_STR "Dev Home"
+#define VER_PRODUCT_VERSION VER_FILE_VERSION
+#define VER_PRODUCT_VERSION_STR VER_FILE_VERSION_STR
+#define VER_INTERNAL_NAME_STR VER_ORIGINAL_FILENAME_STR
+#define VER_COPYRIGHT_STR "Copyright (c) Microsoft Corporation"
+
+#ifdef _DEBUG
+#define VER_VER_DEBUG VS_FF_DEBUG
+#else
+#define VER_VER_DEBUG 0
+#endif
+
+#define VER_FILEOS VOS_NT_WINDOWS32
+#define VER_FILEFLAGS VER_VER_DEBUG
+#define VER_FILETYPE VFT_APP
+
diff --git a/build/cppversion/version.rc b/build/cppversion/version.rc
new file mode 100644
index 0000000000..74e46c30cd
--- /dev/null
+++ b/build/cppversion/version.rc
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+//
+#include "version.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VER_FILE_VERSION
+ PRODUCTVERSION VER_PRODUCT_VERSION
+ FILEFLAGSMASK 0x3fL
+ FILEFLAGS VER_FILEFLAGS
+ FILEOS VER_FILEOS
+ FILETYPE VER_FILETYPE
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", VER_FILE_DESCRIPTION_STR "\0"
+ VALUE "FileVersion", VER_FILE_VERSION_STR "\0"
+ VALUE "InternalName", VER_INTERNAL_NAME_STR "\0"
+ VALUE "LegalCopyright", VER_COPYRIGHT_STR "\0"
+ VALUE "OriginalFilename", VER_ORIGINAL_FILENAME_STR "\0"
+ VALUE "ProductName", VER_PRODUCTNAME_STR
+ VALUE "ProductVersion", VER_PRODUCT_VERSION_STR "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/build/cppversion/version.vcxitems b/build/cppversion/version.vcxitems
new file mode 100644
index 0000000000..27136662bc
--- /dev/null
+++ b/build/cppversion/version.vcxitems
@@ -0,0 +1,23 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ {6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}
+
+
+
+ %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)include
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build/scripts/CreateBuildInfo.ps1 b/build/scripts/CreateBuildInfo.ps1
index 4acf970541..232711ad55 100644
--- a/build/scripts/CreateBuildInfo.ps1
+++ b/build/scripts/CreateBuildInfo.ps1
@@ -6,7 +6,7 @@ Param(
)
$Major = "0"
-$Minor = "14"
+$Minor = "15"
$Patch = "99" # default to 99 for local builds
$versionSplit = $Version.Split(".");
diff --git a/build/scripts/update-binver.ps1 b/build/scripts/update-binver.ps1
new file mode 100644
index 0000000000..386d7f2c90
--- /dev/null
+++ b/build/scripts/update-binver.ps1
@@ -0,0 +1,66 @@
+<#
+.SYNOPSIS
+ Updates the given version header file to match the version info passed in as a string.
+.DESCRIPTION
+ Updates the given header file with the version information passed in. The version information is passed in as a string in the format "Major.Minor.Build.Revision".
+ to match. See the existing version.h for the format.
+.PARAMETER TargetFile
+ The file to update with version information.
+.PARAMETER BuildVersion
+ The build version to use.
+#>
+param(
+ [Parameter(Mandatory=$true)]
+ [string]$TargetFile,
+
+ [Parameter(Mandatory=$true)]
+ [string]$BuildVersion = "1.0.0.0"
+)
+
+
+$VersionParts = $BuildVersion.Split('.')
+$MajorVersion = $VersionParts[0]
+$MinorVersion = $VersionParts[1]
+$BuildVersion = $VersionParts[2]
+
+Write-Host "Using version: $MajorVersion.$MinorVersion.$BuildVersion.0"
+
+if (![String]::IsNullOrEmpty($TargetFile))
+{
+ $Local:FullPath = Resolve-Path $TargetFile
+ Write-Host "Updating file: $Local:FullPath"
+ if (Test-Path $TargetFile)
+ {
+ $Local:ResultContent = ""
+ foreach ($Local:line in [System.IO.File]::ReadLines($Local:FullPath))
+ {
+ if ($Local:line.StartsWith("#define VERSION_MAJOR"))
+ {
+ $Local:ResultContent += "#define VERSION_MAJOR $MajorVersion";
+ }
+ elseif ($Local:line.StartsWith("#define VERSION_MINOR"))
+ {
+ $Local:ResultContent += "#define VERSION_MINOR $MinorVersion";
+ }
+ elseif ($Local:line.StartsWith("#define VERSION_BUILD"))
+ {
+ $Local:ResultContent += "#define VERSION_BUILD $BuildVersion";
+ }
+ else
+ {
+ $Local:ResultContent += $Local:line;
+ }
+
+ # Add a newline if the line does not already have one
+ if (!$Local:line.EndsWith("`n"))
+ {
+ $Local:ResultContent += "`n";
+ }
+ }
+ Set-Content -Path $Local:FullPath -Value $Local:ResultContent -NoNewline
+ }
+ else
+ {
+ Write-Error "Did not find target file: $TargetFile"
+ }
+}
diff --git a/build/templates/EsrpSigning-Steps.yml b/build/templates/EsrpSigning-Steps.yml
new file mode 100644
index 0000000000..63eb25b47c
--- /dev/null
+++ b/build/templates/EsrpSigning-Steps.yml
@@ -0,0 +1,22 @@
+parameters:
+ - name: displayName
+ type: string
+ default: ESRP Code Signing
+ - name: inputs
+ type: object
+ default: {}
+
+steps:
+ - task: EsrpCodeSigning@5
+ displayName: ${{ parameters.displayName }}
+ inputs:
+ ConnectedServiceName: $(EsrpConnectedServiceName)
+ AppRegistrationClientId: $(EsrpAppRegistrationClientId)
+ AppRegistrationTenantId: $(EsrpAppRegistrationTenantId)
+ AuthAKVName: $(EsrpAuthAKVName)
+ AuthCertName: $(EsrpAuthCertName)
+ AuthSignCertName: $(EsrpAuthSignCertName)
+ SessionTimeout: '60'
+ MaxConcurrency: '50'
+ MaxRetryAttempts: '5'
+ ${{ insert }}: ${{ parameters.inputs }}
diff --git a/common/DevHome.Common.csproj b/common/DevHome.Common.csproj
index 5e046bf8ec..d730ac47e8 100644
--- a/common/DevHome.Common.csproj
+++ b/common/DevHome.Common.csproj
@@ -25,7 +25,7 @@
-
+
@@ -55,7 +55,7 @@
-
+
diff --git a/common/Environments/CustomControls/CardHeader.xaml b/common/Environments/CustomControls/CardHeader.xaml
index 21b3e5270e..6c20d72f53 100644
--- a/common/Environments/CustomControls/CardHeader.xaml
+++ b/common/Environments/CustomControls/CardHeader.xaml
@@ -52,6 +52,7 @@
IsTabStop="False"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Stretch"
- ContentTemplate="{x:Bind ActionControlTemplate, Mode=OneWay}"/>
+ ContentTemplate="{x:Bind ActionControlTemplate, Mode=OneWay}"
+ Visibility="{x:Bind OperationsVisibility, Mode=OneWay}"/>
diff --git a/common/Environments/CustomControls/CardHeader.xaml.cs b/common/Environments/CustomControls/CardHeader.xaml.cs
index c243ff0925..aac684e5ac 100644
--- a/common/Environments/CustomControls/CardHeader.xaml.cs
+++ b/common/Environments/CustomControls/CardHeader.xaml.cs
@@ -26,6 +26,12 @@ public string HeaderCaption
set => SetValue(HeaderCaptionProperty, value);
}
+ public bool OperationsVisibility
+ {
+ get => (bool)GetValue(OperationsVisibilityProperty);
+ set => SetValue(OperationsVisibilityProperty, value);
+ }
+
public BitmapImage HeaderIcon
{
get => (BitmapImage)GetValue(HeaderIconProperty);
@@ -35,4 +41,5 @@ public BitmapImage HeaderIcon
private static readonly DependencyProperty ActionControlTemplateProperty = DependencyProperty.Register(nameof(ActionControlTemplate), typeof(DataTemplate), typeof(CardHeader), new PropertyMetadata(null));
private static readonly DependencyProperty HeaderCaptionProperty = DependencyProperty.Register(nameof(HeaderCaption), typeof(string), typeof(CardHeader), new PropertyMetadata(null));
private static readonly DependencyProperty HeaderIconProperty = DependencyProperty.Register(nameof(HeaderIcon), typeof(BitmapImage), typeof(CardHeader), new PropertyMetadata(null));
+ private static readonly DependencyProperty OperationsVisibilityProperty = DependencyProperty.Register(nameof(HeaderCaption), typeof(bool), typeof(CardHeader), new PropertyMetadata(false));
}
diff --git a/common/Environments/Models/ComputeSystemCache.cs b/common/Environments/Models/ComputeSystemCache.cs
index 56eaf4ecd5..e3653e8f8a 100644
--- a/common/Environments/Models/ComputeSystemCache.cs
+++ b/common/Environments/Models/ComputeSystemCache.cs
@@ -237,16 +237,6 @@ public async Task FetchDataAsync()
_ = await GetStateAsync();
var supportedOperations = SupportedOperations?.Value ?? ComputeSystemOperations.None;
- if (supportedOperations.HasFlag(ComputeSystemOperations.PinToStartMenu))
- {
- _ = await GetIsPinnedToStartMenuAsync();
- }
-
- if (supportedOperations.HasFlag(ComputeSystemOperations.PinToTaskbar))
- {
- _ = await GetIsPinnedToTaskbarAsync();
- }
-
_ = await GetComputeSystemThumbnailAsync(string.Empty);
_ = await GetComputeSystemPropertiesAsync(string.Empty);
}
diff --git a/common/Environments/Styles/HorizontalCardStyles.xaml b/common/Environments/Styles/HorizontalCardStyles.xaml
index 3ef4708fc8..7a9e98c2b8 100644
--- a/common/Environments/Styles/HorizontalCardStyles.xaml
+++ b/common/Environments/Styles/HorizontalCardStyles.xaml
@@ -61,6 +61,7 @@
TargetType="Grid">
+
diff --git a/common/Extensions/StackedNotificationsBehaviorExtensions.cs b/common/Extensions/StackedNotificationsBehaviorExtensions.cs
index 93b665302d..c93bbec88b 100644
--- a/common/Extensions/StackedNotificationsBehaviorExtensions.cs
+++ b/common/Extensions/StackedNotificationsBehaviorExtensions.cs
@@ -1,17 +1,12 @@
// 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.Input;
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Behaviors;
+using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using WinUIEx;
namespace DevHome.Common.Extensions;
@@ -25,9 +20,9 @@ public static void ShowWithWindowExtension(
IRelayCommand? command = null,
string? buttonContent = null)
{
- var dispatcherQueue = Application.Current.GetService().DispatcherQueue;
+ var dispatcherQueue = Application.Current.GetService();
- dispatcherQueue.EnqueueAsync(() =>
+ dispatcherQueue?.EnqueueAsync(() =>
{
var notificationToShow = new Notification
{
@@ -55,9 +50,9 @@ public static void ShowWithWindowExtension(
public static void RemoveWithWindowExtension(this StackedNotificationsBehavior behavior, Notification notification)
{
- var dispatcherQueue = Application.Current.GetService().DispatcherQueue;
+ var dispatcherQueue = Application.Current.GetService();
- dispatcherQueue.EnqueueAsync(() =>
+ dispatcherQueue?.EnqueueAsync(() =>
{
behavior.Remove(notification);
});
@@ -65,7 +60,7 @@ public static void RemoveWithWindowExtension(this StackedNotificationsBehavior b
public static void ClearWithWindowExtension(this StackedNotificationsBehavior behavior)
{
- var dispatcherQueue = Application.Current.GetService().DispatcherQueue;
+ var dispatcherQueue = Application.Current.GetService().DispatcherQueue;
dispatcherQueue.EnqueueAsync(() =>
{
diff --git a/common/Extensions/WindowExExtensions.cs b/common/Extensions/WindowExExtensions.cs
index 07202c7e70..6f37578819 100644
--- a/common/Extensions/WindowExExtensions.cs
+++ b/common/Extensions/WindowExExtensions.cs
@@ -2,16 +2,20 @@
// Licensed under the MIT License.
using System;
+using System.Runtime.InteropServices;
using System.Threading.Tasks;
using DevHome.Common.Helpers;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using WinUIEx;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Graphics.Gdi;
+using Windows.Win32.UI.WindowsAndMessaging;
namespace DevHome.Common.Extensions;
///
-/// This class add extension methods to the class.
+/// This class add extension methods to the class.
///
public static class WindowExExtensions
{
@@ -24,7 +28,7 @@ public static class WindowExExtensions
/// Dialog title.
/// Dialog content.
/// Close button text.
- public static async Task ShowErrorMessageDialogAsync(this WindowEx window, string title, string content, string buttonText)
+ public static async Task ShowErrorMessageDialogAsync(this Window window, string title, string content, string buttonText)
{
await window.ShowMessageDialogAsync(dialog =>
{
@@ -32,7 +36,7 @@ await window.ShowMessageDialogAsync(dialog =>
dialog.Content = new TextBlock()
{
Text = content,
- TextWrapping = Microsoft.UI.Xaml.TextWrapping.WrapWholeWords,
+ TextWrapping = TextWrapping.WrapWholeWords,
};
dialog.PrimaryButtonText = buttonText;
});
@@ -42,11 +46,11 @@ await window.ShowMessageDialogAsync(dialog =>
/// Generic implementation for creating and displaying a message dialog on
/// a window.
///
- /// This extension method overloads .
+ /// This extension method overloads .
///
/// Target window.
/// Action performed on the created dialog.
- private static async Task ShowMessageDialogAsync(this WindowEx window, Action action)
+ private static async Task ShowMessageDialogAsync(this Window window, Action action)
{
var dialog = new ContentDialog()
{
@@ -61,7 +65,7 @@ private static async Task ShowMessageDialogAsync(this WindowEx window, Action
/// Target window
/// New theme.
- public static void SetRequestedTheme(this WindowEx window, ElementTheme theme)
+ public static void SetRequestedTheme(this Window window, ElementTheme theme)
{
if (window.Content is FrameworkElement rootElement)
{
@@ -69,4 +73,37 @@ public static void SetRequestedTheme(this WindowEx window, ElementTheme theme)
TitleBarHelper.UpdateTitleBar(window, rootElement.ActualTheme);
}
}
+
+ ///
+ /// Gets the native HWND pointer handle for the window
+ ///
+ /// The window to return the handle for
+ /// HWND handle
+ public static IntPtr GetWindowHandle(this Microsoft.UI.Xaml.Window window)
+ => window is null ? throw new ArgumentNullException(nameof(window)) : WinRT.Interop.WindowNative.GetWindowHandle(window);
+
+ ///
+ /// Centers the window on the current monitor
+ ///
+ /// The window to center
+ /// Width of the window in device independent pixels, or null if keeping the current size
+ /// Height of the window in device independent pixels, or null if keeping the current size
+ public static void CenterOnScreen(this Microsoft.UI.Xaml.Window window, double? width = null, double? height = null)
+ {
+ var hwnd = window.GetWindowHandle();
+ var hwndDesktop = PInvoke.MonitorFromWindow((HWND)hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
+ var info = default(MONITORINFO);
+ info.cbSize = (uint)Marshal.SizeOf(info);
+ PInvoke.GetMonitorInfo(hwndDesktop, ref info);
+ var dpi = PInvoke.GetDpiForWindow((HWND)hwnd);
+ PInvoke.GetWindowRect((HWND)hwnd, out RECT windowRect);
+ var scalingFactor = dpi / 96d;
+ var w = width.HasValue ? (int)(width * scalingFactor) : windowRect.right - windowRect.left;
+ var h = height.HasValue ? (int)(height * scalingFactor) : windowRect.bottom - windowRect.top;
+ var cx = (info.rcMonitor.left + info.rcMonitor.right) / 2;
+ var cy = (info.rcMonitor.bottom + info.rcMonitor.top) / 2;
+ var left = cx - (w / 2);
+ var top = cy - (h / 2);
+ PInvoke.SetWindowPos((HWND)hwnd, (HWND)IntPtr.Zero, left, top, w, h, SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW);
+ }
}
diff --git a/common/Helpers/RuntimeHelper.cs b/common/Helpers/RuntimeHelper.cs
index 7ab541f6f2..1fb3af8d43 100644
--- a/common/Helpers/RuntimeHelper.cs
+++ b/common/Helpers/RuntimeHelper.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Security.Principal;
using Windows.Win32;
using Windows.Win32.Foundation;
@@ -27,4 +28,10 @@ public static bool IsOnWindows11
return version.Major >= 10 && version.Build >= 22000;
}
}
+
+ public static bool IsCurrentProcessRunningAsAdmin()
+ {
+ var identity = WindowsIdentity.GetCurrent();
+ return identity.Owner?.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid) ?? false;
+ }
}
diff --git a/common/NativeMethods.txt b/common/NativeMethods.txt
index 8507f58dc3..ea4531780a 100644
--- a/common/NativeMethods.txt
+++ b/common/NativeMethods.txt
@@ -13,3 +13,8 @@ SHLoadIndirectString
StrFormatByteSizeEx
SFBS_FLAGS
MAX_PATH
+GetDpiForWindow
+GetWindowRect
+GetMonitorInfo
+SetWindowPos
+MonitorFromWindow
diff --git a/common/Renderers/LabelGroup.cs b/common/Renderers/LabelGroup.cs
index 83ef7e6df9..4352cb9859 100644
--- a/common/Renderers/LabelGroup.cs
+++ b/common/Renderers/LabelGroup.cs
@@ -95,6 +95,8 @@ public UIElement Render(IAdaptiveCardElement element, AdaptiveRenderContext cont
{
Name = LabelGroup.CustomTypeString,
Orientation = Orientation.Horizontal,
+ HorizontalSpacing = 4,
+ VerticalSpacing = 4,
};
if (element is LabelGroup labelGroup)
@@ -108,7 +110,6 @@ public UIElement Render(IAdaptiveCardElement element, AdaptiveRenderContext cont
{
Background = GetBrushFromColor(label.Item2, 0.4),
Padding = new Thickness(7, 2, 7, 2),
- Margin = new Thickness(0, 0, 10, 0),
};
if (labelGroup.RoundedCorners)
{
diff --git a/common/Services/AdaptiveCardRenderingService.cs b/common/Services/AdaptiveCardRenderingService.cs
index 3e712f476d..e5fa079cf4 100644
--- a/common/Services/AdaptiveCardRenderingService.cs
+++ b/common/Services/AdaptiveCardRenderingService.cs
@@ -7,10 +7,10 @@
using AdaptiveCards.Rendering.WinUI3;
using DevHome.Common.Renderers;
using DevHome.Contracts.Services;
+using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Serilog;
using Windows.Storage;
-using WinUIEx;
namespace DevHome.Common.Services;
@@ -20,7 +20,7 @@ public class AdaptiveCardRenderingService : IAdaptiveCardRenderingService, IDisp
public event EventHandler RendererUpdated = (_, _) => { };
- private readonly WindowEx _windowEx;
+ private readonly DispatcherQueue _dispatcherQueue;
private readonly IThemeSelectorService _themeSelectorService;
@@ -30,9 +30,9 @@ public class AdaptiveCardRenderingService : IAdaptiveCardRenderingService, IDisp
private bool _disposedValue;
- public AdaptiveCardRenderingService(WindowEx windowEx, IThemeSelectorService themeSelectorService)
+ public AdaptiveCardRenderingService(DispatcherQueue dispatcherQueue, IThemeSelectorService themeSelectorService)
{
- _windowEx = windowEx;
+ _dispatcherQueue = dispatcherQueue;
_themeSelectorService = themeSelectorService;
_themeSelectorService.ThemeChanged += OnThemeChanged;
}
@@ -122,7 +122,7 @@ private async Task UpdateHostConfig()
_log.Error(ex, "Error retrieving HostConfig");
}
- _windowEx.DispatcherQueue.TryEnqueue(() =>
+ _dispatcherQueue.TryEnqueue(() =>
{
if (!string.IsNullOrEmpty(hostConfigContents))
{
diff --git a/common/Services/StringResource.cs b/common/Services/StringResource.cs
index 068f7b5f78..c4628b65f2 100644
--- a/common/Services/StringResource.cs
+++ b/common/Services/StringResource.cs
@@ -48,7 +48,12 @@ public string GetLocalized(string key, params object[] args)
try
{
value = _resourceLoader.GetString(key);
- value = string.Format(CultureInfo.CurrentCulture, value, args);
+
+ // only replace the placeholders if args is not empty
+ if (args.Length > 0)
+ {
+ value = string.Format(CultureInfo.CurrentCulture, value, args);
+ }
}
catch
{
diff --git a/common/ToolPage.cs b/common/ToolPage.cs
index bec0df868e..4ce07613f7 100644
--- a/common/ToolPage.cs
+++ b/common/ToolPage.cs
@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Microsoft.UI.Xaml.Controls;
+using DevHome.Common.Views;
namespace DevHome.Common;
-public abstract class ToolPage : Page
+public abstract class ToolPage : DevHomePage
{
public abstract string ShortName { get; }
}
diff --git a/common/Views/AdaptiveCardViews/ContentDialogWithNonInteractiveContent.xaml.cs b/common/Views/AdaptiveCardViews/ContentDialogWithNonInteractiveContent.xaml.cs
index 23f78ba236..8b778f41b6 100644
--- a/common/Views/AdaptiveCardViews/ContentDialogWithNonInteractiveContent.xaml.cs
+++ b/common/Views/AdaptiveCardViews/ContentDialogWithNonInteractiveContent.xaml.cs
@@ -1,14 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.Threading.Tasks;
-using AdaptiveCards.Rendering.WinUI3;
using DevHome.Common.DevHomeAdaptiveCards.CardModels;
using DevHome.Common.Extensions;
using DevHome.Common.Services;
+using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
-using WinUIEx;
namespace DevHome.Common.Views.AdaptiveCardViews;
@@ -22,7 +20,7 @@ public ContentDialogWithNonInteractiveContent(DevHomeContentDialogContent conten
this.InitializeComponent();
// Since we use the renderer service to allow the card to receive theming updates, we need to ensure the UI thread is used.
- var dispatcherQueue = Application.Current.GetService().DispatcherQueue;
+ var dispatcherQueue = Application.Current.GetService();
dispatcherQueue.TryEnqueue(async () =>
{
Title = content.Title;
@@ -31,8 +29,13 @@ public ContentDialogWithNonInteractiveContent(DevHomeContentDialogContent conten
var renderer = await rendererService.GetRendererAsync();
renderer.HostConfig.ContainerStyles.Default.BackgroundColor = Microsoft.UI.Colors.Transparent;
var card = renderer.RenderAdaptiveCardFromJsonString(content.ContentDialogInternalAdaptiveCardJson?.Stringify() ?? string.Empty);
- Content = card.FrameworkElement;
+ Content = new ScrollViewer
+ {
+ VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
+ Content = card.FrameworkElement,
+ };
SecondaryButtonText = content.SecondaryButtonText;
+ this.Focus(FocusState.Programmatic);
});
}
}
diff --git a/common/Views/DevHomePage.cs b/common/Views/DevHomePage.cs
new file mode 100644
index 0000000000..75681024a7
--- /dev/null
+++ b/common/Views/DevHomePage.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.Common.Views;
+
+///
+/// This page is used to auto focus on the first selectable element.
+/// Please inherit from this class for pages.
+/// If the Page needs custom focus logic (for example, waiting until adaptive cards are loaded)
+/// 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()
+ {
+ Loaded += (s, e) =>
+ {
+ Focus(FocusState.Programmatic);
+ };
+ }
+}
diff --git a/common/Views/DevHomeUserControl.cs b/common/Views/DevHomeUserControl.cs
new file mode 100644
index 0000000000..0d8f55344a
--- /dev/null
+++ b/common/Views/DevHomeUserControl.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace DevHome.Common.Views;
+
+///
+/// This UserControl is used to auto focus on the first selectable element.
+/// Please inherit from this class for UserControl.
+/// If the UserControl needs custom focus logic (for example, waiting until adaptive cards are loaded)
+/// the individual UserControl 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 DevHomeUserControl : UserControl
+{
+ public DevHomeUserControl()
+ {
+ Loaded += (s, args) =>
+ {
+ Focus(FocusState.Programmatic);
+ };
+ }
+}
diff --git a/common/Windows/SecondaryWindow.cs b/common/Windows/SecondaryWindow.cs
index a5834f21cd..fce42968e1 100644
--- a/common/Windows/SecondaryWindow.cs
+++ b/common/Windows/SecondaryWindow.cs
@@ -8,23 +8,23 @@
using DevHome.Contracts.Services;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Markup;
+using Windows.Graphics;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
-using WinUIEx;
namespace DevHome.Common.Windows;
[ContentProperty(Name = nameof(SecondaryWindowContent))]
-public class SecondaryWindow : WindowEx
+public class SecondaryWindow : WinUIEx.WindowEx
{
private readonly SecondaryWindowTemplate _windowTemplate;
- private WindowEx? _primaryWindow;
+ private Window? _primaryWindow;
private bool _useAppTheme;
private bool _isModal;
private bool _isTopLevel;
- private WindowEx MainWindow => Application.Current.GetService();
+ private Window MainWindow => Application.Current.GetService();
private IThemeSelectorService ThemeSelector => Application.Current.GetService();
@@ -149,7 +149,7 @@ public bool IsTopLevel
}
}
- public WindowEx? PrimaryWindow
+ public Window? PrimaryWindow
{
get => _primaryWindow;
set
@@ -195,7 +195,7 @@ public SecondaryWindow()
SystemBackdrop = PrimaryWindow.SystemBackdrop;
Title = AppInfo.GetAppNameLocalized();
- this.SetIcon(AppInfo.IconPath);
+ AppWindow.SetIcon(AppInfo.IconPath);
ShowInTaskbar();
}
@@ -212,7 +212,7 @@ public SecondaryWindow(object secondaryWindowContent)
///
///
/// This method should be called after the secondary window is shown.
- /// See also:
+ /// See also:
///
public void CenterOnWindow()
{
@@ -224,22 +224,23 @@ public void CenterOnWindow()
{
// Get DPI for primary widow
const float defaultDPI = 96f;
- var dpi = HwndExtensions.GetDpiForWindow(PrimaryWindow.GetWindowHandle()) / defaultDPI;
+ var dpi = PInvoke.GetDpiForWindow((HWND)PrimaryWindow.GetWindowHandle()) / defaultDPI;
// Extract primary window dimensions
var primaryWindowLeftOffset = PrimaryWindow.AppWindow.Position.X;
var primaryWindowTopOffset = PrimaryWindow.AppWindow.Position.Y;
- var primaryWindowHalfWidth = (PrimaryWindow.Width * dpi) / 2;
- var primaryWindowHalfHeight = (PrimaryWindow.Height * dpi) / 2;
+ var primaryWindowHalfWidth = (PrimaryWindow.AppWindow.Size.Width * dpi) / 2;
+ var primaryWindowHalfHeight = (PrimaryWindow.AppWindow.Size.Height * dpi) / 2;
// Derive secondary window dimensions
- var secondaryWindowHalfWidth = (Width * dpi) / 2;
- var secondaryWindowHalfHeight = (Height * dpi) / 2;
+ var secondaryWindowHalfWidth = (AppWindow.Size.Width * dpi) / 2;
+ var secondaryWindowHalfHeight = (AppWindow.Size.Height * dpi) / 2;
var secondaryWindowLeftOffset = primaryWindowLeftOffset + primaryWindowHalfWidth - secondaryWindowHalfWidth;
var secondaryWindowTopOffset = primaryWindowTopOffset + primaryWindowHalfHeight - secondaryWindowHalfHeight;
// Move and resize secondary window
- this.MoveAndResize(secondaryWindowLeftOffset, secondaryWindowTopOffset, Width, Height);
+ var newRect = new RectInt32((int)secondaryWindowLeftOffset, (int)secondaryWindowTopOffset, AppWindow.Size.Width, AppWindow.Size.Height);
+ AppWindow.MoveAndResize(newRect);
}
}
diff --git a/docs/readme.md b/docs/readme.md
index 31bd0c698f..122550af80 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -3,5 +3,5 @@
Before you dive into developing for Dev Home, check out these resources:
- [Dev Home Architecture](./architecture.md)
- [Dev Home Tools](./tools/readme.md)
-- [Dev Home Extensions](./extensions/extensions.md)
+- [Dev Home Extensions](./extensions/readme.md)
diff --git a/docs/tools/Dashboard.md b/docs/tools/Dashboard.md
index d6cc48f007..bc7810a075 100644
--- a/docs/tools/Dashboard.md
+++ b/docs/tools/Dashboard.md
@@ -12,3 +12,7 @@ Widgets are rendered by Adaptive Cards, and there are a few ways Dev Home custom
* Dev Home widgets use the [Adaptive Card schema](https://adaptivecards.io/explorer/) version 1.5, which is the most recent schema supported by the WinUI 3 Adaptive Card renderer.
* There are [HostConfig](https://learn.microsoft.com/adaptive-cards/sdk/rendering-cards/uwp/host-config) files that define common styles (e.g., font family, font sizes, default spacing) and behaviors (e.g., max number of actions) for all the widgets. There is one for [light mode](../../tools/Dashboard/DevHome.Dashboard/Assets/HostConfigLight.json) and one for [dark mode](../tools/Dashboard/DevHome.Dashboard/Assets/HostConfigDark.json).
* Dev Home supports a custom AdaptiveElement type called [`LabelGroup`](../../common/Renderers/LabelGroup.cs). This allows a card author to render a set of labels, each with a specified background color. For an example of how to use this type, please see the [GitHub Issues widget](https://github.com/microsoft/devhomegithubextension/blob/main/src/GitHubExtension/Widgets/Templates/GitHubIssuesTemplate.json).
+
+### Widget providers
+
+When creating a widget for Dev Home, you should include all manifest values described in [Widget provider package manifest XML format](https://learn.microsoft.com/windows/apps/develop/widgets/widget-provider-manifest).
diff --git a/docs/tools/common/SecondaryWindow.md b/docs/tools/common/SecondaryWindow.md
index f07a7c89f3..26a0dd3023 100644
--- a/docs/tools/common/SecondaryWindow.md
+++ b/docs/tools/common/SecondaryWindow.md
@@ -14,7 +14,7 @@ Create a secondary application window that derives from `WinUIEx.WindowEx` ensur
## Additional methods
| Property | Retrun type | Description |
| -------- | -------- | -------- |
-| CenterOnWindow() | void | If the primary window is set, center the secondary window on the primary window. Otherwise, center the secondary window on the screen by calling `WinUIEx.WindowExtensions.CenterOnScreen()`. |
+| CenterOnWindow() | void | If the primary window is set, center the secondary window on the primary window. Otherwise, center the secondary window on the screen by calling `WindowExExtensions.CenterOnScreen()`. |
## Usage
### Example 1: Set content from XAML
diff --git a/extensions/WindowsSandboxExtension/Assets/windows-sandbox-icon.png b/extensions/WindowsSandboxExtension/Assets/windows-sandbox-icon.png
new file mode 100644
index 0000000000..73ca5be9d2
Binary files /dev/null and b/extensions/WindowsSandboxExtension/Assets/windows-sandbox-icon.png differ
diff --git a/extensions/WindowsSandboxExtension/Assets/windows-sandbox-thumbnail.jpg b/extensions/WindowsSandboxExtension/Assets/windows-sandbox-thumbnail.jpg
new file mode 100644
index 0000000000..6569fdd746
Binary files /dev/null and b/extensions/WindowsSandboxExtension/Assets/windows-sandbox-thumbnail.jpg differ
diff --git a/extensions/WindowsSandboxExtension/Constants.cs b/extensions/WindowsSandboxExtension/Constants.cs
new file mode 100644
index 0000000000..167c5dfa8d
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Constants.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WindowsSandboxExtension;
+
+internal sealed class Constants
+{
+ public const string WindowsSandboxExe = "WindowsSandbox.exe";
+ public const string ProviderDisplayName = "Windows Sandbox";
+ public const string ProviderId = "Microsoft.WindowsSandbox";
+ public const string Thumbnail = "ms-appx:///Assets/windows-sandbox-thumbnail.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/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/Assets/windows-sandbox-icon.png";
+#elif STABLE_BUILD
+ public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome/Files/Assets/windows-sandbox-icon.png";
+#else
+ public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome.Dev/Files/Assets/windows-sandbox-icon.png";
+#endif
+}
diff --git a/extensions/WindowsSandboxExtension/Helpers/DependencyChecker.cs b/extensions/WindowsSandboxExtension/Helpers/DependencyChecker.cs
new file mode 100644
index 0000000000..cb76ef75c6
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Helpers/DependencyChecker.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Management;
+using System.Security.Principal;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.Management.Deployment;
+using WinRT;
+
+namespace WindowsSandboxExtension.Helpers;
+
+internal sealed class DependencyChecker
+{
+ private const string OptionalComponentName = "Containers-DisposableClientVM";
+ private const string PackageFamilyName = "MicrosoftWindows.WindowsSandbox_cw5n1h2txyewy";
+
+ public static bool IsOptionalComponentEnabled()
+ {
+ var searcher = new ManagementObjectSearcher($"SELECT InstallState FROM Win32_OptionalFeature WHERE Name = '{OptionalComponentName}'");
+ var collection = searcher.Get();
+
+ foreach (ManagementObject instance in collection)
+ {
+ if (instance["InstallState"] != null)
+ {
+ var state = Convert.ToInt32(instance.GetPropertyValue("InstallState"), CultureInfo.InvariantCulture);
+
+ // 1 means the feature is enabled
+ return state == 1;
+ }
+ }
+
+ // Return false if the feature is not found
+ return false;
+ }
+
+ public static bool IsNewWindowsSandboxExtensionInstalled()
+ {
+ PackageManager packageManager = new PackageManager();
+
+ var securityId = WindowsIdentity.GetCurrent().Owner?.ToString();
+ var packages = packageManager.FindPackagesForUser(securityId, PackageFamilyName);
+
+ return packages.Any();
+ }
+}
diff --git a/extensions/WindowsSandboxExtension/Helpers/Logging.cs b/extensions/WindowsSandboxExtension/Helpers/Logging.cs
new file mode 100644
index 0000000000..8821497401
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Helpers/Logging.cs
@@ -0,0 +1,24 @@
+// 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 Windows.Storage;
+
+namespace WindowsSandboxExtension.Helpers;
+
+public class Logging
+{
+ public static readonly string LogExtension = ".dhlog";
+
+ public static readonly string LogFolderName = "Logs";
+
+ public static readonly string DefaultLogFileName = "WindowsSandbox";
+
+ private static readonly Lazy _logFolderRoot = new(() => Path.Combine(ApplicationData.Current.TemporaryFolder.Path, LogFolderName));
+
+ public static readonly string LogFolderRoot = _logFolderRoot.Value;
+}
diff --git a/extensions/WindowsSandboxExtension/Helpers/Resources.cs b/extensions/WindowsSandboxExtension/Helpers/Resources.cs
new file mode 100644
index 0000000000..315be43064
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Helpers/Resources.cs
@@ -0,0 +1,38 @@
+// 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 Microsoft.Windows.ApplicationModel.Resources;
+using Serilog;
+
+namespace WindowsSandboxExtension.Helpers;
+
+internal sealed class Resources
+{
+ private static ResourceLoader? _resourceLoader;
+
+ public static string GetResource(string identifier, ILogger? log = null)
+ {
+ try
+ {
+ if (_resourceLoader == null)
+ {
+ var path = ResourceLoader.GetDefaultResourceFilePath();
+ _resourceLoader = new ResourceLoader(path);
+ }
+
+ return _resourceLoader.GetString(identifier);
+ }
+ catch (Exception ex)
+ {
+ log?.Error(ex, $"Failed loading resource: {identifier}");
+
+ // If we fail, load the original identifier so it is obvious which resource is missing.
+ return identifier;
+ }
+ }
+}
diff --git a/extensions/WindowsSandboxExtension/NativeMethods.txt b/extensions/WindowsSandboxExtension/NativeMethods.txt
new file mode 100644
index 0000000000..0caa4406b1
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/NativeMethods.txt
@@ -0,0 +1 @@
+SetForegroundWindow
\ No newline at end of file
diff --git a/extensions/WindowsSandboxExtension/Program.cs b/extensions/WindowsSandboxExtension/Program.cs
new file mode 100644
index 0000000000..54f5b83f6f
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Program.cs
@@ -0,0 +1,130 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics;
+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 WindowsSandboxExtension.Helpers;
+using WindowsSandboxExtension.Providers;
+using WinRT;
+
+namespace WindowsSandboxExtension;
+
+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("DEVHOME_LOGS_ROOT", Path.Join(Helpers.Logging.LogFolderRoot, "WindowsSandbox"));
+ var configuration = new ConfigurationBuilder()
+ .AddJsonFile("appsettings_WindowsSandbox.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, Microsoft.Windows.AppLifecycle.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 Windows Sandbox Extension 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.AddSingleton();
+ services.AddSingleton();
+ }).
+ Build();
+ }
+
+ private static void HandleCOMServerActivation()
+ {
+ Debug.Assert(Host != null, "Host is null");
+ 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 Microsoft.Windows.DevHome.SDK.ExtensionServer();
+ var windowsSandboxExtension = Host.Services.GetRequiredService(typeof(WindowsSandboxExtension)).As();
+
+ // 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(() => windowsSandboxExtension, true);
+
+ // This will make the main thread wait until the event is signalled by the extension class.
+ // Since we have single instance of the extension object, we exit as soon as it is disposed.
+ windowsSandboxExtension.ExtensionDisposedEvent.WaitOne();
+ Log.Information($"Extension is disposed.");
+ }
+}
diff --git a/extensions/WindowsSandboxExtension/Properties/PublishProfiles/win-arm64.pubxml b/extensions/WindowsSandboxExtension/Properties/PublishProfiles/win-arm64.pubxml
new file mode 100644
index 0000000000..b2f119ed57
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Properties/PublishProfiles/win-arm64.pubxml
@@ -0,0 +1,17 @@
+
+
+
+
+ FileSystem
+ arm64
+ win-arm64
+ true
+ False
+ False
+ True
+ True
+ True
+
+
\ No newline at end of file
diff --git a/extensions/WindowsSandboxExtension/Properties/PublishProfiles/win-x64.pubxml b/extensions/WindowsSandboxExtension/Properties/PublishProfiles/win-x64.pubxml
new file mode 100644
index 0000000000..0c9801b313
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Properties/PublishProfiles/win-x64.pubxml
@@ -0,0 +1,17 @@
+
+
+
+
+ FileSystem
+ x64
+ win-x64
+ true
+ False
+ False
+ True
+ True
+ True
+
+
\ No newline at end of file
diff --git a/extensions/WindowsSandboxExtension/Properties/PublishProfiles/win-x86.pubxml b/extensions/WindowsSandboxExtension/Properties/PublishProfiles/win-x86.pubxml
new file mode 100644
index 0000000000..84817dc699
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Properties/PublishProfiles/win-x86.pubxml
@@ -0,0 +1,17 @@
+
+
+
+
+ FileSystem
+ x86
+ win-x86
+ true
+ False
+ False
+ True
+ True
+ True
+
+
\ No newline at end of file
diff --git a/extensions/WindowsSandboxExtension/Properties/launchSettings.json b/extensions/WindowsSandboxExtension/Properties/launchSettings.json
new file mode 100644
index 0000000000..20a32d9c87
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "WindowsSandboxExtension": {
+ "commandName": "Project",
+ "commandLineArgs": "-RegisterProcessAsComServer"
+ }
+ }
+}
\ No newline at end of file
diff --git a/extensions/WindowsSandboxExtension/Providers/WindowsSandboxComputeSystem.cs b/extensions/WindowsSandboxExtension/Providers/WindowsSandboxComputeSystem.cs
new file mode 100644
index 0000000000..b80dd7303c
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Providers/WindowsSandboxComputeSystem.cs
@@ -0,0 +1,240 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using System.Diagnostics;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Microsoft.Windows.DevHome.SDK;
+using Serilog;
+using Windows.Foundation;
+using Windows.Storage;
+using Windows.Storage.Streams;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using WindowsSandboxExtension.Helpers;
+using WindowsSandboxExtension.Telemetry;
+
+using Timer = System.Timers.Timer;
+
+namespace WindowsSandboxExtension.Providers;
+
+public class WindowsSandboxComputeSystem : IComputeSystem, IDisposable
+{
+ private const long ByteSizeGB = 1024 * 1024 * 1024;
+ private const long DefaultMemorySizeInBytes = 4 * ByteSizeGB;
+ private const long DefaultStorageSizeInBytes = 80 * ByteSizeGB;
+
+ private readonly Guid _id = Guid.NewGuid();
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WindowsSandboxProvider));
+ private readonly object _windowsSandboxStartLock = new();
+
+ private Process? _windowsSandboxExeProcess;
+ private ComputeSystemState _state = ComputeSystemState.Stopped;
+
+ private ComputeSystemState State
+ {
+ get => _state;
+
+ set
+ {
+ _state = value;
+ StateChanged?.Invoke(this, value);
+ }
+ }
+
+ public string AssociatedProviderId => Constants.ProviderId;
+
+ public string DisplayName => Resources.GetResource("WindowsSandboxDisplayName", _log);
+
+ public string Id => _id.ToString();
+
+ public string SupplementalDisplayName => string.Empty;
+
+ public ComputeSystemOperations SupportedOperations => State switch
+ {
+ ComputeSystemState.Running => ComputeSystemOperations.Terminate,
+ _ => ComputeSystemOperations.None,
+ };
+
+ public IDeveloperId? AssociatedDeveloperId => null;
+
+ public event TypedEventHandler? StateChanged;
+
+ public IAsyncOperation GetComputeSystemThumbnailAsync(string options)
+ {
+ return Task.Run(async () =>
+ {
+ var uri = new Uri(Constants.Thumbnail);
+ 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 new ComputeSystemThumbnailResult(bytes);
+ }).AsAsyncOperation();
+ }
+
+ public IAsyncOperation> GetComputeSystemPropertiesAsync(string options)
+ {
+ return Task.Run(() =>
+ {
+ var properties = new List
+ {
+ ComputeSystemProperty.Create(ComputeSystemPropertyKind.CpuCount, Environment.ProcessorCount),
+ ComputeSystemProperty.Create(ComputeSystemPropertyKind.AssignedMemorySizeInBytes, DefaultMemorySizeInBytes),
+ ComputeSystemProperty.Create(ComputeSystemPropertyKind.StorageSizeInBytes, DefaultStorageSizeInBytes),
+ };
+
+ return properties.AsEnumerable();
+ }).AsAsyncOperation();
+ }
+
+ public IAsyncOperation GetStateAsync()
+ {
+ return Task.Run(() =>
+ {
+ return new ComputeSystemStateResult(State);
+ }).AsAsyncOperation();
+ }
+
+ public IAsyncOperation ConnectAsync(string options)
+ {
+ return Task.Run(() =>
+ {
+ try
+ {
+ // Windows Sandbox is not running.
+ if (_windowsSandboxExeProcess == null || _windowsSandboxExeProcess.HasExited)
+ {
+ State = ComputeSystemState.Starting;
+
+ var system32Path = Environment.GetFolderPath(Environment.SpecialFolder.System);
+ var windowsSandboxExePath = Path.Combine(system32Path, Constants.WindowsSandboxExe);
+
+ _windowsSandboxExeProcess = new();
+ _windowsSandboxExeProcess.StartInfo.FileName = windowsSandboxExePath;
+ _windowsSandboxExeProcess.EnableRaisingEvents = true;
+ _windowsSandboxExeProcess.Exited += WindowsSandboxProcessExited;
+
+ State = ComputeSystemState.Running;
+ TraceLogging.StartingWindowsSandbox();
+
+ _windowsSandboxExeProcess.Start();
+
+ PInvoke.SetForegroundWindow((HWND)_windowsSandboxExeProcess.MainWindowHandle);
+ }
+
+ BringWindowsSandboxClientToForeground();
+
+ return new ComputeSystemOperationResult();
+ }
+ catch (Exception ex)
+ {
+ State = ComputeSystemState.Unknown;
+
+ _log.Error(ex, "Failed to start Windows Sandbox");
+ TraceLogging.ExceptionThrown(ex);
+
+ return new ComputeSystemOperationResult(
+ ex,
+ Resources.GetResource("WindowsSandboxFailedToStart", _log),
+ "Failed to start Windows Sandbox");
+ }
+ }).AsAsyncOperation();
+ }
+
+ private void WindowsSandboxProcessExited(object? sender, EventArgs e)
+ {
+ State = ComputeSystemState.Stopped;
+ _windowsSandboxExeProcess?.Dispose();
+ _windowsSandboxExeProcess = null;
+ }
+
+ private Process? GetWindowsSandboxClientProcess()
+ {
+ return Process.GetProcessesByName("WindowsSandboxClient").FirstOrDefault();
+ }
+
+ private void BringWindowsSandboxClientToForeground()
+ {
+ var clientProcess = GetWindowsSandboxClientProcess();
+ var windowHandle = clientProcess?.MainWindowHandle ?? IntPtr.Zero;
+
+ PInvoke.SetForegroundWindow((HWND)windowHandle);
+ }
+
+ public IAsyncOperation TerminateAsync(string options)
+ {
+ return Task.Run(() =>
+ {
+ try
+ {
+ if (_windowsSandboxExeProcess == null || _windowsSandboxExeProcess.HasExited)
+ {
+ State = ComputeSystemState.Stopped;
+ return new ComputeSystemOperationResult();
+ }
+
+ GetWindowsSandboxClientProcess()?.Kill();
+ _windowsSandboxExeProcess.Kill();
+
+ return new ComputeSystemOperationResult();
+ }
+ catch (Exception ex)
+ {
+ State = ComputeSystemState.Unknown;
+
+ _log.Error(ex, "Failed to terminate Windows Sandbox");
+ TraceLogging.ExceptionThrown(ex);
+
+ return new ComputeSystemOperationResult(
+ ex,
+ Resources.GetResource("FailedToTerminateWindowsSandbox", _log),
+ "Failed to terminate Windows Sandbox");
+ }
+ }).AsAsyncOperation();
+ }
+
+ private IAsyncOperation NotImplemntedComputeSystemOperation()
+ {
+ NotImplementedException ex = new("This operation is not implemented.");
+ ComputeSystemOperationResult result = new(ex, Resources.GetResource("NotImplemented", _log), ex.Message);
+
+ return Task.FromResult(result).AsAsyncOperation();
+ }
+
+ public IApplyConfigurationOperation? CreateApplyConfigurationOperation(string configuration)
+ {
+ return null;
+ }
+
+ public IAsyncOperation CreateSnapshotAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation DeleteAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation DeleteSnapshotAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation ModifyPropertiesAsync(string inputJson) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation PauseAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation RestartAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation ResumeAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation RevertSnapshotAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation SaveAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation ShutDownAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public IAsyncOperation StartAsync(string options) => NotImplemntedComputeSystemOperation();
+
+ public void Dispose()
+ {
+ _windowsSandboxExeProcess?.Dispose();
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/extensions/WindowsSandboxExtension/Providers/WindowsSandboxProvider.cs b/extensions/WindowsSandboxExtension/Providers/WindowsSandboxProvider.cs
new file mode 100644
index 0000000000..fcf662b925
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Providers/WindowsSandboxProvider.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Management.Automation;
+using Microsoft.Windows.DevHome.SDK;
+using Serilog;
+using Windows.Foundation;
+using WindowsSandboxExtension.Helpers;
+
+namespace WindowsSandboxExtension.Providers;
+
+internal sealed class WindowsSandboxProvider : IComputeSystemProvider
+{
+ private readonly ILogger _log = Log.ForContext("SourceContext", nameof(WindowsSandboxProvider));
+
+ public string DisplayName => Constants.ProviderDisplayName;
+
+ public Uri Icon => new(Constants.ExtensionIcon);
+
+ public string Id => Constants.ProviderId;
+
+ public ComputeSystemProviderOperations SupportedOperations => ComputeSystemProviderOperations.None;
+
+ public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForComputeSystem(IComputeSystem computeSystem, ComputeSystemAdaptiveCardKind sessionKind)
+ {
+ return new ComputeSystemAdaptiveCardResult(
+ new NotImplementedException(),
+ Resources.GetResource("NotImplemented", _log),
+ "Create Windows Sandbox compute system is not implemented.");
+ }
+
+ public ComputeSystemAdaptiveCardResult CreateAdaptiveCardSessionForDeveloperId(IDeveloperId developerId, ComputeSystemAdaptiveCardKind sessionKind)
+ {
+ return new ComputeSystemAdaptiveCardResult(
+ new NotImplementedException(),
+ Resources.GetResource("NotImplemented", _log),
+ "Developer Id Adaptive Card session is not implmented for Windows Sandbox.");
+ }
+
+ public ICreateComputeSystemOperation? CreateCreateComputeSystemOperation(IDeveloperId developerId, string inputJson)
+ {
+ return null;
+ }
+
+ public IAsyncOperation GetComputeSystemsAsync(IDeveloperId developerId)
+ {
+ return Task.Run(() =>
+ {
+ List list = new();
+ list.Add(new WindowsSandboxComputeSystem());
+
+ return new ComputeSystemsResult(list);
+ }).AsAsyncOperation();
+ }
+}
diff --git a/extensions/WindowsSandboxExtension/Strings/en-US/Resources.resw b/extensions/WindowsSandboxExtension/Strings/en-US/Resources.resw
new file mode 100644
index 0000000000..c968db3346
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Strings/en-US/Resources.resw
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+ Failed to terminate Windows Sandbox
+ {Locked="Windows Sandbox"} Failed to terminate display message.
+
+
+ This operation is not yet implemented for Windows Sandbox
+
+
+ Windows Sandbox Environment
+ {Locked="Windows Sandbox"} The diplay name used for the default Windows Sandbox compute system.
+
+
+ Failed to start Windows Sandbox
+ {Locked="Windows Sandbox"} Failed to start display message.
+
+
\ No newline at end of file
diff --git a/extensions/WindowsSandboxExtension/Telemetry/TelemetryEventSource.cs b/extensions/WindowsSandboxExtension/Telemetry/TelemetryEventSource.cs
new file mode 100644
index 0000000000..40a98a0a48
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Telemetry/TelemetryEventSource.cs
@@ -0,0 +1,343 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+#if TELEMETRYEVENTSOURCE_USE_NUGET
+using Microsoft.Diagnostics.Tracing;
+#else
+using System.Diagnostics.Tracing;
+#endif
+using System;
+using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
+
+#pragma warning disable 3021 // 'type' does not need a CLSCompliant attribute
+
+namespace Microsoft.Diagnostics.Telemetry
+{
+ ///
+ ///
+ /// An EventSource with extra methods and constants commonly used in Microsoft's
+ /// TraceLogging-based ETW. This class inherits from EventSource, and is exactly
+ /// the same as EventSource except that it always enables
+ /// EtwSelfDescribingEventFormat and never uses traits. It also provides several
+ /// constants and helpers commonly used by Microsoft code.
+ ///
+ ///
+ /// Different versions of this class use different provider traits. The provider
+ /// traits in this class are empty. As a result, providers using this class will
+ /// not join any ETW Provider Groups and will not be given any special treatment
+ /// by group-sensitive ETW listeners.
+ ///
+ ///
+ /// When including this class in your project, you may define the following
+ /// conditional-compilation symbols to adjust the default behaviors:
+ ///
+ ///
+ /// TELEMETRYEVENTSOURCE_USE_NUGET - use Microsoft.Diagnostics.Tracing instead
+ /// of System.Diagnostics.Tracing.
+ ///
+ ///
+ /// TELEMETRYEVENTSOURCE_PUBLIC - define TelemetryEventSource as public instead
+ /// of internal.
+ ///
+ ///
+#if TELEMETRYEVENTSOURCE_PUBLIC
+ public
+#else
+ internal
+#endif
+ class TelemetryEventSource
+ : EventSource
+ {
+ ///
+ /// Keyword 0x0000100000000000 is reserved for future definition. Do
+ /// not use keyword 0x0000100000000000 in Microsoft-style ETW.
+ ///
+ public const EventKeywords Reserved44Keyword = (EventKeywords)0x0000100000000000;
+
+ ///
+ /// Add TelemetryKeyword to eventSourceOptions.Keywords to indicate that
+ /// an event is for general-purpose telemetry.
+ /// This keyword should not be combined with MeasuresKeyword or
+ /// CriticalDataKeyword.
+ ///
+ public const EventKeywords TelemetryKeyword = (EventKeywords)0x0000200000000000;
+
+ ///
+ /// Add MeasuresKeyword to eventSourceOptions.Keywords to indicate that
+ /// an event is for understanding measures and reporting scenarios.
+ /// This keyword should not be combined with TelemetryKeyword or
+ /// CriticalDataKeyword.
+ ///
+ public const EventKeywords MeasuresKeyword = (EventKeywords)0x0000400000000000;
+
+ ///
+ /// Add CriticalDataKeyword to eventSourceOptions.Keywords to indicate that
+ /// an event powers user experiences or is critical to business intelligence.
+ /// This keyword should not be combined with TelemetryKeyword or
+ /// MeasuresKeyword.
+ ///
+ public const EventKeywords CriticalDataKeyword = (EventKeywords)0x0000800000000000;
+
+ ///
+ /// Add CostDeferredLatency to eventSourceOptions.Tags to indicate that an event
+ /// should try to upload over free networks for a period of time before resorting
+ /// to upload over costed networks.
+ ///
+ public const EventTags CostDeferredLatency = (EventTags)0x040000;
+
+ ///
+ /// Add CoreData to eventSourceOptions.Tags to indicate that an event
+ /// contains high priority "core data".
+ ///
+ public const EventTags CoreData = (EventTags)0x00080000;
+
+ ///
+ /// Add InjectXToken to eventSourceOptions.Tags to indicate that an XBOX
+ /// identity token should be injected into the event before the event is
+ /// uploaded.
+ ///
+ public const EventTags InjectXToken = (EventTags)0x00100000;
+
+ ///
+ /// Add RealtimeLatency to eventSourceOptions.Tags to indicate that an event
+ /// should be transmitted in real time (via any available connection).
+ ///
+ public const EventTags RealtimeLatency = (EventTags)0x0200000;
+
+ ///
+ /// Add NormalLatency to eventSourceOptions.Tags to indicate that an event
+ /// should be transmitted via the preferred connection based on device policy.
+ ///
+ public const EventTags NormalLatency = (EventTags)0x0400000;
+
+ ///
+ /// Add CriticalPersistence to eventSourceOptions.Tags to indicate that an
+ /// event should be deleted last when low on spool space.
+ ///
+ public const EventTags CriticalPersistence = (EventTags)0x0800000;
+
+ ///
+ /// Add NormalPersistence to eventSourceOptions.Tags to indicate that an event
+ /// should be deleted first when low on spool space.
+ ///
+ public const EventTags NormalPersistence = (EventTags)0x1000000;
+
+ ///
+ /// Add DropPii to eventSourceOptions.Tags to indicate that an event contains
+ /// PII and should be anonymized by the telemetry client. If this tag is
+ /// present, PartA fields that might allow identification or cross-event
+ /// correlation will be removed from the event.
+ ///
+ public const EventTags DropPii = (EventTags)0x02000000;
+
+ ///
+ /// Add HashPii to eventSourceOptions.Tags to indicate that an event contains
+ /// PII and should be anonymized by the telemetry client. If this tag is
+ /// present, PartA fields that might allow identification or cross-event
+ /// correlation will be hashed (obfuscated).
+ ///
+ public const EventTags HashPii = (EventTags)0x04000000;
+
+ ///
+ /// Add MarkPii to eventSourceOptions.Tags to indicate that an event contains
+ /// PII but may be uploaded as-is. If this tag is present, the event will be
+ /// marked so that it will only appear on the private stream.
+ ///
+ public const EventTags MarkPii = (EventTags)0x08000000;
+
+ ///
+ /// Add DropPiiField to eventFieldAttribute.Tags to indicate that a field
+ /// contains PII and should be dropped by the telemetry client.
+ ///
+ public const EventFieldTags DropPiiField = (EventFieldTags)0x04000000;
+
+ ///
+ /// Add HashPiiField to eventFieldAttribute.Tags to indicate that a field
+ /// contains PII and should be hashed (obfuscated) prior to uploading.
+ ///
+ public const EventFieldTags HashPiiField = (EventFieldTags)0x08000000;
+
+ ///
+ /// Constructs a new instance of the TelemetryEventSource class with the
+ /// specified name. Sets the EtwSelfDescribingEventFormat option.
+ ///
+ /// The name of the event source.
+ [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")]
+ public TelemetryEventSource(
+ string eventSourceName)
+ : base(
+ eventSourceName,
+ EventSourceSettings.EtwSelfDescribingEventFormat)
+ {
+ return;
+ }
+
+ ///
+ /// For use by derived classes that set the eventSourceName via EventSourceAttribute.
+ /// Sets the EtwSelfDescribingEventFormat option.
+ ///
+ [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")]
+ protected TelemetryEventSource()
+ : base(
+ EventSourceSettings.EtwSelfDescribingEventFormat)
+ {
+ return;
+ }
+
+ ///
+ /// Constructs a new instance of the TelemetryEventSource class with the
+ /// specified name. Sets the EtwSelfDescribingEventFormat option.
+ ///
+ /// The name of the event source.
+ /// The parameter is not used.
+ [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")]
+ [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "API compatibility")]
+ public TelemetryEventSource(
+ string eventSourceName,
+ TelemetryGroup telemetryGroup)
+ : base(
+ eventSourceName,
+ EventSourceSettings.EtwSelfDescribingEventFormat)
+ {
+ return;
+ }
+
+ ///
+ /// Returns an instance of EventSourceOptions with the TelemetryKeyword set.
+ ///
+ /// Returns an instance of EventSourceOptions with the TelemetryKeyword set.
+ [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")]
+ public static EventSourceOptions TelemetryOptions()
+ {
+ return new EventSourceOptions { Keywords = TelemetryKeyword };
+ }
+
+ ///
+ /// Returns an instance of EventSourceOptions with the MeasuresKeyword set.
+ ///
+ /// Returns an instance of EventSourceOptions with the MeasuresKeyword set.
+ [SuppressMessage("Microsoft.Performance", "CA1811", Justification = "Shared class with tiny helper methods - not all constructors/methods are used by all consumers")]
+ public static EventSourceOptions MeasuresOptions()
+ {
+ return new EventSourceOptions { Keywords = MeasuresKeyword };
+ }
+ }
+
+ ///
+ ///
+ /// The PrivTags class defines privacy tags that can be used to specify the privacy
+ /// category of an event. Add a privacy tag as a field with name "PartA_PrivTags".
+ /// As a shortcut, you can use _1 as the field name, which will automatically be
+ /// expanded to "PartA_PrivTags" at runtime.
+ ///
+ ///
+ /// Multiple tags can be OR'ed together if necessary (rarely needed).
+ ///
+ ///
+ ///
+ /// Typical usage:
+ ///
+ /// es.Write("UsageEvent", new
+ /// {
+ /// _1 = PrivTags.ProductAndServiceUsage,
+ /// field1 = fieldValue1,
+ /// field2 = fieldValue2
+ /// });
+ ///
+ ///
+#if TELEMETRYEVENTSOURCE_PUBLIC
+ [CLSCompliant(false)]
+ public
+#else
+ internal
+#endif
+ static class PrivTags
+ {
+ ///
+ public const Internal.PartA_PrivTags BrowsingHistory = Internal.PartA_PrivTags.BrowsingHistory;
+
+ ///
+ public const Internal.PartA_PrivTags DeviceConnectivityAndConfiguration = Internal.PartA_PrivTags.DeviceConnectivityAndConfiguration;
+
+ ///
+ public const Internal.PartA_PrivTags InkingTypingAndSpeechUtterance = Internal.PartA_PrivTags.InkingTypingAndSpeechUtterance;
+
+ ///
+ public const Internal.PartA_PrivTags ProductAndServicePerformance = Internal.PartA_PrivTags.ProductAndServicePerformance;
+
+ ///
+ public const Internal.PartA_PrivTags ProductAndServiceUsage = Internal.PartA_PrivTags.ProductAndServiceUsage;
+
+ ///
+ public const Internal.PartA_PrivTags SoftwareSetupAndInventory = Internal.PartA_PrivTags.SoftwareSetupAndInventory;
+ }
+ ///
+ /// Pass a TelemetryGroup value to the constructor of TelemetryEventSource
+ /// to control which telemetry group should be joined.
+ /// Note: has no effect in this version of TelemetryEventSource.
+ ///
+#if TELEMETRYEVENTSOURCE_PUBLIC
+ public
+#else
+ internal
+#endif
+ enum TelemetryGroup
+ {
+ ///
+ /// The default group. Join this group to log normal, non-critical, non-coredata
+ /// events.
+ ///
+ MicrosoftTelemetry,
+
+ ///
+ /// Join this group to log CriticalData, CoreData, or other specially approved
+ /// events.
+ ///
+ WindowsCoreTelemetry
+ }
+
+#pragma warning disable SA1403 // File may only contain a single namespace
+ namespace Internal
+#pragma warning restore SA1403 // File may only contain a single namespace
+ {
+ ///
+ /// The complete list of privacy tags supported for events.
+ /// Multiple tags can be OR'ed together if an event belongs in multiple
+ /// categories.
+ /// Note that the PartA_PrivTags enum should not be used directly.
+ /// Instead, use values from the PrivTags class.
+ ///
+ [Flags]
+#if TELEMETRYEVENTSOURCE_PUBLIC
+ [CLSCompliant(false)]
+ public
+#else
+ internal
+#endif
+ enum PartA_PrivTags
+ : ulong
+ {
+ ///
+ None = 0,
+
+ ///
+ BrowsingHistory = 0x0000000000000002u,
+
+ ///
+ DeviceConnectivityAndConfiguration = 0x0000000000000800u,
+
+ ///
+ InkingTypingAndSpeechUtterance = 0x0000000000020000u,
+
+ ///
+ ProductAndServicePerformance = 0x0000000001000000u,
+
+ ///
+ ProductAndServiceUsage = 0x0000000002000000u,
+
+ ///
+ SoftwareSetupAndInventory = 0x0000000080000000u,
+ }
+ }
+}
diff --git a/extensions/WindowsSandboxExtension/Telemetry/TraceLogging.cs b/extensions/WindowsSandboxExtension/Telemetry/TraceLogging.cs
new file mode 100644
index 0000000000..e8a39e8b16
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/Telemetry/TraceLogging.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation.
+// 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 Microsoft.Diagnostics.Telemetry;
+
+namespace WindowsSandboxExtension.Telemetry;
+
+internal sealed class TraceLogging
+{
+ private const string ProviderName = "Microsoft.Windows.Containers.WindowsSandboxExtension";
+ private const string StartingEventName = "StartingWindowsSandbox";
+ private const string ExceptionThrownEventName = "ExceptionThrown";
+
+ private static readonly TelemetryEventSource EventSource = new(ProviderName);
+
+ public static void StartingWindowsSandbox()
+ {
+ var options = TelemetryEventSource.MeasuresOptions();
+ options.Level = EventLevel.Informational;
+
+ EventSource.Write(StartingEventName, options);
+ }
+
+ public static void ExceptionThrown(Exception exception)
+ {
+ var options = TelemetryEventSource.MeasuresOptions();
+ options.Level = EventLevel.Error;
+
+ EventSource.Write(
+ ExceptionThrownEventName,
+ options,
+ new
+ {
+ name = exception.GetType().Name,
+ stackTrace = exception.StackTrace,
+ innerName = exception.InnerException?.GetType().Name,
+ innerMessage = exception.InnerException?.Message,
+ innerStackTrace = exception.InnerException?.ToString(),
+ message = exception.Message,
+ });
+ }
+}
diff --git a/extensions/WindowsSandboxExtension/WindowsSandboxExtension.cs b/extensions/WindowsSandboxExtension/WindowsSandboxExtension.cs
new file mode 100644
index 0000000000..495dec4e7f
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/WindowsSandboxExtension.cs
@@ -0,0 +1,88 @@
+// 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 WindowsSandboxExtension.Helpers;
+
+namespace WindowsSandboxExtension;
+
+[ComVisible(true)]
+[Guid("6A52115B-083C-4FB1-85F4-BBE23289220E")]
+[ComDefaultInterface(typeof(IExtension))]
+internal sealed class WindowsSandboxExtension : IExtension, IDisposable
+{
+ private readonly IHost _host;
+ private readonly ILogger _logger;
+ private bool _disposed;
+
+ public WindowsSandboxExtension(IHost host)
+ {
+ _host = host;
+ _logger = Log.ForContext("SourceContext", nameof(WindowsSandboxExtension));
+ }
+
+ public ManualResetEvent ExtensionDisposedEvent { get; } = new(false);
+
+ public object? GetProvider(ProviderType providerType)
+ {
+ object? provider = null;
+
+ try
+ {
+ switch (providerType)
+ {
+ case ProviderType.ComputeSystem:
+
+ provider = GetComputeSystemProvider();
+ break;
+ default:
+ _logger.Information($"Unsupported provider: {providerType}");
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex, $"Failed to get provider for provider type {providerType}");
+ }
+
+ return provider;
+ }
+
+ private object? GetComputeSystemProvider()
+ {
+ if (DependencyChecker.IsNewWindowsSandboxExtensionInstalled())
+ {
+ _logger.Information("New Windows Sandbox appx package is installed.");
+ return null;
+ }
+
+ if (!DependencyChecker.IsOptionalComponentEnabled())
+ {
+ _logger.Information("Windows Sandbox optional component is not enabled.");
+ return null;
+ }
+
+ return _host.Services.GetService(typeof(IComputeSystemProvider));
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ ExtensionDisposedEvent.Set();
+ }
+
+ _disposed = true;
+ }
+ }
+}
diff --git a/extensions/WindowsSandboxExtension/WindowsSandboxExtension.csproj b/extensions/WindowsSandboxExtension/WindowsSandboxExtension.csproj
new file mode 100644
index 0000000000..4738e476b5
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/WindowsSandboxExtension.csproj
@@ -0,0 +1,48 @@
+
+
+
+
+ Exe
+
+
+ WinExe
+
+
+
+ enable
+ enable
+ false
+ false
+ win-x86;win-x64;win-arm64
+ WindowsSandboxExtension.Program
+ x86;x64;arm64
+ $(SolutionDir)\src\Properties\PublishProfiles\win-$(Platform).pubxml
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
diff --git a/extensions/WindowsSandboxExtension/appsettings_WindowsSandbox.json b/extensions/WindowsSandboxExtension/appsettings_WindowsSandbox.json
new file mode 100644
index 0000000000..c26d22a19c
--- /dev/null
+++ b/extensions/WindowsSandboxExtension/appsettings_WindowsSandbox.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": "%DEVHOME_LOGS_ROOT%\\WindowsSandbox.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": "WindowsSandboxExtension"
+ }
+ }
+}
\ No newline at end of file
diff --git a/settings/DevHome.Settings/Strings/en-us/Resources.resw b/settings/DevHome.Settings/Strings/en-us/Resources.resw
index 812608237b..f13444365a 100644
--- a/settings/DevHome.Settings/Strings/en-us/Resources.resw
+++ b/settings/DevHome.Settings/Strings/en-us/Resources.resw
@@ -1,17 +1,17 @@
-
@@ -547,11 +547,19 @@
Quiet background processes
- Name of experimental feature 'Quiet background processes' on the 'Settings -> Experiments' page where you enable it.
+ Name of experimental feature 'Quiet background processes' on the 'Settings -> Experiments' page where you enable it.
Quiet background processes allows you to free up resources while developing
- Inline description of the Quiet background processes experimental feature on the 'Settings -> Experiments' page where you enable it.
+ Inline description of the Quiet background processes experimental feature on the 'Settings -> Experiments' page where you enable it.
+
+
+ Project Ironsides
+ 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
+ Inline description of the Project Ironsides experimental feature on the 'Settings -> Experiments' page where you enable it.
Quickstart Playground
diff --git a/settings/DevHome.Settings/Views/AboutPage.xaml b/settings/DevHome.Settings/Views/AboutPage.xaml
index 6aceb5db4f..b486092cf2 100644
--- a/settings/DevHome.Settings/Views/AboutPage.xaml
+++ b/settings/DevHome.Settings/Views/AboutPage.xaml
@@ -1,10 +1,11 @@
-
@@ -30,13 +31,13 @@
-
+
diff --git a/settings/DevHome.Settings/Views/AboutPage.xaml.cs b/settings/DevHome.Settings/Views/AboutPage.xaml.cs
index 743dc23bb1..79b8c342c0 100644
--- a/settings/DevHome.Settings/Views/AboutPage.xaml.cs
+++ b/settings/DevHome.Settings/Views/AboutPage.xaml.cs
@@ -5,14 +5,14 @@
using System.Diagnostics;
using CommunityToolkit.Mvvm.Input;
using DevHome.Common.Extensions;
+using DevHome.Common.Views;
using DevHome.Settings.ViewModels;
using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
using Serilog;
namespace DevHome.Settings.Views;
-public sealed partial class AboutPage : Page
+public sealed partial class AboutPage : DevHomePage
{
public AboutViewModel ViewModel { get; }
@@ -20,17 +20,6 @@ public AboutPage()
{
ViewModel = Application.Current.GetService();
this.InitializeComponent();
-
-#if DEBUG
- Loaded += ShowViewLogsButton;
-#endif
- }
-
-#if DEBUG
- private void ShowViewLogsButton(object sender, RoutedEventArgs e)
- {
- ViewLogsSettingsCard.Visibility = Visibility.Visible;
- ViewLogsSettingsCard.Command = OpenLogsLocationCommand;
}
[RelayCommand]
@@ -47,5 +36,4 @@ private void OpenLogsLocation()
log.Error(e, $"Error opening log location");
}
}
-#endif
}
diff --git a/settings/DevHome.Settings/Views/AccountsPage.xaml b/settings/DevHome.Settings/Views/AccountsPage.xaml
index 90ebc7febc..5b65c8758b 100644
--- a/settings/DevHome.Settings/Views/AccountsPage.xaml
+++ b/settings/DevHome.Settings/Views/AccountsPage.xaml
@@ -1,4 +1,4 @@
-
@@ -14,9 +15,7 @@
-
-
-
+
@@ -41,12 +40,19 @@
-
+
+ Configuration in progress
+ Title displayed to the user when they attempt to activate the app through a URI but the machine configuration flow is currently in progress.
+
Transfer developer machine settings
Title text of an instruction banner section for transferring settings from a developer machine
@@ -546,7 +550,7 @@
Body text description for a card than when clicked takes the user to a multi-step flow for setting up their machine
- [Experimental] Use natural language and AI to set up a new devcontainer-based programming environment
+ [Experimental] Use natural language and AI to set up a new devcontainer-based programming environment.
Body text description for a card than when clicked takes the user to a flow for creating a devcontainer environment for their project idea
@@ -657,7 +661,7 @@
Generate Configuration file
Text for a generating configuration file button
-
+
Generate a WinGet Configuration file (.winget) to repeat this set up in the future or share it with others.
{Locked="WinGet",".winget"}Tooltip text about the generated configuration file
@@ -1199,6 +1203,10 @@
Enter the full path and folder name
Error message to tell the user to fully qualify their clone path
+
+ The URI activation flow cannot be initiated while machine configuration is in progress. Please complete or cancel your current configuration tasks and then try again.
+ Message displayed to the user when they attempt to activate the app through a URI but the machine configuration flow is currently in progress.
+
Enter a repository name beginning with https://
Error string to show the user if the url is not an absolute URL
@@ -1793,7 +1801,7 @@
Header for a card that when clicked takes the user to a multi-step flow for creating an environment
- Create a local or cloud environment
+ Create a local or cloud environment.
Body text description for a card than when clicked takes the user to a multi-step flow for creating an environment
@@ -2003,4 +2011,12 @@
Error: {0}
Locked={"{0}"} Error message with additional details in {0} shown when we failed to generate the project.
+
+ Version
+ Name for the version combo box
+
+
+ Version
+ Name for the version combo box
+
\ No newline at end of file
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Styles/AppManagement_ThemeResources.xaml b/tools/SetupFlow/DevHome.SetupFlow/Styles/AppManagement_ThemeResources.xaml
index 213fa6b626..da05f591b5 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Styles/AppManagement_ThemeResources.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Styles/AppManagement_ThemeResources.xaml
@@ -77,10 +77,11 @@
-
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/AppManagementTaskGroup.cs b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/AppManagementTaskGroup.cs
index 28b7c62939..781a2b2ec7 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/AppManagementTaskGroup.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/AppManagementTaskGroup.cs
@@ -39,11 +39,6 @@ public AppManagementTaskGroup(
public void HandleSearchQuery(string query)
{
- var searchParameter = HttpUtility.ParseQueryString(query)["search"];
- if (!string.IsNullOrEmpty(searchParameter))
- {
- var trimmedSearchParameter = searchParameter.Trim('\"');
- _appManagementViewModel.PerformSearch(trimmedSearchParameter);
- }
+ _appManagementViewModel.PerformSearch(query);
}
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SetupTargetTaskGroup.cs b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SetupTargetTaskGroup.cs
index 4bd1bf8489..7bef0ace60 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SetupTargetTaskGroup.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/TaskGroups/SetupTargetTaskGroup.cs
@@ -6,7 +6,7 @@
using DevHome.SetupFlow.Models;
using DevHome.SetupFlow.Services;
using DevHome.SetupFlow.ViewModels;
-using WinUIEx;
+using Microsoft.UI.Dispatching;
namespace DevHome.SetupFlow.TaskGroups;
@@ -24,7 +24,7 @@ public SetupTargetTaskGroup(
IComputeSystemManager computeSystemManager,
ConfigurationFileBuilder configurationFileBuilder,
SetupFlowOrchestrator setupFlowOrchestrator,
- WindowEx windowEx)
+ DispatcherQueue dispatcherQueue)
{
_setupTargetViewModel = setupTargetViewModel;
_setupTargetReviewViewModel = setupTargetReviewViewModel;
@@ -34,7 +34,7 @@ public SetupTargetTaskGroup(
computeSystemManager,
configurationFileBuilder,
setupFlowOrchestrator,
- windowEx);
+ dispatcherQueue);
}
///
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs
index cf2d67c7f6..1e96653859 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/AddRepoViewModel.cs
@@ -23,12 +23,12 @@
using DevHome.Telemetry;
using Microsoft.Extensions.Hosting;
using Microsoft.UI;
+using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
using Windows.Foundation;
-using WinUIEx;
using static DevHome.SetupFlow.Models.Common;
namespace DevHome.SetupFlow.ViewModels;
@@ -445,6 +445,44 @@ private void RepoProviderSelected(string repositoryProviderName)
_selectedRepoProvider = repositoryProviderName;
}
+ ///
+ /// Adds or removes the default dev drive. This dev drive will be made at the loading screen.
+ ///
+ [RelayCommand]
+ private void MakeNewDevDrive(bool isCheckBoxChecked)
+ {
+ // Getting here means
+ // 1. The user does not have any existing dev drives
+ // 2. The user wants to clone to a new dev drive.
+ // 3. The user un-checked this and does not want a new dev drive.
+ if (isCheckBoxChecked)
+ {
+ UpdateDevDriveInfo();
+ }
+ else
+ {
+ FolderPickerViewModel.CloneLocationAlias = string.Empty;
+ FolderPickerViewModel.InDevDriveScenario = false;
+ EditDevDriveViewModel.RemoveNewDevDrive();
+ FolderPickerViewModel.EnableBrowseButton();
+ FolderPickerViewModel.CloneLocation = _addRepoDialog.OldCloneLocation;
+ }
+ }
+
+ ///
+ /// Update dialog to show Dev Drive information.
+ ///
+ public void UpdateDevDriveInfo()
+ {
+ EditDevDriveViewModel.MakeDefaultDevDrive();
+ FolderPickerViewModel.DisableBrowseButton();
+ _addRepoDialog.OldCloneLocation = FolderPickerViewModel.CloneLocation;
+ FolderPickerViewModel.CloneLocation = EditDevDriveViewModel.GetDriveDisplayName();
+ FolderPickerViewModel.CloneLocationAlias = EditDevDriveViewModel.GetDriveDisplayName(DevDriveDisplayNameKind.FormattedDriveLabelKind);
+ FolderPickerViewModel.InDevDriveScenario = true;
+ EditDevDriveViewModel.IsDevDriveCheckboxChecked = true;
+ }
+
[RelayCommand]
private void CancelButtonPressed()
{
@@ -691,7 +729,7 @@ public AddRepoViewModel(
_addRepoDialog = addRepoDialog;
_stringResource = stringResource;
_host = host;
- _dispatcherQueue = host.GetService().DispatcherQueue;
+ _dispatcherQueue = host.GetService();
_loginUiContent = new Frame();
_setupFlowOrchestrator = setupFlowOrchestrator;
@@ -1066,6 +1104,7 @@ public void AddOrRemoveRepository(string accountName, IList
public void UpdateExtensionAdaptiveCard(ComputeSystemAdaptiveCardResult adaptiveCardSessionResult)
{
- _windowEx.DispatcherQueue.TryEnqueue(() =>
+ _dispatcherQueue.TryEnqueue(() =>
{
try
{
@@ -221,7 +221,7 @@ public void UpdateExtensionAdaptiveCard(ComputeSystemAdaptiveCardResult adaptive
///
public void OnAdaptiveCardUpdated(object sender, AdaptiveCard adaptiveCard)
{
- _windowEx.DispatcherQueue.TryEnqueue(() =>
+ _dispatcherQueue.TryEnqueue(() =>
{
// Render the adaptive card and set the action event handler.
_renderedAdaptiveCard = _adaptiveCardRenderer.RenderAdaptiveCard(adaptiveCard);
@@ -247,7 +247,7 @@ public void OnAdaptiveCardUpdated(object sender, AdaptiveCard adaptiveCard)
/// The action and user inputs from within the adaptive card
private void OnRenderedAdaptiveCardAction(object sender, AdaptiveActionEventArgs args)
{
- _windowEx.DispatcherQueue.TryEnqueue(async () =>
+ _dispatcherQueue.TryEnqueue(async () =>
{
IsAdaptiveCardSessionLoaded = false;
AdaptiveCardLoadingMessage = StringResource.GetLocalized(StringResourceKey.EnvironmentCreationAdaptiveCardLoadingMessage, _curProviderDetails.ComputeSystemProvider.DisplayName);
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs
index 4ff1b10fd7..3e7d246534 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/Environments/SelectEnvironmentProviderViewModel.cs
@@ -1,25 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
-using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Behaviors;
using DevHome.Common.Contracts.Services;
using DevHome.Common.Environments.Helpers;
using DevHome.Common.Environments.Models;
-using DevHome.Common.Environments.Services;
using DevHome.Common.Services;
using DevHome.SetupFlow.Models.Environments;
using DevHome.SetupFlow.Services;
-using Microsoft.UI.Xaml;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
-using WinUIEx;
namespace DevHome.SetupFlow.ViewModels.Environments;
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs
index f4e5b397c9..b515822989 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/FolderPickerViewModel.cs
@@ -10,7 +10,6 @@
using DevHome.SetupFlow.Services;
using Microsoft.UI.Xaml;
using Serilog;
-using WinUIEx;
namespace DevHome.SetupFlow.ViewModels;
@@ -131,7 +130,7 @@ private async Task PickCloneDirectoryAsync()
{
_log.Information("Opening folder picker to select clone directory");
using var folderPicker = new WindowOpenFolderDialog();
- var locationToCloneTo = await folderPicker.ShowAsync(Application.Current.GetService());
+ var locationToCloneTo = await folderPicker.ShowAsync(Application.Current.GetService());
if (locationToCloneTo != null && locationToCloneTo.Path.Length > 0)
{
_log.Information($"Selected '{locationToCloneTo.Path}' as location to clone to");
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingMessageViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingMessageViewModel.cs
index 3e6c248c36..358999c7d6 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingMessageViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingMessageViewModel.cs
@@ -18,18 +18,6 @@ public partial class LoadingMessageViewModel : ObservableObject
[ObservableProperty]
private string _messageToShow;
- ///
- /// If the progress ring should be shown. Only show a progress ring when the task is running.
- ///
- [ObservableProperty]
- private bool _shouldShowProgressRing;
-
- ///
- /// The status symbol icon is the red, green, or yellow icon that is next to a task when it has been completed.
- ///
- [ObservableProperty]
- private bool _shouldShowStatusSymbolIcon;
-
///
/// The icon to display in the loading screen after a task is finished.
///
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs
index e3e465f58b..cf2fa99f0a 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/LoadingViewModel.cs
@@ -18,10 +18,10 @@
using DevHome.SetupFlow.TaskGroups;
using DevHome.Telemetry;
using Microsoft.Extensions.Hosting;
+using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Imaging;
using Serilog;
-using WinUIEx;
namespace DevHome.SetupFlow.ViewModels;
@@ -44,9 +44,7 @@ public partial class LoadingViewModel : SetupPageViewModelBase
private static readonly BitmapImage LightSuccess = new(new Uri("ms-appx:///DevHome.SetupFlow/Assets/LightSuccess.png"));
private static readonly BitmapImage LightInfo = new(new Uri("ms-appx:///DevHome.SetupFlow/Assets/LightInfo.png"));
-#pragma warning disable SA1310 // Field names should not contain underscore
- private const int MAX_RETRIES = 1;
-#pragma warning restore SA1310 // Field names should not contain underscore
+ private const int MaxRetries = 1;
private int _retryCount;
@@ -77,7 +75,10 @@ public partial class LoadingViewModel : SetupPageViewModelBase
private ObservableCollection _tasksToRun;
[ObservableProperty]
- private ObservableCollection _messages;
+ private ObservableCollection _executingMessages;
+
+ [ObservableProperty]
+ private ObservableCollection _nonExecutingMessages;
[ObservableProperty]
private ObservableCollection _summaryInformation;
@@ -216,43 +217,37 @@ public void GoToSummaryPage()
public void AddMessage(string message, MessageSeverityKind severityKind = MessageSeverityKind.Info)
{
- Application.Current.GetService().DispatcherQueue.TryEnqueue(() =>
+ Application.Current.GetService().TryEnqueue(() =>
{
var messageToDisplay = _host.GetService();
messageToDisplay.MessageToShow = message;
- messageToDisplay.ShouldShowStatusSymbolIcon = false;
- messageToDisplay.ShouldShowProgressRing = false;
if (severityKind == MessageSeverityKind.Warning)
{
- messageToDisplay.ShouldShowStatusSymbolIcon = true;
messageToDisplay.StatusSymbolIcon = (_currentTheme == ElementTheme.Dark) ? DarkCaution : LightCaution;
}
else if (severityKind == MessageSeverityKind.Error)
{
- messageToDisplay.ShouldShowStatusSymbolIcon = true;
messageToDisplay.StatusSymbolIcon = (_currentTheme == ElementTheme.Dark) ? DarkError : LightError;
}
else if (severityKind == MessageSeverityKind.Success)
{
- messageToDisplay.ShouldShowStatusSymbolIcon = true;
messageToDisplay.StatusSymbolIcon = (_currentTheme == ElementTheme.Dark) ? DarkSuccess : LightSuccess;
}
else if (severityKind == MessageSeverityKind.Info)
{
- messageToDisplay.ShouldShowStatusSymbolIcon = true;
messageToDisplay.StatusSymbolIcon = (_currentTheme == ElementTheme.Dark) ? DarkInfo : LightInfo;
}
- Messages.Insert(Messages.Count - _numberOfExecutingTasks, messageToDisplay);
+ NonExecutingMessages.Add(messageToDisplay);
});
}
public void UpdateActionCenterMessage(ActionCenterMessages message, ActionMessageRequestKind requestKind)
{
- // ALl referenced to WindowEx and Application.Current will be removed in the future,
- // in the loadingViewModel.
- Application.Current.GetService().DispatcherQueue.TryEnqueue(() =>
+ // All references to DispatcherQueue and Application.Current will be removed in the future,
+ // in the LoadingViewModel.
+ Application.Current.GetService().TryEnqueue(() =>
{
// We need to add/remove the message in a temporary list and then re add the items to a new collection. This is because
// of the adaptive card panel and it receiving UI updates in the listview. There can be random crashes if we don't do this when
@@ -314,7 +309,8 @@ public LoadingViewModel(
ShowRetryButton = Visibility.Collapsed;
_failedTasks = new List();
ActionCenterItems = new();
- Messages = new();
+ ExecutingMessages = new();
+ NonExecutingMessages = new();
_activityId = orchestrator.ActivityId;
_summaryInformation = new ObservableCollection();
}
@@ -377,73 +373,109 @@ private void SetExecutingTaskAndActionCenter()
}
///
- /// Changes the internals of information according to the taskFinishedState.
+ /// Uses information and the task state to figure out what message needs to be placed into the loading screen.
///
- /// The information that will change.
- /// The status of the task.
- ///
- /// TaskInformation is an ObservableObject inside an ObservableCollection. Any changes to information
- /// will change the UI.
- ///
- private void ChangeMessage(TaskInformation information, LoadingMessageViewModel loadingMessage, TaskFinishedState taskFinishedState)
+ /// Used to know if the computer needs to reboot.
+ /// The state of the finished task.
+ /// A LoadingMessageViewModel that can be placed into the UI.
+ private LoadingMessageViewModel GenerateFinishedMessage(TaskInformation information, TaskFinishedState finishedState)
{
- _log.Debug($"Updating message for task {information.MessageToShow} with state {taskFinishedState}");
+ _log.Debug($"Updating message for task {information.MessageToShow} with state {finishedState}");
var stringToReplace = string.Empty;
BitmapImage statusSymbolIcon = null;
// Two things to do.
// 1. Change the message color and icon in information
// 2. Add a new message with the done message.
- if (taskFinishedState == TaskFinishedState.Success)
+ if (finishedState == TaskFinishedState.Success)
{
if (information.TaskToExecute.RequiresReboot)
{
- _log.Debug("Task succeeded but requires reboot; adding to action center");
stringToReplace = information.TaskToExecute.GetLoadingMessages().NeedsReboot;
statusSymbolIcon = (_currentTheme == ElementTheme.Dark) ? DarkCaution : LightCaution;
- ActionCenterItems.Insert(0, information.TaskToExecute.GetRebootMessage());
}
else
{
- _log.Debug("Task succeeded");
stringToReplace = information.TaskToExecute.GetLoadingMessages().Finished;
statusSymbolIcon = (_currentTheme == ElementTheme.Dark) ? DarkSuccess : LightSuccess;
}
+ }
+ else if (finishedState == TaskFinishedState.Failure)
+ {
+ stringToReplace = information.TaskToExecute.GetLoadingMessages().Error;
+ statusSymbolIcon = (_currentTheme == ElementTheme.Dark) ? DarkError : LightError;
+ }
+
+ var newLoadingScreenMessage = _host.GetService();
+ newLoadingScreenMessage.MessageToShow = stringToReplace;
+ newLoadingScreenMessage.StatusSymbolIcon = statusSymbolIcon;
+
+ return newLoadingScreenMessage;
+ }
+
+ ///
+ /// Updates the loading screen task logging UI to show that the task is finished.
+ ///
+ /// The executing message.
+ /// The finished message.
+ private void InsertFinishedMessageIntoLogScreen(LoadingMessageViewModel originalMessage, LoadingMessageViewModel finishedMessage)
+ {
+ // Remove the executing message from the list.
+ ExecutingMessages.Remove(originalMessage);
+
+ // Insert the same message. All messages in this list have their foreground set to
+ // secondary.
+ NonExecutingMessages.Add(originalMessage);
+
+ // Add the execution finished message
+ NonExecutingMessages.Add(finishedMessage);
+ }
+
+ ///
+ /// Adds an item to the ActionCenterItems collection if needs be. Updates task counters.
+ ///
+ /// Used to know if the computer needs to reboot.
+ /// The status of the finished task.
+ private void PostTaskUiUpdate(TaskInformation information, TaskFinishedState taskFinishedState)
+ {
+ if (taskFinishedState == TaskFinishedState.Success)
+ {
+ if (information.TaskToExecute.RequiresReboot)
+ {
+ _log.Debug("Task succeeded but requires reboot; adding to action center");
+ ActionCenterItems.Insert(0, information.TaskToExecute.GetRebootMessage());
+ }
+ else
+ {
+ _log.Debug("Task succeeded");
+ }
TasksFinishedSuccessfully++;
}
else if (taskFinishedState == TaskFinishedState.Failure)
{
_log.Debug("Task failed");
- stringToReplace = information.TaskToExecute.GetLoadingMessages().Error;
- statusSymbolIcon = (_currentTheme == ElementTheme.Dark) ? DarkError : LightError;
ActionCenterItems.Insert(0, information.TaskToExecute.GetErrorMessages());
TasksFailed++;
_log.Debug("Adding task to list for retry");
_failedTasks.Add(information);
}
+ }
- // When a task is done
- // Following logic is to keep all "executing" messages at the bottom of the list.
- // Remove the "executing" message from the list.
- Messages.Remove(loadingMessage);
-
- // Modify the message so it looks done.
- loadingMessage.ShouldShowProgressRing = false;
-
- // Insert the message right before any "executing" messages.
- Messages.Insert(Messages.Count - _numberOfExecutingTasks, loadingMessage);
-
- // Add the "Execution finished" message
- var newLoadingScreenMessage = _host.GetService();
- newLoadingScreenMessage.MessageToShow = stringToReplace;
- newLoadingScreenMessage.StatusSymbolIcon = statusSymbolIcon;
- newLoadingScreenMessage.ShouldShowProgressRing = false;
- newLoadingScreenMessage.ShouldShowStatusSymbolIcon = true;
-
- // Insert the message right before any "executing" messages.
- Messages.Insert(Messages.Count - _numberOfExecutingTasks, newLoadingScreenMessage);
+ ///
+ /// Updates the Action Center, Inserts the finished message, clears out the executing message,
+ /// and updates any task counters.
+ ///
+ /// Information on the task. Used to figure out if a reboot is needed.
+ /// The executing messages placed into the Loading Screen. This
+ /// is used to find the executing message to remove.
+ /// The state of the task. Used for a variety of things.
+ private void PerformPostTaskTasks(TaskInformation information, LoadingMessageViewModel originalMessage, TaskFinishedState finishedState)
+ {
+ PostTaskUiUpdate(information, finishedState);
+ var finishedMessage = GenerateFinishedMessage(information, finishedState);
+ InsertFinishedMessageIntoLogScreen(originalMessage, finishedMessage);
}
///
@@ -463,7 +495,7 @@ protected async override Task OnFirstNavigateToAsync()
private async Task StartAllTasks(ObservableCollection tasks)
{
_log.Information("Starting all tasks");
- var window = Application.Current.GetService();
+ var dispatcherQueue = Application.Current.GetService();
await Task.Run(async () =>
{
var tasksToRunFirst = new List();
@@ -487,13 +519,13 @@ await Task.Run(async () =>
// Run all tasks that don't need dev drive installed.
await Parallel.ForEachAsync(tasksToRunFirst, async (taskInformation, token) =>
{
- await StartTaskAndReportResult(window, taskInformation);
+ await StartTaskAndReportResult(dispatcherQueue, taskInformation);
});
// Run all the tasks that need dev drive installed.
await Parallel.ForEachAsync(tasksToRunSecond, async (taskInformation, token) =>
{
- await StartTaskAndReportResult(window, taskInformation);
+ await StartTaskAndReportResult(dispatcherQueue, taskInformation);
});
});
@@ -505,7 +537,7 @@ await Parallel.ForEachAsync(tasksToRunSecond, async (taskInformation, token) =>
_log.Information("All tasks succeeded. Moving to next page");
ExecutionFinished.Invoke(null, null);
}
- else if (_retryCount >= MAX_RETRIES)
+ else if (_retryCount >= MaxRetries)
{
_log.Information("Max number of retries reached; moving to next page");
ShowOutOfRetriesBanner = true;
@@ -530,17 +562,19 @@ await Parallel.ForEachAsync(tasksToRunSecond, async (taskInformation, token) =>
///
/// Runs the specified task and updates the UI when the task is finished.
///
- /// Used to get access to the dispatcher queue.
+ /// Dispatcher queue associated with the window for the task.
/// Information about the task to execute. Will be modified
/// An awaitable task
- private async Task StartTaskAndReportResult(WinUIEx.WindowEx window, TaskInformation taskInformation)
+ private async Task StartTaskAndReportResult(DispatcherQueue dispatcherQueue, TaskInformation taskInformation)
{
+ // loadingMessage is used in the catch.
+ var loadingMessage = _host.GetService();
+ loadingMessage.MessageToShow = taskInformation.MessageToShow;
+
// Start the task and wait for it to complete.
try
{
- var loadingMessage = _host.GetService();
- loadingMessage.MessageToShow = taskInformation.MessageToShow;
- window.DispatcherQueue.TryEnqueue(() =>
+ dispatcherQueue.TryEnqueue(() =>
{
TasksStarted++;
if (!Orchestrator.IsSettingUpATargetMachine)
@@ -552,10 +586,9 @@ private async Task StartTaskAndReportResult(WinUIEx.WindowEx window, TaskInforma
SetupTargetText = StringResource.GetLocalized(StringResourceKey.LoadingPageSetupTargetText, taskInformation.TaskToExecute.TargetName);
}
- loadingMessage.ShouldShowProgressRing = true;
- Messages.Add(loadingMessage);
+ ExecutingMessages.Add(loadingMessage);
- // Keep increment inside TryEnqueue to enforce "locking"
+ // Keep increment inside TryEnqueue to enforce locking
_numberOfExecutingTasks++;
});
@@ -570,20 +603,45 @@ private async Task StartTaskAndReportResult(WinUIEx.WindowEx window, TaskInforma
taskFinishedState = await taskInformation.TaskToExecute.Execute();
}
- window.DispatcherQueue.TryEnqueue(() =>
+ dispatcherQueue.TryEnqueue(() =>
{
- // Keep decrement inside TryEnqueue to enforce "locking"
- _numberOfExecutingTasks--;
- ChangeMessage(taskInformation, loadingMessage, taskFinishedState);
- TasksCompleted++;
- ActionCenterDisplay = StringResource.GetLocalized(StringResourceKey.ActionCenterDisplay, TasksFailed);
+ PerformPostTaskTasks(taskInformation, loadingMessage, taskFinishedState);
});
}
- catch
+ catch (Exception e)
{
- // Don't let a single task break everything
- // TODO: Show failed tasks on UI
- // https://github.com/microsoft/devhome/issues/629
+ dispatcherQueue.TryEnqueue(() =>
+ {
+ // This code block mostly duplicates logic in PerformPostTaskTasks.
+ // The difference is the message isn't stored in the task.
+ // PerformPostTaskTasks uses information inside LoadingMessageViewModel
+ // and TaskInformation to determine what message to show.
+ // Until those two peices of information are de-coupled this code should stay here.
+ var newLoadingMessage = _host.GetService();
+ newLoadingMessage.MessageToShow = $"Could not finish {taskInformation.MessageToShow} because {e.Message}";
+ newLoadingMessage.StatusSymbolIcon = (_currentTheme == ElementTheme.Dark) ? DarkError : LightError;
+
+ InsertFinishedMessageIntoLogScreen(loadingMessage, newLoadingMessage);
+
+ TasksFailed++;
+
+ _log.Debug("Adding task to list for retry");
+ _failedTasks.Add(taskInformation);
+
+ var actionCenterErrorMessage = new ActionCenterMessages();
+ actionCenterErrorMessage.PrimaryMessage = e.Message;
+ ActionCenterItems.Insert(0, actionCenterErrorMessage);
+ });
+
+ _log.Error(e, $"Could not finish all tasks.");
}
+
+ dispatcherQueue.TryEnqueue(() =>
+ {
+ // Keep decrement inside TryEnqueue to enforce locking
+ _numberOfExecutingTasks--;
+ TasksCompleted++;
+ ActionCenterDisplay = StringResource.GetLocalized(StringResourceKey.ActionCenterDisplay, TasksFailed);
+ });
}
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs
index 915400e78f..770dc897b3 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/MainPageViewModel.cs
@@ -145,11 +145,11 @@ public async Task StartConfigurationFileAsync(StorageFile file)
}
}
- internal void StartAppManagementFlow(string query)
+ internal void StartAppManagementFlow(string query = null)
{
- _log.Information($"Launching app management flow for query:{query}");
+ _log.Information("Launching app management flow");
var appManagementSetupFlow = _host.GetService();
- StartSetupFlowForTaskGroups(null, "App Search URI", appManagementSetupFlow);
+ StartSetupFlowForTaskGroups(null, "App Activation URI", appManagementSetupFlow);
appManagementSetupFlow.HandleSearchQuery(query);
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs
index f8728f3722..6119abb93e 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageCatalogListViewModel.cs
@@ -14,8 +14,8 @@
using DevHome.SetupFlow.Behaviors;
using DevHome.SetupFlow.Services;
using DevHome.Telemetry;
+using Microsoft.UI.Dispatching;
using Serilog;
-using WinUIEx;
namespace DevHome.SetupFlow.ViewModels;
@@ -25,7 +25,7 @@ public partial class PackageCatalogListViewModel : ObservableObject, IDisposable
private readonly ICatalogDataSourceLoader _catalogDataSourceLoader;
private readonly IExtensionService _extensionService;
private readonly PackageCatalogViewModelFactory _packageCatalogViewModelFactory;
- private readonly WindowEx _windowEx;
+ private readonly DispatcherQueue _dispatcherQueue;
private readonly SemaphoreSlim _loadCatalogsSemaphore = new(1, 1);
[ObservableProperty]
@@ -56,10 +56,10 @@ public PackageCatalogListViewModel(
IExtensionService extensionService,
ICatalogDataSourceLoader catalogDataSourceLoader,
PackageCatalogViewModelFactory packageCatalogViewModelFactory,
- WindowEx windowEx)
+ DispatcherQueue dispatcherQueue)
{
_extensionService = extensionService;
- _windowEx = windowEx;
+ _dispatcherQueue = dispatcherQueue;
_catalogDataSourceLoader = catalogDataSourceLoader;
_packageCatalogViewModelFactory = packageCatalogViewModelFactory;
}
@@ -172,7 +172,7 @@ private void OnUnloaded()
private async void OnExtensionChangedAsync(object sender, EventArgs e)
{
- await _windowEx.DispatcherQueue.EnqueueAsync(() => LoadCatalogsAsync());
+ await _dispatcherQueue.EnqueueAsync(() => LoadCatalogsAsync());
}
private void Dispose(bool disposing)
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageViewModel.cs
index a5c59743c2..6c7d38926d 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/PackageViewModel.cs
@@ -58,7 +58,7 @@ public partial class PackageViewModel : ObservableObject
/// Indicates if a package is selected
///
[ObservableProperty]
- [NotifyPropertyChangedFor(nameof(ButtonAutomationName))]
+ [NotifyPropertyChangedFor(nameof(ActionButtonDescription))]
[NotifyPropertyChangedFor(nameof(ButtonAutomationId))]
private bool _isSelected;
@@ -130,7 +130,7 @@ public PackageViewModel(
public bool CanInstall => _orchestrator.IsSettingUpATargetMachine || !IsInstalled || _package.InstalledVersion != SelectedVersion;
- public string ButtonAutomationName => IsSelected ?
+ public string ActionButtonDescription => IsSelected ?
_stringResource.GetLocalized(StringResourceKey.RemoveApplication) :
_stringResource.GetLocalized(StringResourceKey.AddApplication);
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/QuickstartPlaygroundViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/QuickstartPlaygroundViewModel.cs
index d30b69cf25..9ad91bc1ea 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/QuickstartPlaygroundViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/QuickstartPlaygroundViewModel.cs
@@ -24,7 +24,6 @@
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.System;
-using WinUIEx;
namespace DevHome.SetupFlow.ViewModels;
@@ -195,7 +194,7 @@ public Task SaveProject()
// TODO: Replace with WindowSaveFileDialog
var folderPicker = new FolderPicker();
- var hWnd = Application.Current.GetService().GetWindowHandle();
+ var hWnd = Application.Current.GetService().GetWindowHandle();
WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hWnd);
folderPicker.FileTypeFilter.Add("*");
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs
index fb6f899d1c..41d4333552 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/ReviewViewModel.cs
@@ -16,8 +16,8 @@
using DevHome.SetupFlow.Services;
using DevHome.SetupFlow.TaskGroups;
using DevHome.Telemetry;
+using Microsoft.UI.Xaml;
using Serilog;
-using WinUIEx;
namespace DevHome.SetupFlow.ViewModels;
@@ -27,7 +27,7 @@ public partial class ReviewViewModel : SetupPageViewModelBase
private readonly SetupFlowOrchestrator _setupFlowOrchestrator;
private readonly ConfigurationFileBuilder _configFileBuilder;
- private readonly WindowEx _mainWindow;
+ private readonly Window _mainWindow;
[ObservableProperty]
private IList _reviewTabs;
@@ -95,7 +95,7 @@ public ReviewViewModel(
ISetupFlowStringResource stringResource,
SetupFlowOrchestrator orchestrator,
ConfigurationFileBuilder configFileBuilder,
- WindowEx mainWindow)
+ Window mainWindow)
: base(stringResource, orchestrator)
{
NextPageButtonText = StringResource.GetLocalized(StringResourceKey.SetUpButton);
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs
index b4bedba0dc..fff1bd2375 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupFlowViewModel.cs
@@ -144,7 +144,7 @@ public void OnNavigatedTo(NavigationEventArgs args)
}
}
- public void StartAppManagementFlow(string query)
+ public void StartAppManagementFlow(string query = null)
{
Orchestrator.FlowPages = [_mainPageViewModel];
_mainPageViewModel.StartAppManagementFlow(query);
diff --git a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs
index 84737e727b..3799f89623 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/ViewModels/SetupTargetViewModel.cs
@@ -3,9 +3,6 @@
using System;
using System.Collections.ObjectModel;
-using System.Configuration;
-using System.Globalization;
-using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -18,10 +15,9 @@
using DevHome.Common.Services;
using DevHome.SetupFlow.Models.Environments;
using DevHome.SetupFlow.Services;
-using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Dispatching;
using Microsoft.Windows.DevHome.SDK;
using Serilog;
-using WinUIEx;
namespace DevHome.SetupFlow.ViewModels;
@@ -29,7 +25,7 @@ public partial class SetupTargetViewModel : SetupPageViewModelBase
{
private readonly ILogger _log = Log.ForContext("SourceContext", nameof(SetupTargetViewModel));
- private readonly WindowEx _windowEx;
+ private readonly DispatcherQueue _dispatcherQueue;
private const string SortByDisplayName = "DisplayName";
@@ -87,7 +83,7 @@ public SetupTargetViewModel(
SetupFlowOrchestrator orchestrator,
IComputeSystemManager computeSystemManager,
ComputeSystemViewModelFactory computeSystemViewModelFactory,
- WindowEx windowEx)
+ DispatcherQueue dispatcherQueue)
: base(stringResource, orchestrator)
{
// Setup initial state for page.
@@ -111,7 +107,7 @@ public SetupTargetViewModel(
// Add AdvancedCollectionView to make filtering and sorting the list of ComputeSystemsListViewModels easier.
ComputeSystemsCollectionView = new AdvancedCollectionView(_computeSystemViewModelList, true);
- _windowEx = windowEx;
+ _dispatcherQueue = dispatcherQueue;
_computeSystemViewModelFactory = computeSystemViewModelFactory;
ComputeSystemManagerObj = computeSystemManager;
_setupFlowViewModel = setupFlowModel;
@@ -410,7 +406,7 @@ await Parallel.ForEachAsync(curListViewModel.ComputeSystems, async (computeSyste
}
});
- await _windowEx.DispatcherQueue.EnqueueAsync(async () =>
+ await _dispatcherQueue.EnqueueAsync(async () =>
{
foreach (var computeSystem in curListViewModel.ComputeSystems)
{
@@ -426,7 +422,7 @@ await _windowEx.DispatcherQueue.EnqueueAsync(async () =>
computeSystem,
curListViewModel.Provider,
packageFullName,
- _windowEx);
+ _dispatcherQueue);
// Don't show environments that aren't in a state to configure
if (!ShouldShowCard(card.CardState))
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml
index 78d3d3aa87..194daca8fb 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AddRepoDialog.xaml
@@ -170,7 +170,13 @@
into the command. I tried converting this to use a command on SelectionChanged and pass SelectedItems into the command.
I ran into issues because the viewmodel expects one repository at a time. Not a list.
Another issue is .SelectRange() needs to be called to "select" repos when the dialog is opened more than once. -->
-
+
@@ -276,12 +282,18 @@
+ x:Uid="NewDevDriveComboBox"
+ x:Name="NewDevDriveComboBox">
+
+
+
+
+
+
- /// Hold the clone location in case the user decides not to add a dev drive.
+ /// Gets or sets the clone location in case the user decides not to add a dev drive.
///
- private string _oldCloneLocation;
+ public string OldCloneLocation { get; set; }
public AddRepoDialog(
SetupFlowOrchestrator setupFlowOrchestrator,
@@ -222,30 +222,6 @@ private async void AddRepoContentDialog_PrimaryButtonClick(ContentDialog sender,
}
}
- ///
- /// Adds or removes the default dev drive. This dev drive will be made at the loading screen.
- ///
- private void MakeNewDevDriveCheckBox_Click(object sender, RoutedEventArgs e)
- {
- // Getting here means
- // 1. The user does not have any existing dev drives
- // 2. The user wants to clone to a new dev drive.
- // 3. The user un-checked this and does not want a new dev drive.
- var isChecked = (sender as CheckBox).IsChecked;
- if (isChecked.Value)
- {
- UpdateDevDriveInfo();
- }
- else
- {
- AddRepoViewModel.FolderPickerViewModel.CloneLocationAlias = string.Empty;
- AddRepoViewModel.FolderPickerViewModel.InDevDriveScenario = false;
- AddRepoViewModel.EditDevDriveViewModel.RemoveNewDevDrive();
- AddRepoViewModel.FolderPickerViewModel.EnableBrowseButton();
- AddRepoViewModel.FolderPickerViewModel.CloneLocation = _oldCloneLocation;
- }
- }
-
///
/// Putting the event in the view so SelectRange can be called.
/// SelectRange needs a reference to the ListView.
@@ -262,20 +238,6 @@ private void FilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
}
}
- ///
- /// Update dialog to show Dev Drive information.
- ///
- public void UpdateDevDriveInfo()
- {
- AddRepoViewModel.EditDevDriveViewModel.MakeDefaultDevDrive();
- AddRepoViewModel.FolderPickerViewModel.DisableBrowseButton();
- _oldCloneLocation = AddRepoViewModel.FolderPickerViewModel.CloneLocation;
- AddRepoViewModel.FolderPickerViewModel.CloneLocation = AddRepoViewModel.EditDevDriveViewModel.GetDriveDisplayName();
- AddRepoViewModel.FolderPickerViewModel.CloneLocationAlias = AddRepoViewModel.EditDevDriveViewModel.GetDriveDisplayName(DevDriveDisplayNameKind.FormattedDriveLabelKind);
- AddRepoViewModel.FolderPickerViewModel.InDevDriveScenario = true;
- AddRepoViewModel.EditDevDriveViewModel.IsDevDriveCheckboxChecked = true;
- }
-
private void FilterSuggestions(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
sender.ItemsSource = _searchFieldsAndValues[sender.Header.ToString()].Where(x => x.Contains(sender.Text));
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementReviewView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementReviewView.xaml
index 5beffd8074..8d49950bef 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementReviewView.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementReviewView.xaml
@@ -60,7 +60,7 @@
-
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml
index 4d23463b32..e5e14aeb1e 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/AppManagementView.xaml
@@ -195,7 +195,7 @@
+
+
+
@@ -46,10 +48,15 @@
+
+ Padding="-15">
+
+
+
+
-
-
-
-
-
+ 18
+ 18
@@ -135,48 +118,61 @@
VerticalScrollBarVisibility="Auto"
Grid.Column="0"
Grid.Row="2">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
32
-
+
@@ -87,19 +96,21 @@
BackgroundSource="{ThemeResource Setup_Banner_Back}"
OverlaySource="{ThemeResource Setup_Banner_Front}" />
-
+
-
-
+
+
+
-
+
@@ -163,15 +175,16 @@
-
+
@@ -179,78 +192,83 @@
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
+
+
+
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/PackageView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/PackageView.xaml
index 210a2d76c1..9ddd6af640 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/PackageView.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/PackageView.xaml
@@ -68,12 +68,13 @@
-
+
@@ -103,6 +104,7 @@
-
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs
index c0a90a74a3..350fdc2df8 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/RepoConfigView.xaml.cs
@@ -72,7 +72,7 @@ private async Task AddRepoAsync()
if (_addRepoDialog.AddRepoViewModel.EditDevDriveViewModel.CanShowDevDriveUI && ViewModel.ShouldAutoCheckDevDriveCheckbox)
{
- _addRepoDialog.UpdateDevDriveInfo();
+ _addRepoDialog.AddRepoViewModel.UpdateDevDriveInfo();
}
_addRepoDialog.IsSecondaryButtonEnabled = true;
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml
index b270959109..de8a04aa87 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml
@@ -62,7 +62,8 @@
+ MinHeight="64" CornerRadius="{ThemeResource ControlCornerRadius}" Padding="24,0,24,7"
+ AutomationProperties.Name="{x:Bind ViewModel.ReviewPageExpanderDescription, Mode=OneWay}">
@@ -77,16 +78,18 @@
-
-
-
-
-
-
-
+
+
+
+
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml.cs b/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml.cs
index 3a06ee8591..5977b44eb1 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml.cs
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/ReviewView.xaml.cs
@@ -6,6 +6,7 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Input;
namespace DevHome.SetupFlow.Views;
@@ -31,4 +32,14 @@ private void ReviewNavigationView_Loaded(object sender, RoutedEventArgs e)
}
}
}
+
+ private void GenerateConfigInfoButton_Click(object sender, RoutedEventArgs e)
+ {
+ GenerateConfigInfoTeachingTip.IsOpen = !GenerateConfigInfoTeachingTip.IsOpen;
+ }
+
+ private void GenerateConfigInfoButton_PointerEntered(object sender, PointerRoutedEventArgs e)
+ {
+ GenerateConfigInfoButton_Click(sender, e);
+ }
}
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupTargetView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupTargetView.xaml
index 127d3aa992..84d21d9977 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/SetupTargetView.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SetupTargetView.xaml
@@ -43,7 +43,7 @@
Style="{StaticResource HorizontalCardRootForSetupTargetFlow}"
IsTabStop="False">
+ Padding="0 -13 0 -10">
diff --git a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml
index 9ee24f1ce2..fb2f0c74e8 100644
--- a/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml
+++ b/tools/SetupFlow/DevHome.SetupFlow/Views/SummaryView.xaml
@@ -31,7 +31,7 @@
-
+
@@ -183,7 +183,7 @@
-
+
@@ -340,7 +340,7 @@
-
+
@@ -586,7 +586,7 @@
-
+
@@ -662,7 +662,7 @@
-
+
@@ -673,7 +673,7 @@
-
+
@@ -695,7 +695,7 @@
-
+
@@ -712,9 +712,10 @@
Tag="{Binding ElementName=ViewAllButton}"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind InstallationNotes}"/>
-
Launch
+
+ Launch
+
Utilities
Utilities
+
+ An application for gaining deeper insights into your applications.
+
+
+ Project Ironsides
+
Edit and visualize Windows Registry files.
diff --git a/tools/Utilities/src/TelemetryEvents/UtilitiesLaunchEvent.cs b/tools/Utilities/src/TelemetryEvents/UtilitiesLaunchEvent.cs
index 37c99bc974..96a18a0bdc 100644
--- a/tools/Utilities/src/TelemetryEvents/UtilitiesLaunchEvent.cs
+++ b/tools/Utilities/src/TelemetryEvents/UtilitiesLaunchEvent.cs
@@ -12,22 +12,32 @@ namespace DevHome.Utilities.TelemetryEvents;
[EventData]
public class UtilitiesLaunchEvent : EventBase
{
- public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServiceUsage;
+ public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServiceUsage;
+
+ public enum Phase
+ {
+ Start,
+ Error,
+ Complete,
+ }
+
+ public string ActivityId { get; }
- public string UtilityName
- {
- get;
- }
-
- public bool LaunchedAsAdmin
- {
- get;
- }
+ public string UtilityName { get; }
- public UtilitiesLaunchEvent(string utilityName, bool launchedAsAdmin)
- {
+ public bool LaunchedAsAdmin { get; }
+
+ public Phase LaunchPhase { get; }
+
+ public string ErrorString { get; }
+
+ public UtilitiesLaunchEvent(Guid activityId, string utilityName, bool launchedAsAdmin, Phase phase, string errorString = "")
+ {
+ ActivityId = activityId.ToString();
UtilityName = utilityName;
- LaunchedAsAdmin = launchedAsAdmin;
+ LaunchedAsAdmin = launchedAsAdmin;
+ LaunchPhase = phase;
+ ErrorString = errorString;
}
public override void ReplaceSensitiveStrings(Func replaceSensitiveStrings)
diff --git a/tools/Utilities/src/TelemetryEvents/UtilitiesMainPageViewModelEvent.cs b/tools/Utilities/src/TelemetryEvents/UtilitiesMainPageViewModelEvent.cs
new file mode 100644
index 0000000000..fb4b0413d0
--- /dev/null
+++ b/tools/Utilities/src/TelemetryEvents/UtilitiesMainPageViewModelEvent.cs
@@ -0,0 +1,25 @@
+// 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;
+
+namespace DevHome.Utilities.TelemetryEvents;
+
+[EventData]
+public class UtilitiesMainPageViewModelEvent : EventBase
+{
+ public override PartA_PrivTags PartA_PrivTags => PrivTags.ProductAndServiceUsage;
+
+ public UtilitiesMainPageViewModelEvent()
+ {
+ }
+
+ public override void ReplaceSensitiveStrings(Func replaceSensitiveStrings)
+ {
+ // No sensitive strings to replace.
+ }
+}
diff --git a/tools/Utilities/src/ViewModels/UtilitiesMainPageViewModel.cs b/tools/Utilities/src/ViewModels/UtilitiesMainPageViewModel.cs
index 1f9354a0d5..123deb3e0e 100644
--- a/tools/Utilities/src/ViewModels/UtilitiesMainPageViewModel.cs
+++ b/tools/Utilities/src/ViewModels/UtilitiesMainPageViewModel.cs
@@ -6,6 +6,8 @@
using System.IO;
using CommunityToolkit.Mvvm.ComponentModel;
using DevHome.Common.Services;
+using DevHome.Telemetry;
+using DevHome.Utilities.TelemetryEvents;
using Windows.ApplicationModel;
namespace DevHome.Utilities.ViewModels;
@@ -14,7 +16,7 @@ public partial class UtilitiesMainPageViewModel : ObservableObject
{
public ObservableCollection Utilities { get; set; }
- public UtilitiesMainPageViewModel()
+ public UtilitiesMainPageViewModel(IExperimentationService experimentationService)
{
var appExAliasAbsFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), $"Microsoft\\WindowsApps\\{Package.Current.Id.FamilyName}");
var stringResource = new StringResource("DevHome.Utilities.pri", "DevHome.Utilities/Resources");
@@ -28,6 +30,7 @@ public UtilitiesMainPageViewModel()
NavigateUri = "https://go.microsoft.com/fwlink/?Linkid=2271355",
ImageSource = Path.Combine(AppContext.BaseDirectory, "Assets\\HostsUILib", "Hosts.ico"),
SupportsLaunchAsAdmin = Microsoft.UI.Xaml.Visibility.Visible,
+ UtilityAutomationId = "DevHome.HostsFileEditor",
},
new(Path.Combine(appExAliasAbsFolderPath, "DevHome.RegistryPreviewApp.exe"))
{
@@ -36,6 +39,7 @@ public UtilitiesMainPageViewModel()
NavigateUri = "https://go.microsoft.com/fwlink/?Linkid=2270966",
ImageSource = Path.Combine(AppContext.BaseDirectory, "Assets\\RegistryPreview", "RegistryPreview.ico"),
SupportsLaunchAsAdmin = Microsoft.UI.Xaml.Visibility.Collapsed,
+ UtilityAutomationId = "DevHome.RegistryPreview",
},
new(Path.Combine(appExAliasAbsFolderPath, "DevHome.EnvironmentVariablesApp.exe"))
{
@@ -44,7 +48,18 @@ public UtilitiesMainPageViewModel()
NavigateUri = "https://go.microsoft.com/fwlink/?Linkid=2270894",
ImageSource = Path.Combine(AppContext.BaseDirectory, "Assets\\EnvironmentVariables", "EnvironmentVariables.ico"),
SupportsLaunchAsAdmin = Microsoft.UI.Xaml.Visibility.Visible,
+ UtilityAutomationId = "DevHome.EnvironmentVariables",
+ },
+ new(Path.Combine(appExAliasAbsFolderPath, "devhome.pi.exe"), experimentationService, "ProjectIronsidesExperiment")
+ {
+ Title = stringResource.GetLocalized("ProjectIronsidesTitle"),
+ Description = stringResource.GetLocalized("ProjectIronsidesDesc"),
+ NavigateUri = "https://aka.ms/projectironsides",
+ ImageSource = Path.Combine(AppContext.BaseDirectory, "PI.ico"),
+ UtilityAutomationId = "DevHome.PI",
},
};
+
+ TelemetryFactory.Get().Log("Utilities_UtilitiesMainPage", LogLevel.Critical, new UtilitiesMainPageViewModelEvent());
}
}
diff --git a/tools/Utilities/src/ViewModels/UtilityViewModel.cs b/tools/Utilities/src/ViewModels/UtilityViewModel.cs
index 9f547e9ea5..3819deca5f 100644
--- a/tools/Utilities/src/ViewModels/UtilityViewModel.cs
+++ b/tools/Utilities/src/ViewModels/UtilityViewModel.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System;
-using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -55,6 +54,9 @@ public bool Visible
[ObservableProperty]
private bool _launchAsAdmin;
+ [ObservableProperty]
+ private string _utilityAutomationId;
+
#nullable enable
public UtilityViewModel(string exeName, IExperimentationService? experimentationService = null, string? experimentalFeature = null)
{
@@ -68,7 +70,9 @@ public UtilityViewModel(string exeName, IExperimentationService? experimentation
private void Launch()
{
+ var activityId = Guid.NewGuid();
_log.Information($"Launching {_exeName}, as admin: {LaunchAsAdmin}");
+ TelemetryFactory.Get().Log("Utilities_UtilitiesLaunchEvent", LogLevel.Critical, new UtilitiesLaunchEvent(activityId, Title, LaunchAsAdmin, UtilitiesLaunchEvent.Phase.Start));
// We need to start the process with ShellExecute to run elevated
var processStartInfo = new ProcessStartInfo
@@ -85,14 +89,16 @@ 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));
throw new InvalidOperationException("Failed to start process");
}
}
catch (Exception ex)
{
_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(Title, LaunchAsAdmin), null);
+ TelemetryFactory.Get().Log("Utilities_UtilitiesLaunchEvent", LogLevel.Critical, new UtilitiesLaunchEvent(activityId, Title, LaunchAsAdmin, UtilitiesLaunchEvent.Phase.Complete), null);
}
}
diff --git a/tools/Utilities/src/Views/UtilityView.xaml b/tools/Utilities/src/Views/UtilityView.xaml
index d1e5c90dcd..d0a6950289 100644
--- a/tools/Utilities/src/Views/UtilityView.xaml
+++ b/tools/Utilities/src/Views/UtilityView.xaml
@@ -9,6 +9,8 @@
d:DesignHeight="300" d:DesignWidth="300">
+ Margin="15 0 0 5"
+ AutomationProperties.AutomationId="AdminToggleAutomationId"/>
+ Command="{x:Bind ViewModel.LaunchCommand}"
+ AutomationProperties.AutomationId="LaunchButtonAutomationId">
diff --git a/uitest/Common/DevHomeApplication.cs b/uitest/Common/DevHomeApplication.cs
index 944faa176a..82c74a9fc0 100644
--- a/uitest/Common/DevHomeApplication.cs
+++ b/uitest/Common/DevHomeApplication.cs
@@ -38,7 +38,9 @@ public sealed class DevHomeApplication
private WindowsElement SettingsNavigationItem => _devHomeSession.Driver.FindElementByAccessibilityId("SettingsItem");
- private WindowsElement ExtensionsNavigationItem => _devHomeSession.Driver.FindElementByAccessibilityId("Extensions");
+ private WindowsElement ExtensionsNavigationItem => _devHomeSession.Driver.FindElementByAccessibilityId("Extensions");
+
+ private WindowsElement UtilitiesNavigationItem => _devHomeSession.Driver.FindElementByAccessibilityId("DevHome.Utilities");
private DevHomeApplication()
{
@@ -101,6 +103,13 @@ public ExtensionsPage NavigateToExtensionsPage()
ExtensionsNavigationItem.Click();
return new(_devHomeSession.Driver);
}
+
+ public UtilitiesPage NavigateToUtilitiesPage()
+ {
+ Trace.WriteLine("Navigating to Utilities");
+ UtilitiesNavigationItem.Click();
+ return new(_devHomeSession.Driver);
+ }
///
/// Start Dev Home application
diff --git a/uitest/DevHome.UITest.csproj b/uitest/DevHome.UITest.csproj
index 4d36904914..1c0fd0ffa9 100644
--- a/uitest/DevHome.UITest.csproj
+++ b/uitest/DevHome.UITest.csproj
@@ -11,10 +11,22 @@
resources.pri
$(MSBuildProjectDirectory)\Test.runsettings
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/uitest/NativeMethods.json b/uitest/NativeMethods.json
new file mode 100644
index 0000000000..1175df48bf
--- /dev/null
+++ b/uitest/NativeMethods.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://aka.ms/CsWin32.schema.json",
+ "wideCharOnly": true
+}
\ No newline at end of file
diff --git a/uitest/NativeMethods.txt b/uitest/NativeMethods.txt
new file mode 100644
index 0000000000..bb03830eab
--- /dev/null
+++ b/uitest/NativeMethods.txt
@@ -0,0 +1 @@
+OpenProcessToken
\ No newline at end of file
diff --git a/uitest/Pages/UtilitiesPage.cs b/uitest/Pages/UtilitiesPage.cs
new file mode 100644
index 0000000000..a7ae2170d4
--- /dev/null
+++ b/uitest/Pages/UtilitiesPage.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Security.Principal;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.Win32.SafeHandles;
+using OpenQA.Selenium.Appium.Windows;
+using Windows.Win32;
+
+namespace DevHome.UITest.Pages;
+
+public class UtilitiesPage : ApplicationPage
+{
+ public UtilitiesPage(WindowsDriver driver)
+ : base(driver)
+ {
+ }
+
+ public void LaunchAndVerifyUtility(string utilityName, bool launchAsAdmin = false)
+ {
+ Trace.WriteLine($"Launching {utilityName} with admin: {launchAsAdmin}");
+ WindowsElement utilityView = Driver.FindElementByAccessibilityId(utilityName);
+
+ if (launchAsAdmin)
+ {
+ var launchAsAdminToggle = utilityView.FindElementByAccessibilityId("AdminToggleAutomationId");
+ launchAsAdminToggle.Click();
+ }
+
+ var launchButton = utilityView.FindElementByAccessibilityId("LaunchButtonAutomationId");
+ launchButton.Click();
+
+ // Wait for the utility to launch
+ Thread.Sleep(2000);
+
+ var processes = Process.GetProcessesByName(utilityName);
+ Assert.IsTrue(processes.Length == 1, $"{utilityName} as admin:{launchAsAdmin} did not launch");
+
+ if (launchAsAdmin)
+ {
+ try
+ {
+ var isAdmin = false;
+ SafeFileHandle processToken;
+ var result = PInvoke.OpenProcessToken(processes.First().SafeHandle, Windows.Win32.Security.TOKEN_ACCESS_MASK.TOKEN_QUERY, out processToken);
+ if (result != 0)
+ {
+ var identity = new WindowsIdentity(processToken.DangerousGetHandle());
+ isAdmin = identity?.Owner?.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid) ?? false;
+ }
+
+ Assert.IsTrue(isAdmin, $"{utilityName} did not launch as admin");
+ }
+ catch (Win32Exception ex)
+ {
+ Assert.Fail($"Failed to check if {utilityName} launched as admin: {ex.Message}");
+ }
+ }
+ }
+}
diff --git a/uitest/UtilitiesTest.cs b/uitest/UtilitiesTest.cs
new file mode 100644
index 0000000000..cb3dba450f
--- /dev/null
+++ b/uitest/UtilitiesTest.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics;
+using DevHome.UITest.Common;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace DevHome.Tests.UITest;
+
+[TestClass]
+public class UtilitiesTest : DevHomeTestBase
+{
+ [DataTestMethod]
+ [DataRow("DevHome.HostsFileEditor", false, DisplayName = "HostsUtility")]
+ [DataRow("DevHome.RegistryPreview", false, DisplayName = "RegistryPreview")]
+ [DataRow("DevHome.EnvironmentVariables", false, DisplayName = "EnvironmentVariables")]
+ [DataRow("DevHome.PI", false, DisplayName = "PI")]
+ [DataRow("DevHome.HostsFileEditor", true, DisplayName = "HostsUtility as Admin")]
+ [DataRow("DevHome.EnvironmentVariables", true, DisplayName = "EnvironmentVariables as Admin")]
+ [DataRow("DevHome.PI", true, DisplayName = "PI as Admin")]
+ public void LaunchUtilityTest(string utilityName, bool launchAsAdmin)
+ {
+ var utilities = Application.NavigateToUtilitiesPage();
+ utilities.LaunchAndVerifyUtility(utilityName, launchAsAdmin);
+ }
+
+ [TestCleanup]
+ public void UtilitiesTestCleanup()
+ {
+ // Kill all utilities after each test
+ string[] utilityNames = { "DevHome.HostsFileEditor", "DevHome.RegistryPreview", "DevHome.EnvironmentVariables", "DevHome.PI" };
+ foreach (var utilityName in utilityNames)
+ {
+ var processes = Process.GetProcessesByName(utilityName);
+
+ foreach (var process in processes)
+ {
+ process.Kill();
+ }
+ }
+ }
+}