diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml index c5f67ac6b8..046fcc5ae4 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -36,9 +36,9 @@ body: label: NuGet package version description: Specify the version of Windows App SDK (or Project Reunion) you're using. options: - - "Windows App SDK 1.8.0: 1.8.250907003" + - "Windows App SDK 1.8.2: 1.8.251003001" - "Windows App SDK 1.8 Preview 1: 1.8.250814004-preview1" - - "Windows App SDK 1.8 Experimental 4: 1.8.250702007-experimental4" + - "Windows App SDK 2.0 Experimental 1: 2.0.250930001-experimental1" - type: dropdown attributes: label: Packaging type diff --git a/TestAll.ps1 b/TestAll.ps1 index e6b9e061e6..016afd2b4c 100644 --- a/TestAll.ps1 +++ b/TestAll.ps1 @@ -72,7 +72,10 @@ param( [Switch]$ShowSystemInfo=$true, [Parameter(Mandatory=$true)] - [string]$wprProfilePath + [string]$wprProfilePath, + + [Parameter(Mandatory=$false)] + [string]$callingStage = '' ) $StartTime = Get-Date @@ -120,6 +123,15 @@ function Get-Tests } } + if ($callingStage -eq 'TestSampleApps') + { + $tests = $tests | Where-Object { $_.Filename -like "WindowsAppSDK.Test.SampleTests.dll" } + } + else + { + $tests = $tests | Where-Object { $_.Filename -notlike "WindowsAppSDK.Test.SampleTests.dll" } + } + $tests } @@ -140,7 +152,7 @@ function Run-TaefTest $teLogFile = (Join-Path $env:Build_SourcesDirectory "BuildOutput\$Configuration\$Platform\Te.wtl") $teLogPathTo = (Join-Path $env:Build_SourcesDirectory "TestOutput\$Configuration\$Platform") - & $tePath $dllFile $test.Parameters /enableWttLogging /appendWttLogging /screenCaptureOnError /logFile:$teLogFile $/testMode:EtwLogger /EtwLogger:WprProfile=WDGDEPAdex /EtwLogger:SavePoint=TestFailure /EtwLogger:RecordingScope=Execution /EtwLogger:WprProfileFile=$wprProfilePath + & $tePath $dllFile $test.Parameters /enableWttLogging /appendWttLogging /screenCaptureOnError /logFile:$teLogFile /testMode:EtwLogger /EtwLogger:WprProfile=WDGDEPAdex /EtwLogger:SavePoint=TestFailure /EtwLogger:RecordingScope=Execution /EtwLogger:WprProfileFile=$wprProfilePath } function Run-PowershellTest @@ -218,6 +230,9 @@ function Get-SystemInfo Write-Host "Powershell : $($PSVersionTable.PSEdition) $($PSVersionTable.PSVersion)" } +$env:Build_Platform = $Platform.ToLower() +$env:Build_Configuration = $Configuration.ToLower() + if ($ShowSystemInfo -eq $true) { Get-SystemInfo diff --git a/WindowsAppRuntime.sln b/WindowsAppRuntime.sln index 9da57f3c05..714bda40d2 100644 --- a/WindowsAppRuntime.sln +++ b/WindowsAppRuntime.sln @@ -742,6 +742,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mrmmin", "dev\MRTCore\mrt\m EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MRM", "dev\MRTCore\mrt\Core\src\MRM.vcxproj", "{CF03CC8D-FFF1-4CDC-B773-D219AD4E6F76}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAppSDK.Test.NetCore", "test\WindowsAppSDK.Test.NetCore\WindowsAppSDK.Test.NetCore.csproj", "{BF580B26-B869-3AF1-43EC-D0FD55A49E4D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAppSDK.Test.SampleTests", "test\WindowsAppSDK.Test.SampleTests\WindowsAppSDK.Test.SampleTests.csproj", "{346E099B-45E4-FF40-E63D-8B34915223C1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -2588,6 +2592,38 @@ Global {CF03CC8D-FFF1-4CDC-B773-D219AD4E6F76}.Release|x64.Build.0 = Release|x64 {CF03CC8D-FFF1-4CDC-B773-D219AD4E6F76}.Release|x86.ActiveCfg = Release|Win32 {CF03CC8D-FFF1-4CDC-B773-D219AD4E6F76}.Release|x86.Build.0 = Release|Win32 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|Any CPU.ActiveCfg = Debug|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|Any CPU.Build.0 = Debug|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|ARM64.ActiveCfg = Debug|arm64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|ARM64.Build.0 = Debug|arm64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|x64.ActiveCfg = Debug|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|x64.Build.0 = Debug|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|x86.ActiveCfg = Debug|x86 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Debug|x86.Build.0 = Debug|x86 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|Any CPU.ActiveCfg = Release|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|Any CPU.Build.0 = Release|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|ARM64.ActiveCfg = Release|arm64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|ARM64.Build.0 = Release|arm64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|x64.ActiveCfg = Release|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|x64.Build.0 = Release|x64 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|x86.ActiveCfg = Release|x86 + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D}.Release|x86.Build.0 = Release|x86 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|Any CPU.ActiveCfg = Debug|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|Any CPU.Build.0 = Debug|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|ARM64.ActiveCfg = Debug|arm64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|ARM64.Build.0 = Debug|arm64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|x64.ActiveCfg = Debug|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|x64.Build.0 = Debug|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|x86.ActiveCfg = Debug|x86 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Debug|x86.Build.0 = Debug|x86 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|Any CPU.ActiveCfg = Release|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|Any CPU.Build.0 = Release|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|ARM64.ActiveCfg = Release|arm64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|ARM64.Build.0 = Release|arm64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x64.ActiveCfg = Release|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x64.Build.0 = Release|x64 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x86.ActiveCfg = Release|x86 + {346E099B-45E4-FF40-E63D-8B34915223C1}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2811,6 +2847,8 @@ Global {E9C055BB-6AE4-497A-A354-D07841E68976} = {022E355A-AB24-48EE-9CC0-965BEFDF5E8C} {DC453DE3-18FD-43E7-8103-20763C8B97C8} = {5012149E-F09F-4F18-A03C-FFE597203821} {C40AE1D8-FD5F-472E-86B5-DDA5ABA6FF99} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} + {BF580B26-B869-3AF1-43EC-D0FD55A49E4D} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} + {346E099B-45E4-FF40-E63D-8B34915223C1} = {8630F7AA-2969-4DC9-8700-9B468C1DC21D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4B3D7591-CFEC-4762-9A07-ABE99938FB77} diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-BuildAndTestSampleApps-Stage.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildAndTestSampleApps-Stage.yml new file mode 100644 index 0000000000..4d723844a8 --- /dev/null +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildAndTestSampleApps-Stage.yml @@ -0,0 +1,148 @@ +# Note: In Foundation, the approach to test launching sample apps differs slightly from Aggregator: +# 1. In Foundation, we only exercises a subset of the sample apps that are tested in Aggregator. +# 2. All sample apps in Foundation are built and tested in SelfContained mode. +# The reason for this difference is: sample apps only use component package in Foundation. There is no associated framework package, Runtime package is not generated until Aggregator. +# 3. Foundation pipeline only supports running sample app testing in one specific stage. + +parameters: + - name: "IsOneBranch" + type: boolean + default: true + - name: TestSampleApps + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: true + - name: TestOnArm64 + type: boolean + default: true + - name: SamplesBuildOutputArtifactName + displayName: "Base name for Samples build output artifacts" + type: string + default: 'SamplesBuildOutput' + - name: runStaticAnalysis + type: boolean + default: true + - name: maxParallel_x64 + type: number + default: 10 + - name: maxParallel_arm64 + type: number + default: 10 + # sampleFeatureAreasList param have the same naming rule and pattern as Aggregator repo: + # https://microsoft.visualstudio.com/ProjectReunion/_git/WindowsAppSDKAggregator?path=/build/AzurePipelinesTemplates/WindowsAppSDK-BuildWindowsAppSDK-Stages.yml + - name: "sampleFeatureAreasList" + type: object + default: + SamplesCompatTest: + - 'Installer' + - 'Unpackaged' + - 'Notifications-Push' + - 'Insights' + - 'AppLifecycle-Activation-cpp-cpp--console--unpackaged' + - 'AppLifecycle-Activation-cpp-cpp--win32--unpackaged' + - 'AppLifecycle-Activation-cs' + - 'AppLifecycle-Instancing-cpp-cpp--console--unpackaged' + - 'AppLifecycle-Instancing-cpp-cpp--win32--packaged' + - 'AppLifecycle-Instancing-cpp-cpp--win32--unpackaged' + - 'AppLifecycle-Instancing-cs' + - 'AppLifecycle-Instancing-cs1' + - 'AppLifecycle-StateNotifications-cpp-cpp--console--unpackaged' + - 'AppLifecycle-StateNotifications-cpp-cpp--win32--packaged' + - 'AppLifecycle-StateNotifications-cpp-cpp--win32--unpackaged' + - 'AppLifecycle-StateNotifications-cs' + - 'AppLifecycle-StateNotifications-cs1' + - 'AppLifecycle-EnvironmentVariables' + - 'AppLifecycle-Restart-cpp--console--unpackaged' + - 'ResourceManagement-cpp-cpp--console--unpackaged' + - 'ResourceManagement-cs-cs--winforms--unpackaged' + - 'ResourceManagement-cs1' + - 'Mica-cpp--win32' + - 'Windowing-cpp-cpp--win32' + - 'Windowing-cs-cs--winforms--unpackaged' + - 'SelfContainedDeployment-cpp-cpp--console--unpackaged' + - 'SelfContainedDeployment-cs' + +stages: +- stage: BuildSampleApps_x64 + dependsOn: Pack + variables: + SamplesRepoName: 'WindowsAppSDK-Samples' + SelfRepoName: 'WindowsAppSDK' + FoundationVersion: $[ stageDependencies.Pack.NugetPackage.outputs['DetermineComponentNugetPackageVersion.componentPackageVersion'] ] + jobs: + - template: WindowsAppSDK-BuildSamplesCompat-Job.yml + parameters: + IsOneBranch: ${{ parameters.IsOneBranch }} + JobName: "SamplesCompatTest" + FeatureAreas: ${{ parameters.sampleFeatureAreasList.SamplesCompatTest }} + BuildConfig: + - 'Release' + - 'Debug' + BuildPlatform: + - 'x64' + ${{ if eq(parameters.TestSampleApps, 'true') }}: + SamplesArtifactName: ${{ parameters.SamplesBuildOutputArtifactName }}_x64 + ${{ if ne(parameters.TestSampleApps, 'true') }}: + SamplesArtifactName: '' + runStaticAnalysis : ${{ parameters.runStaticAnalysis }} + maxParallel: ${{ parameters.maxParallel_x64 }} + +- stage: BuildSampleApps_arm64 + dependsOn: Pack + variables: + SamplesRepoName: 'WindowsAppSDK-Samples' + SelfRepoName: 'WindowsAppSDK' + FoundationVersion: $[ stageDependencies.Pack.NugetPackage.outputs['DetermineComponentNugetPackageVersion.componentPackageVersion'] ] + jobs: + - template: WindowsAppSDK-BuildSamplesCompat-Job.yml + parameters: + IsOneBranch: ${{ parameters.IsOneBranch }} + JobName: "SamplesCompatTest" + FeatureAreas: ${{ parameters.sampleFeatureAreasList.SamplesCompatTest }} + BuildConfig: + - 'Release' + - 'Debug' + BuildPlatform: + - 'arm64' + ${{ if eq(parameters.TestSampleApps, 'true') }}: + SamplesArtifactName: ${{ parameters.SamplesBuildOutputArtifactName }}_arm64 + ${{ if ne(parameters.TestSampleApps, 'true') }}: + SamplesArtifactName: '' + runStaticAnalysis : ${{ parameters.runStaticAnalysis }} + maxParallel: ${{ parameters.maxParallel_arm64 }} + +- ${{ if eq( parameters.TestSampleApps, true ) }}: + - stage: TestSampleApps_x64 + dependsOn: + - BuildSampleApps_x64 + - Build_x64 + variables: + SamplesRepoName: 'WindowsAppSDK-Samples' + SelfRepoName: 'WindowsAppSDK' + jobs: + - template: WindowsAppSDK-RunTestsInPipeline-Job.yml + parameters: + jobName: TestSamplesX64 + samplesArtifactName: ${{ parameters.SamplesBuildOutputArtifactName }}_x64 + callingStage: 'TestSampleApps' + sampleFeatureAreasList: ${{ parameters.sampleFeatureAreasList }} + IsOneBranch: ${{ parameters.IsOneBranch }} + TestMatrix: $[ variables.SampleAppsTests ] + + - ${{ if eq(parameters.TestOnArm64, true)}}: + - stage: TestSampleApps_arm64 + dependsOn: + - BuildSampleApps_arm64 + - Build_arm64 + variables: + SamplesRepoName: 'WindowsAppSDK-Samples' + SelfRepoName: 'WindowsAppSDK' + jobs: + - template: WindowsAppSDK-RunTestsInPipeline-Job.yml + parameters: + jobName: TestSamplesArm64 + samplesArtifactName: ${{ parameters.SamplesBuildOutputArtifactName }}_arm64 + callingStage: 'TestSampleApps' + sampleFeatureAreasList: ${{ parameters.sampleFeatureAreasList }} + IsOneBranch: ${{ parameters.IsOneBranch }} + TestMatrix: $[ variables.SampleAppsTestsArm64 ] \ No newline at end of file diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-BuildSamplesCompat-Job.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildSamplesCompat-Job.yml new file mode 100644 index 0000000000..7fcb80321c --- /dev/null +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-BuildSamplesCompat-Job.yml @@ -0,0 +1,398 @@ +# This yml file is used to build sample apps, having the same structure and pattern as Aggregator repo: +# https://microsoft.visualstudio.com/ProjectReunion/_git/WindowsAppSDKAggregator?path=/build/AzurePipelinesTemplates/WindowsAppSDK-BuildSamplesCompat-Job.yml +parameters: +- name: "IsOneBranch" + type: boolean + default: true +- name: JobName + type: string + default: 'SamplesCompatTest' +- name: FeatureAreas + type: object + default: + - '' +- name: "BuildConfig" + type: object + default: + - 'Release' + - 'Debug' +- name: "BuildPlatform" + type: object + default: + - 'x64' +- name: "SamplesArtifactName" + displayName: "Supply a valid base name for Sample Apps BuildOutput to trigger publishing of these artifacts" + type: string + default: '' +- name: runStaticAnalysis + type: boolean + default: true +- name: maxParallel + type: number + default: 10 + +jobs: +- job: ${{ parameters.JobName }} + pool: + ${{ if parameters.IsOneBranch }}: + type: windows + ${{ if not( parameters.IsOneBranch ) }}: + type: windows + isCustom: true + name: 'ProjectReunionESPool-2022' + timeoutInMinutes: 120 + strategy: + maxParallel: ${{ parameters.maxParallel }} + matrix: + ${{ each featureArea in parameters.FeatureAreas }}: + ${{ featureArea }}: + feature: ${{ featureArea }} + variables: + ob_outputDirectory: '$(Build.ArtifactStagingDirectory)' + ob_sdl_codeSignValidation_excludes: '-|**\*' + ob_artifactBaseName: '${{ parameters.SamplesArtifactName }}' + ob_artifactSuffix: '_$(feature)' + # Currently, the version of PREfast in Guardian does not correctly detect VSBuild tasks, that is a known issue tracked by: + # Feature 190028: Ingest 1ES Template update to successfully detect VSBuild tasks and run PREfast successfully + # Because of that, we've been explicitly invoking the PREfast task instead of relying on Guardian's PREfast. Recently, the + # non-functional PREfast task injected via Guardian is starting to emit non-fatal errors (previously, it quietly skipped + # itself), to the effect of "no build operation detected to triggger PREfast for", creating noise in the build output page. + # Therefore, turning off Guardian's PREfast altogether to eliminate the noise. Our pipeline still does our own PREfast scan + # and the "Guardian: Post Analysis" task still analyzes the logs produced by our explicit PREfast scan as usual. + ob_sdl_prefast_enabled: false + ob_sdl_checkCompliantCompilerWarnings: true # This setting has no effect unless ob_sdl_msbuildOverride below is also set to true. + ob_sdl_msbuildOverride: true # Because we are calling MSBuild directly instead of through the MSBuild@1 or VSBuild@1 tasks. + ob_sdl_prefast_runDuring: 'Guardian' # The default 'Build' setting does not match the fact that we are calling msbuild.exe directly. + + steps: + - checkout: self + - checkout: WindowsAppSDKSamples + + # Used to restore Windows SDK projection preview package from MSFTNuGet feed + - task: NuGetAuthenticate@1 + displayName: "NuGet authenticate to restore Windows SDK projection" + + - task: PowerShell@2 + displayName: 'Add Windows SDK 10.0.22000' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + targetType: filePath + filePath: $(Build.SourcesDirectory)\$(SelfRepoName)\build\scripts\windows-sdk.ps1 + # TODO: the SdkVersion parameter does not yet support arbitrary versions. + arguments: > + -SdkVersion "10.0.22000" + + # Local variable 'feature' used to potentially contain a '\' and that is handy when using + # 'feature' as part of a file path. However, when we started using 'feature' as part of + # the name of an artifact in OneBranch, the '\' is an illegal character in an artifact + # name. Hence, a '-' replaced the '\' in 'feature', and we convert the '-' to '\' in + # there to produce variable 'featureForFilePath' and use it as part of a file path. + - task: PowerShell@2 + name: MakeFeatureUsableAsFilePath + displayName: Convert feature to featureForFilePath + inputs: + targetType: 'inline' + script: | + $tempFeature = '$(feature)' + if ($tempFeature.Contains("-")) + { + $tempFeature = $tempFeature.Replace("--", "TEMPHYPHEN") + $tempFeature = $tempFeature.Replace("-", "\") + $tempFeature = $tempFeature.Replace("TEMPHYPHEN", "-") + } + Write-Host "##vso[task.setvariable variable=featureForFilePath;isOutput=true]$tempFeature" + Write-Host "##vso[task.setvariable variable=featureForFilePath;]$tempFeature" + + - task: DownloadPipelineArtifact@2 + displayName: 'Download WindowsAppSDK.Foundation' + inputs: + artifactName: 'TransportPackage' + targetPath: '$(Build.SourcesDirectory)\TransportPackage' + + # Copy WindowsAppSDK.Foundation to local package folder + - task: CopyFiles@2 + displayName: 'Copy WindowsAppSDK.Foundation to local package folder' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\TransportPackage' + Contents: | + Microsoft.WindowsAppSDK.Foundation.[0-9]*.nupkg + TargetFolder: '$(Build.SourcesDirectory)\localpackages\NugetPackages' + + # Install Microsoft.WindowsAppSDK.Foundation to get all its dependencies and their version. + - task: NuGetCommand@2 + inputs: + command: 'custom' + arguments: > + install "Microsoft.WindowsAppSDK.Foundation" + -Source "$(Build.SourcesDirectory)\localpackages\NugetPackages" + -Version "$(FoundationVersion)" + -OutputDirectory "$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\localpackages" + -FallbackSource "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json" + + # The environment variable VCToolsInstallDir isn't defined on lab machines, so we need to retrieve it ourselves. + - script: | + "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -Latest -prerelease -requires Microsoft.Component.MSBuild -property InstallationPath > %TEMP%\vsinstalldir.txt + set /p _VSINSTALLDIR15=<%TEMP%\vsinstalldir.txt + del %TEMP%\vsinstalldir.txt + call "%_VSINSTALLDIR15%\Common7\Tools\VsDevCmd.bat" + echo VCToolsInstallDir = %VCToolsInstallDir% + echo ##vso[task.setvariable variable=VCToolsInstallDir]%VCToolsInstallDir% + displayName: 'Retrieve VC tools directory' + + # In Aggregator, we use global.json to specify the .NET SDK version to use. + # However, in Foundation it encounters error when using global.json, so we specify the version and call UseDotNet@2 multiple times instead. + #==================================================================================================================== + - task: UseDotNet@2 + displayName: Use .NET Core SDK 6 + inputs: + packageType: 'sdk' + version: '6.0.427' + + - task: UseDotNet@2 + displayName: Use .NET Core SDK 8 + inputs: + packageType: 'sdk' + version: '8.0.100' + + - task: UseDotNet@2 + displayName: Use .NET Core SDK 9 + inputs: + packageType: 'sdk' + version: '9.0.200' + #==================================================================================================================== + + - task: PowerShell@2 + name: GetWASDKNugetDependencies + displayName: 'Get Nuget Dependencies for WindowsAppSDK' + inputs: + targetType: filePath + filePath: $(Build.SourcesDirectory)\$(SelfRepoName)\test\GetNugetDependencies.ps1 + arguments: > + -PackageName "Microsoft.WindowsAppSDK" + -OutputVariableName "wasdkDependencies" + + - task: PowerShell@2 + displayName: "Modify Sample Apps References" + inputs: + filePath: '$(Build.SourcesDirectory)\$(SelfRepoName)\test\ModifySampleAppsReferences.ps1' + arguments: > + -SampleRepoRoot "$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples" + -FoundationVersion "$(FoundationVersion)" + -FoundationPackagesFolder "$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\localpackages" + -WASDKNugetDependencies "$(wasdkDependencies)" + + # update the nuget.config file to point to the internal feed + - powershell: | + $nugetConfigPath = "$(Build.SourcesDirectory)/$(SamplesRepoName)/Samples/nuget.config" + + $sourceName = "WinAppSDK-SampleDeps" + $newSourceUrl = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json" + + if (Test-Path $nugetConfigPath) { + [xml]$config = Get-Content $nugetConfigPath + + $packageSources = $config.configuration.packageSources.add + + $existingSource = $packageSources | Where-Object { $_.key -eq $sourceName } + + if ($existingSource) { + $existingSource.value = $newSourceUrl + Write-Host "Updated source '$sourceName' to '$newSourceUrl'" + + $config.Save($nugetConfigPath) + Write-Host "nuget.config updated successfully." + } + } else { + Write-Host "nuget.config file does not exist." + } + displayName: 'Modify NuGet.config feed to internal feed' + + - task: NuGetCommand@2 + inputs: + command: 'restore' + feedsToUse: 'config' + nugetConfigPath: '.\$(SamplesRepoName)\Samples\nuget.config' + restoreSolution: '.\$(SamplesRepoName)\Samples\$(featureForFilePath)\**\*.sln' + includeNuGetOrg: false + + - task: CopyFiles@2 + displayName: 'Copy tsaoptions.json from under ~\WindowsAppSDKAggregator\.config to ~\.config for the TSAOption task to find' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SelfRepoName)\.config' + Contents: | + tsaoptions.json + TargetFolder: '$(Build.SourcesDirectory)\.config' + + - task: CopyFiles@2 + displayName: 'Copy OneBranch.gdnsuppress from under ~\WindowsAppSDKAggregator\.gdn to ~\.gdn for the Guardian: Post Analysis task to find' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SelfRepoName)\.gdn' + Contents: | + OneBranch.gdnsuppress + TargetFolder: '$(Build.SourcesDirectory)\.gdn' + + - ${{ each config in parameters.BuildConfig }}: + - ${{ each platform in parameters.BuildPlatform }}: + - task: VSBuild@1 + displayName: 'Restore nuget packages for all solutions' + inputs: + solution: .\$(SamplesRepoName)\Samples\$(featureForFilePath)\**\*.sln + platform: '${{ platform }}' + configuration: '${{ config }}' + msbuildArgs: '/t:restore /p:PublishReadyToRun=true /p:VCToolsInstallDir="$(VCToolsInstallDir)\" /p:RestoreAdditionalProjectSources="$(Build.SourcesDirectory)\localpackages\NugetPackages" /binaryLogger:$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)\$(feature).restore.${{ platform }}.${{ config }}.binlog' + ${{ if and(eq( parameters.EnablePREFast, 'true'), eq(parameters.RunSDLBinaryAnalysis, 'true')) }}: + # This property should match that of 'Build all Sample solutions' below. Otherwise, tool + # architecture detection in the PreFast task may (incorrectly) choose the tool architecture + # deduced from this step and inappropriately use it in the MSBuild commandline for triggering + # PREFast. We only need to specify this property for x64, which is currently the only + # architecture for which we run PreFast. Guardian PreFast currently does not support arm any + # way. Also no need for this property if this pipeline run does not enable PREFast. + msbuildArchitecture: x64 + + - task: VSBuild@1 + displayName: 'Build all Sample solutions' + inputs: + solution: .\$(SamplesRepoName)\Samples\$(featureForFilePath)\**\*.sln + platform: '${{ platform }}' + configuration: '${{ config }}' + msbuildArgs: '/p:VCToolsInstallDir="$(VCToolsInstallDir)\" /p:RestoreAdditionalProjectSources="$(Build.SourcesDirectory)\localpackages\NugetPackages" /binaryLogger:$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)\$(feature).build.${{ platform }}.${{ config }}.binlog' + ${{ if and(eq( parameters.EnablePREFast, 'true'), eq(parameters.RunSDLBinaryAnalysis, 'true')) }}: + # This property helps prevent tool architecture detection in the PreFast task to pick x86 (default) + # as the "preferred tool architecture", which results in multiple sample apps failing to build due + # to "out of heap space" errors, which in turn is due to unfixed memory leaks in the 32-bit favor + # of a PREFast tool. We only need to specify this property for x64, which is currently the only + # architecture for which we run PreFast. Guardian PreFast currently does not support arm any + # way. Also no need for this property if this pipeline run does not enable PREFast. + msbuildArchitecture: x64 + + - task: CopyFiles@2 + displayName: 'Copy binlogs' + condition: succeededOrFailed() + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)' + Contents: | + **/*.binlog + TargetFolder: '$(Build.ArtifactStagingDirectory)\$(featureForFilePath)' + + - ${{ if and(eq(parameters.runStaticAnalysis, 'true'), eq(platform, 'x64'), eq(config, 'Release')) }}: + # There are currently challenges with PREFast scanning the sample apps: + # 1) SDLNativeRules@3 does not seem to support the wild card in the input "solution: .\$(SamplesRepoName)\Samples\$(featureForFilePath)\**\*.sln" we pass to VSBuild@1 + # above, so we need to enumerate the .sln files at the target folder and explicitly pass the full path of each .sln file to a corresponding call to SDLNativeRules@3. + # 2) There are currently >71 sample apps. It takes a *long* time for PREFast to finish them. + # Given those challenges and that we currently prioritize Sample code over Product code, in this first round we just pick a few a Sample apps to PREFast scan them. + # In the future, considering the long time required to scan the Sample apps, it might make more sense to create a separate weekly pipeline to PREFast scan the Sample + # apps, for instance. + - task: SDLNativeRules@3 + displayName: PREfast SDL Native Rules for AppLifecycle\Activation\cpp + condition: and(succeeded(), eq(variables['featureForFilePath'], 'AppLifecycle\Activation\cpp\cpp-console-unpackaged')) + inputs: + setupCommandlines: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsMSBuildCmd.bat"' + msBuildArchitecture: amd64 + msBuildCommandline: 'msbuild.exe /nologo /nr:false /p:configuration=${{ config }} /p:platform=${{ platform }} $(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)\CppWinRtConsoleActivation.sln' + # Generally speaking, we leave it to the external repos to scan the bits in their packages. + excludedPaths: | + $(Build.SourcesDirectory)\packages + # Explicitly specify the EO-compliant rule set, as the default Sdl.Recommended.Warning.ruleset is not EO-compliant. + rulesetName: Custom + customRuleset: $(Agent.ToolsDirectory)\NativeCompilerStaticAnalysisRuleset\mandatory_to_fix.ruleset + policyName: 'WindowsUndocked' + continueOnError: true + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + + - task: PowerShell@2 + displayName: Display storage and diagnostic info + condition: succeededOrFailed() + inputs: + targetType: 'inline' + script: | + Get-WmiObject win32_logicaldisk | Format-Table DeviceId, MediaType, @{n="Size";e={[math]::Round($_.Size/1GB,2)}},@{n="FreeSpace";e={[math]::Round($_.FreeSpace/1GB,2)}}, @{n="UsedSpace";e={[math]::Round((($_.Size-$_.FreeSpace)/1GB),2)}} + Write-Host '$(feature), $(featureForFilePath), $(SamplesRepoName).' + + # These steps, done on a per-featureArea basis, are only required if we are going to test Sample apps one way or + # another in the current pipeline run. OneBranch is limited to publishing 1 artifact per job, and different jobs + # can't publish to the same artifact. Therefore, each job in the strategy matrix that builds a specific + # featureArea is forced to publish its own featureArea-specific artifact, resulting in many artfacts. + - ${{ if ne(parameters.SamplesArtifactName, '') }}: + - script: | + md $(Build.ArtifactStagingDirectory)\$(featureForFilePath) + dir $(Build.ArtifactStagingDirectory) + dir $(Build.SourcesDirectory) + displayName: Create target folder for staging artifact + + # Reduce the number of files not required for running the tests later by deletion, before copying + # them to the staging location. The file copying operations takes 20+ min for big sample apps if we + # don't do this filtering first, also the total size of the artifacts gets too big, resulting in + # long download time that causes jobs to timeout, and/or out of disk space issues. + # It seems to be most helpful to filter out the .pch and .pdb files. There might be more + # file types that we can filtered out then those listed below. + - task: DeleteFiles@1 + displayName: 'Delete unneeded Files under: $(Build.ArtifactStagingDirectory)' + condition: succeededOrFailed() + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)' + Contents: | + **/*.c + **/*.config + **/*.cpp + **/*.cs + **/*.csproj + **/*.editorconfig + **/*.foo + **/*.h + **/*.hpp + **/*.idb + **/*.idl + **/*.ilk + **/*.iobj + **/*.ipdb + **/*.lib + **/*.log + **/*.obj + **/*.pch + **/*.pdb + **/*.props + **/*.pubxml + **/*.sln + **/*.targets + **/*.tlog + **/*.txt + **/*.vcxproj* + **/*.vcxproj*/** + **/*.wapproj + **/*.winmd + **/*.xaml + **/*.xbf + **/*.xsd + **/*.semmle + **/arm/* + **/x86/* + **/*_Debug_Test/* + **/obj/* + **/packages/* + + - task: CopyFiles@2 + displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)' + condition: succeededOrFailed() + inputs: + SourceFolder: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples\$(featureForFilePath)' + TargetFolder: '$(Build.ArtifactStagingDirectory)\$(featureForFilePath)' + + - script: | + dir /s $(Build.ArtifactStagingDirectory) + condition: succeededOrFailed() + displayName: DIAG - Show staging trees to validate filtering + + - ${{ if not( parameters.IsOneBranch ) }}: + - task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: '$(ob_outputDirectory)' + artifactName: '$(ob_artifactBaseName)$(ob_artifactSuffix)' + +# The idea of merging the artifacts for the featureAreas produced by one call to this yml file has +# been attempted and abandoned. Although this will free the subsequent Stages that run the SampleTests +# from needing to know the artifacts of the individual featureAreas and downloading them one by one, +# thus needing to download only 3 bigger artifacts with fixed names, those bigger artifact took up much +# space and they seem to hit download errors more frequently. The E2E run time of the pipeline is +# also longer. Therefore, this idea was not adopted. diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-Publish-Stage.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-Publish-Stage.yml index 08a779f0cd..d3c1dcabd3 100644 --- a/build/AzurePipelinesTemplates/WindowsAppSDK-Publish-Stage.yml +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-Publish-Stage.yml @@ -9,6 +9,18 @@ parameters: - name: "IgnoreFailures" type: boolean default: true +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: false +- name: "TestOnArm64" + displayName: "Enable running of sample tests on arm64 platform (Default: true)" + type: boolean + default: false +- name: "MaestroDependOnTestSamples" + displayName: "Whether Maestro publishing should depend on successful Sample tests" + type: boolean + default: false stages: - stage: Publish dependsOn: @@ -17,6 +29,10 @@ stages: - ${{ if eq(parameters.TestOnArm64, true)}}: - Test_arm64 - StaticValidationTest + - ${{ if and(eq( parameters.TestSampleApps, true ), eq( parameters.MaestroDependOnTestSamples, true )) }}: + - TestSampleApps_x64 + - ${{ if eq( parameters.TestOnArm64, true ) }}: + - TestSampleApps_arm64 condition: not(failed()) jobs: # Publish diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-RunTests-Steps.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-RunTests-Steps.yml index 7ccd53d079..573fe1c796 100644 --- a/build/AzurePipelinesTemplates/WindowsAppSDK-RunTests-Steps.yml +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-RunTests-Steps.yml @@ -5,6 +5,9 @@ parameters: TaefSelect: '*' BinaryCompatSwitch: '' testLocale: '' + SamplesArtifactName: '' + IsOneBranch: false + callingStage: '' steps: - task: powershell@2 @@ -76,6 +79,14 @@ steps: script: | $(Build.SourcesDirectory)\redist\dotnet-windowsdesktop-runtime-installer.exe /quiet /install /norestart + - task: PowerShell@2 + displayName: 'Install VCLibs and VCLibs Desktop' + inputs: + filePath: 'build/InstallVCLibs.ps1' + arguments: > + -SourceDirectory "$(Build.SourcesDirectory)\BuildOutput\Release" + -Platform "${{ parameters.buildPlatform }}" + - task: powerShell@2 displayName: 'Install vc_redist' inputs: @@ -91,6 +102,67 @@ steps: arguments: -NoInteractive -Offline -Verbose -CheckTAEFService -ShowSystemInfo workingDirectory: '$(Build.SourcesDirectory)' + - task: PowerShell@2 + displayName: Display storage info and init variables + inputs: + targetType: 'inline' + script: | + Get-WmiObject win32_logicaldisk | Format-Table DeviceId, MediaType, @{n="Size";e={[math]::Round($_.Size/1GB,2)}},@{n="FreeSpace";e={[math]::Round($_.FreeSpace/1GB,2)}}, @{n="UsedSpace";e={[math]::Round((($_.Size-$_.FreeSpace)/1GB),2)}} + $IsPlatformX86 = (('${{ parameters.buildPlatform }}' -eq 'x86') -or ('${{ parameters.buildPlatform }}' -eq 'X86')) + Write-Output "##vso[task.setvariable variable=isPlatformX86;isOutput=true]$IsPlatformX86" + Write-Output "##vso[task.setvariable variable=isPlatformX86]$IsPlatformX86" + $IsReleaseConfig = (('${{ parameters.buildConfiguration }}' -eq 'release') -or ('${{ parameters.buildConfiguration }}' -eq 'Release')) + Write-Output "##vso[task.setvariable variable=isReleaseConfig;isOutput=true]$IsReleaseConfig" + Write-Output "##vso[task.setvariable variable=isReleaseConfig]$IsReleaseConfig" + Write-Host ${{ parameters.buildPlatform }}, ${{ parameters.buildConfiguration }}, $isPlatformX86, $isReleaseConfig, 'callingStage: ${{ parameters.callingStage }}' + - ${{ if ne(parameters.samplesArtifactName, '') }}: + - ${{ each featureAreas in parameters.sampleFeatureAreasList }}: + - ${{ if parameters.IsOneBranch }}: + - ${{ each artifactNameSuffix in featureAreas.value }}: + # If we are downloading for Release config, omit the *debug* specific stuff to save time and storage. + # If we are running for x86 platform, then don't download Sample app artifacts, as we currently don't build Samples for x86. + - task: DownloadPipelineArtifact@2 + condition: and(and(succeeded(), ne(variables['isPlatformX86'], 'true')), eq(variables['IsReleaseConfig'], 'true')) + displayName: 'Download Release ${{ parameters.SamplesArtifactName }}_${{ artifactNameSuffix }}' + inputs: + source: specific + runVersion: specific + project: $(System.TeamProjectId) + pipeline: $(_useBuildOutputFromPipeline) + pipelineId: $(_useBuildOutputFromBuildId) + artifactName: ${{ parameters.SamplesArtifactName }}_${{ artifactNameSuffix }} + targetPath: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples' + patterns: | + ** + !**/*binlog + !**/*debug*/**/* + - ${{ if not( parameters.IsOneBranch ) }}: + - task: DownloadPipelineArtifact@2 + # It is currently by design to _not_ test Sample apps when platform=x86 or config=Debug. + condition: and(and(succeeded(), ne(variables['isPlatformX86'], 'true')), eq(variables['IsReleaseConfig'], 'true')) + displayName: 'Download Release ${{ parameters.SamplesArtifactName }}_${{ featureAreas.key }}' + inputs: + source: specific + runVersion: specific + project: $(System.TeamProjectId) + pipeline: $(_useBuildOutputFromPipeline) + pipelineId: $(_useBuildOutputFromBuildId) + artifactName: ${{ parameters.SamplesArtifactName }}_${{ featureAreas.key }} + targetPath: '$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples' + patterns: | + ** + !**/*binlog + !**/*debug*/**/* + # WindowsAppSDKSampleAppTests requires SAMPLES_ROOT_PATH to be set. + - task: PowerShell@2 + displayName: Set SAMPLES_ROOT_PATH env variable + condition: and(succeeded(), and(ne(variables['isPlatformX86'], 'true'), eq(variables['IsReleaseConfig'], 'true'))) + inputs: + targetType: 'inline' + script: | + Write-Host "##vso[task.setvariable variable=SAMPLES_ROOT_PATH;isOutput=true]$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples" + Write-Host "##vso[task.setvariable variable=SAMPLES_ROOT_PATH;]$(Build.SourcesDirectory)\$(SamplesRepoName)\Samples" + - task: VisualStudioTestPlatformInstaller@1 inputs: versionSelector: latestStable @@ -151,6 +223,15 @@ steps: sc.exe queryex te.service | Write-Host sc.exe qc te.service | Write-Host + # For test sample apps, we need to copy the 'net6' subfolder generated by WindowsAppSDK.Test.NetCore.csproj to WindowsAppSDK.Test.SampleTests folder + - ${{ if eq( parameters.callingStage, 'TestSampleApps' ) }}: + - task: CopyFiles@2 + displayName: 'Copy net6 output to SampleTests folder' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\BuildOutput\$(buildConfiguration)\$(buildPlatform)\WindowsAppSDK.Test.NetCore\net6' + TargetFolder: '$(Build.SourcesDirectory)\BuildOutput\$(buildConfiguration)\$(buildPlatform)\WindowsAppSDK.Test.SampleTests\net6' + Contents: '**' + - task: PowerShell@2 displayName: 'Run TAEF Tests' inputs: @@ -162,6 +243,7 @@ steps: -Test -List -wprProfilePath "$(Build.SourcesDirectory)\WindowsAppSDKConfig\src\test\AppModelProviders.wprp" + -callingStage "${{ parameters.callingStage }}" - template: AzurePipelinesTemplates\WindowsAppSDK-ConvertWttLogToXUnit-Steps.yml@WindowsAppSDKConfig parameters: diff --git a/build/AzurePipelinesTemplates/WindowsAppSDK-RunTestsInPipeline-Job.yml b/build/AzurePipelinesTemplates/WindowsAppSDK-RunTestsInPipeline-Job.yml index 3641d0ccc7..49241481ed 100644 --- a/build/AzurePipelinesTemplates/WindowsAppSDK-RunTestsInPipeline-Job.yml +++ b/build/AzurePipelinesTemplates/WindowsAppSDK-RunTestsInPipeline-Job.yml @@ -6,17 +6,25 @@ parameters: - name: jobName type: string default: PipelineTests -- name: dependsOn +- name: samplesArtifactName + displayName: "Supply a valid base name for Sample Apps BuildOutput to trigger publishing of the artifact" + type: string + default: '' +- name: sampleFeatureAreasList type: object - default: - - '' - # testMatrix is supplied by WindowsAppSDKConfig/WindowsAppSDK-Foundation-TestConfig.yml + default: [] +- name: callingStage + type: string + default: '' - name: testMatrix type: object default: '' - name: buildPlatform type: string default: x64 +- name: "IsOneBranch" + type: boolean + default: true jobs: - job: ${{ parameters.jobName }} @@ -55,3 +63,7 @@ jobs: buildConfiguration: $(buildConfiguration) testLocale: $(testLocale) ImageName: $(imageName) + callingStage: ${{ parameters.callingStage }} + samplesArtifactName: ${{ parameters.samplesArtifactName }} + sampleFeatureAreasList: ${{ parameters.sampleFeatureAreasList }} + IsOneBranch: ${{ parameters.IsOneBranch }} \ No newline at end of file diff --git a/build/InstallVCLibs.ps1 b/build/InstallVCLibs.ps1 new file mode 100644 index 0000000000..84a6068d43 --- /dev/null +++ b/build/InstallVCLibs.ps1 @@ -0,0 +1,55 @@ +param( + [Parameter(Mandatory = $true)] + [ValidateSet("x86", "x64", "arm64")] + [string]$Platform, + + # Expecting BuildOutput\Release + [Parameter(Mandatory = $true)] + [string]$SourceDirectory +) + +$ErrorActionPreference = "Stop" + +Write-Host "Installing VCLibs packages for platform: $Platform" -ForegroundColor Green +Write-Host "Source directory: $SourceDirectory" -ForegroundColor Yellow + +# Check if the source directory exists +if (-not (Test-Path $SourceDirectory)) { + Write-Error "Source directory not found: $SourceDirectory" + exit 1 +} + +# Search pattern for VCLibs packages +# This pattern should get both VCLibs, VCLibs Desktop and possibly UWP desktop if present +# VCLibs: Microsoft.VCLibs.{platform}.14.00.appx +# VCLibs Desktop: Microsoft.VCLibs.{platform}.14.00.Desktop.appx +$SearchPath = Join-Path $SourceDirectory "$Platform\AppxPackages\*\Dependencies\$Platform\Microsoft.VCLibs.$Platform.14.00*.appx" +$VCLibsPackages = Get-ChildItem -Path $SearchPath -ErrorAction SilentlyContinue + +Write-Host "Found: $($VCLibsPackages.Count) VCLibs packages" -ForegroundColor White + +# Track installed packages to avoid duplicates +# We want to install each unique package only once +$InstalledPackages = @{} + +foreach ($Package in $VCLibsPackages) { + $PackageName = $Package.Name + + if ($InstalledPackages.ContainsKey($PackageName)) { + continue + } + + try { + Write-Host "Installing: $PackageName" -ForegroundColor White + Add-AppxPackage -Path $Package.FullName -ForceApplicationShutdown + + $InstalledPackages[$PackageName] = $Package.FullName + $TotalInstalled++ + Write-Host " -> Success" -ForegroundColor Green + } + catch { + Write-Error "Failed to install $PackageName`: $($_.Exception.Message)" + } +} + +Write-Host "Completed successfully!" -ForegroundColor Green \ No newline at end of file diff --git a/build/ProjectReunion-BuildFoundation.yml b/build/ProjectReunion-BuildFoundation.yml index a36becb7b7..57f6f1663e 100644 --- a/build/ProjectReunion-BuildFoundation.yml +++ b/build/ProjectReunion-BuildFoundation.yml @@ -5,10 +5,32 @@ name: $(version) trigger: none +parameters: +- name: "BuildSampleApps" + displayName: "Test build of sample apps" + type: boolean + default: false +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: false +- name: "TestOnArm64" + displayName: "Enable running of sample tests on arm64 platform (Default: true)" + type: boolean + default: false +- name: runStaticAnalysisInBuildAndTestSampleApps + displayName: "Run Static Analysis (e.g., PREFast, APIScan) In BuildAndTestSampleApps Stage" + type: boolean + default: false + variables: - template: WindowsAppSDK-Foundation-TestConfig.yml@WindowsAppSDKConfig - template: AzurePipelinesTemplates\WindowsAppSDK-Versions.yml@WindowsAppSDKVersionConfig - template: WindowsAppSDK-CommonVariables.yml +- name: maxParallelForBuildSamplesCompatJob_x64 + value: 10 +- name: maxParallelForBuildSamplesCompatJob_arm64 + value: 10 resources: repositories: @@ -20,8 +42,14 @@ resources: type: git name: ProjectReunion/WindowsAppSDKConfig ref: refs/heads/release/2.0-stable + - repository: WindowsAppSDKSamples + type: github + endpoint: 'GitHub - benkuhn - 2-18' + name: microsoft/WindowsAppSDK-Samples + ref: $(SamplesBranch) stages: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildInstaller-Stage.yml@self - template: AzurePipelinesTemplates\WindowsAppSDK-BuildVSIX-Stage.yml@self @@ -49,4 +77,13 @@ stages: SignOutput: false PublishPackage: false IsOneBranch: false - BuildMockWindowsAppSDK: false + +- ${{ if eq( parameters.BuildSampleApps, true ) }}: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildAndTestSampleApps-Stage.yml@self + parameters: + IsOneBranch: false + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + runStaticAnalysis: ${{ parameters.runStaticAnalysisInBuildAndTestSampleApps }} + maxParallel_x64: ${{ variables.maxParallelForBuildSamplesCompatJob_x64 }} + maxParallel_arm64: ${{ variables.maxParallelForBuildSamplesCompatJob_arm64 }} \ No newline at end of file diff --git a/build/WindowsAppSDK-CommonVariables.yml b/build/WindowsAppSDK-CommonVariables.yml index 503740412d..0f17f183a0 100644 --- a/build/WindowsAppSDK-CommonVariables.yml +++ b/build/WindowsAppSDK-CommonVariables.yml @@ -8,6 +8,7 @@ variables: versionMinDate: $[format('{0:yyMMdd}', pipeline.startTime)] versionCounter: $[counter(variables['versionDate'], 0)] version: $[format('{0}.{1}.{2}-{3}.{4}', variables['MajorVersion'], variables['MinorVersion'], variables['PatchVersion'], variables['versionDate'], variables['versionCounter'])] + SamplesBranch: "release/experimental" # Used in the Samples Build Compat Test #OneBranch Variables CDP_DEFINITION_BUILD_COUNT: $[counter('', 0)] # needed for onebranch.pipeline.version task https://aka.ms/obpipelines/versioning @@ -34,3 +35,5 @@ variables: WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' Codeql.Enabled: true # CodeQL runs every 3 days on the default branch for all languages its applicable to in that pipeline. + + enableTestPass: true diff --git a/build/WindowsAppSDK-Foundation-Nightly.yml b/build/WindowsAppSDK-Foundation-Nightly.yml index dfa65aa5b0..5de8f53844 100644 --- a/build/WindowsAppSDK-Foundation-Nightly.yml +++ b/build/WindowsAppSDK-Foundation-Nightly.yml @@ -33,15 +33,32 @@ parameters: displayName: "Run Static Analysis (e.g., PREFast, APIScan)" type: boolean default: true +- name: "BuildSampleApps" + displayName: "Run Build Sample Apps Stage" + type: boolean + default: true +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: true - name: "TestOnArm64" displayName: "Enable running of tests on arm64 platform (Default: false)" type: boolean + # Temporarily default to not testing on arm64 to mitigate arm64 capacity shortage problem. + default: false +- name: "MaestroDependOnTestSamples" + displayName: "Whether Maestro publishing should depend on successful Sample tests" + type: boolean default: false - + variables: - template: WindowsAppSDK-Foundation-TestConfig.yml@WindowsAppSDKConfig - template: AzurePipelinesTemplates\WindowsAppSDK-Versions.yml@WindowsAppSDKVersionConfig - template: WindowsAppSDK-CommonVariables.yml +- name: maxParallelForBuildSamplesCompatJob_x64 + value: 20 +- name: maxParallelForBuildSamplesCompatJob_arm64 + value: 20 resources: repositories: @@ -57,6 +74,11 @@ resources: type: git name: ProjectReunion/WindowsAppSDKConfig ref: refs/heads/release/2.0-stable + - repository: WindowsAppSDKSamples + type: github + endpoint: 'GitHub - benkuhn - 2-18' + name: microsoft/WindowsAppSDK-Samples + ref: $(SamplesBranch) extends: template: v2/Microsoft.NonOfficial.yml@templates # https://aka.ms/obpipelines/templates @@ -149,4 +171,15 @@ extends: parameters: PublishToMaestro: ${{ parameters.PublishToMaestro }} IgnoreFailures: ${{ parameters.IgnoreFailures }} - TestOnArm64: ${{ parameters.TestOnArm64 }} + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + MaestroDependOnTestSamples: ${{ parameters.MaestroDependOnTestSamples }} + + - ${{ if eq( parameters.BuildSampleApps, true ) }}: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildAndTestSampleApps-Stage.yml@self + parameters: + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + runStaticAnalysis: ${{ parameters.runStaticAnalysis }} + maxParallel_x64: ${{ variables.maxParallelForBuildSamplesCompatJob_x64 }} + maxParallel_arm64: ${{ variables.maxParallelForBuildSamplesCompatJob_arm64 }} diff --git a/build/WindowsAppSDK-Foundation-Official.yml b/build/WindowsAppSDK-Foundation-Official.yml index e8572ed747..ad20ea908d 100644 --- a/build/WindowsAppSDK-Foundation-Official.yml +++ b/build/WindowsAppSDK-Foundation-Official.yml @@ -33,15 +33,33 @@ parameters: displayName: "Run Static Analysis (e.g., PREFast, APIScan)" type: boolean default: true +- name: "BuildSampleApps" + displayName: "Run Build Sample Apps Stage" + type: boolean + default: true +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: true - name: "TestOnArm64" displayName: "Enable running of tests on arm64 platform (Default: false)" type: boolean + # Temporarily default to not testing on arm64 to mitigate arm64 capacity shortage problem. + default: false +- name: "MaestroDependOnTestSamples" + displayName: "Whether Maestro publishing should depend on successful Sample tests" + type: boolean default: false variables: - template: WindowsAppSDK-Foundation-TestConfig.yml@WindowsAppSDKConfig - template: AzurePipelinesTemplates\WindowsAppSDK-Versions.yml@WindowsAppSDKVersionConfig - template: WindowsAppSDK-CommonVariables.yml +- name: maxParallelForBuildSamplesCompatJob_x64 + value: 20 +- name: maxParallelForBuildSamplesCompatJob_arm64 + value: 20 + resources: repositories: - repository: templates @@ -135,4 +153,16 @@ extends: parameters: PublishToMaestro: ${{ parameters.PublishToMaestro }} IgnoreFailures: ${{ parameters.IgnoreFailures }} + TestSampleApps: ${{ parameters.TestSampleApps }} TestOnArm64: ${{ parameters.TestOnArm64 }} + MaestroDependOnTestSamples: ${{ parameters.MaestroDependOnTestSamples }} + + + - ${{ if eq( parameters.BuildSampleApps, true ) }}: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildAndTestSampleApps-Stage.yml@self + parameters: + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + runStaticAnalysis: ${{ parameters.runStaticAnalysis }} + maxParallel_x64: ${{ variables.maxParallelForBuildSamplesCompatJob_x64 }} + maxParallel_arm64: ${{ variables.maxParallelForBuildSamplesCompatJob_arm64 }} diff --git a/build/WindowsAppSDK-Foundation-PR.yml b/build/WindowsAppSDK-Foundation-PR.yml index 86a7a639a4..ca443df016 100644 --- a/build/WindowsAppSDK-Foundation-PR.yml +++ b/build/WindowsAppSDK-Foundation-PR.yml @@ -20,6 +20,18 @@ name: $(version) trigger: none parameters: # parameters are shown up in ADO UI in a build queue time +- name: "BuildSampleApps" + displayName: "Run Build Sample Apps Stage" + type: boolean + default: false +- name: "TestSampleApps" + displayName: "Test launch of Sample apps separately in the TestSampleApps Stage" + type: boolean + default: false +- name: "TestOnArm64" + displayName: "Enable running of tests on arm64 platform (Default: false)" + type: boolean + default: false - name: runStaticAnalysis displayName: "Run Static Analysis (e.g., PREFast, APIScan)" type: boolean @@ -33,6 +45,10 @@ variables: - template: WindowsAppSDK-Foundation-TestConfig.yml@WindowsAppSDKConfig - template: AzurePipelinesTemplates\WindowsAppSDK-Versions.yml@WindowsAppSDKVersionConfig - template: WindowsAppSDK-CommonVariables.yml +- name: maxParallelForBuildSamplesCompatJob_x64 + value: 20 +- name: maxParallelForBuildSamplesCompatJob_arm64 + value: 20 resources: repositories: @@ -48,6 +64,11 @@ resources: type: git name: ProjectReunion/WindowsAppSDKConfig ref: refs/heads/release/2.0-stable + - repository: WindowsAppSDKSamples + type: github + endpoint: 'GitHub - benkuhn - 2-18' + name: microsoft/WindowsAppSDK-Samples + ref: $(SamplesBranch) extends: template: v2/Microsoft.NonOfficial.yml@templates # https://aka.ms/obpipelines/templates @@ -119,3 +140,12 @@ extends: - template: AzurePipelinesTemplates\WindowsAppSDK-Test-Stage.yml@self parameters: TestOnArm64: ${{ parameters.TestOnArm64 }} + + - ${{ if eq( parameters.BuildSampleApps, true ) }}: + - template: AzurePipelinesTemplates\WindowsAppSDK-BuildAndTestSampleApps-Stage.yml@self + parameters: + TestSampleApps: ${{ parameters.TestSampleApps }} + TestOnArm64: ${{ parameters.TestOnArm64 }} + runStaticAnalysis: ${{ parameters.runStaticAnalysis }} + maxParallel_x64: ${{ variables.maxParallelForBuildSamplesCompatJob_x64 }} + maxParallel_arm64: ${{ variables.maxParallelForBuildSamplesCompatJob_arm64 }} diff --git a/dev/Common/TerminalVelocityFeatures-StoragePickers2.h b/dev/Common/TerminalVelocityFeatures-StoragePickers2.h new file mode 100644 index 0000000000..e44c4cfaa5 --- /dev/null +++ b/dev/Common/TerminalVelocityFeatures-StoragePickers2.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +// THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT IT + +// INPUT FILE: dev\Common\TerminalVelocityFeatures-StoragePickers2.xml +// OPTIONS: -Channel Experimental -Language C++ -Namespace Microsoft.Windows.Storage.Pickers -Path dev\Common\TerminalVelocityFeatures-StoragePickers2.xml -Output dev\Common\TerminalVelocityFeatures-StoragePickers2.h + +#if defined(__midlrt) +namespace features +{ + feature_name Feature_StoragePickers2 = { DisabledByDefault, FALSE }; +} +#endif // defined(__midlrt) + +// Feature constants +#define WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_STORAGE_PICKERS_FEATURE_STORAGEPICKERS2_ENABLED 1 + +#if defined(__cplusplus) + +namespace Microsoft::Windows::Storage::Pickers +{ + +__pragma(detect_mismatch("ODR_violation_WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_STORAGE_PICKERS_FEATURE_STORAGEPICKERS2_ENABLED_mismatch", "AlwaysEnabled")) +struct Feature_StoragePickers2 +{ + static constexpr bool IsEnabled() { return WINDOWSAPPRUNTIME_MICROSOFT_WINDOWS_STORAGE_PICKERS_FEATURE_STORAGEPICKERS2_ENABLED == 1; } +}; + +} // namespace Microsoft.Windows.Storage.Pickers + +#endif // defined(__cplusplus) diff --git a/dev/Common/TerminalVelocityFeatures-StoragePickers2.xml b/dev/Common/TerminalVelocityFeatures-StoragePickers2.xml new file mode 100644 index 0000000000..b878a8128f --- /dev/null +++ b/dev/Common/TerminalVelocityFeatures-StoragePickers2.xml @@ -0,0 +1,20 @@ + + + + + + + + + + Feature_StoragePickers2 + New functionalities in StoragePickers for the WindowsAppRuntime: SuggestedDefaultFolder, FileTypeChoices + AlwaysEnabled + + Preview + Stable + + + diff --git a/dev/Deployment/DeploymentActivityContext.cpp b/dev/Deployment/DeploymentActivityContext.cpp index a38a3bd3c6..73111979d0 100644 --- a/dev/Deployment/DeploymentActivityContext.cpp +++ b/dev/Deployment/DeploymentActivityContext.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #include diff --git a/dev/Deployment/DeploymentManager.cpp b/dev/Deployment/DeploymentManager.cpp index e5f4371a48..074771d407 100644 --- a/dev/Deployment/DeploymentManager.cpp +++ b/dev/Deployment/DeploymentManager.cpp @@ -527,6 +527,7 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem HRESULT DeploymentManager::InstallLicenses(const std::wstring& frameworkPackageFullName) { + ::WindowsAppRuntime::Deployment::Activity::Context::Get().Reset(); ::WindowsAppRuntime::Deployment::Activity::Context::Get().SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::GetLicensePath); // Build path for licenses @@ -554,7 +555,6 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem auto licenseFilename{ licensePath }; licenseFilename /= findFileData.cFileName; - ::WindowsAppRuntime::Deployment::Activity::Context::Get().Reset(); ::WindowsAppRuntime::Deployment::Activity::Context::Get().SetCurrentResourceId(licenseFilename); RETURN_IF_FAILED_MSG(licenseInstaller.InstallLicenseFile(licenseFilename.c_str()), @@ -574,15 +574,16 @@ namespace winrt::Microsoft::Windows::ApplicationModel::WindowsAppRuntime::implem HRESULT DeploymentManager::DeployPackages(const std::wstring& frameworkPackageFullName, const bool forceDeployment) { auto initializeActivity{ ::WindowsAppRuntime::Deployment::Activity::Context::Get() }; + initializeActivity.Reset(); initializeActivity.SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::GetPackagePath); const auto frameworkPath{ std::filesystem::path(GetPackagePath(frameworkPackageFullName)) }; - initializeActivity.SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::AddPackage); for (auto package : c_targetPackages) { auto isSingleton{ CompareStringOrdinal(package.identifier.c_str(), -1, WINDOWSAPPRUNTIME_PACKAGE_SUBTYPENAME_SINGLETON, -1, TRUE) == CSTR_EQUAL }; initializeActivity.Reset(); + initializeActivity.SetInstallStage(::WindowsAppRuntime::Deployment::Activity::DeploymentStage::AddPackage); initializeActivity.SetCurrentResourceId(package.identifier); std::filesystem::path packagePath{}; diff --git a/dev/DynamicDependency/Powershell/Add-PackageDependency.ps1 b/dev/DynamicDependency/Powershell/Add-PackageDependency.ps1 index 2360e510a4..48cbec1ea5 100644 --- a/dev/DynamicDependency/Powershell/Add-PackageDependency.ps1 +++ b/dev/DynamicDependency/Powershell/Add-PackageDependency.ps1 @@ -25,6 +25,11 @@ the resolved package is added before others of the same rank. For more information, see https://learn.microsoft.com/windows/win32/api/appmodel/ne-appmodel-addpackagedependencyoptions +.PARAMETER SpecifiedPackageFamilyOnly + Only add the target package family to the package graph. + By default the target package family and its resolved dependencies + are added to the package graph. + .LINK https://learn.microsoft.com/windows/win32/api/appmodel/nf-appmodel-addpackagedependency #> @@ -35,7 +40,9 @@ param( [int]$Rank=0, - [switch]$PrependIfRankCollision + [switch]$PrependIfRankCollision, + + [switch]$SpecifiedPackageFamilyOnly ) Set-StrictMode -Version 3.0 @@ -55,10 +62,27 @@ if ($PrependIfRankCollision -eq $true) $options = $options -bor [Microsoft.Windows.ApplicationModel.DynamicDependency.AddPackageDependencyOptions]::PrependIfRankCollision } +if ($SpecifiedPackageFamilyOnly -eq $true) +{ + $options2 = [Microsoft.Windows.ApplicationModel.DynamicDependency.AddPackageDependencyOptions2]::SpecifiedPackageFamilyOnly + if ($PrependIfRankCollision -eq $true) + { + $options2 = $options2 -bor [Microsoft.Windows.ApplicationModel.DynamicDependency.AddPackageDependencyOptions2]::PrependIfRankCollision + } +} + $packageDependencyContext = [IntPtr]0 $packageFullName = "" -$hr = [Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency]::Add( - $PackageDependencyId, $Rank, $options, [ref] $packageDependencyContext, [ref] $packageFullName) +if ($SpecifiedPackageFamilyOnly -eq $true) +{ + $hr = [Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency]::Add2( + $PackageDependencyId, $Rank, $options2, [ref] $packageDependencyContext, [ref] $packageFullName) +} +else +{ + $hr = [Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency]::Add( + $PackageDependencyId, $Rank, $options, [ref] $packageDependencyContext, [ref] $packageFullName) +} if ($hr -lt 0) { $win32ex = [System.ComponentModel.Win32Exception]::new($hr) diff --git a/dev/DynamicDependency/Powershell/Get-PackageDependency.ps1 b/dev/DynamicDependency/Powershell/Get-PackageDependency.ps1 new file mode 100644 index 0000000000..f65472ef25 --- /dev/null +++ b/dev/DynamicDependency/Powershell/Get-PackageDependency.ps1 @@ -0,0 +1,197 @@ +# Copyright (c) Microsoft Corporation and Contributors. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Return the process ids of processes using the package dependency. + +.DESCRIPTION + Return the process ids of processes using the package dependency. + + This does not add the package to the invoking process' package graph. + + NOTE: -All, -PackageDependencyId and -PackageFamilyName are mutually exclusive. + +.PARAMETER PackageDependencyId + Find package dependencies with this id. + +.PARAMETER All + Find all package dependencies. + +.PARAMETER PackageFamilyName + Find package dependencies with this package family. + +.PARAMETER ScopeIsSystem + Find package dependencies created with CreatePackageDependencyOptions_ScopeIsSystem. + +.LINK + https://learn.microsoft.com/windows/win32/api/appmodel/nf-appmodel-findpackagedependency + https://learn.microsoft.com/windows/win32/api/appmodel/nf-appmodel-getpackagedependencyinformation + https://learn.microsoft.com/windows/win32/api/appmodel/nf-appmodel-getprocessesusingpackagedependency +#> +[CmdletBinding(SupportsShouldProcess=$true)] +param( + [Parameter(ParameterSetName='All')] + [switch]$All, + + [Parameter(ParameterSetName='Id')] + [string]$PackageDependencyId, + + [Parameter(ParameterSetName='Package')] + [string]$PackageFamilyName, + + [switch]$ScopeIsSystem +) + +Set-StrictMode -Version 3.0 + +$ErrorActionPreference = "Stop" + +$ERROR_SUCCESS = 0 + +function Is-PackageDependencyId +{ + param( + [string]$id + ) + + if (($id -ne $null) -and ($id.Length -ge 3)) + { + $prefix = $id.Substring(0, 2) + return (($prefix -eq 'T:') -or ($prefix -eq 'P:')) + } + return $false +} + +function Is-PackageFamilyName +{ + param( + [string]$packageFamilyName + ) + + $rc = [Microsoft.Windows.ApplicationModel.PackageFamilyName]::Verify($packageFamilyName) + return $rc == $ERROR_SUCCESS +} + +function Get-Processes +{ + param( + [string]$id + ) + + [uint32]$processIdsCount = 0 + [uint32[]]$processIds = $null + $hr = [Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency]::GetProcesses( + $id, $ScopeIsSystem, [ref] $processIdsCount, [ref] $processIds) + if ($hr -lt 0) + { + $win32ex = [System.ComponentModel.Win32Exception]::new($hr) + Write-Error "Error 0x$($hr.ToString('X')): $($win32ex.Message)" -ErrorAction Stop + } + return $processIds +} + +# Import the MSIX Dynamic Dependency module +if (-not (Get-Module | Where-Object {$_.Name -eq 'MsixDynamicDependency'})) +{ + $module = Join-Path $PSScriptRoot 'MsixDynamicDependency.psm1' + Import-Module -Name $module -ErrorAction Stop +} + +$ids = [ordered]@{} +if ($PSCmdlet.ParameterSetName -eq 'None') +{ + if ([string]::IsNullOrWhiteSpace($PackageDependencyId)) + { + $All = $true + } + elseif (Is-PackageFamilyName $PackageDependencyId) + { + $PackageFamilyName = $PackageDependencyId + $PackageDependencyId = $null + $PSCmdlet.ParameterSetName = 'Package' + } + else + { + $PSCmdlet.ParameterSetName = 'Id' + } +} + + + +if (-not [string]::IsNullOrWhiteSpace($PackageDependencyId)) +{ + $ids.Add($PackageDependencyId, $null) +} +else #if ($PSCmdlet.ParameterSetName -eq 'Package') +{ + $findPackageDependencyCriteria = New-Object Microsoft.Windows.ApplicationModel.DynamicDependency.FindPackageDependencyCriteria + $findPackageDependencyCriteria.User = [IntPtr]::Zero + $findPackageDependencyCriteria.ScopeIsSystem = $ScopeIsSystem + if ($PSCmdlet.ParameterSetName -eq 'Package') + { + $findPackageDependencyCriteria.PackageFamilyName = $PackageFamilyName + } + else # -All + { + $findPackageDependencyCriteria.PackageFamilyName = "" #$null + } + [uint]$packageDependencyIdsCount = 0 + [string[]]$packageDependencyIds = $null + $hr = [Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency]::Find( + $findPackageDependencyCriteria, [ref] $packageDependencyIdsCount, [ref] $packageDependencyIds); + if ($hr -lt 0) + { + $win32ex = [System.ComponentModel.Win32Exception]::new($hr) + Write-Error "Error 0x$($hr.ToString('X')): $($win32ex.Message)" -ErrorAction Stop + } + else + { + Write-Host "Package Dependencies: $packageDependencyIdsCount" + } + ForEach ($pdi in $packageDependencyIds) + { + $ids.Add($pdi, $null) + } +} + +ForEach ($pdi in $ids.Keys) +{ + $pids = Get-Processes $pdi + + [string]$familyName = $null + [ulong]$minVersion = 0 + $processorArchitectures = 0 + $lifetimeKind = 0 + [string]$lifetimeArtifact = $null + $options = 0 + $lifetimeExpiration = New-Object DateTime + $hr = [Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency]::GetInfo( + $pdi, [ref] $familyName, [ref] $minVersion, [ref] $processorArchitectures, + [ref] $lifetimeKind, [ref] $lifetimeArtifact, [ref] $options, [ref] $lifetimeExpiration) + if ($hr -lt 0) + { + $win32ex = [System.ComponentModel.Win32Exception]::new($hr) + Write-Error "Error 0x$($hr.ToString('X')): $($win32ex.Message)" -ErrorAction Stop + } + if ($lifetimeExpiration.Ticks -eq 0) + { + $lifetimeExpiration = $null + } + + $packageFullName = .\Get-PackageDependencyResolved.ps1 $pdi + + $pd = [PSCustomObject]@{ + PackageDependencyId = $pdi + PackageFamilyName = $familyName + ResolvedPackage = $packageFullName + MinVersion = $minVersion + ProcessorArchitectures = $processorArchitectures + LifetimeKind = $lifetimeKind + LifetimeArtifact = $lifetimeArtifact + Options = $options + LifetimeExpiration = $lifetimeExpiration + ProcessIDs = $pids + } + $pd | Format-List +} diff --git a/dev/DynamicDependency/Powershell/Get-PackageDependencyProcesses.ps1 b/dev/DynamicDependency/Powershell/Get-PackageDependencyProcesses.ps1 new file mode 100644 index 0000000000..5e8afd9aa4 --- /dev/null +++ b/dev/DynamicDependency/Powershell/Get-PackageDependencyProcesses.ps1 @@ -0,0 +1,52 @@ +# Copyright (c) Microsoft Corporation and Contributors. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Return the process ids of processes using the package dependency. + +.DESCRIPTION + Return the process ids of processes using the package dependency. + + This does not add the package to the invoking process' package graph. + +.PARAMETER PackageDependencyId + The ID of the resolved package dependency. + +.LINK + https://learn.microsoft.com/windows/win32/api/appmodel/nf-appmodel-getprocessesusingpackagedependency +#> +[CmdletBinding(SupportsShouldProcess=$true)] +param( + [Parameter(Mandatory=$true)] + [string]$PackageDependencyId, + + [switch]$ScopeIsSystem +) + +Set-StrictMode -Version 3.0 + +$ErrorActionPreference = "Stop" + +# Import the MSIX Dynamic Dependency module +if (-not (Get-Module | Where-Object {$_.Name -eq 'MsixDynamicDependency'})) +{ + $module = Join-Path $PSScriptRoot 'MsixDynamicDependency.psm1' + Import-Module -Name $module -ErrorAction Stop +} + +[uint32]$processIdsCount = 0 +[uint32[]]$processIds = $null +$hr = [Microsoft.Windows.ApplicationModel.DynamicDependency.PackageDependency]::GetProcesses( + $PackageDependencyId, $ScopeIsSystem, [ref] $processIdsCount, [ref] $processIds) +if ($hr -lt 0) +{ + $win32ex = [System.ComponentModel.Win32Exception]::new($hr) + Write-Error "Error 0x$($hr.ToString('X')): $($win32ex.Message)" -ErrorAction Stop +} +else +{ + Write-Host "Processes: $processIdsCount" +} + +$processIds diff --git a/dev/DynamicDependency/Powershell/MsixDynamicDependency.psm1 b/dev/DynamicDependency/Powershell/MsixDynamicDependency.psm1 index 2590b6c6f2..13cc53a7d5 100644 --- a/dev/DynamicDependency/Powershell/MsixDynamicDependency.psm1 +++ b/dev/DynamicDependency/Powershell/MsixDynamicDependency.psm1 @@ -21,6 +21,22 @@ None Add-Type -TypeDefinition @" using System; using System.Runtime.InteropServices; +using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; + +namespace Microsoft.Windows.ApplicationModel +{ + public class PackageFamilyName + { + [DllImport("kernelbase.dll", EntryPoint="VerifyPackageFamilyName", ExactSpelling=true, CharSet = CharSet.Unicode)] + private static extern int kernelbase_VerifyPackageFamilyName( + /*_In_ PCWSTR*/ string packageFamilyName); + + public static int Verify(string packageFamilyName) + { + return kernelbase_VerifyPackageFamilyName(packageFamilyName); + } + } +} namespace Microsoft.Windows.ApplicationModel.DynamicDependency { @@ -59,6 +75,13 @@ namespace Microsoft.Windows.ApplicationModel.DynamicDependency PrependIfRankCollision = 0x00000001, }; + public enum AddPackageDependencyOptions2 + { + None = 0, + PrependIfRankCollision = 0x00000001, + SpecifiedPackageFamilyOnly = 0x00000002, + }; + public class Rank { public const int Default = 0; @@ -75,6 +98,24 @@ namespace Microsoft.Windows.ApplicationModel.DynamicDependency X86A64 = 0x00000020, }; + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct FindPackageDependencyCriteria + { + /// Match package dependencies for this user if not NULL. + /// Match package dependencies for the current user if NULL (and ScopeIsSystem=FALSE). + /// @note This MUST be NULL if ScopeIsSystem=TRUE. + /// @note Admin privilege is required if User is not NULL and not the current user. + public /*PSID*/ IntPtr User; + + /// Match package dependencies created with CreatePackageDependencyOptions_ScopeIsSystem this is TRUE. + /// @note Admin privilege is required if ScopeIsSystem is TRUE. + [MarshalAs(UnmanagedType.Bool)] + public bool ScopeIsSystem; + + /// Match package dependencies with this package family. Ignore if NULL or empty (""). + public string PackageFamilyName; + } + public class PackageDependency { [DllImport("kernelbase.dll", EntryPoint="GetProcessHeap", ExactSpelling=true, SetLastError=true)] @@ -88,38 +129,82 @@ namespace Microsoft.Windows.ApplicationModel.DynamicDependency return kernel32_HeapFree(kernel32_GetProcessHeap(), 0, p); } - // Define a package dependency. The criteria for a PackageDependency - // (package family name, minimum version, etc) - // may match multiple packages, but ensures Deployment won't remove - // a package if it's the only one satisfying the PackageDependency. - // - // @note A package matching a PackageDependency pin can still be removed - // as long as there's another package that satisfies the PackageDependency. - // For example, if Fwk-v1 is installed and a PackageDependency specifies - // MinVersion=1 and then Fwk-v2 is installed, Deployment could remove - // Fwk-v1 because Fwk-v2 will satisfy the PackageDependency. After Fwk-v1 - // is removed Deployment won't remove Fwk-v2 because it's the only package - // satisfying the PackageDependency. Thus Fwk-v1 and Fwk-v2 (and any other - // package matching the PackageDependency) are 'loosely pinned'. Deployment - // guarantees it won't remove a package if it would make a PackageDependency - // unsatisfied. - // - // A PackageDependency specifies criteria (package family, minimum version, etc) - // and not a specific package. Deployment reserves the right to use a different - // package (e.g. higher version) to satisfy the PackageDependency if/when - // one becomes available. - // - // @param user the user scope of the package dependency. If NULL the caller's - // user context is used. MUST be NULL if CreatePackageDependencyOptions_ScopeIsSystem - // is specified - // @param lifetimeArtifact MUST be NULL if lifetimeKind=Process - // @param packageDependencyId allocated via HeapAlloc; use HeapFree to deallocate - // - // @note TryCreatePackageDependency() fails if the PackageDependency cannot be resolved to a specific - // package. This package resolution check is skipped if - // CreatePackageDependencyOptions_DoNotVerifyDependencyResolution is specified. This is useful - // for installers running as user contexts other than the target user (e.g. installers - // running as LocalSystem). + [System.Flags] + enum LoadLibraryFlags : uint + { + DONT_RESOLVE_DLL_REFERENCES = 0x00000001, + LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010, + LOAD_LIBRARY_AS_DATAFILE = 0x00000002, + LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040, + LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020, + LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200, + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000, + LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100, + LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800, + LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400, + LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 + } + + [DllImport("kernel32.dll", EntryPoint="LoadLibraryExW", ExactSpelling=true, SetLastError=true)] + static extern IntPtr LoadLibraryExW(string lpFileName, IntPtr hReservedNull, LoadLibraryFlags dwFlags); + + [DllImport("kernel32.dll", EntryPoint="FreeLibrary", ExactSpelling=true, SetLastError=true)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool FreeLibrary(IntPtr hModule); + + [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)] + static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + private static bool IsExport(string feature) + { + IntPtr hmodule = LoadLibraryExW("kernelbase.dll", IntPtr.Zero, 0); + IntPtr fn = GetProcAddress(hmodule, "AddPackageDependency2"); + FreeLibrary(hmodule); + return fn != IntPtr.Zero; + } + + private static bool IsSupported(string feature) + { + switch (feature) + { + case "AddPackageDependency2": return IsExport(feature); + default: return false; + } + } + + /// Define a package dependency. The criteria for a PackageDependency + /// (package family name, minimum version, etc) + /// may match multiple packages, but ensures Deployment won't remove + /// a package if it's the only one satisfying the PackageDependency. + /// + /// @note A package matching a PackageDependency pin can still be removed + /// as long as there's another package that satisfies the PackageDependency. + /// For example, if Fwk-v1 is installed and a PackageDependency specifies + /// MinVersion=1 and then Fwk-v2 is installed, Deployment could remove + /// Fwk-v1 because Fwk-v2 will satisfy the PackageDependency. After Fwk-v1 + /// is removed Deployment won't remove Fwk-v2 because it's the only package + /// satisfying the PackageDependency. Thus Fwk-v1 and Fwk-v2 (and any other + /// package matching the PackageDependency) are 'loosely pinned'. Deployment + /// guarantees it won't remove a package if it would make a PackageDependency + /// unsatisfied. + /// + /// A PackageDependency specifies criteria (package family, minimum version, etc) + /// and not a specific package. Deployment reserves the right to use a different + /// package (e.g. higher version) to satisfy the PackageDependency if/when + /// one becomes available. + /// + /// @param user the user scope of the package dependency. If NULL the caller's + /// user context is used. MUST be NULL if CreatePackageDependencyOptions_ScopeIsSystem + /// is specified + /// @param lifetimeArtifact MUST be NULL if lifetimeKind=PackageDependencyLifetimeKind_Process + /// @param packageDependencyId allocated via HeapAlloc; use HeapFree to deallocate + /// + /// @note TryCreatePackageDependency() fails if the PackageDependency cannot be resolved to a specific + /// package. This package resolution check is skipped if + /// CreatePackageDependencyOptions_DoNotVerifyDependencyResolution is specified. This is useful + /// for installers running as user contexts other than the target user (e.g. installers + /// running as LocalSystem). + /// @see TryCreatePackageDependency2 [DllImport("kernelbase.dll", ExactSpelling=true, CharSet=CharSet.Unicode)] private static extern int TryCreatePackageDependency( /*PSID*/ IntPtr user, @@ -156,6 +241,92 @@ namespace Microsoft.Windows.ApplicationModel.DynamicDependency return hr; } + private static DateTime FiletimeToDateTime(FILETIME fileTime) + { + long hFT2 = (((long) fileTime.dwHighDateTime) << 32) | ((uint) fileTime.dwLowDateTime); + return DateTime.FromFileTimeUtc(hFT2); + } + + private static FILETIME DateTimeToFiletime(DateTime time) + { + FILETIME ft; + long hFT1 = time.ToFileTimeUtc(); + ft.dwLowDateTime = (int) (hFT1 & 0xFFFFFFFF); + ft.dwHighDateTime = (int) (hFT1 >> 32); + return ft; + } + + /// Define a package dependency. The criteria for a PackageDependency + /// (package family name, minimum version, etc) + /// may match multiple packages, but ensures Deployment won't remove + /// a package if it's the only one satisfying the PackageDependency. + /// + /// @note A package matching a PackageDependency pin can still be removed + /// as long as there's another package that satisfies the PackageDependency. + /// For example, if Fwk-v1 is installed and a PackageDependency specifies + /// MinVersion=1 and then Fwk-v2 is installed, Deployment could remove + /// Fwk-v1 because Fwk-v2 will satisfy the PackageDependency. After Fwk-v1 + /// is removed Deployment won't remove Fwk-v2 because it's the only package + /// satisfying the PackageDependency. Thus Fwk-v1 and Fwk-v2 (and any other + /// package matching the PackageDependency) are 'loosely pinned'. Deployment + /// guarantees it won't remove a package if it would make a PackageDependency + /// unsatisfied. + /// + /// A PackageDependency specifies criteria (package family, minimum version, etc) + /// and not a specific package. Deployment reserves the right to use a different + /// package (e.g. higher version) to satisfy the PackageDependency if/when + /// one becomes available. + /// + /// @param user the user scope of the package dependency. If NULL the caller's + /// user context is used. MUST be NULL if CreatePackageDependencyOptions_ScopeIsSystem + /// is specified + /// @param lifetimeArtifact MUST be NULL if lifetimeKind=PackageDependencyLifetimeKind_Process + /// @param packageDependencyId allocated via HeapAlloc; use HeapFree to deallocate + /// + /// @note TryCreatePackageDependency2() fails if the PackageDependency cannot be resolved to a specific + /// package. This package resolution check is skipped if + /// CreatePackageDependencyOptions_DoNotVerifyDependencyResolution is specified. This is useful + /// for installers running as user contexts other than the target user (e.g. installers + /// running as LocalSystem). + /// @see TryCreatePackageDependency + [DllImport("kernelbase.dll", ExactSpelling=true, CharSet=CharSet.Unicode)] + private static extern int TryCreatePackageDependency2( + /*PSID*/ IntPtr user, + string packageFamilyName, + /*PACKAGE_VERSION*/ long minVersion, + /*PackageDependencyProcessorArchitectures*/ int packageDependencyProcessorArchitectures, + /*PackageDependencyLifetimeKind*/ int lifetimeKind, + string lifetimeArtifact, + /*CreatePackageDependencyOptions*/ int options, + /*const FILETIME* */ ref FILETIME lifetimeExpiration, + /*_Outptr_result_maybenull_ PWSTR* */ out IntPtr packageDependencyId); + + public static int TryCreate2( + string packageFamilyName, + long minVersion, + /*PackageDependencyProcessorArchitectures*/ int packageDependencyProcessorArchitectures, + /*PackageDependencyLifetimeKind*/ int lifetimeKind, + string lifetimeArtifact, + /*CreatePackageDependencyOptions*/ int options, + /*const FILETIME* */ FILETIME lifetimeExpiration, + /*_Outptr_result_maybenull_ PWSTR* */ out string packageDependencyId) + { + packageDependencyId = null; + + IntPtr pdi = IntPtr.Zero; + int hr = TryCreatePackageDependency2(IntPtr.Zero, packageFamilyName, minVersion, packageDependencyProcessorArchitectures, + lifetimeKind, lifetimeArtifact, options, ref lifetimeExpiration, out pdi); + if (hr >= 0) + { + packageDependencyId = Marshal.PtrToStringUni(pdi); + } + if (pdi != IntPtr.Zero) + { + HeapFree(pdi); + } + return hr; + } + // Undefine a package dependency. Removing a pin on a PackageDependency is typically done at uninstall-time. // This implicitly occurs if the package dependency's 'lifetime artifact' (specified via TryCreatePackageDependency) // is deleted. Packages that are not referenced by other packages and have no pins are elegible to be removed. @@ -172,45 +343,46 @@ namespace Microsoft.Windows.ApplicationModel.DynamicDependency return DeletePackageDependency(packageDependencyId); } - // Resolve a previously-pinned PackageDependency to a specific package and - // add it to the invoking process' package graph. Once the dependency has - // been added other code-loading methods (LoadLibrary, CoCreateInstance, etc) - // can find the binaries in the resolved package. - // - // Package resolution is specific to a user and can return different values - // for different users on a system. - // - // Each successful AddPackageDependency() adds the resolve packaged to the - // calling process' package graph, even if already present. There is no - // duplicate 'detection' or 'filtering' applied by the API (multiple - // references from a package is not harmful). Once resolution is complete - // the package dependency stays resolved for that user until the last reference across - // all processes for that user is removed via RemovePackageDependency (or - // process termination). - // - // AddPackageDependency() adds the resolved package to the caller's package graph, - // per the rank specified. A process' package graph is a list of packages sorted by - // rank in ascending order (-infinity...0...+infinity). If package(s) are present in the - // package graph with the same rank as the call to AddPackageDependency the resolved - // package is (by default) added after others of the same rank. To add a package - // before others of the same rank, specify AddPackageDependencyOptions_PrependIfRankCollision. - // - // Every AddPackageDependency can be balanced by a RemovePackageDependency - // to remove the entry from the package graph. If the process terminates all package - // references are removed, but any pins stay behind. - // - // AddPackageDependency adds the resolved package to the process' package - // graph, per the rank and options parameters. The process' package - // graph is used to search for DLLs (per Dynamic-Link Library Search Order), - // WinRT objects and other resources; the caller can now load DLLs, activate - // WinRT objects and use other resources from the framework package until - // RemovePackageDependency is called. The packageDependencyId parameter - // must match a package dependency defined for the calling user or the - // system (i.e. pinned with CreatePackageDependencyOptions_ScopeIsSystem) else - // an error is returned. - // - // @param packageDependencyContext valid until passed to RemovePackageDependency() - // @param packageFullName allocated via HeapAlloc; use HeapFree to deallocate + /// Resolve a previously-pinned PackageDependency to a specific package and + /// add it to the invoking process' package graph. Once the dependency has + /// been added other code-loading methods (LoadLibrary, CoCreateInstance, etc) + /// can find the binaries in the resolved package. + /// + /// Package resolution is specific to a user and can return different values + /// for different users on a system. + /// + /// Each successful AddPackageDependency() adds the resolve packaged to the + /// calling process' package graph, even if already present. There is no + /// duplicate 'detection' or 'filtering' applied by the API (multiple + /// references from a package is not harmful). Once resolution is complete + /// the package dependency stays resolved for that user until the last reference across + /// all processes for that user is removed via RemovePackageDependency (or + /// process termination). + /// + /// AddPackageDependency() adds the resolved package to the caller's package graph, + /// per the rank specified. A process' package graph is a list of packages sorted by + /// rank in ascending order (-infinity...0...+infinity). If package(s) are present in the + /// package graph with the same rank as the call to AddPackageDependency the resolved + /// package is (by default) added after others of the same rank. To add a package + /// before others o the same rank, specify AddPackageDependencyOptions_PrependIfRankCollision. + /// + /// Every AddPackageDependency can be balanced by a RemovePackageDependency + /// to remove the entry from the package graph. If the process terminates all package + /// references are removed, but any pins stay behind. + /// + /// AddPackageDependency adds the resolved package to the process' package + /// graph, per the rank and options parameters. The process' package + /// graph is used to search for DLLs (per Dynamic-Link Library Search Order), + /// WinRT objects and other resources; the caller can now load DLLs, activate + /// WinRT objects and use other resources from the framework package until + /// RemovePackageDependency is called. The packageDependencyId parameter + /// must match a package dependency defined for the calling user or the + /// system (i.e. pinned with CreatePackageDependencyOptions_ScopeIsSystem) else + /// an error is returned. + /// + /// @param packageDependencyContext valid until passed to RemovePackageDependency() + /// @param packageFullName allocated via HeapAlloc; use HeapFree to deallocate + /// @see AddPackageDependency2() [DllImport("kernelbase.dll", ExactSpelling=true, CharSet=CharSet.Unicode)] private static extern int AddPackageDependency( string packageDependencyId, @@ -244,6 +416,79 @@ namespace Microsoft.Windows.ApplicationModel.DynamicDependency return hr; } + /// Resolve a previously-pinned PackageDependency to a specific package and + /// add it to the invoking process' package graph. Once the dependency has + /// been added other code-loading methods (LoadLibrary, CoCreateInstance, etc) + /// can find the binaries in the resolved package. + /// + /// Package resolution is specific to a user and can return different values + /// for different users on a system. + /// + /// Each successful AddPackageDependency2() adds the resolve packaged to the + /// calling process' package graph, even if already present. There is no + /// duplicate 'detection' or 'filtering' applied by the API (multiple + /// references from a package is not harmful). Once resolution is complete + /// the package dependency stays resolved for that user until the last reference across + /// all processes for that user is removed via RemovePackageDependency (or + /// process termination). + /// + /// AddPackageDependency2() adds the resolved package to the caller's package graph, + /// per the rank specified. A process' package graph is a list of packages sorted by + /// rank in ascending order (-infinity...0...+infinity). If package(s) are present in the + /// package graph with the same rank as the call to AddPackageDependency2 the resolved + /// package is (by default) added after others of the same rank. To add a package + /// before others o the same rank, specify AddPackageDependencyOptions2_PrependIfRankCollision. + /// + /// Every AddPackageDependency2 can be balanced by a RemovePackageDependency + /// to remove the entry from the package graph. If the process terminates all package + /// references are removed, but any pins stay behind. + /// + /// AddPackageDependency2 adds the resolved package to the process' package + /// graph, per the rank and options parameters. The process' package + /// graph is used to search for DLLs (per Dynamic-Link Library Search Order), + /// WinRT objects and other resources; the caller can now load DLLs, activate + /// WinRT objects and use other resources from the framework package until + /// RemovePackageDependency is called. The packageDependencyId parameter + /// must match a package dependency defined for the calling user or the + /// system (i.e. pinned with CreatePackageDependencyOptions_ScopeIsSystem) else + /// an error is returned. + /// + /// @param packageDependencyContext valid until passed to RemovePackageDependency() + /// @param packageFullName allocated via HeapAlloc; use HeapFree to deallocate + /// @see AddPackageDependency() + [DllImport("kernelbase.dll", ExactSpelling=true, CharSet=CharSet.Unicode)] + private static extern int AddPackageDependency2( + string packageDependencyId, + int rank, + /*AddPackageDependencyOptions2*/ int options, + /*_Out_ PACKAGEDEPENDENCY_CONTEXT* */ out IntPtr packageDependencyContext, + /*_Outptr_opt_result_maybenull_ PWSTR* */ out IntPtr packageFullName); + + public static int Add2( + string packageDependencyId, + int rank, + /*AddPackageDependencyOptions2*/ int options, + /*_Out_ PACKAGEDEPENDENCY_CONTEXT* */ out IntPtr packageDependencyContext, + out string packageFullName) + { + packageDependencyContext = IntPtr.Zero; + packageFullName = null; + + IntPtr pdc = IntPtr.Zero; + IntPtr pfn = IntPtr.Zero; + int hr = AddPackageDependency2(packageDependencyId, rank, options, out pdc, out pfn); + if (hr >= 0) + { + packageDependencyContext = pdc; + packageFullName = Marshal.PtrToStringUni(pfn); + } + if (pfn != IntPtr.Zero) + { + HeapFree(pfn); + } + return hr; + } + // Remove a resolved PackageDependency from the current process' package graph // (i.e. undo AddPackageDependency). Used at runtime (i.e. the moral equivalent // of Windows' RemoveDllDirectory()). @@ -262,13 +507,14 @@ namespace Microsoft.Windows.ApplicationModel.DynamicDependency return RemovePackageDependency(packageDependencyContext); } - // Return the package full name that would be used if the - // PackageDependency were to be resolved. Does not add the - // package to the process graph. - // - // @param packageFullName allocated via HeapAlloc; use HeapFree to deallocate. - // If the package dependency cannot be resolved the function - // succeeds but packageFullName is nullptr. + /// Return the package full name that would be used if the + /// PackageDependency were to be resolved. Does not add the + /// package to the process graph. + /// + /// @param packageFullName allocated via HeapAlloc; use HeapFree to deallocate. + /// If the package dependency cannot be resolved the function + /// succeeds but packageFullName is NULL. + /// @see GetResolvedPackageFullNameForPackageDependency2 [DllImport("kernelbase.dll", ExactSpelling=true, CharSet=CharSet.Unicode)] private static extern int GetResolvedPackageFullNameForPackageDependency( string packageDependencyId, @@ -293,6 +539,37 @@ namespace Microsoft.Windows.ApplicationModel.DynamicDependency return hr; } + /// Return the package full name that would be used if the + /// PackageDependency were to be resolved. Does not add the + /// package to the process graph. + /// + /// @param packageFullName allocated via HeapAlloc; use HeapFree to deallocate. + /// @return E_INVALIDARG if packageDependencyId is not a valid package dependency. + /// @see GetResolvedPackageFullNameForPackageDependency + [DllImport("kernelbase.dll", ExactSpelling=true, CharSet=CharSet.Unicode)] + private static extern int GetResolvedPackageFullNameForPackageDependency2( + string packageDependencyId, + /*_Outptr_result_maybenull_ PWSTR* */ out IntPtr packageFullName); + + public static int GetResolvedPackageFullName2( + string packageDependencyId, + out string packageFullName) + { + packageFullName = null; + + IntPtr pfn = IntPtr.Zero; + int hr = GetResolvedPackageFullNameForPackageDependency(packageDependencyId, out pfn); + if (hr >= 0) + { + packageFullName = Marshal.PtrToStringUni(pfn); + } + if (pfn != IntPtr.Zero) + { + HeapFree(pfn); + } + return hr; + } + // Return the package dependency for the context. // // @param packageDependencyId allocated via HeapAlloc; use HeapFree to deallocate. @@ -321,6 +598,171 @@ namespace Microsoft.Windows.ApplicationModel.DynamicDependency } return hr; } + + /// Retrieve package dependencies. + /// @param packageDependencyIds allocated via HeapAlloc; use HeapFree to deallocate + /// + /// @see FindPackageDependencyCriteria + /// @see TryCreatePackageDependency2 + [DllImport("kernelbase.dll", ExactSpelling=true, CharSet=CharSet.Unicode)] + private static extern int FindPackageDependency( + /*const FindPackageDependencyCriteria* */ ref FindPackageDependencyCriteria findPackageDependencyCriteria, + /*_Out_ UINT32* */ out /*ref*/ uint packageDependencyIdsCount, + /*_Outptr_result_buffer_maybenull_(*packageDependencyIdsCount) PWSTR** */ out IntPtr packageDependencyIds); + + public static int Find( + FindPackageDependencyCriteria findPackageDependencyCriteria, + out uint packageDependencyIdsCount, + out string[] packageDependencyIds) + { + packageDependencyIdsCount = 0; + packageDependencyIds = null; + + IntPtr pdis = IntPtr.Zero; + int hr = FindPackageDependency(ref findPackageDependencyCriteria, out packageDependencyIdsCount, out pdis); + if (hr >= 0) + { + if (packageDependencyIdsCount > 0) + { + string[] ids = new string[packageDependencyIdsCount]; + int ptrSize = IntPtr.Size; + for (uint index=0; index < packageDependencyIdsCount; ++index) + { + IntPtr stringPtr = Marshal.ReadIntPtr(pdis, (int)(index * ptrSize)); + ids[index] = Marshal.PtrToStringUni(stringPtr); + } + packageDependencyIds = ids; + } + else + { + packageDependencyIds = null; + } + } + if (pdis != IntPtr.Zero) + { + HeapFree(pdis); + } + return hr; + } + + /// Retrieve information about a package dependency. + /// + /// @param user allocated via HeapAlloc; use HeapFree to deallocate + /// @param packageFamilyName allocated via HeapAlloc; use HeapFree to deallocate + /// @param lifetimeArtifact allocated via HeapAlloc; use HeapFree to deallocate + /// @param lifetimeExpiration if specified, the value is zero if expiration date is not set. + /// @note Admin privilege is required the package dependency was created with CreatePackageDependencyOptions_ScopeIsSystem or user is not NULL and not the current user. + /// @see FindPackageDependency + [DllImport("kernelbase.dll", ExactSpelling=true, CharSet=CharSet.Unicode)] + private static extern int GetPackageDependencyInformation( + /*PCWSTR*/ string packageDependencyId, + /*_Outptr_opt_result_maybenull_ PSID* */ out IntPtr user, + /*_Outptr_opt_result_maybenull_ PWSTR* */ out IntPtr packageFamilyName, + /*_Out_opt_ PACKAGE_VERSION* */ out ulong minVersion, + /*_Out_opt_ PackageDependencyProcessorArchitectures* */ out PackageDependencyProcessorArchitectures packageDependencyProcessorArchitectures, + /*_Out_opt_ PackageDependencyLifetimeKind* */ out PackageDependencyLifetimeKind lifetimeKind, + /*_Outptr_opt_result_maybenull_ PWSTR* */ out IntPtr lifetimeArtifact, + /*_Out_opt_ CreatePackageDependencyOptions* */ out int options, + /*_Out_opt_ FILETIME* */ out FILETIME lifetimeExpiration); + + public static int GetInfo( + string packageDependencyId, + out string packageFamilyName, + out ulong minVersion, + out PackageDependencyProcessorArchitectures packageDependencyProcessorArchitectures, + out PackageDependencyLifetimeKind lifetimeKind, + out string lifetimeArtifact, + out int options, + out DateTime lifetimeExpiration) + { + packageFamilyName = null; + minVersion = 0; + packageDependencyProcessorArchitectures = 0; + lifetimeKind = 0; + lifetimeArtifact = null; + options = 0; + lifetimeExpiration = DateTime.MinValue; + + IntPtr u = IntPtr.Zero; + IntPtr pfn = IntPtr.Zero; + IntPtr la = IntPtr.Zero; + FILETIME le; + int hr = GetPackageDependencyInformation(packageDependencyId, out u, out pfn, out minVersion, + out packageDependencyProcessorArchitectures, + out lifetimeKind, out la, out options, out le); + if (hr >= 0) + { + packageFamilyName = Marshal.PtrToStringUni(pfn); + if (la != IntPtr.Zero) + { + lifetimeArtifact = Marshal.PtrToStringUni(la); + } + if ((le.dwHighDateTime != 0) || (le.dwLowDateTime != 0)) + { + lifetimeExpiration = FiletimeToDateTime(le); + } + } + if (pfn != IntPtr.Zero) + { + HeapFree(pfn); + } + if (la != IntPtr.Zero) + { + HeapFree(la); + } + return hr; + } + + /// Retrieve the list of processes using a package dependency. + /// + /// @param user the user scope of the package dependency. If NULL the caller's + /// user context is used. MUST be NULL if scopeIsSystem=TRUE. + /// @param processIdsCount allocated via HeapAlloc; use HeapFree to deallocate + /// @param processIds allocated via HeapAlloc; use HeapFree to deallocate + /// @note Admin privilege is required if scopeIsSystem=TRUE or user is not NULL and not the current user. + /// @see FindPackageDependency + [DllImport("kernelbase.dll", ExactSpelling=true, CharSet=CharSet.Unicode)] + private static extern int GetProcessesUsingPackageDependency( + /*PCWSTR*/ string packageDependencyId, + /*PSID*/ IntPtr user, + /*BOOL*/ bool scopeIsSystem, + /*_Out_ UINT32* */ out uint processIdsCount, + /*_Outptr_result_buffer_maybenull_(*processIdsCount) DWORD** */ out IntPtr processIds); + + public static int GetProcesses( + string packageDependencyId, + bool scopeIsSystem, + out uint processIdsCount, + out uint[] processIds) + { + processIdsCount = 0; + processIds = null; + + IntPtr pis = IntPtr.Zero; + int hr = GetProcessesUsingPackageDependency(packageDependencyId, IntPtr.Zero, scopeIsSystem, out processIdsCount, out pis); + if (hr >= 0) + { + if (processIdsCount > 0) + { + uint[] pids = new uint[processIdsCount]; + int pidSize = sizeof(uint); + for (uint index=0; index < processIdsCount; ++index) + { + pids[index] = (uint)Marshal.ReadInt32(pis, (int)(index * pidSize)); + } + processIds = pids; + } + else + { + processIds = null; + } + } + if (pis != IntPtr.Zero) + { + HeapFree(pis); + } + return hr; + } } public class PackageGraph diff --git a/dev/Interop/StoragePickers/FileOpenPicker.cpp b/dev/Interop/StoragePickers/FileOpenPicker.cpp index 775e6411ab..b61792f9ac 100644 --- a/dev/Interop/StoragePickers/FileOpenPicker.cpp +++ b/dev/Interop/StoragePickers/FileOpenPicker.cpp @@ -10,6 +10,7 @@ #include #include #include "TerminalVelocityFeatures-StoragePickers.h" +#include "TerminalVelocityFeatures-StoragePickers2.h" #include "PickerCommon.h" #include "PickFileResult.h" #include "PickerLocalization.h" @@ -49,17 +50,46 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation PickerCommon::ValidateStringNoEmbeddedNulls(value); m_commitButtonText = value; } - winrt::Windows::Foundation::Collections::IVector FileOpenPicker::FileTypeFilter() + winrt::Windows::Foundation::Collections::IMap> FileOpenPicker::FileTypeChoices() + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + return m_fileTypeChoices; + } + winrt::Windows::Foundation::Collections::IVector FileOpenPicker::FileTypeFilter() { return m_fileTypeFilter; } + winrt::hstring FileOpenPicker::SuggestedFolder() + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + return m_suggestedFolder; + } + void FileOpenPicker::SuggestedFolder(winrt::hstring const& value) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + PickerCommon::ValidateFolderPath(value, "SuggestedFolder"); + m_suggestedFolder = value; + } + winrt::hstring FileOpenPicker::SuggestedStartFolder() + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + return m_suggestedStartFolder; + } + void FileOpenPicker::SuggestedStartFolder(winrt::hstring const& value) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + PickerCommon::ValidateFolderPath(value, "SuggestedStartFolder"); + m_suggestedStartFolder = value; + } void FileOpenPicker::CaptureParameters(PickerCommon::PickerParameters& parameters) { parameters.HWnd = winrt::Microsoft::UI::GetWindowFromWindowId(m_windowId); parameters.CommitButtonText = m_commitButtonText; - parameters.PickerLocationId = m_suggestedStartLocation; - parameters.CaptureFilterSpec(m_fileTypeFilter.GetView()); + parameters.SuggestedFolder = m_suggestedFolder; + parameters.SuggestedStartLocation = m_suggestedStartLocation; + parameters.SuggestedStartFolder = m_suggestedStartFolder; + parameters.CaptureFilterSpecData(m_fileTypeFilter.GetView(), m_fileTypeChoices.GetView()); } winrt::Windows::Foundation::IAsyncOperation FileOpenPicker::PickSingleFileAsync() @@ -87,7 +117,6 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation auto dialog = create_instance(CLSID_FileOpenDialog, CLSCTX_INPROC_SERVER); parameters.ConfigureDialog(dialog); - check_hresult(dialog->SetFileTypeIndex(parameters.FileTypeFilterPara.size())); { auto hr = dialog->Show(parameters.HWnd); @@ -142,7 +171,6 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation auto dialog = create_instance(CLSID_FileOpenDialog, CLSCTX_INPROC_SERVER); parameters.ConfigureDialog(dialog); - check_hresult(dialog->SetFileTypeIndex(parameters.FileTypeFilterPara.size())); FILEOPENDIALOGOPTIONS dialogOptions; check_hresult(dialog->GetOptions(&dialogOptions)); diff --git a/dev/Interop/StoragePickers/FileOpenPicker.h b/dev/Interop/StoragePickers/FileOpenPicker.h index 47353b6310..c5ff80bc27 100644 --- a/dev/Interop/StoragePickers/FileOpenPicker.h +++ b/dev/Interop/StoragePickers/FileOpenPicker.h @@ -6,6 +6,7 @@ #include "PickerCommon.h" #include "StoragePickersTelemetryHelper.h" #include +#include "FileTypeChoicesMap.h" #include "FileTypeFilterVector.h" namespace winrt::Microsoft::Windows::Storage::Pickers::implementation @@ -23,7 +24,14 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation winrt::hstring CommitButtonText(); void CommitButtonText(winrt::hstring const& value); - winrt::Windows::Foundation::Collections::IVector FileTypeFilter(); + winrt::Windows::Foundation::Collections::IVector FileTypeFilter(); + winrt::Windows::Foundation::Collections::IMap> FileTypeChoices(); + + winrt::hstring SuggestedFolder(); + void SuggestedFolder(winrt::hstring const& value); + + winrt::hstring SuggestedStartFolder(); + void SuggestedStartFolder(winrt::hstring const& value); winrt::Windows::Foundation::IAsyncOperation PickSingleFileAsync(); winrt::Windows::Foundation::IAsyncOperation> PickMultipleFilesAsync(); @@ -34,7 +42,18 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation PickerLocationId m_suggestedStartLocation{ PickerLocationId::Unspecified }; winrt::hstring m_commitButtonText{}; - winrt::Windows::Foundation::Collections::IVector m_fileTypeFilter{ make() }; + winrt::Windows::Foundation::Collections::IVector m_fileTypeFilter{ make() }; + winrt::Windows::Foundation::Collections::IMap> m_fileTypeChoices{ + []() + { + auto map = winrt::make_self(); + map->ForFeature_StoragePickers2 = true; + return map.as>>(); + }() + }; + + winrt::hstring m_suggestedFolder{}; + winrt::hstring m_suggestedStartFolder{}; StoragePickersTelemetryHelper m_telemetryHelper{}; diff --git a/dev/Interop/StoragePickers/FileSavePicker.cpp b/dev/Interop/StoragePickers/FileSavePicker.cpp index aa1f091b53..95e965be00 100644 --- a/dev/Interop/StoragePickers/FileSavePicker.cpp +++ b/dev/Interop/StoragePickers/FileSavePicker.cpp @@ -15,6 +15,7 @@ #include #include #include "TerminalVelocityFeatures-StoragePickers.h" +#include "TerminalVelocityFeatures-StoragePickers2.h" #include "PickerCommon.h" #include "PickerLocalization.h" #include "PickFileResult.h" @@ -63,10 +64,22 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation } void FileSavePicker::SuggestedFolder(hstring const& value) { - PickerCommon::ValidateSuggestedFolder(value); + PickerCommon::ValidateFolderPath(value, "SuggestedFolder"); m_suggestedFolder = value; } + hstring FileSavePicker::SuggestedStartFolder() + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + return m_suggestedStartFolder; + } + void FileSavePicker::SuggestedStartFolder(hstring const& value) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + PickerCommon::ValidateFolderPath(value, "SuggestedStartFolder"); + m_suggestedStartFolder = value; + } + hstring FileSavePicker::SuggestedFileName() { return m_suggestedFileName; @@ -82,10 +95,13 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation { parameters.HWnd = winrt::Microsoft::UI::GetWindowFromWindowId(m_windowId); parameters.CommitButtonText = m_commitButtonText; - parameters.PickerLocationId = m_suggestedStartLocation; parameters.SuggestedFileName = m_suggestedFileName; parameters.SuggestedFolder = m_suggestedFolder; - parameters.CaptureFilterSpec(m_fileTypeChoices.GetView()); + parameters.SuggestedStartLocation = m_suggestedStartLocation; + parameters.SuggestedStartFolder = m_suggestedStartFolder; + parameters.CaptureFilterSpecData( + winrt::Windows::Foundation::Collections::IVectorView{}, + m_fileTypeChoices.GetView()); } winrt::Windows::Foundation::IAsyncOperation FileSavePicker::PickSaveFileAsync() diff --git a/dev/Interop/StoragePickers/FileSavePicker.h b/dev/Interop/StoragePickers/FileSavePicker.h index 59a3eb977a..849c819819 100644 --- a/dev/Interop/StoragePickers/FileSavePicker.h +++ b/dev/Interop/StoragePickers/FileSavePicker.h @@ -29,6 +29,9 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation hstring SuggestedFolder(); void SuggestedFolder(hstring const& value); + hstring SuggestedStartFolder(); + void SuggestedStartFolder(hstring const& value); + hstring SuggestedFileName(); void SuggestedFileName(hstring const& value); @@ -41,6 +44,7 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation winrt::Windows::Foundation::Collections::IMap> m_fileTypeChoices{ make() }; hstring m_defaultFileExtension{}; hstring m_suggestedFolder{}; + hstring m_suggestedStartFolder{}; hstring m_suggestedFileName{}; StoragePickersTelemetryHelper m_telemetryHelper{}; diff --git a/dev/Interop/StoragePickers/FileTypeChoicesMap.cpp b/dev/Interop/StoragePickers/FileTypeChoicesMap.cpp index 3f04a4a373..0d8571ed8b 100644 --- a/dev/Interop/StoragePickers/FileTypeChoicesMap.cpp +++ b/dev/Interop/StoragePickers/FileTypeChoicesMap.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "FileTypeChoicesMap.h" #include "FileTypeFilterVector.h" +#include "TerminalVelocityFeatures-StoragePickers2.h" namespace winrt::Microsoft::Windows::Storage::Pickers::implementation { @@ -13,6 +14,11 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation bool FileTypeChoicesMap::Insert(hstring const& key, winrt::Windows::Foundation::Collections::IVector const& value) { + if (ForFeature_StoragePickers2) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + } + // Create a new FileTypeFilterVector and copy all values from the input vector auto validatingVector = make(); diff --git a/dev/Interop/StoragePickers/FileTypeChoicesMap.h b/dev/Interop/StoragePickers/FileTypeChoicesMap.h index 0f1e2a1a87..1dc6143667 100644 --- a/dev/Interop/StoragePickers/FileTypeChoicesMap.h +++ b/dev/Interop/StoragePickers/FileTypeChoicesMap.h @@ -13,6 +13,8 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation { FileTypeChoicesMap(); + bool ForFeature_StoragePickers2{ false }; + // IMap> winrt::Windows::Foundation::Collections::IVector Lookup(hstring const& key) const; uint32_t Size() const; diff --git a/dev/Interop/StoragePickers/FolderPicker.cpp b/dev/Interop/StoragePickers/FolderPicker.cpp index c95a581607..5da7ab331e 100644 --- a/dev/Interop/StoragePickers/FolderPicker.cpp +++ b/dev/Interop/StoragePickers/FolderPicker.cpp @@ -9,6 +9,7 @@ #include #include #include "TerminalVelocityFeatures-StoragePickers.h" +#include "TerminalVelocityFeatures-StoragePickers2.h" #include "PickerCommon.h" #include "PickFolderResult.h" #include "PickerLocalization.h" @@ -47,15 +48,38 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation PickerCommon::ValidateStringNoEmbeddedNulls(value); m_commitButtonText = value; } + hstring FolderPicker::SuggestedFolder() + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + return m_suggestedFolder; + } + void FolderPicker::SuggestedFolder(hstring const& value) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + PickerCommon::ValidateFolderPath(value, "SuggestedFolder"); + m_suggestedFolder = value; + } + hstring FolderPicker::SuggestedStartFolder() + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + return m_suggestedStartFolder; + } + void FolderPicker::SuggestedStartFolder(hstring const& value) + { + THROW_HR_IF(E_NOTIMPL, !::Microsoft::Windows::Storage::Pickers::Feature_StoragePickers2::IsEnabled()); + PickerCommon::ValidateFolderPath(value, "SuggestedStartFolder"); + m_suggestedStartFolder = value; + } void FolderPicker::CaptureParameters(PickerCommon::PickerParameters& parameters) { parameters.HWnd = winrt::Microsoft::UI::GetWindowFromWindowId(m_windowId); parameters.CommitButtonText = m_commitButtonText; - parameters.PickerLocationId = m_suggestedStartLocation; + parameters.SuggestedFolder = m_suggestedFolder; + parameters.SuggestedStartLocation = m_suggestedStartLocation; + parameters.SuggestedStartFolder = m_suggestedStartFolder; } - winrt::Windows::Foundation::IAsyncOperation FolderPicker::PickSingleFolderAsync() { // TODO: remove get strong reference when telementry is safe stop diff --git a/dev/Interop/StoragePickers/FolderPicker.h b/dev/Interop/StoragePickers/FolderPicker.h index f5e8f88136..49e7c4b1f7 100644 --- a/dev/Interop/StoragePickers/FolderPicker.h +++ b/dev/Interop/StoragePickers/FolderPicker.h @@ -21,6 +21,12 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation hstring CommitButtonText(); void CommitButtonText(hstring const& value); + hstring SuggestedFolder(); + void SuggestedFolder(hstring const& value); + + hstring SuggestedStartFolder(); + void SuggestedStartFolder(hstring const& value); + winrt::Windows::Foundation::IAsyncOperation PickSingleFolderAsync(); private: @@ -29,6 +35,8 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation PickerViewMode m_viewMode{ PickerViewMode::List }; PickerLocationId m_suggestedStartLocation{ PickerLocationId::Unspecified }; hstring m_commitButtonText{}; + hstring m_suggestedFolder{}; + hstring m_suggestedStartFolder{}; StoragePickersTelemetryHelper m_telemetryHelper{}; void CaptureParameters(PickerCommon::PickerParameters& parameters); diff --git a/dev/Interop/StoragePickers/Microsoft.Windows.Storage.Pickers.idl b/dev/Interop/StoragePickers/Microsoft.Windows.Storage.Pickers.idl index 895fb2c621..c71d622193 100644 --- a/dev/Interop/StoragePickers/Microsoft.Windows.Storage.Pickers.idl +++ b/dev/Interop/StoragePickers/Microsoft.Windows.Storage.Pickers.idl @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. +#include + namespace Microsoft.Windows.Storage.Pickers { - [contractversion(1.8)] + [contractversion(2.0)] apicontract StoragePickersContract {}; - + [contract(StoragePickersContract, 1.8)] enum PickerViewMode { @@ -41,8 +43,21 @@ namespace Microsoft.Windows.Storage.Pickers Microsoft.Windows.Storage.Pickers.PickerViewMode ViewMode; Microsoft.Windows.Storage.Pickers.PickerLocationId SuggestedStartLocation; String CommitButtonText; + + [contract(StoragePickersContract, 2.0)] + [feature(Feature_StoragePickers2)] + Windows.Foundation.Collections.IMap > FileTypeChoices{ get; }; + Windows.Foundation.Collections.IVector FileTypeFilter{ get; }; + [contract(StoragePickersContract, 2.0)] + [feature(Feature_StoragePickers2)] + String SuggestedFolder; + + [contract(StoragePickersContract, 2.0)] + [feature(Feature_StoragePickers2)] + String SuggestedStartFolder; + [remote_sync] Windows.Foundation.IAsyncOperation PickSingleFileAsync(); [remote_sync] Windows.Foundation.IAsyncOperation > PickMultipleFilesAsync(); } @@ -59,6 +74,10 @@ namespace Microsoft.Windows.Storage.Pickers String SuggestedFileName; String SuggestedFolder; + [contract(StoragePickersContract, 2.0)] + [feature(Feature_StoragePickers2)] + String SuggestedStartFolder; + [remote_sync] Windows.Foundation.IAsyncOperation PickSaveFileAsync(); } @@ -77,6 +96,14 @@ namespace Microsoft.Windows.Storage.Pickers Microsoft.Windows.Storage.Pickers.PickerLocationId SuggestedStartLocation; String CommitButtonText; + [contract(StoragePickersContract, 2.0)] + [feature(Feature_StoragePickers2)] + String SuggestedFolder; + + [contract(StoragePickersContract, 2.0)] + [feature(Feature_StoragePickers2)] + String SuggestedStartFolder; + [remote_sync] Windows.Foundation.IAsyncOperation PickSingleFolderAsync(); } } diff --git a/dev/Interop/StoragePickers/PickerCommon.cpp b/dev/Interop/StoragePickers/PickerCommon.cpp index dec7a50d3a..d5c003cc77 100644 --- a/dev/Interop/StoragePickers/PickerCommon.cpp +++ b/dev/Interop/StoragePickers/PickerCommon.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include namespace { @@ -224,7 +226,7 @@ namespace PickerCommon { ValidateStringNoEmbeddedNulls(suggestedFileName); } - void ValidateSuggestedFolder(winrt::hstring const& path) + void ValidateFolderPath(winrt::hstring const& path, std::string const& propertyName) { if (path.empty()) { @@ -237,13 +239,14 @@ namespace PickerCommon { auto pathObj = std::filesystem::path(path.c_str()); if (!pathObj.is_absolute()) { - throw std::invalid_argument("SuggestedFolder"); + throw std::invalid_argument(propertyName); } + // The method SHSimpleIDListFromPath does syntax check on the path string. wil::unique_cotaskmem_ptr pidl(SHSimpleIDListFromPath(path.c_str())); if (!pidl) { - throw std::invalid_argument("SuggestedFolder"); + throw std::invalid_argument(propertyName); } } @@ -278,6 +281,28 @@ namespace PickerCommon { return result; } + void PickerParameters::CaptureFilterSpecData( + winrt::Windows::Foundation::Collections::IVectorView fileTypeFilterView, + winrt::Windows::Foundation::Collections::IMapView> fileTypeChoicesView) + { + // The FileTypeChoices takes precedence over FileTypeFilter if both are provided. + if (fileTypeChoicesView && fileTypeChoicesView.Size() > 0) + { + CaptureFilterSpec(fileTypeChoicesView); + return; + } + + if (fileTypeFilterView && fileTypeFilterView.Size() > 0) + { + CaptureFilterSpec(fileTypeFilterView); + return; + } + + // Even if no filters provided, we still need to set filter to All Files *.* + auto emptyFilters = winrt::single_threaded_vector(); + CaptureFilterSpec(emptyFilters.GetView()); + } + /// /// Capture and processing pickers filter inputs and convert them into Common Item Dialog's accepting type, for FileOpenPicker /// @@ -330,6 +355,8 @@ namespace PickerCommon { { FileTypeFilterPara.push_back({ FileTypeFilterData.at(i * 2).c_str(), FileTypeFilterData.at(i * 2 + 1).c_str() }); } + + FocusLastFilter = true; } /// @@ -372,15 +399,41 @@ namespace PickerCommon { check_hresult(dialog->SetOkButtonLabel(CommitButtonText.c_str())); } - auto defaultFolder = GetKnownFolderFromId(PickerLocationId); - if (defaultFolder != nullptr) + winrt::com_ptr defaultFolder{}; + + // The SuggestedStartFolder takes precedence over SuggestedStartLocation if both are provided. + if (!IsHStringNullOrEmpty(SuggestedStartFolder)) + { + defaultFolder = TryParseFolderItem(SuggestedStartFolder); + } + + if (!defaultFolder) + { + defaultFolder = GetKnownFolderFromId(SuggestedStartLocation); + } + + if (defaultFolder) { check_hresult(dialog->SetDefaultFolder(defaultFolder.get())); } + // SuggestedFolder takes precedence over SuggestedStartFolder/SuggestedStartLocation if both are provided. + if (!IsHStringNullOrEmpty(SuggestedFolder)) + { + if (auto folderItem = TryParseFolderItem(SuggestedFolder)) + { + check_hresult(dialog->SetFolder(folderItem.get())); + } + } + if (FileTypeFilterPara.size() > 0) { check_hresult(dialog->SetFileTypes((UINT)FileTypeFilterPara.size(), FileTypeFilterPara.data())); + + if (FocusLastFilter) + { + check_hresult(dialog->SetFileTypeIndex(FileTypeFilterPara.size())); + } } } @@ -395,13 +448,5 @@ namespace PickerCommon { check_hresult(dialog->SetFileName(SuggestedFileName.c_str())); } - if (!PickerCommon::IsHStringNullOrEmpty(SuggestedFolder)) - { - winrt::com_ptr folderItem = TryParseFolderItem(SuggestedFolder); - if (folderItem) - { - check_hresult(dialog->SetFolder(folderItem.get())); - } - } } } diff --git a/dev/Interop/StoragePickers/PickerCommon.h b/dev/Interop/StoragePickers/PickerCommon.h index 124a9eb261..f2bc2045ab 100644 --- a/dev/Interop/StoragePickers/PickerCommon.h +++ b/dev/Interop/StoragePickers/PickerCommon.h @@ -26,26 +26,33 @@ namespace PickerCommon { void ValidateSuggestedStartLocation(winrt::Microsoft::Windows::Storage::Pickers::PickerLocationId const& value); void ValidateSingleFileTypeFilterElement(winrt::hstring const& filter); void ValidateSuggestedFileName(winrt::hstring const& suggestedFileName); - void ValidateSuggestedFolder(winrt::hstring const& path); + void ValidateFolderPath(winrt::hstring const& path, std::string const& propertyName); struct PickerParameters { HWND HWnd{}; winrt::hstring CommitButtonText; - winrt::Microsoft::Windows::Storage::Pickers::PickerLocationId PickerLocationId; + winrt::Microsoft::Windows::Storage::Pickers::PickerLocationId SuggestedStartLocation; std::vector FileTypeFilterData{}; std::vector FileTypeFilterPara{}; + bool FocusLastFilter{ false }; winrt::hstring AllFilesText{ L"All Files" }; // initialize to All Files as a default value, will be updated by localization winrt::hstring SuggestedFileName; winrt::hstring SuggestedFolder; + winrt::hstring SuggestedStartFolder; winrt::hstring FormatExtensionWithWildcard(winrt::hstring extension); winrt::hstring JoinExtensions(winrt::Windows::Foundation::Collections::IVectorView extensions); - void CaptureFilterSpec(winrt::Windows::Foundation::Collections::IVectorView filters); - void CaptureFilterSpec(winrt::Windows::Foundation::Collections::IMapView> filters); + void CaptureFilterSpecData( + winrt::Windows::Foundation::Collections::IVectorView fileTypeFilterView, + winrt::Windows::Foundation::Collections::IMapView> fileTypeChoicesView); void ConfigureDialog(winrt::com_ptr dialog); void ConfigureFileSaveDialog(winrt::com_ptr dialog); + + private: + void CaptureFilterSpec(winrt::Windows::Foundation::Collections::IVectorView filters); + void CaptureFilterSpec(winrt::Windows::Foundation::Collections::IMapView> filters); }; } diff --git a/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.cpp b/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.cpp index 067a9158eb..8f233ba6cd 100644 --- a/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.cpp +++ b/dev/PackageManager/API/M.W.M.D.PackageDeploymentManager.cpp @@ -1966,6 +1966,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; const auto progressAfter{ progressBefore + PercentageToProgress(progressInfo.percentage, progressMaxPerPackageSetItem) }; if (packageDeploymentProgress.Progress < progressAfter) { @@ -2050,6 +2051,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; const auto progressAfter{ progressBefore + PercentageToProgress(progressInfo.percentage, progressMaxPerPackage) }; if (packageDeploymentProgress.Progress < progressAfter) { @@ -2144,6 +2146,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; const auto progressAfter{ progressBefore + PercentageToProgress(progressInfo.percentage, progressMaxPerPackage) }; if (packageDeploymentProgress.Progress < progressAfter) { @@ -2238,6 +2241,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; const auto progressAfter{ progressBefore + PercentageToProgress(progressInfo.percentage, progressMaxPerPackage) }; if (packageDeploymentProgress.Progress < progressAfter) { @@ -2316,6 +2320,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; const auto progressAfter{ progressBefore + PercentageToProgress(progressInfo.percentage, progressMaxPerPackage) }; if (packageDeploymentProgress.Progress < progressAfter) { @@ -2391,6 +2396,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; const double progressMaxPerPackage{ 1.0 }; packageDeploymentProgress.Progress = PercentageToProgress(progressInfo.percentage, progressMaxPerPackage); progress(packageDeploymentProgress); @@ -2496,6 +2502,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; packageDeploymentProgress.Progress = PercentageToProgress(progressInfo.percentage, progressMaxPerPackage); progress(packageDeploymentProgress); }); @@ -2815,6 +2822,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; packageDeploymentProgress.Progress = PercentageToProgress(progressInfo.percentage, progressMaxPerPackage); progress(packageDeploymentProgress); }); @@ -3049,6 +3057,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; packageDeploymentProgress.Progress = PercentageToProgress(progressInfo.percentage, progressMaxPerPackage); progress(packageDeploymentProgress); }); @@ -3192,6 +3201,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; const double progressMaxPerPackage{ 1.0 }; packageDeploymentProgress.Progress = PercentageToProgress(progressInfo.percentage, progressMaxPerPackage); progress(packageDeploymentProgress); @@ -3296,6 +3306,7 @@ namespace winrt::Microsoft::Windows::Management::Deployment::implementation winrt::Windows::Management::Deployment::DeploymentProgress> const& /*sender*/, winrt::Windows::Management::Deployment::DeploymentProgress const& progressInfo) { + packageDeploymentProgress.Status = PackageDeploymentProgressStatus::InProgress; const double progressMaxPerPackage{ 1.0 }; packageDeploymentProgress.Progress = PercentageToProgress(progressInfo.percentage, progressMaxPerPackage); progress(packageDeploymentProgress); diff --git a/dev/PushNotifications/PushNotificationsLongRunningTask/ToastRegistrationManager.h b/dev/PushNotifications/PushNotificationsLongRunningTask/ToastRegistrationManager.h index 26492de344..ebf4e748ee 100644 --- a/dev/PushNotifications/PushNotificationsLongRunningTask/ToastRegistrationManager.h +++ b/dev/PushNotifications/PushNotificationsLongRunningTask/ToastRegistrationManager.h @@ -1,10 +1,29 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #include #include #include +// Custom case-insensitive hash and equality for std::wstring +struct CaseInsensitiveWStringHash +{ + size_t operator()(const std::wstring& str) const + { + std::wstring lowerStr = str; + std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), ::towlower); + return std::hash{}(lowerStr); + } +}; + +struct CaseInsensitiveWStringEqual +{ + bool operator()(const std::wstring& left, const std::wstring& right) const + { + return _wcsicmp(left.c_str(), right.c_str()) == 0; + } +}; + class ToastRegistrationManager { public: @@ -19,6 +38,6 @@ class ToastRegistrationManager private: winrt::Windows::Storage::ApplicationDataContainer m_toastStorage{ nullptr }; - std::unordered_map m_registrationMap = {}; + std::unordered_map m_registrationMap = {}; wil::srwlock m_lock; }; diff --git a/dev/PushNotifications/externs.h b/dev/PushNotifications/externs.h index 87f43c4aef..a8547aca3c 100644 --- a/dev/PushNotifications/externs.h +++ b/dev/PushNotifications/externs.h @@ -37,6 +37,5 @@ inline std::wstring GetCurrentProcessPath() { std::wstring processPath{}; THROW_IF_FAILED(wil::GetModuleFileNameExW(GetCurrentProcess(), nullptr, processPath)); - std::transform(processPath.begin(), processPath.end(), processPath.begin(), ::towlower); return processPath; }; diff --git a/specs/Storage.Pickers/FileOpenPicker.md b/specs/Storage.Pickers/FileOpenPicker.md index a14e6b7c19..525145381b 100644 --- a/specs/Storage.Pickers/FileOpenPicker.md +++ b/specs/Storage.Pickers/FileOpenPicker.md @@ -19,8 +19,14 @@ runtimeclass FileOpenPicker FileOpenPicker(Microsoft.UI.WindowId windowId); string CommitButtonText; + + IMap> FileTypeChoices{ get; }; IVector FileTypeFilter{ get; }; + + string SuggestedFolder; + String SuggestedStartFolder; PickerLocationId SuggestedStartLocation; + PickerViewMode ViewMode; Windows.Foundation.IAsyncOperation PickSingleFileAsync(); @@ -38,6 +44,18 @@ using Microsoft.Windows.Storage.Pickers; var openPicker = new FileOpenPicker(this.AppWindow.Id) { + // (Optional) Sets the folder that the file dialog always tries to display when it opens. + // SuggestedFolder will not be overriden by the last picked folder. + // If not specified, or the specified path doesn't exist, defaults to the last folder the user picked. + // On first launch of the picker, SuggestedFolder takes precedence over the SuggestedStartFolder if both set. + SuggestedFolder = @"C:\MyFiles", + + // (Optional) Sets an initial folder path shown when the picker is first launched. + // Once the user has picked from a directory, SuggestedStartFolder will be silently ignored. + // Takes precedence over SuggestedStartLocation when both defined. + // If this folder is not found, falls back to SuggestedStartLocation. + SuggestedStartFolder = @"C:\MyFiles", + // (Optional) Specify the initial location for the picker. // If the specified location doesn't exist on the user's machine, it falls back to the DocumentsLibrary. // If not set, it defaults to PickerLocationId.Unspecified, and the system will use its default location. @@ -47,6 +65,13 @@ var openPicker = new FileOpenPicker(this.AppWindow.Id) // If not specified, the system uses a default label of "Open" (suitably translated). CommitButtonText = "Choose selected files", + // (Optional) group file types into labeled choices + // FileTypeChoices takes precedence over FileTypeFilter when both defined. + FileTypeChoices = { + { "Documents", new List { ".txt", ".doc", ".docx" } }, + { "Pictures", new List { ".png", ".jpg", ".jpeg", ".bmp" } } + }, + // (Optional) specify file extension filters. If not specified, defaults to all files (*.*). FileTypeFilter = { ".txt", ".pdf", ".doc", ".docx" }, @@ -63,6 +88,18 @@ using namespace winrt::Microsoft::Windows::Storage::Pickers; FileOpenPicker openPicker(AppWindow().Id()); +// (Optional) Sets the folder that the file dialog always tries to display when it opens. +// SuggestedFolder will not be overriden by the last picked folder. +// If not specified, or the specified path doesn't exist, defaults to the last folder the user picked. +// On first launch of the picker, SuggestedFolder takes precedence over the SuggestedStartFolder if both set. +openPicker.SuggestedFolder(L"C:\\MyFiles"); + +// (Optional) Sets an initial folder path shown when the picker is first launched. +// Once the user has picked from a directory, SuggestedStartFolder will be silently ignored. +// Takes precedence over SuggestedStartLocation when both defined. +// If this folder is not found, falls back to SuggestedStartLocation. +openPicker.SuggestedStartFolder(L"C:\\MyFiles"); + // (Optional) Specify the initial location for the picker. // If the specified location doesn't exist on the user's machine, it falls back to the DocumentsLibrary. // If not set, it defaults to PickerLocationId.Unspecified, and the system will use its default location. @@ -72,6 +109,12 @@ openPicker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary); // If not specified, the system uses a default label of "Open" (suitably translated). openPicker.CommitButtonText(L"Choose selected files"); +// (Optional) group file types into labeled choices +// FileTypeChoices takes precedence over FileTypeFilter when both defined. +auto choices = openPicker.FileTypeChoices(); +choices.Insert(L"Documents", winrt::single_threaded_vector({ L".txt", L".doc", L".docx" })); +choices.Insert(L"Pictures", winrt::single_threaded_vector({ L".png", L".jpg", L".jpeg", L".bmp" })); + // (Optional) specify file extension filters. If not specified, defaults to all files (*.*). openPicker.FileTypeFilter().ReplaceAll({ L".txt", L".pdf", L".doc", L".docx" }); diff --git a/specs/Storage.Pickers/FileSavePicker.md b/specs/Storage.Pickers/FileSavePicker.md index c4c936e54f..4d28aeab5f 100644 --- a/specs/Storage.Pickers/FileSavePicker.md +++ b/specs/Storage.Pickers/FileSavePicker.md @@ -19,10 +19,11 @@ runtimeclass FileSavePicker string CommitButtonText; string DefaultFileExtension; string SuggestedFileName; - string SuggestedFolder; IMap> FileTypeChoices{ get; }; + string SuggestedFolder; + String SuggestedStartFolder; PickerLocationId SuggestedStartLocation; Windows.Foundation.IAsyncOperation PickSaveFileAsync(); @@ -39,6 +40,18 @@ using Microsoft.Windows.Storage.Pickers; var savePicker = new FileSavePicker(this.AppWindow.Id) { + // (Optional) Sets the folder that the file save dialog always tries to display when it opens. + // SuggestedFolder will not be overriden by the last picked folder. + // If not specified, or the specified path doesn't exist, defaults to the last folder the user picked. + // On first launch of the picker, SuggestedFolder takes precedence over the SuggestedStartFolder if both set. + SuggestedFolder = @"C:\MyFiles", + + // (Optional) Sets an initial folder path shown when the picker is first launched. + // Once the user has picked from a directory, SuggestedStartFolder will be silently ignored. + // Takes precedence over SuggestedStartLocation when both defined. + // If this folder is not found, falls back to SuggestedStartLocation. + SuggestedStartFolder = @"C:\MyFiles", + // (Optional) Specify the initial location for the picker. // If the specified location doesn't exist on the user's machine, it falls back to the DocumentsLibrary. // If not set, it defaults to PickerLocationId.Unspecified, and the system will use its default location. @@ -47,10 +60,6 @@ var savePicker = new FileSavePicker(this.AppWindow.Id) // (Optional) specify the default file name. If not specified, use system default. SuggestedFileName = "My Document", - // (Optional) Sets the folder that the file save dialog displays when it opens. - // If not specified or the specified path doesn't exist, defaults to the last folder the user visited. - SuggestedFolder = @"C:\MyFiles", - // (Optional) specify the text displayed on the commit button. // If not specified, the system uses a default label of "Save" (suitably translated). CommitButtonText = "Save Document", @@ -75,6 +84,18 @@ using namespace winrt::Microsoft::Windows::Storage::Pickers; FileSavePicker savePicker(AppWindow().Id()); +// (Optional) Sets the folder that the file save dialog always tries to display when it opens. +// SuggestedFolder will not be overriden by the last picked folder. +// If not specified, or the specified path doesn't exist, defaults to the last folder the user picked. +// On first launch of the picker, SuggestedFolder takes precedence over the SuggestedStartFolder if both set. +savePicker.SuggestedFolder(L"C:\\MyFiles"); + +// (Optional) Sets an initial folder path shown when the picker is first launched. +// Once the user has picked from a directory, SuggestedStartFolder will be silently ignored. +// Takes precedence over SuggestedStartLocation when both defined. +// If this folder is not found, falls back to SuggestedStartLocation. +savePicker.SuggestedStartFolder(L"C:\\MyFiles"); + // (Optional) Specify the initial location for the picker. // If the specified location doesn't exist on the user's machine, it falls back to the DocumentsLibrary. // If not set, it defaults to PickerLocationId.Unspecified, and the system will use its default location. @@ -83,10 +104,6 @@ savePicker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary); // (Optional) specify the default file name. If not specified, use system default. savePicker.SuggestedFileName(L"NewDocument"); -// (Optional) Sets the folder that the file save dialog displays when it opens. -// If not specified or the specified path doesn't exist, defaults to the last folder the user visited. -savePicker.SuggestedFolder = L"C:\\MyFiles", - // (Optional) specify the text displayed on the commit button. // If not specified, the system uses a default label of "Save" (suitably translated). savePicker.CommitButtonText(L"Save Document"); diff --git a/specs/Storage.Pickers/FolderPicker.md b/specs/Storage.Pickers/FolderPicker.md index 1a56464e05..6b0592cc76 100644 --- a/specs/Storage.Pickers/FolderPicker.md +++ b/specs/Storage.Pickers/FolderPicker.md @@ -20,7 +20,10 @@ runtimeclass FolderPicker string CommitButtonText; + string SuggestedFolder; + String SuggestedStartFolder; PickerLocationId SuggestedStartLocation; + PickerViewMode ViewMode; Windows.Foundation.IAsyncOperation PickSingleFolderAsync(); @@ -42,6 +45,18 @@ using Microsoft.Windows.Storage.Pickers; var folderPicker = new FolderPicker(this.AppWindow.Id) { + // (Optional) Sets the folder that the folder dialog always tries to display when it opens. + // SuggestedFolder will not be overriden by the last picked folder. + // If not specified, or the specified path doesn't exist, defaults to the last folder the user picked. + // On first launch of the picker, SuggestedFolder takes precedence over the SuggestedStartFolder if both set. + SuggestedFolder = @"C:\MyFiles", + + // (Optional) Sets an initial folder path shown when the picker is first launched. + // Once the user has picked from a directory, SuggestedStartFolder will be silently ignored. + // Takes precedence over SuggestedStartLocation when both defined. + // If this folder is not found, falls back to SuggestedStartLocation. + SuggestedStartFolder = @"C:\MyFiles", + // (Optional) Specify the initial location for the picker. // If the specified location doesn't exist on the user's machine, it falls back to the DocumentsLibrary. // If not set, it defaults to PickerLocationId.Unspecified, and the system will use its default location. @@ -64,6 +79,18 @@ using namespace winrt::Microsoft::Windows::Storage::Pickers; FolderPicker folderPicker(AppWindow().Id()); +// (Optional) Sets the folder that the folder dialog always tries to display when it opens. +// SuggestedFolder will not be overriden by the last picked folder. +// If not specified, or the specified path doesn't exist, defaults to the last folder the user picked. +// On first launch of the picker, SuggestedFolder takes precedence over the SuggestedStartFolder if both set. +folderPicker.SuggestedFolder(L"C:\\MyFiles"); + +// (Optional) Sets an initial folder path shown when the picker is first launched. +// Once the user has picked from a directory, SuggestedStartFolder will be silently ignored. +// Takes precedence over SuggestedStartLocation when both defined. +// If this folder is not found, falls back to SuggestedStartLocation. +folderPicker.SuggestedStartFolder(L"C:\\MyFiles"); + // (Optional) Specify the initial location for the picker. // If the specified location doesn't exist on the user's machine, it falls back to the DocumentsLibrary. // If not set, it defaults to PickerLocationId.Unspecified, and the system will use its default location. @@ -124,3 +151,7 @@ else # See Also [PickFolderResult](./PickFolderResult.md) + +Notes: + +- SuggestedStartFolder takes precedence over SuggestedStartLocation. diff --git a/specs/Storage.Pickers/Microsoft.Windows.Storage.Pickers.md b/specs/Storage.Pickers/Microsoft.Windows.Storage.Pickers.md index 18a0f986ab..2036ab3ff6 100644 --- a/specs/Storage.Pickers/Microsoft.Windows.Storage.Pickers.md +++ b/specs/Storage.Pickers/Microsoft.Windows.Storage.Pickers.md @@ -28,6 +28,8 @@ in elevated scenarios.* 1. *Similarly, the `FileSavePicker.SuggestedSaveFile` property (which returned a `StorageFile`) has been replaced. Its functionality is now covered by two string properties: `SuggestedFolder` and `SuggestedFileName`. These allow for suggesting the folder and file name for the save dialog.* +1. Also adding the `SuggestedFolder` property to `FileOpenPicker` and `FolderPicker`, to better +support a commonly requested scenario - setting a persistent folder for all pickers. 1. *All new pickers are designed specifically for desktop apps and use a `WindowId` property to link the picker to its host window, replacing the `WinRT.Interop.InitializeWithWindow.Initialize` pattern.* @@ -46,6 +48,15 @@ because the new APIs are currently designed for desktop scenarios where each use interactive session, and each session is completely independent of the other sessions on the device. This is in contrast to Xbox One or other multi-user devices.* +1. Adding `SuggestedStartFolder` for all 3 pickers. This allows setting the initial folder with +an absolute folder path in string. When its specified folder exists, `SuggestedStartFolder` +takes precedence over `SuggestedStartLocation`; when its folder not found, the picker falls back +to `SuggestedStartLocation`, then to the system default. + +1. Adding `FileTypeChoices` for `FileOpenPicker`. This allows the dialog of FileOpenPicker to have +catagorized filter types. When both `FileTypeChoices` and `FileTypeFilter` are provided, +`FileTypeChoices` is used and `FileTypeFilter` is ignored. + # Conceptual pages # API @@ -108,8 +119,14 @@ namespace Microsoft.Windows.Storage.Pickers FileOpenPicker(Microsoft.UI.WindowId windowId); string CommitButtonText; + + IMap> FileTypeChoices{ get; }; IVector FileTypeFilter{ get; }; + + string SuggestedFolder; + string SuggestedStartFolder; PickerLocationId SuggestedStartLocation; + PickerViewMode ViewMode; Windows.Foundation.IAsyncOperation PickSingleFileAsync(); @@ -123,10 +140,11 @@ namespace Microsoft.Windows.Storage.Pickers string CommitButtonText; string DefaultFileExtension; string SuggestedFileName; - string SuggestedFolder; IMap> FileTypeChoices{ get; }; + string SuggestedFolder; + string SuggestedStartFolder; PickerLocationId SuggestedStartLocation; Windows.Foundation.IAsyncOperation PickSaveFileAsync(); @@ -138,10 +156,34 @@ namespace Microsoft.Windows.Storage.Pickers string CommitButtonText; + string SuggestedFolder; + string SuggestedStartFolder; PickerLocationId SuggestedStartLocation; + PickerViewMode ViewMode; Windows.Foundation.IAsyncOperation PickSingleFolderAsync(); } } ``` + +Note: **Understanding SuggestedStartFolder/SuggestedStartLocation vs SuggestedFolder:** + +These two kinds of properties have fundamentally different behaviors in terms of when and how they +affect the picker: + +- `SuggestedFolder` sets the path that will always be tried when opening the picker, regardless of + the user's previous operations. This uses the [SetFolder](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder) + method of the underlying COM APIs and takes precedence over any user navigation history. + +- `SuggestedStartFolder` sets the path shown only the first time the user launches the picker + (typically when the app is newly installed). After the user has picked a file, subsequent + launches of the picker will open to the user's last selected folder, and `SuggestedStartFolder` + becomes silent. This corresponds to the [SetDefaultFolder](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setdefaultfolder) + method in the COM API. + + The effective time span of `SuggestedStartFolder` is the same as that of `SuggestedStartLocation`, + both only influence the picker's initial behavior before user interaction establishes a + navigation history. + + `SuggestedStartFolder` takes precedence over `SuggestedStartLocation` when both specified. diff --git a/specs/package/Package.md b/specs/package/Package.md new file mode 100644 index 0000000000..0de5f61fa0 --- /dev/null +++ b/specs/package/Package.md @@ -0,0 +1,631 @@ +# 1. MSIX Package + +This feature provides package APIs comparable to those in appmodel.h and Package-related types in +namespace Windows.ApplicationModel +([Package](https://learn.microsoft.com/uwp/api/windows.applicationmodel.package), +[PackageId](https://learn.microsoft.com/uwp/api/windows.applicationmodel.packageid), +[PackageStatus](https://learn.microsoft.com/uwp/api/windows.applicationmodel.packagestatus), etc) +but with additional functionality, improved developer experience and performance optimizations. + +- [1. MSIX Package](#1-msix-package) +- [2. Background](#2-background) +- [3. Description](#3-description) + - [3.1. Find a packaged file](#31-find-a-packaged-file) + - [3.1.1. Package Location Search Order](#311-package-location-search-order) + - [3.1.2. GetPackageFilePath](#312-getpackagefilepath) + - [3.1.3. GetPackageFilePathInPackageGraph](#313-getpackagefilepathinpackagegraph) +- [4. Examples](#4-examples) + - [4.1. Get a file path in the current process' main package](#41-get-a-file-path-in-the-current-process-main-package) + - [4.1.1. C# Example](#411-c-example) + - [4.1.2. C++ Example](#412-c-example) + - [4.2. Get a file path in a specific package](#42-get-a-file-path-in-a-specific-package) + - [4.2.1. C# Example](#421-c-example) + - [4.2.2. C++ Example](#422-c-example) + - [4.3. Get a file path in a specific package with options](#43-get-a-file-path-in-a-specific-package-with-options) + - [4.3.1. C# Example](#431-c-example) + - [4.3.2. C++ Example](#432-c-example) + - [4.4. Get a file path in the current process' package graph](#44-get-a-file-path-in-the-current-process-package-graph) + - [4.4.1. C# Example](#441-c-example) + - [4.4.2. C++ Example](#442-c-example) + - [4.5. Get a file path in the current process' package graph with options](#45-get-a-file-path-in-the-current-process-package-graph-with-options) + - [4.5.1. C# Example](#451-c-example) + - [4.5.2. C++ Example](#452-c-example) +- [5. Remarks](#5-remarks) + - [5.1. Platform Support](#51-platform-support) +- [6. API Details](#6-api-details) + - [6.1. WinRT API](#61-winrt-api) + - [6.2. Win32 API](#62-win32-api) + +# 2. Background + +Windows supports the ability to query, format, parse, validate and otherwise examine MSIX packages +via Win32 APIs in appmodel.h and WinRT APIs in the Windows.ApplicationModel namespace. + +The package APIs were originally introduced in Windows 8 and have continuously expanded to meet +MSIX's growing demands over the past decade. However, as one of the earliest WinRT APIs they include +some patterns out of step with current API practices and recommendations. Windows App SDK offers the +opportunity to provide a new generation of package APIs in line with the latest patterns and +recommendations for an improved developer experience as well as functional enhancements and improved +runtime efficiencies. + +Microsoft-internal task [TODO](https://task.ms/TODO) + +# 3. Description + +This API provides enhanced access to Windows' package information, focusing on the following +scenarios: + +* Find a packaged file in a package +* Find a packaged file in the current process' package graph + +## 3.1. Find a packaged file + +An MSIX package contains... + +* [Footprint files](https://learn.microsoft.com/windows/win32/api/appxpackaging/ne-appxpackaging-appx_footprint_file_type) + e.g. `AppxManifest.xml` +* Content files i.e. all other files (*.exe, *.dll, *.jpg, *.dat, ...) + +A package always has an Install location and may also have other locations: + +* Install location +* Mutable location +* Machine External location +* User External location + +Locations other than the Install location are optional and may occur in any combination. + +See [PackagePathType](https://learn.microsoft.com/windows/win32/api/appmodel/ne-appmodel-packagepathtype) +for more details. + +Footprint files are always in a package's Install location. Content files can by in any of a +package's location(s) e.g. + +* Install location contains Content files when the package has no other location +* Mutable location typically contains files modified (overridden) by a user +* Packages registered for a user typically use Machine -or- User External location e.g. + * Package Foo has Machine External location = Q:\Foo + * Package Foo is registered for Bob has a User External location = X:\Bar + * Package Foo is registered for Mary w/no User External location + * THUS: + * Bob uses his User External location (X:\Bar) and ignores the Machine External location + * Mary has no User External location so she uses the Machine External location (Q:\Foo) + +Packages typically have Content files in one location but this isn't required. For example, some +files can appear in any of multiple locations and some code isn't External location aware (sometimes +ByDesign, sometimes due to bugs). Changing behavior by reducing location awareness poses appcompat +risks leading some features needing to broaden their support to search across multiple locations. + +The `find package file` APIs search the filesystem for a specified file as appropriate across the +package(s)' location(s). + +The APIs are available as Win32 exports and WinRT, similar to existing APIs in `appmodel.h` and +`namespace Windows.ApplicationModel`. + +**NOTE:** This functionality is available today but requires multiple steps, and understanding +that packages may have multiple locations and how to search across them. This puts a high bar on all +developers using MSIX to do the legwork, and doing so correctly, consistently and efficiently. These +new APIs provide a consistent, correct and easy to use solution. + +### 3.1.1. Package Location Search Order + +The search order for a package's locations is: + +1. External (User or Machine) +2. Mutable +3. Install + +**NOTE:** If a package has a UserExternal location then MachineExternal location is not checked +(even if the package has one). + +### 3.1.2. Get file path for a package + +`Package.GetFilePath(filename[packageFullName, [, options]])` searches the location(s) +of a package for the specified file, per the algorithm: + +``` +IF packageFullName not specified + IF current process has package identity + package = GetPackageForCurrentProcess() + ELSE current process lacks package identity + throw APPMODEL_ERROR_NO_PACKAGE +ELSE + package = GetPackage(packageFullName) + +IF SearchUserExternalPath OR SearchMachineExternalPath in options + IF SearchUserExternalPath AND SearchMachineExternalPath in options + path = package.GetEffectiveExternalPath() + ELSEIF SearchUserExternalPath in options + path = package.GetUserExternalPath() + ELSEIF SearchMachineExternalPath in options + path = package.GetMachineExternalPath() + IF path != null AND FileExists(path\filename) + RETURN path\filename + +IF SearchMutablePath in options + path = package.GetMutablePath() + IF path != null AND FileExists(path\filename) + RETURN path\filename + +path = package.GetInstallPath() +IF FileExists(path\filename) + RETURN path\filename + +// Not Found +RETURN null +``` + +Overloads without the `options` parameter use the default (search everything). + + +### 3.1.3. Get file path in current process' package graph + +`PackageGraph.GetFilePath(filename[, options])` searches the location(s) of each package in the caller's +package graph for the specified file, per the algorithm: + +``` +FOREACH pkg IN GetPackageGraph(): + file = Package.GetFilePath(filename, pkg.fullname) + IF file != null + RETURN file +NEXT + +// Not found +RETURN null +``` + +Overloads without the `options` parameter use the default (search everything). + +# 4. Examples + +## 4.1. Get a file path in the current process' main package + +Locate `resources.pri` in the main package for the current packaged process. + +### 4.1.1. C# Example + +```c# +using Microsoft.Windows.ApplicationModel; + +string GetResourcesPri() +{ + var absoluteFilename = Package.GetFilePath("resources.pri"); + if (absoluteFilename == null) + { + Console.WriteLine($"ERROR: resources.pri not found for {Package.Current.Id.FullName}"); + throw new FileNotFoundException($"resources.pri not found for {Package.Current.Id.FullName}"); + } + return absoluteFilename; +} +``` + +### 4.1.2. C++ Example + +```c++ +std::wstring GetResourcesPri(PCWSTR packageFullName) +{ + GetPackageFilePathOptions options{}; + wil::unique_process_heap_string absoluteFilename; + const HRESULT hr{ GetPackageFilePath( + nullptr, L"resources.pri", options, wistd::out_param(absoluteFilename)) }; + if (FAILED_LOG(hr)) + { + wprintf(L"ERROR: 0x%08X locating resources.pri in the current process' main package\n", hr); + THROW_HR(hr); + } + else if (!absoluteFilename) + { + wprintf(L"ERROR: resources.pri not found in the current process' main package\n"); + THROW_WIN32_ERROR(ERROR_FILE_NOT_FOUND); + } + return std::wstring{ absoluteFilename.get() }; +} +``` + +## 4.2. Get a file path in a specific package + +Locate `resources.pri` in the package `Contoso_1.2.3.4_x64__1234567890abc`. + +### 4.2.1. C# Example + +```c# +using Microsoft.Windows.ApplicationModel; + +string GetResourcesPri(string packageFullName) +{ + var absoluteFilename = Package.GetFilePath("resources.pri", packageFullName); + if (absoluteFilename == null) + { + Console.WriteLine($"ERROR: resources.pri not found for {packageFullName}"); + throw new FileNotFoundException($"resources.pri not found for {packageFullName}"); + } + return absoluteFilename; +} +``` + +### 4.2.2. C++ Example + +```c++ +std::wstring GetResourcesPri(PCWSTR packageFullName) +{ + GetPackageFilePathOptions options{}; + wil::unique_process_heap_string absoluteFilename; + const HRESULT hr{ GetPackageFilePath( + packageFullName, L"resources.pri", options, wistd::out_param(absoluteFilename)) }; + if (FAILED_LOG(hr)) + { + wprintf(L"ERROR: 0x%08X locating resources.pri for %ls\n", hr, packageFullName); + THROW_HR(hr); + } + else if (!absoluteFilename) + { + wprintf(L"ERROR: resources.pri not found for %ls\n", hr, packageFullName); + THROW_WIN32_ERROR(ERROR_FILE_NOT_FOUND); + } + return std::wstring{ absoluteFilename.get() }; +} +``` + +## 4.3. Get a file path in a specific package with options + +Locate `resources.pri` in the package `Contoso_1.2.3.4_x64__1234567890abc` but ignore the package's +Mutable location. + +### 4.3.1. C# Example + +```c# +using Microsoft.Windows.ApplicationModel; + +string GetResourcesPri(string packageFullName) +{ + var options = GetPackageFilePathOptions.SearchInstallPath | + GetPackageFilePathOptions.SearchMachineExternalPath | + GetPackageFilePathOptions.SearchUserExternalPath; + var absoluteFilename = Package.GetFilePath("resources.pri", packageFullName, options); + if (absoluteFilename == null) + { + Console.WriteLine($"ERROR: resources.pri not found for {packageFullName}"); + throw new FileNotFoundException($"resources.pri not found for {packageFullName}"); + } + return absoluteFilename; +} +``` + +### 4.3.2. C++ Example + +```c++ +std::wstring GetResourcesPri(PCWSTR packageFullName) +{ + GetPackageFilePathOptions options{ GetPackageFilePathOptions_SearchInstallPath | + GetPackageFilePathOptions_SearchMachineExternalPath | + GetPackageFilePathOptions_SearchUserExternalPath }; + wil::unique_process_heap_string absoluteFilename; + const HRESULT hr{ GetPackageFilePath( + packageFullName, L"resources.pri", options, wistd::out_param(absoluteFilename)) }; + if (FAILED_LOG(hr)) + { + wprintf(L"ERROR: 0x%08X locating resources.pri for %ls\n", hr, packageFullName); + THROW_HR(hr); + } + else if (!absoluteFilename) + { + wprintf(L"ERROR: resources.pri not found for %ls\n", hr, packageFullName); + THROW_WIN32_ERROR(ERROR_FILE_NOT_FOUND); + } + return std::wstring{ absoluteFilename.get() }; +} +``` + +## 4.4. Get a file path in the current process' package graph + +Locate `Microsoft.UI.Xaml.winmd` in the current process' package graph. + +### 4.4.1. C# Example + +```c# +using Microsoft.Windows.ApplicationModel; + +string GetXamlWinMD() +{ + var absoluteFilename = PackageGraph.GetFilePath("Microsoft.UI.Xaml.winmd"); + if (absoluteFilename == null) + { + Console.WriteLine($"ERROR: Microsoft.UI.Xaml.winmd pri not found"); + throw new FileNotFoundException($"Microsoft.UI.Xaml.winmd not found"); + } + return absoluteFilename; +} +``` + +### 4.4.2. C++ Example + +```c++ +std::wstring GetXamlWinMD() +{ + GetPackageFilePathOptions options{}; + wil::unique_process_heap_string absoluteFilename; + const HRESULT hr{ GetPackageFilePathInPackageGraph( + L"Microsoft.UI.Xaml.winmd", options, wistd::out_param(absoluteFilename)) }; + if (FAILED_LOG(hr)) + { + wprintf(L"ERROR: 0x%08X locating Microsoft.UI.Xaml.winmd\n", hr); + THROW_HR(hr); + } + else if (!absoluteFilename) + { + wprintf(L"ERROR: Microsoft.UI.Xaml.winmd not found\n", hr); + THROW_WIN32_ERROR(ERROR_FILE_NOT_FOUND); + } + return std::wstring{ absoluteFilename.get() }; +} +``` + +## 4.5. Get a file path in the current process' package graph with options + +Locate `Microsoft.UI.Xaml.winmd` in the current process' package graph but ignore Mutable locations, +Resource packages and HostRuntime dependencies. + +### 4.5.1. C# Example + +```c# +using Microsoft.Windows.ApplicationModel; + +string GetXamlWinMD() +{ + var options = GetPackageFilePathOptions.SearchInstallPath | + GetPackageFilePathOptions.SearchMachineExternalPath | + GetPackageFilePathOptions.SearchUserExternalPath | + GetPackageFilePathOptions.SearchMainPackages | + GetPackageFilePathOptions.SearchFrameworkPath | + GetPackageFilePathOptions.SearchOptionalPath | + GetPackageFilePathOptions.SearchStaticDependencies | + GetPackageFilePathOptions.SearchDynamicDependencies; + var absoluteFilename = PackageGraph.GetFilePath("Microsoft.UI.Xaml.winmd", options); + if (absoluteFilename == null) + { + Console.WriteLine($"ERROR: Microsoft.UI.Xaml.winmd not found"); + throw new FileNotFoundException($"Microsoft.UI.Xaml.winmd not found"); + } + return absoluteFilename; +} +``` + +### 4.5.2. C++ Example + +```c++ +std::wstring GetXamlWinMD() +{ + GetPackageFilePathOptions options{ GetPackageFilePathOptions_SearchInstallPath | + GetPackageFilePathOptions_SearchMachineExternalPath | + GetPackageFilePathOptions_SearchUserExternalPath | + GetPackageFilePathOptions_SearchMainPackages | + GetPackageFilePathOptions_SearchFrameworkPath | + GetPackageFilePathOptions_SearchOptionalPath | + GetPackageFilePathOptions_SearchStaticDependencies | + GetPackageFilePathOptions_SearchDynamicDependencies }; + wil::unique_process_heap_string absoluteFilename; + const HRESULT hr{ GetPackageFilePathInPackageGraph( + L"Microsoft.UI.Xaml.winmd", options, wistd::out_param(absoluteFilename)) }; + if (FAILED_LOG(hr)) + { + wprintf(L"ERROR: 0x%08X locating Microsoft.UI.Xaml.winmd\n", hr); + THROW_HR(hr); + } + else if (!absoluteFilename) + { + wprintf(L"ERROR: Microsoft.UI.Xaml.winmd not found\n", hr); + THROW_WIN32_ERROR(ERROR_FILE_NOT_FOUND); + } + return std::wstring{ absoluteFilename.get() }; +} +``` + +# 5. Remarks + +## 5.1. Platform Support + +This API is available on Windows >= 10.0.17763.0 (aka RS5). + +# 6. API Details + +## 6.1. WinRT API + +```c# (but really MIDL3) +namespace Microsoft.Windows.ApplicationModel +{ + [contractversion(1)] + apicontract PackageRuntimeContract{}; + + /// Options for GetFilePath*() + /// @see Package.GetFilePath + /// @see PackageGraph.GetFilePath + [contract(PackageRuntimeContract, 1)] + [flags] + enum GetFilePathOptions + { + /// Default behavior + None = 0, + + /// Include the package's Install path in the file search order + SearchInstallPath = 0x0001, + + /// Include the package's Mutable path (if it has one) in the file search order + SearchMutablePath = 0x0002, + + /// Include the package's Machine External path (if it has one) in the file search order + SearchMachineExternalPath = 0x0004, + + /// Include the package's User External path (if it has one) in the file search order + SearchUserExternalPath = 0x0008, + + /// Include Main packages in the file search order + SearchMainPackages = 0x0010, + + /// Include Framework packages in the file search order + SearchFrameworkPackages = 0x0020, + + /// Include Optional packages in the file search order + SearchOptionalPackages = 0x0040, + + /// Include Resource packages in the file search order + SearchResourcePackages = 0x0080, + + /// Include HostRuntime dependencies in the file search order + SearchHostRuntimeDependencies = 0x0100, + + /// Include Static package dependencies in the file search order + SearchStaticDependencies = 0x0200, + + /// Include Dynamic package dependencies in the file search order + SearchDynamicDependencies = 0x0400, + } + + [contract(PackageRuntimeContract, 1)] + runtimeclass Package + { + /// Return the absolute path to the file in the current process' package. This uses the + /// current process' package identity, or fails with HRESULT_FROM_WIN32(APPMODEL_ERROR_NO_PACKAGE) + /// if the process lacks package identity. + /// @param filename file to locate. + /// @param packageFile absolute path to the packaged file, or empty string ("") if not found. + /// @note The package path search order is External(User or Machine) -> Mutable -> Install. + /// @note If a package has a UserExternal location then MachineExternal location is not checked (even if the package has one). + /// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getpackagepathbyfullname2 + /// @see PackageGraph.GetFilePath + static String GetFilePath(String filename); + + /// Return the absolute path to the file in the package. + /// @param filename file to locate. + /// @param packageFullName the package. + /// @param packageFile absolute path to the packaged file, or empty string ("") if not found. + /// @note The package path search order is External(User or Machine) -> Mutable -> Install. + /// @note If a package has a UserExternal location then MachineExternal location is not checked (even if the package has one). + /// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getpackagepathbyfullname2 + /// @see PackageGraph.GetFilePath + [method_name("GetFilePathInPackage")] + static String GetFilePath(String filename, String packageFullName); + + /// Return the absolute path to the file in the package. + /// @param filename file to locate. + /// @param packageFullName the package. + /// @param packageFile absolute path to the packaged file, or empty string ("") if not found. + /// @param options options for the search. + /// @note The package path search order is External(User or Machine) -> Mutable -> Install. + /// @note If a package has a UserExternal location then MachineExternal location is not checked (even if the package has one). + /// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getpackagepathbyfullname2 + /// @see PackageGraph.GetFilePath + /// @see PackageGraph.GetFilePathOptions + [method_name("GetFilePathInPackageWithOptions")] + static String GetFilePath(String filename, String packageFullName, GetFilePathOptions options); + } + + [contract(PackageRuntimeContract, 1)] + runtimeclass PackageGraph + { + /// Return the absolute path to the file in the package graph. + /// @param filename file to locate. + /// @param packageFile absolute path to the packaged file, or empty string ("") if not found. + /// @note The package paths search order is External(User or Machine) -> Mutable -> Install. + /// @note If a package has a UserExternal location then MachineExternal location is not checked (even if the package has one). + /// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getpackagepathbyfullname2 + /// @see Package.GetFilePath + static String GetFilePath(String filename); + + /// Return the absolute path to the file in the package graph. + /// @param filename file to locate. + /// @param packageFile absolute path to the packaged file, or empty string ("") if not found. + /// @param options options for the search. + /// @note The package paths search order is External(User or Machine) -> Mutable -> Install. + /// @note If a package has a UserExternal location then MachineExternal location is not checked (even if the package has one). + /// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getpackagepathbyfullname2 + /// @see Package.GetFilePath + /// @see GetPackageFilePathOptions + [method_name("GetFilePathWithOptions")] + static String GetFilePath(String filename, GetPackageFilePathOptions options); + }; +} +``` + +## 6.2. Win32 API + +```c +// package_runtime.h + +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +#if !defined(PACKAGE_RUNTIME_H) +#define PACKAGE_RUNTIME_H + +/// Options for GetPackageFilePath*() functions +typedef enum GetPackageFilePathOptions +{ + /// Default behavior + GetPackageFilePathOptions_None = 0, + + /// Include the package's Install path in the file search order + GetPackageFilePathOptions_SearchInstallPath = 0x0001, + + /// Include the package's Mutable path (if it has one) in the file search order + GetPackageFilePathOptions_SearchMutablePath = 0x0002, + + /// Include the package's Machine External path (if it has one) in the file search order + GetPackageFilePathOptions_SearchMachineExternalPath = 0x0004, + + /// Include the package's User External path (if it has one) in the file search order + GetPackageFilePathOptions_SearchUserExternalPath = 0x0008, + + /// Include Main packages in the file search order + GetPackageFilePathOptions_SearchMainPackages = 0x0010, + + /// Include Framework packages in the file search order + GetPackageFilePathOptions_SearchFrameworkPackages = 0x0020, + + /// Include Optional packages in the file search order + GetPackageFilePathOptions_SearchOptionalPackages = 0x0040, + + /// Include Resource packages in the file search order + GetPackageFilePathOptions_SearchResourcePackages = 0x0080, + + /// Include HostRuntime dependencies in the file search order + GetPackageFilePathOptions_SearchHostRuntimeDependencies = 0x0100, + + /// Include Static package dependencies in the file search order + GetPackageFilePathOptions_SearchStaticDependencies = 0x0200, + + /// Include Dynamic package dependencies in the file search order + GetPackageFilePathOptions_SearchDynamicDependencies = 0x0400, +} + +/// Return the absolute path to the file in the package. +/// @param packageFullName the package, or NULL or "" to use the current process' package identity. +/// @param filename file to locate. +/// @param options options for the search. +/// @param packageFile absolute path to the packaged file, or NULL if not found. Allocated via HeapAlloc; use HeapFree to deallocate +/// @note The package path search order is External(User or Machine) -> Mutable -> Install. +/// @note If a package has a UserExternal location then MachineExternal location is not checked (even if the package has one). +/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getpackagepathbyfullname2 +/// @see GetPackageFilePathInPackageGraph() +/// @see GetPackageFilePathOptions +STDAPI GetPackageFilePath( + PCWSTR packageFullName, + _In_ PCWSTR filename, + _In_ GetPackageFilePathOptions options, + _Outptr_result_maybenull_ PWSTR* packageFile) noexcept; + +/// Return the absolute path to the file in the caller's package graph. +/// @param filename file to locate. +/// @param options options for the search. +/// @param packageFile absolute path to the packaged file, or NULL if not found. Allocated via HeapAlloc; use HeapFree to deallocate +/// @note The search order is External(User or Machine) -> Mutable -> Install for each package in the package graph. +/// @note If a package has a UserExternal location then MachineExternal location is not checked (even if the package has one). +/// @see https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getpackagepathbyfullname2 +/// @see GetPackageFilePath() +/// @see GetPackageFilePathOptions +STDAPI GetPackageFilePathInPackageGraph( + _In_ PCWSTR filename, + _In_ GetPackageFilePathOptions options, + _Outptr_result_maybenull_ PWSTR* packageFile) noexcept; + +#endif // PACKAGE_RUNTIME_H +``` diff --git a/test/AppLifecycle/AppLifecycle.testdef b/test/AppLifecycle/AppLifecycle.testdef index 321d12eb51..2f891573e2 100644 --- a/test/AppLifecycle/AppLifecycle.testdef +++ b/test/AppLifecycle/AppLifecycle.testdef @@ -4,7 +4,7 @@ "Description": "AppLifecycle API (x86 not currently enabled)", "Filename": "AppLifecycleTests.dll", "Parameters": "", - "Architectures": ["x64", "arm64"], + "Architectures": ["x64", "x86", "arm64"], "Status": "Enabled" } ] diff --git a/test/AppNotificationTests/AppNotification-Test-Constants.h b/test/AppNotificationTests/AppNotification-Test-Constants.h index 9f62bb7ea6..bc3f72b110 100644 --- a/test/AppNotificationTests/AppNotification-Test-Constants.h +++ b/test/AppNotificationTests/AppNotification-Test-Constants.h @@ -11,3 +11,4 @@ inline const std::wstring c_appUserModelId{ L"TaefTestAppId" }; inline const std::chrono::milliseconds c_sleepTimeout{ 5000 }; inline const std::chrono::milliseconds c_delay{ 25 }; constexpr PCWSTR c_nonExistentPackage{ L"NonExistentPackage" }; +constexpr PCWSTR c_fakePackageFamilyName{ L"AppNotificationTest.PackageFamilyName.DoesNotExist_1234567890abc" }; diff --git a/test/AppNotificationTests/AppNotificationTests.testdef b/test/AppNotificationTests/AppNotificationTests.testdef new file mode 100644 index 0000000000..a84cdeba70 --- /dev/null +++ b/test/AppNotificationTests/AppNotificationTests.testdef @@ -0,0 +1,18 @@ +{ + "Tests": [ + { + "Description": "App Notification Tests Packaged", + "Filename": "AppNotificationTests.dll", + "Parameters": "/name:PackagedTests*", + "Architectures": ["x64", "x86"], + "Status": "Disabled" + }, + { + "Description": "App Notification Tests Unpackaged", + "Filename": "AppNotificationTests.dll", + "Parameters": "/name:UnpackagedTests*", + "Architectures": ["x64", "x86"], + "Status": "Disabled" + } + ] +} diff --git a/test/AppNotificationTests/AppNotificationTests.vcxproj b/test/AppNotificationTests/AppNotificationTests.vcxproj index 33c3c8454d..a586593b02 100644 --- a/test/AppNotificationTests/AppNotificationTests.vcxproj +++ b/test/AppNotificationTests/AppNotificationTests.vcxproj @@ -169,6 +169,7 @@ + diff --git a/test/AppNotificationTests/AppNotifications-AppxManifest.xml b/test/AppNotificationTests/AppNotifications-AppxManifest.xml index 167a04d7e0..90304589ca 100644 --- a/test/AppNotificationTests/AppNotifications-AppxManifest.xml +++ b/test/AppNotificationTests/AppNotifications-AppxManifest.xml @@ -24,7 +24,7 @@ - + diff --git a/test/AppNotificationTests/BaseTestSuite.cpp b/test/AppNotificationTests/BaseTestSuite.cpp index ab66a3842b..dddbf38450 100644 --- a/test/AppNotificationTests/BaseTestSuite.cpp +++ b/test/AppNotificationTests/BaseTestSuite.cpp @@ -6,6 +6,7 @@ #include "AppNotification-Test-Constants.h" #include "AppNotifications.Test.h" #include "BaseTestSuite.h" +#include "MddWin11.h" using namespace WEX::Common; using namespace WEX::Logging; @@ -23,6 +24,13 @@ using namespace AppNotifications::Test; void BaseTestSuite::ClassSetup() { + // Skip tests on Win10 RS5 and earlier due to COM registration issues + if (!WindowsVersion::IsWindows10_19H1OrGreater()) + { + WEX::Logging::Log::Result(WEX::Logging::TestResults::Skipped, L"AppNotification require Win10 19H1 or greater due to COM registration compatibility. Skipping tests on this OS version."); + return; + } + ::Test::Bootstrap::Setup(); } @@ -36,17 +44,24 @@ void BaseTestSuite::MethodSetup() bool isSelfContained{}; VERIFY_SUCCEEDED(TestData::TryGetValue(L"SelfContained", isSelfContained)); - if (!isSelfContained) + PCWSTR testFrameworkPackageFamilyName{ ::Test::Bootstrap::TP::WindowsAppRuntimeFramework::c_PackageFamilyName }; + PCWSTR testMainPackageFamilyName{ ::Test::Bootstrap::TP::WindowsAppRuntimeMain::c_PackageFamilyName }; + + if (isSelfContained) { - ::WindowsAppRuntime::VersionInfo::TestInitialize(::Test::Bootstrap::TP::WindowsAppRuntimeFramework::c_PackageFamilyName, - ::Test::Bootstrap::TP::WindowsAppRuntimeMain::c_PackageFamilyName); - VERIFY_IS_FALSE(::WindowsAppRuntime::SelfContained::IsSelfContained()); + testFrameworkPackageFamilyName = testMainPackageFamilyName = c_fakePackageFamilyName; } - else + + // For Windows 11 newer versions, the TestInitialize will fail fast if we pass a non null package family name. + // https://github.com/microsoft/WindowsAppSDK/blob/main/dev/Common/WindowsAppRuntime.VersionInfo.cpp#L123-L133 + if (MddCore::Win11::IsSupported()) { - ::WindowsAppRuntime::VersionInfo::TestInitialize(L"I_don't_exist_package!", L"I_don't_exist_package!"); - VERIFY_IS_TRUE(::WindowsAppRuntime::SelfContained::IsSelfContained()); + testMainPackageFamilyName = nullptr; } + + ::WindowsAppRuntime::VersionInfo::TestInitialize(testFrameworkPackageFamilyName, testMainPackageFamilyName); + + VERIFY_ARE_EQUAL(isSelfContained, ::WindowsAppRuntime::SelfContained::IsSelfContained()); } void BaseTestSuite::MethodCleanup() @@ -369,7 +384,16 @@ void BaseTestSuite::VerifyRemoveWithIdentifierAsyncUsingNonActiveToastIdentifier removeNotificationAsync.Cancel(); }); - VERIFY_ARE_EQUAL(removeNotificationAsync.wait_for(c_timeout), winrt::Windows::Foundation::AsyncStatus::Completed); + // On x86, the wait_for may return Error instead of Completed. + // We check if the HR is E_INVALIDARG in that case. + auto status = removeNotificationAsync.wait_for(c_timeout); + VERIFY_IS_TRUE(status == winrt::Windows::Foundation::AsyncStatus::Completed || status == winrt::Windows::Foundation::AsyncStatus::Error); + + if (status == winrt::Windows::Foundation::AsyncStatus::Error) + { + VERIFY_THROWS_HR(removeNotificationAsync.GetResults(), E_INVALIDARG); + } + scope_exit.release(); } diff --git a/test/AppNotificationTests/UnpackagedTests.h b/test/AppNotificationTests/UnpackagedTests.h index fb32ce7a5c..12faa3dcc1 100644 --- a/test/AppNotificationTests/UnpackagedTests.h +++ b/test/AppNotificationTests/UnpackagedTests.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" @@ -13,7 +13,7 @@ class UnpackagedTests : BaseTestSuite BEGIN_TEST_CLASS(UnpackagedTests) TEST_CLASS_PROPERTY(L"Description", L"Windows App SDK App Notifications test") TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") - TEST_CLASS_PROPERTY(L"RunAs:Class", L"RestrictedUser") + TEST_CLASS_PROPERTY(L"RunAs", L"RestrictedUser") TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class") TEST_CLASS_PROPERTY(L"Data:SelfContained", L"{true, false}") END_TEST_CLASS() diff --git a/test/BackgroundTaskTests/BackgroundTaskBuilder-AppxManifest.xml b/test/BackgroundTaskTests/BackgroundTaskBuilder-AppxManifest.xml index 18d5947ccd..db76e25ecf 100644 --- a/test/BackgroundTaskTests/BackgroundTaskBuilder-AppxManifest.xml +++ b/test/BackgroundTaskTests/BackgroundTaskBuilder-AppxManifest.xml @@ -21,7 +21,7 @@ - + diff --git a/test/BadgeNotificationTest/BadgeNotifications-AppxManifest.xml b/test/BadgeNotificationTest/BadgeNotifications-AppxManifest.xml index 8da2890278..caac8b8fc8 100644 --- a/test/BadgeNotificationTest/BadgeNotifications-AppxManifest.xml +++ b/test/BadgeNotificationTest/BadgeNotifications-AppxManifest.xml @@ -24,7 +24,7 @@ - + diff --git a/test/BypassTests.json b/test/BypassTests.json index 5ad4cff672..f055707d3e 100644 --- a/test/BypassTests.json +++ b/test/BypassTests.json @@ -1539,6 +1539,19 @@ "release_x64_Win10_rs5_DC.Test::AppLifecycle::FunctionalTests::SingleInstanceTest_PackagedWin32", "release_x64_Win10_rs5_DC.Test::AppLifecycle::FunctionalTests::RequestRestart_Win32", "release_x64_Win10_rs5_DC.Test::AppLifecycle::FunctionalTests::RequestRestart_PackagedWin32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::GetActivatedEventArgsIsNotNull", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::GetActivatedEventArgsForLaunch", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::GetActivatedEventArgsForFile_Win32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::GetActivatedEventArgsForUnicodeNamedFile_Win32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::GetActivatedEventArgsForFile_PackagedWin32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::GetActivatedEventArgsForProtocol_Win32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::GetActivatedEventArgsForProtocol_PackagedWin32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::GetActivatedEventArgsForPush_Win32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::GetActivatedEventArgsForStartup_Win32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::SingleInstanceTest_Win32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::SingleInstanceTest_PackagedWin32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::RequestRestart_Win32", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AppLifecycle::FunctionalTests::RequestRestart_PackagedWin32", "release_x64_Win10_rs5_DC.UnpackagedTests#metadataSet0::ChannelRequestUsingNullRemoteId", "release_x64_Win10_rs5_DC.UnpackagedTests#metadataSet0::ChannelRequestUsingRemoteId", "release_x64_Win10_rs5_DC.UnpackagedTests#metadataSet0::ChannelRequestCheckExpirationTime", @@ -1619,11 +1632,21 @@ "release_x64_Windows.10.Enterprise.LTSC.2021.UnpackagedTests#metadataSet1::VerifyUnregisterAndUnregisterAll", "release_x64_Windows.10.Enterprise.LTSC.2021.UnpackagedTests#metadataSet1::VerifyForegroundHandlerSucceeds", "release_x64_Windows.10.Enterprise.LTSC.2021.UnpackagedTests#metadataSet1::VerifyForegroundHandlerFails", - "release_x64_Win10_rs5_DC.LRPTests::LaunchLRP_FromCoCreateInstance", - "release_x64_Win10_rs5_DC.LRPTests::LaunchLRP_FromStartupTask", - "release_x64_Win10_rs5_DC.LRPTests::AddToastRegistrationMappingNoSink", - "release_x64_Win10_rs5_DC.LRPTests::RemoveToastRegistrationMappingNoSink", - "release_x64_Win10_rs5_DC.LRPTests::AddRemoveToastRegistrationMappingWithSink", + "release_x64_Win10_rs5_DC.Test::LRP::LRPTests::LaunchLRP_FromStartupTask", + "release_x64_Win10_rs5_DC.Test::LRP::LRPTests::AddRemoveToastRegistrationMappingNoSink", + "release_x64_Win10_rs5_DC.Test::LRP::LRPTests::AddRemoveToastRegistrationMappingWithSink", + "release_x64_Win10_rs5_DC.Test::LRP::LRPTests::RegisterUnregisterLongRunningActivatorWithClsid", + "release_x64_Win10_rs5_DC.Test::LRP::LRPTests::RegisterUnregisterLongRunningActivator", + "release_x64_Windows.10.Enterprise.LTSC.2021.Test::LRP::LRPTests::LaunchLRP_FromStartupTask", + "release_x64_Windows.10.Enterprise.LTSC.2021.Test::LRP::LRPTests::AddRemoveToastRegistrationMappingNoSink", + "release_x64_Windows.10.Enterprise.LTSC.2021.Test::LRP::LRPTests::AddRemoveToastRegistrationMappingWithSink", + "release_x64_Windows.10.Enterprise.LTSC.2021.Test::LRP::LRPTests::RegisterUnregisterLongRunningActivatorWithClsid", + "release_x64_Windows.10.Enterprise.LTSC.2021.Test::LRP::LRPTests::RegisterUnregisterLongRunningActivator", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::LRP::LRPTests::LaunchLRP_FromStartupTask", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::LRP::LRPTests::AddRemoveToastRegistrationMappingNoSink", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::LRP::LRPTests::AddRemoveToastRegistrationMappingWithSink", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::LRP::LRPTests::RegisterUnregisterLongRunningActivatorWithClsid", + "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::LRP::LRPTests::RegisterUnregisterLongRunningActivator", "release_x64_Windows.10.Enterprise.LTSC.2021.Test::DynamicDependency::Test_WinRT::Create_Delete", "release_x64_Windows.10.Enterprise.LTSC.2021.Test::DynamicDependency::Test_WinRT::FullLifecycle_ProcessLifetime_Framework_WindowsAppRuntime", "release_x64_Windows.10.Enterprise.LTSC.2021.Test::DynamicDependency::Test_WinRT::FullLifecycle_ProcessLifetime_Frameworks_WindowsAppRuntime_MathAdd", @@ -1635,59 +1658,6 @@ "release_x64_Windows.10.Enterprise.LTSC.2021.Test::DynamicDependency::Test_WinRT::Create_DoNotVerifyDependencyResolution", "release_x64_Windows.10.Enterprise.LTSC.2021.Test::DynamicDependency::Test_WinRT::Create_Add_Architectures_Explicit", "release_x64_Windows.10.Enterprise.LTSC.2021.Test::DynamicDependency::Test_WinRT::Create_Add_Architectures_Current", - "release_x64_Windows.10.Enterprise.LTSC.2021.OAuth2ManagerTests::OAuth2APITests::Protocol_AuthorizationCode_BasicEndToEnd", - "release_x64_Windows.10.Enterprise.LTSC.2021.OAuth2ManagerTests::OAuth2APITests::AuthorizationCodeWithClientAuth", - "release_x64_Windows.10.Enterprise.LTSC.2021.OAuth2ManagerTests::OAuth2APITests::ClientCredentialsTokenRequest", - "release_x64_Windows.10.Enterprise.LTSC.2021.OAuth2ManagerTests::OAuth2APITests::RefreshTokenRequest", - "release_x64_Windows.10.Enterprise.LTSC.2021.OAuth2ManagerTests::OAuth2APITests::ExtensionTokenRequest", - "release_x64_Windows.10.Enterprise.LTSC.2021.OAuth2ManagerTests::OAuth2APITests::TokenRequestErrorResponse", - "release_x64_Windows.10.Enterprise.LTSC.2021.OAuth2ManagerTests::OAuth2APITests::AdditionalParams", - "release_x64_Windows.10.Enterprise.LTSC.2021.OAuth2ManagerTests::OAuth2APITests::CompleteInvalidState", "release_x64_Win10_rs5_DC.OAuth2ManagerTests::OAuth2APITests::Protocol_AuthorizationCode_BasicEndToEnd", - "release_x64_Win10_rs5_DC.OAuth2ManagerTests::OAuth2APITests::AuthorizationCodeWithClientAuth", - "release_x64_Win10_rs5_DC.OAuth2ManagerTests::OAuth2APITests::ClientCredentialsTokenRequest", - "release_x64_Win10_rs5_DC.OAuth2ManagerTests::OAuth2APITests::RefreshTokenRequest", - "release_x64_Win10_rs5_DC.OAuth2ManagerTests::OAuth2APITests::ExtensionTokenRequest", - "release_x64_Win10_rs5_DC.OAuth2ManagerTests::OAuth2APITests::TokenRequestErrorResponse", - "release_x64_Win10_rs5_DC.OAuth2ManagerTests::OAuth2APITests::AdditionalParams", - "release_x64_Win10_rs5_DC.OAuth2ManagerTests::OAuth2APITests::CompleteInvalidState", - "release_x86_Windows.10.Enterprise.22h2.OAuth2ManagerTests::OAuth2APITests::Protocol_AuthorizationCode_BasicEndToEnd", - "release_x86_Windows.10.Enterprise.22h2.OAuth2ManagerTests::OAuth2APITests::AuthorizationCodeWithClientAuth", - "release_x86_Windows.10.Enterprise.22h2.OAuth2ManagerTests::OAuth2APITests::ClientCredentialsTokenRequest", - "release_x86_Windows.10.Enterprise.22h2.OAuth2ManagerTests::OAuth2APITests::RefreshTokenRequest", - "release_x86_Windows.10.Enterprise.22h2.OAuth2ManagerTests::OAuth2APITests::ExtensionTokenRequest", - "release_x86_Windows.10.Enterprise.22h2.OAuth2ManagerTests::OAuth2APITests::TokenRequestErrorResponse", - "release_x86_Windows.10.Enterprise.22h2.OAuth2ManagerTests::OAuth2APITests::AdditionalParams", - "release_x86_Windows.10.Enterprise.22h2.OAuth2ManagerTests::OAuth2APITests::CompleteInvalidState", - "release_x86_Windows.11.Professional.23H2.zh-CN.OAuth2ManagerTests::OAuth2APITests::Protocol_AuthorizationCode_BasicEndToEnd", - "release_x86_Windows.11.Professional.23H2.zh-CN.OAuth2ManagerTests::OAuth2APITests::AuthorizationCodeWithClientAuth", - "release_x86_Windows.11.Professional.23H2.zh-CN.OAuth2ManagerTests::OAuth2APITests::ClientCredentialsTokenRequest", - "release_x86_Windows.11.Professional.23H2.zh-CN.OAuth2ManagerTests::OAuth2APITests::RefreshTokenRequest", - "release_x86_Windows.11.Professional.23H2.zh-CN.OAuth2ManagerTests::OAuth2APITests::ExtensionTokenRequest", - "release_x86_Windows.11.Professional.23H2.zh-CN.OAuth2ManagerTests::OAuth2APITests::TokenRequestErrorResponse", - "release_x86_Windows.11.Professional.23H2.zh-CN.OAuth2ManagerTests::OAuth2APITests::AdditionalParams", - "release_x86_Windows.11.Professional.23H2.zh-CN.OAuth2ManagerTests::OAuth2APITests::CompleteInvalidState", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.OAuth2ManagerTests::OAuth2APITests::Protocol_AuthorizationCode_BasicEndToEnd", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.OAuth2ManagerTests::OAuth2APITests::AuthorizationCodeWithClientAuth", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.OAuth2ManagerTests::OAuth2APITests::ClientCredentialsTokenRequest", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.OAuth2ManagerTests::OAuth2APITests::RefreshTokenRequest", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.OAuth2ManagerTests::OAuth2APITests::ExtensionTokenRequest", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.OAuth2ManagerTests::OAuth2APITests::TokenRequestErrorResponse", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.OAuth2ManagerTests::OAuth2APITests::AdditionalParams", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.OAuth2ManagerTests::OAuth2APITests::CompleteInvalidState", - "release_x64_Windows.10.Enterprise.LTSC.2021.Test::AccessControl::APITests::FlatAPITest", - "release_x64_Windows.10.Enterprise.LTSC.2021.Test::AccessControl::APITests::WinRTStringTest", - "release_x64_Windows.10.Enterprise.LTSC.2021.Test::AccessControl::APITests::WinRTBytesTest", - "release_x64_Win10_rs5_DC.Test::AccessControl::APITests::FlatAPITest", - "release_x64_Win10_rs5_DC.Test::AccessControl::APITests::WinRTStringTest", - "release_x64_Win10_rs5_DC.Test::AccessControl::APITests::WinRTBytesTest", - "release_x86_Windows.10.Enterprise.22h2.Test::AccessControl::APITests::FlatAPITest", - "release_x86_Windows.10.Enterprise.22h2.Test::AccessControl::APITests::WinRTStringTest", - "release_x86_Windows.10.Enterprise.22h2.Test::AccessControl::APITests::WinRTBytesTest", - "release_x86_Windows.11.Professional.23H2.zh-CN.Test::AccessControl::APITests::FlatAPITest", - "release_x86_Windows.11.Professional.23H2.zh-CN.Test::AccessControl::APITests::WinRTStringTest", - "release_x86_Windows.11.Professional.23H2.zh-CN.Test::AccessControl::APITests::WinRTBytesTest", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AccessControl::APITests::FlatAPITest", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AccessControl::APITests::WinRTStringTest", - "release_x86_Windows.Server.2019.DataCenter.zh-CN.Test::AccessControl::APITests::WinRTBytesTest" + "release_x86_Windows.Server.2019.DataCenter.zh-CN.OAuth2ManagerTests::OAuth2APITests::Protocol_AuthorizationCode_BasicEndToEnd" ] diff --git a/test/CameraCaptureUITests/CameraCaptureUI-AppxManifest.xml b/test/CameraCaptureUITests/CameraCaptureUI-AppxManifest.xml index 1afbace63b..a37be81c8a 100644 --- a/test/CameraCaptureUITests/CameraCaptureUI-AppxManifest.xml +++ b/test/CameraCaptureUITests/CameraCaptureUI-AppxManifest.xml @@ -21,7 +21,7 @@ - + diff --git a/test/Deployment/API/Deployment-Capabilities-AppxManifest.xml b/test/Deployment/API/Deployment-Capabilities-AppxManifest.xml index b0fbb7295a..3adbc676b8 100644 --- a/test/Deployment/API/Deployment-Capabilities-AppxManifest.xml +++ b/test/Deployment/API/Deployment-Capabilities-AppxManifest.xml @@ -1,4 +1,4 @@ - + - diff --git a/test/Deployment/API/Deployment-RealNameFramework-AppxManifest.xml b/test/Deployment/API/Deployment-RealNameFramework-AppxManifest.xml index e24969a933..cd9c483c4e 100644 --- a/test/Deployment/API/Deployment-RealNameFramework-AppxManifest.xml +++ b/test/Deployment/API/Deployment-RealNameFramework-AppxManifest.xml @@ -1,4 +1,4 @@ - + - + @@ -46,7 +46,6 @@ - diff --git a/test/Deployment/API/DeploymentTests.testdef b/test/Deployment/API/DeploymentTests.testdef new file mode 100644 index 0000000000..d42720c633 --- /dev/null +++ b/test/Deployment/API/DeploymentTests.testdef @@ -0,0 +1,11 @@ +{ + "Tests": [ + { + "Description": "Deployment Tests", + "Filename": "DeploymentTests.dll", + "Parameters": "", + "Architectures": ["x64", "x86", "arm64"], + "Status": "Enabled" + } + ] +} \ No newline at end of file diff --git a/test/Deployment/API/DeploymentTests.vcxproj b/test/Deployment/API/DeploymentTests.vcxproj index 550949296a..4eb0e170c7 100644 --- a/test/Deployment/API/DeploymentTests.vcxproj +++ b/test/Deployment/API/DeploymentTests.vcxproj @@ -234,6 +234,9 @@ + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. diff --git a/test/Deployment/data/WindowsAppRuntime.Test.Framework/WindowsAppRuntime.Test.Framework.vcxproj b/test/Deployment/data/WindowsAppRuntime.Test.Framework/WindowsAppRuntime.Test.Framework.vcxproj index a6dc3b639e..7a4fc05dfd 100644 --- a/test/Deployment/data/WindowsAppRuntime.Test.Framework/WindowsAppRuntime.Test.Framework.vcxproj +++ b/test/Deployment/data/WindowsAppRuntime.Test.Framework/WindowsAppRuntime.Test.Framework.vcxproj @@ -111,6 +111,7 @@ + MSIX\Main.msix @@ -130,6 +131,9 @@ {b73ad907-6164-4294-88fb-f3c9c10da1f1} + + {4410d374-a90c-4adf-8b15-aa2aae2636bf} + {34519337-9249-451e-b5a9-1ecacf9c3da8} diff --git a/test/Deployment/data/WindowsAppRuntime.Test.Main/appxmanifest.xml b/test/Deployment/data/WindowsAppRuntime.Test.Main/appxmanifest.xml index 9111305943..52dadfab36 100644 --- a/test/Deployment/data/WindowsAppRuntime.Test.Main/appxmanifest.xml +++ b/test/Deployment/data/WindowsAppRuntime.Test.Main/appxmanifest.xml @@ -1,4 +1,4 @@ - + - Microsoft.WindowsAppRuntime.Main.Test (aka Microsoft.WindowsAppRuntime.Main) fake for tests + MicrosoftCorporationII.WinAppRuntime.Main.Test (aka Microsoft.WindowsAppRuntime.Main) fake for tests Microsoft Corporation Assets\logo.png diff --git a/test/Deployment/data/WindowsAppRuntime.Test.Singleton/appxmanifest.xml b/test/Deployment/data/WindowsAppRuntime.Test.Singleton/appxmanifest.xml index 90984f4f17..9792706bef 100644 --- a/test/Deployment/data/WindowsAppRuntime.Test.Singleton/appxmanifest.xml +++ b/test/Deployment/data/WindowsAppRuntime.Test.Singleton/appxmanifest.xml @@ -1,4 +1,4 @@ - + - Microsoft.WindowsAppRuntime.Singleton.Test (aka Microsoft.WindowsAppRuntime.Singleton) fake for tests + MicrosoftCorporationII.WinAppRuntime.Singleton.Test (aka Microsoft.WindowsAppRuntime.Singleton) fake for tests Microsoft Corporation Assets\logo.png diff --git a/test/DynamicDependency/Test_Win32/TestPackages.h b/test/DynamicDependency/Test_Win32/TestPackages.h index 70e8398a4c..e68d036e8a 100644 --- a/test/DynamicDependency/Test_Win32/TestPackages.h +++ b/test/DynamicDependency/Test_Win32/TestPackages.h @@ -128,9 +128,9 @@ namespace DynamicDependencyLifetimeManagerGC1010 namespace WindowsAppRuntimeFramework { constexpr PCWSTR c_PackageDirName = L"Microsoft.WindowsAppRuntime.Framework"; - constexpr PCWSTR c_PackageNamePrefix = L"Microsoft.WindowsAppRuntime.Framework"; - constexpr PCWSTR c_PackageFamilyName = L"Microsoft.WindowsAppRuntime.Framework.4.1_8wekyb3d8bbwe"; - constexpr PCWSTR c_PackageFullName = L"Microsoft.WindowsAppRuntime.Framework.4.1_4.1.1967.333_neutral__8wekyb3d8bbwe"; + constexpr PCWSTR c_PackageNamePrefix = L"Microsoft.WindowsAppRuntime"; + constexpr PCWSTR c_PackageFamilyName = L"Microsoft.WindowsAppRuntime.4.1_8wekyb3d8bbwe"; + constexpr PCWSTR c_PackageFullName = L"Microsoft.WindowsAppRuntime.4.1_4.1.1967.333_neutral__8wekyb3d8bbwe"; constexpr const PACKAGE_VERSION GetPackageVersion() { PACKAGE_VERSION version{}; diff --git a/test/DynamicDependency/Test_WinRT/TestPackages.h b/test/DynamicDependency/Test_WinRT/TestPackages.h index 8cbe398afc..9b892b3677 100644 --- a/test/DynamicDependency/Test_WinRT/TestPackages.h +++ b/test/DynamicDependency/Test_WinRT/TestPackages.h @@ -128,9 +128,9 @@ namespace DynamicDependencyLifetimeManagerGC1010 namespace WindowsAppRuntimeFramework { constexpr PCWSTR c_PackageDirName = L"Microsoft.WindowsAppRuntime.Framework"; - constexpr PCWSTR c_PackageNamePrefix = L"Microsoft.WindowsAppRuntime.Framework"; - constexpr PCWSTR c_PackageFamilyName = L"Microsoft.WindowsAppRuntime.Framework.4.1_8wekyb3d8bbwe"; - constexpr PCWSTR c_PackageFullName = L"Microsoft.WindowsAppRuntime.Framework.4.1_4.1.1967.333_neutral__8wekyb3d8bbwe"; + constexpr PCWSTR c_PackageNamePrefix = L"Microsoft.WindowsAppRuntime"; + constexpr PCWSTR c_PackageFamilyName = L"Microsoft.WindowsAppRuntime.4.1_8wekyb3d8bbwe"; + constexpr PCWSTR c_PackageFullName = L"Microsoft.WindowsAppRuntime.4.1_4.1.1967.333_neutral__8wekyb3d8bbwe"; constexpr const PACKAGE_VERSION GetPackageVersion() { PACKAGE_VERSION version{}; diff --git a/test/DynamicDependency/data/DynamicDependency.DataStore.Msix/appxmanifest.xml b/test/DynamicDependency/data/DynamicDependency.DataStore.Msix/appxmanifest.xml index d9e71892d6..8b2fd7fef0 100644 --- a/test/DynamicDependency/data/DynamicDependency.DataStore.Msix/appxmanifest.xml +++ b/test/DynamicDependency/data/DynamicDependency.DataStore.Msix/appxmanifest.xml @@ -22,7 +22,7 @@ - + diff --git a/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-arm64.xml b/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-arm64.xml index 7bc7dfdc6e..2da0059930 100644 --- a/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-arm64.xml +++ b/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-arm64.xml @@ -23,7 +23,7 @@ - + diff --git a/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-x64.xml b/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-x64.xml index e3e792adcb..bb441777cc 100644 --- a/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-x64.xml +++ b/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-x64.xml @@ -23,7 +23,7 @@ - + diff --git a/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-x86.xml b/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-x86.xml index d5afbd3903..59f6341d8b 100644 --- a/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-x86.xml +++ b/test/DynamicDependency/data/DynamicDependencyLifetimeManager.Msix/appxmanifest-x86.xml @@ -23,7 +23,7 @@ - + diff --git a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-arm64.xml b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-arm64.xml index a329285232..d631917fd5 100644 --- a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-arm64.xml +++ b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-arm64.xml @@ -23,7 +23,7 @@ - + diff --git a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-x64.xml b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-x64.xml index 36d5bf39ab..4be667749b 100644 --- a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-x64.xml +++ b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-x64.xml @@ -23,7 +23,7 @@ - + diff --git a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-x86.xml b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-x86.xml index e7b5a472cc..5521c26e01 100644 --- a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-x86.xml +++ b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1000.Msix/appxmanifest-x86.xml @@ -23,7 +23,7 @@ - + diff --git a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-arm64.xml b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-arm64.xml index 36405758f5..b928157cbd 100644 --- a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-arm64.xml +++ b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-arm64.xml @@ -23,7 +23,7 @@ - + diff --git a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-x64.xml b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-x64.xml index e2cac7038e..1dd12c1073 100644 --- a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-x64.xml +++ b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-x64.xml @@ -23,7 +23,7 @@ - + diff --git a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-x86.xml b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-x86.xml index edd8e6a929..32634d5fe2 100644 --- a/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-x86.xml +++ b/test/DynamicDependency/data/DynamicDependencyLifetimeManagerGC1010.Msix/appxmanifest-x86.xml @@ -23,7 +23,7 @@ - + diff --git a/test/DynamicDependency/data/Microsoft.WindowsAppRuntime.Framework/appxmanifest.xml b/test/DynamicDependency/data/Microsoft.WindowsAppRuntime.Framework/appxmanifest.xml index 2380c63e0b..b94b09a1b3 100644 --- a/test/DynamicDependency/data/Microsoft.WindowsAppRuntime.Framework/appxmanifest.xml +++ b/test/DynamicDependency/data/Microsoft.WindowsAppRuntime.Framework/appxmanifest.xml @@ -6,7 +6,7 @@ IgnorableNamespaces="uap"> diff --git a/test/DynamicDependency/data/WindowsAppRuntime.Test.Singleton.Msix/appxmanifest.xml b/test/DynamicDependency/data/WindowsAppRuntime.Test.Singleton.Msix/appxmanifest.xml index fc696cb55a..85b46a7087 100644 --- a/test/DynamicDependency/data/WindowsAppRuntime.Test.Singleton.Msix/appxmanifest.xml +++ b/test/DynamicDependency/data/WindowsAppRuntime.Test.Singleton.Msix/appxmanifest.xml @@ -22,7 +22,7 @@ - + diff --git a/test/EnvironmentManagerTests/AppxManifest.pkg.xml b/test/EnvironmentManagerTests/AppxManifest.pkg.xml index 864b6f9b45..6fa75394d1 100644 --- a/test/EnvironmentManagerTests/AppxManifest.pkg.xml +++ b/test/EnvironmentManagerTests/AppxManifest.pkg.xml @@ -15,7 +15,7 @@ - + diff --git a/test/EnvironmentManagerTests/CentennialAppxManifest.pkg.xml b/test/EnvironmentManagerTests/CentennialAppxManifest.pkg.xml index 5f54b773f5..b1c309bbc6 100644 --- a/test/EnvironmentManagerTests/CentennialAppxManifest.pkg.xml +++ b/test/EnvironmentManagerTests/CentennialAppxManifest.pkg.xml @@ -17,7 +17,7 @@ - + diff --git a/test/EnvironmentManagerTests/EnvironmentManagerTests.testdef b/test/EnvironmentManagerTests/EnvironmentManagerTests.testdef new file mode 100644 index 0000000000..400190dd51 --- /dev/null +++ b/test/EnvironmentManagerTests/EnvironmentManagerTests.testdef @@ -0,0 +1,11 @@ +{ + "Tests": [ + { + "Description": "Environment Manager Tests", + "Filename": "EnvironmentManagerTests.dll", + "Parameters": "", + "Architectures": ["x64", "x86", "arm64"], + "Status": "Disabled" + } + ] +} diff --git a/test/EnvironmentManagerTests/EnvironmentManagerTests.vcxproj b/test/EnvironmentManagerTests/EnvironmentManagerTests.vcxproj index 6d5f91ce5f..cafdaf0b01 100644 --- a/test/EnvironmentManagerTests/EnvironmentManagerTests.vcxproj +++ b/test/EnvironmentManagerTests/EnvironmentManagerTests.vcxproj @@ -172,6 +172,7 @@ + diff --git a/test/GetNugetDependencies.ps1 b/test/GetNugetDependencies.ps1 new file mode 100644 index 0000000000..0d3157f9ea --- /dev/null +++ b/test/GetNugetDependencies.ps1 @@ -0,0 +1,154 @@ +param( + [Parameter(Mandatory = $true)] + [string]$PackageName, + + [string]$Source = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json", + [string]$TempFolder = (Join-Path $env:TEMP "NuGetDeps"), + [string]$OutputVariableName = "dependencyList" +) + +Set-StrictMode -Version 3.0 +$ErrorActionPreference = 'Stop' + +# Clean start +if (Test-Path $TempFolder) { + Remove-Item $TempFolder -Recurse -Force -ErrorAction SilentlyContinue +} +New-Item -Path $TempFolder -ItemType Directory -Force | Out-Null + +$allDependencies = @{} # Use hashtable to track unique dependencies +$processed = @{} # Track processed packages + +Write-Host "=== NuGet Dependency Discovery ===" -ForegroundColor Green +Write-Host "Source: $Source" -ForegroundColor Yellow +Write-Host "Temp Folder: $TempFolder" -ForegroundColor Yellow + +function Extract-DependenciesFromPackage { + param( + [string]$PackageName + ) + + Write-Host "`nProcessing: $PackageName" -ForegroundColor Cyan + + # Build download arguments with unique output directory to avoid conflicts + $packageTempDir = Join-Path $TempFolder "pkg_$PackageName" + $downloadArgs = @("install", $PackageName, "-Source", $Source, "-OutputDirectory", $packageTempDir, "-ExcludeVersion", "-NonInteractive", "-DependencyVersion", "Ignore", "-PreRelease") + + try { + $result = & nuget.exe @downloadArgs 2>&1 + if ($LASTEXITCODE -ne 0) { + Write-Warning " Failed to download $PackageName" + Write-Host " Error details: $result" -ForegroundColor Red + return @() + } + + # Find and extract nupkg + $packagePath = Join-Path $packageTempDir $PackageName + $nupkgFile = Get-ChildItem -Path $packagePath -Filter "*.nupkg" | Select-Object -First 1 + + if (-not $nupkgFile) { + Write-Warning " No nupkg found for $PackageName" + return @() + } + + # Extract nupkg to find nuspec + $extractPath = Join-Path $TempFolder "extract_$PackageName" + if (Test-Path $extractPath) { + Remove-Item $extractPath -Recurse -Force -ErrorAction SilentlyContinue + } + New-Item -Path $extractPath -ItemType Directory -Force | Out-Null + + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($nupkgFile.FullName, $extractPath) + + # Find and parse nuspec + $nuspecFile = Get-ChildItem -Path $extractPath -Filter "*.nuspec" | Select-Object -First 1 + if (-not $nuspecFile) { + Write-Host " No dependencies found (no nuspec)" -ForegroundColor Gray + return @() + } + + [xml]$nuspec = Get-Content $nuspecFile.FullName -Encoding UTF8 + + # Extract dependencies + $dependencies = @() + $ns = $nuspec.DocumentElement.NamespaceURI + + if ($ns) { + $nsManager = New-Object System.Xml.XmlNamespaceManager($nuspec.NameTable) + $nsManager.AddNamespace("n", $ns) + $depNodes = $nuspec.SelectNodes("//n:dependency", $nsManager) + } else { + $depNodes = $nuspec.SelectNodes("//dependency") + } + + foreach ($dep in $depNodes) { + $depId = $dep.GetAttribute("id") + if ($depId -and $depId -ne $PackageName) { + $dependencies += $depId + } + } + + if ($dependencies.Count -gt 0) { + Write-Host " Dependencies: $($dependencies -join ', ')" -ForegroundColor Gray + } else { + Write-Host " No dependencies found" -ForegroundColor Gray + } + + return $dependencies + } + catch { + throw "Error processing $PackageName`: $($_.Exception.Message)" + } +} + +function Process-PackageRecursively { + param( + [string]$PackageName, + [int]$Depth = 0 + ) + + # Avoid infinite recursion + if ($Depth -gt 10) { + Write-Warning "Maximum depth reached for $PackageName" + return + } + + # Skip if already processed + if ($processed.ContainsKey($PackageName)) { + return + } + + $processed[$PackageName] = $true + + # Get dependencies for this package + $dependencies = Extract-DependenciesFromPackage -PackageName $PackageName + + # Add dependencies to global list and process recursively + foreach ($dep in $dependencies) { + if (-not $allDependencies.ContainsKey($dep)) { + $allDependencies[$dep] = $true + } + + Process-PackageRecursively -PackageName $dep -Depth ($Depth + 1) + } +} + +# Start processing +Process-PackageRecursively -PackageName $PackageName + +# Display results +Write-Host "`n=== RESULTS ===" -ForegroundColor Green +Write-Host "Total dependencies found: $($allDependencies.Count)" -ForegroundColor Yellow + +if ($allDependencies.Count -gt 0) { + Write-Host "`nAll Dependencies:" -ForegroundColor Cyan + $allDependencies.Keys | Sort-Object | ForEach-Object { + Write-Host " $_" -ForegroundColor White + } + + Write-Host "`nDependency Names (semicolon-separated string):" -ForegroundColor Cyan + $dependenciesString = $allDependencies.Keys -join ';' + Write-Host "##vso[task.setvariable variable=$OutputVariableName;isOutput=true]$dependenciesString" + Write-Host "##vso[task.setvariable variable=$OutputVariableName;]$dependenciesString" +} \ No newline at end of file diff --git a/test/LRPTests/APITests.cpp b/test/LRPTests/APITests.cpp index e4843ebbca..b1eefc8597 100644 --- a/test/LRPTests/APITests.cpp +++ b/test/LRPTests/APITests.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #include "pch.h" @@ -6,6 +6,7 @@ #include #include #include "NotificationPlatformActivation.h" +#include "MddWin11.h" using namespace WEX::Common; using namespace WEX::Logging; @@ -26,7 +27,7 @@ namespace Test::LRP BEGIN_TEST_CLASS(LRPTests) TEST_CLASS_PROPERTY(L"Description", L"Windows App SDK Push Notifications Long Running Process tests") TEST_CLASS_PROPERTY(L"ThreadingModel", L"MTA") - TEST_CLASS_PROPERTY(L"RunAs:Class", L"RestrictedUser") + TEST_CLASS_PROPERTY(L"RunAs", L"RestrictedUser") END_TEST_CLASS() wil::com_ptr GetNotificationPlatform() @@ -40,13 +41,28 @@ namespace Test::LRP TEST_CLASS_SETUP(ClassInit) { - ::Test::Bootstrap::SetupPackages(Test::Bootstrap::Packages::Framework | Test::Bootstrap::Packages::Singleton); + ::Test::Bootstrap::Setup(); + + PCWSTR testFrameworkPackageFamilyName = ::Test::Bootstrap::TP::WindowsAppRuntimeFramework::c_PackageFamilyName; + PCWSTR testMainPackageFamilyName = ::Test::Bootstrap::TP::WindowsAppRuntimeMain::c_PackageFamilyName; + + + // For Windows 11 newer versions, the TestInitialize will fail fast if we pass a non null package family name. + // https://github.com/microsoft/WindowsAppSDK/blob/main/dev/Common/WindowsAppRuntime.VersionInfo.cpp#L123-L133 + if (MddCore::Win11::IsSupported()) + { + testMainPackageFamilyName = nullptr; + } + + ::WindowsAppRuntime::VersionInfo::TestInitialize(testFrameworkPackageFamilyName, testMainPackageFamilyName); + return true; } TEST_CLASS_CLEANUP(ClassUninit) { - ::Test::Bootstrap::CleanupPackages(Test::Bootstrap::Packages::Framework | Test::Bootstrap::Packages::Singleton); + ::WindowsAppRuntime::VersionInfo::TestShutdown(); + ::Test::Bootstrap::Cleanup(); return true; } @@ -77,7 +93,7 @@ namespace Test::LRP &processInfo))); // Wait for the process to come up and be captured in the snapshot from verification step. - Sleep(1000); + Sleep(5000); VerifyLRP_IsRunning(true); } @@ -92,7 +108,7 @@ namespace Test::LRP BOOL result{ Process32First(processesSnapshot.get(), &processEntry) }; while (result != FALSE) { - if (wcscmp(L"PushNotificationsLongRunningTask.exe", processEntry.szExeFile) == 0) + if (_wcsicmp(L"PushNotificationsLongRunningTask.exe", processEntry.szExeFile) == 0) { VERIFY_IS_TRUE(isRunning); DWORD processId{ processEntry.th32ProcessID }; diff --git a/test/LRPTests/LRPTests.testdef b/test/LRPTests/LRPTests.testdef new file mode 100644 index 0000000000..69f6c5c7c8 --- /dev/null +++ b/test/LRPTests/LRPTests.testdef @@ -0,0 +1,11 @@ +{ + "Tests": [ + { + "Description": "Push Notifications Long Running Process (LRP) tests", + "Filename": "LRPTests.dll", + "Parameters": "", + "Architectures": ["x64", "x86"], + "Status": "Enabled" + } + ] +} diff --git a/test/LRPTests/LRPTests.vcxproj b/test/LRPTests/LRPTests.vcxproj index fd78795ef9..53b8c0e24d 100644 --- a/test/LRPTests/LRPTests.vcxproj +++ b/test/LRPTests/LRPTests.vcxproj @@ -82,6 +82,8 @@ Windows onecore.lib;onecoreuap.lib;Microsoft.WindowsAppRuntime.lib;wex.common.lib;wex.logger.lib;te.common.lib;%(AdditionalDependencies) $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories);$(OutDir)\..\WindowsAppRuntime_DLL + Microsoft.WindowsAppRuntime.Bootstrap.dll;%(DelayLoadDLLs) + Microsoft.WindowsAppRuntime.dll;%(DelayLoadDLLs) @@ -115,6 +117,11 @@ + + + {f76b776e-86f5-48c5-8fc7-d2795ecc9746} + + @@ -129,6 +136,7 @@ + diff --git a/test/LRPTests/pch.h b/test/LRPTests/pch.h index e4bd75dfcc..168037a9a2 100644 --- a/test/LRPTests/pch.h +++ b/test/LRPTests/pch.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation and Contributors. +// Copyright (c) Microsoft Corporation and Contributors. // Licensed under the MIT License. #ifndef PCH_H @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include diff --git a/test/ModifySampleAppsReferences.ps1 b/test/ModifySampleAppsReferences.ps1 new file mode 100644 index 0000000000..18aeb92487 --- /dev/null +++ b/test/ModifySampleAppsReferences.ps1 @@ -0,0 +1,93 @@ +# This script is a Foundation version of https://github.com/microsoft/WindowsAppSDK-Samples/blob/release/experimental/UpdateVersions.ps1 +Param( + [string]$SampleRepoRoot = "", + [string]$FoundationVersion = "", + [string]$FoundationPackagesFolder = "", + [string]$WASDKNugetDependencies = "" +) + +Set-StrictMode -Version 3.0 +$ErrorActionPreference = 'Stop' + +$packagesToRemoveList = $WASDKNugetDependencies -split ';' +$packagesToRemoveList += @("Microsoft.WindowsAppSDK") + +$packagesToUpdateTable = @{"Microsoft.WindowsAppSDK.Foundation" = $FoundationVersion} + +# Go through the nuget packages folder to get the versions of all dependency packages. +if (!($FoundationPackagesFolder -eq "")) +{ + Get-ChildItem $FoundationPackagesFolder | + Where-Object { $_.Name -like "Microsoft.WindowsAppSDK.*" -or + $_.Name -like "Microsoft.Windows.SDK.BuildTools.*"} | + Where-Object { $_.Name -notlike "*.nupkg" } | + ForEach-Object { + if ($_.Name -match "^(Microsoft\.WindowsAppSDK\.[a-zA-Z]+)\.([0-9].*)$" -or + $_.Name -match "^(Microsoft\.Windows\.SDK\.BuildTools\.MSIX)\.([0-9].*)$" -or + $_.Name -match "^(Microsoft\.Windows\.SDK\.BuildTools)\.([0-9].*)$") + { + $packagesToUpdateTable[$Matches[1]] = $Matches[2] + Write-Host "Found $($Matches[1]) - $($Matches[2])" + + $packagesToRemoveList = $packagesToRemoveList | Where-Object { $_ -ne $Matches[1] } + } + } +} +Write-Host "NuGet packages to version table: $($packagesToUpdateTable | Out-String)" +Write-Host "NuGet packages to remove: $($packagesToRemoveList | Out-String)" + +Get-ChildItem -Recurse packages.config -Path $SampleRepoRoot | foreach-object { + $content = Get-Content $_.FullName -Raw + + foreach ($nugetPackageToVersion in $packagesToUpdateTable.GetEnumerator()) + { + $newVersionString = 'package id="' + $nugetPackageToVersion.Key + '" version="' + $nugetPackageToVersion.Value + '"' + $oldVersionString = 'package id="' + $nugetPackageToVersion.Key + '" version="[-.0-9a-zA-Z]*"' + $content = $content -replace $oldVersionString, $newVersionString + } + foreach ($package in $packagesToRemoveList) + { + $packageReferenceString = '(?m)^\s*]*?/>\s*$\r?\n?' + $content = $content -replace $packageReferenceString, '' + } + + Set-Content -Path $_.FullName -Value $content + Write-Host "Modified " $_.FullName +} + +Get-ChildItem -Recurse *.vcxproj -Path $SampleRepoRoot | foreach-object { + $content = Get-Content $_.FullName -Raw + + foreach ($nugetPackageToVersion in $packagesToUpdateTable.GetEnumerator()) + { + $newVersionString = "\$($nugetPackageToVersion.Key)." + $nugetPackageToVersion.Value + '\' + $oldVersionString = "\\$($nugetPackageToVersion.Key).[0-9][-.0-9a-zA-Z]*\\" + $content = $content -replace $oldVersionString, $newVersionString + } + foreach ($package in $packagesToRemoveList) + { + $packageReferenceString = "(?m)^.*\\$package\.[0-9][-.0-9a-zA-Z]*\\.*\r?\n?" + $content = $content -replace $packageReferenceString, '' + } + + Set-Content -Path $_.FullName -Value $content + Write-Host "Modified " $_.FullName +} + +Get-ChildItem -Recurse *.wapproj -Path $SampleRepoRoot | foreach-object { + $newVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK.Foundation" Version="'+ $FoundationVersion + '"' + $oldVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK" Version="[-.0-9a-zA-Z]*"' + $content = Get-Content $_.FullName -Raw + $content = $content -replace $oldVersionString, $newVersionString + Set-Content -Path $_.FullName -Value $content + Write-Host "Modified " $_.FullName +} + +Get-ChildItem -Recurse *.csproj -Path $SampleRepoRoot | foreach-object { + $newVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK.Foundation" Version="'+ $FoundationVersion + '"' + $oldVersionString = 'PackageReference Include="Microsoft.WindowsAppSDK" Version="[-.0-9a-zA-Z]*"' + $content = Get-Content $_.FullName -Raw + $content = $content -replace $oldVersionString, $newVersionString + Set-Content -Path $_.FullName -Value $content + Write-Host "Modified " $_.FullName +} \ No newline at end of file diff --git a/test/PackageManager/API/PackageDeploymentManagerTests.h b/test/PackageManager/API/PackageDeploymentManagerTests.h index 4c23e842d3..bd74f63735 100644 --- a/test/PackageManager/API/PackageDeploymentManagerTests.h +++ b/test/PackageManager/API/PackageDeploymentManagerTests.h @@ -154,6 +154,10 @@ namespace Test::PackageManager::Tests [&](const IAsyncOperationWithProgress&, PackageDeploymentProgress progress) { WEX::Logging::Log::Comment(WEX::Common::String().Format(L"...State:%d Percentage:%lf", static_cast(progress.Status), progress.Progress)); + if (progress.Progress != 0) + { + VERIFY_ARE_EQUAL(PackageDeploymentProgressStatus::InProgress, progress.Status); + } } ); deploymentOperation.Progress(progressCallback); diff --git a/test/PowerNotifications/PowerNotifications-AppxManifest.xml b/test/PowerNotifications/PowerNotifications-AppxManifest.xml index 356c440fa9..c81ca09511 100644 --- a/test/PowerNotifications/PowerNotifications-AppxManifest.xml +++ b/test/PowerNotifications/PowerNotifications-AppxManifest.xml @@ -21,7 +21,7 @@ - + diff --git a/test/PowerNotifications/PowerNotifications.testdef b/test/PowerNotifications/PowerNotifications.testdef new file mode 100644 index 0000000000..7aa0b083ca --- /dev/null +++ b/test/PowerNotifications/PowerNotifications.testdef @@ -0,0 +1,11 @@ +{ + "Tests": [ + { + "Description": "Power Notification Tests", + "Filename": "PowerNotifications.dll", + "Parameters": "", + "Architectures": ["x64", "x86", "arm64"], + "Status": "Disabled" + } + ] +} diff --git a/test/PowerNotifications/PowerNotifications.vcxproj b/test/PowerNotifications/PowerNotifications.vcxproj index d3c7054c5e..4a6083f0a4 100644 --- a/test/PowerNotifications/PowerNotifications.vcxproj +++ b/test/PowerNotifications/PowerNotifications.vcxproj @@ -162,6 +162,7 @@ + diff --git a/test/PushNotificationTests/PushNotifications-AppxManifest.xml b/test/PushNotificationTests/PushNotifications-AppxManifest.xml index b3e91fa141..1bdac462b2 100644 --- a/test/PushNotificationTests/PushNotifications-AppxManifest.xml +++ b/test/PushNotificationTests/PushNotifications-AppxManifest.xml @@ -23,7 +23,7 @@ - + diff --git a/test/StoragePickersTests/PickerCommonTests.cpp b/test/StoragePickersTests/PickerCommonTests.cpp index 29c870c7f8..555eec06a1 100644 --- a/test/StoragePickersTests/PickerCommonTests.cpp +++ b/test/StoragePickersTests/PickerCommonTests.cpp @@ -99,6 +99,7 @@ namespace Test::PickerCommonTests // Act. auto dialog = winrt::create_instance(CLSID_FileSaveDialog, CLSCTX_INPROC_SERVER); + parameters.ConfigureDialog(dialog.as()); parameters.ConfigureFileSaveDialog(dialog); // Assert. @@ -130,6 +131,7 @@ namespace Test::PickerCommonTests // Act. auto dialog = winrt::create_instance(CLSID_FileSaveDialog, CLSCTX_INPROC_SERVER); + parameters.ConfigureDialog(dialog.as()); parameters.ConfigureFileSaveDialog(dialog); // Assert. @@ -150,7 +152,7 @@ namespace Test::PickerCommonTests // Act. PickerParameters parameters{}; - parameters.CaptureFilterSpec(picker.FileTypeFilter().GetView()); + parameters.CaptureFilterSpecData(picker.FileTypeFilter().GetView(), nullptr); // Assert. VERIFY_ARE_EQUAL(parameters.FileTypeFilterPara.size(), 3); @@ -176,7 +178,7 @@ namespace Test::PickerCommonTests // Act. PickerParameters parameters{}; - parameters.CaptureFilterSpec(picker.FileTypeFilter().GetView()); + parameters.CaptureFilterSpecData(picker.FileTypeFilter().GetView(), nullptr); // Assert. VERIFY_ARE_EQUAL(parameters.FileTypeFilterPara.size(), 1); @@ -196,7 +198,7 @@ namespace Test::PickerCommonTests // Act. PickerParameters parameters{}; - parameters.CaptureFilterSpec(picker.FileTypeFilter().GetView()); + parameters.CaptureFilterSpecData(picker.FileTypeFilter().GetView(), nullptr); // Assert. VERIFY_ARE_EQUAL(parameters.FileTypeFilterPara.size(), 1); @@ -206,6 +208,34 @@ namespace Test::PickerCommonTests L"*"); } + TEST_METHOD(VerifyFilters_FileOpenPickerWhenFileTypeChoicesDefinedExpectMatchingSpec) + { + // Arrange. + winrt::Microsoft::UI::WindowId windowId{}; + winrt::Microsoft::Windows::Storage::Pickers::FileOpenPicker picker(windowId); + + picker.FileTypeChoices().Insert( + L"Documents", winrt::single_threaded_vector({ L".txt", L".doc", L".docx" })); + picker.FileTypeChoices().Insert( + L"Pictures", winrt::single_threaded_vector({ L".png", L".jpg", L".jpeg", L".bmp" })); + + // Act. + PickerParameters parameters{}; + parameters.CaptureFilterSpecData( + winrt::Windows::Foundation::Collections::IVectorView{}, + picker.FileTypeChoices().GetView()); + + // Assert. + VERIFY_ARE_EQUAL(parameters.FileTypeFilterPara.size(), 2); + + VERIFY_ARE_EQUAL( + std::wstring(parameters.FileTypeFilterPara[0].pszSpec), + L"*.txt;*.doc;*.docx"); + VERIFY_ARE_EQUAL( + std::wstring(parameters.FileTypeFilterPara[1].pszSpec), + L"*.png;*.jpg;*.jpeg;*.bmp"); + } + TEST_METHOD(VerifyFilters_FileSavePickerWhenFileTypeChoicesDefinedExpectMatchingSpec) { // Arrange. @@ -219,7 +249,9 @@ namespace Test::PickerCommonTests // Act. PickerParameters parameters{}; - parameters.CaptureFilterSpec(picker.FileTypeChoices().GetView()); + parameters.CaptureFilterSpecData( + winrt::Windows::Foundation::Collections::IVectorView{}, + picker.FileTypeChoices().GetView()); // Assert. VERIFY_ARE_EQUAL(parameters.FileTypeFilterPara.size(), 2); @@ -242,7 +274,9 @@ namespace Test::PickerCommonTests // Act. PickerParameters parameters{}; - parameters.CaptureFilterSpec(picker.FileTypeChoices().GetView()); + parameters.CaptureFilterSpecData( + winrt::Windows::Foundation::Collections::IVectorView{}, + picker.FileTypeChoices().GetView()); // Assert. VERIFY_ARE_EQUAL(parameters.FileTypeFilterPara.size(), 1); @@ -250,6 +284,9 @@ namespace Test::PickerCommonTests VERIFY_ARE_EQUAL( std::wstring(parameters.FileTypeFilterPara[0].pszSpec), L"*"); + VERIFY_ARE_EQUAL( + std::wstring(parameters.FileTypeFilterPara[0].pszName), + L"All Files"); } TEST_METHOD(VerifyFilters_FileSavePickerWhenAsteriskFileTypeChoicesDefinedExpectAsteriskSpec) @@ -265,7 +302,9 @@ namespace Test::PickerCommonTests // Act. PickerParameters parameters{}; - parameters.CaptureFilterSpec(picker.FileTypeChoices().GetView()); + parameters.CaptureFilterSpecData( + winrt::Windows::Foundation::Collections::IVectorView{}, + picker.FileTypeChoices().GetView()); // Assert. VERIFY_ARE_EQUAL(parameters.FileTypeFilterPara.size(), 1); @@ -628,6 +667,9 @@ namespace Test::PickerCommonTests { picker.SuggestedFolder(suggestedFolder); VERIFY_ARE_EQUAL(picker.SuggestedFolder(), suggestedFolder); + + picker.SuggestedStartFolder(suggestedFolder); + VERIFY_ARE_EQUAL(picker.SuggestedStartFolder(), suggestedFolder); } else { @@ -641,6 +683,17 @@ namespace Test::PickerCommonTests { // Expected exception for invalid suggested folder } + + try + { + picker.SuggestedStartFolder(suggestedFolder); + std::wstring errorMessage = L"Expected exception for invalid suggested start folder: " + std::wstring(suggestedFolder); + VERIFY_FAIL(errorMessage.c_str()); + } + catch (...) + { + // Expected exception for invalid suggested start folder + } } } } diff --git a/test/StoragePickersTests/StoragePickersTests.cpp b/test/StoragePickersTests/StoragePickersTests.cpp index 5c63b5a2d2..97721e7c2a 100644 --- a/test/StoragePickersTests/StoragePickersTests.cpp +++ b/test/StoragePickersTests/StoragePickersTests.cpp @@ -153,6 +153,17 @@ namespace Test::StoragePickersTests picker.FileTypeFilter().Append(L"*"); VERIFY_ARE_EQUAL(picker.FileTypeFilter().GetAt(0), L"*"); + + auto openPickerChoices = winrt::single_threaded_vector(); + openPickerChoices.Append(L".txt"); + picker.FileTypeChoices().Insert(L"Documents", openPickerChoices); + VERIFY_ARE_EQUAL(picker.FileTypeChoices().Lookup(L"Documents").GetAt(0), L".txt"); + + picker.SuggestedFolder(L"C:\\temp_fileopenpicker_ut_temp"); + VERIFY_ARE_EQUAL(picker.SuggestedFolder(), L"C:\\temp_fileopenpicker_ut_temp"); + + picker.SuggestedStartFolder(L"C:\\temp_fileopenpicker_ut_start"); + VERIFY_ARE_EQUAL(picker.SuggestedStartFolder(), L"C:\\temp_fileopenpicker_ut_start"); } TEST_METHOD(VerifyFileSavePickerOptionsAreReadCorrectly) @@ -174,6 +185,9 @@ namespace Test::StoragePickersTests filters.Append(L"*"); picker.FileTypeChoices().Insert(L"All Files", filters); VERIFY_ARE_EQUAL(picker.FileTypeChoices().Lookup(L"All Files").GetAt(0), L"*"); + + picker.SuggestedStartFolder(L"C:\\temp_filesavepicker_start"); + VERIFY_ARE_EQUAL(picker.SuggestedStartFolder(), L"C:\\temp_filesavepicker_start"); } TEST_METHOD(VerifyFolderPickerOptionsAreReadCorrectly) @@ -193,6 +207,12 @@ namespace Test::StoragePickersTests picker.CommitButtonText(L"commit"); VERIFY_ARE_EQUAL(picker.CommitButtonText(), L"commit"); + + picker.SuggestedFolder(L"C:\\temp_folderpicker_ut_temp"); + VERIFY_ARE_EQUAL(picker.SuggestedFolder(), L"C:\\temp_folderpicker_ut_temp"); + + picker.SuggestedStartFolder(L"C:\\temp_folderpicker_ut_start"); + VERIFY_ARE_EQUAL(picker.SuggestedStartFolder(), L"C:\\temp_folderpicker_ut_start"); } }; diff --git a/test/TestApps/AccessControlTestAppPackage/Package.appxmanifest b/test/TestApps/AccessControlTestAppPackage/Package.appxmanifest index fb54b4e24a..c05de3bfda 100644 --- a/test/TestApps/AccessControlTestAppPackage/Package.appxmanifest +++ b/test/TestApps/AccessControlTestAppPackage/Package.appxmanifest @@ -21,7 +21,7 @@ - + diff --git a/test/TestApps/AppLifecycleTestApp/Helpers.cpp b/test/TestApps/AppLifecycleTestApp/Helpers.cpp index 6ba6da0570..6def0e1f36 100644 --- a/test/TestApps/AppLifecycleTestApp/Helpers.cpp +++ b/test/TestApps/AppLifecycleTestApp/Helpers.cpp @@ -42,7 +42,7 @@ HRESULT BootstrapInitialize() constexpr PCWSTR c_PackageNamePrefix{ L"WindowsAppRuntime.Test.DDLM" }; constexpr PCWSTR c_PackagePublisherId{ L"8wekyb3d8bbwe" }; - constexpr PCWSTR c_FrameworkPackageNamePrefix = L"Microsoft.WindowsAppRuntime.Framework"; + constexpr PCWSTR c_FrameworkPackageNamePrefix = L"Microsoft.WindowsAppRuntime"; constexpr PCWSTR c_MainPackageNamePrefix = L"WindowsAppRuntime.Test.DynDep.DataStore"; RETURN_IF_FAILED(mddTestInitialize(c_PackageNamePrefix, c_PackagePublisherId, c_FrameworkPackageNamePrefix, c_MainPackageNamePrefix)); diff --git a/test/TestApps/AppLifecycleTestPackage/Package.appxmanifest b/test/TestApps/AppLifecycleTestPackage/Package.appxmanifest index 803dbcdcea..159050fedd 100644 --- a/test/TestApps/AppLifecycleTestPackage/Package.appxmanifest +++ b/test/TestApps/AppLifecycleTestPackage/Package.appxmanifest @@ -20,7 +20,7 @@ - + diff --git a/test/TestApps/ManualTestApp/main.cpp b/test/TestApps/ManualTestApp/main.cpp index efab0e30cf..2d57ffc452 100644 --- a/test/TestApps/ManualTestApp/main.cpp +++ b/test/TestApps/ManualTestApp/main.cpp @@ -35,7 +35,7 @@ HRESULT BootstrapInitialize() constexpr PCWSTR c_PackageNamePrefix{ L"WindowsAppRuntime.Test.DDLM" }; constexpr PCWSTR c_PackagePublisherId{ L"8wekyb3d8bbwe" }; - constexpr PCWSTR c_FrameworkPackageFamilyName = L"Microsoft.WindowsAppRuntime.Framework.4.1_8wekyb3d8bbwe"; + constexpr PCWSTR c_FrameworkPackageFamilyName = L"Microsoft.WindowsAppRuntime.4.1_8wekyb3d8bbwe"; constexpr PCWSTR c_MainPackageFamilyName = L"WindowsAppRuntime.Test.DynDep.DataStore.4.1_8wekyb3d8bbwe"; RETURN_IF_FAILED(MddBootstrapTestInitialize(c_PackageNamePrefix, c_PackagePublisherId, c_FrameworkPackageFamilyName, c_MainPackageFamilyName)); diff --git a/test/TestApps/OAuthTestAppPackage/Package.appxmanifest b/test/TestApps/OAuthTestAppPackage/Package.appxmanifest index b50c239425..ef3a268bcb 100644 --- a/test/TestApps/OAuthTestAppPackage/Package.appxmanifest +++ b/test/TestApps/OAuthTestAppPackage/Package.appxmanifest @@ -21,7 +21,7 @@ - + diff --git a/test/TestApps/PushNotificationsDemoPackage/Package.appxmanifest b/test/TestApps/PushNotificationsDemoPackage/Package.appxmanifest index a15bd58296..25b4b44fc0 100644 --- a/test/TestApps/PushNotificationsDemoPackage/Package.appxmanifest +++ b/test/TestApps/PushNotificationsDemoPackage/Package.appxmanifest @@ -22,7 +22,7 @@ - + diff --git a/test/TestApps/PushNotificationsTestAppPackage/Package.appxmanifest b/test/TestApps/PushNotificationsTestAppPackage/Package.appxmanifest index bda0ba68a4..7167f44b82 100644 --- a/test/TestApps/PushNotificationsTestAppPackage/Package.appxmanifest +++ b/test/TestApps/PushNotificationsTestAppPackage/Package.appxmanifest @@ -21,7 +21,7 @@ - + diff --git a/test/TestApps/ToastNotificationsDemoApp/main.cpp b/test/TestApps/ToastNotificationsDemoApp/main.cpp index 3f321c6dbf..4845002082 100644 --- a/test/TestApps/ToastNotificationsDemoApp/main.cpp +++ b/test/TestApps/ToastNotificationsDemoApp/main.cpp @@ -145,7 +145,7 @@ int main() { constexpr PCWSTR c_PackageNamePrefix{ L"WindowsAppRuntime.Test.DDLM" }; constexpr PCWSTR c_PackagePublisherId{ L"8wekyb3d8bbwe" }; - constexpr PCWSTR c_FrameworkPackageFamilyName = L"Microsoft.WindowsAppRuntime.Framework.4.1_8wekyb3d8bbwe"; + constexpr PCWSTR c_FrameworkPackageFamilyName = L"Microsoft.WindowsAppRuntime.4.1_8wekyb3d8bbwe"; constexpr PCWSTR c_MainPackageFamilyName = L"WindowsAppRuntime.Test.DynDep.DataStore.4.1_8wekyb3d8bbwe"; RETURN_IF_FAILED(MddBootstrapTestInitialize(c_PackageNamePrefix, c_PackagePublisherId, c_FrameworkPackageFamilyName, c_MainPackageFamilyName)); diff --git a/test/TestApps/ToastNotificationsDemoAppPackage/Package.appxmanifest b/test/TestApps/ToastNotificationsDemoAppPackage/Package.appxmanifest index 94874f7a8a..54f309d5d2 100644 --- a/test/TestApps/ToastNotificationsDemoAppPackage/Package.appxmanifest +++ b/test/TestApps/ToastNotificationsDemoAppPackage/Package.appxmanifest @@ -22,7 +22,7 @@ - + diff --git a/test/TestApps/ToastNotificationsDemoPackage/Package.appxmanifest b/test/TestApps/ToastNotificationsDemoPackage/Package.appxmanifest index c03144bcd7..def1c78ea7 100644 --- a/test/TestApps/ToastNotificationsDemoPackage/Package.appxmanifest +++ b/test/TestApps/ToastNotificationsDemoPackage/Package.appxmanifest @@ -22,7 +22,7 @@ - + diff --git a/test/TestApps/ToastNotificationsTestAppPackage/Package.appxmanifest b/test/TestApps/ToastNotificationsTestAppPackage/Package.appxmanifest index 4859716f74..4bcdfd942f 100644 --- a/test/TestApps/ToastNotificationsTestAppPackage/Package.appxmanifest +++ b/test/TestApps/ToastNotificationsTestAppPackage/Package.appxmanifest @@ -21,7 +21,7 @@ - + diff --git a/test/WindowsAppSDK.Test.NetCore/Program.cs b/test/WindowsAppSDK.Test.NetCore/Program.cs new file mode 100644 index 0000000000..f8c0904c89 --- /dev/null +++ b/test/WindowsAppSDK.Test.NetCore/Program.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +using System; + +namespace WindowsAppSDK.Test.NetCore +{ + // This app justs exists as a convenient way to output the .net 5 runtime binaries to BuildOutput + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/test/WindowsAppSDK.Test.NetCore/WindowsAppSDK.Test.NetCore.csproj b/test/WindowsAppSDK.Test.NetCore/WindowsAppSDK.Test.NetCore.csproj new file mode 100644 index 0000000000..0af8d387fc --- /dev/null +++ b/test/WindowsAppSDK.Test.NetCore/WindowsAppSDK.Test.NetCore.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + win10-$(Platform) + win10-x86 + false + $(BaseOutputPath)\$(MSBuildProjectName)\net6\ + False + x86;x64;arm64 + + + diff --git a/test/WindowsAppSDK.Test.SampleTests/SampleTest.testdef b/test/WindowsAppSDK.Test.SampleTests/SampleTest.testdef new file mode 100644 index 0000000000..d611769e96 --- /dev/null +++ b/test/WindowsAppSDK.Test.SampleTests/SampleTest.testdef @@ -0,0 +1,11 @@ +{ + "Tests": [ + { + "Description": "Tests to launch sample apps", + "Filename": "WindowsAppSDK.Test.SampleTests.dll", + "Parameters": "", + "Architectures": ["x64", "arm64"], + "Status": "Enabled" + } + ] +} diff --git a/test/WindowsAppSDK.Test.SampleTests/TestAssembly.cs b/test/WindowsAppSDK.Test.SampleTests/TestAssembly.cs new file mode 100644 index 0000000000..f46275bd32 --- /dev/null +++ b/test/WindowsAppSDK.Test.SampleTests/TestAssembly.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using WEX.Logging.Interop; +using WEX.TestExecution; +using WEX.TestExecution.Markup; + +namespace WindowsAppSDK.Test.SampleTests +{ + [TestClass] + class TestAssembly + { + [AssemblyInitialize] + [TestProperty("CoreClrProfile", "net6")] + [TestProperty("IsolationLevel", "Class")] + public static void AssemblyInitialize(TestContext testContext) + { + Log.Comment("AssemblyInitialize"); + Log.Comment($" CurrentDirectory: {System.IO.Directory.GetCurrentDirectory()}"); + Log.Comment($" Executable: {System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName}"); + Log.Comment($" EntryAssembly: {EntryAssemblyLocation}"); + Log.Comment($"ExecutingAssembly: {System.Reflection.Assembly.GetExecutingAssembly().Location}"); + Log.Comment($" CallingAssembly: {System.Reflection.Assembly.GetCallingAssembly().Location}"); + } + + private static string EntryAssemblyLocation + { + get + { + var assembly = System.Reflection.Assembly.GetEntryAssembly(); + return assembly == null ? "" : assembly.Location; + } + } + } +} diff --git a/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDK.Test.SampleTests.csproj b/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDK.Test.SampleTests.csproj new file mode 100644 index 0000000000..d0f7ee7872 --- /dev/null +++ b/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDK.Test.SampleTests.csproj @@ -0,0 +1,25 @@ + + + + net6.0-windows10.0.18362 + win10-x86;win10-x64;win10-arm64 + $(BaseOutputPath)\$(MSBuildProjectName)\ + False + x86;x64;arm64 + + false + true + + + + + + + + + + + + + + diff --git a/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDKSampleAppTests.cs b/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDKSampleAppTests.cs new file mode 100644 index 0000000000..5722f02d91 --- /dev/null +++ b/test/WindowsAppSDK.Test.SampleTests/WindowsAppSDKSampleAppTests.cs @@ -0,0 +1,1397 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License. + +using System.Linq; +using Microsoft.Windows.Apps.Test.Foundation; +using Microsoft.Windows.Apps.Test.Foundation.Waiters; + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Collections.Generic; + +using WEX.Logging.Interop; +using WEX.TestExecution; +using WEX.TestExecution.Markup; + +/* + General Notes: + - If you are onboarding a new Sample app, please look for instructions in the "Adding a new Sample app" section below. + - This TAEF test attempts to verify that Sample apps on the Samples repo can be launched successfully given a newly built WASDK Foundation product + (i.e., regression detection). + - Currently, ~70 buildable and launchable Sample apps have been identified on the Samples repo. + - Because there are different kinds of Sample apps, the extend to which the app launch can be verified varies from one app to another. The + basic permutations are: [ Packaged | Unpackaged ] x [ Windowed | Console | wpf ]. + - "Packaged + Windowed" is generally the most rigiously verified, while "Unpackaged + Console" is minimally verified. + - This TAEF test requires the environment variable SAMPLES_ROOT_PATH to be set to point to the root folder of the sample apps, under which folders + such as AppLifeCycle and ResourcesManagement can be found, e.g. "set SAMPLES_ROOT_PATH=C:\\myRepo\\WindowsAppSDK-Samples\\Samples". This approach + seems to be more effective than spending time searching all logical drives the the machine for the samples root folder on each run of the test. + - The environment variable can be set in the pipeline or on a developer's local machine. + - The caller can optionally set environment variables Build_Platform and Build_Configuration to modify the paths to the target sample binaries. + Build_Platform can be one of { x86 | x64 | arm64 }. By default, Build_Platform=x64. + Build_Configuration can be one of { Debug | Release }. By default, Build_Configuration=Release. + - Currently, not all Sample apps are being built for all permutations of { x86 | x64 | arm64 } X { Debug | Release }, with X64+Release being the + greatest common denominator. When a test case exercises the Build_Platform + Build_Configuration which the corresponding Sample app isn't built + for, that test is being skipped. + - Not all Sample apps are working on all versions of Windows supported by WASDK. When a Sample app does not support a particular version of + Windows, the corresponding test case is being skipped on that version of Windows. + - The following information about each Sample app is currently hardcoded: relative path to executable/manifest, process name, window title, app + ID, package full/family names. The benefit of this hardcoding approach is simplicity. + - Test cases are intentionally "flat" structured, such that when any sample app test is failing, it is straight forward to diagnose and fix. + In case some of these per-app pieces are churning frequently enough in the future to create a maintenance burden, we can consider + investing into detection logic for populating most if not all of these pieces. + - It is currently deemed non-critical to maximize the degree of verification for every app on the Samples repo, given the purpose of increasing + test coverage for nightly builds, as there likely is already redundance in the test cases. The ROI on including more sample apps in the future + is anticipated to diminish, unless a new feature or app activation mechansim needs to be verified. +*/ + +namespace WindowsAppSDK.Test.SampleTests +{ + [TestClass] + public class WindowsAppSDKSampleAppTests + { + // 20-second timeout period for general shell commands. + public const int TIMEOUT = 20000; + + public static TestContext TestContext { get; private set; } + public static string SamplesRootPath = null; + public static string BuildArch = null; + public static string BuildConfig = null; + public Process Process { get; private set; } + + private class ProcessComparer : IEqualityComparer + { + public bool Equals(Process x, Process y) + { + // NOTE: The following expression has been updated locally and is now out of sync with its source: + // https://dev.azure.com/microsoft/WinUI/_search?action=history&type=code&text=def%3AProcessComparer&filters=ProjectFilters%7BWinUI%7DRepositoryFilters%7Bmicrosoft-ui-xaml-lift%7D&includeFacets=false&result=DefaultCollection/WinUI/microsoft-ui-xaml-lift/GBmain//controls/test/testinfra/MUXTestInfra/Infra/Application.cs. + return (x == null && y == null) || ((x != null && y != null) && (x.Id == y.Id)); + } + + public int GetHashCode(Process obj) + { + return obj.GetHashCode(); + } + } + + private int GetProcessIdFromAppWindow(string appWindowTitle) + { + var topWindowCondition = UICondition.CreateFromName(appWindowTitle); + if (UIObject.Root.Children.TryFind(topWindowCondition, out UIObject topWindowObj)) + { + Log.Comment("Found topWindowObj for #{0}...", topWindowCondition); + return topWindowObj.ProcessId; + } + + Log.Comment("topWindowObj _NOT FOUND_ for #{0}...", topWindowCondition); + return -1; + } + + private bool KillProcessAndWaitForExit(Process process) + { + Log.Comment($"Killing process {process.Id}"); + if (process.HasExited) + { + return true; + } + + process.Kill(); + return process.WaitForExit(10000 /*milliseconds*/); + } + + // If an explicit appWindowsProcessId is prescribed then add the corresponding process name to a list of processes to try to kill. + // Regardless of that, as long as a valid appProcessName is prescribed then we should be able to just add that name to the kill list. + // Hopefully that would leave us with a list of process names, potentially with duplicates, to kill. + // We then try to kill each unique process name, with verification. + // If the target process is not found, it's unexpected, bubble up an issue. + private void EnsureApplicationProcessHasExited(int appWindowsProcessId, string appProcessName) + { + const int MaxLaunchRetries = 4; + for (int retries = 1; retries <= MaxLaunchRetries; ++retries) + { + var appProcesses = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(appProcessName)).ToList(); + + if (appWindowsProcessId != -1) + { + try + { + appProcesses.Add(Process.GetProcessById(appWindowsProcessId)); + } + catch (Exception) + { + // Ignore. GetProcessById throws if the process has already exited. + } + } + + if (Process != null) + { + appProcesses.Add(Process); + } + + if (appProcesses.Count > 0) + { + foreach (var proc in appProcesses.Distinct(new ProcessComparer())) + { + if (!proc.HasExited && !KillProcessAndWaitForExit(proc)) + { + throw new Exception($"Unable to kill process: {proc}"); + } + } + return; + } + + // appWindowsProcessId==-1 means the caller did not wait for the app's window at launch; so in case we've got + // here too quickly, retry a couple of times to find the app's processes. + if ((appWindowsProcessId != -1) || (retries >= MaxLaunchRetries)) + { + // http://task.ms/58006958. Preliminary investigation revealed that, when we can't detect the expected + // sample app process after the launch command has returned success, even with retries, is probably not + // because we don't wait long enough for the sampple app's process to be found, but is more likely + // because the sample app process has naturally terminated so quickly that the test code doesn't catch + // it soon enough. IOW, increasing the wait time for the app launch is unlikely to help, and the + // intermittent sample app launch failures being reported recently are most likely noise (false positives). + // To unblock pipeline runs, we are making it a non-fatal situation when we hit this code path. Log a + // warning message to maintain visibility to the "process not found" errors. + // When we can properly detect the "sample app terminates too quickly" situation in the future, we might + // want to bring back the following commented out statement to make this code path a fatal problem again. + // Verify.Fail($"Couldn't find the expected process {appProcessName}, not good!"); + Log.Warning($"[WARNING]: Couldn't find the expected process {appProcessName}, not good!"); + return; + } + + Log.Comment("Found no process related to {0} to terminate, trying again {1} after 2 sec.", appProcessName, retries); + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + return; + } + + // Try to launch an unpackaged app by its full exe path, with retries. + // If a valid appWindowTitle is prescribed, then the launch isn't considered successful until the window with the expected + // title is found. Also verify that the expected process name is present, and then use that name to terminate the appp process. + // Process termination is also verified. + private void LaunchAndCloseUnpackagedApp(string unpackagedExeFullPath, string appWindowTitle, string appProcessName) + { + const int MaxLaunchRetries = 3; + + Log.Comment($"Using executable: {unpackagedExeFullPath}"); + if (!File.Exists(unpackagedExeFullPath)) + { + Verify.Fail($"Executable not found at {unpackagedExeFullPath}!"); + return; + } + + for (int retries = 1; retries <= MaxLaunchRetries; ++retries) + { + try + { + Log.Comment("Attempting launch, try #{0}...", retries); + + if (String.IsNullOrEmpty(appWindowTitle)) + { + //Instead of launching the process directly it is invoked through explorer.exe, resulting in the process starting unelevated. + ProcessStartInfo unelevatedProcessStartInfo = new ProcessStartInfo(); + unelevatedProcessStartInfo.FileName = "C:\\Windows\\explorer.exe"; + unelevatedProcessStartInfo.Arguments = unpackagedExeFullPath; + + Process.Start(unelevatedProcessStartInfo); + + // Unlike the case below, there is no window to wait for here. So, as long as we didn't hit an activation error, just + // wait a couple of seconds to give the app a chance to run, then proceed to kill it, at that point the presence of the + // expected process is verified. + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + else + { + // In this case we can verify the window with the expected title is present. We currently don't perform further checks such as the window type. + UICondition WindowCondition = UICondition.CreateFromName(appWindowTitle); + + using (AppLaunchWaiter launchWaiter = new AppLaunchWaiter(WindowCondition)) + { + // Instead of launching the process directly it is invoked through explorer.exe, resulting in the process starting unelevated. + ProcessStartInfo unelevatedProcessStartInfo = new ProcessStartInfo(); + unelevatedProcessStartInfo.FileName = "C:\\Windows\\explorer.exe"; + unelevatedProcessStartInfo.Arguments = unpackagedExeFullPath; + + Process.Start(unelevatedProcessStartInfo); + + launchWaiter.Wait(); + } + } + + Log.Comment("Launch successful!"); + break; + } + catch (Exception ex) + { + Log.Comment("Failed to launch app. Exception: " + ex.ToString()); + + if (retries < MaxLaunchRetries) + { + Log.Comment("UAPApp.Launch might not have waited long enough, trying again {0}", retries); + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + else + { + Log.Error("Could not launch app {0} !", unpackagedExeFullPath); + throw; + } + } + } + + // If appWindowTitle is valid, use it to derive the target process ID to terminate. Otherwise, we fall back to + // using the appProcessName to derive the process ID. + int appWindowsProcessId = -1; + if (!String.IsNullOrEmpty(appWindowTitle)) + { + appWindowsProcessId = GetProcessIdFromAppWindow(appWindowTitle); + if (appWindowsProcessId < 0) + { + throw new Exception($"Could not find window with title: {appWindowTitle}"); + } + } + + EnsureApplicationProcessHasExited(appWindowsProcessId, appProcessName); + + if (Process != null) + { + Process.Dispose(); + Process = null; + } + return; + } + + private static void UpdateBuildArchIfNeeded() + { + if (String.IsNullOrEmpty(BuildArch)) + { + string getEnv = Environment.GetEnvironmentVariable("Build_Platform"); + if (String.IsNullOrEmpty(getEnv)) + { + BuildArch = "x64"; + } + else + { + BuildArch = getEnv; + } + Log.Comment("BuildArch updated to: " + BuildArch); + } + } + + private static void UpdateBuildConfigIfNeeded() + { + if (String.IsNullOrEmpty(BuildConfig)) + { + string getEnv = Environment.GetEnvironmentVariable("Build_Configuration"); + if (String.IsNullOrEmpty(getEnv)) + { + BuildConfig = "Release"; + } + else + { + BuildConfig = getEnv; + } + + Log.Comment("BuildConfig updated to: " + BuildConfig); + } + } + + // The prescribed fileRelativePath is expected to be relative to the root of the samples folder. See example below. + // Construct the full path by appending fileRelativePath to the samples root folder. + // Verify existence of the resulting full path. + // Similarly, use _optional_ environment variables to initialize build architecture and config. + private static string GetFullFilePathFromRelativePath(string fileRelativePath) + { + if (String.IsNullOrEmpty(fileRelativePath)) + { + Verify.Fail($"fileRelativePath cannot be null!"); + return null; + } + + // Fetch the samples root folder from environment variable on demand. + if (String.IsNullOrEmpty(SamplesRootPath)) + { + string getEnv = Environment.GetEnvironmentVariable("SAMPLES_ROOT_PATH"); + if (String.IsNullOrEmpty(getEnv)) + { + Log.Error("**** Please set environment variable SAMPLES_ROOT_PATH before running this test ****"); + Log.Error("SAMPLES_ROOT_PATH should point to a folder that contains sample folders such as AppLifecycle, PhotoEditor, ResourceManagement, etc."); + Verify.Fail($"e.g., set SAMPLES_ROOT_PATH=C:\\myRepo\\WindowsAppSDK-Samples\\Samples"); + Log.Error("You can also optionally set Build_Platform to one of { x86 | x64 | arm64 }, the default is x64."); + Log.Error($"e.g., set Build_Platform=arm64"); + Log.Error("You can also optionally set Build_Configuration to one of { Debug | Release }, the default is Release."); + Log.Error($"Build_Platform and Build_Configuration modifies the file path to the test binary targeted."); + return null; + } + + if (!Directory.Exists(getEnv)) + { + Verify.Fail($"Path {getEnv} in SAMPLES_ROOT_PATH was not found!"); + return null; + } + + SamplesRootPath = getEnv; + Log.Comment("SamplesRootPath updated to: " + SamplesRootPath); + } + + UpdateBuildArchIfNeeded(); + UpdateBuildConfigIfNeeded(); + + var fileFullPath1 = Path.Combine(SamplesRootPath, fileRelativePath); + var fileFullPath2 = fileFullPath1.Replace("[BuildArch]", BuildArch); + var fileFullPath3 = fileFullPath2.Replace("[BuildConfig]", BuildConfig); + if (!File.Exists(fileFullPath3)) + { + Verify.Fail($"File path {fileFullPath3} was not found!"); + return null; + } + + return fileFullPath3; + } + + private static class NativeMethods + { + public enum ActivateOptions + { + None = 0x00000000, // No flags set + DesignMode = 0x00000001, // The application is being activated for design mode, and thus will not be able to + // to create an immersive window. Window creation must be done by design tools which + // load the necessary components by communicating with a designer-specified service on + // the site chain established on the activation manager. The splash screen normally + // shown when an application is activated will also not appear. Most activations + // will not use this flag. + NoErrorUI = 0x00000002, // Do not show an error dialog if the app fails to activate. + NoSplashScreen = 0x00000004, // Do not show the splash screen when activating the app. + } + + public const string CLSID_ApplicationActivationManager_String = "45ba127d-10a8-46ea-8ab7-56ea9078943c"; + public const string CLSID_IApplicationActivationManager_String = "2e941141-7f97-4756-ba1d-9decde894a3d"; + + public static readonly Guid CLSID_ApplicationActivationManager = new Guid(CLSID_ApplicationActivationManager_String); + public static readonly Guid CLSID_IApplicationActivationManager = new Guid(CLSID_IApplicationActivationManager_String); + + [ComImport, Guid(CLSID_IApplicationActivationManager_String), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IApplicationActivationManager + { + // Activates the specified immersive application for the "Launch" contract, passing the provided arguments + // string into the application. Callers can obtain the process Id of the application instance fulfilling this contract. + IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId); + IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId); + IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId); + } + + [DllImport("api-ms-win-ntuser-ie-window-l1-1-0.dll", SetLastError = true)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] + public static extern UInt32 CoCreateInstance( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, + IntPtr pUnkOuter, + CLSCTX dwClsContext, + [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid, + [MarshalAs(UnmanagedType.IUnknown)] out object rReturnedComObject); + + [Flags] + public enum CLSCTX : uint + { + CLSCTX_INPROC_SERVER = 0x1, + CLSCTX_INPROC_HANDLER = 0x2, + CLSCTX_LOCAL_SERVER = 0x4, + CLSCTX_INPROC_SERVER16 = 0x8, + CLSCTX_REMOTE_SERVER = 0x10, + CLSCTX_INPROC_HANDLER16 = 0x20, + CLSCTX_RESERVED1 = 0x40, + CLSCTX_RESERVED2 = 0x80, + CLSCTX_RESERVED3 = 0x100, + CLSCTX_RESERVED4 = 0x200, + CLSCTX_NO_CODE_DOWNLOAD = 0x400, + CLSCTX_RESERVED5 = 0x800, + CLSCTX_NO_CUSTOM_MARSHAL = 0x1000, + CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000, + CLSCTX_NO_FAILURE_LOG = 0x4000, + CLSCTX_DISABLE_AAA = 0x8000, + CLSCTX_ENABLE_AAA = 0x10000, + CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000, + CLSCTX_ACTIVATE_32_BIT_SERVER = 0x40000, + CLSCTX_ACTIVATE_64_BIT_SERVER = 0x80000, + CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, + CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER, + CLSCTX_ALL = CLSCTX_SERVER | CLSCTX_INPROC_HANDLER + } + } + + // Verify that process creation of the prescribed exe by full path hits no error. + // Generally, there isn't much else we can verify in this basic scenario. + private void LaunchUnpackagedConsoleApp(string unpackagedExeFullPath) + { + Log.Comment($"Using executable: {unpackagedExeFullPath}"); + try + { + if (!File.Exists(unpackagedExeFullPath)) + { + Verify.Fail($"Executable not found at {unpackagedExeFullPath}!"); + return; + } + + ProcessStartInfo unelevatedProcessStartInfo = new ProcessStartInfo(); + unelevatedProcessStartInfo.FileName = unpackagedExeFullPath; + unelevatedProcessStartInfo.Arguments = ""; + + Process.Start(unelevatedProcessStartInfo); + + Log.Comment("Launch successful!"); + } + catch (Exception ex) + { + Log.Comment("Failed to launch app. Exception: " + ex.ToString()); + Log.Error("Could not launch app {0} !", unpackagedExeFullPath); + throw; + } + + return; + } + + // Use the Activation Manager to launch a packaged app by appName (app ID). When a valid appWindowTitle is prescribed, + // the launch isn't deemed finish until a window with expected title appWindowTitle is found. Verify that a process + // with expected name appProcessName can be found, and then the corresponding process is terminated by process ID. + // Termination of the process is also verified. + private void LaunchAndClosePackagedApp(string appName, string appWindowTitle, string appProcessName) + { + const int MaxLaunchRetries = 3; + + Log.Comment($"Using NonUWPApp: {appName}"); + + for (int retries = 1; retries <= MaxLaunchRetries; ++retries) + { + try + { + Log.Comment("Attempting launch, try #{0}...", retries); + + if (NativeMethods.CoCreateInstance( + NativeMethods.CLSID_ApplicationActivationManager, + IntPtr.Zero, + NativeMethods.CLSCTX.CLSCTX_LOCAL_SERVER, + NativeMethods.CLSID_IApplicationActivationManager, + out object applicationActivationManagerAsObject) != 0) + { + throw new Exception("Failed to create ApplicationActivationManager!"); + } + + if (String.IsNullOrEmpty(appWindowTitle)) + { + var applicationActivationManager = (NativeMethods.IApplicationActivationManager)applicationActivationManagerAsObject; + applicationActivationManager.ActivateApplication(appName, null, NativeMethods.ActivateOptions.None, out uint processId); + + // Unlike the case below, there is no window to wait for here. So, as long as we didn't hit an activation error, just + // wait a couple of seconds to give the app a chance to run, proceed to kill it. + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + else + { + UICondition WindowCondition = UICondition.CreateFromName(appWindowTitle); + UIObject window; + using (AppLaunchWaiter launchWaiter = new AppLaunchWaiter(WindowCondition)) + { + var applicationActivationManager = (NativeMethods.IApplicationActivationManager)applicationActivationManagerAsObject; + applicationActivationManager.ActivateApplication(appName, null, NativeMethods.ActivateOptions.None, out uint processId); + + launchWaiter.Wait(); + window = launchWaiter.Source; + } + + NativeMethods.SetForegroundWindow(window.NativeWindowHandle); + } + + Log.Comment("Launch successful!"); + break; + } + catch (Exception ex) + { + Log.Comment("Failed to launch app. Exception: " + ex.ToString()); + + if (retries < MaxLaunchRetries) + { + Log.Comment("UAPApp.Launch might not have waited long enough, trying again {0}", retries); + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + else + { + Log.Error("Could not launch app {0} !", appName); + throw; + } + } + } + + int appWindowsProcessId = -1; + if (!String.IsNullOrEmpty(appWindowTitle)) + { + // appWindowTitle being valid means we would have waited for the app's window with the expected title at launch above. + // Hence, putting the logic below in a retry loop probably won't help. + appWindowsProcessId = GetProcessIdFromAppWindow(appWindowTitle); + if (appWindowsProcessId < 0) + { + throw new Exception($"Could not find window with title: {appWindowTitle}"); + } + } + + EnsureApplicationProcessHasExited(appWindowsProcessId, appProcessName); + + if (Process != null) + { + Process.Dispose(); + Process = null; + } + return; + } + + // Invoke CreateProcess to run exeName with arguments. Wiat for completion. + // Verify no error is returned. + private static void RunCommand(string exeName, string arguments) + { + Log.Comment("Running the following command line: " + exeName + " " + arguments); + + Process process = new Process(); + ProcessStartInfo psi = new ProcessStartInfo(); + + psi.FileName = exeName; + if (arguments != null) + { + psi.Arguments = arguments; + } + psi.UseShellExecute = false; + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + + process.StartInfo = psi; + process.Start(); + string stdout = process.StandardOutput.ReadToEnd(); + string stderr = process.StandardError.ReadToEnd(); + + // Ensure things went smoothly + Verify.IsTrue(process.WaitForExit(TIMEOUT), "Waiting for " + exeName + " to finish"); + Log.Comment("Tool std output: " + stdout); + Log.Comment("Tool error output: " + stderr); + + // Ensure we dont fail for any commands + Verify.IsTrue((process.ExitCode == 0), "Expected an exit code of 0, and got back " + process.ExitCode); + process.Dispose(); + } + + private static void RunPowerShellCommand(string arguments) + { + RunCommand("powershell.exe ", arguments); + } + + private static void RemoveAppxPackage(string packageFullNameTempalte) + { + var packageFullName = packageFullNameTempalte.Replace("[BuildArch]", BuildArch); + RunPowerShellCommand("remove-appxpackage " + packageFullName); + } + + private static void RegisterAppxPackage(string manifestRelativePath) + { + var manifestFullPath = GetFullFilePathFromRelativePath(manifestRelativePath); + + // Powershell needs single quotes around the path in order to cope with spaces in the path. + var manifestFullPathQuoted = "\'" + manifestFullPath + "\'"; + + Log.Comment("manifestFullPathQuoted: " + manifestFullPathQuoted); + RunPowerShellCommand("add-appxpackage " + "-register " + manifestFullPathQuoted); + } + + private bool IsArchX86() + { + UpdateBuildArchIfNeeded(); + return ((BuildArch == "x86") || (BuildArch == "X86")); + } + + private bool IsArchX64() + { + UpdateBuildArchIfNeeded(); + return ((BuildArch == "x64") || (BuildArch == "X64")); + } + + private bool IsBuildConfigDebug() + { + UpdateBuildConfigIfNeeded(); + return ((BuildConfig == "debug") || (BuildConfig == "Debug")); + } + + [ClassInitialize] + [TestProperty("RunAs", "User")] + public static void ClassInitialize(TestContext testContext) + { + return; + } + + [TestCleanup] + public void TestCleanup() + { + return; + } + + [ClassCleanupAttribute] + public static void ClassCleanup() + { + return; + } + +/* + Adding a new Sample app + +- If you are adding a new Sample app to an existing feature area, please find the existing feature area below and add it there. +- Sections are marked by banners that look like "**** XYZ sample apps ***". +- If you are adding a new feature area, please start a new section using existing feature areas, such as "SelfContainedDeployment", + as example. Add new test cses for the Sample apps in the new section. +- Feature areas that currently only have 1 Sample app are grouped at the end in the "Other" section. Feel free to start a new + section if a feature area there is getting more than 1 sample app. +- There is typically one test method per Sample app. Please use the feature area as the prefix of the name of the new test method, + followed by the name/ID of the Sample app. Please see examples below. +- Currently, no Sample apps are being built in the pipeline for x86, hence, your sample app test method typically will have a "if + IsArchX86() then skip the test" check at top. You should be able to find examples below. +- If your Sample app does not support arm64, you might want to use a "if (!IsArchX64()) then skip the test" check at top. Again, + there are examples below. +- If your Sample app is "release only", then you might want to have a "if (IsBuildConfigDebug()) then skip the test" check. +- If your Sample app does not support older Windows version, please use Environment.OSVersion.Version.* to skip your test as needed. + Examples are below. +- Please keep in mind that this TAEF test might be run manually on a local machine, so configuration permutations currently not + being exercised in the pipeline can happen, and your test method should handle that. +- If your Sample app is a _unpackaged_ + - console app (i.e., no UI), please use LaunchUnpackagedConsoleApp() to verify its launch, where is the relative + path to the .exe with your feature area as the root. Please see examples below. + - app that pops a window, please use LaunchAndCloseUnpackagedApp(, , ) to verify its launch. + Please see examples below. + - app that does not pop a window but a process is still expected to be running after launch, please use LaunchAndCloseUnpackagedApp( + , null, ) to verify its launch. Please see examples below. +- If your Sample app is a _packaged_ app + - that pops a window, please use LaunchAndClosePackagedApp(, , ) to verify its launch. + - The packaged app should be explicitly registered using RegisterAppxPackage() and removed using RemoveAppxPackage( + ). Please see examples below. + - that does not pop a window, please use LaunchAndClosePackagedApp(, null, ) to verify its launch. +- Even if for whatever reason a packaged sample app cannot be verified for launch, there is still merit to automate verification of + the Register and Remove of the package. Please see examples below. +- Please strive to maximize the degree of verification for your Sample app. Add runtime checks for your app after launch if applicable. + +*/ + + /**** SelfContainedDeployment sample apps ****/ + + // For unpackaged console apps, generally we verify that launch of the app by full path to the exe does not return an error. + [TestMethod] + public void SelfContainedDeploymentCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("SelfContainedDeployment\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\SelfContainedDeployment.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + [TestProperty("Ignore", "True")] // Temporarily skipping this due to http://task.ms/56699769 + public void SelfContainedDeploymentCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("SelfContainedDeployment\\cs\\cs-console-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\win10-[BuildArch]\\SelfContainedDeployment.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + [TestProperty("Ignore", "True")] // Temporarily skipping this due to http://task.ms/56699769 + public void SelfContainedDeploymentCsWpfUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("SelfContainedDeployment\\cs\\cs-wpf-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.17763.0\\win10-[BuildArch]\\SelfContainedDeployment.exe"); + LaunchAndCloseUnpackagedApp(exePath, "MainWindow", "SelfContainedDeployment.exe"); + return; + } + + /**** AppLifecycle sample apps ****/ + + [TestMethod] + public void AppLifecycleActivationCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinRtConsoleActivation.exe"); + + // TODO: Even when launched in VS, this app is hitting "Error 0x80070491 in MddBootstrapInitialize(0x00010000, , 0.0.0.0)". + // Therefore, resorting to minimal verification for now, improve on this in the future. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + // TODO: This sample app quickly exits after launch, with message: "CsConsoleActivation.exe (process 11628) exited with code 0.", hence not much verification is + // being done currently. Consider improving on that. Same for the next few apps. + [TestMethod] + public void AppLifecycleActivationCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cs\\cs-console-unpackaged\\CsConsoleActivation\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsConsoleActivation.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleActivationCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cpp\\cpp-win32-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinMainActivation.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleActivationCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cs\\cs-winforms-unpackaged\\CsWinFormsActivation\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWinFormsActivation.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleActivationCsWpfUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Activation\\cs\\cs-wpf-unpackaged\\CsWpfActivation\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWpfActivation.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + // For unpackaged apps that does not put up a window, generally we verify that + // 1) launch of the app by full path to the exe does not return an error, + // 2) a process with the expected name is present, + // 3) processes associated with the app can be terminated. + // TODO: Missing WASDK 1.2 is blocking WinCppWinRtConsoleEnv.exe from activating the app's main window? Temporarily skip looking for the app's main + // window, but still look for the expected process name to terminate. Same for the next 2 sample apps. + [TestMethod] + public void AppLifecycleEnvironmentVariablesCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\EnvironmentVariables\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinRtConsoleEnv.exe"); + LaunchAndCloseUnpackagedApp(exePath, null, "CppWinRtConsoleEnv.exe"); + return; + } + + [TestMethod] + public void AppLifecycleEnvironmentVariablesCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\EnvironmentVariables\\cpp-win32-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinMainEnv.exe"); + LaunchAndCloseUnpackagedApp(exePath, null, "CppWinMainEnv.exe"); + return; + } + + [TestMethod] + public void AppLifecycleEnvironmentVariablesCsWinfromsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + if (!IsArchX64()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, + "This Sample app is currently failing to launch on arm64fre builds due to MSVCP140.dll NOT FOUND error."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\EnvironmentVariables\\cs-winforms-unpackaged\\CsWinFormsEnv\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWinFormsEnv.exe"); + LaunchAndCloseUnpackagedApp(exePath, null, "CsWinFormsEnv.exe"); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinRtConsoleInstancing.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cs\\cs-console-unpackaged\\CsConsoleInstancing\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsConsoleInstancing.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cpp\\cpp-win32-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinMainInstancing.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cs\\cs-winforms-unpackaged\\CsWinFormsInstancing\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWinFormsInstancing.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleInstancingCsWpfUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\Instancing\\cs\\cs-wpf-unpackaged\\CsWpfInstancing\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWpfInstancing.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinRtConsoleState.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cs\\cs-console-unpackaged\\CsConsoleState\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsConsoleState.exe"); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cpp\\cpp-win32-unpackaged\\[BuildArch]\\[BuildConfig]\\CppWinMainState.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cs\\cs-winforms-unpackaged\\CsWinFormsState\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWinFormsState.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void AppLifecycleStateNotificationsCsWpfUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("AppLifecycle\\StateNotifications\\cs\\cs-wpf-unpackaged\\CsWpfState\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\CsWpfState.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + // [TestMethod] + // public void AppLifecycleInstancingCppWin32Packaged() + // { + // if (IsArchX86() || IsBuildConfigDebug()) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + // return; + // } + + // Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + // if ((Environment.OSVersion.Version.Major < 10) || ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build < 19041))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This test requires Win 10 Build 19041 or higher."); + // return; + // } + + // if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + // return; + // } + + // RegisterAppxPackage("AppLifecycle\\Instancing\\cpp\\cpp-win32-packaged\\CppWinMainInstancingPkg\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + // LaunchAndClosePackagedApp("d9bcd10a-b42d-4e1e-9656-a2284d39e12d_s9y1p3hwd5qda!App", "CppWinMainInstancing", "CppWinMainInstancing.exe"); + // RemoveAppxPackage("d9bcd10a-b42d-4e1e-9656-a2284d39e12d_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + // return; + // } + + [TestMethod] + public void AppLifecycleInstancingCsWpfPackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major < 10) || ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build < 19041))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This test requires Win 10 Build 19041 or higher."); + return; + } + + if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + return; + } + + RegisterAppxPackage("AppLifecycle\\Instancing\\cs1\\cs-wpf-packaged\\CsWpfInstancingPkg\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + LaunchAndClosePackagedApp("1b900d33-3e06-4f3e-9797-2fc2ffebb6f1_s9y1p3hwd5qda!App", "CsWpfInstancingPkg", "CsWpfInstancing.exe"); + RemoveAppxPackage("1b900d33-3e06-4f3e-9797-2fc2ffebb6f1_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + return; + } + + // [TestMethod] + // public void AppLifecycleStateNotificationsCppWin32Packaged() + // { + // if (IsArchX86() || IsBuildConfigDebug()) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + // return; + // } + + // Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + // if ((Environment.OSVersion.Version.Major < 10) || ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build < 19041))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This test requires Win 10 Build 19041 or higher."); + // return; + // } + + // if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + // return; + // } + + // RegisterAppxPackage("AppLifecycle\\StateNotifications\\cpp\\cpp-win32-packaged\\CppWinMainStatePkg\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + // LaunchAndClosePackagedApp("e0d8ad55-f3f2-4d7f-9182-3ee6905208a8_s9y1p3hwd5qda!App", "CppWinMainState", "CppWinMainState.exe"); + // RemoveAppxPackage("e0d8ad55-f3f2-4d7f-9182-3ee6905208a8_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + // return; + // } + + [TestMethod] + public void AppLifecycleStateNotificationsCsWpfPackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major < 10) || ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build < 19041))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This test requires Win 10 Build 19041 or higher."); + return; + } + + if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + return; + } + + RegisterAppxPackage("AppLifecycle\\StateNotifications\\cs1\\cs-wpf-packaged\\CsWpfStatePkg\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + LaunchAndClosePackagedApp("7718eec3-5345-4d4e-815f-97df705dc89c_s9y1p3hwd5qda!App", "C# WPF Activation", "CsWpfState.exe"); + RemoveAppxPackage("7718eec3-5345-4d4e-815f-97df705dc89c_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + return; + } + + /**** ResourceManagement sample apps ****/ + + // TODO: Missing WASDK 1.2 is blocking Unpackaged.exe from activating? Skip looking for the app's main window, but still + // look for the expected process name to terminate. Same for the next app. + [TestMethod] + public void ResourceManagementCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("ResourceManagement\\cpp\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\console_unpackaged_app.exe"); + + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build == 22000)) + { + Log.Comment("This test is currently failing on the 21h2 Enterprise Multi-Session OS image seemingly because a Welcome to MS Teams! window is on top. Doing minimal activation checking until this is resolved."); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + LaunchAndCloseUnpackagedApp(exePath, null, "console_unpackaged_app.exe"); + return; + } + + [TestMethod] + public void ResourceManagementCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + if (!IsArchX64()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, + "This Sample app is currently failing to launch on arm64fre builds due to MSVCP140.dll NOT FOUND error."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("ResourceManagement\\cs\\cs-winforms-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\winforms_unpackaged_app.exe"); + LaunchAndCloseUnpackagedApp(exePath, null, "winforms_unpackaged_app.exe"); + return; + } + + [TestMethod] + public void ResourceManagementCsWpfPackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + return; + } + + RegisterAppxPackage("ResourceManagement\\cs1\\cs-wpf\\wpf_packaged_app (Package)\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + LaunchAndClosePackagedApp("25dee5b5-8e25-431f-a644-e75d98163029_s9y1p3hwd5qda!App", "MRT Core WPF sample", "wpf_packaged_app.exe"); + RemoveAppxPackage("25dee5b5-8e25-431f-a644-e75d98163029_1.0.0.0_[BuildArch]__s9y1p3hwd5qda"); + return; + } + + /**** Windowing sample apps ****/ + + [TestMethod] + public void WindowingCsWinformsUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + if (!IsArchX64()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, + "This Sample app is currently failing to launch on arm64fre builds due to MSVCP140.dll NOT FOUND error."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Windowing\\cs\\cs-winforms-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\winforms_unpackaged_app.exe"); + + // TODO: Missing WASDK 1.2 is blocking winforms_unpackaged_app.exe from activating the app's main window? Temporarily skip looking for the app's main + // window, but still look for the expected process name to terminate. + LaunchAndCloseUnpackagedApp(exePath, null, "winforms_unpackaged_app.exe"); + return; + } + + [TestMethod] + public void WindowingCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Windowing\\cpp\\cpp-win32\\[BuildArch]\\[BuildConfig]\\Windowing.exe"); + + // TODO: Missing WASDK 1.2 is blocking Windowing.exe from activating the app's main window? Temporarily skip looking for the app's main + // window, but still look for the expected process name to terminate. + LaunchAndCloseUnpackagedApp(exePath, null, "Windowing.exe"); + return; + } + + /**** Unpackaged sample apps ****/ + + // TODO: Missing WASDK 1.2 is blocking Unpackaged.exe from activating? Skip looking for the app's main window, but still + // look for the expected process name to terminate. Same for the next app. + [TestMethod] + public void UnpackagedCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Unpackaged\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\Unpackaged.exe"); + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build == 22000)) + { + Log.Comment("This test is currently failing on the 21h2 Enterprise Multi-Session OS image seemingly because a Welcome to MS Teams! window is on top. Doing minimal activation checking until this is resolved."); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + LaunchAndCloseUnpackagedApp(exePath, null, "Unpackaged.exe"); + return; + } + + [TestMethod] + public void UnpackagedCsConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Unpackaged\\cs-console-unpackaged\\bin\\[BuildArch]\\[BuildConfig]\\net6.0-windows10.0.19041.0\\Unpackaged.exe"); + LaunchUnpackagedConsoleApp(exePath); + + // TODO: The following stronger verification seems to work fine on a local machine but not in the Azure pipeline. Try harder to enable it. + // LaunchAndCloseUnpackagedApp(exePath, null, "Unpackaged.exe"); + return; + } + + /**** Notifications sample apps ****/ + + [TestMethod] + public void NotificationsPushCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Notifications\\Push\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\cpp-console-unpackaged.exe"); + + // TODO: Missing WASDK 1.2 is blocking CppUnpackagedAppNotifications.exe from activating? Skip looking for the app's main window, but still + // look for the expected process name to terminate. Same for the next app. + LaunchAndCloseUnpackagedApp(exePath, null, "cpp-console-unpackaged.exe"); + return; + } + + // For packaged console apps which does not put up a window, generally we verify that + // 1) deployment of the app's package is successful, + // 2) launch of the app by appName (appID) does not return an error, + // 3) a process with the expected name is present, + // 4) processes associated with the app can be terminated. + // 5) the app's package can be successfully removed. + // [TestMethod] + // public void NotificationsPushCppConsolePackaged() + // { + // if (IsArchX86() || IsBuildConfigDebug()) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + // return; + // } + + // if ((Environment.OSVersion.Version.Major == 10) && ((Environment.OSVersion.Version.Build == 19044) || (Environment.OSVersion.Version.Build == 19045))) + // { + // WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "Skipping this test on LTSC 2021 (19044) and 22h2 (19045) until b#53993352 is fixed."); + // return; + // } + // if ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Minor == 0) && ((Environment.OSVersion.Version.Build == 17763))) + // { + // // https://task.ms/54900647 + // Log.Comment("This test is currently failing on the Win 10 Server 2019 OS image. Skipping this launch test to unblock exercising of the image until b#54900647 is fixed."); + // return; + // } + + // RegisterAppxPackage("Notifications\\Push\\cpp-console-packaged\\cpp-console-package\\bin\\[BuildArch]\\[BuildConfig]\\AppxManifest.xml"); + + // // Upon launch, the app throws the following error message, which looks expected: + // // There was an error obtaining the WNS Channel URI + // // The remoteId has not been set.Refer to the readme file accompanying this sample + // // for the instructions on how to obtain and setup a remote id + // // Press 'Enter' at any time to exit App. + // // + // // Therefore, for this app we won't wait for a window with a specific title. Just proceed to look + // // for the expected process name to terminate. + // // + // // TODO: consider automating the above setup steps so that we can verify the main app window is present. + // LaunchAndClosePackagedApp("PushNotificationsSample_ph1m9x8skttmg!App", null, "cpp-console.exe"); + + // RemoveAppxPackage("PushNotificationsSample_1.0.0.0_[BuildArch]__ph1m9x8skttmg"); + // return; + // } + + /**** Other sample apps ****/ + + [TestMethod] + public void InstallerCppConsoleUnpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Installer\\cpp-console-unpackaged\\[BuildArch]\\[BuildConfig]\\LaunchInstaller.exe"); + Log.Comment($"OSVersion {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}.{Environment.OSVersion.Version.Build}"); + if ((Environment.OSVersion.Version.Major == 10) && (Environment.OSVersion.Version.Build == 22000)) + { + Log.Comment("This test is currently failing on the 21h2 Enterprise Multi-Session OS image seemingly because a Welcome to MS Teams! window is on top. Doing minimal activation checking until this is resolved."); + LaunchUnpackagedConsoleApp(exePath); + return; + } + + LaunchAndCloseUnpackagedApp(exePath, null, "LaunchInstaller.exe"); + return; + } + + [TestMethod] + public void InsightsCppWin32Unpackaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Insights\\cpp-win32\\[BuildArch]\\[BuildConfig]\\Insights.exe"); + + // This sample app seems to behave like a console app. + LaunchUnpackagedConsoleApp(exePath); + return; + } + + [TestMethod] + public void MicaCppWin32Packaged() + { + if (IsArchX86() || IsBuildConfigDebug()) + { + WEX.Logging.Interop.Log.Result(WEX.Logging.Interop.TestResult.Skipped, "This Sample app is currently not being tested when platform=x86 or config=Debug."); + return; + } + + var exePath = GetFullFilePathFromRelativePath("Mica\\cpp-win32\\[BuildArch]\\[BuildConfig]\\WinAppSDKMicaSample.exe"); + + // TODO: Missing WASDK 1.2 is blocking cpp-console-unpackaged.exe from activating? Skip looking for the app's main window, but still + // look for the expected process name to terminate. + LaunchAndCloseUnpackagedApp(exePath, null, "WinAppSDKMicaSample.exe"); + return; + } + } +} diff --git a/test/inc/WindowsAppRuntime.Test.Metadata.h b/test/inc/WindowsAppRuntime.Test.Metadata.h index 58e58dca27..e39fdd828f 100644 --- a/test/inc/WindowsAppRuntime.Test.Metadata.h +++ b/test/inc/WindowsAppRuntime.Test.Metadata.h @@ -13,7 +13,7 @@ #define WINDOWSAPPRUNTIME_TEST_MSIX_PUBLISHERID L"8wekyb3d8bbwe" -#define WINDOWSAPPRUNTIME_TEST_MSIX_FRAMEWORK_PACKAGE_NAME L"Microsoft.WindowsAppRuntime.Framework.4.1" +#define WINDOWSAPPRUNTIME_TEST_MSIX_FRAMEWORK_PACKAGE_NAME L"Microsoft.WindowsAppRuntime.4.1" #define WINDOWSAPPRUNTIME_TEST_MSIX_DDLM_PACKAGE_NAME L"WindowsAppRuntime.Test.DDLM" #define WINDOWSAPPRUNTIME_TEST_MSIX_MAIN_PACKAGE_NAME L"WindowsAppRuntime.Test.DynDep.DataStore.4.1" #define WINDOWSAPPRUNTIME_TEST_MSIX_SINGLETON_PACKAGE_NAME L"WindowsAppRuntime.Test.Singleton" diff --git a/test/inc/WindowsAppRuntime.Test.Package.h b/test/inc/WindowsAppRuntime.Test.Package.h index 71cb40035d..d8a6d319e0 100644 --- a/test/inc/WindowsAppRuntime.Test.Package.h +++ b/test/inc/WindowsAppRuntime.Test.Package.h @@ -21,14 +21,14 @@ #define WINDOWSAPPRUNTIME_TEST_MSIX_PUBLISHERID L"8wekyb3d8bbwe" -#define WINDOWSAPPRUNTIME_TEST_MSIX_FRAMEWORK_PACKAGE_NAME L"Microsoft.WindowsAppRuntime.Framework.4.1" +#define WINDOWSAPPRUNTIME_TEST_MSIX_FRAMEWORK_PACKAGE_NAME L"Microsoft.WindowsAppRuntime.4.1" #define WINDOWSAPPRUNTIME_TEST_MSIX_DDLM_PACKAGE_NAME L"WindowsAppRuntime.Test.DDLM" #define WINDOWSAPPRUNTIME_TEST_MSIX_MAIN_PACKAGE_NAME L"WindowsAppRuntime.Test.DynDep.DataStore.4.1" #define WINDOWSAPPRUNTIME_TEST_MSIX_SINGLETON_PACKAGE_NAME L"WindowsAppRuntime.Test.Singleton" #define WINDOWSAPPRUNTIME_TEST_MSIX_DEPLOYMENT_FRAMEWORK_PACKAGE_NAME L"Microsoft.WindowsAppRuntime.1.0-Test" -#define WINDOWSAPPRUNTIME_TEST_MSIX_DEPLOYMENT_MAIN_PACKAGE_NAME L"Microsoft.WindowsAppRuntime.Main.1.0-Test" -#define WINDOWSAPPRUNTIME_TEST_MSIX_DEPLOYMENT_SINGLETON_PACKAGE_NAME L"Microsoft.WindowsAppRuntime.Singleton-Test" +#define WINDOWSAPPRUNTIME_TEST_MSIX_DEPLOYMENT_MAIN_PACKAGE_NAME L"MicrosoftCorporationII.WinAppRuntime.Main.1.0-T" +#define WINDOWSAPPRUNTIME_TEST_MSIX_DEPLOYMENT_SINGLETON_PACKAGE_NAME L"MicrosoftCorporationII.WinAppRuntime.Singleton-T" #define WINDOWSAPPRUNTIME_TEST_PACKAGE_DDLM_NAMEPREFIX L"WindowsAppRuntime.Test.DDLM" #define WINDOWSAPPRUNTIME_TEST_PACKAGE_DDLM_VERSION WINDOWSAPPRUNTIME_TEST_METADATA_VERSION @@ -85,7 +85,7 @@ namespace WindowsAppRuntimeFramework { constexpr PCWSTR c_PackageDirName = L"Microsoft.WindowsAppRuntime.Framework"; constexpr PCWSTR c_PackageMsixFilename = L"Microsoft.WindowsAppRuntime.Framework.msix"; - constexpr PCWSTR c_PackageNamePrefix = L"Microsoft.WindowsAppRuntime.Framework"; + constexpr PCWSTR c_PackageNamePrefix = L"Microsoft.WindowsAppRuntime"; constexpr PCWSTR c_PackageFamilyName = WINDOWSAPPRUNTIME_TEST_MSIX_FRAMEWORK_PACKAGE_NAME L"_" WINDOWSAPPRUNTIME_TEST_MSIX_PUBLISHERID; constexpr PCWSTR c_PackageFullName = WINDOWSAPPRUNTIME_TEST_MSIX_FRAMEWORK_PACKAGE_NAME L"_" WINDOWSAPPRUNTIME_TEST_METADATA_VERSION_STRING L"_neutral__" WINDOWSAPPRUNTIME_TEST_MSIX_PUBLISHERID; } diff --git a/tools/DevCheck/DevCheck.ps1 b/tools/DevCheck/DevCheck.ps1 index 5443c5cdf7..fc810f8e7f 100644 --- a/tools/DevCheck/DevCheck.ps1 +++ b/tools/DevCheck/DevCheck.ps1 @@ -55,6 +55,9 @@ .PARAMETER FixLongPath Enable LongPath support if necessary. +.PARAMETER InstallVCLibs + Install VCLibs MSIX packages. + .PARAMETER InstallWindowsSDK Download and install Windows Platform SDKs (if necessary). @@ -145,6 +148,8 @@ Param( [Switch]$CheckTestPfx=$false, + [Switch]$CheckVCLibs=$false, + [Switch]$CheckVisualStudio=$false, [Switch]$CheckWindowsSDK=$false, @@ -155,6 +160,8 @@ Param( [Switch]$FixLongPath=$false, + [Switch]$InstallVCLibs=$false, + [Switch]$InstallWindowsSDK=$false, [Switch]$NoInteractive=$false, @@ -233,6 +240,8 @@ $global:windows_sdks = (('10.0.17763.0', 'https://go.microsoft.com/fwlink/p/?Lin $global:nuget_restore_filenames = ('.') #-------------------------------------- +$null = [Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') + function Get-Issues { return $global:issues + $global:issues_valid_test_pfx_thumbprint_not_found + $global:issues_valid_test_certificate_thumbprint_not_found + $global:issues_nuget_exe_not_found @@ -836,6 +845,139 @@ function Test-WindowsSDKInstall return $found } +function Install-VCLibsAppx +{ + param( + [string]$file, + [string]$name, + [string]$architecture + ) + + $install_issues = 0 + + $packages = Get-AppxPackage $name | Where-Object Architecture -eq $architecture + if (-not $packages) + { + Write-Host "...$name $($architecture) not installed" + $install_issues++ + $identity = $null + } + else + { + $zip = [IO.Compression.ZipFile]::OpenRead($file) + $stream = $zip.GetEntry('AppxManifest.xml').Open() + $reader = New-Object IO.StreamReader($stream) + $manifest = $reader.ReadToEnd() + $reader.Close() + $stream.Close() + $zip.Dispose() + $xml = [xml]$manifest + $identity = $xml.documentElement.Identity + $appx_version = $identity.Version + $appx_version_fields = Parse-DotQuadVersion $appx_version + $appx_version_comparable = "{0:X04}.{1:X04}.{2:X04}.{3:X04}" -f $appx_version_fields + + $max_found_version = $null + $max_found_version_comparable = $null + ForEach ($package in $packages) + { + if (($max_found_version_comparable -eq $null) -or ($max_found_version_comparable -lt $appx_version)) + { + $max_found_version = $package.Version + $version_fields = Parse-DotQuadVersion $max_found_version + $max_found_version_comparable = "{0:X04}.{1:X04}.{2:X04}.{3:X04}" -f $version_fields + } + } + if ($max_found_version) + { + if ($max_found_version_comparable -ge $appx_version_comparable) + { + Write-Host "...$($name) $($architecture): Latest version $($max_found_version) installed" + } + else + { + Write-Host "...$($name) $($architecture): $($max_found_version) installed but newer version $($appx_version) available" + $install_issues++ + } + } + } + + if ($InstallVCLibs) + { + if ($install_issues) + { + if ($identity) + { + Write-Host "...Installing $name $($architecture) $($appx_version)..." + } + else + { + Write-Host "...Installing $name $($architecture)..." + } + Add-AppxPackage $file + } + } + else + { + $global:issues += $install_issues + } + return $install_issues -eq 0 +} + +function Install-VCLibs +{ + $extension_sdks = Join-Path ${Env:ProgramFiles(x86)} 'Microsoft SDKs\Windows Kits\10\ExtensionSDKs' + $path = Join-Path $extension_sdks 'Microsoft.VCLibs\14.0\Appx' + $path_desktop = Join-Path $extension_sdks 'Microsoft.VCLibs.Desktop\14.0\Appx' + $found = Test-Path $path -PathType Container + $found_desktop = Test-Path $path_desktop -PathType Container + if (-not $found) + { + Write-Host "...ERROR: Microsoft.VCLibs.*.14.00.appx not found or valid." -ForegroundColor Red -BackgroundColor Black + $global:issues++ + } + if (-not $found_desktop) + { + Write-Host "...ERROR: Microsoft.VCLibs.*.14.00.Desktop.appx not found or valid." -ForegroundColor Red -BackgroundColor Black + $global:issues++ + } + if ((-not $found) -or (-not $found_desktop)) + { + return $false + } + + Write-Host "Installing VCLibs MSIX packages..." + $cpu = Get-CpuArchitecture + + Install-VCLibsAppx (Join-Path $path 'Retail\x86\Microsoft.VCLibs.x86.14.00.appx') 'Microsoft.VCLibs.140.00' 'x86' + Install-VCLibsAppx (Join-Path $path 'Debug\x86\Microsoft.VCLibs.x86.Debug.14.00.appx') 'Microsoft.VCLibs.140.00.Debug' 'x86' + Install-VCLibsAppx (Join-Path $path_desktop 'Retail\x86\Microsoft.VCLibs.x86.14.00.Desktop.appx') 'Microsoft.VCLibs.140.00.UWPDesktop' 'x86' + Install-VCLibsAppx (Join-Path $path_desktop 'Debug\x86\Microsoft.VCLibs.x86.Debug.14.00.Desktop.appx') 'Microsoft.VCLibs.140.00.Debug.UWPDesktop' 'x86' + + if (($cpu -eq 'x64') -or ($cpu -eq 'arm64')) + { + Install-VCLibsAppx (Join-Path $path 'Retail\x64\Microsoft.VCLibs.x64.14.00.appx') 'Microsoft.VCLibs.140.00' 'x64' + Install-VCLibsAppx (Join-Path $path 'Debug\x64\Microsoft.VCLibs.x64.Debug.14.00.appx') 'Microsoft.VCLibs.140.00.Debug' 'x64' + Install-VCLibsAppx (Join-Path $path_desktop 'Retail\x64\Microsoft.VCLibs.x64.14.00.Desktop.appx') 'Microsoft.VCLibs.140.00.UWPDesktop' 'x64' + Install-VCLibsAppx (Join-Path $path_desktop 'Debug\x64\Microsoft.VCLibs.x64.Debug.14.00.Desktop.appx') 'Microsoft.VCLibs.140.00.Debug.UWPDesktop' 'x64' + } + + if ($cpu -eq 'arm64') + { + Install-VCLibsAppx (Join-Path $path 'Retail\arm64\Microsoft.VCLibs.arm64.14.00.appx') 'Microsoft.VCLibs.140.00' 'arm64' + Install-VCLibsAppx (Join-Path $path 'Debug\arm64\Microsoft.VCLibs.arm64.Debug.14.00.appx') 'Microsoft.VCLibs.140.00.Debug' 'arm64' + Install-VCLibsAppx (Join-Path $path_desktop 'Retail\arm64\Microsoft.VCLibs.arm64.14.00.Desktop.appx') 'Microsoft.VCLibs.140.00.UWPDesktop' 'arm64' + Install-VCLibsAppx (Join-Path $path_desktop 'Debug\arm64\Microsoft.VCLibs.arm64.Debug.14.00.Desktop.appx') 'Microsoft.VCLibs.140.00.Debug.UWPDesktop' 'arm64' + } + + return $true +} + +function Test-VCLibsInstall +{ + $null = Install-VCLibs +} + function Test-DevTestPfx { if ($Clean -eq $true) @@ -1875,7 +2017,7 @@ $null = Get-UserSettings $remove_any = ($RemoveAll -eq $true) -or ($RemoveTaefService -eq $true) -or ($RemoveTestCert -eq $true) -or ($RemoveTestCert -eq $true) if (($remove_any -eq $false) -And ($CheckTAEFService -eq $false) -And ($StartTAEFService -eq $false) -And ($StopTAEFService -eq $false) -And ($CheckTestCert -eq $false) -And ($CheckTestPfx -eq $false) -And - ($CheckVisualStudio -eq $false) -And ($CheckWindowsSDK -eq $false) -And + ($CheckVCLibs -eq $false) -And ($CheckVisualStudio -eq $false) -And ($CheckWindowsSDK -eq $false) -And ($CheckDependencies -eq $false) -And ($SyncDependencies -eq $false) -And ($CheckNugetExe -eq $false) -And ($NugetExeUpdate -eq $false) -And ($CheckDeveloperMode -eq $false) -And ($ShowSystemInfo -eq $false)) @@ -1889,6 +2031,7 @@ if ($SyncDependencies -eq $true) if ($FixAll -eq $true) { $FixLongPath = $true + $InstallVCLibs = $true $InstallWindowsSDK = $true } @@ -1915,6 +2058,11 @@ if (($CheckAll -ne $false) -Or ($CheckWindowsSDK -ne $false)) } } +if (($CheckAll -ne $false) -Or ($CheckVCLibs -ne $false)) +{ + $ok = Test-VCLibsInstall +} + if (($CheckAll -ne $false) -Or ($CheckVisualStudio -ne $false)) { $ok = Test-VisualStudio2022Install