commit eac0b6718df18dd714d9ff6a709d7b54f2e5fe2f parent a5eec1646e96f68109b978ec3fe1a9eec31252d2 Author: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 23 Apr 2020 13:20:08 -0700 Add E2E tests (#85) Also enabled x86 tests. Diffstat:
47 files changed, 2597 insertions(+), 143 deletions(-)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml @@ -48,6 +48,12 @@ jobs: inputs: restoreSolution: '$(solution)' + - task: DotNetCoreCLI@2 + displayName: DotNet Restore + inputs: + command: 'restore' + projects: '**/*.csproj' + - task: PowerShell@2 displayName: Update Binary Version condition: not(eq(variables['Build.Reason'], 'PullRequest')) @@ -62,33 +68,36 @@ jobs: platform: 'x86' solution: '$(solution)' configuration: '$(buildConfiguration)' - msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Always /p:UapAppxPackageBuildMode=StoreUpload' + msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)" + /p:AppxPackageDir="$(appxPackageDir)" + /p:AppxBundle=Always + /p:UapAppxPackageBuildMode=StoreUpload' - task: CmdLine@2 - displayName: Run Tests Unpackaged + displayName: Run Unit Tests Unpackaged x64 inputs: script: | - AppInstallerCLITests.exe -logto AICLI-Unpackaged.log -s -r junit -o TEST-AppInstallerCLI-Unpackaged.xml + AppInstallerCLITests.exe -logto AICLI-Unpackaged-x64.log -s -r junit -o TEST-AppInstallerCLI-Unpackaged-x64.xml workingDirectory: 'src\x64\Release\AppInstallerCLITests\' - task: PublishBuildArtifacts@1 - displayName: Publish Unpackaged Log + displayName: Publish Unit Tests Unpackaged Log x64 inputs: - PathtoPublish: 'src\x64\Release\AppInstallerCLITests\AICLI-Unpackaged.log' + PathtoPublish: 'src\x64\Release\AppInstallerCLITests\AICLI-Unpackaged-x64.log' ArtifactName: 'TestPassUnpackagedLog' publishLocation: 'Container' condition: succeededOrFailed() - task: PublishBuildArtifacts@1 - displayName: Publish Unpackaged Output + displayName: Publish Unit Tests Unpackaged Output x64 inputs: - PathtoPublish: 'src\x64\Release\AppInstallerCLITests\TEST-AppInstallerCLI-Unpackaged.xml' + PathtoPublish: 'src\x64\Release\AppInstallerCLITests\TEST-AppInstallerCLI-Unpackaged-x64.xml' ArtifactName: 'TestPassUnpackagedOutput' publishLocation: 'Container' condition: succeededOrFailed() - task: PowerShell@2 - displayName: Install Dependencies + displayName: Install Tests Dependencies x64 inputs: targetType: 'inline' script: | @@ -96,35 +105,136 @@ jobs: workingDirectory: $(appxPackageDir) - task: PowerShell@2 - displayName: Run Tests Packaged + displayName: Run Unit Tests Packaged x64 inputs: filePath: 'src\AppInstallerCLITests\Run-TestsInPackage.ps1' - arguments: '-Args "~[pips]" -BuildRoot x64\Release -PackageRoot AppInstallerCLIPackage\bin\x64\Release -LogTarget x64\Release\AICLI-Packaged.log -TestResultsTarget x64\Release\TEST-AppInstallerCLI-Packaged.xml -ScriptWait' + arguments: '-Args "~[pips]" -BuildRoot x64\Release -PackageRoot AppInstallerCLIPackage\bin\x64\Release -LogTarget x64\Release\AICLI-Packaged-x64.log -TestResultsTarget x64\Release\TEST-AppInstallerCLI-Packaged-x64.xml -ScriptWait' workingDirectory: 'src' - task: PublishBuildArtifacts@1 - displayName: Publish Packaged Log + displayName: Publish Unit Tests Packaged Log x64 inputs: - PathtoPublish: 'src\x64\Release\AICLI-Packaged.log' + PathtoPublish: 'src\x64\Release\AICLI-Packaged-x64.log' ArtifactName: 'TestPassPackagedLog' publishLocation: 'Container' condition: succeededOrFailed() - task: PublishBuildArtifacts@1 - displayName: Publish Packaged Output + displayName: Publish Unit Tests Packaged Output x64 inputs: - PathtoPublish: 'src\x64\Release\TEST-AppInstallerCLI-Packaged.xml' + PathtoPublish: 'src\x64\Release\TEST-AppInstallerCLI-Packaged-x64.xml' + ArtifactName: 'TestPassPackagedOutput' + publishLocation: 'Container' + condition: succeededOrFailed() + + - task: CmdLine@2 + displayName: Run Unit Tests Unpackaged x86 + inputs: + script: | + AppInstallerCLITests.exe -logto AICLI-Unpackaged-x86.log -s -r junit -o TEST-AppInstallerCLI-Unpackaged-x86.xml + workingDirectory: 'src\x86\Release\AppInstallerCLITests\' + + - task: PublishBuildArtifacts@1 + displayName: Publish Unit Tests Unpackaged Log x86 + inputs: + PathtoPublish: 'src\x86\Release\AppInstallerCLITests\AICLI-Unpackaged-x86.log' + ArtifactName: 'TestPassUnpackagedLog' + publishLocation: 'Container' + condition: succeededOrFailed() + + - task: PublishBuildArtifacts@1 + displayName: Publish Unit Tests Unpackaged Output x86 + inputs: + PathtoPublish: 'src\x86\Release\AppInstallerCLITests\TEST-AppInstallerCLI-Unpackaged-x86.xml' + ArtifactName: 'TestPassUnpackagedOutput' + publishLocation: 'Container' + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: Install Tests Dependencies x86 + inputs: + targetType: 'inline' + script: | + Add-AppxPackage AppInstallerCLIPackage_0.0.0.2_Test\Dependencies\x86\Microsoft.VCLibs.x86.14.00.Desktop.appx + workingDirectory: $(appxPackageDir) + + - task: PowerShell@2 + displayName: Run Unit Tests Packaged x86 + inputs: + filePath: 'src\AppInstallerCLITests\Run-TestsInPackage.ps1' + arguments: '-Args "~[pips]" -BuildRoot x86\Release -PackageRoot AppInstallerCLIPackage\bin\x86\Release -LogTarget x86\Release\AICLI-Packaged-x86.log -TestResultsTarget x86\Release\TEST-AppInstallerCLI-Packaged-x86.xml -ScriptWait' + workingDirectory: 'src' + + - task: PublishBuildArtifacts@1 + displayName: Publish Unit Tests Packaged Log x86 + inputs: + PathtoPublish: 'src\x86\Release\AICLI-Packaged-x86.log' + ArtifactName: 'TestPassPackagedLog' + publishLocation: 'Container' + condition: succeededOrFailed() + + - task: PublishBuildArtifacts@1 + displayName: Publish Unit Tests Packaged Output x86 + inputs: + PathtoPublish: 'src\x86\Release\TEST-AppInstallerCLI-Packaged-x86.xml' ArtifactName: 'TestPassPackagedOutput' publishLocation: 'Container' condition: succeededOrFailed() - task: PublishTestResults@2 - displayName: Publish Test Results + displayName: Publish Unit Test Results inputs: testResultsFormat: 'JUnit' testResultsFiles: '**/TEST-*.xml' failTaskOnFailedTests: true condition: succeededOrFailed() + + - task: VisualStudioTestPlatformInstaller@1 + displayName: Prepare VSTest for E2E Tests + inputs: + packageFeedSelector: 'nugetOrg' + + - task: VSTest@2 + displayName: Run E2E Tests Unpackaged x64 + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.dll' + runSettingsFile: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\Test.runsettings' + overrideTestrunParameters: '-PackagedContext false + -AICLIPath $(system.defaultWorkingDirectory)\src\x64\Release\AppInstallerCLI\AppInstallerCLI.exe' + + - task: VSTest@2 + displayName: Run E2E Tests Packaged x64 + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.dll' + runSettingsFile: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\Test.runsettings' + overrideTestrunParameters: '-PackagedContext true + -AICLIPackagePath $(system.defaultWorkingDirectory)\src\AppInstallerCLIPackage\bin\x64\Release + -AICLIPath AppInstallerCLI\AppInstallerCLI.exe + -LooseFileRegistration true + -InvokeCommandInDesktopPackage true' + + - task: VSTest@2 + displayName: Run E2E Tests Unpackaged x86 + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.dll' + runSettingsFile: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\Test.runsettings' + overrideTestrunParameters: '-PackagedContext false + -AICLIPath $(system.defaultWorkingDirectory)\src\x86\Release\AppInstallerCLI\AppInstallerCLI.exe' + + - task: VSTest@2 + displayName: Run E2E Tests Packaged x86 + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.dll' + runSettingsFile: 'src\AnyCPU\Release\AppInstallerCLIE2ETests\Test.runsettings' + overrideTestrunParameters: '-PackagedContext true + -AICLIPackagePath $(system.defaultWorkingDirectory)\src\AppInstallerCLIPackage\bin\x86\Release + -AICLIPath AppInstallerCLI\AppInstallerCLI.exe + -LooseFileRegistration true + -InvokeCommandInDesktopPackage true' - task: PublishBuildArtifacts@1 displayName: Publish CLI Binary @@ -132,7 +242,7 @@ jobs: PathtoPublish: 'src\x64\Release\AppInstallerCLI\AppInstallerCLI.exe' ArtifactName: 'AppInstallerCLI.exe' publishLocation: 'Container' - + - task: PublishBuildArtifacts@1 displayName: Publish Util Binary inputs: @@ -146,7 +256,7 @@ jobs: PathtoPublish: 'src\x64\Release\WinGetUtil\WinGetUtil.pdb' ArtifactName: 'WinGetUtil.pdb' publishLocation: 'Container' - + - task: ComponentGovernanceComponentDetection@0 displayName: Component Governance inputs: diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln @@ -33,6 +33,16 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerTestExeInstalle EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "binver", "binver\binver.vcxitems", "{6E36DDD7-1602-474E-B1D7-D0A7E1D5AD86}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppInstallerCLIE2ETests", "AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.csproj", "{3C0269FA-E582-4CA7-9E33-3881A005CA0C}" +EndProject +Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AppInstallerTestMsixInstaller", "AppInstallerTestMsixInstaller\AppInstallerTestMsixInstaller.wapproj", "{3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}" +EndProject +Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "AppInstallerTestMsiInstaller", "AppInstallerTestMsiInstaller\AppInstallerTestMsiInstaller.vdproj", "{C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tool", "Tool", "{EA8CD934-0702-4911-A2C5-A40600E616DE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IndexCreationTool", "IndexCreationTool\IndexCreationTool.csproj", "{3B8466CF-4FDD-4329-9C80-91321C4AAC99}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution catch2\catch2.vcxitems*{5295e21e-9868-4de2-a177-fbb97b36579b}*SharedItemsImports = 9 @@ -184,18 +194,82 @@ Global {FB313532-38B0-4676-9303-AB200AA13576}.Release|x64.Build.0 = Release|x64 {FB313532-38B0-4676-9303-AB200AA13576}.Release|x86.ActiveCfg = Release|Win32 {FB313532-38B0-4676-9303-AB200AA13576}.Release|x86.Build.0 = Release|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM.ActiveCfg = Debug|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM64.ActiveCfg = Debug|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM.ActiveCfg = Debug|ARM + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM.Build.0 = Debug|ARM + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM64.Build.0 = Debug|ARM64 {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x64.ActiveCfg = Debug|x64 {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x64.Build.0 = Debug|x64 {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x86.ActiveCfg = Debug|Win32 {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x86.Build.0 = Debug|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM.ActiveCfg = Release|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM64.ActiveCfg = Release|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM.ActiveCfg = Release|ARM + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM.Build.0 = Release|ARM + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM64.ActiveCfg = Release|ARM64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM64.Build.0 = Release|ARM64 {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x64.ActiveCfg = Release|x64 {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x64.Build.0 = Release|x64 {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x86.ActiveCfg = Release|Win32 {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x86.Build.0 = Release|Win32 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x64.ActiveCfg = Debug|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x64.Build.0 = Debug|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x86.ActiveCfg = Debug|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x86.Build.0 = Debug|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|ARM.ActiveCfg = Release|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|ARM64.ActiveCfg = Release|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x64.ActiveCfg = Release|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x64.Build.0 = Release|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x86.ActiveCfg = Release|Any CPU + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x86.Build.0 = Release|Any CPU + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM.ActiveCfg = Debug|ARM + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM.Build.0 = Debug|ARM + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM.Deploy.0 = Debug|ARM + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM64.Build.0 = Debug|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x64.ActiveCfg = Debug|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x64.Build.0 = Debug|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x64.Deploy.0 = Debug|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x86.ActiveCfg = Debug|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x86.Build.0 = Debug|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x86.Deploy.0 = Debug|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM.ActiveCfg = Release|ARM + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM.Build.0 = Release|ARM + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM.Deploy.0 = Release|ARM + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM64.ActiveCfg = Release|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM64.Build.0 = Release|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM64.Deploy.0 = Release|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x64.ActiveCfg = Release|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x64.Build.0 = Release|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x64.Deploy.0 = Release|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x86.ActiveCfg = Release|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x86.Build.0 = Release|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x86.Deploy.0 = Release|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM64.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x64.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x86.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x86.ActiveCfg = Release + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|ARM.ActiveCfg = Debug|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|ARM.Build.0 = Debug|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|ARM64.Build.0 = Debug|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.ActiveCfg = Debug|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.Build.0 = Debug|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x86.ActiveCfg = Debug|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x86.Build.0 = Debug|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|ARM.ActiveCfg = Release|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|ARM.Build.0 = Release|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|ARM64.ActiveCfg = Release|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|ARM64.Build.0 = Release|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x64.ActiveCfg = Release|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x64.Build.0 = Release|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x86.ActiveCfg = Release|Any CPU + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -204,6 +278,7 @@ Global {5295E21E-9868-4DE2-A177-FBB97B36579B} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {8BB94BB8-374F-4294-BCA1-C7811514A6B7} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {6E36DDD7-1602-474E-B1D7-D0A7E1D5AD86} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {3B8466CF-4FDD-4329-9C80-91321C4AAC99} = {EA8CD934-0702-4911-A2C5-A40600E616DE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -0,0 +1,32 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netcoreapp3.1</TargetFramework> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\AppInstallerCLIE2ETests\</OutDir> + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" /> + <PackageReference Include="nunit" Version="3.12.0" /> + <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> + </ItemGroup> + + <ItemGroup> + <Folder Include="TestData\Manifests\" /> + </ItemGroup> + + <ItemGroup> + <Content Include="TestData\**"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> + </ItemGroup> + + <ItemGroup> + <None Update="Test.runsettings"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + </ItemGroup> + +</Project> diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests +{ + public class Constants + { + // Runtime test parameters + public const string PackagedContextParameter = "PackagedContext"; + public const string AICLIPathParameter = "AICLIPath"; + public const string AICLIPackagePathParameter = "AICLIPackagePath"; + public const string VerboseLoggingParameter = "VerboseLogging"; + public const string LooseFileRegistrationParameter = "LooseFileRegistration"; + public const string InvokeCommandInDesktopPackageParameter = "InvokeCommandInDesktopPackage"; + + public const string AppInstallerTestCert = "AppInstallerTest.cer"; + public const string AppInstallerTestCertThumbprint = "d03e7a688b388b1edde8476a627531c49db88017"; + + // Todo: not needed if switch to use prod index for source tests + public const string IndexPackageCert = "IndexPackageInt.cer"; + public const string IndexPackageCertThumbprint = "1da968e670a0257f61628aad20c60c64fdecd41a"; + + public const string AICLIPackageFamilyName = "AppInstallerCLI_8wekyb3d8bbwe"; + public const string AICLIPackageName = "AppInstallerCLI"; + public const string AICLIAppId = "AppInst"; + + // Todo: there's a deployment bug that if the last optional package is removed, the main package is also removed. + // We should remove this when the deployment bug is fixed. + public const string PlaceholderPackageFile = "PlaceholderPackage.msix"; + public const string PlaceholderPackageName = "AppInstallerSQLiteIndex-e2etest-placeholder"; + + public class ErrorCode + { + public const int S_OK = 0; + public const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002); + public const int ERROR_NO_RANGES_PROCESSED = unchecked((int)0x80070138); + public const int OPC_E_ZIP_MISSING_END_OF_CENTRAL_DIRECTORY = unchecked((int)0x8051100f); + public const int ERROR_OLD_WIN_VERSION = unchecked((int)0x8007047e); + + // AICLI custom HResults + public const int ERROR_INTERNAL_ERROR = unchecked((int)0x8A150001); + public const int ERROR_INVALID_CL_ARGUMENTS = unchecked((int)0x8A150002); + public const int ERROR_COMMAND_FAILED = unchecked((int)0x8A150003); + public const int ERROR_MANIFEST_FAILED = unchecked((int)0x8A150004); + //public const int ERROR_WORKFLOW_FAILED = unchecked((int)0x8A150005) // Unused, can be repurposed + public const int ERROR_SHELLEXEC_INSTALL_FAILED = unchecked((int)0x8A150006); + //public const int ERROR_RUNTIME_ERROR = unchecked((int)0x8A150007) // Unused, can be repurposed + public const int ERROR_DOWNLOAD_FAILED = unchecked((int)0x8A150008); + public const int ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX = unchecked((int)0x8A150009); + public const int ERROR_INDEX_INTEGRITY_COMPROMISED = unchecked((int)0x8A15000A); + public const int ERROR_SOURCES_INVALID = unchecked((int)0x8A15000B); + public const int ERROR_SOURCE_NAME_ALREADY_EXISTS = unchecked((int)0x8A15000C); + public const int ERROR_INVALID_SOURCE_TYPE = unchecked((int)0x8A15000D); + public const int ERROR_PACKAGE_IS_BUNDLE = unchecked((int)0x8A15000E); + public const int ERROR_SOURCE_DATA_MISSING = unchecked((int)0x8A15000F); + public const int ERROR_NO_APPLICABLE_INSTALLER = unchecked((int)0x8A150010); + public const int ERROR_INSTALLER_HASH_MISMATCH = unchecked((int)0x8A150011); + public const int ERROR_SOURCE_NAME_DOES_NOT_EXIST = unchecked((int)0x8A150012); + public const int ERROR_SOURCE_ARG_ALREADY_EXISTS = unchecked((int)0x8A150013); + public const int ERROR_NO_APPLICATIONS_FOUND = unchecked((int)0x8A150014); + public const int ERROR_NO_SOURCES_DEFINED = unchecked((int)0x8A150015); + public const int ERROR_MULTIPLE_APPLICATIONS_FOUND = unchecked((int)0x8A150016); + public const int ERROR_NO_MANIFEST_FOUND = unchecked((int)0x8A150017); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/HashCommand.cs b/src/AppInstallerCLIE2ETests/HashCommand.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + + public class HashCommand + { + [Test] + public void HashCommands() + { + // Hash a file + var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTest.cer")); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("9b4c49ad7e47afd97d2e666e93347745e1647c55f1a7ebba6d31b7dd5f69ee68")); + + // Hash msix + result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile(Constants.PlaceholderPackageFile) + " -m"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("08917b781939a7796746b5e2349e1f1d83b6c15599b60cd3f62816f15e565fc4")); + Assert.True(result.StdOut.Contains("223b318c4b1154a1fb72b1bc23422810faa5ce899a8e774ba2a02834b2058f00")); + + // The input is not msix but -m is used + result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTest.cer") + " -m"); + Assert.AreEqual(Constants.ErrorCode.OPC_E_ZIP_MISSING_END_OF_CENTRAL_DIRECTORY, result.ExitCode); + Assert.True(result.StdOut.Contains("9b4c49ad7e47afd97d2e666e93347745e1647c55f1a7ebba6d31b7dd5f69ee68")); + Assert.True(result.StdOut.Contains("Please verify that the input file is a valid, signed MSIX.")); + + // Input file not found + result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("DoesNot.Exist")); + Assert.AreEqual(Constants.ErrorCode.ERROR_FILE_NOT_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("File does not exist")); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + using System.IO; + + public class InstallCommand + { + // Todo: this should point to a loopback address. Disabling the install tests until we have loopback support done in our e2e tests. + private const string InstallTestSourceUrl = @"https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData"; + private const string InstallTestSourceName = @"InstallTestSource"; + + private const string InstallTestExeInstalledFile = @"TestExeInstalled.txt"; + private const string InstallTestMsiInstalledFile = @"AppInstallerTestExeInstaller.exe"; + private const string InstallTestMsiProductId = @"{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}"; + private const string InstallTestMsixName = @"6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e"; + + //[SetUp] + public void Setup() + { + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("source add", $"{InstallTestSourceName} {InstallTestSourceUrl}").ExitCode); + } + + //[TearDown] + public void TearDown() + { + TestCommon.RunAICLICommand("source remove", InstallTestSourceName); + TestCommon.WaitForDeploymentFinish(); + } + + //[Test] + public void InstallCommands() + { + // Cannot find an app to install + var result = TestCommon.RunAICLICommand("install", "DoesNotExist"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No app found matching input criteria.")); + + // Too many apps match the query + result = TestCommon.RunAICLICommand("install", "AppInstallerTest"); + Assert.AreEqual(Constants.ErrorCode.ERROR_MULTIPLE_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("Multiple apps found matching input criteria. Please refine the input.")); + + // Install test exe + var installDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("install", $"TestExeInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(VerifyTestExeInstalled(installDir, "/execustom")); + + // Install test exe but min os version too high + installDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("install", $"InapplicableOsVersion --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_OLD_WIN_VERSION, result.ExitCode); + Assert.True(result.StdOut.Contains("Cannot install application, as it requires a higher OS version")); + Assert.False(VerifyTestExeInstalled(installDir)); + + // Install test exe but hash mismatch, passing N should cause the installation to fail + installDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("install", $"TestExeSha256Mismatch --silent -l {installDir}", "N"); + Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, result.ExitCode); + Assert.True(result.StdOut.Contains("Installer hash mismatch")); + Assert.False(VerifyTestExeInstalled(installDir)); + + // Install test exe but hash mismatch, passing Y should cause the installation to continue + installDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("install", $"TestExeSha256Mismatch --silent -l {installDir}", "Y"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(VerifyTestExeInstalled(installDir, "/execustom")); + + // Install test inno, manifest does not provide silent switch, we should be populating the default + installDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("install", $"TestInnoInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(VerifyTestExeInstalled(installDir, "/VERYSILENT")); + + // Install test burn, manifest does not provide silent switch, we should be populating the default + installDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("install", $"TestBurnInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(VerifyTestExeInstalled(installDir, "/quiet")); + + // Install test Nullsoft, manifest does not provide silent switch, we should be populating the default + installDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("install", $"TestNullsoftInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(VerifyTestExeInstalled(installDir, "/S")); + + // Install test msi + installDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("install", $"TestMsiInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(VerifyTestMsiInstalledAndCleanup(installDir)); + + // Install test msix + result = TestCommon.RunAICLICommand("install", $"TestMsixInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(VerifyTestMsixInstalledAndCleanup()); + + // Install test msix with signature provided + result = TestCommon.RunAICLICommand("install", $"TestMsixWithSignatureHash"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(VerifyTestMsixInstalledAndCleanup()); + + // Install test msix with signature hash mismatch, passing N should cause the installation to fail + result = TestCommon.RunAICLICommand("install", $"TestMsixSignatureHashMismatch", "N"); + Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, result.ExitCode); + Assert.True(result.StdOut.Contains("Installer hash mismatch")); + Assert.False(VerifyTestMsixInstalledAndCleanup()); + + // Install test msix with signature hash mismatch, passing Y should cause the installation to continue + result = TestCommon.RunAICLICommand("install", $"TestMsixSignatureHashMismatch", "Y"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(VerifyTestMsixInstalledAndCleanup()); + } + + private bool VerifyTestExeInstalled(string installDir, string expectedContent = null) + { + if (!File.Exists(Path.Combine(installDir, InstallTestExeInstalledFile))) + { + return false; + } + + if (!string.IsNullOrEmpty(expectedContent)) + { + string content = File.ReadAllText(Path.Combine(installDir, InstallTestExeInstalledFile)); + return content.Contains(expectedContent); + } + + return true; + } + + private bool VerifyTestMsiInstalledAndCleanup(string installDir) + { + if (!File.Exists(Path.Combine(installDir, InstallTestMsiInstalledFile))) + { + return false; + } + + return TestCommon.RunCommand("msiexec.exe", $"/qn /x {InstallTestMsiProductId}"); + } + + private bool VerifyTestMsixInstalledAndCleanup() + { + var result = TestCommon.RunCommandWithResult("powershell", $"Get-AppxPackage {InstallTestMsixName}"); + + if (!result.StdOut.Contains(InstallTestMsixName)) + { + return false; + } + + return TestCommon.RemoveMsix(InstallTestMsixName); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/SearchCommand.cs b/src/AppInstallerCLIE2ETests/SearchCommand.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + using System.Threading; + + public class SearchCommand + { + // Todo: use created test source when available + private const string SearchTestSourceUrl = @"https://winget-int.azureedge.net/cache"; + private const string SearchTestSourceName = @"SearchTestSource"; + + [SetUp] + public void Setup() + { + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("source add", $"{SearchTestSourceName} {SearchTestSourceUrl}").ExitCode); + } + + [TearDown] + public void TearDown() + { + TestCommon.RunAICLICommand("source remove", SearchTestSourceName); + + TestCommon.WaitForDeploymentFinish(); + } + + [Test] + public void SearchCommands() + { + // Search without args list every app + var result = TestCommon.RunAICLICommand("search", ""); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Microsoft.PowerToys")); + Assert.True(result.StdOut.Contains("Microsoft.VisualStudioCode")); + + // Search query + result = TestCommon.RunAICLICommand("search", "VisualStudioCode"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Microsoft.VisualStudioCode")); + + // Search through id found the app + result = TestCommon.RunAICLICommand("search", "--id VisualStudioCode"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Microsoft.VisualStudioCode")); + + // Search through name. No app found because name is "Visual Studio Code" + result = TestCommon.RunAICLICommand("search", "--name VisualStudioCode"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No app found matching input criteria.")); + + // Search Microsoft should return multiple + result = TestCommon.RunAICLICommand("search", "Microsoft"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Microsoft.PowerToys")); + Assert.True(result.StdOut.Contains("Microsoft.VisualStudioCode")); + + // Search Microsoft with exact arg should return none + result = TestCommon.RunAICLICommand("search", "Microsoft -e"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No app found matching input criteria.")); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/SetUpFixture.cs b/src/AppInstallerCLIE2ETests/SetUpFixture.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests +{ + using Microsoft.Win32; + using NUnit.Framework; + using System; + using System.IO; + + [SetUpFixture] + public class SetUpFixture + { + private static bool ShouldDisableDevModeOnExit = true; + + [OneTimeSetUp] + public void Setup() + { + // Read TestParameters and set runtime variables + TestCommon.PackagedContext = TestContext.Parameters.Exists(Constants.PackagedContextParameter) && + TestContext.Parameters.Get(Constants.PackagedContextParameter).Equals("true", StringComparison.OrdinalIgnoreCase); + + TestCommon.VerboseLogging = TestContext.Parameters.Exists(Constants.VerboseLoggingParameter) && + TestContext.Parameters.Get(Constants.VerboseLoggingParameter).Equals("true", StringComparison.OrdinalIgnoreCase); + + TestCommon.LooseFileRegistration = TestContext.Parameters.Exists(Constants.LooseFileRegistrationParameter) && + TestContext.Parameters.Get(Constants.LooseFileRegistrationParameter).Equals("true", StringComparison.OrdinalIgnoreCase); + + TestCommon.InvokeCommandInDesktopPackage = TestContext.Parameters.Exists(Constants.InvokeCommandInDesktopPackageParameter) && + TestContext.Parameters.Get(Constants.InvokeCommandInDesktopPackageParameter).Equals("true", StringComparison.OrdinalIgnoreCase); + + if (TestContext.Parameters.Exists(Constants.AICLIPathParameter)) + { + TestCommon.AICLIPath = TestContext.Parameters.Get(Constants.AICLIPathParameter); + } + else + { + if (TestCommon.PackagedContext) + { + // For packaged context, default to AppExecutionAlias + TestCommon.AICLIPath = "AppInst.exe"; + } + else + { + TestCommon.AICLIPath = TestCommon.GetTestFile("AppInstallerCli.exe"); + } + } + + if (TestContext.Parameters.Exists(Constants.AICLIPackagePathParameter)) + { + TestCommon.AICLIPackagePath = TestContext.Parameters.Get(Constants.AICLIPackagePathParameter); + } + else + { + TestCommon.AICLIPackagePath = TestCommon.GetTestFile("AppInstallerCLIPackage.appxbundle"); + } + + if (TestCommon.LooseFileRegistration && TestCommon.InvokeCommandInDesktopPackage) + { + TestCommon.AICLIPath = Path.Combine(TestCommon.AICLIPackagePath, TestCommon.AICLIPath); + } + + ShouldDisableDevModeOnExit = EnableDevMode(true); + + Assert.True(TestCommon.RunCommand("certutil.exe", "-addstore -f \"TRUSTEDPEOPLE\" " + TestCommon.GetTestDataFile(Constants.AppInstallerTestCert))); + Assert.True(TestCommon.RunCommand("certutil.exe", "-addstore -f \"TRUSTEDPEOPLE\" " + TestCommon.GetTestDataFile(Constants.IndexPackageCert))); + + if (TestCommon.PackagedContext) + { + if (TestCommon.LooseFileRegistration) + { + Assert.True(TestCommon.InstallMsixRegister(TestCommon.AICLIPackagePath)); + } + else + { + Assert.True(TestCommon.InstallMsix(TestCommon.AICLIPackagePath)); + } + Assert.True(TestCommon.InstallMsix(TestCommon.GetTestDataFile(Constants.PlaceholderPackageFile))); + } + } + + [OneTimeTearDown] + public void TearDown() + { + if (ShouldDisableDevModeOnExit) + { + EnableDevMode(false); + } + + TestCommon.RunCommand("certutil.exe", $"-delstore \"TRUSTEDPEOPLE\" {Constants.AppInstallerTestCertThumbprint}"); + TestCommon.RunCommand("certutil.exe", $"-delstore \"TRUSTEDPEOPLE\" {Constants.IndexPackageCertThumbprint}"); + + if (TestCommon.PackagedContext) + { + TestCommon.RemoveMsix(Constants.PlaceholderPackageName); + TestCommon.RemoveMsix(Constants.AICLIPackageName); + } + } + + // Returns whether there's a change to the dev mode state after execution + private bool EnableDevMode(bool enable) + { + var appModelUnlockKey = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"); + + if (enable) + { + var value = appModelUnlockKey.GetValue("AllowDevelopmentWithoutDevLicense"); + if (value == null || (Int32)value == 0) + { + appModelUnlockKey.SetValue("AllowDevelopmentWithoutDevLicense", 1, RegistryValueKind.DWord); + return true; + } + } + else + { + var value = appModelUnlockKey.GetValue("AllowDevelopmentWithoutDevLicense"); + if (value != null && ((UInt32)value) != 0) + { + appModelUnlockKey.SetValue("AllowDevelopmentWithoutDevLicense", 0, RegistryValueKind.DWord); + return true; + } + } + + return false; + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ShowCommand.cs b/src/AppInstallerCLIE2ETests/ShowCommand.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + using System.Threading; + + public class ShowCommand + { + // Todo: use created test source when available + private const string ShowTestSourceUrl = @"https://winget-int.azureedge.net/cache"; + private const string ShowTestSourceName = @"ShowTestSource"; + + [SetUp] + public void Setup() + { + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("source add", $"{ShowTestSourceName} {ShowTestSourceUrl}").ExitCode); + } + + [TearDown] + public void TearDown() + { + TestCommon.RunAICLICommand("source remove", ShowTestSourceName); + + TestCommon.WaitForDeploymentFinish(); + } + + [Test] + public void ShowCommands() + { + // Show with no arg lists every app and a warning message + var result = TestCommon.RunAICLICommand("show", ""); + Assert.AreEqual(Constants.ErrorCode.ERROR_MULTIPLE_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("Multiple apps found matching input criteria. Please refine the input.")); + Assert.True(result.StdOut.Contains("Microsoft.PowerToys")); + Assert.True(result.StdOut.Contains("Microsoft.VisualStudioCode")); + + // Show with multiple search matches shows a "please refine input" + result = TestCommon.RunAICLICommand("show", "Microsoft"); + Assert.AreEqual(Constants.ErrorCode.ERROR_MULTIPLE_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("Multiple apps found matching input criteria. Please refine the input.")); + Assert.True(result.StdOut.Contains("Microsoft.PowerToys")); + Assert.True(result.StdOut.Contains("Microsoft.VisualStudioCode")); + + // Show with 0 search match shows a "please refine input" + result = TestCommon.RunAICLICommand("show", "DoesNotExist"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No app found matching input criteria.")); + + // Show with 1 search match shows detailed manifest info + result = TestCommon.RunAICLICommand("show", "VisualStudioCode"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Microsoft.VisualStudioCode")); + Assert.True(result.StdOut.Contains("Visual Studio Code")); + + // Show with --versions list the versions + result = TestCommon.RunAICLICommand("show", "VisualStudioCode --versions"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Microsoft.VisualStudioCode")); + Assert.True(result.StdOut.Contains("1.41.1")); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/SourceCommand.cs b/src/AppInstallerCLIE2ETests/SourceCommand.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + + public class SourceCommand + { + // Todo: switch to use prod index when available + private const string SourceTestSourceUrl = @"https://winget-int.azureedge.net/cache"; + private const string SourceTestSourceName = @"SourceTestSource"; + + [TearDown] + public void TearDown() + { + TestCommon.RunAICLICommand("source remove", SourceTestSourceName); + + TestCommon.WaitForDeploymentFinish(); + } + + [Test] + public void SourceCommands() + { + // Add test source should succeed + var result = TestCommon.RunAICLICommand("source add", $"{SourceTestSourceName} {SourceTestSourceUrl}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Done")); + + // Add source with duplicate name should fail + result = TestCommon.RunAICLICommand("source add", $"{SourceTestSourceName} https://microsoft.com"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_ALREADY_EXISTS, result.ExitCode); + Assert.True(result.StdOut.Contains("A source with the given name already exists and refers to a different location")); + + // Add source with invalid url should fail + result = TestCommon.RunAICLICommand("source add", "AnotherSource https://microsoft.com"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_RANGES_PROCESSED, result.ExitCode); + Assert.True(result.StdOut.Contains("error occurred while executing the command")); + + // List with no args should list all available sources + result = TestCommon.RunAICLICommand("source list", ""); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("https://winget-int.azureedge.net/cache")); + + // List when source name matches, it shows detailed info + result = TestCommon.RunAICLICommand("source list", $"-n {SourceTestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("SourceTestSource")); + Assert.True(result.StdOut.Contains("https://winget-int.azureedge.net/cache")); + Assert.True(result.StdOut.Contains("AppInstallerSQLiteIndex-int")); + Assert.True(result.StdOut.Contains("Updated")); + + // List when source name does not match + result = TestCommon.RunAICLICommand("source list", "-n UnknownName"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + Assert.True(result.StdOut.Contains("Did not find a source named")); + + // Update should succeed + result = TestCommon.RunAICLICommand("source update", $"-n {SourceTestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Done")); + + // Update with bad name should fail + result = TestCommon.RunAICLICommand("source update", "-n UnknownName"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + Assert.True(result.StdOut.Contains("Did not find a source named: UnknownName")); + + // Remove with a bad name should fail + result = TestCommon.RunAICLICommand("source remove", "-n UnknownName"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + Assert.True(result.StdOut.Contains("Did not find a source named: UnknownName")); + + // Remove with a good name should succeed + result = TestCommon.RunAICLICommand("source remove", $"-n {SourceTestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Done")); + + TestCommon.WaitForDeploymentFinish(); + + // List should show no source available + result = TestCommon.RunAICLICommand("source list", ""); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("There are no sources configured.")); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/Test.runsettings b/src/AppInstallerCLIE2ETests/Test.runsettings @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> + +<RunSettings> + <!-- + Parameters used by tests at run time. + + PackagedContext: Indicates if the test should be run under packaged context + VerboseLogging: Turn on/off verbose logging in the test result + AICLIPath: The AICLI executable under test. + If using loose file registration and using Invoke-Command when + AppExecutionAlias is not available, this will be relative path to package root. + AICLIPackagePath: Used in packaged context. Path to the package containing executable under test. + If LooseFileRegistration is true, this should be path to unpackaged root. + LooseFileRegistration: Bool to set if loose file registration should be used. + InvokeCommandInDesktopPackage: Bool to indicate using Invoke-CommandInDesktopPackage for test execution. + This is used when AppExecutionAlias is not available, or disabled. + --> + <TestRunParameters> + <Parameter name="PackagedContext" value="true" /> + <Parameter name="VerboseLogging" value="false" /> + <Parameter name="AICLIPath" value="AppInst.exe" /> + <Parameter name="AICLIPackagePath" value="AppInstallerCLIPackage.appxbundle" /> + <Parameter name="LooseFileRegistration" value="false" /> + <Parameter name="InvokeCommandInDesktopPackage" value="false" /> + </TestRunParameters> +</RunSettings>+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -0,0 +1,228 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + using System; + using System.Diagnostics; + using System.IO; + using System.Threading; + + public class TestCommon + { + public static string AICLIPath { get; set; } + + public static string AICLIPackagePath { get; set; } + + public static bool PackagedContext { get; set; } + + public static bool VerboseLogging { get; set; } + + public static bool LooseFileRegistration { get; set; } + + public static bool InvokeCommandInDesktopPackage { get; set; } + + public struct RunCommandResult + { + public int ExitCode; + public string StdOut; + public string StdErr; + } + + public static RunCommandResult RunAICLICommand(string command, string parameters, string stdIn = null, int timeOut = 60000) + { + string inputMsg = + "AICLI path: " + AICLIPath + + " Command: " + command + + " Parameters: " + parameters + + (string.IsNullOrEmpty(stdIn) ? "" : " StdIn: " + stdIn) + + " Timeout: " + timeOut; + + TestContext.Out.WriteLine($"Starting command run. {inputMsg} InvokeCommandInDesktopPackage: {InvokeCommandInDesktopPackage}"); + + if (InvokeCommandInDesktopPackage) + { + return RunAICLICommandViaInvokeCommandInDesktopPackage(command, parameters, stdIn, timeOut); + } + else + { + return RunAICLICommandViaDirectProcess(command, parameters, stdIn, timeOut); + } + } + + public static RunCommandResult RunAICLICommandViaDirectProcess(string command, string parameters, string stdIn = null, int timeOut = 60000) + { + RunCommandResult result = new RunCommandResult(); + Process p = new Process(); + p.StartInfo = new ProcessStartInfo(AICLIPath, command + ' ' + parameters); + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + + if (!string.IsNullOrEmpty(stdIn)) + { + p.StartInfo.RedirectStandardInput = true; + } + + p.Start(); + + if (!string.IsNullOrEmpty(stdIn)) + { + p.StandardInput.Write(stdIn); + } + + if (p.WaitForExit(timeOut)) + { + result.ExitCode = p.ExitCode; + result.StdOut = p.StandardOutput.ReadToEnd(); + result.StdErr = p.StandardError.ReadToEnd(); + + TestContext.Out.WriteLine("Command run completed with exit code: " + result.ExitCode); + + if (!string.IsNullOrEmpty(result.StdErr)) + { + TestContext.Error.WriteLine("Command run error. Error: " + result.StdErr); + } + + if (VerboseLogging && !string.IsNullOrEmpty(result.StdOut)) + { + TestContext.Out.WriteLine("Command run output. Output: " + result.StdOut); + } + } + else + { + throw new TimeoutException("Command run timed out."); + } + + return result; + } + + // This method is used when the test is run in an OS that does not support AppExecutionAlias. E,g, our build machine. + // There is not any existing API that'll activate a packaged app and wait for result, and not possible to capture the stdIn and stdOut. + // This method tries to call Invoke-CommandInDesktopPackage PS command to make test executable run in packaged context. + // Since Invoke-CommandInDesktopPackage just launches the executable and return, we use cmd pipe to get execution results. + // The final constructed command will look like: + // Invoke-CommandInDesktopPackage ...... -Command cmd.exe -Args '-c <cmd command>' + // where <cmd command> will look like: "echo stdIn | appinst.exe args > stdout.txt 2> stderr.txt & echo %ERRORLEVEL% > exitcode.txt" + // Then this method will read the piped result and return as RunCommandResult. + public static RunCommandResult RunAICLICommandViaInvokeCommandInDesktopPackage(string command, string parameters, string stdIn = null, int timeOut = 60000) + { + string cmdCommandPiped = ""; + if (!string.IsNullOrEmpty(stdIn)) + { + cmdCommandPiped += $"echo {stdIn} | "; + } + + string workDirectory = GetRandomTestDir(); + string exitCodeFile = Path.Combine(workDirectory, "ExitCode.txt"); + string stdOutFile = Path.Combine(workDirectory, "StdOut.txt"); + string stdErrFile = Path.Combine(workDirectory, "StdErr.txt"); + + cmdCommandPiped += $"{AICLIPath} {command} {parameters} > {stdOutFile} 2> {stdErrFile} & call echo %^ERRORLEVEL% > {exitCodeFile}"; + + string psCommand = $"Invoke-CommandInDesktopPackage -PackageFamilyName {Constants.AICLIPackageFamilyName} -AppId {Constants.AICLIAppId} -PreventBreakaway -Command cmd.exe -Args '/c \"{cmdCommandPiped}\"'"; + + var psInvokeResult = RunCommandWithResult("powershell", psCommand); + + if (psInvokeResult.ExitCode != 0) + { + // PS invocation failed, return result and no need to check piped output. + return psInvokeResult; + } + + // The PS command just launches the app and immediately returns, we'll have to wait for up to the timeOut specified here + int waitedTime = 0; + while (!File.Exists(exitCodeFile) && waitedTime <= timeOut) + { + Thread.Sleep(1000); + waitedTime += 1000; + } + + RunCommandResult result = new RunCommandResult(); + + result.ExitCode = File.Exists(exitCodeFile) ? int.Parse(File.ReadAllText(exitCodeFile).Trim()) : unchecked((int)0x80004005); + result.StdOut = File.Exists(stdOutFile) ? File.ReadAllText(stdOutFile) : ""; + result.StdErr = File.Exists(stdErrFile) ? File.ReadAllText(stdErrFile) : ""; + + return result; + } + + public static bool RunCommand(string fileName, string args, int timeOut = 60000) + { + return RunCommandWithResult(fileName, args, timeOut).ExitCode == 0; + } + + public static RunCommandResult RunCommandWithResult(string fileName, string args, int timeOut = 60000) + { + Process p = new Process(); + p.StartInfo = new ProcessStartInfo(fileName, args); + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + p.Start(); + + RunCommandResult result = new RunCommandResult(); + if (p.WaitForExit(timeOut)) + { + result.ExitCode = p.ExitCode; + result.StdOut = p.StandardOutput.ReadToEnd(); + result.StdErr = p.StandardError.ReadToEnd(); + + if (VerboseLogging) + { + TestContext.Out.WriteLine($"Command run finished. {fileName} {args} {timeOut}. Output: {result.StdOut} Error: {result.StdErr}"); + } + } + else + { + throw new TimeoutException($"Command run timed out. {fileName} {args} {timeOut}"); + } + + return result; + } + + public static string GetTestFile(string fileName) + { + return Path.Combine(TestContext.CurrentContext.TestDirectory, fileName); + } + + public static string GetTestDataFile(string fileName) + { + return GetTestFile(Path.Combine("TestData", fileName)); + } + + public static string GetRandomTestDir() + { + string randDir = Path.Combine(TestContext.CurrentContext.TestDirectory, Path.Combine("WorkDirectory", Path.GetRandomFileName())); + Directory.CreateDirectory(randDir); + return randDir; + } + + public static bool InstallMsix(string file) + { + return RunCommand("powershell", $"Add-AppxPackage \"{file}\""); + } + + public static bool InstallMsixRegister(string packagePath) + { + string manifestFile = Path.Combine(packagePath, "AppxManifest.xml"); + return RunCommand("powershell", $"Add-AppxPackage -Register \"{manifestFile}\""); + } + + public static bool RemoveMsix(string name) + { + return RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage"); + } + + public static void WaitForDeploymentFinish() + { + if (PackagedContext) + { + // Since we are doing a lot index add/remove, and some of the methods are fire and forget. + // Sometimes process start will fail because app is updating. + // Or index package is not completely added, removed. + Thread.Sleep(5000); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/TestData/AppInstallerTest.cer b/src/AppInstallerCLIE2ETests/TestData/AppInstallerTest.cer Binary files differ. diff --git a/src/AppInstallerCLIE2ETests/TestData/IndexPackageInt.cer b/src/AppInstallerCLIE2ETests/TestData/IndexPackageInt.cer Binary files differ. diff --git a/src/AppInstallerCLIE2ETests/TestData/IndexPackageManifest.xml b/src/AppInstallerCLIE2ETests/TestData/IndexPackageManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" + xmlns:uap4="http://schemas.microsoft.com/appx/manifest/uap/windows10/4" + IgnorableNamespaces="uap4"> + + <Identity Name="AppInstallerSQLiteIndex-e2etest" + ProcessorArchitecture="neutral" + Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" + Version="1.0.0.0" /> + + <Properties> + <DisplayName>AppInstallerSQLiteIndex for AppInstallerCLI.</DisplayName> + <PublisherDisplayName>Microsoft Corporation</PublisherDisplayName> + <Logo>Assets\AppPackageStoreLogo.png</Logo> + </Properties> + + <Dependencies> + <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.16299.0" MaxVersionTested="10.0.18287.0" /> + <uap4:MainPackageDependency Name="Microsoft.DesktopAppInstaller" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"/> + <uap4:MainPackageDependency Name="AppInstallerCLI" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"/> + </Dependencies> + + <Resources> + <Resource Language="en-US" /> + </Resources> + +</Package>+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.yaml @@ -0,0 +1,11 @@ +Id: AppInstallerTest.TestBurnInstaller +Name: TestBurnInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestExeInstaller.exe + Sha256: 7c616e2fe3853b555ddea4cb50bd307da1dc47898a5fbc3a8646a470107802ab + InstallerType: burn + Switches: + InstallLocation: /InstallDir <INSTALLPATH> diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml @@ -0,0 +1,17 @@ +Id: AppInstallerTest.TestExeInstaller +Name: TestExeInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestExeInstaller.exe + Sha256: 7c616e2fe3853b555ddea4cb50bd307da1dc47898a5fbc3a8646a470107802ab + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog <LOGPATH> + InstallLocation: /InstallDir <INSTALLPATH> diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InapplicableOsVersion.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InapplicableOsVersion.yaml @@ -0,0 +1,18 @@ +Id: AppInstallerTest.InapplicableOsVersion +Name: InapplicableOsVersion +Version: 1.0.0.0 +Publisher: AppInstallerTest +MinOSVersion: 11.0.0.0 +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestExeInstaller.exe + Sha256: 7c616e2fe3853b555ddea4cb50bd307da1dc47898a5fbc3a8646a470107802ab + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog <LOGPATH> + InstallLocation: /InstallDir <INSTALLPATH> diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_Sha256Mismatch.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_Sha256Mismatch.yaml @@ -0,0 +1,17 @@ +Id: AppInstallerTest.TestExeSha256Mismatch +Name: TestExeSha256Mismatch +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestExeInstaller.exe + Sha256: 7c616e2fe3853b555ddea4cb50bd307da1dc47898a5fbc3a8646a470107802aa + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog <LOGPATH> + InstallLocation: /InstallDir <INSTALLPATH> diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.yaml @@ -0,0 +1,11 @@ +Id: AppInstallerTest.TestInnoInstaller +Name: TestInnoInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestExeInstaller.exe + Sha256: 7c616e2fe3853b555ddea4cb50bd307da1dc47898a5fbc3a8646a470107802ab + InstallerType: inno + Switches: + InstallLocation: /InstallDir <INSTALLPATH> diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInvalidManifest.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInvalidManifest.yaml @@ -0,0 +1,17 @@ +Id: TestInvalidManifest TestInvalidManifest +Name: TestInvalidManifest +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestExeInstaller.exe + Sha256: 7c616e2fe3853b555ddea4cb50bd307da1dc47898a5fbc3a8646a470107802ab + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog <LOGPATH> + InstallLocation: /InstallDir <INSTALLPATH> diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.yaml @@ -0,0 +1,10 @@ +Id: AppInstallerTest.TestMsiInstaller +Name: TestMsiInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestMsiInstaller.msi + Sha256: 18a07fadd84fd66fce094e323a51afba648fd8c2a826573d61b60960d7e35e63 + InstallerType: msi + diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller.yaml @@ -0,0 +1,10 @@ +Id: AppInstallerTest.TestMsixInstaller +Name: TestMsixInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestMsixInstaller.msix + Sha256: 714c861a52478bfa44228739c500c9429f4e75c7b78d48cac06bfb8f62bad627 + InstallerType: msix + diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_SignatureHashMismatch.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_SignatureHashMismatch.yaml @@ -0,0 +1,11 @@ +Id: AppInstallerTest.TestMsixSignatureHashMismatch +Name: TestMsixSignatureHashMismatch +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestMsixInstaller.msix + Sha256: 714c861a52478bfa44228739c500c9429f4e75c7b78d48cac06bfb8f62bad626 + SignatureSha256: d8a54b79a9c5956df5b338fdaf37f48e56316465defd182cbe64d8e5d3d53d4d + InstallerType: msix + diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_WithSignatureHash.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_WithSignatureHash.yaml @@ -0,0 +1,11 @@ +Id: AppInstallerTest.TestMsixWithSignatureHash +Name: TestMsixWithSignatureHash +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestMsixInstaller.msix + Sha256: 714c861a52478bfa44228739c500c9429f4e75c7b78d48cac06bfb8f62bad627 + SignatureSha256: d8a54b79a9c5956df5b338fdaf37f48e56316465defd182cbe64d8e5d3d53d4e + InstallerType: msix + diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.yaml @@ -0,0 +1,11 @@ +Id: AppInstallerTest.TestNullsoftInstaller +Name: TestNullsoftInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +Installers: + - Arch: x86 + Url: https://github.com/microsoft/appinstaller-cli/raw/master/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestExeInstaller.exe + Sha256: 7c616e2fe3853b555ddea4cb50bd307da1dc47898a5fbc3a8646a470107802ab + InstallerType: nullsoft + Switches: + InstallLocation: /InstallDir <INSTALLPATH> diff --git a/src/AppInstallerCLIE2ETests/TestData/PlaceholderPackage.msix b/src/AppInstallerCLIE2ETests/TestData/PlaceholderPackage.msix Binary files differ. diff --git a/src/AppInstallerCLIE2ETests/ValidateCommand.cs b/src/AppInstallerCLIE2ETests/ValidateCommand.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + + public class ValidateCommand + { + [Test] + public void ValidateCommands() + { + // Validate a good yaml + var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestExeInstaller.yaml")); + Assert.AreEqual(result.ExitCode, Constants.ErrorCode.S_OK); + Assert.True(result.StdOut.Contains("Manifest validation succeeded.")); + + // Validate invalid yaml + result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestInvalidManifest.yaml")); + Assert.AreEqual(result.ExitCode, Constants.ErrorCode.S_OK); + Assert.True(result.StdOut.Contains("Manifest validation failed.")); + + // Input file not found + result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\DoesNotExist")); + Assert.AreEqual(result.ExitCode, Constants.ErrorCode.ERROR_FILE_NOT_FOUND); + Assert.True(result.StdOut.Contains("File does not exist")); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -1,13 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <Package - xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" - xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" - xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" - xmlns:uap8="http://schemas.microsoft.com/appx/manifest/uap/windows10/8" - xmlns:wincap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/windowscapabilities" - xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" - xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" - IgnorableNamespaces="uap uap5 uap8 wincap rescap desktop"> + xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" + xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" + xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" + xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" + IgnorableNamespaces="uap uap5 rescap"> <Identity Name="AppInstallerCLI" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="0.0.0.2" /> <Properties> <DisplayName>App Installer CLI</DisplayName> @@ -28,7 +25,7 @@ <Extensions> <uap5:Extension Category="windows.appExecutionAlias"> <uap5:AppExecutionAlias> - <uap8:ExecutionAlias Alias="AppInst.exe" /> + <uap5:ExecutionAlias Alias="AppInst.exe" /> </uap5:AppExecutionAlias> </uap5:Extension> </Extensions> diff --git a/src/AppInstallerCLITests/Run-TestsInPackage.ps1 b/src/AppInstallerCLITests/Run-TestsInPackage.ps1 @@ -145,4 +145,7 @@ if ($ScriptWait) Wait-ForFileClose $TestResultsTarget } Write-Host "Done" -} + + Write-Host "Remove registered package" + Get-AppxPackage AppInstallerCLI | Remove-AppxPackage +}+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/Sources.cpp b/src/AppInstallerCLITests/Sources.cpp @@ -74,7 +74,8 @@ struct TestSource : public ISource static std::shared_ptr<ISource> Create(const SourceDetails& details) { - return std::make_shared<TestSource>(details); + // using return std::make_shared<TestSource>(details); will crash the x86 test during destruction. + return std::shared_ptr<ISource>(new TestSource(details)); } // ISource diff --git a/src/AppInstallerCLITests/TEST-AppInstallerCLI-1.xml b/src/AppInstallerCLITests/TEST-AppInstallerCLI-1.xml @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<testsuites> - <testsuite name="AppInstallerCLITests.exe" errors="0" failures="0" tests="271" hostname="tbd" time="3.0509" timestamp="2020-02-05T01:16:40Z"> - <testcase classname="AppInstallerCLITests.exe.global" name="DownloadValidFileAndVerifyHash" time="0.424575"/> - <testcase classname="AppInstallerCLITests.exe.global" name="DownloadValidFileAndCancel" time="0.907437"/> - <testcase classname="AppInstallerCLITests.exe.global" name="DownloadUnreachableUrl" time="0.082048"/> - <testcase classname="AppInstallerCLITests.exe.global" name="InstallFlowWithTestManifest" time="0.72304"/> - <testcase classname="AppInstallerCLITests.exe.global" name="InstallFlowWithNonApplicableArchitecture" time="0.020354"/> - <testcase classname="AppInstallerCLITests.exe.global" name="DestructionToken" time="0.00016"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteIndexCreateLatestAndReopen" time="0.057503"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteIndexCreateAndAddManifest" time="0.068016"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteIndexCreateAndAddManifestFile" time="0.11345"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteIndex_RemoveManifestFile_NotPresent" time="0.034248"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteIndex_RemoveManifest" time="0.18614"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteIndex_RemoveManifestFile" time="0.14229"/> - <testcase classname="AppInstallerCLITests.exe.global" name="PathPartTable_EnsurePathExists_Negative_Paths" time="0.013909"/> - <testcase classname="AppInstallerCLITests.exe.global" name="PathPartTable_EnsurePathExists" time="0.112738"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteWrapperMemoryCreate" time="0.001666"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteWrapperFileCreateAndReopen" time="0.045612"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteWrapperSavepointRollback" time="0.002914"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteWrapperSavepointRollbackOnDestruct" time="0.002808"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLiteWrapperSavepointCommit" time="0.003673"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLBuilder_SimpleSelectBind" time="0.003718"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLBuilder_SimpleSelectUnbound" time="0.00303"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLBuilder_SimpleSelectNull" time="0.002924"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLBuilder_SimpleSelectOptional" time="0.003667"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLBuilder_CreateTable" time="0.005427"/> - <testcase classname="AppInstallerCLITests.exe.global" name="SQLBuilder_InsertValueBinding" time="0.002521"/> - <testcase classname="AppInstallerCLITests.exe.global" name="ReadGoodManifestAndVerifyContents" time="0.019252"/> - <testcase classname="AppInstallerCLITests.exe.global" name="ReadBadManifestAndVerifyThrow" time="0.032321"/> - <system-out/> - <system-err/> - </testsuite> -</testsuites> diff --git a/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj b/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj @@ -5,7 +5,10 @@ <ProjectGuid>{6CB84692-5994-407D-B9BD-9216AF77FE83}</ProjectGuid> <Keyword>Win32Proj</Keyword> <RootNamespace>AppInstallerTestExeInstaller</RootNamespace> - <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> + <WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.18362.0</WindowsTargetPlatformVersion> + <WindowsTargetPlatformMinVersion>10.0.16299.0</WindowsTargetPlatformMinVersion> + <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport> + <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <ItemGroup Label="ProjectConfigurations"> @@ -17,6 +20,14 @@ <Configuration>Release</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> + <ProjectConfiguration Include="Debug|ARM"> + <Configuration>Debug</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|ARM"> + <Configuration>Release</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> <ProjectConfiguration Include="Debug|x64"> <Configuration>Debug</Configuration> <Platform>x64</Platform> @@ -25,6 +36,14 @@ <Configuration>Release</Configuration> <Platform>x64</Platform> </ProjectConfiguration> + <ProjectConfiguration Include="Debug|ARM64"> + <Configuration>Debug</Configuration> + <Platform>ARM64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|ARM64"> + <Configuration>Release</Configuration> + <Platform>ARM64</Platform> + </ProjectConfiguration> </ItemGroup> <PropertyGroup Label="Configuration"> <ConfigurationType>Application</ConfigurationType> @@ -70,101 +89,64 @@ </ImportGroup> <ImportGroup Label="Shared"> </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> - <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> - </ImportGroup> - <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ImportGroup Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> - <LinkIncremental>true</LinkIncremental> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> - <LinkIncremental>true</LinkIncremental> - </PropertyGroup> - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> - <LinkIncremental>false</LinkIncremental> - </PropertyGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ItemDefinitionGroup> <ClCompile> - <PrecompiledHeader> - </PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <FunctionLevelLinking>true</FunctionLevelLinking> - <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> - <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <ConformanceMode>true</ConformanceMode> - <LanguageStandard>stdcpp17</LanguageStandard> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> + <PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile> + <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <WarningLevel>Level4</WarningLevel> + <AdditionalOptions>%(AdditionalOptions) /permissive- /bigobj</AdditionalOptions> </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <EnableCOMDATFolding>true</EnableCOMDATFolding> - <OptimizeReferences>true</OptimizeReferences> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'"> <ClCompile> - <PrecompiledHeader> - </PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <SDLCheck>true</SDLCheck> - <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <ConformanceMode>true</ConformanceMode> - <LanguageStandard>stdcpp17</LanguageStandard> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</TreatWarningAsError> + <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</TreatWarningAsError> + <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</TreatWarningAsError> + <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">stdcpp17</LanguageStandard> + <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">stdcpp17</LanguageStandard> + <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">stdcpp17</LanguageStandard> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> - <TreatWarningAsError>true</TreatWarningAsError> </ClCompile> <Link> <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> + <GenerateWindowsMetadata>false</GenerateWindowsMetadata> </Link> </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> <ClCompile> - <PrecompiledHeader> - </PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> - <SDLCheck>true</SDLCheck> - <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <ConformanceMode>true</ConformanceMode> - <LanguageStandard>stdcpp17</LanguageStandard> - <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> - <TreatWarningAsError>true</TreatWarningAsError> + <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</TreatWarningAsError> + <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">stdcpp17</LanguageStandard> </ClCompile> - <Link> - <SubSystem>Console</SubSystem> - <GenerateDebugInformation>true</GenerateDebugInformation> - </Link> </ItemDefinitionGroup> - <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ItemDefinitionGroup Condition="'$(Configuration)'=='Release'"> <ClCompile> - <PrecompiledHeader> - </PrecompiledHeader> - <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> - <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> - <ConformanceMode>true</ConformanceMode> - <LanguageStandard>stdcpp17</LanguageStandard> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</TreatWarningAsError> + <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</TreatWarningAsError> + <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</TreatWarningAsError> + <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</TreatWarningAsError> + <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">stdcpp17</LanguageStandard> + <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">stdcpp17</LanguageStandard> + <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">stdcpp17</LanguageStandard> + <LanguageStandard Condition="'$(Configuration)|$(Platform)'=='Release|x64'">stdcpp17</LanguageStandard> </ClCompile> <Link> <SubSystem>Console</SubSystem> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> - <GenerateDebugInformation>true</GenerateDebugInformation> + <GenerateWindowsMetadata>false</GenerateWindowsMetadata> </Link> </ItemDefinitionGroup> <ItemGroup> @@ -174,6 +156,6 @@ <ImportGroup Label="ExtensionTargets"> </ImportGroup> <Target Name="AfterBuild"> - <Copy SourceFiles="$(OutDir)\$(TargetFileName)" DestinationFolder="$(OutDir)\..\AppInstallerCliTests" ContinueOnError="true" /> + <Copy SourceFiles="$(OutDir)\$(TargetFileName)" DestinationFolder="$(OutDir)\..\AppInstallerCLITests" ContinueOnError="true" /> </Target> </Project> \ No newline at end of file diff --git a/src/AppInstallerTestExeInstaller/main.cpp b/src/AppInstallerTestExeInstaller/main.cpp @@ -4,17 +4,30 @@ #include <iostream> #include <fstream> #include <filesystem> +#include <sstream> +// The installer simply prints all args to an output file int main(int argc, const char** argv) { - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestExeInstalled.txt"; - std::ofstream file(temp, std::ofstream::out); + std::filesystem::path outFilePath = std::filesystem::temp_directory_path(); + std::stringstream outContent; for (int i = 1; i < argc; i++) { - file << argv[i] << ' '; + outContent << argv[i] << ' '; + + // Supports custom install path. + if (_stricmp(argv[i], "/InstallDir") == 0 && ++i < argc) + { + outFilePath = argv[i]; + outContent << argv[i] << ' '; + } } + outFilePath /= "TestExeInstalled.txt"; + std::ofstream file(outFilePath, std::ofstream::out); + + file << outContent.str(); + file.close(); } diff --git a/src/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.vdproj b/src/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.vdproj @@ -0,0 +1,709 @@ +"DeployProject" +{ +"VSVersion" = "3:800" +"ProjectType" = "8:{978C614F-708E-4E1A-B201-565925725DBA}" +"IsWebType" = "8:FALSE" +"ProjectName" = "8:AppInstallerTestMsiInstaller" +"LanguageId" = "3:1033" +"CodePage" = "3:1252" +"UILanguageId" = "3:1033" +"SccProjectName" = "8:" +"SccLocalPath" = "8:" +"SccAuxPath" = "8:" +"SccProvider" = "8:" + "Hierarchy" + { + } + "Configurations" + { + "Debug" + { + "DisplayName" = "8:Debug" + "IsDebugOnly" = "11:TRUE" + "IsReleaseOnly" = "11:FALSE" + "OutputFilename" = "8:Debug\\AppInstallerTestMsiInstaller.msi" + "PackageFilesAs" = "3:2" + "PackageFileSize" = "3:-2147483648" + "CabType" = "3:1" + "Compression" = "3:2" + "SignOutput" = "11:FALSE" + "CertificateFile" = "8:" + "PrivateKeyFile" = "8:" + "TimeStampServer" = "8:" + "InstallerBootstrapper" = "3:2" + "BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}" + { + "Enabled" = "11:TRUE" + "PromptEnabled" = "11:TRUE" + "PrerequisitesLocation" = "2:1" + "Url" = "8:" + "ComponentsUrl" = "8:" + "Items" + { + "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2" + { + "Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)" + "ProductCode" = "8:.NETFramework,Version=v4.7.2" + } + } + } + } + "Release" + { + "DisplayName" = "8:Release" + "IsDebugOnly" = "11:FALSE" + "IsReleaseOnly" = "11:TRUE" + "OutputFilename" = "8:Release\\AppInstallerTestMsiInstaller.msi" + "PackageFilesAs" = "3:2" + "PackageFileSize" = "3:-2147483648" + "CabType" = "3:1" + "Compression" = "3:2" + "SignOutput" = "11:FALSE" + "CertificateFile" = "8:" + "PrivateKeyFile" = "8:" + "TimeStampServer" = "8:" + "InstallerBootstrapper" = "3:2" + "BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}" + { + "Enabled" = "11:TRUE" + "PromptEnabled" = "11:TRUE" + "PrerequisitesLocation" = "2:1" + "Url" = "8:" + "ComponentsUrl" = "8:" + "Items" + { + "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2" + { + "Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)" + "ProductCode" = "8:.NETFramework,Version=v4.7.2" + } + } + } + } + } + "Deployable" + { + "CustomAction" + { + } + "DefaultFeature" + { + "Name" = "8:DefaultFeature" + "Title" = "8:" + "Description" = "8:" + } + "ExternalPersistence" + { + "LaunchCondition" + { + } + } + "File" + { + } + "FileType" + { + } + "Folder" + { + "{3C67513D-01DD-4637-8A68-80971EB9504F}:_3FD954FA64934D1C9D4F66157C4603AF" + { + "DefaultLocation" = "8:[ProgramFilesFolder][Manufacturer]\\[ProductName]" + "Name" = "8:#1925" + "AlwaysCreate" = "11:FALSE" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Property" = "8:TARGETDIR" + "Folders" + { + } + } + "{1525181F-901A-416C-8A58-119130FE478E}:_7CE3351BC05D486393C4720064B75896" + { + "Name" = "8:#1916" + "AlwaysCreate" = "11:FALSE" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Property" = "8:DesktopFolder" + "Folders" + { + } + } + "{1525181F-901A-416C-8A58-119130FE478E}:_9537533CAD3E4467B320694E22A39C4C" + { + "Name" = "8:#1919" + "AlwaysCreate" = "11:FALSE" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Property" = "8:ProgramMenuFolder" + "Folders" + { + } + } + } + "LaunchCondition" + { + } + "Locator" + { + } + "MsiBootstrapper" + { + "LangId" = "3:1033" + "RequiresElevation" = "11:FALSE" + } + "Product" + { + "Name" = "8:Microsoft Visual Studio" + "ProductName" = "8:AppInstallerTestMsiInstaller" + "ProductCode" = "8:{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}" + "PackageCode" = "8:{B21B22D8-5E23-401C-84FA-EE074DB23F8F}" + "UpgradeCode" = "8:{B9CF9DD5-D46F-4CE0-BFC9-633BF9D3A6F4}" + "AspNetVersion" = "8:4.0.30319.0" + "RestartWWWService" = "11:FALSE" + "RemovePreviousVersions" = "11:FALSE" + "DetectNewerInstalledVersion" = "11:TRUE" + "InstallAllUsers" = "11:FALSE" + "ProductVersion" = "8:1.0.0" + "Manufacturer" = "8:AppInstallerCLI" + "ARPHELPTELEPHONE" = "8:" + "ARPHELPLINK" = "8:" + "Title" = "8:AppInstallerTestMsiInstaller" + "Subject" = "8:" + "ARPCONTACT" = "8:AppInstallerCLI" + "Keywords" = "8:" + "ARPCOMMENTS" = "8:" + "ARPURLINFOABOUT" = "8:" + "ARPPRODUCTICON" = "8:" + "ARPIconIndex" = "3:0" + "SearchPath" = "8:" + "UseSystemSearchPath" = "11:TRUE" + "TargetPlatform" = "3:0" + "PreBuildEvent" = "8:" + "PostBuildEvent" = "8:" + "RunPostBuildEvent" = "3:0" + } + "Registry" + { + "HKLM" + { + "Keys" + { + "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_951AD5F7C8424CCAB911C2469F382BA0" + { + "Name" = "8:Software" + "Condition" = "8:" + "AlwaysCreate" = "11:FALSE" + "DeleteAtUninstall" = "11:FALSE" + "Transitive" = "11:FALSE" + "Keys" + { + "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_1245A15ED08E4D31AC11FE6C6AF43AA6" + { + "Name" = "8:[Manufacturer]" + "Condition" = "8:" + "AlwaysCreate" = "11:FALSE" + "DeleteAtUninstall" = "11:FALSE" + "Transitive" = "11:FALSE" + "Keys" + { + } + "Values" + { + } + } + } + "Values" + { + } + } + } + } + "HKCU" + { + "Keys" + { + "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_A0E8E016B459429AB33F8A8041B409CF" + { + "Name" = "8:Software" + "Condition" = "8:" + "AlwaysCreate" = "11:FALSE" + "DeleteAtUninstall" = "11:FALSE" + "Transitive" = "11:FALSE" + "Keys" + { + "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_6F792619739B49D49348459164023C26" + { + "Name" = "8:[Manufacturer]" + "Condition" = "8:" + "AlwaysCreate" = "11:FALSE" + "DeleteAtUninstall" = "11:FALSE" + "Transitive" = "11:FALSE" + "Keys" + { + } + "Values" + { + } + } + } + "Values" + { + } + } + } + } + "HKCR" + { + "Keys" + { + } + } + "HKU" + { + "Keys" + { + } + } + "HKPU" + { + "Keys" + { + } + } + } + "Sequences" + { + } + "Shortcut" + { + } + "UserInterface" + { + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_07F5BD380D034D2788D0E26EC943AF9E" + { + "Name" = "8:#1900" + "Sequence" = "3:2" + "Attributes" = "3:1" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_0F2C662EB54446EEBB450AB41783B804" + { + "Sequence" = "3:300" + "DisplayName" = "8:Confirm Installation" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdAdminConfirmDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_5B669F9D2835440A97450ED9A579EBB0" + { + "Sequence" = "3:100" + "DisplayName" = "8:Welcome" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdAdminWelcomeDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "CopyrightWarning" + { + "Name" = "8:CopyrightWarning" + "DisplayName" = "8:#1002" + "Description" = "8:#1102" + "Type" = "3:3" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1202" + "DefaultValue" = "8:#1202" + "UsePlugInResources" = "11:TRUE" + } + "Welcome" + { + "Name" = "8:Welcome" + "DisplayName" = "8:#1003" + "Description" = "8:#1103" + "Type" = "3:3" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1203" + "DefaultValue" = "8:#1203" + "UsePlugInResources" = "11:TRUE" + } + } + } + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_A1B6CF1DE99744E9BA298D9516F4FBBB" + { + "Sequence" = "3:200" + "DisplayName" = "8:Installation Folder" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdAdminFolderDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_31DFC87DB7EB49BBBE91080D4E17EBC3" + { + "Name" = "8:#1901" + "Sequence" = "3:2" + "Attributes" = "3:2" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_B0FC86DC72004463B28043005E93933D" + { + "Sequence" = "3:100" + "DisplayName" = "8:Progress" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdAdminProgressDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "ShowProgress" + { + "Name" = "8:ShowProgress" + "DisplayName" = "8:#1009" + "Description" = "8:#1109" + "Type" = "3:5" + "ContextData" = "8:1;True=1;False=0" + "Attributes" = "3:0" + "Setting" = "3:0" + "Value" = "3:1" + "DefaultValue" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_49F7C7303D464C18981485552AA18870" + { + "Name" = "8:#1901" + "Sequence" = "3:1" + "Attributes" = "3:2" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_78EF5C34DBDB4BC88F2789CBE1F17043" + { + "Sequence" = "3:100" + "DisplayName" = "8:Progress" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdProgressDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "ShowProgress" + { + "Name" = "8:ShowProgress" + "DisplayName" = "8:#1009" + "Description" = "8:#1109" + "Type" = "3:5" + "ContextData" = "8:1;True=1;False=0" + "Attributes" = "3:0" + "Setting" = "3:0" + "Value" = "3:1" + "DefaultValue" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_68474A3513F54E56B4D451594D7639DC" + { + "Name" = "8:#1902" + "Sequence" = "3:2" + "Attributes" = "3:3" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_3EC2CCC643CA4EEB9DAFF092520273EE" + { + "Sequence" = "3:100" + "DisplayName" = "8:Finished" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdAdminFinishedDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_81A3A282324C416DB8EA6A5FABEAD776" + { + "UseDynamicProperties" = "11:FALSE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdUserInterface.wim" + } + "{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_869386ADC36F4DAE92F694770A481917" + { + "UseDynamicProperties" = "11:FALSE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdBasicDialogs.wim" + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_9E39D7A2A2BB4185AE8290AF159CBDE8" + { + "Name" = "8:#1902" + "Sequence" = "3:1" + "Attributes" = "3:3" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_18EEDC13BA92450EA46E5CFAD31164FE" + { + "Sequence" = "3:100" + "DisplayName" = "8:Finished" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdFinishedDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "UpdateText" + { + "Name" = "8:UpdateText" + "DisplayName" = "8:#1058" + "Description" = "8:#1158" + "Type" = "3:15" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1258" + "DefaultValue" = "8:#1258" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_A1D7AFF21DC048769B063F0ABB6BAD93" + { + "Name" = "8:#1900" + "Sequence" = "3:1" + "Attributes" = "3:1" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_6AFCB2A470844F02AC97138D2C4255B5" + { + "Sequence" = "3:300" + "DisplayName" = "8:Confirm Installation" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdConfirmDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_73B000DF31AF44D789DC52D2EB3AC63B" + { + "Sequence" = "3:200" + "DisplayName" = "8:Installation Folder" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdFolderDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "InstallAllUsersVisible" + { + "Name" = "8:InstallAllUsersVisible" + "DisplayName" = "8:#1059" + "Description" = "8:#1159" + "Type" = "3:5" + "ContextData" = "8:1;True=1;False=0" + "Attributes" = "3:0" + "Setting" = "3:0" + "Value" = "3:1" + "DefaultValue" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_E411887483814552AF4BF33BBB4F1B1D" + { + "Sequence" = "3:100" + "DisplayName" = "8:Welcome" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:<VsdDialogDir>\\VsdWelcomeDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "CopyrightWarning" + { + "Name" = "8:CopyrightWarning" + "DisplayName" = "8:#1002" + "Description" = "8:#1102" + "Type" = "3:3" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1202" + "DefaultValue" = "8:#1202" + "UsePlugInResources" = "11:TRUE" + } + "Welcome" + { + "Name" = "8:Welcome" + "DisplayName" = "8:#1003" + "Description" = "8:#1103" + "Type" = "3:3" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1203" + "DefaultValue" = "8:#1203" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + } + "MergeModule" + { + } + "ProjectOutput" + { + "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_9892A03006D742BB8C58FDF406605C8B" + { + "SourcePath" = "8:..\\x86\\Release\\AppInstallerTestExeInstaller\\AppInstallerTestExeInstaller.exe" + "TargetName" = "8:" + "Tag" = "8:" + "Folder" = "8:_3FD954FA64934D1C9D4F66157C4603AF" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Vital" = "11:TRUE" + "ReadOnly" = "11:FALSE" + "Hidden" = "11:FALSE" + "System" = "11:FALSE" + "Permanent" = "11:FALSE" + "SharedLegacy" = "11:FALSE" + "PackageAs" = "3:1" + "Register" = "3:1" + "Exclude" = "11:FALSE" + "IsDependency" = "11:FALSE" + "IsolateTo" = "8:" + "ProjectOutputGroupRegister" = "3:1" + "OutputConfiguration" = "8:Release|Win32" + "OutputGroupCanonicalName" = "8:Built" + "OutputProjectGuid" = "8:{6CB84692-5994-407D-B9BD-9216AF77FE83}" + "ShowKeyOutput" = "11:TRUE" + "ExcludeFilters" + { + } + } + } + } +} diff --git a/src/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.wapproj b/src/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.wapproj @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'"> + <VisualStudioVersion>15.0</VisualStudioVersion> + </PropertyGroup> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|x86"> + <Configuration>Debug</Configuration> + <Platform>x86</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x86"> + <Configuration>Release</Configuration> + <Platform>x86</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|ARM"> + <Configuration>Debug</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|ARM"> + <Configuration>Release</Configuration> + <Platform>ARM</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|ARM64"> + <Configuration>Debug</Configuration> + <Platform>ARM64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|ARM64"> + <Configuration>Release</Configuration> + <Platform>ARM64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|AnyCPU"> + <Configuration>Debug</Configuration> + <Platform>AnyCPU</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|AnyCPU"> + <Configuration>Release</Configuration> + <Platform>AnyCPU</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup> + <WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath> + </PropertyGroup> + <Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" /> + <PropertyGroup> + <ProjectGuid>3e2cba31-ceba-4d63-bf52-49c0718e19ea</ProjectGuid> + <TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion> + <TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion> + <DefaultLanguage>en-US</DefaultLanguage> + <AppxPackageSigningEnabled>false</AppxPackageSigningEnabled> + <EntryPointProjectUniqueName>..\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.vcxproj</EntryPointProjectUniqueName> + </PropertyGroup> + <ItemGroup> + <AppxManifest Include="Package.appxmanifest"> + <SubType>Designer</SubType> + </AppxManifest> + </ItemGroup> + <ItemGroup> + <Content Include="Images\SplashScreen.scale-200.png" /> + <Content Include="Images\LockScreenLogo.scale-200.png" /> + <Content Include="Images\Square150x150Logo.scale-200.png" /> + <Content Include="Images\Square44x44Logo.scale-200.png" /> + <Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" /> + <Content Include="Images\StoreLogo.png" /> + <Content Include="Images\Wide310x150Logo.scale-200.png" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.vcxproj" /> + </ItemGroup> + <Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" /> +</Project>+ \ No newline at end of file diff --git a/src/AppInstallerTestMsixInstaller/Images/LockScreenLogo.scale-200.png b/src/AppInstallerTestMsixInstaller/Images/LockScreenLogo.scale-200.png Binary files differ. diff --git a/src/AppInstallerTestMsixInstaller/Images/SplashScreen.scale-200.png b/src/AppInstallerTestMsixInstaller/Images/SplashScreen.scale-200.png Binary files differ. diff --git a/src/AppInstallerTestMsixInstaller/Images/Square150x150Logo.scale-200.png b/src/AppInstallerTestMsixInstaller/Images/Square150x150Logo.scale-200.png Binary files differ. diff --git a/src/AppInstallerTestMsixInstaller/Images/Square44x44Logo.scale-200.png b/src/AppInstallerTestMsixInstaller/Images/Square44x44Logo.scale-200.png Binary files differ. diff --git a/src/AppInstallerTestMsixInstaller/Images/Square44x44Logo.targetsize-24_altform-unplated.png b/src/AppInstallerTestMsixInstaller/Images/Square44x44Logo.targetsize-24_altform-unplated.png Binary files differ. diff --git a/src/AppInstallerTestMsixInstaller/Images/StoreLogo.png b/src/AppInstallerTestMsixInstaller/Images/StoreLogo.png Binary files differ. diff --git a/src/AppInstallerTestMsixInstaller/Images/Wide310x150Logo.scale-200.png b/src/AppInstallerTestMsixInstaller/Images/Wide310x150Logo.scale-200.png Binary files differ. diff --git a/src/AppInstallerTestMsixInstaller/Package.appxmanifest b/src/AppInstallerTestMsixInstaller/Package.appxmanifest @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> + +<Package + xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" + xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" + xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" + IgnorableNamespaces="uap rescap"> + + <Identity + Name="6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e" + Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" + Version="1.0.0.0" /> + + <Properties> + <DisplayName>AppInstallerTestMsixInstaller</DisplayName> + <PublisherDisplayName>AppInstallerTest</PublisherDisplayName> + <Logo>Images\StoreLogo.png</Logo> + </Properties> + + <Dependencies> + <TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" /> + <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14393.0" MaxVersionTested="10.0.14393.0" /> + </Dependencies> + + <Resources> + <Resource Language="x-generate"/> + </Resources> + + <Applications> + <Application Id="App" + Executable="$targetnametoken$.exe" + EntryPoint="$targetentrypoint$"> + <uap:VisualElements + DisplayName="AppInstallerTestMsixInstaller" + Description="AppInstallerTestMsixInstaller" + BackgroundColor="transparent" + Square150x150Logo="Images\Square150x150Logo.png" + Square44x44Logo="Images\Square44x44Logo.png"> + <uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" /> + <uap:SplashScreen Image="Images\SplashScreen.png" /> + </uap:VisualElements> + </Application> + </Applications> + + <Capabilities> + <Capability Name="internetClient" /> + <rescap:Capability Name="runFullTrust" /> + </Capabilities> +</Package> diff --git a/src/IndexCreationTool/AppInstallerSQLiteIndexUtilWrapper.cs b/src/IndexCreationTool/AppInstallerSQLiteIndexUtilWrapper.cs @@ -0,0 +1,265 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace IndexCreationTool +{ + using System; + using System.IO; + using System.Runtime.InteropServices; + + /// <summary> + /// Wrapper class around AppInstallerSQLiteIndexUtil index modifier native implementation. + /// </summary> + internal class AppInstallerSQLiteIndexUtilWrapper : IDisposable + { + /// <summary> + /// Dll name. + /// </summary> + public const string DllName = @"AppInstallerSQLiteIndexUtil.dll"; + + private const uint LatestVersion = unchecked((uint)-1); + + private IntPtr indexHandle; + + /// <summary> + /// Initializes a new instance of the <see cref="AppInstallerSQLiteIndexUtilWrapper"/> class. + /// </summary> + /// <param name="indexHandle">Handle of the index.</param> + private AppInstallerSQLiteIndexUtilWrapper(IntPtr indexHandle) + { + this.indexHandle = indexHandle; + } + + /// <summary> + /// Creates a new index file in the specified path. + /// </summary> + /// <param name="indexFile">Index file to create.</param> + /// <returns>Instance of AppInstallerSQLiteIndexUtilWrapper.</returns> + public static AppInstallerSQLiteIndexUtilWrapper Create(string indexFile) + { + try + { + AppInstallerSQLiteIndexCreate( + indexFile, + AppInstallerSQLiteIndexUtilWrapper.LatestVersion, + AppInstallerSQLiteIndexUtilWrapper.LatestVersion, + out IntPtr index); + return new AppInstallerSQLiteIndexUtilWrapper(index); + } + catch (Exception e) + { + Console.WriteLine($"Error to create {indexFile}. {Environment.NewLine}{e.ToString()}"); + throw; + } + } + + /// <summary> + /// Open the index. + /// </summary> + /// <param name="indexFile">Index file to open.</param> + /// <returns>Instance of AppInstallerSQLiteIndexUtilWrapper.</returns> + public static AppInstallerSQLiteIndexUtilWrapper Open(string indexFile) + { + try + { + AppInstallerSQLiteIndexOpen(indexFile, out IntPtr index); + return new AppInstallerSQLiteIndexUtilWrapper(index); + } + catch (Exception e) + { + Console.WriteLine($"Error to open {indexFile}. {Environment.NewLine}{e.ToString()}"); + throw; + } + } + + /// <summary> + /// Adds manifest to index. + /// </summary> + /// <param name="manifestPath">Manifest to add.</param> + /// <param name="relativePath">Path of the manifest in the repository.</param> + public void AddManifest(string manifestPath, string relativePath) + { + try + { + Console.WriteLine($"Adding manifest {manifestPath} on index file."); + AppInstallerSQLiteIndexAddManifest(this.indexHandle, manifestPath, relativePath); + return; + } + catch (Exception e) + { + Console.WriteLine($"Error to add manifest {manifestPath} with relative path {relativePath}. {Environment.NewLine}{e.ToString()}"); + throw; + } + } + + /// <summary> + /// Updates manifest in the index. + /// </summary> + /// <param name="manifestPath">Path to manifest to modify.</param> + /// <param name="relativePath">Path of the manifest in the repository.</param> + /// <returns>True if index was modified.</returns> + public bool UpdateManifest(string manifestPath, string relativePath) + { + try + { + Console.WriteLine($"Updating manifest {manifestPath} on index file."); + + // For now, modifying a manifest implies that the file didn't got moved in the repository. So only + // contents of the file are modified. However, in the future we might support moving which requires + // oldManifestPath, oldRelativePath, newManigestPath and oldManifestPath. + AppInstallerSQLiteIndexUpdateManifest( + this.indexHandle, + manifestPath, + relativePath, + out bool indexModified); + + if (!indexModified) + { + // This means that some of the attributes that don't get indexed, like Description, where + // were modified, so the index doesn't got updated. + Console.WriteLine($"Manifest {manifestPath} didn't result in a modification to the index."); + } + + return indexModified; + } + catch (Exception e) + { + Console.WriteLine($"Error to update manifest {manifestPath} with relative path {relativePath}. {Environment.NewLine}{e.ToString()}"); + throw; + } +} + + /// <summary> + /// Delete manifest from index. + /// </summary> + /// <param name="manifestPath">Path to manifest to modify.</param> + /// <param name="relativePath">Path of the manifest in the repository.</param> + public void RemoveManifest(string manifestPath, string relativePath) + { + try + { + AppInstallerSQLiteIndexRemoveManifest(this.indexHandle, manifestPath, relativePath); + return; + } + catch (Exception e) + { + Console.WriteLine($"Error to remove manifest {manifestPath} with relative path {relativePath}. {Environment.NewLine}{e.ToString()}"); + throw; + } + } + + /// <summary> + /// Wrapper for AppInstallerSQLiteIndexPrepareForPackaging. + /// </summary> + public void PrepareForPackaging() + { + try + { + AppInstallerSQLiteIndexPrepareForPackaging(this.indexHandle); + return; + } + catch (Exception e) + { + Console.WriteLine($"Error to prepare for packaging. {Environment.NewLine}{e.ToString()}"); + throw; + } + } + + /// <summary> + /// Dispose method. + /// </summary> + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Dispose method to dispose the Git desktop process runner. + /// </summary> + /// <param name="disposing">Bool value indicating if Dispose is being run.</param> + protected void Dispose(bool disposing) + { + if (disposing) + { + if (this.indexHandle != null) + { + AppInstallerSQLiteIndexClose(this.indexHandle); + } + } + } + + /// <summary> + /// Creates a new index file at filePath with the given version. + /// </summary> + /// <param name="filePath">File path to create index.</param> + /// <param name="majorVersion">Major version.</param> + /// <param name="minorVersion">Minor version.</param> + /// <param name="index">Out handle of the index.</param> + /// <returns>HRESULT.</returns> + [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] + private static extern IntPtr AppInstallerSQLiteIndexCreate(string filePath, uint majorVersion, uint minorVersion, out IntPtr index); + + /// <summary> + /// Opens an existing index at filePath. + /// </summary> + /// <param name="filePath">File path of index.</param> + /// <param name="index">Out handle of the index.</param> + /// <returns>HRESULT.</returns> + [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] + private static extern IntPtr AppInstallerSQLiteIndexOpen(string filePath, out IntPtr index); + + /// <summary> + /// Closes the index. + /// </summary> + /// <param name="index">Handle of the index.</param> + /// <returns>HRESULT.</returns> + [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] + private static extern IntPtr AppInstallerSQLiteIndexClose(IntPtr index); + + /// <summary> + /// Adds the manifest at the repository relative path to the index. + /// If the function succeeds, the manifest has been added. + /// </summary> + /// <param name="index">Handle of the index.</param> + /// <param name="manifestPath">Manifest to add.</param> + /// <param name="relativePath">Path of the manifest in the container.</param> + /// <returns>HRESULT.</returns> + [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] + private static extern IntPtr AppInstallerSQLiteIndexAddManifest(IntPtr index, string manifestPath, string relativePath); + + /// <summary> + /// Updates the manifest at the repository relative path in the index. + /// The out value indicates whether the index was modified by the function. + /// </summary> + /// <param name="index">Handle of the index.</param> + /// <param name="manifestPath">Old manifest path.</param> + /// <param name="relativePath">Old relative path in the container.</param> + /// <param name="indexModified">Out bool if the index is modified.</param> + /// <returns>HRESULT.</returns> + [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] + private static extern IntPtr AppInstallerSQLiteIndexUpdateManifest( + IntPtr index, + string manifestPath, + string relativePath, + [MarshalAs(UnmanagedType.U1)] out bool indexModified); + + /// <summary> + /// Removes the manifest at the repository relative path from the index. + /// </summary> + /// <param name="index">Index handle.</param> + /// <param name="manifestPath">Manifest path to remove.</param> + /// <param name="relativePath">Relative path in the container.</param> + /// <returns>HRESULT.</returns> + [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] + private static extern IntPtr AppInstallerSQLiteIndexRemoveManifest(IntPtr index, string manifestPath, string relativePath); + + /// <summary> + /// Removes data that is no longer needed for an index that is to be published. + /// </summary> + /// <param name="index">Index handle.</param> + /// <returns>HRESULT.</returns> + [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] + private static extern IntPtr AppInstallerSQLiteIndexPrepareForPackaging(IntPtr index); + } +} diff --git a/src/IndexCreationTool/IndexCreationTool.csproj b/src/IndexCreationTool/IndexCreationTool.csproj @@ -0,0 +1,9 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>netcoreapp3.1</TargetFramework> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\IndexCreationTool\</OutDir> + </PropertyGroup> + +</Project> diff --git a/src/IndexCreationTool/Program.cs b/src/IndexCreationTool/Program.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace IndexCreationTool +{ + using System; + using System.Diagnostics; + using System.IO; + + class Program + { + public const string IndexName = @"index.db"; + public const string IndexPackageName = @"index.msix"; + + static void Main(string[] args) + { + string rootDir = string.Empty; + string appxManifestPath = string.Empty; + string certPath = string.Empty; + + for (int i = 0; i < args.Length; i++) + { + if (args[i] == "-d" && ++i < args.Length) + { + rootDir = args[i]; + } + else if (args[i] == "-m" && ++i < args.Length) + { + appxManifestPath = args[i]; + } + else if (args[i] == "-c" && ++i < args.Length) + { + certPath = args[i]; + } + } + + if (string.IsNullOrEmpty(rootDir)) + { + Console.WriteLine("Usage: IndexCreationTool.exe -d <Path to search for yaml> [-m <appxmanifest for index package> [-c <cert for signing index package>]]"); + return; + } + + try + { + if (File.Exists(IndexName)) + { + File.Delete(IndexName); + } + + using (var indexHelper = AppInstallerSQLiteIndexUtilWrapper.Create(IndexName)) + { + foreach (string file in Directory.EnumerateFiles(rootDir, "*.yaml", SearchOption.AllDirectories)) + { + indexHelper.AddManifest(file, Path.GetRelativePath(rootDir, file)); + } + indexHelper.PrepareForPackaging(); + } + + if (!string.IsNullOrEmpty(appxManifestPath)) + { + using (StreamWriter outputFile = new StreamWriter("MappingFile.txt")) + { + outputFile.WriteLine("[Files]"); + outputFile.WriteLine($"\"{IndexName}\" \"{IndexName}\""); + outputFile.WriteLine($"\"{appxManifestPath}\" \"AppxManifest.xml\""); + } + + RunCommand("makeappx.exe", $"pack /f MappingFile.txt /o /nv /p {IndexPackageName}"); + + if (!string.IsNullOrEmpty(certPath)) + { + RunCommand("signtool.exe", $"sign /a /fd sha256 /f {certPath} {IndexPackageName}"); + } + } + } + catch (Exception e) + { + Console.WriteLine("Failed. Reason: " + e.Message); + } + + Environment.Exit(0); + } + + static void RunCommand(string command, string args) + { + Process p = new Process(); + p.StartInfo = new ProcessStartInfo(command, args); + p.Start(); + p.WaitForExit(); + } + } +}