Add MSI transaction tests.
Sean Hall committed
Dec 1, 2020 at 23:02 UTC
5d0434843c6f307a46fa95139c1e754221cc13af
50 files changed
+2684
-1
BurnE2ETests.sln
new
+53
@@ -0,0 +1,53 @@
1
+
2
+Microsoft Visual Studio Solution File, Format Version 12.00
3
+# Visual Studio Version 16
4
+VisualStudioVersion = 16.0.29503.13
5
+MinimumVisualStudioVersion = 10.0.40219.1
6
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestBA", "src\Utilities\TestBA\TestBA.csproj", "{04022D35-6D75-49D0-91D2-4208E09DBA6D}"
7
+EndProject
8
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.BurnE2E", "src\WixToolsetTest.BurnE2E\WixToolsetTest.BurnE2E.csproj", "{FED9D707-E5C3-4867-87B0-FABDB5EB0823}"
9
+EndProject
10
+Global
11
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
12
+ Debug|Any CPU = Debug|Any CPU
13
+ Debug|x64 = Debug|x64
14
+ Debug|x86 = Debug|x86
15
+ Release|Any CPU = Release|Any CPU
16
+ Release|x64 = Release|x64
17
+ Release|x86 = Release|x86
18
+ EndGlobalSection
19
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
20
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
22
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Debug|x64.ActiveCfg = Debug|Any CPU
23
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Debug|x64.Build.0 = Debug|Any CPU
24
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Debug|x86.ActiveCfg = Debug|Any CPU
25
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Debug|x86.Build.0 = Debug|Any CPU
26
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
27
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Release|Any CPU.Build.0 = Release|Any CPU
28
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Release|x64.ActiveCfg = Release|Any CPU
29
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Release|x64.Build.0 = Release|Any CPU
30
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Release|x86.ActiveCfg = Release|Any CPU
31
+ {04022D35-6D75-49D0-91D2-4208E09DBA6D}.Release|x86.Build.0 = Release|Any CPU
32
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Debug|Any CPU.Build.0 = Debug|Any CPU
34
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Debug|x64.ActiveCfg = Debug|Any CPU
35
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Debug|x64.Build.0 = Debug|Any CPU
36
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Debug|x86.ActiveCfg = Debug|Any CPU
37
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Debug|x86.Build.0 = Debug|Any CPU
38
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Release|Any CPU.ActiveCfg = Release|Any CPU
39
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Release|Any CPU.Build.0 = Release|Any CPU
40
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Release|x64.ActiveCfg = Release|Any CPU
41
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Release|x64.Build.0 = Release|Any CPU
42
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Release|x86.ActiveCfg = Release|Any CPU
43
+ {FED9D707-E5C3-4867-87B0-FABDB5EB0823}.Release|x86.Build.0 = Release|Any CPU
44
+ EndGlobalSection
45
+ GlobalSection(SolutionProperties) = preSolution
46
+ HideSolutionNode = FALSE
47
+ EndGlobalSection
48
+ GlobalSection(NestedProjects) = preSolution
49
+ EndGlobalSection
50
+ GlobalSection(ExtensibilityGlobals) = postSolution
51
+ SolutionGuid = {74DE2EED-ECAA-4FDD-9792-9D3B0C0C1321}
52
+ EndGlobalSection
53
+EndGlobal
README.md
+1
-1
@@ -1,2 +1,2 @@
1
-# Bal.wixext
1
+# burn-e2e-tests
2
burn-e2e-tests - End-to-end tests for WiX bundles, building real installers and running them.
appveyor.cmd
new
+11
@@ -0,0 +1,11 @@
1
+@setlocal
2
+@pushd %~dp0
3
+@set _C=Release
4
+
5
+msbuild -p:Configuration=%_C% -Restore || exit /b
6
+msbuild -p:Configuration=%_C% src\TestData -Restore || exit /b
7
+
8
+dotnet test -c %_C% --no-build src\WixToolsetTest.BurnE2E || exit /b
9
+
10
+@popd
11
+@endlocal
appveyor.yml
new
+25
@@ -0,0 +1,25 @@
1
+# Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+branches:
4
+ only:
5
+ - master
6
+
7
+image: Visual Studio 2019
8
+
9
+environment:
10
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
11
+ DOTNET_CLI_TELEMETRY_OPTOUT: 1
12
+ NUGET_XMLDOC_MODE: skip
13
+ RuntimeTestsEnabled: true
14
+ WIX_COMPRESSION_LEVEL: none
15
+
16
+build_script:
17
+ - appveyor.cmd
18
+
19
+test: off
20
+
21
+skip_tags: true
22
+
23
+on_finish:
24
+ - ps: 7z a logs.zip $env:TEMP\*.log $env:TEMP\..\*.log
25
+ - ps: Push-AppveyorArtifact logs.zip
global.json
new
+8
@@ -0,0 +1,8 @@
1
+{
2
+ "msbuild-sdks": {
3
+ "WixToolset.Sdk": "4.0.0-build-0168"
4
+ },
5
+ "sdk": {
6
+ "allowPrerelease": false
7
+ }
8
+}
nuget.config
new
+14
@@ -0,0 +1,14 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<configuration>
3
+ <packageSources>
4
+ <clear />
5
+ <add key="wixtoolset-balutil" value="https://ci.appveyor.com/nuget/wixtoolset-balutil" />
6
+ <add key="wixtoolset-bal-wixext" value="https://ci.appveyor.com/nuget/wixtoolset-bal-wixext" />
7
+ <add key="wixtoolset-dtf" value="https://ci.appveyor.com/nuget/wixtoolset-dtf" />
8
+ <add key="wixtoolset-netfx-wixext" value="https://ci.appveyor.com/nuget/wixtoolset-netfx-wixext" />
9
+ <add key="wixtoolset-tools" value="https://ci.appveyor.com/nuget/wixtoolset-tools" />
10
+ <add key="wixtoolset-util-wixext" value="https://ci.appveyor.com/nuget/wixtoolset-util-wixext" />
11
+ <add key="wixbuildtools" value="https://ci.appveyor.com/nuget/wixbuildtools" />
12
+ <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
13
+ </packageSources>
14
+</configuration>
\ No newline at end of file
src/Directory.Build.props
new
+29
@@ -0,0 +1,29 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+<!--
4
+ Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.props
5
+ then update all of the repos.
6
+-->
7
+<Project>
8
+ <PropertyGroup>
9
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
10
+ <EnableSourceLink Condition=" '$(NCrunch)' == '1' ">false</EnableSourceLink>
11
+ <MSBuildWarningsAsMessages>MSB3246</MSBuildWarningsAsMessages>
12
+
13
+ <ProjectName Condition=" '$(ProjectName)' == '' ">$(MSBuildProjectName)</ProjectName>
14
+ <BaseOutputPath>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\))</BaseOutputPath>
15
+ <BaseIntermediateOutputPath>$(BaseOutputPath)obj\$(ProjectName)\</BaseIntermediateOutputPath>
16
+ <OutputPath>$(BaseOutputPath)$(Configuration)\</OutputPath>
17
+
18
+ <Authors>WiX Toolset Team</Authors>
19
+ <Company>WiX Toolset</Company>
20
+ <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright>
21
+ <PackageLicenseExpression>MS-RL</PackageLicenseExpression>
22
+ <Product>WiX Toolset</Product>
23
+ </PropertyGroup>
24
+
25
+ <Import Project="CSharp.Build.props" Condition=" '$(MSBuildProjectExtension)'=='.csproj' and Exists('CSharp.Build.props') " />
26
+ <Import Project="Cpp.Build.props" Condition=" Exists('Cpp.Build.props') And '$(MSBuildProjectExtension)'=='.vcxproj' " />
27
+ <Import Project="Wix.Build.props" Condition=" Exists('Wix.Build.props') And '$(MSBuildProjectExtension)'=='.wixproj' " />
28
+ <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " />
29
+</Project>
src/Directory.Build.targets
new
+50
@@ -0,0 +1,50 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+<!--
4
+ Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.targets
5
+ then update all of the repos.
6
+-->
7
+<!--
8
+ Replace PackageReferences with ProjectReferences when the projects can be found in .sln.
9
+ See the original here: https://github.com/dotnet/sdk/issues/1151#issuecomment-385133284
10
+-->
11
+<Project>
12
+ <PropertyGroup>
13
+ <ReplacePackageReferences>true</ReplacePackageReferences>
14
+ <TheSolutionPath Condition=" '$(NCrunch)'=='' ">$(SolutionPath)</TheSolutionPath>
15
+ <TheSolutionPath Condition=" '$(NCrunch)'=='1' ">$(NCrunchOriginalSolutionPath)</TheSolutionPath>
16
+ </PropertyGroup>
17
+
18
+ <Choose>
19
+ <When Condition="$(ReplacePackageReferences) AND '$(TheSolutionPath)' != '' AND '$(TheSolutionPath)' != '*undefined*' AND Exists('$(TheSolutionPath)')">
20
+
21
+ <PropertyGroup>
22
+ <SolutionFileContent>$([System.IO.File]::ReadAllText($(TheSolutionPath)))</SolutionFileContent>
23
+ <SmartSolutionDir>$([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) ))</SmartSolutionDir>
24
+ <RegexPattern>(?<="[PackageName]", ")(.*)(?=", ")</RegexPattern>
25
+ </PropertyGroup>
26
+
27
+ <ItemGroup>
28
+ <!-- Keep the identity of the PackageReference -->
29
+ <SmartPackageReference Include="@(PackageReference)">
30
+ <PackageName>%(Identity)</PackageName>
31
+ <InSolution>$(SolutionFileContent.Contains('\%(Identity).csproj'))</InSolution>
32
+ </SmartPackageReference>
33
+
34
+ <!-- Filter them by mapping them to another ItemGroup using the WithMetadataValue item function -->
35
+ <PackageInSolution Include="@(SmartPackageReference->WithMetadataValue('InSolution', True))">
36
+ <Pattern>$(RegexPattern.Replace('[PackageName]','%(PackageName)') )</Pattern>
37
+ <SmartPath>$([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)'))</SmartPath>
38
+ </PackageInSolution>
39
+
40
+ <ProjectReference Include="@(PackageInSolution->'$(SmartSolutionDir)\%(SmartPath)' )"/>
41
+
42
+ <!-- Remove the package references that are now referenced as projects -->
43
+ <PackageReference Remove="@(PackageInSolution->'%(PackageName)' )"/>
44
+ </ItemGroup>
45
+
46
+ </When>
47
+ </Choose>
48
+
49
+ <Import Project="Wix.Build.targets" Condition=" Exists('Wix.Build.targets') And '$(MSBuildProjectExtension)'=='.wixproj' " />
50
+</Project>
src/TestData/MsiTransaction/BundleAv1/BundleA.wxi
new
+19
@@ -0,0 +1,19 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+<?ifndef Version?>
5
+<?define Version = 1.0.0.0?>
6
+<?endif?>
7
+
8
+<Include xmlns="http://wixtoolset.org/schemas/v4/wxs">
9
+ <Bundle Name="~$(var.TestGroupName) - Bundle A" Version="$(var.Version)" UpgradeCode="{90ED10D5-B187-4470-B498-05D80DAB729A}" Compressed="yes">
10
+ <Log Prefix="~$(var.TestGroupName)_BundleA"/>
11
+
12
+ <Variable Name="TestGroupName" Value="$(var.TestGroupName)" />
13
+
14
+ <Chain>
15
+ <PackageGroupRef Id="TestBA" />
16
+ <PackageGroupRef Id="BundlePackages" />
17
+ </Chain>
18
+ </Bundle>
19
+</Include>
src/TestData/MsiTransaction/BundleAv1/BundleAv1.wixproj
new
+16
@@ -0,0 +1,16 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <PropertyGroup>
4
+ <OutputType>Bundle</OutputType>
5
+ </PropertyGroup>
6
+ <ItemGroup>
7
+ <ProjectReference Include="..\..\TestBA\TestBAWixlib\testbawixlib.wixproj" />
8
+ <ProjectReference Include="..\PackageA\PackageA.wixproj" />
9
+ <ProjectReference Include="..\PackageBv1\PackageBv1.wixproj" />
10
+ <ProjectReference Include="..\PackageCv1\PackageCv1.wixproj" />
11
+ </ItemGroup>
12
+ <ItemGroup>
13
+ <PackageReference Include="WixToolset.Bal.wixext" Version="4.0.63" />
14
+ <PackageReference Include="WixToolset.NetFx.wixext" Version="4.0.51" />
15
+ </ItemGroup>
16
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/BundleAv1/BundleAv1.wxs
new
+15
@@ -0,0 +1,15 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+
5
+<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
6
+ <?include BundleA.wxi ?>
7
+ <Fragment>
8
+ <PackageGroup Id="BundlePackages">
9
+ <MsiPackage Id="PackageA" SourceFile="$(var.PackageA.TargetPath)" />
10
+ <RollbackBoundary Transaction='yes' />
11
+ <MsiPackage Id="PackageB" SourceFile="$(var.PackageBv1.TargetPath)" />
12
+ <MsiPackage Id="PackageC" SourceFile="$(var.PackageCv1.TargetPath)" />
13
+ </PackageGroup>
14
+ </Fragment>
15
+</Wix>
src/TestData/MsiTransaction/BundleAv2/BundleAv2.wixproj
new
+17
@@ -0,0 +1,17 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <PropertyGroup>
4
+ <OutputType>Bundle</OutputType>
5
+ <Version>2.0.0.0</Version>
6
+ </PropertyGroup>
7
+ <ItemGroup>
8
+ <ProjectReference Include="..\..\TestBA\TestBAWixlib\testbawixlib.wixproj" />
9
+ <ProjectReference Include="..\PackageBv2\PackageBv2.wixproj" />
10
+ <ProjectReference Include="..\PackageCv2\PackageCv2.wixproj" />
11
+ <ProjectReference Include="..\PackageD\PackageD.wixproj" />
12
+ </ItemGroup>
13
+ <ItemGroup>
14
+ <PackageReference Include="WixToolset.Bal.wixext" Version="4.0.63" />
15
+ <PackageReference Include="WixToolset.NetFx.wixext" Version="4.0.51" />
16
+ </ItemGroup>
17
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/BundleAv2/BundleAv2.wxs
new
+15
@@ -0,0 +1,15 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+
5
+<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
6
+ <?include ..\BundleAv1\BundleA.wxi ?>
7
+ <Fragment>
8
+ <PackageGroup Id="BundlePackages">
9
+ <MsiPackage Id="PackageD" SourceFile="$(var.PackageD.TargetPath)" />
10
+ <RollbackBoundary Transaction='yes' />
11
+ <MsiPackage Id="PackageB" SourceFile="$(var.PackageBv2.TargetPath)" />
12
+ <MsiPackage Id="PackageC" SourceFile="$(var.PackageCv2.TargetPath)" />
13
+ </PackageGroup>
14
+ </Fragment>
15
+</Wix>
src/TestData/MsiTransaction/BundleBv1/BundleB.wxi
new
+19
@@ -0,0 +1,19 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+<?ifndef Version?>
5
+<?define Version = 1.0.0.0?>
6
+<?endif?>
7
+
8
+<Include xmlns="http://wixtoolset.org/schemas/v4/wxs">
9
+ <Bundle Name="~$(var.TestGroupName) - Bundle B" Version="$(var.Version)" UpgradeCode="{552FD011-4DD6-42B2-A4C6-AD1417C829B2}" Compressed="yes">
10
+ <Log Prefix="~$(var.TestGroupName)_BundleB"/>
11
+
12
+ <Variable Name="TestGroupName" Value="$(var.TestGroupName)" />
13
+
14
+ <Chain>
15
+ <PackageGroupRef Id="TestBA" />
16
+ <PackageGroupRef Id="BundlePackages" />
17
+ </Chain>
18
+ </Bundle>
19
+</Include>
src/TestData/MsiTransaction/BundleBv1/BundleBv1.wixproj
new
+14
@@ -0,0 +1,14 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <PropertyGroup>
4
+ <OutputType>Bundle</OutputType>
5
+ </PropertyGroup>
6
+ <ItemGroup>
7
+ <ProjectReference Include="..\..\TestBA\TestBAWixlib\testbawixlib.wixproj" />
8
+ <ProjectReference Include="..\PackageBv1\PackageBv1.wixproj" />
9
+ </ItemGroup>
10
+ <ItemGroup>
11
+ <PackageReference Include="WixToolset.Bal.wixext" Version="4.0.63" />
12
+ <PackageReference Include="WixToolset.NetFx.wixext" Version="4.0.51" />
13
+ </ItemGroup>
14
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/BundleBv1/BundleBv1.wxs
new
+12
@@ -0,0 +1,12 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+
5
+<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
6
+ <?include BundleB.wxi ?>
7
+ <Fragment>
8
+ <PackageGroup Id="BundlePackages">
9
+ <MsiPackage Id="PackageB" SourceFile="$(var.PackageBv1.TargetPath)" />
10
+ </PackageGroup>
11
+ </Fragment>
12
+</Wix>
src/TestData/MsiTransaction/BundleBv2/BundleBv2.wixproj
new
+17
@@ -0,0 +1,17 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <PropertyGroup>
4
+ <OutputType>Bundle</OutputType>
5
+ <Version>2.0.0.0</Version>
6
+ </PropertyGroup>
7
+ <ItemGroup>
8
+ <ProjectReference Include="..\..\TestBA\TestBAWixlib\testbawixlib.wixproj" />
9
+ <ProjectReference Include="..\PackageA\PackageA.wixproj" />
10
+ <ProjectReference Include="..\PackageBv2\PackageBv2.wixproj" />
11
+ <ProjectReference Include="..\PackageF\PackageF.wixproj" />
12
+ </ItemGroup>
13
+ <ItemGroup>
14
+ <PackageReference Include="WixToolset.Bal.wixext" Version="4.0.63" />
15
+ <PackageReference Include="WixToolset.NetFx.wixext" Version="4.0.51" />
16
+ </ItemGroup>
17
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/BundleBv2/BundleBv2.wxs
new
+15
@@ -0,0 +1,15 @@
1
+<?xml version='1.0' encoding='utf-8'?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+
5
+<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
6
+ <?include ..\BundleBv1\BundleB.wxi ?>
7
+ <Fragment>
8
+ <PackageGroup Id="BundlePackages">
9
+ <MsiPackage Id="PackageA" SourceFile="$(var.PackageA.TargetPath)" />
10
+ <RollbackBoundary Transaction='yes' />
11
+ <MsiPackage Id="PackageB" SourceFile="$(var.PackageBv2.TargetPath)" />
12
+ <MsiPackage Id="PackageF" SourceFile="$(var.PackageF.TargetPath)" />
13
+ </PackageGroup>
14
+ </Fragment>
15
+</Wix>
src/TestData/MsiTransaction/PackageA/PackageA.wixproj
new
+9
@@ -0,0 +1,9 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <PropertyGroup>
4
+ <UpgradeCode>{7772FCDF-5FDB-497D-B5DF-C6D17D667976}</UpgradeCode>
5
+ </PropertyGroup>
6
+ <ItemGroup>
7
+ <Compile Include="..\..\Templates\Package.wxs" Link="Package.wxs" />
8
+ </ItemGroup>
9
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/PackageBv1/PackageB.props
new
+9
@@ -0,0 +1,9 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project>
3
+ <PropertyGroup>
4
+ <UpgradeCode>{EAFC0C6B-626E-415C-8132-536FBD19F49B}</UpgradeCode>
5
+ </PropertyGroup>
6
+ <ItemGroup>
7
+ <Compile Include="..\..\Templates\Package.wxs" Link="Package.wxs" />
8
+ </ItemGroup>
9
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/PackageBv1/PackageBv1.wixproj
new
+4
@@ -0,0 +1,4 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <Import Project="PackageB.props" />
4
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/PackageBv2/PackageBv2.wixproj
new
+7
@@ -0,0 +1,7 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <Import Project="..\PackageBv1\PackageB.props" />
4
+ <PropertyGroup>
5
+ <Version>2.0.0.0</Version>
6
+ </PropertyGroup>
7
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/PackageCv1/PackageC.props
new
+9
@@ -0,0 +1,9 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project>
3
+ <PropertyGroup>
4
+ <UpgradeCode>{A18BDC12-DAEC-43EE-87D1-31B2C2BC6269}</UpgradeCode>
5
+ </PropertyGroup>
6
+ <ItemGroup>
7
+ <Compile Include="..\..\Templates\Package.wxs" Link="Package.wxs" />
8
+ </ItemGroup>
9
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/PackageCv1/PackageCv1.wixproj
new
+4
@@ -0,0 +1,4 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <Import Project="PackageC.props" />
4
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/PackageCv2/PackageCv2.wixproj
new
+7
@@ -0,0 +1,7 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <Import Project="..\PackageCv1\PackageC.props" />
4
+ <PropertyGroup>
5
+ <Version>2.0.0.0</Version>
6
+ </PropertyGroup>
7
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/PackageD/PackageD.wixproj
new
+9
@@ -0,0 +1,9 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <PropertyGroup>
4
+ <UpgradeCode>{78B072D5-1C23-4895-9C4C-1B52E3C80621}</UpgradeCode>
5
+ </PropertyGroup>
6
+ <ItemGroup>
7
+ <Compile Include="..\..\Templates\Package.wxs" Link="Package.wxs" />
8
+ </ItemGroup>
9
+</Project>
\ No newline at end of file
src/TestData/MsiTransaction/PackageF/PackageF.wixproj
new
+12
@@ -0,0 +1,12 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <PropertyGroup>
4
+ <UpgradeCode>{3D59F8F2-8AC5-403E-B6F7-453870DE7063}</UpgradeCode>
5
+ </PropertyGroup>
6
+ <ItemGroup>
7
+ <Compile Include="..\..\Templates\PackageFail.wxs" Link="PackageFail.wxs" />
8
+ </ItemGroup>
9
+ <ItemGroup>
10
+ <PackageReference Include="WixToolset.Util.wixext" Version="4.0.65" />
11
+ </ItemGroup>
12
+</Project>
\ No newline at end of file
src/TestData/Templates/Package.wxs
new
+52
@@ -0,0 +1,52 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+
3
+
4
+
5
+<?ifndef Version?>
6
+<?define Version = 1.0.0.0?>
7
+<?endif?>
8
+
9
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
10
+ <Package Name="~$(var.TestGroupName) - $(var.PackageName)" Language="1033" Version="$(var.Version)" Manufacturer="Microsoft Corporation" UpgradeCode="$(var.UpgradeCode)" Compressed="yes">
11
+
12
+
13
+ <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
14
+ <MediaTemplate EmbedCab="yes" />
15
+
16
+ <Property Id="MsiLogging" Value="voicewarmup" />
17
+ <PropertyRef Id="TestVersion" />
18
+
19
+ <Feature Id="Complete" Level="1">
20
+ <ComponentRef Id="FileComponent" />
21
+ <ComponentRef Id="RegistryComponent" />
22
+ </Feature>
23
+ </Package>
24
+
25
+ <Fragment>
26
+ <Directory Id="TARGETDIR" Name="SourceDir">
27
+ <Directory Id="ProgramFilesFolder">
28
+ <Directory Id="WixDir" Name="~Test WiX">
29
+ <Directory Id="TestDir" Name="$(var.TestGroupName)">
30
+ <Directory Id="INSTALLFOLDER" Name="$(var.PackageName)" />
31
+ </Directory>
32
+ </Directory>
33
+ </Directory>
34
+ </Directory>
35
+ </Fragment>
36
+
37
+ <Fragment>
38
+ <Component Id="FileComponent" Directory="INSTALLFOLDER">
39
+ <File Source="$(sys.SOURCEFILEPATH)" />
40
+ </Component>
41
+ </Fragment>
42
+
43
+ <Fragment>
44
+ <Component Id="RegistryComponent" Directory="INSTALLFOLDER">
45
+ <RegistryValue Root="HKLM" Key="Software\WiX\Tests\$(var.TestGroupName)" Name="$(var.PackageName)" Value="!(bind.Property.TestVersion)" Type="string" />
46
+ </Component>
47
+ </Fragment>
48
+
49
+ <Fragment>
50
+ <Property Id="TestVersion" Value="$(var.Version)" />
51
+ </Fragment>
52
+</Wix>
src/TestData/Templates/PackageFail.wxs
new
+55
@@ -0,0 +1,55 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+
3
+
4
+
5
+<?ifndef Version?>
6
+<?define Version = 1.0.0.0?>
7
+<?endif?>
8
+
9
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
10
+ <Package Name="~$(var.TestGroupName) - $(var.PackageName)" Language="1033" Version="$(var.Version)" Manufacturer="Microsoft Corporation" UpgradeCode="$(var.UpgradeCode)" Compressed="yes">
11
+
12
+
13
+ <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
14
+ <MediaTemplate EmbedCab="yes" />
15
+
16
+ <Property Id="MsiLogging" Value="voicewarmup" />
17
+ <PropertyRef Id="TestVersion" />
18
+
19
+ <Feature Id="Complete" Level="1">
20
+ <ComponentRef Id="FileComponent" />
21
+ <ComponentRef Id="RegistryComponent" />
22
+ </Feature>
23
+
24
+ <util:FailWhenDeferred />
25
+ <Property Id="WIXFAILWHENDEFERRED" Value="1" />
26
+ </Package>
27
+
28
+ <Fragment>
29
+ <Directory Id="TARGETDIR" Name="SourceDir">
30
+ <Directory Id="ProgramFilesFolder">
31
+ <Directory Id="WixDir" Name="~Test WiX">
32
+ <Directory Id="TestDir" Name="$(var.TestGroupName)">
33
+ <Directory Id="INSTALLFOLDER" Name="$(var.PackageName)" />
34
+ </Directory>
35
+ </Directory>
36
+ </Directory>
37
+ </Directory>
38
+ </Fragment>
39
+
40
+ <Fragment>
41
+ <Component Id="FileComponent" Directory="INSTALLFOLDER">
42
+ <File Source="$(sys.SOURCEFILEPATH)" />
43
+ </Component>
44
+ </Fragment>
45
+
46
+ <Fragment>
47
+ <Component Id="RegistryComponent" Directory="INSTALLFOLDER">
48
+ <RegistryValue Root="HKLM" Key="Software\WiX\Tests\$(var.TestGroupName)" Name="$(var.PackageName)" Value="!(bind.Property.TestVersion)" Type="string" />
49
+ </Component>
50
+ </Fragment>
51
+
52
+ <Fragment>
53
+ <Property Id="TestVersion" Value="$(var.Version)" />
54
+ </Fragment>
55
+</Wix>
src/TestData/TestBA/TestBAWixlib/TestBA.wxs
new
+15
@@ -0,0 +1,15 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3
+ <Fragment>
4
+ <BootstrapperApplicationRef Id='ManagedBootstrapperApplicationHost'>
5
+ <Payload Name='WixToolset.Mba.Host.config' SourceFile='!(bindpath.x86)\TestBA.BootstrapperCore.config' />
6
+ <Payload SourceFile='!(bindpath.x86)\TestBA.dll' />
7
+ <Payload SourceFile="!(bindpath.x86)\mbanative.dll" />
8
+ <Payload SourceFile="!(bindpath.x86)\WixToolset.Mba.Core.dll" />
9
+ </BootstrapperApplicationRef>
10
+
11
+ <PackageGroup Id="TestBA">
12
+ <PackageGroupRef Id="NetFx48WebAsPrereq" />
13
+ </PackageGroup>
14
+ </Fragment>
15
+</Wix>
src/TestData/TestBA/TestBAWixlib/testbawixlib.wixproj
new
+17
@@ -0,0 +1,17 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project Sdk="WixToolset.Sdk">
3
+ <PropertyGroup>
4
+ <OutputType>Library</OutputType>
5
+ <BindFiles>true</BindFiles>
6
+ <Cultures>en-us</Cultures>
7
+ </PropertyGroup>
8
+ <ItemGroup>
9
+ <BindInputPaths Include="$(OutputPath)..\net35\win-x86" BindName="x86" />
10
+ </ItemGroup>
11
+ <ItemGroup>
12
+ <ProjectReference Include="..\..\..\Utilities\TestBA\TestBA.csproj" />
13
+ </ItemGroup>
14
+ <ItemGroup>
15
+ <PackageReference Include="WixToolset.Bal.wixext" Version="4.0.63" />
16
+ </ItemGroup>
17
+</Project>
\ No newline at end of file
src/TestData/TestData.proj
new
+17
@@ -0,0 +1,17 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+
5
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6
+ <ItemGroup>
7
+ <TestDataProject Include="**\*.wixproj" />
8
+ </ItemGroup>
9
+
10
+ <Target Name="Build">
11
+ <MSBuild Projects="%(TestDataProject.Identity)" />
12
+ </Target>
13
+
14
+ <Target Name="Restore">
15
+ <MSBuild Projects="%(TestDataProject.Identity)" Targets="Restore" />
16
+ </Target>
17
+</Project>
\ No newline at end of file
src/Utilities/TestBA/Hresult.cs
new
+22
@@ -0,0 +1,22 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.Test.BA
4
+{
5
+ using System;
6
+
7
+ /// <summary>
8
+ /// Utility class to work with HRESULTs
9
+ /// </summary>
10
+ internal class Hresult
11
+ {
12
+ /// <summary>
13
+ /// Determines if an HRESULT was a success code or not.
14
+ /// </summary>
15
+ /// <param name="status">HRESULT to verify.</param>
16
+ /// <returns>True if the status is a success code.</returns>
17
+ public static bool Succeeded(int status)
18
+ {
19
+ return status >= 0;
20
+ }
21
+ }
22
+}
src/Utilities/TestBA/TestBA.BootstrapperCore.config
new
+18
@@ -0,0 +1,18 @@
1
+<?xml version="1.0" encoding="utf-8" ?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+
5
+<configuration>
6
+ <configSections>
7
+ <sectionGroup name="wix.bootstrapper" type="WixToolset.Mba.Host.BootstrapperSectionGroup, WixToolset.Mba.Host">
8
+ <section name="host" type="WixToolset.Mba.Host.HostSection, WixToolset.Mba.Host" />
9
+ </sectionGroup>
10
+ </configSections>
11
+ <startup>
12
+ <supportedRuntime version="v4.0" />
13
+ <supportedRuntime version="v2.0.50727" />
14
+ </startup>
15
+ <wix.bootstrapper>
16
+ <host assemblyName="TestBA" />
17
+ </wix.bootstrapper>
18
+</configuration>
src/Utilities/TestBA/TestBA.cs
new
+469
@@ -0,0 +1,469 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.Test.BA
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.IO;
8
+ using System.Linq;
9
+ using System.Threading;
10
+ using System.Windows.Forms;
11
+ using Microsoft.Win32;
12
+ using WixToolset.Mba.Core;
13
+
14
+ /// <summary>
15
+ /// A minimal UX used for testing.
16
+ /// </summary>
17
+ public class TestBA : BootstrapperApplication
18
+ {
19
+ private const string BurnBundleVersionVariable = "WixBundleVersion";
20
+
21
+ private ApplicationContext appContext;
22
+ private Form dummyWindow;
23
+ private LaunchAction action;
24
+ private int result;
25
+
26
+ private string updateBundlePath;
27
+
28
+ private int sleepDuringCache;
29
+ private int cancelCacheAtProgress;
30
+ private int sleepDuringExecute;
31
+ private int cancelExecuteAtProgress;
32
+ private int retryExecuteFilesInUse;
33
+
34
+ private IBootstrapperCommand Command { get; }
35
+
36
+ private IEngine Engine => this.engine;
37
+
38
+ /// <summary>
39
+ /// Initializes test user experience.
40
+ /// </summary>
41
+ public TestBA(IEngine engine, IBootstrapperCommand bootstrapperCommand)
42
+ : base(engine)
43
+ {
44
+ this.Command = bootstrapperCommand;
45
+ }
46
+
47
+ /// <summary>
48
+ /// Get the version of the install.
49
+ /// </summary>
50
+ public string Version { get; private set; }
51
+
52
+ /// <summary>
53
+ /// Indicates if DetectUpdate found a newer version to update.
54
+ /// </summary>
55
+ private bool UpdateAvailable { get; set; }
56
+
57
+ /// <summary>
58
+ /// UI Thread entry point for TestUX.
59
+ /// </summary>
60
+ protected override void Run()
61
+ {
62
+ this.action = this.Command.Action;
63
+ this.TestVariables();
64
+
65
+ this.Version = this.engine.GetVariableVersion(BurnBundleVersionVariable);
66
+ this.Log("Version: {0}", this.Version);
67
+
68
+ List<string> verifyArguments = this.ReadVerifyArguments();
69
+
70
+ foreach (string arg in this.Command.CommandLineArgs)
71
+ {
72
+ // If we're not in the update already, process the updatebundle.
73
+ if (this.Command.Relation != RelationType.Update && arg.StartsWith("-updatebundle:", StringComparison.OrdinalIgnoreCase))
74
+ {
75
+ this.updateBundlePath = arg.Substring(14);
76
+ FileInfo info = new FileInfo(this.updateBundlePath);
77
+ this.Engine.SetUpdate(this.updateBundlePath, null, info.Length, UpdateHashType.None, null);
78
+ this.UpdateAvailable = true;
79
+ this.action = LaunchAction.UpdateReplaceEmbedded;
80
+ }
81
+ else if (this.Command.Relation != RelationType.Update && arg.StartsWith("-checkupdate", StringComparison.OrdinalIgnoreCase))
82
+ {
83
+ this.action = LaunchAction.UpdateReplace;
84
+ }
85
+
86
+ verifyArguments.Remove(arg);
87
+ }
88
+ this.Log("Action: {0}", this.action);
89
+
90
+ // If there are any verification arguments left, error out.
91
+ if (0 < verifyArguments.Count)
92
+ {
93
+ foreach (string expectedArg in verifyArguments)
94
+ {
95
+ this.Log("Failure. Expected command-line to have argument: {0}", expectedArg);
96
+ }
97
+
98
+ this.Engine.Quit(-1);
99
+ return;
100
+ }
101
+
102
+ this.dummyWindow = new Form();
103
+ this.dummyWindow.CreateControl();
104
+ this.appContext = new ApplicationContext();
105
+
106
+ int redetectCount = 0;
107
+ string redetect = this.ReadPackageAction(null, "RedetectCount");
108
+ if (String.IsNullOrEmpty(redetect) || !Int32.TryParse(redetect, out redetectCount))
109
+ {
110
+ redetectCount = 0;
111
+ }
112
+
113
+ do
114
+ {
115
+ this.Engine.Detect();
116
+ this.Log("Completed detection phase: {0} re-runs remaining", redetectCount);
117
+ } while (0 < redetectCount--);
118
+
119
+ Application.Run(this.appContext);
120
+ this.Engine.Quit(this.result & 0xFFFF); // return plain old Win32 error, not HRESULT.
121
+ }
122
+
123
+ protected override void OnDetectUpdateBegin(DetectUpdateBeginEventArgs args)
124
+ {
125
+ this.Log("OnDetectUpdateBegin");
126
+ if ((LaunchAction.UpdateReplaceEmbedded == this.action)|(LaunchAction.UpdateReplace == this.action))
127
+ {
128
+ args.Skip = false;
129
+ }
130
+ }
131
+
132
+ protected override void OnDetectUpdate(DetectUpdateEventArgs e)
133
+ {
134
+ // The list of updates is sorted in descending version, so the first callback should be the largest update available.
135
+ // This update should be either larger than ours (so we are out of date), the same as ours (so we are current)
136
+ // or smaller than ours (we have a private build). If we really wanted to, we could leave the e.StopProcessingUpdates alone and
137
+ // enumerate all of the updates.
138
+ this.Log(String.Format("Potential update v{0} from '{1}'; current version: v{2}", e.Version, e.UpdateLocation, this.Version));
139
+ if (this.Engine.CompareVersions(e.Version, this.Version) > 0)
140
+ {
141
+ this.Log(String.Format("Selected update v{0}", e.Version));
142
+ this.Engine.SetUpdate(null, e.UpdateLocation, e.Size, UpdateHashType.None, null);
143
+ this.UpdateAvailable = true;
144
+ }
145
+ else
146
+ {
147
+ this.UpdateAvailable = false;
148
+ }
149
+ e.StopProcessingUpdates = true;
150
+ }
151
+
152
+ protected override void OnDetectUpdateComplete(DetectUpdateCompleteEventArgs e)
153
+ {
154
+ this.Log("OnDetectUpdateComplete");
155
+
156
+ // Failed to process an update, allow the existing bundle to still install.
157
+ if (!Hresult.Succeeded(e.Status))
158
+ {
159
+ this.Log(String.Format("Failed to locate an update, status of 0x{0:X8}, updates disabled.", e.Status));
160
+ e.IgnoreError = true; // But continue on...
161
+ }
162
+ }
163
+
164
+ protected override void OnDetectComplete(DetectCompleteEventArgs args)
165
+ {
166
+ this.result = args.Status;
167
+
168
+ if (Hresult.Succeeded(this.result) && (this.UpdateAvailable | (!((LaunchAction.UpdateReplaceEmbedded == this.action) | (LaunchAction.UpdateReplace == this.action)))))
169
+ {
170
+ this.Engine.Plan(this.action);
171
+ }
172
+ else
173
+ {
174
+ this.appContext.ExitThread();
175
+ }
176
+ }
177
+
178
+ protected override void OnPlanPackageBegin(PlanPackageBeginEventArgs args)
179
+ {
180
+ RequestState state;
181
+ string action = this.ReadPackageAction(args.PackageId, "Requested");
182
+ if (TryParseEnum<RequestState>(action, out state))
183
+ {
184
+ args.State = state;
185
+ }
186
+ }
187
+
188
+ protected override void OnPlanTargetMsiPackage(PlanTargetMsiPackageEventArgs args)
189
+ {
190
+ RequestState state;
191
+ string action = this.ReadPackageAction(args.PackageId, "Requested");
192
+ if (TryParseEnum<RequestState>(action, out state))
193
+ {
194
+ args.State = state;
195
+ }
196
+ }
197
+
198
+ protected override void OnPlanMsiFeature(PlanMsiFeatureEventArgs args)
199
+ {
200
+ FeatureState state;
201
+ string action = this.ReadFeatureAction(args.PackageId, args.FeatureId, "Requested");
202
+ if (TryParseEnum<FeatureState>(action, out state))
203
+ {
204
+ args.State = state;
205
+ }
206
+ }
207
+
208
+ protected override void OnPlanComplete(PlanCompleteEventArgs args)
209
+ {
210
+ this.result = args.Status;
211
+ if (Hresult.Succeeded(this.result))
212
+ {
213
+ this.Engine.Apply(this.dummyWindow.Handle);
214
+ }
215
+ else
216
+ {
217
+ this.appContext.ExitThread();
218
+ }
219
+ }
220
+
221
+ protected override void OnCachePackageBegin(CachePackageBeginEventArgs args)
222
+ {
223
+ this.Log("OnCachePackageBegin() - package: {0}, payloads to cache: {1}", args.PackageId, args.CachePayloads);
224
+
225
+ string slowProgress = this.ReadPackageAction(args.PackageId, "SlowCache");
226
+ if (String.IsNullOrEmpty(slowProgress) || !Int32.TryParse(slowProgress, out this.sleepDuringCache))
227
+ {
228
+ this.sleepDuringCache = 0;
229
+ }
230
+
231
+ string cancelCache = this.ReadPackageAction(args.PackageId, "CancelCacheAtProgress");
232
+ if (String.IsNullOrEmpty(cancelCache) || !Int32.TryParse(cancelCache, out this.cancelCacheAtProgress))
233
+ {
234
+ this.cancelCacheAtProgress = -1;
235
+ }
236
+ }
237
+
238
+ protected override void OnCacheAcquireProgress(CacheAcquireProgressEventArgs args)
239
+ {
240
+ this.Log("OnCacheAcquireProgress() - container/package: {0}, payload: {1}, progress: {2}, total: {3}, overall progress: {4}%", args.PackageOrContainerId, args.PayloadId, args.Progress, args.Total, args.OverallPercentage);
241
+
242
+ if (this.cancelCacheAtProgress > 0 && this.cancelCacheAtProgress <= args.Progress)
243
+ {
244
+ args.Cancel = true;
245
+ }
246
+ else if (this.sleepDuringCache > 0)
247
+ {
248
+ Thread.Sleep(this.sleepDuringCache);
249
+ }
250
+ }
251
+
252
+ protected override void OnExecutePackageBegin(ExecutePackageBeginEventArgs args)
253
+ {
254
+ this.Log("OnExecutePackageBegin() - package: {0}, rollback: {1}", args.PackageId, !args.ShouldExecute);
255
+
256
+ string slowProgress = this.ReadPackageAction(args.PackageId, "SlowExecute");
257
+ if (String.IsNullOrEmpty(slowProgress) || !Int32.TryParse(slowProgress, out this.sleepDuringExecute))
258
+ {
259
+ this.sleepDuringExecute = 0;
260
+ }
261
+
262
+ string cancelExecute = this.ReadPackageAction(args.PackageId, "CancelExecuteAtProgress");
263
+ if (String.IsNullOrEmpty(cancelExecute) || !Int32.TryParse(cancelExecute, out this.cancelExecuteAtProgress))
264
+ {
265
+ this.cancelExecuteAtProgress = -1;
266
+ }
267
+
268
+ string retryBeforeCancel = this.ReadPackageAction(args.PackageId, "RetryExecuteFilesInUse");
269
+ if (String.IsNullOrEmpty(retryBeforeCancel) || !Int32.TryParse(retryBeforeCancel, out this.retryExecuteFilesInUse))
270
+ {
271
+ this.retryExecuteFilesInUse = 0;
272
+ }
273
+ }
274
+
275
+ protected override void OnExecuteFilesInUse(ExecuteFilesInUseEventArgs args)
276
+ {
277
+ this.Log("OnExecuteFilesInUse() - package: {0}, retries remaining: {1}, data: {2}", args.PackageId, this.retryExecuteFilesInUse, String.Join(", ", args.Files.ToArray()));
278
+
279
+ if (this.retryExecuteFilesInUse > 0)
280
+ {
281
+ --this.retryExecuteFilesInUse;
282
+ args.Result = Result.Retry;
283
+ }
284
+ else
285
+ {
286
+ args.Result = Result.Abort;
287
+ }
288
+ }
289
+
290
+ protected override void OnExecuteProgress(ExecuteProgressEventArgs args)
291
+ {
292
+ this.Log("OnExecuteProgress() - package: {0}, progress: {1}%, overall progress: {2}%", args.PackageId, args.ProgressPercentage, args.OverallPercentage);
293
+
294
+ if (this.cancelExecuteAtProgress > 0 && this.cancelExecuteAtProgress <= args.ProgressPercentage)
295
+ {
296
+ args.Cancel = true;
297
+ }
298
+ else if (this.sleepDuringExecute > 0)
299
+ {
300
+ Thread.Sleep(this.sleepDuringExecute);
301
+ }
302
+ }
303
+
304
+ protected override void OnExecutePatchTarget(ExecutePatchTargetEventArgs args)
305
+ {
306
+ this.Log("OnExecutePatchTarget - Patch Package: {0}, Target Product Code: {1}", args.PackageId, args.TargetProductCode);
307
+ }
308
+
309
+ protected override void OnProgress(ProgressEventArgs args)
310
+ {
311
+ this.Log("OnProgress() - progress: {0}%, overall progress: {1}%", args.ProgressPercentage, args.OverallPercentage);
312
+ if (this.Command.Display == Display.Embedded)
313
+ {
314
+ this.Engine.SendEmbeddedProgress(args.ProgressPercentage, args.OverallPercentage);
315
+ }
316
+ }
317
+
318
+ protected override void OnResolveSource(ResolveSourceEventArgs args)
319
+ {
320
+ if (!String.IsNullOrEmpty(args.DownloadSource))
321
+ {
322
+ args.Action = BOOTSTRAPPER_RESOLVESOURCE_ACTION.Download;
323
+ }
324
+ }
325
+
326
+ protected override void OnApplyComplete(ApplyCompleteEventArgs args)
327
+ {
328
+ // Output what the privileges are now.
329
+ this.Log("After elevation: WixBundleElevated = {0}", this.Engine.GetVariableNumeric("WixBundleElevated"));
330
+
331
+ this.result = args.Status;
332
+ this.appContext.ExitThread();
333
+ }
334
+
335
+ protected override void OnSystemShutdown(SystemShutdownEventArgs args)
336
+ {
337
+ // Always prevent shutdown.
338
+ this.Log("Disallowed system request to shut down the bootstrapper application.");
339
+ args.Cancel = true;
340
+
341
+ this.appContext.ExitThread();
342
+ }
343
+
344
+ private void TestVariables()
345
+ {
346
+ // First make sure we can check and get standard variables of each type.
347
+ {
348
+ string value = null;
349
+ if (this.Engine.ContainsVariable("WindowsFolder"))
350
+ {
351
+ value = this.Engine.GetVariableString("WindowsFolder");
352
+ this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a string variable: WindowsFolder");
353
+ }
354
+ else
355
+ {
356
+ throw new Exception("Engine did not define a standard variable: WindowsFolder");
357
+ }
358
+ }
359
+
360
+ {
361
+ long value = 0;
362
+ if (this.Engine.ContainsVariable("NTProductType"))
363
+ {
364
+ value = this.Engine.GetVariableNumeric("NTProductType");
365
+ this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a numeric variable: NTProductType");
366
+ }
367
+ else
368
+ {
369
+ throw new Exception("Engine did not define a standard variable: NTProductType");
370
+ }
371
+ }
372
+
373
+ {
374
+ string value = null;
375
+ if (this.Engine.ContainsVariable("VersionMsi"))
376
+ {
377
+ value = this.Engine.GetVariableVersion("VersionMsi");
378
+ this.Engine.Log(LogLevel.Verbose, "TEST: Successfully retrieved a version variable: VersionMsi");
379
+ }
380
+ else
381
+ {
382
+ throw new Exception("Engine did not define a standard variable: VersionMsi");
383
+ }
384
+ }
385
+
386
+ // Now validate that Contians returns false for non-existant variables of each type.
387
+ if (this.Engine.ContainsVariable("TestStringVariableShouldNotExist"))
388
+ {
389
+ throw new Exception("Engine defined a variable that should not exist: TestStringVariableShouldNotExist");
390
+ }
391
+ else
392
+ {
393
+ this.Engine.Log(LogLevel.Verbose, "TEST: Successfully checked for non-existent string variable: TestStringVariableShouldNotExist");
394
+ }
395
+
396
+ if (this.Engine.ContainsVariable("TestNumericVariableShouldNotExist"))
397
+ {
398
+ throw new Exception("Engine defined a variable that should not exist: TestNumericVariableShouldNotExist");
399
+ }
400
+ else
401
+ {
402
+ this.Engine.Log(LogLevel.Verbose, "TEST: Successfully checked for non-existent numeric variable: TestNumericVariableShouldNotExist");
403
+ }
404
+
405
+ if (this.Engine.ContainsVariable("TestVersionVariableShouldNotExist"))
406
+ {
407
+ throw new Exception("Engine defined a variable that should not exist: TestVersionVariableShouldNotExist");
408
+ }
409
+ else
410
+ {
411
+ this.Engine.Log(LogLevel.Verbose, "TEST: Successfully checked for non-existent version variable: TestVersionVariableShouldNotExist");
412
+ }
413
+
414
+ // Output what the initially run privileges were.
415
+ this.Engine.Log(LogLevel.Verbose, String.Format("TEST: WixBundleElevated = {0}", this.Engine.GetVariableNumeric("WixBundleElevated")));
416
+ }
417
+
418
+ private void Log(string format, params object[] args)
419
+ {
420
+ string relation = this.Command.Relation != RelationType.None ? String.Concat(" (", this.Command.Relation.ToString().ToLowerInvariant(), ")") : String.Empty;
421
+ string message = String.Format(format, args);
422
+
423
+ this.Engine.Log(LogLevel.Standard, String.Concat("TESTBA", relation, ": ", message));
424
+ }
425
+
426
+ private List<string> ReadVerifyArguments()
427
+ {
428
+ string testName = this.Engine.GetVariableString("TestGroupName");
429
+ using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}", testName)))
430
+ {
431
+ string verifyArguments = testKey == null ? null : testKey.GetValue("VerifyArguments") as string;
432
+ return verifyArguments == null ? new List<string>() : new List<string>(verifyArguments.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
433
+ }
434
+ }
435
+
436
+ private string ReadPackageAction(string packageId, string state)
437
+ {
438
+ string testName = this.Engine.GetVariableString("TestGroupName");
439
+ using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\{1}", testName, String.IsNullOrEmpty(packageId) ? String.Empty : packageId)))
440
+ {
441
+ return testKey == null ? null : testKey.GetValue(state) as string;
442
+ }
443
+ }
444
+
445
+ private string ReadFeatureAction(string packageId, string featureId, string state)
446
+ {
447
+ string testName = this.Engine.GetVariableString("TestGroupName");
448
+ using (RegistryKey testKey = Registry.LocalMachine.OpenSubKey(String.Format(@"Software\WiX\Tests\TestBAControl\{0}\{1}", testName, packageId)))
449
+ {
450
+ string registryName = String.Concat(featureId, state);
451
+ return testKey == null ? null : testKey.GetValue(registryName) as string;
452
+ }
453
+ }
454
+
455
+ private static bool TryParseEnum<T>(string value, out T t)
456
+ {
457
+ try
458
+ {
459
+ t = (T)Enum.Parse(typeof(T), value, true);
460
+ return true;
461
+ }
462
+ catch (ArgumentException) { }
463
+ catch (OverflowException) { }
464
+
465
+ t = default(T);
466
+ return false;
467
+ }
468
+ }
469
+}
src/Utilities/TestBA/TestBA.csproj
new
+24
@@ -0,0 +1,24 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+<Project Sdk="Microsoft.NET.Sdk">
5
+ <PropertyGroup>
6
+ <TargetFramework>net35</TargetFramework>
7
+ <AssemblyName>TestBA</AssemblyName>
8
+ <RootNamespace>WixToolset.Test.BA</RootNamespace>
9
+ <DebugType>embedded</DebugType>
10
+ <RuntimeIdentifier>win-x86</RuntimeIdentifier>
11
+ </PropertyGroup>
12
+
13
+ <ItemGroup>
14
+ <Content Include="TestBA.BootstrapperCore.config" CopyToOutputDirectory="PreserveNewest" />
15
+ </ItemGroup>
16
+
17
+ <ItemGroup>
18
+ <PackageReference Include="WixToolset.Mba.Core" Version="4.0.41" />
19
+ </ItemGroup>
20
+
21
+ <ItemGroup>
22
+ <Reference Include="System.Windows.Forms" />
23
+ </ItemGroup>
24
+</Project>
\ No newline at end of file
src/Utilities/TestBA/TestBAFactory.cs
new
+22
@@ -0,0 +1,22 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+[assembly: WixToolset.Mba.Core.BootstrapperApplicationFactory(typeof(WixToolset.Test.BA.TestBAFactory))]
4
+namespace WixToolset.Test.BA
5
+{
6
+ using WixToolset.Mba.Core;
7
+
8
+ public class TestBAFactory : BaseBootstrapperApplicationFactory
9
+ {
10
+ private static int loadCount = 0;
11
+
12
+ protected override IBootstrapperApplication Create(IEngine engine, IBootstrapperCommand bootstrapperCommand)
13
+ {
14
+ if (loadCount > 0)
15
+ {
16
+ engine.Log(LogLevel.Standard, $"Reloaded {loadCount} time(s)");
17
+ }
18
+ ++loadCount;
19
+ return new TestBA(engine, bootstrapperCommand);
20
+ }
21
+ }
22
+}
src/Wix.Build.props
new
+7
@@ -0,0 +1,7 @@
1
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
+<Project>
3
+ <PropertyGroup>
4
+ <TestGroupName Condition=" '$(TestGroupName)'=='' ">$([System.IO.Path]::GetFileName($([System.IO.Path]::GetDirectoryName($(MSBuildProjectDirectory)))))</TestGroupName>
5
+ <OutputPath>$(OutputPath)$(TestGroupName)\</OutputPath>
6
+ </PropertyGroup>
7
+</Project>
src/Wix.Build.targets
new
+10
@@ -0,0 +1,10 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+<Project>
4
+ <PropertyGroup>
5
+ <PackageName Condition=" '$(PackageName)' == '' ">$(MSBuildProjectName)</PackageName>
6
+ <DefineConstants>TestGroupName=$(TestGroupName);PackageName=$(PackageName);$(DefineConstants)</DefineConstants>
7
+ <DefineConstants Condition=" '$(UpgradeCode)' != '' ">UpgradeCode=$(UpgradeCode);$(DefineConstants)</DefineConstants>
8
+ <DefineConstants Condition=" '$(Version)' != '' ">Version=$(Version);$(DefineConstants)</DefineConstants>
9
+ </PropertyGroup>
10
+</Project>
src/WixToolsetTest.BurnE2E/BundleInstaller.cs
new
+128
@@ -0,0 +1,128 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.BurnE2E
4
+{
5
+ using System;
6
+ using System.IO;
7
+ using System.Text;
8
+
9
+ public class BundleInstaller : IDisposable
10
+ {
11
+ public BundleInstaller(WixTestContext testContext, string name)
12
+ {
13
+ this.Bundle = Path.Combine(testContext.TestDataFolder, $"{name}.exe");
14
+ this.TestGroupName = testContext.TestGroupName;
15
+ this.TestName = testContext.TestName;
16
+ }
17
+
18
+ public string Bundle { get; }
19
+
20
+ public string TestGroupName { get; }
21
+
22
+ public string TestName { get; }
23
+
24
+ /// <summary>
25
+ /// Installs the bundle with optional arguments.
26
+ /// </summary>
27
+ /// <param name="expectedExitCode">Expected exit code, defaults to success.</param>
28
+ /// <param name="arguments">Optional arguments to pass to the tool.</param>
29
+ /// <returns>Path to the generated log file.</returns>
30
+ public string Install(int expectedExitCode = (int)MSIExec.MSIExecReturnCode.SUCCESS, params string[] arguments)
31
+ {
32
+ return this.RunBundleWithArguments(expectedExitCode, MSIExec.MSIExecMode.Install, arguments);
33
+ }
34
+
35
+ /// <summary>
36
+ /// Modify the bundle with optional arguments.
37
+ /// </summary>
38
+ /// <param name="expectedExitCode">Expected exit code, defaults to success.</param>
39
+ /// <param name="arguments">Optional arguments to pass to the tool.</param>
40
+ /// <returns>Path to the generated log file.</returns>
41
+ public string Modify(int expectedExitCode = (int)MSIExec.MSIExecReturnCode.SUCCESS, params string[] arguments)
42
+ {
43
+ return this.RunBundleWithArguments(expectedExitCode, MSIExec.MSIExecMode.Modify, arguments);
44
+ }
45
+
46
+ /// <summary>
47
+ /// Repairs the bundle with optional arguments.
48
+ /// </summary>
49
+ /// <param name="expectedExitCode">Expected exit code, defaults to success.</param>
50
+ /// <param name="arguments">Optional arguments to pass to the tool.</param>
51
+ /// <returns>Path to the generated log file.</returns>
52
+ public string Repair(int expectedExitCode = (int)MSIExec.MSIExecReturnCode.SUCCESS, params string[] arguments)
53
+ {
54
+ return this.RunBundleWithArguments(expectedExitCode, MSIExec.MSIExecMode.Repair, arguments);
55
+ }
56
+
57
+ /// <summary>
58
+ /// Uninstalls the bundle with optional arguments.
59
+ /// </summary>
60
+ /// <param name="expectedExitCode">Expected exit code, defaults to success.</param>
61
+ /// <param name="arguments">Optional arguments to pass to the tool.</param>
62
+ /// <returns>Path to the generated log file.</returns>
63
+ public string Uninstall(int expectedExitCode = (int)MSIExec.MSIExecReturnCode.SUCCESS, params string[] arguments)
64
+ {
65
+ return this.RunBundleWithArguments(expectedExitCode, MSIExec.MSIExecMode.Uninstall, arguments);
66
+ }
67
+
68
+ /// <summary>
69
+ /// Executes the bundle with optional arguments.
70
+ /// </summary>
71
+ /// <param name="expectedExitCode">Expected exit code.</param>
72
+ /// <param name="mode">Install mode.</param>
73
+ /// <param name="arguments">Optional arguments to pass to the tool.</param>
74
+ /// <returns>Path to the generated log file.</returns>
75
+ private string RunBundleWithArguments(int expectedExitCode, MSIExec.MSIExecMode mode, string[] arguments, bool assertOnError = true)
76
+ {
77
+ TestTool bundle = new TestTool(this.Bundle);
78
+ var sb = new StringBuilder();
79
+
80
+ // Be sure to run silent.
81
+ sb.Append(" -quiet");
82
+
83
+ // Generate the log file name.
84
+ string logFile = Path.Combine(Path.GetTempPath(), String.Format("{0}_{1}_{2:yyyyMMddhhmmss}_{4}_{3}.log", this.TestGroupName, this.TestName, DateTime.UtcNow, Path.GetFileNameWithoutExtension(this.Bundle), mode));
85
+ sb.AppendFormat(" -log \"{0}\"", logFile);
86
+
87
+ // Set operation.
88
+ switch (mode)
89
+ {
90
+ case MSIExec.MSIExecMode.Modify:
91
+ sb.Append(" -modify");
92
+ break;
93
+
94
+ case MSIExec.MSIExecMode.Repair:
95
+ sb.Append(" -repair");
96
+ break;
97
+
98
+ case MSIExec.MSIExecMode.Cleanup:
99
+ case MSIExec.MSIExecMode.Uninstall:
100
+ sb.Append(" -uninstall");
101
+ break;
102
+ }
103
+
104
+ // Add additional arguments.
105
+ if (null != arguments)
106
+ {
107
+ sb.Append(" ");
108
+ sb.Append(String.Join(" ", arguments));
109
+ }
110
+
111
+ // Set the arguments.
112
+ bundle.Arguments = sb.ToString();
113
+
114
+ // Run the tool and assert the expected code.
115
+ bundle.ExpectedExitCode = expectedExitCode;
116
+ bundle.Run(assertOnError);
117
+
118
+ // Return the log file name.
119
+ return logFile;
120
+ }
121
+
122
+ public void Dispose()
123
+ {
124
+ string[] args = { "-burn.ignoredependencies=ALL" };
125
+ this.RunBundleWithArguments((int)MSIExec.MSIExecReturnCode.SUCCESS, MSIExec.MSIExecMode.Cleanup, args, assertOnError: false);
126
+ }
127
+ }
128
+}
src/WixToolsetTest.BurnE2E/BurnE2EFixture.cs
new
+28
@@ -0,0 +1,28 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.BurnE2E
4
+{
5
+ using System;
6
+ using System.Security.Principal;
7
+
8
+ public class BurnE2EFixture
9
+ {
10
+ const string RequiredEnvironmentVariableName = "RuntimeTestsEnabled";
11
+
12
+ public BurnE2EFixture()
13
+ {
14
+ using var identity = WindowsIdentity.GetCurrent();
15
+ var principal = new WindowsPrincipal(identity);
16
+ if (!principal.IsInRole(WindowsBuiltInRole.Administrator))
17
+ {
18
+ throw new InvalidOperationException("These tests must run elevated.");
19
+ }
20
+
21
+ var testsEnabledString = Environment.GetEnvironmentVariable(RequiredEnvironmentVariableName);
22
+ if (!bool.TryParse(testsEnabledString, out var testsEnabled) || !testsEnabled)
23
+ {
24
+ throw new InvalidOperationException($"These tests affect machine state. Set the {RequiredEnvironmentVariableName} environment variable to true to accept the consequences.");
25
+ }
26
+ }
27
+ }
28
+}
src/WixToolsetTest.BurnE2E/BurnE2ETests.cs
new
+48
@@ -0,0 +1,48 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.BurnE2E
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using Xunit;
8
+ using Xunit.Abstractions;
9
+
10
+ [Collection("BurnE2E")]
11
+ public abstract class BurnE2ETests : WixTestBase, IDisposable
12
+ {
13
+ protected BurnE2ETests(ITestOutputHelper testOutputHelper, string testGroupName) : base(testOutputHelper, testGroupName) { }
14
+
15
+ private Queue<IDisposable> Installers { get; } = new Queue<IDisposable>();
16
+
17
+ protected BundleInstaller CreateBundleInstaller(string name)
18
+ {
19
+ var installer = new BundleInstaller(this.TestContext, name);
20
+ this.Installers.Enqueue(installer);
21
+ return installer;
22
+ }
23
+
24
+ protected PackageInstaller CreatePackageInstaller(string name)
25
+ {
26
+ var installer = new PackageInstaller(this.TestContext, name);
27
+ this.Installers.Enqueue(installer);
28
+ return installer;
29
+ }
30
+
31
+ public void Dispose()
32
+ {
33
+ while (this.Installers.TryDequeue(out var installer))
34
+ {
35
+ try
36
+ {
37
+ installer.Dispose();
38
+ }
39
+ catch { }
40
+ }
41
+ }
42
+ }
43
+
44
+ [CollectionDefinition("BurnE2E", DisableParallelization = true)]
45
+ public class BurnE2ECollectionDefinition : ICollectionFixture<BurnE2EFixture>
46
+ {
47
+ }
48
+}
src/WixToolsetTest.BurnE2E/MSIExec.cs
new
+753
@@ -0,0 +1,753 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.BurnE2E
4
+{
5
+ using System;
6
+ using System.IO;
7
+ using System.Text;
8
+ using WixBuildTools.TestSupport;
9
+
10
+ public class MSIExec : TestTool
11
+ {
12
+ /// <summary>
13
+ /// The expected exit code of the tool
14
+ /// </summary>
15
+ public new MSIExecReturnCode ExpectedExitCode
16
+ {
17
+ get { return (MSIExecReturnCode)base.ExpectedExitCode; }
18
+ set { base.ExpectedExitCode = (int?)value; }
19
+ }
20
+
21
+ /// <summary>
22
+ /// Mode of execution (install, uninstall, or repair)
23
+ /// </summary>
24
+ public MSIExecMode ExecutionMode { get; set; }
25
+
26
+ /// <summary>
27
+ /// Path to msi or ProductCode
28
+ /// </summary>
29
+ public string Product { get; set; }
30
+
31
+ /// <summary>
32
+ /// Logging Options
33
+ /// </summary>
34
+ public MSIExecLoggingOptions LoggingOptions { get; set; }
35
+
36
+ /// <summary>
37
+ /// Path to the log file
38
+ /// </summary>
39
+ public string LogFile { get; set; }
40
+
41
+ /// <summary>
42
+ /// Unattended mode - progress bar only
43
+ /// </summary>
44
+ public bool Passive { get; set; }
45
+
46
+ /// <summary>
47
+ /// Quiet mode, no user interaction
48
+ /// </summary>
49
+ public bool Quiet { get; set; }
50
+
51
+ /// <summary>
52
+ /// Sets user interface level
53
+ /// </summary>
54
+ public MSIExecUserInterfaceLevel UserInterfaceLevel { get; set; }
55
+
56
+ /// <summary>
57
+ /// Do not restart after the installation is complete
58
+ /// </summary>
59
+ public bool NoRestart { get; set; }
60
+
61
+ /// <summary>
62
+ /// Prompts the user for restart if necessary
63
+ /// </summary>
64
+ public bool PromptRestart { get; set; }
65
+
66
+ /// <summary>
67
+ /// Always restart the computer after installation
68
+ /// </summary>
69
+ public bool ForceRestart { get; set; }
70
+
71
+ /// <summary>
72
+ /// Other arguments.
73
+ /// </summary>
74
+ public string OtherArguments { get; set; }
75
+
76
+ /// <summary>
77
+ /// Constructor that uses the default location for MSIExec.
78
+ /// </summary>
79
+ public MSIExec()
80
+ : this(Environment.SystemDirectory)
81
+ {
82
+ }
83
+
84
+ /// <summary>
85
+ /// Constructor that accepts a path to the MSIExec location.
86
+ /// </summary>
87
+ /// <param name="toolDirectory">The directory of MSIExec.exe.</param>
88
+ public MSIExec(string toolDirectory)
89
+ : base(Path.Combine(toolDirectory, "MSIExec.exe"))
90
+ {
91
+ this.SetDefaultArguments();
92
+ }
93
+
94
+ public override ExternalExecutableResult Run(bool assertOnError)
95
+ {
96
+ this.Arguments = this.GetArguments();
97
+ return base.Run(assertOnError);
98
+ }
99
+
100
+ /// <summary>
101
+ /// Clears all of the assigned arguments and resets them to the default values.
102
+ /// </summary>
103
+ public void SetDefaultArguments()
104
+ {
105
+ this.ExecutionMode = MSIExecMode.Install;
106
+ this.Product = String.Empty;
107
+ this.Quiet = true;
108
+ this.Passive = false;
109
+ this.UserInterfaceLevel = MSIExecUserInterfaceLevel.None;
110
+ this.NoRestart = true;
111
+ this.ForceRestart = false;
112
+ this.PromptRestart = false;
113
+ this.LogFile = string.Empty;
114
+ this.LoggingOptions = MSIExecLoggingOptions.VOICEWARMUP;
115
+ this.OtherArguments = String.Empty;
116
+ }
117
+
118
+ public string GetArguments()
119
+ {
120
+ var arguments = new StringBuilder();
121
+
122
+ // quiet
123
+ if (this.Quiet)
124
+ {
125
+ arguments.Append(" /quiet ");
126
+ }
127
+
128
+ // passive
129
+ if (this.Passive)
130
+ {
131
+ arguments.Append(" /passive ");
132
+ }
133
+
134
+ // UserInterfaceLevel
135
+ switch (this.UserInterfaceLevel)
136
+ {
137
+ case MSIExecUserInterfaceLevel.None:
138
+ arguments.Append(" /qn ");
139
+ break;
140
+ case MSIExecUserInterfaceLevel.Basic:
141
+ arguments.Append(" /qb ");
142
+ break;
143
+ case MSIExecUserInterfaceLevel.Reduced:
144
+ arguments.Append(" /qr ");
145
+ break;
146
+ case MSIExecUserInterfaceLevel.Full:
147
+ arguments.Append(" /qf ");
148
+ break;
149
+ }
150
+
151
+ // NoRestart
152
+ if (this.NoRestart)
153
+ {
154
+ arguments.Append(" /norestart ");
155
+ }
156
+
157
+ // PromptRestart
158
+ if (this.PromptRestart)
159
+ {
160
+ arguments.Append(" /promptrestart ");
161
+ }
162
+
163
+ // ForceRestart
164
+ if (this.ForceRestart)
165
+ {
166
+ arguments.Append(" /forcerestart ");
167
+ }
168
+
169
+ // Logging options
170
+ var loggingOptionsString = new StringBuilder();
171
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Status_Messages) == MSIExecLoggingOptions.Status_Messages)
172
+ {
173
+ loggingOptionsString.Append("i");
174
+ }
175
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Nonfatal_Warnings) == MSIExecLoggingOptions.Nonfatal_Warnings)
176
+ {
177
+ loggingOptionsString.Append("w");
178
+ }
179
+ if ((this.LoggingOptions & MSIExecLoggingOptions.All_Error_Messages) == MSIExecLoggingOptions.All_Error_Messages)
180
+ {
181
+ loggingOptionsString.Append("e");
182
+ }
183
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Start_Up_Of_Actions) == MSIExecLoggingOptions.Start_Up_Of_Actions)
184
+ {
185
+ loggingOptionsString.Append("a");
186
+ }
187
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Action_Specific_Records) == MSIExecLoggingOptions.Action_Specific_Records)
188
+ {
189
+ loggingOptionsString.Append("r");
190
+ }
191
+ if ((this.LoggingOptions & MSIExecLoggingOptions.User_Requests) == MSIExecLoggingOptions.User_Requests)
192
+ {
193
+ loggingOptionsString.Append("u");
194
+ }
195
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Initial_UI_Parameters) == MSIExecLoggingOptions.Initial_UI_Parameters)
196
+ {
197
+ loggingOptionsString.Append("c");
198
+ }
199
+ if ((this.LoggingOptions & MSIExecLoggingOptions.OutOfMemory_Or_Fatal_Exit_Information) == MSIExecLoggingOptions.OutOfMemory_Or_Fatal_Exit_Information)
200
+ {
201
+ loggingOptionsString.Append("m");
202
+ }
203
+ if ((this.LoggingOptions & MSIExecLoggingOptions.OutOfDiskSpace_Messages) == MSIExecLoggingOptions.OutOfDiskSpace_Messages)
204
+ {
205
+ loggingOptionsString.Append("o");
206
+ }
207
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Terminal_Properties) == MSIExecLoggingOptions.Terminal_Properties)
208
+ {
209
+ loggingOptionsString.Append("p");
210
+ }
211
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Verbose_Output) == MSIExecLoggingOptions.Verbose_Output)
212
+ {
213
+ loggingOptionsString.Append("v");
214
+ }
215
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Extra_Debugging_Information) == MSIExecLoggingOptions.Extra_Debugging_Information)
216
+ {
217
+ loggingOptionsString.Append("x");
218
+ }
219
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Append_To_Existing_Log_File) == MSIExecLoggingOptions.Append_To_Existing_Log_File)
220
+ {
221
+ loggingOptionsString.Append("+");
222
+ }
223
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Flush_Each_line) == MSIExecLoggingOptions.Flush_Each_line)
224
+ {
225
+ loggingOptionsString.Append("!");
226
+ }
227
+ if ((this.LoggingOptions & MSIExecLoggingOptions.Log_All_Information) == MSIExecLoggingOptions.Log_All_Information)
228
+ {
229
+ loggingOptionsString.Append("*");
230
+ }
231
+
232
+ // logfile and logging options
233
+ if (0 != loggingOptionsString.Length || !string.IsNullOrEmpty(this.LogFile))
234
+ {
235
+ arguments.Append(" /l");
236
+ if (0 != loggingOptionsString.Length)
237
+ {
238
+ arguments.AppendFormat("{0} ", loggingOptionsString);
239
+ }
240
+ if (!string.IsNullOrEmpty(this.LogFile))
241
+ {
242
+ arguments.AppendFormat(" \"{0}\" ", this.LogFile);
243
+ }
244
+ }
245
+
246
+ // OtherArguments
247
+ if (!String.IsNullOrEmpty(this.OtherArguments))
248
+ {
249
+ arguments.AppendFormat(" {0} ", this.OtherArguments);
250
+ }
251
+
252
+ // execution mode
253
+ switch (this.ExecutionMode)
254
+ {
255
+ case MSIExecMode.Install:
256
+ arguments.Append(" /package ");
257
+ break;
258
+ case MSIExecMode.AdministrativeInstall:
259
+ arguments.Append(" /a ");
260
+ break;
261
+ case MSIExecMode.Repair:
262
+ arguments.Append(" /f ");
263
+ break;
264
+ case MSIExecMode.Cleanup:
265
+ case MSIExecMode.Uninstall:
266
+ arguments.Append(" /uninstall ");
267
+ break;
268
+ };
269
+
270
+ // product
271
+ if (!string.IsNullOrEmpty(this.Product))
272
+ {
273
+ arguments.AppendFormat(" \"{0}\" ", this.Product);
274
+ }
275
+
276
+ return arguments.ToString();
277
+ }
278
+
279
+ /// <summary>
280
+ /// Return codes from an MSI install or uninstall
281
+ /// </summary>
282
+ /// <remarks>
283
+ /// Error codes indicative of success are:
284
+ /// ERROR_SUCCESS, ERROR_SUCCESS_REBOOT_INITIATED, and ERROR_SUCCESS_REBOOT_REQUIRED
285
+ /// </remarks>
286
+ public enum MSIExecReturnCode
287
+ {
288
+ /// <summary>
289
+ /// ERROR_SUCCESS 0
290
+ /// Action completed successfully.
291
+ /// </summary>
292
+ SUCCESS = 0,
293
+
294
+ /// <summary>
295
+ /// ERROR_INVALID_DATA 13
296
+ /// The data is invalid.
297
+ /// </summary>
298
+ ERROR_INVALID_DATA = 13,
299
+
300
+ /// <summary>
301
+ /// ERROR_INVALID_PARAMETER 87
302
+ /// One of the parameters was invalid.
303
+ /// </summary>
304
+ ERROR_INVALID_PARAMETER = 87,
305
+
306
+ /// <summary>
307
+ /// ERROR_CALL_NOT_IMPLEMENTED 120
308
+ /// This value is returned when a custom action attempts to call a function that cannot be called from custom actions.
309
+ /// The function returns the value ERROR_CALL_NOT_IMPLEMENTED. Available beginning with Windows Installer version 3.0.
310
+ /// </summary>
311
+ ERROR_CALL_NOT_IMPLEMENTED = 120,
312
+
313
+ /// <summary>
314
+ /// ERROR_APPHELP_BLOCK 1259
315
+ /// If Windows Installer determines a product may be incompatible with the current operating system,
316
+ /// it displays a dialog box informing the user and asking whether to try to install anyway.
317
+ /// This error code is returned if the user chooses not to try the installation.
318
+ /// </summary>
319
+ ERROR_APPHELP_BLOCK = 1259,
320
+
321
+ /// <summary>
322
+ /// ERROR_INSTALL_SERVICE_FAILURE 1601
323
+ /// The Windows Installer service could not be accessed.
324
+ /// Contact your support personnel to verify that the Windows Installer service is properly registered.
325
+ /// </summary>
326
+ ERROR_INSTALL_SERVICE_FAILURE = 1601,
327
+
328
+
329
+ /// <summary>
330
+ /// ERROR_INSTALL_USEREXIT 1602
331
+ /// The user cancels installation.
332
+ /// </summary>
333
+ ERROR_INSTALL_USEREXIT = 1602,
334
+
335
+ /// <summary>
336
+ /// ERROR_INSTALL_FAILURE 1603
337
+ /// A fatal error occurred during installation.
338
+ /// </summary>
339
+ ERROR_INSTALL_FAILURE = 1603,
340
+
341
+ /// <summary>
342
+ /// ERROR_INSTALL_SUSPEND 1604
343
+ /// Installation suspended, incomplete.
344
+ /// </summary>
345
+ ERROR_INSTALL_SUSPEND = 1604,
346
+
347
+ /// <summary>
348
+ /// ERROR_UNKNOWN_PRODUCT 1605
349
+ /// This action is only valid for products that are currently installed.
350
+ /// </summary>
351
+ ERROR_UNKNOWN_PRODUCT = 1605,
352
+
353
+ /// <summary>
354
+ /// ERROR_UNKNOWN_FEATURE 1606
355
+ /// The feature identifier is not registered.
356
+ /// </summary>
357
+ ERROR_UNKNOWN_FEATURE = 1606,
358
+
359
+ /// <summary>
360
+ /// ERROR_UNKNOWN_COMPONENT 1607
361
+ /// The component identifier is not registered.
362
+ /// </summary>
363
+ ERROR_UNKNOWN_COMPONENT = 1607,
364
+
365
+ /// <summary>
366
+ /// ERROR_UNKNOWN_PROPERTY 1608
367
+ /// This is an unknown property.
368
+ /// </summary>
369
+ ERROR_UNKNOWN_PROPERTY = 1608,
370
+
371
+ /// <summary>
372
+ /// ERROR_INVALID_HANDLE_STATE 1609
373
+ /// The handle is in an invalid state.
374
+ /// </summary>
375
+ ERROR_INVALID_HANDLE_STATE = 1609,
376
+
377
+ /// <summary>
378
+ /// ERROR_BAD_CONFIGURATION 1610
379
+ /// The configuration data for this product is corrupt. Contact your support personnel.
380
+ /// </summary>
381
+ ERROR_BAD_CONFIGURATION = 1610,
382
+
383
+ /// <summary>
384
+ /// ERROR_INDEX_ABSENT 1611
385
+ /// The component qualifier not present.
386
+ /// </summary>
387
+ ERROR_INDEX_ABSENT = 1611,
388
+
389
+ /// <summary>ERROR_INSTALL_SOURCE_ABSENT 1612
390
+ /// The installation source for this product is not available.
391
+ /// Verify that the source exists and that you can access it.
392
+ /// </summary>
393
+ ERROR_INSTALL_SOURCE_ABSENT = 1612,
394
+
395
+ /// <summary>
396
+ /// ERROR_INSTALL_PACKAGE_VERSION 1613
397
+ /// This installation package cannot be installed by the Windows Installer service.
398
+ /// You must install a Windows service pack that contains a newer version of the Windows Installer service.
399
+ /// </summary>
400
+ ERROR_INSTALL_PACKAGE_VERSION = 1613,
401
+
402
+ /// <summary>
403
+ /// ERROR_PRODUCT_UNINSTALLED 1614
404
+ /// The product is uninstalled.
405
+ /// </summary>
406
+ ERROR_PRODUCT_UNINSTALLED = 1614,
407
+
408
+ /// <summary>
409
+ /// ERROR_BAD_QUERY_SYNTAX 1615
410
+ /// The SQL query syntax is invalid or unsupported.
411
+ /// </summary>
412
+ ERROR_BAD_QUERY_SYNTAX = 1615,
413
+
414
+ /// <summary>
415
+ /// ERROR_INVALID_FIELD 1616
416
+ /// The record field does not exist.
417
+ /// </summary>
418
+ ERROR_INVALID_FIELD = 1616,
419
+
420
+ /// <summary>
421
+ /// ERROR_INSTALL_ALREADY_RUNNING 1618
422
+ /// Another installation is already in progress. Complete that installation before proceeding with this install.
423
+ /// For information about the mutex, see _MSIExecute Mutex.
424
+ /// </summary>
425
+ ERROR_INSTALL_ALREADY_RUNNING = 1618,
426
+
427
+ /// <summary>
428
+ /// ERROR_INSTALL_PACKAGE_OPEN_FAILED 1619
429
+ /// This installation package could not be opened. Verify that the package exists and is accessible, or contact the
430
+ /// application vendor to verify that this is a valid Windows Installer package.
431
+ /// </summary>
432
+ ERROR_INSTALL_PACKAGE_OPEN_FAILED = 1619,
433
+
434
+
435
+ /// <summary>
436
+ /// ERROR_INSTALL_PACKAGE_INVALID 1620
437
+ /// This installation package could not be opened.
438
+ /// Contact the application vendor to verify that this is a valid Windows Installer package.
439
+ /// </summary>
440
+ ERROR_INSTALL_PACKAGE_INVALID = 1620,
441
+
442
+ /// <summary>
443
+ /// ERROR_INSTALL_UI_FAILURE 1621
444
+ /// There was an error starting the Windows Installer service user interface.
445
+ /// Contact your support personnel.
446
+ /// </summary>
447
+ ERROR_INSTALL_UI_FAILURE = 1621,
448
+
449
+ /// <summary>
450
+ /// ERROR_INSTALL_LOG_FAILURE 1622
451
+ /// There was an error opening installation log file.
452
+ /// Verify that the specified log file location exists and is writable.
453
+ /// </summary>
454
+ ERROR_INSTALL_LOG_FAILURE = 1622,
455
+
456
+ /// <summary>
457
+ /// ERROR_INSTALL_LANGUAGE_UNSUPPORTED 1623
458
+ /// This language of this installation package is not supported by your system.
459
+ /// </summary>
460
+ ERROR_INSTALL_LANGUAGE_UNSUPPORTED = 1623,
461
+
462
+ /// <summary>
463
+ /// ERROR_INSTALL_TRANSFORM_FAILURE 1624
464
+ /// There was an error applying transforms.
465
+ /// Verify that the specified transform paths are valid.
466
+ /// </summary>
467
+ ERROR_INSTALL_TRANSFORM_FAILURE = 1624,
468
+
469
+
470
+ /// <summary>
471
+ /// ERROR_INSTALL_PACKAGE_REJECTED 1625
472
+ /// This installation is forbidden by system policy.
473
+ /// Contact your system administrator.
474
+ /// </summary>
475
+ ERROR_INSTALL_PACKAGE_REJECTED = 1625,
476
+
477
+ /// <summary>
478
+ /// ERROR_FUNCTION_NOT_CALLED 1626
479
+ /// The function could not be executed.
480
+ /// </summary>
481
+ ERROR_FUNCTION_NOT_CALLED = 1626,
482
+
483
+ /// <summary>
484
+ /// ERROR_FUNCTION_FAILED 1627
485
+ /// The function failed during execution.
486
+ /// </summary>
487
+ ERROR_FUNCTION_FAILED = 1627,
488
+
489
+ /// <summary>
490
+ /// ERROR_INVALID_TABLE 1628
491
+ /// An invalid or unknown table was specified.
492
+ /// </summary>
493
+ ERROR_INVALID_TABLE = 1628,
494
+
495
+ /// <summary>
496
+ /// ERROR_DATATYPE_MISMATCH 1629
497
+ /// The data supplied is the wrong type.
498
+ /// </summary>
499
+ ERROR_DATATYPE_MISMATCH = 1629,
500
+
501
+ /// <summary>
502
+ /// ERROR_UNSUPPORTED_TYPE 1630
503
+ /// Data of this type is not supported.
504
+ /// </summary>
505
+ ERROR_UNSUPPORTED_TYPE = 1630,
506
+
507
+ /// <summary>
508
+ /// ERROR_CREATE_FAILED 1631
509
+ /// The Windows Installer service failed to start.
510
+ /// Contact your support personnel.
511
+ /// </summary>
512
+ ERROR_CREATE_FAILED = 1631,
513
+
514
+ /// <summary>
515
+ /// ERROR_INSTALL_TEMP_UNWRITABLE 1632
516
+ /// The Temp folder is either full or inaccessible.
517
+ /// Verify that the Temp folder exists and that you can write to it.
518
+ /// </summary>
519
+ ERROR_INSTALL_TEMP_UNWRITABLE = 1632,
520
+
521
+ /// <summary>
522
+ /// ERROR_INSTALL_PLATFORM_UNSUPPORTED 1633
523
+ /// This installation package is not supported on this platform. Contact your application vendor. </summary>
524
+ ERROR_INSTALL_PLATFORM_UNSUPPORTED = 1633,
525
+
526
+ /// <summary>
527
+ /// ERROR_INSTALL_NOTUSED 1634
528
+ /// Component is not used on this machine.
529
+ /// </summary>
530
+ ERROR_INSTALL_NOTUSED = 1634,
531
+
532
+ /// <summary>
533
+ /// ERROR_PATCH_PACKAGE_OPEN_FAILED 1635
534
+ /// This patch package could not be opened. Verify that the patch package exists and is accessible,
535
+ /// or contact the application vendor to verify that this is a valid Windows Installer patch package.
536
+ /// </summary>
537
+ ERROR_PATCH_PACKAGE_OPEN_FAILED = 1635,
538
+
539
+ /// <summary>
540
+ /// ERROR_PATCH_PACKAGE_INVALID 1636
541
+ /// This patch package could not be opened.
542
+ /// Contact the application vendor to verify that this is a valid Windows Installer patch package.
543
+ /// </summary>
544
+ ERROR_PATCH_PACKAGE_INVALID = 1636,
545
+
546
+ /// <summary>
547
+ /// ERROR_PATCH_PACKAGE_UNSUPPORTED 1637
548
+ /// This patch package cannot be processed by the Windows Installer service.
549
+ /// You must install a Windows service pack that contains a newer version of the Windows Installer service.
550
+ /// </summary>
551
+ ERROR_PATCH_PACKAGE_UNSUPPORTED = 1637,
552
+
553
+ /// <summary>
554
+ /// ERROR_PRODUCT_VERSION 1638
555
+ /// Another version of this product is already installed.
556
+ /// Installation of this version cannot continue. To configure or remove the existing version of this product,
557
+ /// use Add/Remove Programs in Control Panel.
558
+ /// </summary>
559
+ ERROR_PRODUCT_VERSION = 1638,
560
+
561
+ /// <summary>
562
+ /// ERROR_INVALID_COMMAND_LINE 1639
563
+ /// Invalid command line argument.
564
+ /// Consult the Windows Installer SDK for detailed command-line help.
565
+ /// </summary>
566
+ ERROR_INVALID_COMMAND_LINE = 1639,
567
+
568
+ /// <summary>
569
+ /// ERROR_INSTALL_REMOTE_DISALLOWED 1640
570
+ /// The current user is not permitted to perform installations from a client session of a server running the
571
+ /// Terminal Server role service.
572
+ /// </summary>
573
+ ERROR_INSTALL_REMOTE_DISALLOWED = 1640,
574
+
575
+ /// <summary>
576
+ /// ERROR_SUCCESS_REBOOT_INITIATED 1641
577
+ /// The installer has initiated a restart.
578
+ /// This message is indicative of a success.
579
+ /// </summary>
580
+ ERROR_SUCCESS_REBOOT_INITIATED = 1641,
581
+
582
+ /// <summary>
583
+ /// ERROR_PATCH_TARGET_NOT_FOUND 1642
584
+ /// The installer cannot install the upgrade patch because the program being upgraded may be missing or the
585
+ /// upgrade patch updates a different version of the program.
586
+ /// Verify that the program to be upgraded exists on your computer and that you have the correct upgrade patch.
587
+ /// </summary>
588
+ ERROR_PATCH_TARGET_NOT_FOUND = 1642,
589
+
590
+ /// <summary>
591
+ /// ERROR_PATCH_PACKAGE_REJECTED 1643
592
+ /// The patch package is not permitted by system policy.
593
+ /// </summary>
594
+ ERROR_PATCH_PACKAGE_REJECTED = 1643,
595
+
596
+ /// <summary>
597
+ /// ERROR_INSTALL_TRANSFORM_REJECTED 1644
598
+ /// One or more customizations are not permitted by system policy.
599
+ /// </summary>
600
+ ERROR_INSTALL_TRANSFORM_REJECTED = 1644,
601
+
602
+ /// <summary>
603
+ /// ERROR_INSTALL_REMOTE_PROHIBITED 1645
604
+ /// Windows Installer does not permit installation from a Remote Desktop Connection.
605
+ /// </summary>
606
+ ERROR_INSTALL_REMOTE_PROHIBITED = 1645,
607
+
608
+ /// <summary>
609
+ /// ERROR_PATCH_REMOVAL_UNSUPPORTED 1646
610
+ /// The patch package is not a removable patch package. Available beginning with Windows Installer version 3.0.
611
+ /// </summary>
612
+ ERROR_PATCH_REMOVAL_UNSUPPORTED = 1646,
613
+
614
+ /// <summary>
615
+ /// ERROR_UNKNOWN_PATCH 1647
616
+ /// The patch is not applied to this product. Available beginning with Windows Installer version 3.0.
617
+ /// </summary>
618
+ ERROR_UNKNOWN_PATCH = 1647,
619
+
620
+ /// <summary>
621
+ /// ERROR_PATCH_NO_SEQUENCE 1648
622
+ /// No valid sequence could be found for the set of patches. Available beginning with Windows Installer version 3.0.
623
+ /// </summary>
624
+ ERROR_PATCH_NO_SEQUENCE = 1648,
625
+
626
+ /// <summary>
627
+ /// ERROR_PATCH_REMOVAL_DISALLOWED 1649
628
+ /// Patch removal was disallowed by policy. Available beginning with Windows Installer version 3.0. </summary>
629
+ ERROR_PATCH_REMOVAL_DISALLOWED = 1649,
630
+
631
+ /// <summary>
632
+ /// ERROR_INVALID_PATCH_XML = 1650
633
+ /// The XML patch data is invalid. Available beginning with Windows Installer version 3.0.
634
+ /// </summary>
635
+ ERROR_INVALID_PATCH_XML = 1650,
636
+
637
+ /// <summary>
638
+ /// ERROR_PATCH_MANAGED_ADVERTISED_PRODUCT 1651
639
+ /// Administrative user failed to apply patch for a per-user managed or a per-machine application that is in advertise state.
640
+ /// Available beginning with Windows Installer version 3.0. </summary>
641
+ ERROR_PATCH_MANAGED_ADVERTISED_PRODUCT = 1651,
642
+
643
+ /// <summary>
644
+ /// ERROR_INSTALL_SERVICE_SAFEBOOT 1652
645
+ /// Windows Installer is not accessible when the computer is in Safe Mode.
646
+ /// Exit Safe Mode and try again or try using System Restore to return your computer to a previous state.
647
+ /// Available beginning with Windows Installer version 4.0.
648
+ /// </summary>
649
+ ERROR_INSTALL_SERVICE_SAFEBOOT = 1652,
650
+
651
+ /// <summary>
652
+ /// ERROR_ROLLBACK_DISABLED 1653
653
+ /// Could not perform a multiple-package transaction because rollback has been disabled.
654
+ /// Multiple-Package Installations cannot run if rollback is disabled. Available beginning with Windows Installer version 4.5.
655
+ /// </summary>
656
+ ERROR_ROLLBACK_DISABLED = 1653,
657
+
658
+ /// <summary>
659
+ /// ERROR_SUCCESS_REBOOT_REQUIRED 3010
660
+ /// A restart is required to complete the install. This message is indicative of a success.
661
+ /// This does not include installs where the ForceReboot action is run.
662
+ /// </summary>
663
+ ERROR_SUCCESS_REBOOT_REQUIRED = 3010
664
+ }
665
+
666
+ /// <summary>
667
+ /// Modes of operations for MSIExec; install, administrator install, uninstall .. etc
668
+ /// </summary>
669
+ public enum MSIExecMode
670
+ {
671
+ /// <summary>
672
+ /// Installs or configures a product
673
+ /// </summary>
674
+ Install = 0,
675
+
676
+ /// <summary>
677
+ /// Administrative install - Installs a product on the network
678
+ /// </summary>
679
+ AdministrativeInstall,
680
+
681
+ /// <summary>
682
+ /// Uninstalls the product
683
+ /// </summary>
684
+ Uninstall,
685
+
686
+ /// <summary>
687
+ /// Repairs a product
688
+ /// </summary>
689
+ Repair,
690
+
691
+ /// <summary>
692
+ /// Modifies a product
693
+ /// </summary>
694
+ Modify,
695
+
696
+ /// <summary>
697
+ /// Uninstalls the product as part of cleanup
698
+ /// </summary>
699
+ Cleanup,
700
+ }
701
+
702
+ /// <summary>
703
+ /// User interfave levels
704
+ /// </summary>
705
+ public enum MSIExecUserInterfaceLevel
706
+ {
707
+ /// <summary>
708
+ /// No UI
709
+ /// </summary>
710
+ None = 0,
711
+
712
+ /// <summary>
713
+ /// Basic UI
714
+ /// </summary>
715
+ Basic,
716
+
717
+ /// <summary>
718
+ /// Reduced UI
719
+ /// </summary>
720
+ Reduced,
721
+
722
+ /// <summary>
723
+ /// Full UI (default)
724
+ /// </summary>
725
+ Full
726
+ }
727
+
728
+ /// <summary>
729
+ /// Logging options
730
+ /// </summary>
731
+ [Flags]
732
+ public enum MSIExecLoggingOptions
733
+ {
734
+ Status_Messages = 0x0001,
735
+ Nonfatal_Warnings = 0x0002,
736
+ All_Error_Messages = 0x0004,
737
+ Start_Up_Of_Actions = 0x0008,
738
+ Action_Specific_Records = 0x0010,
739
+ User_Requests = 0x0020,
740
+ Initial_UI_Parameters = 0x0040,
741
+ OutOfMemory_Or_Fatal_Exit_Information = 0x0080,
742
+ OutOfDiskSpace_Messages = 0x0100,
743
+ Terminal_Properties = 0x0200,
744
+ Verbose_Output = 0x0400,
745
+ Append_To_Existing_Log_File = 0x0800,
746
+
747
+ Flush_Each_line = 0x1000,
748
+ Extra_Debugging_Information = 0x2000,
749
+ Log_All_Information = 0x4000,
750
+ VOICEWARMUP = 0x0FFF
751
+ }
752
+ }
753
+}
src/WixToolsetTest.BurnE2E/MsiTransactionTests.cs
new
+111
@@ -0,0 +1,111 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.BurnE2E
4
+{
5
+ using System;
6
+ using System.IO;
7
+ using Xunit;
8
+ using Xunit.Abstractions;
9
+
10
+ public class MsiTransactionTests : BurnE2ETests
11
+ {
12
+ public MsiTransactionTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper, "MsiTransaction") { }
13
+
14
+ [Fact]
15
+ public void CanUpgradeBundleWithMsiTransaction()
16
+ {
17
+ var packageA = this.CreatePackageInstaller("PackageA");
18
+ var packageBv1 = this.CreatePackageInstaller("PackageBv1");
19
+ var packageBv2 = this.CreatePackageInstaller("PackageBv2");
20
+ var packageCv1 = this.CreatePackageInstaller("PackageCv1");
21
+ var packageCv2 = this.CreatePackageInstaller("PackageCv2");
22
+ var packageD = this.CreatePackageInstaller("PackageD");
23
+
24
+ var bundleAv1 = this.CreateBundleInstaller("BundleAv1");
25
+ var bundleAv2 = this.CreateBundleInstaller("BundleAv2");
26
+
27
+ var packageASourceCodeInstalled = packageA.GetInstalledFilePath("Package.wxs");
28
+ var packageBv1SourceCodeInstalled = packageBv1.GetInstalledFilePath("Package.wxs");
29
+ var packageBv2SourceCodeInstalled = packageBv2.GetInstalledFilePath("Package.wxs");
30
+ var packageCv1SourceCodeInstalled = packageCv1.GetInstalledFilePath("Package.wxs");
31
+ var packageCv2SourceCodeInstalled = packageCv2.GetInstalledFilePath("Package.wxs");
32
+ var packageDSourceCodeInstalled = packageD.GetInstalledFilePath("Package.wxs");
33
+
34
+ // Source file should *not* be installed
35
+ Assert.False(File.Exists(packageASourceCodeInstalled), $"Package A payload should not be there on test start: {packageASourceCodeInstalled}");
36
+ Assert.False(File.Exists(packageBv1SourceCodeInstalled), $"Package Bv1 payload should not be there on test start: {packageBv1SourceCodeInstalled}");
37
+ Assert.False(File.Exists(packageBv2SourceCodeInstalled), $"Package Bv2 payload should not be there on test start: {packageBv2SourceCodeInstalled}");
38
+ Assert.False(File.Exists(packageCv1SourceCodeInstalled), $"Package Cv1 payload should not be there on test start: {packageCv1SourceCodeInstalled}");
39
+ Assert.False(File.Exists(packageCv2SourceCodeInstalled), $"Package Cv2 payload should not be there on test start: {packageCv2SourceCodeInstalled}");
40
+ Assert.False(File.Exists(packageDSourceCodeInstalled), $"Package D payload should not be there on test start: {packageDSourceCodeInstalled}");
41
+
42
+ bundleAv1.Install();
43
+
44
+ // Source file should be installed
45
+ Assert.True(File.Exists(packageASourceCodeInstalled), String.Concat("Should have found Package A payload installed at: ", packageASourceCodeInstalled));
46
+ Assert.True(File.Exists(packageBv1SourceCodeInstalled), String.Concat("Should have found Package Bv1 payload installed at: ", packageBv1SourceCodeInstalled));
47
+ Assert.True(File.Exists(packageCv1SourceCodeInstalled), String.Concat("Should have found Package Cv1 payload installed at: ", packageCv1SourceCodeInstalled));
48
+
49
+ bundleAv2.Install();
50
+
51
+ // Source file should be upgraded
52
+ Assert.True(File.Exists(packageDSourceCodeInstalled), String.Concat("Should have found Package D payload installed at: ", packageDSourceCodeInstalled));
53
+ Assert.True(File.Exists(packageBv2SourceCodeInstalled), String.Concat("Should have found Package Bv2 payload installed at: ", packageBv2SourceCodeInstalled));
54
+ Assert.True(File.Exists(packageCv2SourceCodeInstalled), String.Concat("Should have found Package Cv2 payload installed at: ", packageCv2SourceCodeInstalled));
55
+ Assert.False(File.Exists(packageCv1SourceCodeInstalled), String.Concat("Package Cv1 payload should have been removed by upgrade uninstall from: ", packageCv1SourceCodeInstalled));
56
+ Assert.False(File.Exists(packageBv1SourceCodeInstalled), String.Concat("Package Bv1 payload should have been removed by upgrade uninstall from: ", packageBv1SourceCodeInstalled));
57
+ Assert.False(File.Exists(packageASourceCodeInstalled), String.Concat("Package A payload should have been removed by upgrade uninstall from: ", packageASourceCodeInstalled));
58
+
59
+ // Uninstall everything.
60
+ bundleAv2.Uninstall();
61
+
62
+ // Source file should *not* be installed
63
+ Assert.False(File.Exists(packageDSourceCodeInstalled), String.Concat("Package D payload should have been removed by uninstall from: ", packageDSourceCodeInstalled));
64
+ Assert.False(File.Exists(packageBv2SourceCodeInstalled), String.Concat("Package Bv2 payload should have been removed by uninstall from: ", packageBv2SourceCodeInstalled));
65
+ Assert.False(File.Exists(packageCv2SourceCodeInstalled), String.Concat("Package Cv2 payload should have been removed by uninstall from: ", packageCv2SourceCodeInstalled));
66
+ }
67
+
68
+ /// <summary>
69
+ /// Installs 2 bundles:
70
+ /// BundleBv1- installs package Bv1
71
+ /// BundleBv2- installs packages A, Bv2, F
72
+ /// package Bv2 performs a major upgrade of package Bv1
73
+ /// package F fails
74
+ /// Thus, rolling back the transaction should reinstall package Bv1
75
+ /// </summary>
76
+ [Fact]
77
+ public void CanRelyOnMsiTransactionRollback()
78
+ {
79
+ var packageA = this.CreatePackageInstaller("PackageA");
80
+ var packageBv1 = this.CreatePackageInstaller("PackageBv1");
81
+ var packageBv2 = this.CreatePackageInstaller("PackageBv2");
82
+ this.CreatePackageInstaller("PackageF");
83
+
84
+ var bundleBv1 = this.CreateBundleInstaller("BundleBv1");
85
+ var bundleBv2 = this.CreateBundleInstaller("BundleBv2");
86
+
87
+ var packageASourceCodeInstalled = packageA.GetInstalledFilePath("Package.wxs");
88
+ var packageBv1SourceCodeInstalled = packageBv1.GetInstalledFilePath("Package.wxs");
89
+ var packageBv2SourceCodeInstalled = packageBv2.GetInstalledFilePath("Package.wxs");
90
+
91
+ // Source file should *not* be installed
92
+ Assert.False(File.Exists(packageASourceCodeInstalled), $"Package A payload should not be there on test start: {packageASourceCodeInstalled}");
93
+ Assert.False(File.Exists(packageBv1SourceCodeInstalled), $"Package Bv1 payload should not be there on test start: {packageBv1SourceCodeInstalled}");
94
+ Assert.False(File.Exists(packageBv2SourceCodeInstalled), $"Package Bv2 payload should not be there on test start: {packageBv2SourceCodeInstalled}");
95
+
96
+ bundleBv1.Install();
97
+
98
+ // Source file should be installed
99
+ Assert.True(File.Exists(packageBv1SourceCodeInstalled), String.Concat("Should have found Package Bv1 payload installed at: ", packageBv1SourceCodeInstalled));
100
+
101
+ bundleBv2.Install((int)MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE);
102
+
103
+ // Source file should be installed
104
+ Assert.True(File.Exists(packageASourceCodeInstalled), String.Concat("Should have found Package A payload installed at: ", packageASourceCodeInstalled));
105
+
106
+ // Previous source file should be installed
107
+ Assert.True(File.Exists(packageBv1SourceCodeInstalled), String.Concat("Should have found Package Bv1 payload installed at: ", packageBv1SourceCodeInstalled));
108
+ Assert.False(File.Exists(packageBv2SourceCodeInstalled), String.Concat("Should not have found Package Bv2 payload installed at: ", packageBv2SourceCodeInstalled));
109
+ }
110
+ }
111
+}
src/WixToolsetTest.BurnE2E/PackageInstaller.cs
new
+97
@@ -0,0 +1,97 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.BurnE2E
4
+{
5
+ using System;
6
+ using System.IO;
7
+ using static WixToolsetTest.BurnE2E.MSIExec;
8
+
9
+ public class PackageInstaller : IDisposable
10
+ {
11
+ public PackageInstaller(WixTestContext testContext, string name)
12
+ {
13
+ this.Package = Path.Combine(testContext.TestDataFolder, $"{name}.msi");
14
+ this.PackageName = name;
15
+ this.TestContext = testContext;
16
+ }
17
+
18
+ public string Package { get; }
19
+
20
+ private string PackageName { get; }
21
+
22
+ private WixTestContext TestContext { get; }
23
+
24
+ public string TestGroupName => this.TestContext.TestGroupName;
25
+
26
+ public string TestName => this.TestContext.TestName;
27
+
28
+ public string GetInstalledFilePath(string filename)
29
+ {
30
+ return this.TestContext.GetTestInstallFolder(Path.Combine(this.PackageName, filename));
31
+ }
32
+
33
+ /// <summary>
34
+ /// Installs a .msi file
35
+ /// </summary>
36
+ /// <param name="expectedExitCode">Expected exit code</param>
37
+ /// <param name="otherArguments">Other arguments to pass to MSIExec.</param>
38
+ /// <returns>MSIExec log File</returns>
39
+ public string InstallProduct(MSIExecReturnCode expectedExitCode = MSIExecReturnCode.SUCCESS, params string[] otherArguments)
40
+ {
41
+ return this.RunMSIExec(MSIExecMode.Install, otherArguments, expectedExitCode);
42
+ }
43
+
44
+ /// <summary>
45
+ /// Uninstalls a .msi file
46
+ /// </summary>
47
+ /// <param name="expectedExitCode">Expected exit code</param>
48
+ /// <param name="otherArguments">Other arguments to pass to MSIExec.</param>
49
+ /// <returns>MSIExec log File</returns>
50
+ public string UninstallProduct(MSIExecReturnCode expectedExitCode, params string[] otherArguments)
51
+ {
52
+ return this.RunMSIExec(MSIExecMode.Uninstall, otherArguments, expectedExitCode);
53
+ }
54
+
55
+ /// <summary>
56
+ /// Repairs a .msi file
57
+ /// </summary>
58
+ /// <param name="expectedExitCode">Expected exit code</param>
59
+ /// <param name="otherArguments">Other arguments to pass to msiexe.exe.</param>
60
+ /// <returns>MSIExec log File</returns>
61
+ public string RepairProduct(MSIExecReturnCode expectedExitCode, params string[] otherArguments)
62
+ {
63
+ return this.RunMSIExec(MSIExecMode.Repair, otherArguments, expectedExitCode);
64
+ }
65
+
66
+ /// <summary>
67
+ /// Executes MSIExec on a .msi file
68
+ /// </summary>
69
+ /// <param name="mode">Mode of execution for MSIExec</param>
70
+ /// <param name="otherArguments">Other arguments to pass to MSIExec.</param>
71
+ /// <param name="expectedExitCode">Expected exit code</param>
72
+ /// <returns>MSIExec exit code</returns>
73
+ private string RunMSIExec(MSIExecMode mode, string[] otherArguments, MSIExecReturnCode expectedExitCode, bool assertOnError = true)
74
+ {
75
+ // Generate the log file name.
76
+ var logFile = Path.Combine(Path.GetTempPath(), String.Format("{0}_{1}_{2:yyyyMMddhhmmss}_{4}_{3}.log", this.TestGroupName, this.TestName, DateTime.UtcNow, Path.GetFileNameWithoutExtension(this.Package), mode));
77
+
78
+ var msiexec = new MSIExec
79
+ {
80
+ Product = this.Package,
81
+ ExecutionMode = mode,
82
+ OtherArguments = null != otherArguments ? String.Join(" ", otherArguments) : null,
83
+ ExpectedExitCode = expectedExitCode,
84
+ LogFile = logFile,
85
+ };
86
+
87
+ msiexec.Run(assertOnError);
88
+ return msiexec.LogFile;
89
+ }
90
+
91
+ public void Dispose()
92
+ {
93
+ string[] args = { "IGNOREDEPENDENCIES=ALL", "WIXFAILWHENDEFERRED=0" };
94
+ this.RunMSIExec(MSIExecMode.Cleanup, args, MSIExecReturnCode.SUCCESS, assertOnError: false);
95
+ }
96
+ }
97
+}
src/WixToolsetTest.BurnE2E/TestTool.cs
new
+245
@@ -0,0 +1,245 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.BurnE2E
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.Text;
8
+ using System.Text.RegularExpressions;
9
+ using WixBuildTools.TestSupport;
10
+ using Xunit;
11
+
12
+ public class TestTool : ExternalExecutable
13
+ {
14
+ /// <summary>
15
+ /// Constructor for a TestTool
16
+ /// </summary>
17
+ public TestTool()
18
+ : this(null)
19
+ {
20
+ }
21
+
22
+ /// <summary>
23
+ /// Constructor for a TestTool
24
+ /// </summary>
25
+ /// <param name="toolFile">The full path to the tool. Eg. c:\bin\candle.exe</param>
26
+ public TestTool(string toolFile)
27
+ : base(toolFile)
28
+ {
29
+ this.PrintOutputToConsole = true;
30
+ }
31
+
32
+ /// <summary>
33
+ /// The arguments to pass to the tool
34
+ /// </summary>
35
+ public virtual string Arguments { get; set; }
36
+
37
+ /// <summary>
38
+ /// Stores the errors that occurred when a run was checked against its expected results
39
+ /// </summary>
40
+ public List<string> Errors { get; set; }
41
+
42
+ /// <summary>
43
+ /// A list of Regex's that are expected to match stderr
44
+ /// </summary>
45
+ public List<Regex> ExpectedErrorRegexs { get; set; } = new List<Regex>();
46
+
47
+ /// <summary>
48
+ /// The expected error strings to stderr
49
+ /// </summary>
50
+ public List<string> ExpectedErrorStrings { get; set; } = new List<string>();
51
+
52
+ /// <summary>
53
+ /// The expected exit code of the tool
54
+ /// </summary>
55
+ public int? ExpectedExitCode { get; set; }
56
+
57
+ /// <summary>
58
+ /// A list of Regex's that are expected to match stdout
59
+ /// </summary>
60
+ public List<Regex> ExpectedOutputRegexs { get; set; } = new List<Regex>();
61
+
62
+ /// <summary>
63
+ /// The expected output strings to stdout
64
+ /// </summary>
65
+ public List<string> ExpectedOutputStrings { get; set; } = new List<string>();
66
+
67
+ /// <summary>
68
+ /// Print output from the tool execution to the console
69
+ /// </summary>
70
+ public bool PrintOutputToConsole { get; set; }
71
+
72
+ /// <summary>
73
+ /// The working directory of the tool
74
+ /// </summary>
75
+ public string WorkingDirectory { get; set; }
76
+
77
+ /// <summary>
78
+ /// Print the errors from the last run
79
+ /// </summary>
80
+ public void PrintErrors()
81
+ {
82
+ if (null != this.Errors)
83
+ {
84
+ Console.WriteLine("Errors:");
85
+
86
+ foreach (string error in this.Errors)
87
+ {
88
+ Console.WriteLine(error);
89
+ }
90
+ }
91
+ }
92
+
93
+ /// <summary>
94
+ /// Run the tool
95
+ /// </summary>
96
+ /// <returns>The results of the run</returns>
97
+ public ExternalExecutableResult Run()
98
+ {
99
+ return this.Run(true);
100
+ }
101
+
102
+ /// <summary>
103
+ /// Run the tool
104
+ /// </summary>
105
+ /// <param name="exceptionOnError">Throw an exception if the expected results don't match the actual results</param>
106
+ /// <exception cref="System.Exception">Thrown when the expected results don't match the actual results</exception>
107
+ /// <returns>The results of the run</returns>
108
+ public virtual ExternalExecutableResult Run(bool assertOnError)
109
+ {
110
+ var result = this.Run(this.Arguments, workingDirectory: this.WorkingDirectory ?? String.Empty);
111
+
112
+ if (this.PrintOutputToConsole)
113
+ {
114
+ Console.WriteLine(FormatResult(result));
115
+ }
116
+
117
+ this.Errors = this.CheckResult(result);
118
+
119
+ if (assertOnError && 0 < this.Errors.Count)
120
+ {
121
+ if (this.PrintOutputToConsole)
122
+ {
123
+ this.PrintErrors();
124
+ }
125
+
126
+ Assert.Empty(this.Errors);
127
+ }
128
+
129
+ return result;
130
+ }
131
+
132
+ /// <summary>
133
+ /// Checks that the result from a run matches the expected results
134
+ /// </summary>
135
+ /// <param name="result">A result from a run</param>
136
+ /// <returns>A list of errors</returns>
137
+ public virtual List<string> CheckResult(ExternalExecutableResult result)
138
+ {
139
+ List<string> errors = new List<string>();
140
+
141
+ // Verify that the expected return code matched the actual return code
142
+ if (null != this.ExpectedExitCode && this.ExpectedExitCode != result.ExitCode)
143
+ {
144
+ errors.Add(String.Format("Expected exit code {0} did not match actual exit code {1}", this.ExpectedExitCode, result.ExitCode));
145
+ }
146
+
147
+ var standardErrorString = string.Join(Environment.NewLine, result.StandardError);
148
+
149
+ // Verify that the expected error string are in stderr
150
+ if (null != this.ExpectedErrorStrings)
151
+ {
152
+ foreach (string expectedString in this.ExpectedErrorStrings)
153
+ {
154
+ if (!standardErrorString.Contains(expectedString))
155
+ {
156
+ errors.Add(String.Format("The text '{0}' was not found in stderr", expectedString));
157
+ }
158
+ }
159
+ }
160
+
161
+ var standardOutputString = string.Join(Environment.NewLine, result.StandardOutput);
162
+
163
+ // Verify that the expected output string are in stdout
164
+ if (null != this.ExpectedOutputStrings)
165
+ {
166
+ foreach (string expectedString in this.ExpectedOutputStrings)
167
+ {
168
+ if (!standardOutputString.Contains(expectedString))
169
+ {
170
+ errors.Add(String.Format("The text '{0}' was not found in stdout", expectedString));
171
+ }
172
+ }
173
+ }
174
+
175
+ // Verify that the expected regular expressions match stderr
176
+ if (null != this.ExpectedOutputRegexs)
177
+ {
178
+ foreach (Regex expectedRegex in this.ExpectedOutputRegexs)
179
+ {
180
+ if (!expectedRegex.IsMatch(standardOutputString))
181
+ {
182
+ errors.Add(String.Format("Regex {0} did not match stdout", expectedRegex.ToString()));
183
+ }
184
+ }
185
+ }
186
+
187
+ // Verify that the expected regular expressions match stdout
188
+ if (null != this.ExpectedErrorRegexs)
189
+ {
190
+ foreach (Regex expectedRegex in this.ExpectedErrorRegexs)
191
+ {
192
+ if (!expectedRegex.IsMatch(standardErrorString))
193
+ {
194
+ errors.Add(String.Format("Regex {0} did not match stderr", expectedRegex.ToString()));
195
+ }
196
+ }
197
+ }
198
+
199
+ return errors;
200
+ }
201
+
202
+ /// <summary>
203
+ /// Clears all of the expected results and resets them to the default values
204
+ /// </summary>
205
+ public virtual void SetDefaultExpectedResults()
206
+ {
207
+ this.ExpectedErrorRegexs = new List<Regex>();
208
+ this.ExpectedErrorStrings = new List<string>();
209
+ this.ExpectedExitCode = null;
210
+ this.ExpectedOutputRegexs = new List<Regex>();
211
+ this.ExpectedOutputStrings = new List<string>();
212
+ }
213
+
214
+ /// <summary>
215
+ /// Returns a string with data contained in the result.
216
+ /// </summary>
217
+ /// <returns>A string</returns>
218
+ private static string FormatResult(ExternalExecutableResult result)
219
+ {
220
+ var returnValue = new StringBuilder();
221
+ returnValue.AppendLine();
222
+ returnValue.AppendLine("----------------");
223
+ returnValue.AppendLine("Tool run result:");
224
+ returnValue.AppendLine("----------------");
225
+ returnValue.AppendLine("Command:");
226
+ returnValue.AppendLine($"\"{result.StartInfo.FileName}\" {result.StartInfo.Arguments}");
227
+ returnValue.AppendLine();
228
+ returnValue.AppendLine("Standard Output:");
229
+ foreach (var line in result.StandardOutput ?? new string[0])
230
+ {
231
+ returnValue.AppendLine(line);
232
+ }
233
+ returnValue.AppendLine("Standard Error:");
234
+ foreach (var line in result.StandardError ?? new string[0])
235
+ {
236
+ returnValue.AppendLine(line);
237
+ }
238
+ returnValue.AppendLine("Exit Code:");
239
+ returnValue.AppendLine(Convert.ToString(result.ExitCode));
240
+ returnValue.AppendLine("----------------");
241
+
242
+ return returnValue.ToString();
243
+ }
244
+ }
245
+}
src/WixToolsetTest.BurnE2E/WixTestBase.cs
new
+21
@@ -0,0 +1,21 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.BurnE2E
4
+{
5
+ using System;
6
+ using System.IO;
7
+ using Xunit.Abstractions;
8
+
9
+ public abstract class WixTestBase
10
+ {
11
+ protected WixTestBase(ITestOutputHelper testOutputHelper, string testGroupName)
12
+ {
13
+ this.TestContext = new WixTestContext(testOutputHelper, testGroupName);
14
+ }
15
+
16
+ /// <summary>
17
+ /// The test context for the current test.
18
+ /// </summary>
19
+ public WixTestContext TestContext { get; }
20
+ }
21
+}
src/WixToolsetTest.BurnE2E/WixTestContext.cs
new
+70
@@ -0,0 +1,70 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.BurnE2E
4
+{
5
+ using System;
6
+ using System.IO;
7
+ using System.Reflection;
8
+ using Microsoft.Win32;
9
+ using WixBuildTools.TestSupport;
10
+ using Xunit.Abstractions;
11
+
12
+ public class WixTestContext
13
+ {
14
+ static readonly string RootDataPath = Path.GetFullPath(TestData.Get(".."));
15
+
16
+ public WixTestContext(ITestOutputHelper testOutputHelper, string testGroupName)
17
+ {
18
+ var test = GetTest(testOutputHelper);
19
+
20
+ this.TestDataFolder = Path.Combine(RootDataPath, testGroupName);
21
+ this.TestGroupName = testGroupName;
22
+ this.TestName = test.TestCase.TestMethod.Method.Name;
23
+ }
24
+
25
+ public string TestDataFolder { get; }
26
+
27
+ /// <summary>
28
+ /// Gets the name of the current test group.
29
+ /// </summary>
30
+ public string TestGroupName { get; }
31
+
32
+ public string TestName { get; }
33
+
34
+ /// <summary>
35
+ /// Gets the test install directory for the current test.
36
+ /// </summary>
37
+ /// <param name="additionalPath">Additional subdirectories under the test install directory.</param>
38
+ /// <returns>Full path to the test install directory.</returns>
39
+ /// <remarks>
40
+ /// The package or bundle must install into [ProgramFilesFolder]\~Test WiX\[TestGroupName]\([Additional]).
41
+ /// </remarks>
42
+ public string GetTestInstallFolder(string additionalPath = null)
43
+ {
44
+ return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "~Test WiX", this.TestGroupName, additionalPath ?? String.Empty);
45
+ }
46
+
47
+ /// <summary>
48
+ /// Gets the test registry key for the current test.
49
+ /// </summary>
50
+ /// <param name="additionalPath">Additional subkeys under the test registry key.</param>
51
+ /// <returns>Full path to the test registry key.</returns>
52
+ /// <remarks>
53
+ /// The package must write into HKLM\Software\WiX\Tests\[TestGroupName]\([Additional]).
54
+ /// </remarks>
55
+ public RegistryKey GetTestRegistryRoot(string additionalPath = null)
56
+ {
57
+ var key = String.Format(@"Software\WiX\Tests\{0}\{1}", this.TestName, additionalPath ?? String.Empty);
58
+ return Registry.LocalMachine.OpenSubKey(key, true);
59
+ }
60
+
61
+ private static ITest GetTest(ITestOutputHelper output)
62
+ {
63
+ // https://github.com/xunit/xunit/issues/416#issuecomment-378512739
64
+ var type = output.GetType();
65
+ var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic);
66
+ var test = (ITest)testMember.GetValue(output);
67
+ return test;
68
+ }
69
+ }
70
+}
src/WixToolsetTest.BurnE2E/WixToolsetTest.BurnE2E.csproj
new
+24
@@ -0,0 +1,24 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
+
4
+<Project Sdk="Microsoft.NET.Sdk">
5
+ <PropertyGroup>
6
+ <TargetFramework>netcoreapp3.1</TargetFramework>
7
+ </PropertyGroup>
8
+
9
+ <PropertyGroup>
10
+ <NoWarn>NU1701</NoWarn>
11
+ </PropertyGroup>
12
+
13
+ <ItemGroup>
14
+ <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
15
+ <PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />
16
+ <PackageReference Include="WixBuildTools.TestSupport" Version="4.0.*" />
17
+ </ItemGroup>
18
+
19
+ <ItemGroup>
20
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
21
+ <PackageReference Include="xunit" Version="2.4.1" />
22
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" PrivateAssets="All" />
23
+ </ItemGroup>
24
+</Project>
version.json
new
+11
@@ -0,0 +1,11 @@
1
+{
2
+ "version": "4.0",
3
+ "publicReleaseRefSpec": [
4
+ "^refs/heads/master$"
5
+ ],
6
+ "cloudBuild": {
7
+ "buildNumber": {
8
+ "enabled": true
9
+ }
10
+ }
11
+}