@joebigelow / wix / commits / 5b04bce6

Port UtilExtension.UserTests from wix3.

Sean Hall committed Apr 28, 2022 at 21:02 UTC 5b04bce6567855325810bc4e6bcd2f6e05b329c7
30 files changed +1032
src/internal/SetBuildNumber/Directory.Packages.props.pp
+1
@@ -38,6 +38,7 @@
38 <ItemGroup>
39 <PackageVersion Include="System.Diagnostics.PerformanceCounter" Version="4.7.0" />
40 <PackageVersion Include="System.DirectoryServices" Version="4.7.0" />
41 + <PackageVersion Include="System.DirectoryServices.AccountManagement" Version="4.7.0" />
42 <PackageVersion Include="System.IO.Compression" Version="4.3.0" />
43 <!-- Warning: The version for System.IO.FileSystem.AccessControl must be kept in sync with WixToolset.Core.Native.nuspec -->
44 <PackageVersion Include="System.IO.FileSystem.AccessControl" Version="4.6.0" />
src/test/burn/WixTestTools/UserVerifier.cs new
+348
@@ -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 +
3 +namespace 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 +}
src/test/burn/WixTestTools/WixTestTools.csproj
+2
@@ -9,6 +9,8 @@
9
10 <ItemGroup>
11 <PackageReference Include="Microsoft.Win32.Registry" />
12 + <PackageReference Include="System.DirectoryServices" />
13 + <PackageReference Include="System.DirectoryServices.AccountManagement" />
14 <PackageReference Include="System.Security.Principal.Windows" />
15 <PackageReference Include="WixBuildTools.TestSupport" />
16 <PackageReference Include="WixToolset.Data" />
src/test/msi/Directory.Build.props new
+11
@@ -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>
src/test/msi/Directory.Build.targets new
+6
@@ -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>
src/test/msi/Directory.wixproj.props new
+11
@@ -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>
src/test/msi/Directory.wixproj.targets new
+13
@@ -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>
src/test/msi/MsiE2ETests.sln new
+51
@@ -0,0 +1,51 @@
1 +
2 +Microsoft Visual Studio Solution File, Format Version 12.00
3 +# Visual Studio Version 17
4 +VisualStudioVersion = 17.0.31919.166
5 +MinimumVisualStudioVersion = 10.0.40219.1
6 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixTestTools", "..\burn\WixTestTools\WixTestTools.csproj", "{3D3B02F3-79B6-4BD5-AD49-2889DA3849A7}"
7 +EndProject
8 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.MsiE2E", "WixToolsetTest.MsiE2E\WixToolsetTest.MsiE2E.csproj", "{68E1A5F8-0F44-4B38-8876-C101A2A019F2}"
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 + {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
51 +EndGlobal
src/test/msi/README.md new
+29
@@ -0,0 +1,29 @@
1 +# integration
2 +
3 +This layer is for building installers, and then executing xunit tests that run them and verify that they worked.
4 +
5 +## Running tests
6 +
7 +The main focus of these tests is to validate behavior in a real environment.
8 +Depending on who you talk to, these are integration or system-level or end-to-end (E2E) tests.
9 +They modify machine state so it's strongly recommended *not* to run these tests on your dev box.
10 +They should be run on a VM instead, where you can easily roll back.
11 +
12 +1. Run build.cmd to build everything (the tests will not automatically run).
13 +1. Copy the build\IntegrationMsi\Debug\netcoreapp3.1 folder to your VM.
14 +1. Open an elevated command prompt and navigate to the netcoreapp3.1 folder.
15 +1. Run the runtests.cmd file to run the tests.
16 +
17 +You can modify the runtests.cmd to run specific tests.
18 +For example, the following line runs only the specified test:
19 +
20 +> dotnet test --filter WixToolsetTest.BurnE2E.BasicFunctionalityTests.CanInstallAndUninstallSimpleBundle_x86_wixstdba WixToolsetTest.BurnE2E.dll
21 +
22 +The VM must have:
23 +1. x64 .NET Core SDK of 3.1 or later (for the test runner)
24 +
25 +## Building with local changes
26 +
27 +The 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
src/test/msi/TestData/Templates/CustomActionFail.exe
Binary files /dev/null and b/src/test/msi/TestData/Templates/CustomActionFail.exe differ
src/test/msi/TestData/Templates/Product.wxs new
+47
@@ -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>
src/test/msi/TestData/TestData.proj new
+16
@@ -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>
src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj new
+13
@@ -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
src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs new
+35
@@ -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>
src/test/msi/TestData/UtilExtensionUserTests/ProductFail/ProductFail.wixproj new
+13
@@ -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
src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs new
+39
@@ -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>
src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/FailIfExists.wxs new
+24
@@ -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>
src/test/msi/TestData/UtilExtensionUserTests/ProductFailIfExists/ProductFailIfExists.wixproj new
+13
@@ -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
src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/NonVitalUserGroup.wxs 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 +
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>
src/test/msi/TestData/UtilExtensionUserTests/ProductNonVitalUserGroup/ProductNonVitalUserGroup.wixproj new
+13
@@ -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
src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/ProductRestrictedDomain.wixproj new
+13
@@ -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
src/test/msi/TestData/UtilExtensionUserTests/ProductRestrictedDomain/RestrictedDomain.wxs new
+20
@@ -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>
src/test/msi/WixToolsetTest.MsiE2E/MsiE2EFixture.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.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 +}
src/test/msi/WixToolsetTest.MsiE2E/MsiE2ETests.cs new
+44
@@ -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 +
3 +namespace 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 +}
src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs new
+162
@@ -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 +
3 +namespace 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 +}
src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj new
+31
@@ -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>
src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd new
+2
@@ -0,0 +1,2 @@
1 +SET RuntimeTestsEnabled=true
2 +dotnet test WixToolsetTest.MsiE2E.dll -v normal
\ No newline at end of file
src/test/msi/test_msi.cmd new
+23
@@ -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 +
14 +msbuild -t:Build -Restore -p:Configuration=%_C% -warnaserror -bl:%_L%\test_msi_build.binlog || exit /b
15 +msbuild -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 +
19 +dotnet test -c %_C% --no-build WixToolsetTest.MsiE2E -l "trx;LogFileName=%_L%\TestResults\WixToolsetTest.MsiE2E.trx" || exit /b
20 +
21 +:LExit
22 +@popd
23 +@endlocal
src/test/test.cmd
+1
@@ -11,6 +11,7 @@
11 @if not "%RuntimeTestsEnabled%"=="true" echo Build integration tests %_C%
12 @if "%RuntimeTestsEnabled%"=="true" set _T=test&echo Run integration tests %_C%
13
14 +@call msi\test_msi.cmd %_C% %_T% || exit /b
15 @call burn\test_burn.cmd %_C% %_T% || exit /b
16
17 msbuild -t:Restore dtf\DtfE2ETests.sln -p:Configuration=%_C% -nologo -m -warnaserror -bl:%_L%\dtfe2etests.binlog || exit /b
src/testresultfilelist.txt
+1
@@ -17,5 +17,6 @@ build/logs/TestResults/WixToolsetTest.Dnc.HostGenerator.trx
17 build/logs/TestResults/WixToolsetTest.Heat.trx
18 build/logs/TestResults/WixToolsetTest.ManagedHost.trx
19 build/logs/TestResults/WixToolsetTest.Mba.Core.trx
20 +build/logs/TestResults/WixToolsetTest.MsiE2E.trx
21 build/logs/TestResults/WixToolsetTest.Sdk.trx
22 build/logs/TestResults/WixToolsetTest.WixE2ETests.trx
\ No newline at end of file