From c2ff0ccf46dd2bb07769d39e27a636e7b928a0ab Mon Sep 17 00:00:00 2001
From: Bevan Weiss <bevan.weiss@gmail.com>
Date: Sat, 13 Jul 2024 15:54:41 +1000
Subject: [PATCH 1/2] Tidy up some display around Domain users. Displayed
 appearance should be {domain}\{user} (as per Windows displays),  not
 {domain}/{user}

Signed-off-by: Bevan Weiss <bevan.weiss@gmail.com>
---
 src/test/burn/WixTestTools/UserVerifier.cs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/test/burn/WixTestTools/UserVerifier.cs b/src/test/burn/WixTestTools/UserVerifier.cs
index d192a8b8f..3680d172d 100644
--- a/src/test/burn/WixTestTools/UserVerifier.cs
+++ b/src/test/burn/WixTestTools/UserVerifier.cs
@@ -171,12 +171,12 @@ public static void VerifyUserInformation(string domainName, string userName, boo
 
             Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
 
-            Assert.True(passwordNeverExpires == user.PasswordNeverExpires, String.Format("Password Never Expires for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, user.PasswordNeverExpires, passwordNeverExpires));
-            Assert.True(disabled != user.Enabled, String.Format("Disappled for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, !user.Enabled, disabled));
+            Assert.True(passwordNeverExpires == user.PasswordNeverExpires, String.Format("Password Never Expires for user '{0}\\{1}' is: '{2}', expected: '{3}'.", domainName, userName, user.PasswordNeverExpires, passwordNeverExpires));
+            Assert.True(disabled != user.Enabled, String.Format("Account disabled for user '{0}\\{1}' is: '{2}', expected: '{3}'.", domainName, userName, !user.Enabled, disabled));
 
             DateTime expirationDate = user.AccountExpirationDate.GetValueOrDefault();
             bool accountExpired = expirationDate.ToLocalTime().CompareTo(DateTime.Now) <= 0;
-            Assert.True(passwordExpired == accountExpired, String.Format("Password Expired for user '{0}/{1}' is: '{2}', expected: '{3}'.", domainName, userName, accountExpired, passwordExpired));
+            Assert.True(passwordExpired == accountExpired, String.Format("Password Expired for user '{0}\\{1}' is: '{2}', expected: '{3}'.", domainName, userName, accountExpired, passwordExpired));
         }
 
         /// <summary>
@@ -349,7 +349,7 @@ private static void IsUserMemberOf(string domainName, string userName, bool shou
                     if (found != shouldBeMember)
                     {
                         missedAGroup = true;
-                        message += String.Format("User '{0}/{1}' is {2} a member of local group '{3}'. \r\n", domainName, userName, found ? String.Empty : "NOT", groupName);
+                        message += String.Format("User '{0}\\{1}' is {2} a member of local group '{3}'. \r\n", domainName, userName, found ? String.Empty : "NOT", groupName);
                     }
                 }
                 catch (System.DirectoryServices.AccountManagement.PrincipalOperationException)

From 54715f1cecf12276445ddc8bdb41ac631927456a Mon Sep 17 00:00:00 2001
From: Bevan Weiss <bevan.weiss@gmail.com>
Date: Sat, 13 Jul 2024 15:52:51 +1000
Subject: [PATCH 2/2] Add conditional MsiE2E Tests for Domain functionality.
 This test currently fails under a Domain workstation.

Signed-off-by: Bevan Weiss <bevan.weiss@gmail.com>
---
 .../burn/WixTestTools/RuntimeFactAttribute.cs | 32 ++++++++++++++++++
 .../ProductDomain/ProductDomain.wixproj       | 13 ++++++++
 .../ProductDomain/product.wxs                 | 33 +++++++++++++++++++
 .../UtilExtensionUserTests.cs                 | 23 +++++++++++--
 .../msi/WixToolsetTest.MsiE2E/runtests.cmd    |  1 +
 5 files changed, 100 insertions(+), 2 deletions(-)
 create mode 100644 src/test/msi/TestData/UtilExtensionUserTests/ProductDomain/ProductDomain.wixproj
 create mode 100644 src/test/msi/TestData/UtilExtensionUserTests/ProductDomain/product.wxs

diff --git a/src/test/burn/WixTestTools/RuntimeFactAttribute.cs b/src/test/burn/WixTestTools/RuntimeFactAttribute.cs
index f73c87a29..02cec9e44 100644
--- a/src/test/burn/WixTestTools/RuntimeFactAttribute.cs
+++ b/src/test/burn/WixTestTools/RuntimeFactAttribute.cs
@@ -3,16 +3,21 @@
 namespace WixTestTools
 {
     using System;
+    using System.DirectoryServices.ActiveDirectory;
     using System.Security.Principal;
     using WixInternal.TestSupport.XunitExtensions;
 
     public class RuntimeFactAttribute : SkippableFactAttribute
     {
         const string RequiredEnvironmentVariableName = "RuntimeTestsEnabled";
+        const string RequiredDomainEnvironmentVariableName = "RuntimeDomainTestsEnabled";
 
         public static bool RuntimeTestsEnabled { get; }
         public static bool RunningAsAdministrator { get; }
 
+        public static bool RuntimeDomainTestsEnabled { get; }
+        public static bool RunningInDomain { get; }
+
         static RuntimeFactAttribute()
         {
             using var identity = WindowsIdentity.GetCurrent();
@@ -21,6 +26,33 @@ static RuntimeFactAttribute()
 
             var testsEnabledString = Environment.GetEnvironmentVariable(RequiredEnvironmentVariableName);
             RuntimeTestsEnabled = Boolean.TryParse(testsEnabledString, out var testsEnabled) && testsEnabled;
+
+            RunningInDomain = false;
+            try
+            {
+                RunningInDomain = !String.IsNullOrEmpty(System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain().Name);
+            }
+            catch (ActiveDirectoryObjectNotFoundException) { }
+
+            var domainTestsEnabledString = Environment.GetEnvironmentVariable(RequiredDomainEnvironmentVariableName);
+            RuntimeDomainTestsEnabled = Boolean.TryParse(domainTestsEnabledString, out var domainTestsEnabled) && domainTestsEnabled;
+        }
+
+        private bool _domainRequired;
+        public bool DomainRequired
+        {
+            get
+            {
+                return _domainRequired;
+            }
+            set
+            {
+                _domainRequired = value;
+                if (_domainRequired && String.IsNullOrEmpty(this.Skip) && (!RunningInDomain || !RuntimeDomainTestsEnabled))
+                {
+                    this.Skip = $"These tests require the test host to be running as a domain member ({(RunningInDomain ? "passed" : "failed")}). These tests affect both MACHINE AND DOMAIN state. To accept the consequences, set the {RequiredDomainEnvironmentVariableName} environment variable to true ({(RuntimeDomainTestsEnabled ? "passed" : "failed")}).";
+                }
+            }
         }
 
         public RuntimeFactAttribute()
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductDomain/ProductDomain.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductDomain/ProductDomain.wixproj
new file mode 100644
index 000000000..1477407a6
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductDomain/ProductDomain.wixproj
@@ -0,0 +1,13 @@
+<!-- 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. -->
+<Project Sdk="WixToolset.Sdk">
+  <PropertyGroup>
+    <UpgradeCode>{08806ED8-3CE7-4BC3-A319-3ACCE3AAE7DC}</UpgradeCode>
+    <ProductComponentsRef>true</ProductComponentsRef>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
+  </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="WixToolset.Util.wixext" />
+  </ItemGroup>
+</Project>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductDomain/product.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductDomain/product.wxs
new file mode 100644
index 000000000..93df23518
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductDomain/product.wxs
@@ -0,0 +1,33 @@
+<!-- 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. -->
+
+
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
+    <Fragment>
+        <ComponentGroup Id="ProductComponents">
+            <ComponentRef Id="Component1" />
+        </ComponentGroup>
+
+        <Property Id="TEMPDOMAIN" Secure="yes" />
+        <Property Id="TEMPUSERNAME" Secure="yes" />
+    </Fragment>
+
+    <Fragment>
+        <util:Group Id="ADMIN" Name="Administrators" />
+        <util:Group Id="DOMAIN_GUESTS" Name="Domain Guests" Domain="TESTDOMAIN" />
+
+        <Component Id="Component1" Guid="09624A9A-4BBC-4126-BBF9-0713C5217DB1" Directory="INSTALLFOLDER">
+            <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
+
+            <util:User Id="TEST_USER1" Name="testName1" Domain="TESTDOMAIN" Comment="testComment1"
+                       Password="test123!@#"
+                       PasswordNeverExpires="no"
+                       PasswordExpired="yes"
+                       Disabled="yes"
+                       CreateUser="yes"
+                       RemoveOnUninstall="yes">
+                <util:GroupRef Id="ADMIN" />
+                <util:GroupRef Id="DOMAIN_GUESTS" />
+            </util:User>
+        </Component>
+    </Fragment>
+</Wix>
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs
index 08b7cee18..41ea2f4c8 100644
--- a/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs
+++ b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs
@@ -79,7 +79,7 @@ public void CanRollbackUsers()
         // Verify that command-line parameters are not blocked by repair switches.
         // Original code signalled repair mode by using "-f ", which silently
         // terminated the command-line parsing, ignoring any parameters that followed.
-        [RuntimeFact()]
+        [RuntimeFact]
         public void CanRepairUsersWithCommandLineParameters()
         {
             var arguments = new string[]
@@ -103,7 +103,7 @@ public void CanRepairUsersWithCommandLineParameters()
 
 
         // Verify that the users specified in the authoring are created as expected on repair.
-        [RuntimeFact()]
+        [RuntimeFact]
         public void CanRepairUsers()
         {
             UserVerifier.CreateLocalUser("testName3", "test123!@#");
@@ -279,5 +279,24 @@ public void CanDeleteCommentOfExistingUser()
             // clean up
             UserVerifier.DeleteLocalUser("testName1");
         }
+
+        // Verify that the users specified in the authoring are created as expected.
+        [RuntimeFact(DomainRequired = true)]
+        public void CanInstallAndUninstallDomainUsers()
+        {
+            var productDomain = this.CreatePackageInstaller("ProductDomain");
+
+            productDomain.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
+
+            // Validate New User Information.
+            UserVerifier.VerifyUserInformation("TESTDOMAIN", "testName1", true, false, true);
+            UserVerifier.VerifyUserIsMemberOf("TESTDOMAIN", "testName1", "Administrators", "TESTDOMAIN\\Domain Guests");
+            UserVerifier.VerifyUserComment("TESTDOMAIN", "testName1", "testComment1");
+
+            productDomain.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
+
+            // Verify Users marked as RemoveOnUninstall were removed.
+            Assert.False(UserVerifier.UserExists("TESTDOMAIN", "testName1"), String.Format("User '{0}\\{1}' was not removed on Uninstall", "TESTDOMAIN", "testName1"));
+        }
     }
 }
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd b/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd
index a2e67c891..ed1d50b68 100644
--- a/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd
+++ b/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd
@@ -1,2 +1,3 @@
 SET RuntimeTestsEnabled=true
+SET RuntimeDomainTestsEnabled=true
 dotnet test WixToolsetTest.MsiE2E.dll -v normal --logger trx