@joebigelow / wix / commits / 5d043484

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>(?&lt;="[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 +}