aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/internal/SetBuildNumber/Directory.Packages.props.pp1
-rw-r--r--src/test/burn/WixTestTools/UserVerifier.cs348
-rw-r--r--src/test/burn/WixTestTools/WixTestTools.csproj2
-rw-r--r--src/test/msi/Directory.Build.props11
-rw-r--r--src/test/msi/Directory.Build.targets6
-rw-r--r--src/test/msi/Directory.wixproj.props11
-rw-r--r--src/test/msi/Directory.wixproj.targets13
-rw-r--r--src/test/msi/MsiE2ETests.sln51
-rw-r--r--src/test/msi/README.md29
-rw-r--r--src/test/msi/TestData/Templates/CustomActionFail.exebin0 -> 4608 bytes
-rw-r--r--src/test/msi/TestData/Templates/Product.wxs47
-rw-r--r--src/test/msi/TestData/TestData.proj16
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs35
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductFail/ProductFail.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs39
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/FailIfExists.wxs24
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/ProductFailIfExists.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/NonVitalUserGroup.wxs22
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/ProductNonVitalUserGroup.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/ProductRestrictedDomain.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/RestrictedDomain.wxs20
-rw-r--r--src/test/msi/WixToolsetTest.MsiE2E/MsiE2EFixture.cs28
-rw-r--r--src/test/msi/WixToolsetTest.MsiE2E/MsiE2ETests.cs44
-rw-r--r--src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs162
-rw-r--r--src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj31
-rw-r--r--src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd2
-rw-r--r--src/test/msi/test_msi.cmd23
-rw-r--r--src/test/test.cmd1
-rw-r--r--src/testresultfilelist.txt1
30 files changed, 1032 insertions, 0 deletions
diff --git a/src/internal/SetBuildNumber/Directory.Packages.props.pp b/src/internal/SetBuildNumber/Directory.Packages.props.pp
index 675907e5..48c07179 100644
--- a/src/internal/SetBuildNumber/Directory.Packages.props.pp
+++ b/src/internal/SetBuildNumber/Directory.Packages.props.pp
@@ -38,6 +38,7 @@
38 <ItemGroup> 38 <ItemGroup>
39 <PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="4.7.0" /> 39 <PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="4.7.0" />
40 <PackageVersion Include="System.DirectoryServices" Version="4.7.0" /> 40 <PackageVersion Include="System.DirectoryServices" Version="4.7.0" />
41 <PackageVersion Include="System.DirectoryServices.AccountManagement" Version="4.7.0" />
41 <PackageVersion Include="System.IO.Compression" Version="4.3.0" /> 42 <PackageVersion Include="System.IO.Compression" Version="4.3.0" />
42 <!-- Warning: The version for System.IO.FileSystem.AccessControl must be kept in sync with WixToolset.Core.Native.nuspec --> 43 <!-- Warning: The version for System.IO.FileSystem.AccessControl must be kept in sync with WixToolset.Core.Native.nuspec -->
43 <PackageVersion Include="System.IO.FileSystem.AccessControl" Version="4.6.0" /> 44 <PackageVersion Include="System.IO.FileSystem.AccessControl" Version="4.6.0" />
diff --git a/src/test/burn/WixTestTools/UserVerifier.cs b/src/test/burn/WixTestTools/UserVerifier.cs
new file mode 100644
index 00000000..b5218a79
--- /dev/null
+++ b/src/test/burn/WixTestTools/UserVerifier.cs
@@ -0,0 +1,348 @@
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
3namespace WixTestTools
4{
5 using System;
6 using System.Text;
7 using System.DirectoryServices;
8 using System.DirectoryServices.AccountManagement;
9 using System.Security.Principal;
10 using Xunit;
11
12 /// <summary>
13 /// Contains methods for User account verification
14 /// </summary>
15 public static class UserVerifier
16 {
17 public static class SIDStrings
18 {
19 // Built-In Local Groups
20 public static readonly string BUILTIN_ADMINISTRATORS = "S-1-5-32-544";
21 public static readonly string BUILTIN_USERS = "S-1-5-32-545";
22 public static readonly string BUILTIN_GUESTS = "S-1-5-32-546";
23 public static readonly string BUILTIN_ACCOUNT_OPERATORS = "S-1-5-32-548";
24 public static readonly string BUILTIN_SERVER_OPERATORS = "S-1-5-32-549";
25 public static readonly string BUILTIN_PRINT_OPERATORS = "S-1-5-32-550";
26 public static readonly string BUILTIN_BACKUP_OPERATORS = "S-1-5-32-551";
27 public static readonly string BUILTIN_REPLICATOR = "S-1-5-32-552";
28
29 // Special Groups
30 public static readonly string CREATOR_OWNER = "S-1-3-0";
31 public static readonly string EVERYONE = "S-1-1-0";
32 public static readonly string NT_AUTHORITY_NETWORK = "S-1-5-2";
33 public static readonly string NT_AUTHORITY_INTERACTIVE = "S-1-5-4";
34 public static readonly string NT_AUTHORITY_SYSTEM = "S-1-5-18";
35 public static readonly string NT_AUTHORITY_Authenticated_Users = "S-1-5-11";
36 public static readonly string NT_AUTHORITY_LOCAL_SERVICE = "S-1-5-19";
37 public static readonly string NT_AUTHORITY_NETWORK_SERVICE = "S-1-5-20";
38 }
39
40 /// <summary>
41 /// Create a local user on the machine
42 /// </summary>
43 /// <param name="userName"></param>
44 /// <param name="password"></param>
45 /// <remarks>Has to be run as an Admin</remarks>
46 public static void CreateLocalUser(string userName, string password)
47 {
48 DeleteLocalUser(userName);
49 UserPrincipal newUser = new UserPrincipal(new PrincipalContext(ContextType.Machine));
50 newUser.SetPassword(password);
51 newUser.Name = userName;
52 newUser.Description = "New test User";
53 newUser.UserCannotChangePassword = true;
54 newUser.PasswordNeverExpires = false;
55 newUser.Save();
56 }
57
58 /// <summary>
59 /// Deletes a local user from the machine
60 /// </summary>
61 /// <param name="userName">user name to delete</param>
62 /// <remarks>Has to be run as an Admin</remarks>
63 public static void DeleteLocalUser(string userName)
64 {
65 UserPrincipal newUser = GetUser(String.Empty, userName);
66 if (null != newUser)
67 {
68 newUser.Delete();
69 }
70 }
71
72 /// <summary>
73 /// Verifies that a user exisits or not
74 /// </summary>
75 /// <param name="domainName">domain name for the user, empty for local users</param>
76 /// <param name="userName">the user name</param>
77 public static bool UserExists(string domainName, string userName)
78 {
79 UserPrincipal user = GetUser(domainName, userName);
80
81 return null != user;
82 }
83
84 /// <summary>
85 /// Sets the user information for a given user
86 /// </summary>
87 /// <param name="domainName">domain name for the user, empty for local users</param>
88 /// <param name="userName">the user name</param>
89 /// <param name="passwordExpired">user is required to change the password on first login</param>
90 /// <param name="passwordNeverExpires">password never expires</param>
91 /// <param name="disabled">account is disabled</param>
92 public static void SetUserInformation(string domainName, string userName, bool passwordExpired, bool passwordNeverExpires, bool disabled)
93 {
94 UserPrincipal user = GetUser(domainName, userName);
95
96 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
97 user.PasswordNeverExpires = passwordNeverExpires;
98 user.Enabled = !disabled;
99 if (passwordExpired)
100 {
101 user.ExpirePasswordNow();
102 }
103 else
104 {
105 // extend the expiration date to a month
106 user.AccountExpirationDate = DateTime.Now.Add(new TimeSpan(30, 0, 0, 0, 0));
107 }
108 user.Save();
109 }
110
111 /// <summary>
112 /// Adds the specified user to the specified local group
113 /// </summary>
114 /// <param name="userName">User to add</param>
115 /// <param name="groupName">Group to add too</param>
116 public static void AddUserToGroup(string userName, string groupName)
117 {
118 DirectoryEntry localMachine;
119 DirectoryEntry localGroup;
120
121 localMachine = new DirectoryEntry("WinNT://" + Environment.MachineName.ToString());
122 localGroup = localMachine.Children.Find(groupName, "group");
123 Assert.False(null == localGroup, String.Format("Group '{0}' was not found.", groupName));
124 DirectoryEntry user = FindActiveDirectoryUser(userName);
125 localGroup.Invoke("Add", new object[] { user.Path.ToString() });
126 }
127
128 /// <summary>
129 /// Find the specified user in AD
130 /// </summary>
131 /// <param name="UserName">user name to lookup</param>
132 /// <returns>DirectoryEntry of the user</returns>
133 private static DirectoryEntry FindActiveDirectoryUser(string UserName)
134 {
135 var mLocalMachine = new DirectoryEntry("WinNT://" + Environment.MachineName.ToString());
136 var mLocalEntries = mLocalMachine.Children;
137
138 var theUser = mLocalEntries.Find(UserName);
139 return theUser;
140 }
141
142 /// <summary>
143 /// Verifies the user information for a given user
144 /// </summary>
145 /// <param name="domainName">domain name for the user, empty for local users</param>
146 /// <param name="userName">the user name</param>
147 /// <param name="passwordExpired">user is required to change the password on first login</param>
148 /// <param name="passwordNeverExpires">password never expires</param>
149 /// <param name="disabled">account is disabled</param>
150 public static void VerifyUserInformation(string domainName, string userName, bool passwordExpired, bool passwordNeverExpires, bool disabled)
151 {
152 UserPrincipal user = GetUser(domainName, userName);
153
154 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
155
156 Assert.True(passwordNeverExpires == user.PasswordNeverExpires, String.Format("Password Never Expires for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, user.PasswordNeverExpires, passwordNeverExpires));
157 Assert.True(disabled != user.Enabled, String.Format("Disappled for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, !user.Enabled, disabled));
158
159 DateTime expirationDate = user.AccountExpirationDate.GetValueOrDefault();
160 bool accountExpired = expirationDate.ToLocalTime().CompareTo(DateTime.Now) <= 0;
161 Assert.True(passwordExpired == accountExpired, String.Format("Password Expired for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, accountExpired, passwordExpired));
162 }
163
164 /// <summary>
165 /// Verify that a givin user is member of a local group
166 /// </summary>
167 /// <param name="domainName">domain name for the user, empty for local users</param>
168 /// <param name="userName">the user name</param>
169 /// <param name="groupNames">list of groups to check for membership</param>
170 public static void VerifyUserIsMemberOf(string domainName, string userName, params string[] groupNames)
171 {
172 IsUserMemberOf(domainName, userName, true, groupNames);
173 }
174
175 /// <summary>
176 /// Verify that a givin user is NOT member of a local group
177 /// </summary>
178 /// <param name="domainName">domain name for the user, empty for local users</param>
179 /// <param name="userName">the user name</param>
180 /// <param name="groupNames">list of groups to check for membership</param>
181 public static void VerifyUserIsNotMemberOf(string domainName, string userName, params string[] groupNames)
182 {
183 IsUserMemberOf(domainName, userName, false, groupNames);
184 }
185
186 /// <summary>
187 ///
188 /// </summary>
189 /// <param name="SID">SID to search for</param>
190 /// <returns>AccountName</returns>
191 public static string GetLocalUserNameFromSID(string sidString)
192 {
193 SecurityIdentifier sid = new SecurityIdentifier(sidString);
194 NTAccount account = (NTAccount)sid.Translate(typeof(NTAccount));
195 return account.Value;
196 }
197
198 /// <summary>
199 /// Get the SID string for a given user name
200 /// </summary>
201 /// <param name="Domain"></param>
202 /// <param name="UserName"></param>
203 /// <returns>SID string</returns>
204 public static string GetSIDFromUserName(string Domain, string UserName)
205 {
206 string retVal = null;
207 string domain = Domain;
208 string name = UserName;
209
210 if (String.IsNullOrEmpty(domain))
211 {
212 domain = System.Environment.MachineName;
213 }
214
215 try
216 {
217 DirectoryEntry de = new DirectoryEntry("WinNT://" + domain + "/" + name);
218
219 long iBigVal = 5;
220 byte[] bigArr = BitConverter.GetBytes(iBigVal);
221 System.DirectoryServices.PropertyCollection coll = de.Properties;
222 object obVal = coll["objectSid"].Value;
223 if (null != obVal)
224 {
225 retVal = ConvertByteToSidString((byte[])obVal);
226 }
227 }
228 catch (Exception ex)
229 {
230 retVal = String.Empty;
231 Console.Write(ex.Message);
232 }
233
234 return retVal;
235 }
236
237 /// <summary>
238 /// converts a byte array containing a SID into a string
239 /// </summary>
240 /// <param name="sidBytes"></param>
241 /// <returns>SID string</returns>
242 private static string ConvertByteToSidString(byte[] sidBytes)
243 {
244 short sSubAuthorityCount;
245 StringBuilder strSid = new StringBuilder();
246 strSid.Append("S-");
247 try
248 {
249 // Add SID revision.
250 strSid.Append(sidBytes[0].ToString());
251
252 sSubAuthorityCount = Convert.ToInt16(sidBytes[1]);
253
254 // Next six bytes are SID authority value.
255 if (sidBytes[2] != 0 || sidBytes[3] != 0)
256 {
257 string strAuth = String.Format("0x{0:2x}{1:2x}{2:2x}{3:2x}{4:2x}{5:2x}",
258 (short)sidBytes[2],
259 (short)sidBytes[3],
260 (short)sidBytes[4],
261 (short)sidBytes[5],
262 (short)sidBytes[6],
263 (short)sidBytes[7]);
264 strSid.Append("-");
265 strSid.Append(strAuth);
266 }
267 else
268 {
269 long iVal = (int)(sidBytes[7]) +
270 (int)(sidBytes[6] << 8) +
271 (int)(sidBytes[5] << 16) +
272 (int)(sidBytes[4] << 24);
273 strSid.Append("-");
274 strSid.Append(iVal.ToString());
275 }
276
277 // Get sub authority count...
278 int idxAuth = 0;
279 for (int i = 0; i < sSubAuthorityCount; i++)
280 {
281 idxAuth = 8 + i * 4;
282 uint iSubAuth = BitConverter.ToUInt32(sidBytes, idxAuth);
283 strSid.Append("-");
284 strSid.Append(iSubAuth.ToString());
285 }
286 }
287 catch (Exception ex)
288 {
289 Console.WriteLine(ex.Message);
290 return "";
291 }
292 return strSid.ToString();
293 }
294
295 /// <summary>
296 /// Verify that a given user is member of a local group
297 /// </summary>
298 /// <param name="domainName">domain name for the user, empty for local users</param>
299 /// <param name="userName">the user name</param>
300 /// <param name="shouldBeMember">whether the user is expected to be a member of the groups or not</param>
301 /// <param name="groupNames">list of groups to check for membership</param>
302 private static void IsUserMemberOf(string domainName, string userName, bool shouldBeMember, params string[] groupNames)
303 {
304 UserPrincipal user = GetUser(domainName, userName);
305 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
306
307 bool missedAGroup = false;
308 string message = String.Empty;
309 foreach (string groupName in groupNames)
310 {
311 try
312 {
313 bool found = user.IsMemberOf(new PrincipalContext(ContextType.Machine), IdentityType.Name, groupName);
314 if (found != shouldBeMember)
315 {
316 missedAGroup = true;
317 message += String.Format("User '{0}/{1}' is {2} a member of local group '{3}'. \r\n", domainName, userName, found ? String.Empty : "NOT", groupName);
318 }
319 }
320 catch (System.DirectoryServices.AccountManagement.PrincipalOperationException)
321 {
322 missedAGroup = true;
323 message += String.Format("Local group '{0}' was not found. \r\n", groupName);
324 }
325
326 }
327 Assert.False(missedAGroup, message);
328 }
329
330 /// <summary>
331 /// Returns the UserPrincipal object for a given user
332 /// </summary>
333 /// <param name="domainName">Domain name to look under, if Empty the LocalMachine is assumned as the domain</param>
334 /// <param name="userName"></param>
335 /// <returns>UserPrinicipal Object for the user if found, or null other wise</returns>
336 private static UserPrincipal GetUser(string domainName, string userName)
337 {
338 if (String.IsNullOrEmpty(domainName))
339 {
340 return UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Machine), IdentityType.Name, userName);
341 }
342 else
343 {
344 return UserPrincipal.Current;//.FindByIdentity(new PrincipalContext(ContextType.Domain,domainName), IdentityType.Name, userName);
345 }
346 }
347 }
348}
diff --git a/src/test/burn/WixTestTools/WixTestTools.csproj b/src/test/burn/WixTestTools/WixTestTools.csproj
index 7b726560..49e14f25 100644
--- a/src/test/burn/WixTestTools/WixTestTools.csproj
+++ b/src/test/burn/WixTestTools/WixTestTools.csproj
@@ -9,6 +9,8 @@
9 9
10 <ItemGroup> 10 <ItemGroup>
11 <PackageReference Include="Microsoft.Win32.Registry" /> 11 <PackageReference Include="Microsoft.Win32.Registry" />
12 <PackageReference Include="System.DirectoryServices" />
13 <PackageReference Include="System.DirectoryServices.AccountManagement" />
12 <PackageReference Include="System.Security.Principal.Windows" /> 14 <PackageReference Include="System.Security.Principal.Windows" />
13 <PackageReference Include="WixBuildTools.TestSupport" /> 15 <PackageReference Include="WixBuildTools.TestSupport" />
14 <PackageReference Include="WixToolset.Data" /> 16 <PackageReference Include="WixToolset.Data" />
diff --git a/src/test/msi/Directory.Build.props b/src/test/msi/Directory.Build.props
new file mode 100644
index 00000000..9e2a776f
--- /dev/null
+++ b/src/test/msi/Directory.Build.props
@@ -0,0 +1,11 @@
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 <SegmentName>IntegrationMsi</SegmentName>
6 <SignOutput>false</SignOutput>
7 </PropertyGroup>
8
9 <Import Project="..\..\Directory.Build.props" />
10 <Import Project="Directory$(MSBuildProjectExtension).props" Condition=" Exists('Directory$(MSBuildProjectExtension).props') " />
11</Project>
diff --git a/src/test/msi/Directory.Build.targets b/src/test/msi/Directory.Build.targets
new file mode 100644
index 00000000..4e97b6ca
--- /dev/null
+++ b/src/test/msi/Directory.Build.targets
@@ -0,0 +1,6 @@
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 <Import Project="..\..\Directory.Build.targets" />
5 <Import Project="Directory$(MSBuildProjectExtension).targets" Condition=" Exists('Directory$(MSBuildProjectExtension).targets') " />
6</Project>
diff --git a/src/test/msi/Directory.wixproj.props b/src/test/msi/Directory.wixproj.props
new file mode 100644
index 00000000..f824ff2a
--- /dev/null
+++ b/src/test/msi/Directory.wixproj.props
@@ -0,0 +1,11 @@
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 <BaseIntermediateOutputPath>$(BaseOutputPath)obj\$(TestGroupName)\$(ProjectName)\</BaseIntermediateOutputPath>
6 <OutputPath>$(OutputPath)netcoreapp3.1\TestData\$(TestGroupName)\</OutputPath>
7 <DefaultCompressionLevel>None</DefaultCompressionLevel>
8 <CompilerAdditionalOptions>-wx</CompilerAdditionalOptions>
9 <SuppressValidation>true</SuppressValidation>
10 </PropertyGroup>
11</Project>
diff --git a/src/test/msi/Directory.wixproj.targets b/src/test/msi/Directory.wixproj.targets
new file mode 100644
index 00000000..7dddb141
--- /dev/null
+++ b/src/test/msi/Directory.wixproj.targets
@@ -0,0 +1,13 @@
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=" '$(CabPrefix)' != '' ">CabPrefix=$(CabPrefix);$(DefineConstants)</DefineConstants>
8 <DefineConstants Condition=" '$(ProductCode)' != '' ">ProductCode=$(ProductCode);$(DefineConstants)</DefineConstants>
9 <DefineConstants Condition=" '$(ProductComponentsRef)' == 'true' ">ProductComponents=1;$(DefineConstants)</DefineConstants>
10 <DefineConstants Condition=" '$(UpgradeCode)' != '' ">UpgradeCode=$(UpgradeCode);$(DefineConstants)</DefineConstants>
11 <DefineConstants Condition=" '$(Version)' != '' ">Version=$(Version);$(DefineConstants)</DefineConstants>
12 </PropertyGroup>
13</Project>
diff --git a/src/test/msi/MsiE2ETests.sln b/src/test/msi/MsiE2ETests.sln
new file mode 100644
index 00000000..c8ba93ec
--- /dev/null
+++ b/src/test/msi/MsiE2ETests.sln
@@ -0,0 +1,51 @@
1
2Microsoft Visual Studio Solution File, Format Version 12.00
3# Visual Studio Version 17
4VisualStudioVersion = 17.0.31919.166
5MinimumVisualStudioVersion = 10.0.40219.1
6Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixTestTools", "..\burn\WixTestTools\WixTestTools.csproj", "{3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}"
7EndProject
8Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.MsiE2E", "WixToolsetTest.MsiE2E\WixToolsetTest.MsiE2E.csproj", "{68E1A5F8-0F44-4B38-8876-C101A2A019F2}"
9EndProject
10Global
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 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Debug|x64.ActiveCfg = Debug|Any CPU
23 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Debug|x64.Build.0 = Debug|Any CPU
24 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Debug|x86.ActiveCfg = Debug|Any CPU
25 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Debug|x86.Build.0 = Debug|Any CPU
26 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Release|Any CPU.Build.0 = Release|Any CPU
28 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Release|x64.ActiveCfg = Release|Any CPU
29 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Release|x64.Build.0 = Release|Any CPU
30 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Release|x86.ActiveCfg = Release|Any CPU
31 {3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}.Release|x86.Build.0 = Release|Any CPU
32 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Debug|x64.ActiveCfg = Debug|Any CPU
35 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Debug|x64.Build.0 = Debug|Any CPU
36 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Debug|x86.ActiveCfg = Debug|Any CPU
37 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Debug|x86.Build.0 = Debug|Any CPU
38 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Release|Any CPU.Build.0 = Release|Any CPU
40 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Release|x64.ActiveCfg = Release|Any CPU
41 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Release|x64.Build.0 = Release|Any CPU
42 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Release|x86.ActiveCfg = Release|Any CPU
43 {68E1A5F8-0F44-4B38-8876-C101A2A019F2}.Release|x86.Build.0 = Release|Any CPU
44 EndGlobalSection
45 GlobalSection(SolutionProperties) = preSolution
46 HideSolutionNode = FALSE
47 EndGlobalSection
48 GlobalSection(ExtensibilityGlobals) = postSolution
49 SolutionGuid = {4C9978E3-8548-451E-8BF5-03AD1D0280C2}
50 EndGlobalSection
51EndGlobal
diff --git a/src/test/msi/README.md b/src/test/msi/README.md
new file mode 100644
index 00000000..2b4ff9a4
--- /dev/null
+++ b/src/test/msi/README.md
@@ -0,0 +1,29 @@
1# integration
2
3This layer is for building installers, and then executing xunit tests that run them and verify that they worked.
4
5## Running tests
6
7The main focus of these tests is to validate behavior in a real environment.
8Depending on who you talk to, these are integration or system-level or end-to-end (E2E) tests.
9They modify machine state so it's strongly recommended *not* to run these tests on your dev box.
10They should be run on a VM instead, where you can easily roll back.
11
121. Run build.cmd to build everything (the tests will not automatically run).
131. Copy the build\IntegrationMsi\Debug\netcoreapp3.1 folder to your VM.
141. Open an elevated command prompt and navigate to the netcoreapp3.1 folder.
151. Run the runtests.cmd file to run the tests.
16
17You can modify the runtests.cmd to run specific tests.
18For example, the following line runs only the specified test:
19
20> dotnet test --filter WixToolsetTest.BurnE2E.BasicFunctionalityTests.CanInstallAndUninstallSimpleBundle_x86_wixstdba WixToolsetTest.BurnE2E.dll
21
22The VM must have:
231. x64 .NET Core SDK of 3.1 or later (for the test runner)
24
25## Building with local changes
26
27The current build process will poison your NuGet package cache, so you may have to run the following command to clear it:
28
29> nuget locals all -clear \ No newline at end of file
diff --git a/src/test/msi/TestData/Templates/CustomActionFail.exe b/src/test/msi/TestData/Templates/CustomActionFail.exe
new file mode 100644
index 00000000..cceffe91
--- /dev/null
+++ b/src/test/msi/TestData/Templates/CustomActionFail.exe
Binary files differ
diff --git a/src/test/msi/TestData/Templates/Product.wxs b/src/test/msi/TestData/Templates/Product.wxs
new file mode 100644
index 00000000..c7604c1b
--- /dev/null
+++ b/src/test/msi/TestData/Templates/Product.wxs
@@ -0,0 +1,47 @@
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<?ifndef ProductCode?>
10<?define ProductCode = *?>
11<?endif?>
12
13<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
14 <Package Name="~$(var.TestGroupName) - $(var.PackageName)" Language="1033" Version="$(var.Version)" Manufacturer="WixToolset" UpgradeCode="$(var.UpgradeCode)" Compressed="yes" ProductCode="$(var.ProductCode)">
15 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
16 <?ifndef CabPrefix?>
17 <MediaTemplate EmbedCab="yes" />
18 <?else?>
19 <MediaTemplate CabinetTemplate="{0}$(var.CabPrefix).cab" />
20 <?endif?>
21
22 <Feature Id="Complete" Level="1">
23 <ComponentRef Id="FileComponent" />
24 <?ifdef var.ProductComponents?>
25 <ComponentGroupRef Id="ProductComponents" />
26 <?endif?>
27 </Feature>
28 </Package>
29
30 <Fragment>
31 <StandardDirectory Id="ProgramFiles6432Folder">
32 <Directory Id="WixDir" Name="~Test WiX">
33 <Directory Id="TestDir" Name="$(var.TestGroupName)">
34 <Directory Id="INSTALLFOLDER" Name="$(var.PackageName)" />
35 </Directory>
36 </Directory>
37 </StandardDirectory>
38 </Fragment>
39
40 <Fragment>
41 <Component Id="FileComponent" Guid="12345678-9ABC-DEF0-1234-567890000000" Directory="INSTALLFOLDER">
42 <File Id="CAFile" Name="CustomActionFail.exe" Source="$(sys.SOURCEFILEDIR)\CustomActionFail.exe" />
43 </Component>
44
45 <CustomAction Id="CaFail" FileRef="CAFile" Execute="immediate" Return="check" ExeCommand="" />
46 </Fragment>
47</Wix>
diff --git a/src/test/msi/TestData/TestData.proj b/src/test/msi/TestData/TestData.proj
new file mode 100644
index 00000000..9e130987
--- /dev/null
+++ b/src/test/msi/TestData/TestData.proj
@@ -0,0 +1,16 @@
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 ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <ItemGroup>
6 <TestDataProject Include="**\*.wixproj" />
7 </ItemGroup>
8
9 <Target Name="Build">
10 <MSBuild Projects="%(TestDataProject.Identity)" />
11 </Target>
12
13 <Target Name="Restore">
14 <MSBuild Projects="%(TestDataProject.Identity)" Targets="Restore" />
15 </Target>
16</Project>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj
new file mode 100644
index 00000000..fbc6f292
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj
@@ -0,0 +1,13 @@
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>{1A1795A6-87C0-4A9A-ABD5-DF9BED697037}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs
new file mode 100644
index 00000000..a7bec54e
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs
@@ -0,0 +1,35 @@
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<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9
10 <Property Id="TEMPDOMAIN" Secure="yes" />
11 <Property Id="TEMPUSERNAME" Secure="yes" />
12 </Fragment>
13
14 <Fragment>
15 <util:Group Id="ADMIN" Name="Administrators" />
16 <util:Group Id="POWER_USER" Name="Power Users" />
17
18 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
19 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
20
21 <util:User Id="TEST_USER1" Name="testName1" Password="test123!@#" PasswordExpired="yes">
22 <util:GroupRef Id="ADMIN" />
23 <util:GroupRef Id="POWER_USER" />
24 </util:User>
25
26 <util:User Id="TEST_USER2" Name="testName2" Password="test123!@#" Disabled="yes" RemoveOnUninstall="no" PasswordNeverExpires="yes" UpdateIfExists="yes">
27 <util:GroupRef Id="POWER_USER" />
28 </util:User>
29
30 <util:User Id="TEST_USER3" Name="[TEMPUSERNAME]" Domain="[TEMPDOMAIN]" CreateUser="no">
31 <util:GroupRef Id="POWER_USER" />
32 </util:User>
33 </Component>
34 </Fragment>
35</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/ProductFail.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/ProductFail.wixproj
new file mode 100644
index 00000000..e2fe3aa8
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/ProductFail.wixproj
@@ -0,0 +1,13 @@
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>{91D27DAC-04C1-4160-914E-343676D36CAA}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs
new file mode 100644
index 00000000..c5da862c
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs
@@ -0,0 +1,39 @@
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<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9
10 <Property Id="TEMPDOMAIN" Secure="yes" />
11 <Property Id="TEMPUSERNAME" Secure="yes" />
12
13 <InstallExecuteSequence>
14 <Custom Action="CaFail" After="Wix4ConfigureUsers_X86" />
15 </InstallExecuteSequence>
16 </Fragment>
17
18 <Fragment>
19 <util:Group Id="ADMIN" Name="Administrators" />
20 <util:Group Id="POWER_USER" Name="Power Users" />
21
22 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
23 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
24
25 <util:User Id="TEST_USER1" Name="testName1" Password="test123!@#" PasswordExpired="yes">
26 <util:GroupRef Id="ADMIN" />
27 <util:GroupRef Id="POWER_USER" />
28 </util:User>
29
30 <util:User Id="TEST_USER2" Name="testName2" Password="test123!@#" Disabled="yes" RemoveOnUninstall="no" PasswordNeverExpires="yes" UpdateIfExists="yes">
31 <util:GroupRef Id="POWER_USER" />
32 </util:User>
33
34 <util:User Id="TEST_USER3" Name="[TEMPUSERNAME]" Domain="[TEMPDOMAIN]" CreateUser="no">
35 <util:GroupRef Id="POWER_USER" />
36 </util:User>
37 </Component>
38 </Fragment>
39</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/FailIfExists.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/FailIfExists.wxs
new file mode 100644
index 00000000..0da4f2b9
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/FailIfExists.wxs
@@ -0,0 +1,24 @@
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<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9 </Fragment>
10
11 <Fragment>
12 <util:Group Id="ADMIN" Name="Administrators" />
13 <util:Group Id="POWER_USER" Name="Power Users" />
14
15 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
16 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
17
18 <util:User Id="TEST_USER4" Name="existinguser" Password="test123!@#" LogonAsService="yes" FailIfExists="yes" RemoveOnUninstall="yes">
19 <util:GroupRef Id="ADMIN" />
20 <util:GroupRef Id="POWER_USER" />
21 </util:User>
22 </Component>
23 </Fragment>
24</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/ProductFailIfExists.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/ProductFailIfExists.wixproj
new file mode 100644
index 00000000..9e1a836f
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/ProductFailIfExists.wixproj
@@ -0,0 +1,13 @@
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>{BC803822-929E-47DA-AB3A-3A62EEEA2BFB}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/NonVitalUserGroup.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/NonVitalUserGroup.wxs
new file mode 100644
index 00000000..461648ee
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/NonVitalUserGroup.wxs
@@ -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
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9 </Fragment>
10
11 <Fragment>
12 <util:Group Id="ShouldNotExist" Name="Should Not Exist" />
13
14 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
15 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
16
17 <util:User Id="CurrentUser" Name="[LogonUser]" Domain="[%USERDOMAIN]" RemoveOnUninstall="no" Vital="no">
18 <util:GroupRef Id="ShouldNotExist" />
19 </util:User>
20 </Component>
21 </Fragment>
22</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/ProductNonVitalUserGroup.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/ProductNonVitalUserGroup.wixproj
new file mode 100644
index 00000000..8734224d
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/ProductNonVitalUserGroup.wixproj
@@ -0,0 +1,13 @@
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>{455C8D4F-6D59-405C-AD51-0ACC7FB91A26}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/ProductRestrictedDomain.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/ProductRestrictedDomain.wixproj
new file mode 100644
index 00000000..e4a01a3a
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/ProductRestrictedDomain.wixproj
@@ -0,0 +1,13 @@
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>{50CF526C-A862-4327-9EA3-C96AAB6FABCE}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/RestrictedDomain.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/RestrictedDomain.wxs
new file mode 100644
index 00000000..f200a06a
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/RestrictedDomain.wxs
@@ -0,0 +1,20 @@
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<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9
10 <Property Id="TEMPDOMAIN" Secure="yes" />
11 </Fragment>
12
13 <Fragment>
14 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
15 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
16
17 <util:User Id="TEST_USER_test" Name="testName1" Domain="[TEMPDOMAIN]" Password="test123!@#" PasswordExpired="no" />
18 </Component>
19 </Fragment>
20</Wix>
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/MsiE2EFixture.cs b/src/test/msi/WixToolsetTest.MsiE2E/MsiE2EFixture.cs
new file mode 100644
index 00000000..a7646568
--- /dev/null
+++ b/src/test/msi/WixToolsetTest.MsiE2E/MsiE2EFixture.cs
@@ -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
3namespace WixToolsetTest.MsiE2E
4{
5 using System;
6 using System.Security.Principal;
7
8 public class MsiE2EFixture
9 {
10 const string RequiredEnvironmentVariableName = "RuntimeTestsEnabled";
11
12 public MsiE2EFixture()
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}
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/MsiE2ETests.cs b/src/test/msi/WixToolsetTest.MsiE2E/MsiE2ETests.cs
new file mode 100644
index 00000000..22f2173b
--- /dev/null
+++ b/src/test/msi/WixToolsetTest.MsiE2E/MsiE2ETests.cs
@@ -0,0 +1,44 @@
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
3namespace WixToolsetTest.MsiE2E
4{
5 using System;
6 using System.Collections.Generic;
7 using WixTestTools;
8 using Xunit;
9 using Xunit.Abstractions;
10
11 [Collection("MsiE2E")]
12 public abstract class MsiE2ETests : WixTestBase, IDisposable
13 {
14 protected MsiE2ETests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
15 {
16 }
17
18 private Stack<IDisposable> Installers { get; } = new Stack<IDisposable>();
19
20 protected PackageInstaller CreatePackageInstaller(string filename)
21 {
22 var installer = new PackageInstaller(this.TestContext, filename);
23 this.Installers.Push(installer);
24 return installer;
25 }
26
27 public void Dispose()
28 {
29 while (this.Installers.TryPop(out var installer))
30 {
31 try
32 {
33 installer.Dispose();
34 }
35 catch { }
36 }
37 }
38 }
39
40 [CollectionDefinition("MsiE2E", DisableParallelization = true)]
41 public class MsiE2ECollectionDefinition : ICollectionFixture<MsiE2EFixture>
42 {
43 }
44}
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs
new file mode 100644
index 00000000..21491858
--- /dev/null
+++ b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs
@@ -0,0 +1,162 @@
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
3namespace WixToolsetTest.MsiE2E
4{
5 using System;
6 using WixTestTools;
7 using Xunit;
8 using Xunit.Abstractions;
9
10 public class UtilExtensionUserTests : MsiE2ETests
11 {
12 public UtilExtensionUserTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { }
13
14 const string TempDomain = "USERDOMAIN";
15 const string TempUsername = "USERNAME";
16
17 // Verify that the users specified in the authoring are created as expected.
18 [Fact]
19 public void CanInstallAndUninstallUsers()
20 {
21 var arguments = new string[]
22 {
23 $"TEMPDOMAIN={Environment.GetEnvironmentVariable(TempDomain)}",
24 $"TEMPUSERNAME={Environment.GetEnvironmentVariable(TempUsername)}",
25 };
26 var productA = this.CreatePackageInstaller("ProductA");
27
28 productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
29
30 // Validate New User Information.
31 UserVerifier.VerifyUserInformation(String.Empty, "testName1", true, false, false);
32 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName1", "Administrators", "Power Users");
33
34 UserVerifier.VerifyUserInformation(String.Empty, "testName2", true, true, true);
35 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName2", "Power Users");
36
37 UserVerifier.VerifyUserIsMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
38
39 productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
40
41 // Verify Users marked as RemoveOnUninstall were removed.
42 Assert.False(UserVerifier.UserExists(String.Empty, "testName1"), String.Format("User '{0}' was not removed on Uninstall", "testName1"));
43 Assert.True(UserVerifier.UserExists(String.Empty, "testName2"), String.Format("User '{0}' was removed on Uninstall", "testName2"));
44
45 // clean up
46 UserVerifier.DeleteLocalUser("testName2");
47
48 UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
49 }
50
51 // Verify the rollback action reverts all Users changes.
52 [Fact]
53 public void CanRollbackUsers()
54 {
55 var arguments = new string[]
56 {
57 $"TEMPDOMAIN={Environment.GetEnvironmentVariable(TempDomain)}",
58 $"TEMPUSERNAME={Environment.GetEnvironmentVariable(TempUsername)}",
59 };
60 var productFail = this.CreatePackageInstaller("ProductFail");
61
62 // make sure the user accounts are deleted before we start
63 UserVerifier.DeleteLocalUser("testName1");
64 UserVerifier.DeleteLocalUser("testName2");
65 UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
66
67 productFail.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, arguments);
68
69 // Verify Users marked as RemoveOnUninstall were removed.
70 Assert.False(UserVerifier.UserExists(String.Empty, "testName1"), String.Format("User '{0}' was not removed on Rollback", "testName1"));
71 Assert.False(UserVerifier.UserExists(String.Empty, "testName2"), String.Format("User '{0}' was not removed on Rollback", "testName2"));
72
73 UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
74 }
75
76 // Verify that the users specified in the authoring are created as expected on repair.
77 [Fact(Skip = "Test demonstrates failure")]
78 public void CanRepairUsers()
79 {
80 var arguments = new string[]
81 {
82 $"TEMPDOMAIN={Environment.GetEnvironmentVariable(TempDomain)}",
83 $"TEMPUSERNAME={Environment.GetEnvironmentVariable(TempUsername)}",
84 };
85 var productA = this.CreatePackageInstaller("ProductA");
86
87 productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
88
89 UserVerifier.DeleteLocalUser("testName1");
90 UserVerifier.SetUserInformation(String.Empty, "testName2", true, false, false);
91
92 productA.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
93
94 // Validate New User Information.
95 UserVerifier.VerifyUserInformation(String.Empty, "testName1", true, false, false);
96 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName1", "Administrators", "Power Users");
97
98 UserVerifier.VerifyUserInformation(String.Empty, "testName2", true, true, true);
99 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName2", "Power Users");
100
101 UserVerifier.VerifyUserIsMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
102
103 productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
104
105 // Verify Users marked as RemoveOnUninstall were removed.
106 Assert.False(UserVerifier.UserExists(String.Empty, "testName1"), String.Format("User '{0}' was not removed on Uninstall", "testName1"));
107 Assert.True(UserVerifier.UserExists(String.Empty, "testName2"), String.Format("User '{0}' was removed on Uninstall", "testName2"));
108
109 // clean up
110 UserVerifier.DeleteLocalUser("testName2");
111
112 UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
113 }
114
115 // Verify that Installation fails if FailIfExisits is set.
116 [Fact]
117 public void FailsIfUserExists()
118 {
119 var productFailIfExists = this.CreatePackageInstaller("ProductFailIfExists");
120
121 // Create 'existinguser'
122 UserVerifier.CreateLocalUser("existinguser", "test123!@#");
123
124 try
125 {
126 productFailIfExists.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE);
127
128 // Verify User still exists.
129 bool userExists = UserVerifier.UserExists(String.Empty, "existinguser");
130
131 Assert.True(userExists, String.Format("User '{0}' was removed on Rollback", "existinguser"));
132 }
133 finally
134 {
135 // clean up
136 UserVerifier.DeleteLocalUser("existinguser");
137 }
138
139 }
140
141 // Verify that a user cannot be created on a domain on which you dont have create user permission.
142 [Fact]
143 public void FailsIfRestrictedDomain()
144 {
145 var productRestrictedDomain = this.CreatePackageInstaller("ProductRestrictedDomain");
146
147 string logFile = productRestrictedDomain.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "TEMPDOMAIN=DOESNOTEXIST");
148
149 // Verify expected error message in the log file
150 Assert.True(LogVerifier.MessageInLogFile(logFile, "ConfigureUsers: Failed to check existence of domain: DOESNOTEXIST, user: testName1 (error code 0x800706ba) - continuing"));
151 }
152
153 // Verify that adding a user to a non-existent group does not fail the install when non-vital.
154 [Fact]
155 public void IgnoresMissingGroupWhenNonVital()
156 {
157 var productNonVitalGroup = this.CreatePackageInstaller("ProductNonVitalUserGroup");
158
159 productNonVitalGroup.InstallProduct();
160 }
161 }
162}
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj b/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj
new file mode 100644
index 00000000..ccf98042
--- /dev/null
+++ b/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj
@@ -0,0 +1,31 @@
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 <PlatformTarget>x64</PlatformTarget>
8 <RollForward>Major</RollForward>
9 </PropertyGroup>
10
11 <ItemGroup>
12 <Content Include="runtests.cmd" CopyToOutputDirectory="PreserveNewest" />
13 </ItemGroup>
14
15 <ItemGroup>
16 <ProjectReference Include="..\..\burn\WixTestTools\WixTestTools.csproj" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <PackageReference Include="Microsoft.Win32.Registry" />
21 <PackageReference Include="System.Security.Principal.Windows" />
22 <PackageReference Include="WixBuildTools.TestSupport" />
23 <PackageReference Include="WixToolset.Data" />
24 </ItemGroup>
25
26 <ItemGroup>
27 <PackageReference Include="Microsoft.NET.Test.Sdk" />
28 <PackageReference Include="xunit" />
29 <PackageReference Include="xunit.runner.visualstudio" PrivateAssets="All" />
30 </ItemGroup>
31</Project>
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd b/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd
new file mode 100644
index 00000000..f609ebe4
--- /dev/null
+++ b/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd
@@ -0,0 +1,2 @@
1SET RuntimeTestsEnabled=true
2dotnet test WixToolsetTest.MsiE2E.dll -v normal \ No newline at end of file
diff --git a/src/test/msi/test_msi.cmd b/src/test/msi/test_msi.cmd
new file mode 100644
index 00000000..bb9d0162
--- /dev/null
+++ b/src/test/msi/test_msi.cmd
@@ -0,0 +1,23 @@
1@setlocal
2@pushd %~dp0
3
4@set _RESULT=0
5@set _C=Debug
6@set _L=%~dp0..\..\..\build\logs
7:parse_args
8@if /i "%1"=="release" set _C=Release
9@if /i "%1"=="test" set RuntimeTestsEnabled=true
10@if not "%1"=="" shift & goto parse_args
11
12@echo Msi integration tests %_C%
13
14msbuild -t:Build -Restore -p:Configuration=%_C% -warnaserror -bl:%_L%\test_msi_build.binlog || exit /b
15msbuild -t:Build -Restore TestData\TestData.proj -p:Configuration=%_C% -m -bl:%_L%\test_msi_data_build.binlog || exit /b
16
17@if not "%RuntimeTestsEnabled%"=="true" goto :LExit
18
19dotnet test -c %_C% --no-build WixToolsetTest.MsiE2E -l "trx;LogFileName=%_L%\TestResults\WixToolsetTest.MsiE2E.trx" || exit /b
20
21:LExit
22@popd
23@endlocal
diff --git a/src/test/test.cmd b/src/test/test.cmd
index 4c80ba7d..0b29f240 100644
--- a/src/test/test.cmd
+++ b/src/test/test.cmd
@@ -11,6 +11,7 @@
11@if not "%RuntimeTestsEnabled%"=="true" echo Build integration tests %_C% 11@if not "%RuntimeTestsEnabled%"=="true" echo Build integration tests %_C%
12@if "%RuntimeTestsEnabled%"=="true" set _T=test&echo Run integration tests %_C% 12@if "%RuntimeTestsEnabled%"=="true" set _T=test&echo Run integration tests %_C%
13 13
14@call msi\test_msi.cmd %_C% %_T% || exit /b
14@call burn\test_burn.cmd %_C% %_T% || exit /b 15@call burn\test_burn.cmd %_C% %_T% || exit /b
15 16
16msbuild -t:Restore dtf\DtfE2ETests.sln -p:Configuration=%_C% -nologo -m -warnaserror -bl:%_L%\dtfe2etests.binlog || exit /b 17msbuild -t:Restore dtf\DtfE2ETests.sln -p:Configuration=%_C% -nologo -m -warnaserror -bl:%_L%\dtfe2etests.binlog || exit /b
diff --git a/src/testresultfilelist.txt b/src/testresultfilelist.txt
index 472a8d1d..60fc168b 100644
--- a/src/testresultfilelist.txt
+++ b/src/testresultfilelist.txt
@@ -17,5 +17,6 @@ build/logs/TestResults/WixToolsetTest.Dnc.HostGenerator.trx
17build/logs/TestResults/WixToolsetTest.Heat.trx 17build/logs/TestResults/WixToolsetTest.Heat.trx
18build/logs/TestResults/WixToolsetTest.ManagedHost.trx 18build/logs/TestResults/WixToolsetTest.ManagedHost.trx
19build/logs/TestResults/WixToolsetTest.Mba.Core.trx 19build/logs/TestResults/WixToolsetTest.Mba.Core.trx
20build/logs/TestResults/WixToolsetTest.MsiE2E.trx
20build/logs/TestResults/WixToolsetTest.Sdk.trx 21build/logs/TestResults/WixToolsetTest.Sdk.trx
21build/logs/TestResults/WixToolsetTest.WixE2ETests.trx \ No newline at end of file 22build/logs/TestResults/WixToolsetTest.WixE2ETests.trx \ No newline at end of file