aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRon Martin <cpuwzd@comcast.net>2022-08-29 18:38:07 -0400
committerRob Mensching <rob@firegiant.com>2022-10-21 19:08:08 -0700
commit08cdc6aa2b9dd0e273a3c3a22893616d26342a0e (patch)
tree1d0b9f7e21cec02abfda50b1a3c6d0c24308998b /src
parent40bd65379768f99ec28bffe2691ba43c78c9e9c4 (diff)
downloadwix-08cdc6aa2b9dd0e273a3c3a22893616d26342a0e.tar.gz
wix-08cdc6aa2b9dd0e273a3c3a22893616d26342a0e.tar.bz2
wix-08cdc6aa2b9dd0e273a3c3a22893616d26342a0e.zip
Support add, modify and remove comments on user accounts
Fixes 5371
Diffstat (limited to 'src')
-rw-r--r--src/ext/Iis/ca/sca.h1
-rw-r--r--src/ext/Util/ca/sca.h3
-rw-r--r--src/ext/Util/ca/scaexec.cpp375
-rw-r--r--src/ext/Util/ca/scauser.cpp62
-rw-r--r--src/ext/Util/ca/scauser.h7
-rw-r--r--src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/Package.wxs15
-rw-r--r--src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/PackageComponents.wxs76
-rw-r--r--src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs76
-rw-r--r--src/ext/Util/wixext/Symbols/UserSymbol.cs10
-rw-r--r--src/ext/Util/wixext/UtilCompiler.cs22
-rw-r--r--src/ext/Util/wixext/UtilDecompiler.cs6
-rw-r--r--src/ext/Util/wixext/UtilTableDefinitions.cs1
-rw-r--r--src/libs/wcautil/WixToolset.WcaUtil/wcawrap.cpp11
-rw-r--r--src/test/burn/WixTestTools/MSIExec.cs2
-rw-r--r--src/test/burn/WixTestTools/UserVerifier.cs41
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj2
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs6
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/ProductAddCommentToExistingUser.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/product.wxs25
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/ProductCommentDelete.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/product.wxs18
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/ProductCommentFail.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/product_fail.wxs22
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs2
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/ProductNewUserWithComment.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/product.wxs25
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs21
-rw-r--r--src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs186
29 files changed, 900 insertions, 180 deletions
diff --git a/src/ext/Iis/ca/sca.h b/src/ext/Iis/ca/sca.h
index 64567dcb..6921613b 100644
--- a/src/ext/Iis/ca/sca.h
+++ b/src/ext/Iis/ca/sca.h
@@ -121,4 +121,5 @@ enum SCAU_ATTRIBUTES
121 SCAU_DONT_REMOVE_ON_UNINSTALL = 0x00000100, 121 SCAU_DONT_REMOVE_ON_UNINSTALL = 0x00000100,
122 SCAU_DONT_CREATE_USER = 0x00000200, 122 SCAU_DONT_CREATE_USER = 0x00000200,
123 SCAU_NON_VITAL = 0x00000400, 123 SCAU_NON_VITAL = 0x00000400,
124 SCAU_REMOVE_COMMENT = 0x00000800,
124}; 125};
diff --git a/src/ext/Util/ca/sca.h b/src/ext/Util/ca/sca.h
index 599122ff..84f5ffd9 100644
--- a/src/ext/Util/ca/sca.h
+++ b/src/ext/Util/ca/sca.h
@@ -16,4 +16,5 @@ enum SCAU_ATTRIBUTES
16 SCAU_DONT_REMOVE_ON_UNINSTALL = 0x00000100, 16 SCAU_DONT_REMOVE_ON_UNINSTALL = 0x00000100,
17 SCAU_DONT_CREATE_USER = 0x00000200, 17 SCAU_DONT_CREATE_USER = 0x00000200,
18 SCAU_NON_VITAL = 0x00000400, 18 SCAU_NON_VITAL = 0x00000400,
19}; \ No newline at end of file 19 SCAU_REMOVE_COMMENT = 0x00000800,
20};
diff --git a/src/ext/Util/ca/scaexec.cpp b/src/ext/Util/ca/scaexec.cpp
index 5845c1b4..7bd271d1 100644
--- a/src/ext/Util/ca/scaexec.cpp
+++ b/src/ext/Util/ca/scaexec.cpp
@@ -2,7 +2,6 @@
2 2
3#include "precomp.h" 3#include "precomp.h"
4 4
5
6/******************************************************************** 5/********************************************************************
7 * CreateSmb - CUSTOM ACTION ENTRY POINT for creating fileshares 6 * CreateSmb - CUSTOM ACTION ENTRY POINT for creating fileshares
8 * 7 *
@@ -520,55 +519,88 @@ static HRESULT ModifyUserLocalBatchRight(
520 return hr; 519 return hr;
521} 520}
522 521
523static void SetUserPasswordAndAttributes( 522static HRESULT ApplyAttributes(int iAttributes, DWORD* pFlags)
524 __in USER_INFO_1* puserInfo,
525 __in LPWSTR wzPassword,
526 __in int iAttributes
527 )
528{ 523{
529 Assert(puserInfo); 524 HRESULT hr = S_OK;
530
531 // Set the User's password
532 puserInfo->usri1_password = wzPassword;
533 525
534 // Apply the Attributes
535 if (SCAU_DONT_EXPIRE_PASSWRD & iAttributes) 526 if (SCAU_DONT_EXPIRE_PASSWRD & iAttributes)
536 { 527 {
537 puserInfo->usri1_flags |= UF_DONT_EXPIRE_PASSWD; 528 *pFlags |= UF_DONT_EXPIRE_PASSWD;
538 } 529 }
539 else 530 else
540 { 531 {
541 puserInfo->usri1_flags &= ~UF_DONT_EXPIRE_PASSWD; 532 *pFlags &= ~UF_DONT_EXPIRE_PASSWD;
542 } 533 }
543 534
544 if (SCAU_PASSWD_CANT_CHANGE & iAttributes) 535 if (SCAU_PASSWD_CANT_CHANGE & iAttributes)
545 { 536 {
546 puserInfo->usri1_flags |= UF_PASSWD_CANT_CHANGE; 537 *pFlags |= UF_PASSWD_CANT_CHANGE;
547 } 538 }
548 else 539 else
549 { 540 {
550 puserInfo->usri1_flags &= ~UF_PASSWD_CANT_CHANGE; 541 *pFlags &= ~UF_PASSWD_CANT_CHANGE;
551 } 542 }
552 543
553 if (SCAU_DISABLE_ACCOUNT & iAttributes) 544 if (SCAU_DISABLE_ACCOUNT & iAttributes)
554 { 545 {
555 puserInfo->usri1_flags |= UF_ACCOUNTDISABLE; 546 *pFlags |= UF_ACCOUNTDISABLE;
556 } 547 }
557 else 548 else
558 { 549 {
559 puserInfo->usri1_flags &= ~UF_ACCOUNTDISABLE; 550 *pFlags &= ~UF_ACCOUNTDISABLE;
560 } 551 }
561 552
562 if (SCAU_PASSWD_CHANGE_REQD_ON_LOGIN & iAttributes) // TODO: for some reason this doesn't work 553 if (SCAU_PASSWD_CHANGE_REQD_ON_LOGIN & iAttributes) // TODO: for some reason this doesn't work
563 { 554 {
564 puserInfo->usri1_flags |= UF_PASSWORD_EXPIRED; 555 *pFlags |= UF_PASSWORD_EXPIRED;
565 } 556 }
566 else 557 else
567 { 558 {
568 puserInfo->usri1_flags &= ~UF_PASSWORD_EXPIRED; 559 *pFlags &= ~UF_PASSWORD_EXPIRED;
560 }
561
562 return hr;
563}
564
565static HRESULT ApplyComment(int iAttributes, LPWSTR pwzComment, LPWSTR* ppComment)
566{
567 HRESULT hr = S_OK;
568
569 if (SCAU_REMOVE_COMMENT & iAttributes)
570 {
571 *ppComment = L"";
572 }
573 else if (pwzComment && *pwzComment)
574 {
575 *ppComment = pwzComment;
569 } 576 }
577
578 return hr;
579}
580
581static NET_API_STATUS SetUserPassword(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in LPWSTR pwzPassword)
582{
583 _USER_INFO_1003 userInfo1003;
584
585 userInfo1003.usri1003_password = pwzPassword;
586 return ::NetUserSetInfo(pwzServerName, pwzName, 1003, reinterpret_cast<LPBYTE>(&userInfo1003), NULL);
570} 587}
571 588
589static NET_API_STATUS SetUserComment(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in LPWSTR pwzComment)
590{
591 _USER_INFO_1007 userInfo1007;
592
593 userInfo1007.usri1007_comment = pwzComment;
594 return ::NetUserSetInfo(pwzServerName, pwzName, 1007, reinterpret_cast<LPBYTE>(&userInfo1007), NULL);
595}
596
597static NET_API_STATUS SetUserFlags(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in DWORD flags)
598{
599 _USER_INFO_1008 userInfo1008;
600
601 userInfo1008.usri1008_flags = flags;
602 return ::NetUserSetInfo(pwzServerName, pwzName, 1008, reinterpret_cast<LPBYTE>(&userInfo1008), NULL);
603}
572 604
573static HRESULT RemoveUserInternal( 605static HRESULT RemoveUserInternal(
574 LPWSTR wzGroupCaData, 606 LPWSTR wzGroupCaData,
@@ -624,7 +656,12 @@ static HRESULT RemoveUserInternal(
624 } 656 }
625 if (ERROR_SUCCESS == er) 657 if (ERROR_SUCCESS == er)
626 { 658 {
627 wz = pDomainControllerInfo->DomainControllerName + 2; //Add 2 so that we don't get the \\ prefix 659 if (2 <= wcslen(pDomainControllerInfo->DomainControllerName))
660 {
661 wz = pDomainControllerInfo->DomainControllerName + 2; // Add 2 so that we don't get the \\ prefix.
662 // Pass the entire string if it is too short
663 // to have a \\ prefix.
664 }
628 } 665 }
629 else 666 else
630 { 667 {
@@ -680,6 +717,41 @@ LExit:
680 return hr; 717 return hr;
681} 718}
682 719
720static HRESULT GetServerName(LPWSTR pwzDomain, LPWSTR* ppwzServerName)
721{
722 HRESULT hr = S_OK;
723
724 PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL;
725 UINT er;
726
727 if (pwzDomain && *pwzDomain)
728 {
729 er = ::DsGetDcNameW(NULL, (LPCWSTR)pwzDomain, NULL, NULL, NULL, &pDomainControllerInfo);
730 if (RPC_S_SERVER_UNAVAILABLE == er)
731 {
732 // MSDN says, if we get the above error code, try again with the "DS_FORCE_REDISCOVERY" flag
733 er = ::DsGetDcNameW(NULL, (LPCWSTR)pwzDomain, NULL, NULL, DS_FORCE_REDISCOVERY, &pDomainControllerInfo);
734 }
735 if (ERROR_SUCCESS == er
736 && 2 <= wcslen(pDomainControllerInfo->DomainControllerName)
737 && '\\' == *pDomainControllerInfo->DomainControllerName
738 && '\\' == *pDomainControllerInfo->DomainControllerName + 1)
739 {
740 *ppwzServerName = pDomainControllerInfo->DomainControllerName + 2; // Skip the \\ prefix
741 }
742 else
743 {
744 *ppwzServerName = pwzDomain;
745 }
746 }
747
748 if (pDomainControllerInfo)
749 {
750 ::NetApiBufferFree((LPVOID)pDomainControllerInfo);
751 }
752
753 return hr;
754}
683 755
684/******************************************************************** 756/********************************************************************
685 CreateUser - CUSTOM ACTION ENTRY POINT for creating users 757 CreateUser - CUSTOM ACTION ENTRY POINT for creating users
@@ -688,7 +760,7 @@ LExit:
688 * *****************************************************************/ 760 * *****************************************************************/
689extern "C" UINT __stdcall CreateUser( 761extern "C" UINT __stdcall CreateUser(
690 __in MSIHANDLE hInstall 762 __in MSIHANDLE hInstall
691 ) 763)
692{ 764{
693 //AssertSz(0, "Debug CreateUser"); 765 //AssertSz(0, "Debug CreateUser");
694 766
@@ -699,11 +771,11 @@ extern "C" UINT __stdcall CreateUser(
699 LPWSTR pwz = NULL; 771 LPWSTR pwz = NULL;
700 LPWSTR pwzName = NULL; 772 LPWSTR pwzName = NULL;
701 LPWSTR pwzDomain = NULL; 773 LPWSTR pwzDomain = NULL;
774 LPWSTR pwzComment = NULL;
702 LPWSTR pwzScriptKey = NULL; 775 LPWSTR pwzScriptKey = NULL;
703 LPWSTR pwzPassword = NULL; 776 LPWSTR pwzPassword = NULL;
704 LPWSTR pwzGroup = NULL; 777 LPWSTR pwzGroup = NULL;
705 LPWSTR pwzGroupDomain = NULL; 778 LPWSTR pwzGroupDomain = NULL;
706 PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL;
707 int iAttributes = 0; 779 int iAttributes = 0;
708 BOOL fInitializedCom = FALSE; 780 BOOL fInitializedCom = FALSE;
709 781
@@ -711,10 +783,10 @@ extern "C" UINT __stdcall CreateUser(
711 int iOriginalAttributes = 0; 783 int iOriginalAttributes = 0;
712 int iRollbackAttributes = 0; 784 int iRollbackAttributes = 0;
713 785
714 USER_INFO_1 userInfo; 786 USER_INFO_1 userInfo1;
715 USER_INFO_1* puserInfo = NULL; 787 USER_INFO_1* pUserInfo1 = NULL;
716 DWORD dw; 788 DWORD dw;
717 LPCWSTR wz = NULL; 789 LPWSTR pwzServerName = NULL;
718 790
719 hr = WcaInitialize(hInstall, "CreateUser"); 791 hr = WcaInitialize(hInstall, "CreateUser");
720 ExitOnFailure(hr, "failed to initialize"); 792 ExitOnFailure(hr, "failed to initialize");
@@ -723,7 +795,7 @@ extern "C" UINT __stdcall CreateUser(
723 ExitOnFailure(hr, "failed to initialize COM"); 795 ExitOnFailure(hr, "failed to initialize COM");
724 fInitializedCom = TRUE; 796 fInitializedCom = TRUE;
725 797
726 hr = WcaGetProperty( L"CustomActionData", &pwzData); 798 hr = WcaGetProperty(L"CustomActionData", &pwzData);
727 ExitOnFailure(hr, "failed to get CustomActionData"); 799 ExitOnFailure(hr, "failed to get CustomActionData");
728 800
729 WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); 801 WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData);
@@ -738,6 +810,9 @@ extern "C" UINT __stdcall CreateUser(
738 hr = WcaReadStringFromCaData(&pwz, &pwzDomain); 810 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
739 ExitOnFailure(hr, "failed to read domain from custom action data"); 811 ExitOnFailure(hr, "failed to read domain from custom action data");
740 812
813 hr = WcaReadStringFromCaData(&pwz, &pwzComment);
814 ExitOnFailure(hr, "failed to read user comment from custom action data");
815
741 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); 816 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
742 ExitOnFailure(hr, "failed to read attributes from custom action data"); 817 ExitOnFailure(hr, "failed to read attributes from custom action data");
743 818
@@ -747,96 +822,155 @@ extern "C" UINT __stdcall CreateUser(
747 hr = WcaReadStringFromCaData(&pwz, &pwzPassword); 822 hr = WcaReadStringFromCaData(&pwz, &pwzPassword);
748 ExitOnFailure(hr, "failed to read password from custom action data"); 823 ExitOnFailure(hr, "failed to read password from custom action data");
749 824
750 // There is no rollback scheduled if the key is empty.
751 // Best effort to get original configuration and save it in the script so rollback can restore it.
752 if (*pwzScriptKey)
753 {
754 hr = WcaCaScriptCreate(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, FALSE, &hRollbackScript);
755 ExitOnFailure(hr, "Failed to open rollback CustomAction script.");
756
757 iRollbackAttributes = 0;
758 hr = GetExistingUserRightsAssignments(pwzDomain, pwzName, &iOriginalAttributes);
759 if (FAILED(hr))
760 {
761 WcaLogError(hr, "failed to get existing user rights: %ls, continuing anyway.", pwzName);
762 }
763 else
764 {
765 if (!(SCAU_ALLOW_LOGON_AS_SERVICE & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes))
766 {
767 iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_SERVICE;
768 }
769 if (!(SCAU_ALLOW_LOGON_AS_BATCH & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_BATCH & iAttributes))
770 {
771 iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_BATCH;
772 }
773 }
774
775 hr = WcaCaScriptWriteNumber(hRollbackScript, iRollbackAttributes);
776 ExitOnFailure(hr, "Failed to add data to rollback script.");
777
778 // Nudge the system to get all our rollback data written to disk.
779 WcaCaScriptFlush(hRollbackScript);
780 }
781
782 if (!(SCAU_DONT_CREATE_USER & iAttributes)) 825 if (!(SCAU_DONT_CREATE_USER & iAttributes))
783 { 826 {
784 ::ZeroMemory(&userInfo, sizeof(USER_INFO_1)); 827 pUserInfo1 = &userInfo1;
785 userInfo.usri1_name = pwzName; 828 ::ZeroMemory(pUserInfo1, sizeof(USER_INFO_1));
786 userInfo.usri1_priv = USER_PRIV_USER; 829 pUserInfo1->usri1_name = pwzName;
787 userInfo.usri1_flags = UF_SCRIPT; 830 pUserInfo1->usri1_priv = USER_PRIV_USER;
788 userInfo.usri1_home_dir = NULL; 831 pUserInfo1->usri1_flags = UF_SCRIPT;
789 userInfo.usri1_comment = NULL; 832 pUserInfo1->usri1_home_dir = NULL;
790 userInfo.usri1_script_path = NULL; 833 pUserInfo1->usri1_comment = NULL;
791 834 pUserInfo1->usri1_script_path = NULL;
792 SetUserPasswordAndAttributes(&userInfo, pwzPassword, iAttributes); 835
836 // Set the user's password
837 pUserInfo1->usri1_password = pwzPassword;
838
839 // Set the user's comment
840 hr = ApplyComment(iAttributes, pwzComment, &pUserInfo1->usri1_comment);
841 ExitOnFailure(hr, "failed to apply comment");
842
843 // Set the user's flags
844 hr = ApplyAttributes(iAttributes, &pUserInfo1->usri1_flags);
845 ExitOnFailure(hr, "failed to apply attributes");
793 846
794 // 847 //
795 // Create the User 848 // Create the User
796 // 849 //
797 if (pwzDomain && *pwzDomain) 850 hr = GetServerName(pwzDomain, &pwzServerName);
851 ExitOnFailure(hr, "failed to get server name");
852
853 er = ::NetUserAdd(pwzServerName, 1, reinterpret_cast<LPBYTE>(pUserInfo1), &dw);
854 if (NERR_UserExists == er)
798 { 855 {
799 er = ::DsGetDcNameW( NULL, (LPCWSTR)pwzDomain, NULL, NULL, NULL, &pDomainControllerInfo ); 856 er = ERROR_SUCCESS; // Make sure that we don't report this situation as an error
800 if (RPC_S_SERVER_UNAVAILABLE == er) 857 // if we fall through the tests that follow.
801 { 858 if (SCAU_FAIL_IF_EXISTS & iAttributes)
802 // MSDN says, if we get the above error code, try again with the "DS_FORCE_REDISCOVERY" flag
803 er = ::DsGetDcNameW( NULL, (LPCWSTR)pwzDomain, NULL, NULL, DS_FORCE_REDISCOVERY, &pDomainControllerInfo );
804 }
805 if (ERROR_SUCCESS == er)
806 { 859 {
807 wz = pDomainControllerInfo->DomainControllerName + 2; //Add 2 so that we don't get the \\ prefix 860 hr = HRESULT_FROM_WIN32(er);
861 ExitOnFailure(hr, "User was not supposed to exist, but does.");
808 } 862 }
809 else
810 {
811 wz = pwzDomain;
812 }
813 }
814 863
815 er = ::NetUserAdd(wz, 1, reinterpret_cast<LPBYTE>(&userInfo), &dw);
816 if (NERR_UserExists == er)
817 {
818 if (SCAU_UPDATE_IF_EXISTS & iAttributes) 864 if (SCAU_UPDATE_IF_EXISTS & iAttributes)
819 { 865 {
820 er = ::NetUserGetInfo(wz, pwzName, 1, reinterpret_cast<LPBYTE*>(&puserInfo)); 866 pUserInfo1 = NULL;
821 if (NERR_Success == er) 867 er = ::NetUserGetInfo(pwzServerName, pwzName, 1, reinterpret_cast<LPBYTE*>(&pUserInfo1));
868 if (ERROR_SUCCESS == er)
822 { 869 {
823 // Change the existing user's password and attributes again then try 870 // There is no rollback scheduled if the key is empty.
824 // to update user with this new data 871 // Best effort to get original configuration and save it in the script so rollback can restore it.
825 SetUserPasswordAndAttributes(puserInfo, pwzPassword, iAttributes); 872
873 if (*pwzScriptKey)
874 {
875 // Try to open the rollback script
876 hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript);
877
878 if (INVALID_HANDLE_VALUE != hRollbackScript)
879 {
880 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE);
881 }
882 else
883 {
884 hRollbackScript = NULL;
885 hr = WcaCaScriptCreate(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, FALSE, &hRollbackScript);
886 ExitOnFailure(hr, "Failed to open rollback CustomAction script.");
887
888 iRollbackAttributes = 0;
889 hr = GetExistingUserRightsAssignments(pwzDomain, pwzName, &iOriginalAttributes);
890 if (FAILED(hr))
891 {
892 WcaLogError(hr, "failed to get existing user rights: %ls, continuing anyway.", pwzName);
893 }
894 else
895 {
896 if (!(SCAU_ALLOW_LOGON_AS_SERVICE & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes))
897 {
898 iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_SERVICE;
899 }
900
901 if (!(SCAU_ALLOW_LOGON_AS_BATCH & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_BATCH & iAttributes))
902 {
903 iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_BATCH;
904 }
905 }
906
907 hr = WcaCaScriptWriteString(hRollbackScript, pUserInfo1->usri1_comment);
908 ExitOnFailure(hr, "Failed to add rollback comment to rollback script.");
909
910 if (!pUserInfo1->usri1_comment || !*pUserInfo1->usri1_comment)
911 {
912 iRollbackAttributes |= SCAU_REMOVE_COMMENT;
913 }
914
915 hr = WcaCaScriptWriteNumber(hRollbackScript, iRollbackAttributes);
916 ExitOnFailure(hr, "Failed to add rollback attributes to rollback script.");
917
918 // Nudge the system to get all our rollback data written to disk.
919 WcaCaScriptFlush(hRollbackScript);
920 }
921 }
922 }
826 923
827 er = ::NetUserSetInfo(wz, pwzName, 1, reinterpret_cast<LPBYTE>(puserInfo), &dw); 924 if (ERROR_SUCCESS == er)
925 {
926 hr = HRESULT_FROM_WIN32(::SetUserPassword(pwzServerName, pwzName, pwzPassword));
927 if (FAILED(hr))
928 {
929 WcaLogError(hr, "failed to set user password for user %ls\\%ls, continuing anyway.", pwzServerName, pwzName);
930 }
931
932 if (SCAU_REMOVE_COMMENT & iAttributes)
933 {
934 hr = HRESULT_FROM_WIN32(SetUserComment(pwzServerName, pwzName, L""));
935 if (FAILED(hr))
936 {
937 WcaLogError(hr, "failed to clear user comment for user %ls\\%ls, continuing anyway.", pwzServerName, pwzName);
938 }
939 }
940 else if (pwzComment && *pwzComment)
941 {
942 hr = HRESULT_FROM_WIN32(SetUserComment(pwzServerName, pwzName, pwzComment));
943 if (FAILED(hr))
944 {
945 WcaLogError(hr, "failed to set user comment to %ls for user %ls\\%ls, continuing anyway.", pwzComment, pwzServerName, pwzName);
946 }
947 }
948
949 DWORD flags = pUserInfo1->usri1_flags;
950
951 hr = ApplyAttributes(iAttributes, &flags);
952 if (FAILED(hr))
953 {
954 WcaLogError(hr, "failed to apply attributes for user %ls\\%ls, continuing anyway.", pwzServerName, pwzName);
955 }
956
957 hr = HRESULT_FROM_WIN32(SetUserFlags(pwzServerName, pwzName, flags));
958 if (FAILED(hr))
959 {
960 WcaLogError(hr, "failed to set user flags for user %ls\\%ls, continuing anyway.", pwzServerName, pwzName);
961 }
828 } 962 }
829 } 963 }
830 else if (!(SCAU_FAIL_IF_EXISTS & iAttributes))
831 {
832 er = NERR_Success;
833 }
834 } 964 }
835 else if (NERR_PasswordTooShort == er || NERR_PasswordTooLong == er) 965 else if (NERR_PasswordTooShort == er || NERR_PasswordTooLong == er)
836 { 966 {
837 MessageExitOnFailure(hr = HRESULT_FROM_WIN32(er), msierrUSRFailedUserCreatePswd, "failed to create user: %ls due to invalid password.", pwzName); 967 MessageExitOnFailure(hr = HRESULT_FROM_WIN32(er), msierrUSRFailedUserCreatePswd, "failed to create user: %ls due to invalid password.", pwzName);
838 } 968 }
839 MessageExitOnFailure(hr = HRESULT_FROM_WIN32(er), msierrUSRFailedUserCreate, "failed to create user: %ls", pwzName); 969
970 if (ERROR_SUCCESS != er)
971 {
972 MessageExitOnFailure(hr = HRESULT_FROM_WIN32(er), msierrUSRFailedUserCreate, "failed to create user: %ls", pwzName);
973 }
840 } 974 }
841 975
842 if (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes) 976 if (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes)
@@ -851,14 +985,15 @@ extern "C" UINT __stdcall CreateUser(
851 MessageExitOnFailure(hr, msierrUSRFailedGrantLogonAsService, "Failed to grant logon as batch job rights to user: %ls", pwzName); 985 MessageExitOnFailure(hr, msierrUSRFailedGrantLogonAsService, "Failed to grant logon as batch job rights to user: %ls", pwzName);
852 } 986 }
853 987
854 // 988//
855 // Add the users to groups 989// Add the users to groups
856 // 990//
857 while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup))) 991while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup)))
858 { 992{
859 hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain); 993 hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain);
860 ExitOnFailure(hr, "failed to get domain for group: %ls", pwzGroup); 994 ExitOnFailure(hr, "failed to get domain for group: %ls", pwzGroup);
861 995
996 WcaLog(LOGMSG_STANDARD, "Adding user %ls\\%ls to group %ls\\%ls", pwzDomain, pwzName, pwzGroupDomain, pwzGroup);
862 hr = AddUserToGroup(pwzName, pwzDomain, pwzGroup, pwzGroupDomain); 997 hr = AddUserToGroup(pwzName, pwzDomain, pwzGroup, pwzGroupDomain);
863 MessageExitOnFailure(hr, msierrUSRFailedUserGroupAdd, "failed to add user: %ls to group %ls", pwzName, pwzGroup); 998 MessageExitOnFailure(hr, msierrUSRFailedUserGroupAdd, "failed to add user: %ls to group %ls", pwzName, pwzGroup);
864 } 999 }
@@ -866,24 +1001,23 @@ extern "C" UINT __stdcall CreateUser(
866 { 1001 {
867 hr = S_OK; 1002 hr = S_OK;
868 } 1003 }
1004
869 ExitOnFailure(hr, "failed to get next group in which to include user:%ls", pwzName); 1005 ExitOnFailure(hr, "failed to get next group in which to include user:%ls", pwzName);
870 1006
1007ExitOnFailure(hr, "failed to get next group in which to include user:%ls", pwzName);
1008
871LExit: 1009LExit:
872 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE); 1010 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE);
873 1011
874 if (puserInfo) 1012 if (pUserInfo1 && pUserInfo1 != &userInfo1)
875 { 1013 {
876 ::NetApiBufferFree((LPVOID)puserInfo); 1014 ::NetApiBufferFree((LPVOID)pUserInfo1);
877 }
878
879 if (pDomainControllerInfo)
880 {
881 ::NetApiBufferFree((LPVOID)pDomainControllerInfo);
882 } 1015 }
883 1016
884 ReleaseStr(pwzData); 1017 ReleaseStr(pwzData);
885 ReleaseStr(pwzName); 1018 ReleaseStr(pwzName);
886 ReleaseStr(pwzDomain); 1019 ReleaseStr(pwzDomain);
1020 ReleaseStr(pwzComment);
887 ReleaseStr(pwzScriptKey); 1021 ReleaseStr(pwzScriptKey);
888 ReleaseStr(pwzPassword); 1022 ReleaseStr(pwzPassword);
889 ReleaseStr(pwzGroup); 1023 ReleaseStr(pwzGroup);
@@ -922,15 +1056,17 @@ extern "C" UINT __stdcall CreateUserRollback(
922 1056
923 LPWSTR pwzData = NULL; 1057 LPWSTR pwzData = NULL;
924 LPWSTR pwz = NULL; 1058 LPWSTR pwz = NULL;
1059 LPWSTR pwzScriptKey = NULL;
925 LPWSTR pwzName = NULL; 1060 LPWSTR pwzName = NULL;
926 LPWSTR pwzDomain = NULL; 1061 LPWSTR pwzDomain = NULL;
927 LPWSTR pwzScriptKey = NULL; 1062 LPWSTR pwzComment = NULL;
928 int iAttributes = 0; 1063 int iAttributes = 0;
929 BOOL fInitializedCom = FALSE; 1064 BOOL fInitializedCom = FALSE;
930 1065
931 WCA_CASCRIPT_HANDLE hRollbackScript = NULL; 1066 WCA_CASCRIPT_HANDLE hRollbackScript = NULL;
932 LPWSTR pwzRollbackData = NULL; 1067 LPWSTR pwzRollbackData = NULL;
933 int iOriginalAttributes = 0; 1068 int iOriginalAttributes = 0;
1069 LPWSTR pwzOriginalComment = NULL;
934 1070
935 hr = WcaInitialize(hInstall, "CreateUserRollback"); 1071 hr = WcaInitialize(hInstall, "CreateUserRollback");
936 ExitOnFailure(hr, "failed to initialize"); 1072 ExitOnFailure(hr, "failed to initialize");
@@ -957,6 +1093,9 @@ extern "C" UINT __stdcall CreateUserRollback(
957 hr = WcaReadStringFromCaData(&pwz, &pwzDomain); 1093 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
958 ExitOnFailure(hr, "failed to read domain from custom action data"); 1094 ExitOnFailure(hr, "failed to read domain from custom action data");
959 1095
1096 hr = WcaReadStringFromCaData(&pwz, &pwzComment);
1097 ExitOnFailure(hr, "failed to read comment from custom action data");
1098
960 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); 1099 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
961 ExitOnFailure(hr, "failed to read attributes from custom action data"); 1100 ExitOnFailure(hr, "failed to read attributes from custom action data");
962 1101
@@ -978,6 +1117,15 @@ extern "C" UINT __stdcall CreateUserRollback(
978 WcaLog(LOGMSG_TRACEONLY, "Rollback Data: %ls", pwzRollbackData); 1117 WcaLog(LOGMSG_TRACEONLY, "Rollback Data: %ls", pwzRollbackData);
979 1118
980 pwz = pwzRollbackData; 1119 pwz = pwzRollbackData;
1120 hr = WcaReadStringFromCaData(&pwz, &pwzOriginalComment);
1121 if (FAILED(hr))
1122 {
1123 WcaLogError(hr, "failed to read comment from rollback data, continuing anyway");
1124 }
1125 else
1126 {
1127 pwzComment = pwzOriginalComment;
1128 }
981 hr = WcaReadIntegerFromCaData(&pwz, &iOriginalAttributes); 1129 hr = WcaReadIntegerFromCaData(&pwz, &iOriginalAttributes);
982 if (FAILED(hr)) 1130 if (FAILED(hr))
983 { 1131 {
@@ -998,8 +1146,10 @@ LExit:
998 ReleaseStr(pwzData); 1146 ReleaseStr(pwzData);
999 ReleaseStr(pwzName); 1147 ReleaseStr(pwzName);
1000 ReleaseStr(pwzDomain); 1148 ReleaseStr(pwzDomain);
1149 ReleaseStr(pwzComment);
1001 ReleaseStr(pwzScriptKey); 1150 ReleaseStr(pwzScriptKey);
1002 ReleaseStr(pwzRollbackData); 1151 ReleaseStr(pwzRollbackData);
1152 ReleaseStr(pwzOriginalComment);
1003 1153
1004 if (fInitializedCom) 1154 if (fInitializedCom)
1005 { 1155 {
@@ -1033,6 +1183,7 @@ extern "C" UINT __stdcall RemoveUser(
1033 LPWSTR pwz = NULL; 1183 LPWSTR pwz = NULL;
1034 LPWSTR pwzName = NULL; 1184 LPWSTR pwzName = NULL;
1035 LPWSTR pwzDomain = NULL; 1185 LPWSTR pwzDomain = NULL;
1186 LPWSTR pwzComment = NULL;
1036 int iAttributes = 0; 1187 int iAttributes = 0;
1037 BOOL fInitializedCom = FALSE; 1188 BOOL fInitializedCom = FALSE;
1038 1189
@@ -1058,6 +1209,9 @@ extern "C" UINT __stdcall RemoveUser(
1058 hr = WcaReadStringFromCaData(&pwz, &pwzDomain); 1209 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
1059 ExitOnFailure(hr, "failed to read domain from custom action data"); 1210 ExitOnFailure(hr, "failed to read domain from custom action data");
1060 1211
1212 hr = WcaReadStringFromCaData(&pwz, &pwzComment);
1213 ExitOnFailure(hr, "failed to read comment from custom action data");
1214
1061 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); 1215 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
1062 ExitOnFailure(hr, "failed to read attributes from custom action data"); 1216 ExitOnFailure(hr, "failed to read attributes from custom action data");
1063 1217
@@ -1067,6 +1221,7 @@ LExit:
1067 ReleaseStr(pwzData); 1221 ReleaseStr(pwzData);
1068 ReleaseStr(pwzName); 1222 ReleaseStr(pwzName);
1069 ReleaseStr(pwzDomain); 1223 ReleaseStr(pwzDomain);
1224 ReleaseStr(pwzComment);
1070 1225
1071 if (fInitializedCom) 1226 if (fInitializedCom)
1072 { 1227 {
diff --git a/src/ext/Util/ca/scauser.cpp b/src/ext/Util/ca/scauser.cpp
index f92ebf1b..dc5bebba 100644
--- a/src/ext/Util/ca/scauser.cpp
+++ b/src/ext/Util/ca/scauser.cpp
@@ -2,8 +2,8 @@
2 2
3#include "precomp.h" 3#include "precomp.h"
4 4
5LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Password` FROM `Wix4User` WHERE `User`=?"; 5LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Comment`, `Password` FROM `Wix4User` WHERE `User`=?";
6enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqPassword }; 6enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqComment, vuqPassword };
7 7
8LPCWSTR vcsGroupQuery = L"SELECT `Group`, `Component_`, `Name`, `Domain` FROM `Wix4Group` WHERE `Group`=?"; 8LPCWSTR vcsGroupQuery = L"SELECT `Group`, `Component_`, `Name`, `Domain` FROM `Wix4Group` WHERE `Group`=?";
9enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain }; 9enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain };
@@ -11,8 +11,8 @@ enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain };
11LPCWSTR vcsUserGroupQuery = L"SELECT `User_`, `Group_` FROM `Wix4UserGroup` WHERE `User_`=?"; 11LPCWSTR vcsUserGroupQuery = L"SELECT `User_`, `Group_` FROM `Wix4UserGroup` WHERE `User_`=?";
12enum eUserGroupQuery { vugqUser = 1, vugqGroup }; 12enum eUserGroupQuery { vugqUser = 1, vugqGroup };
13 13
14LPCWSTR vActionableQuery = L"SELECT `User`,`Component_`,`Name`,`Domain`,`Password`,`Attributes` FROM `Wix4User` WHERE `Component_` IS NOT NULL"; 14LPCWSTR vActionableQuery = L"SELECT `User`,`Component_`,`Name`,`Domain`,`Password`,`Comment`,`Attributes` FROM `Wix4User` WHERE `Component_` IS NOT NULL";
15enum eActionableQuery { vaqUser = 1, vaqComponent, vaqName, vaqDomain, vaqPassword, vaqAttributes }; 15enum eActionableQuery { vaqUser = 1, vaqComponent, vaqName, vaqDomain, vaqPassword, vaqComment, vaqAttributes };
16 16
17 17
18static HRESULT AddUserToList( 18static HRESULT AddUserToList(
@@ -78,6 +78,11 @@ HRESULT __stdcall ScaGetUser(
78 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData); 78 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData);
79 ExitOnFailure(hr, "Failed to copy domain string to user object"); 79 ExitOnFailure(hr, "Failed to copy domain string to user object");
80 80
81 hr = WcaGetRecordFormattedString(hRec, vuqComment, &pwzData);
82 ExitOnFailure(hr, "Failed to get Wix4User.Comment");
83 hr = ::StringCchCopyW(pscau->wzComment, countof(pscau->wzComment), pwzData);
84 ExitOnFailure(hr, "Failed to copy comment string to user object");
85
81 hr = WcaGetRecordFormattedString(hRec, vuqPassword, &pwzData); 86 hr = WcaGetRecordFormattedString(hRec, vuqPassword, &pwzData);
82 ExitOnFailure(hr, "Failed to get Wix4User.Password"); 87 ExitOnFailure(hr, "Failed to get Wix4User.Password");
83 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData); 88 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData);
@@ -154,6 +159,11 @@ HRESULT __stdcall ScaGetUserDeferred(
154 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData); 159 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData);
155 ExitOnFailure(hr, "Failed to copy domain string to user object (in deferred CA)"); 160 ExitOnFailure(hr, "Failed to copy domain string to user object (in deferred CA)");
156 161
162 hr = WcaGetRecordString(hRec, vuqComment, &pwzData);
163 ExitOnFailure(hr, "Failed to get Wix4User.Comment");
164 hr = ::StringCchCopyW(pscau->wzComment, countof(pscau->wzComment), pwzData);
165 ExitOnFailure(hr, "Failed to copy comment string to user object (in deferred CA)");
166
157 hr = WcaGetRecordString(hRec, vuqPassword, &pwzData); 167 hr = WcaGetRecordString(hRec, vuqPassword, &pwzData);
158 ExitOnFailure(hr, "Failed to get Wix4User.Password"); 168 ExitOnFailure(hr, "Failed to get Wix4User.Password");
159 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData); 169 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData);
@@ -316,7 +326,7 @@ HRESULT ScaUserRead(
316 ExitOnFailure(hr, "failed to get Component state for Wix4User"); 326 ExitOnFailure(hr, "failed to get Component state for Wix4User");
317 327
318 // don't bother if we aren't installing or uninstalling this component 328 // don't bother if we aren't installing or uninstalling this component
319 if (WcaIsInstalling(isInstalled, isAction) || WcaIsUninstalling(isInstalled, isAction)) 329 if (WcaIsInstalling(isInstalled, isAction) || WcaIsUninstalling(isInstalled, isAction))
320 { 330 {
321 // 331 //
322 // Add the user to the list and populate it's values 332 // Add the user to the list and populate it's values
@@ -345,6 +355,10 @@ HRESULT ScaUserRead(
345 ExitOnFailure(hr, "failed to get Wix4User.Domain"); 355 ExitOnFailure(hr, "failed to get Wix4User.Domain");
346 hr = ::StringCchCopyW(psu->wzDomain, countof(psu->wzDomain), pwzData); 356 hr = ::StringCchCopyW(psu->wzDomain, countof(psu->wzDomain), pwzData);
347 ExitOnFailure(hr, "failed to copy user domain: %ls", pwzData); 357 ExitOnFailure(hr, "failed to copy user domain: %ls", pwzData);
358 hr = WcaGetRecordFormattedString(hRec, vaqComment, &pwzData);
359 ExitOnFailure(hr, "failed to get Wix4User.Comment");
360 hr = ::StringCchCopyW(psu->wzComment, countof(psu->wzComment), pwzData);
361 ExitOnFailure(hr, "failed to copy user comment: %ls", pwzData);
348 362
349 hr = WcaGetRecordFormattedString(hRec, vaqPassword, &pwzData); 363 hr = WcaGetRecordFormattedString(hRec, vaqPassword, &pwzData);
350 ExitOnFailure(hr, "failed to get Wix4User.Password"); 364 ExitOnFailure(hr, "failed to get Wix4User.Password");
@@ -492,15 +506,16 @@ HRESULT ScaUserExecute(
492 { 506 {
493 USER_EXISTS ueUserExists = USER_EXISTS_INDETERMINATE; 507 USER_EXISTS ueUserExists = USER_EXISTS_INDETERMINATE;
494 508
495 // Always put the User Name and Domain plus Attributes on the front of the CustomAction 509 // Always put the User Name, Domain, and Comment on the front of the CustomAction data.
496 // data. Sometimes we'll add more data. 510 // The attributes will be added when we have finished adjusting them. Sometimes we'll
511 // add more data.
497 Assert(psu->wzName); 512 Assert(psu->wzName);
498 hr = WcaWriteStringToCaData(psu->wzName, &pwzActionData); 513 hr = WcaWriteStringToCaData(psu->wzName, &pwzActionData);
499 ExitOnFailure(hr, "Failed to add user name to custom action data: %ls", psu->wzName); 514 ExitOnFailure(hr, "Failed to add user name to custom action data: %ls", psu->wzName);
500 hr = WcaWriteStringToCaData(psu->wzDomain, &pwzActionData); 515 hr = WcaWriteStringToCaData(psu->wzDomain, &pwzActionData);
501 ExitOnFailure(hr, "Failed to add user domain to custom action data: %ls", psu->wzDomain); 516 ExitOnFailure(hr, "Failed to add user domain to custom action data: %ls", psu->wzDomain);
502 hr = WcaWriteIntegerToCaData(psu->iAttributes, &pwzActionData); 517 hr = WcaWriteStringToCaData(psu->wzComment, &pwzActionData);
503 ExitOnFailure(hr, "failed to add user attributes to custom action data for user: %ls", psu->wzKey); 518 ExitOnFailure(hr, "Failed to add user comment to custom action data: %ls", psu->wzComment);
504 519
505 // Check to see if the user already exists since we have to be very careful when adding 520 // Check to see if the user already exists since we have to be very careful when adding
506 // and removing users. Note: MSDN says that it is safe to call these APIs from any 521 // and removing users. Note: MSDN says that it is safe to call these APIs from any
@@ -520,7 +535,12 @@ HRESULT ScaUserExecute(
520 } 535 }
521 if (ERROR_SUCCESS == er) 536 if (ERROR_SUCCESS == er)
522 { 537 {
523 wzDomain = pDomainControllerInfo->DomainControllerName + 2; //Add 2 so that we don't get the \\ prefix 538 if (2 <= wcslen(pDomainControllerInfo->DomainControllerName))
539 {
540 wzDomain = pDomainControllerInfo->DomainControllerName + 2; // Add 2 so that we don't get the \\ prefix.
541 // Pass the entire string if it is too short
542 // to have a \\ prefix.
543 }
524 } 544 }
525 } 545 }
526 546
@@ -544,23 +564,32 @@ HRESULT ScaUserExecute(
544 564
545 if (WcaIsInstalling(psu->isInstalled, psu->isAction)) 565 if (WcaIsInstalling(psu->isInstalled, psu->isAction))
546 { 566 {
547 // If the user exists, check to see if we are supposed to fail if user the exists before 567 // If the user exists, check to see if we are supposed to fail if the user exists before
548 // the install. 568 // the install.
549 if (USER_EXISTS_YES == ueUserExists) 569 if (USER_EXISTS_YES == ueUserExists)
550 { 570 {
551 // Reinstalls will always fail if we don't remove the check for "fail if exists". 571 // Re-installs will always fail if we don't remove the check for "fail if exists".
552 if (WcaIsReInstalling(psu->isInstalled, psu->isAction)) 572 if (WcaIsReInstalling(psu->isInstalled, psu->isAction))
553 { 573 {
554 psu->iAttributes &= ~SCAU_FAIL_IF_EXISTS; 574 psu->iAttributes &= ~SCAU_FAIL_IF_EXISTS;
575
576 // If install would create the user, re-install should be able to update the user.
577 if (!(psu->iAttributes & SCAU_DONT_CREATE_USER))
578 {
579 psu->iAttributes |= SCAU_UPDATE_IF_EXISTS;
580 }
555 } 581 }
556 582
557 if ((SCAU_FAIL_IF_EXISTS & (psu->iAttributes)) && !(SCAU_UPDATE_IF_EXISTS & (psu->iAttributes))) 583 if (SCAU_FAIL_IF_EXISTS & psu->iAttributes && !(SCAU_UPDATE_IF_EXISTS & psu->iAttributes))
558 { 584 {
559 hr = HRESULT_FROM_WIN32(NERR_UserExists); 585 hr = HRESULT_FROM_WIN32(NERR_UserExists);
560 MessageExitOnFailure(hr, msierrUSRFailedUserCreateExists, "Failed to create user: %ls because user already exists.", psu->wzName); 586 MessageExitOnFailure(hr, msierrUSRFailedUserCreateExists, "Failed to create user: %ls because user already exists.", psu->wzName);
561 } 587 }
562 } 588 }
563 589
590 hr = WcaWriteIntegerToCaData(psu->iAttributes, &pwzActionData);
591 ExitOnFailure(hr, "failed to add user attributes to custom action data for user: %ls", psu->wzKey);
592
564 // Rollback only if the user already exists, we couldn't determine if the user exists, or we are going to create the user 593 // Rollback only if the user already exists, we couldn't determine if the user exists, or we are going to create the user
565 if ((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists) || !(psu->iAttributes & SCAU_DONT_CREATE_USER)) 594 if ((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists) || !(psu->iAttributes & SCAU_DONT_CREATE_USER))
566 { 595 {
@@ -597,7 +626,6 @@ HRESULT ScaUserExecute(
597 ExitOnFailure(hr, "Failed to add user domain to rollback custom action data: %ls", psu->wzDomain); 626 ExitOnFailure(hr, "Failed to add user domain to rollback custom action data: %ls", psu->wzDomain);
598 hr = WcaWriteIntegerToCaData(iRollbackUserAttributes, &pwzRollbackData); 627 hr = WcaWriteIntegerToCaData(iRollbackUserAttributes, &pwzRollbackData);
599 ExitOnFailure(hr, "failed to add user attributes to rollback custom action data for user: %ls", psu->wzKey); 628 ExitOnFailure(hr, "failed to add user attributes to rollback custom action data for user: %ls", psu->wzKey);
600
601 // If the user already exists, add relevant group information to rollback data 629 // If the user already exists, add relevant group information to rollback data
602 if (USER_EXISTS_YES == ueUserExists || USER_EXISTS_INDETERMINATE == ueUserExists) 630 if (USER_EXISTS_YES == ueUserExists || USER_EXISTS_INDETERMINATE == ueUserExists)
603 { 631 {
@@ -630,11 +658,13 @@ HRESULT ScaUserExecute(
630 } 658 }
631 else if (((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists)) && WcaIsUninstalling(psu->isInstalled, psu->isAction) && !(psu->iAttributes & SCAU_DONT_REMOVE_ON_UNINSTALL)) 659 else if (((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists)) && WcaIsUninstalling(psu->isInstalled, psu->isAction) && !(psu->iAttributes & SCAU_DONT_REMOVE_ON_UNINSTALL))
632 { 660 {
661 hr = WcaWriteIntegerToCaData(psu->iAttributes, &pwzActionData);
662 ExitOnFailure(hr, "failed to add user attributes to custom action data for user: %ls", psu->wzKey);
663
633 // Add user's group information - this will ensure the user can be removed from any groups they were added to, if the user isn't be deleted 664 // Add user's group information - this will ensure the user can be removed from any groups they were added to, if the user isn't be deleted
634 hr = WriteGroupInfo(psu->psgGroups, &pwzActionData); 665 hr = WriteGroupInfo(psu->psgGroups, &pwzActionData);
635 ExitOnFailure(hr, "failed to add group information to custom action data"); 666 ExitOnFailure(hr, "failed to add group information to custom action data");
636 667
637 //
638 // Schedule the removal because the user exists and we don't have any flags set 668 // Schedule the removal because the user exists and we don't have any flags set
639 // that say, don't remove the user on uninstall. 669 // that say, don't remove the user on uninstall.
640 // 670 //
@@ -642,7 +672,7 @@ HRESULT ScaUserExecute(
642 // CustomAction. 672 // CustomAction.
643 hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RemoveUser"), pwzActionData, COST_USER_DELETE); 673 hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RemoveUser"), pwzActionData, COST_USER_DELETE);
644 ExitOnFailure(hr, "failed to schedule RemoveUser"); 674 ExitOnFailure(hr, "failed to schedule RemoveUser");
645 } 675 }
646 676
647 ReleaseNullStr(pwzScriptKey); 677 ReleaseNullStr(pwzScriptKey);
648 ReleaseNullStr(pwzActionData); 678 ReleaseNullStr(pwzActionData);
diff --git a/src/ext/Util/ca/scauser.h b/src/ext/Util/ca/scauser.h
index a5fd5ea8..3da847b5 100644
--- a/src/ext/Util/ca/scauser.h
+++ b/src/ext/Util/ca/scauser.h
@@ -31,6 +31,7 @@ struct SCA_USER
31 WCHAR wzDomain[MAX_DARWIN_COLUMN + 1]; 31 WCHAR wzDomain[MAX_DARWIN_COLUMN + 1];
32 WCHAR wzName[MAX_DARWIN_COLUMN + 1]; 32 WCHAR wzName[MAX_DARWIN_COLUMN + 1];
33 WCHAR wzPassword[MAX_DARWIN_COLUMN + 1]; 33 WCHAR wzPassword[MAX_DARWIN_COLUMN + 1];
34 WCHAR wzComment[MAX_DARWIN_COLUMN + 1];
34 INT iAttributes; 35 INT iAttributes;
35 36
36 SCA_GROUP *psgGroups; 37 SCA_GROUP *psgGroups;
@@ -41,16 +42,16 @@ struct SCA_USER
41 42
42// prototypes 43// prototypes
43HRESULT __stdcall ScaGetUser( 44HRESULT __stdcall ScaGetUser(
44 __in LPCWSTR wzUser, 45 __in LPCWSTR wzUser,
45 __out SCA_USER* pscau 46 __out SCA_USER* pscau
46 ); 47 );
47HRESULT __stdcall ScaGetUserDeferred( 48HRESULT __stdcall ScaGetUserDeferred(
48 __in LPCWSTR wzUser, 49 __in LPCWSTR wzUser,
49 __in WCA_WRAPQUERY_HANDLE hUserQuery, 50 __in WCA_WRAPQUERY_HANDLE hUserQuery,
50 __out SCA_USER* pscau 51 __out SCA_USER* pscau
51 ); 52 );
52HRESULT __stdcall ScaGetGroup( 53HRESULT __stdcall ScaGetGroup(
53 __in LPCWSTR wzGroup, 54 __in LPCWSTR wzGroup,
54 __out SCA_GROUP* pscag 55 __out SCA_GROUP* pscag
55 ); 56 );
56void ScaUserFreeList( 57void ScaUserFreeList(
diff --git a/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/Package.wxs b/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/Package.wxs
new file mode 100644
index 00000000..af861986
--- /dev/null
+++ b/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/Package.wxs
@@ -0,0 +1,15 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="CreateUser" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
4
5 <Feature Id="ProductFeature" Title="MsiPackage">
6 <ComponentGroupRef Id="ProductComponents" />
7 </Feature>
8 </Package>
9
10 <Fragment>
11 <StandardDirectory Id="ProgramFilesFolder">
12 <Directory Id="INSTALLFOLDER" Name="CreateUser" />
13 </StandardDirectory>
14 </Fragment>
15</Wix>
diff --git a/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/PackageComponents.wxs b/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/PackageComponents.wxs
new file mode 100644
index 00000000..9fc7ba23
--- /dev/null
+++ b/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/PackageComponents.wxs
@@ -0,0 +1,76 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentRef Id="Component1" />
6 </ComponentGroup>
7 </Fragment>
8 <Fragment>
9 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
10 <util:User Id="TEST_USER00" Name="testName00" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="no" FailIfExists="no" Vital="no" RemoveOnUninstall="no" />
11 <util:User Id="TEST_USER01" Name="testName01" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="no" FailIfExists="no" Vital="no" RemoveOnUninstall="no" />
12 <util:User Id="TEST_USER02" Name="testName02" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="no" FailIfExists="no" Vital="no" RemoveOnUninstall="no" />
13 <util:User Id="TEST_USER03" Name="testName03" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="no" FailIfExists="no" Vital="no" RemoveOnUninstall="no" />
14 <util:User Id="TEST_USER04" Name="testName04" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="yes" FailIfExists="no" Vital="no" RemoveOnUninstall="no" />
15 <util:User Id="TEST_USER05" Name="testName05" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="yes" FailIfExists="no" Vital="no" RemoveOnUninstall="no" />
16 <util:User Id="TEST_USER06" Name="testName06" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="yes" FailIfExists="no" Vital="no" RemoveOnUninstall="no" />
17 <util:User Id="TEST_USER07" Name="testName07" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="yes" FailIfExists="no" Vital="no" RemoveOnUninstall="no" />
18 <util:User Id="TEST_USER10" Name="testName10" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="no" FailIfExists="yes" Vital="no" RemoveOnUninstall="no" />
19 <util:User Id="TEST_USER11" Name="testName11" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="no" FailIfExists="yes" Vital="no" RemoveOnUninstall="no" />
20 <util:User Id="TEST_USER12" Name="testName12" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="no" FailIfExists="yes" Vital="no" RemoveOnUninstall="no" />
21 <util:User Id="TEST_USER13" Name="testName13" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="no" FailIfExists="yes" Vital="no" RemoveOnUninstall="no" />
22 <util:User Id="TEST_USER14" Name="testName14" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="yes" FailIfExists="yes" Vital="no" RemoveOnUninstall="no" />
23 <util:User Id="TEST_USER15" Name="testName15" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="yes" FailIfExists="yes" Vital="no" RemoveOnUninstall="no" />
24 <util:User Id="TEST_USER16" Name="testName16" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="yes" FailIfExists="yes" Vital="no" RemoveOnUninstall="no" />
25 <util:User Id="TEST_USER17" Name="testName17" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="yes" FailIfExists="yes" Vital="no" RemoveOnUninstall="no" />
26 <util:User Id="TEST_USER20" Name="testName20" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="no" FailIfExists="no" Vital="yes" RemoveOnUninstall="no" />
27 <util:User Id="TEST_USER21" Name="testName21" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="no" FailIfExists="no" Vital="yes" RemoveOnUninstall="no" />
28 <util:User Id="TEST_USER22" Name="testName22" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="no" FailIfExists="no" Vital="yes" RemoveOnUninstall="no" />
29 <util:User Id="TEST_USER23" Name="testName23" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="no" FailIfExists="no" Vital="yes" RemoveOnUninstall="no" />
30 <util:User Id="TEST_USER24" Name="testName24" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="yes" FailIfExists="no" Vital="yes" RemoveOnUninstall="no" />
31 <util:User Id="TEST_USER25" Name="testName25" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="yes" FailIfExists="no" Vital="yes" RemoveOnUninstall="no" />
32 <util:User Id="TEST_USER26" Name="testName26" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="yes" FailIfExists="no" Vital="yes" RemoveOnUninstall="no" />
33 <util:User Id="TEST_USER27" Name="testName27" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="yes" FailIfExists="no" Vital="yes" RemoveOnUninstall="no" />
34 <util:User Id="TEST_USER30" Name="testName30" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="no" FailIfExists="yes" Vital="yes" RemoveOnUninstall="no" />
35 <util:User Id="TEST_USER31" Name="testName31" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="no" FailIfExists="yes" Vital="yes" RemoveOnUninstall="no" />
36 <util:User Id="TEST_USER32" Name="testName32" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="no" FailIfExists="yes" Vital="yes" RemoveOnUninstall="no" />
37 <util:User Id="TEST_USER33" Name="testName33" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="no" FailIfExists="yes" Vital="yes" RemoveOnUninstall="no" />
38 <util:User Id="TEST_USER34" Name="testName34" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="yes" FailIfExists="yes" Vital="yes" RemoveOnUninstall="no" />
39 <util:User Id="TEST_USER35" Name="testName35" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="yes" FailIfExists="yes" Vital="yes" RemoveOnUninstall="no" />
40 <util:User Id="TEST_USER36" Name="testName36" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="yes" FailIfExists="yes" Vital="yes" RemoveOnUninstall="no" />
41 <util:User Id="TEST_USER37" Name="testName37" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="yes" FailIfExists="yes" Vital="yes" RemoveOnUninstall="no" />
42 <util:User Id="TEST_USER40" Name="testName40" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="no" FailIfExists="no" Vital="no" RemoveOnUninstall="yes"/>
43 <util:User Id="TEST_USER41" Name="testName41" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="no" FailIfExists="no" Vital="no" RemoveOnUninstall="yes"/>
44 <util:User Id="TEST_USER42" Name="testName42" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="no" FailIfExists="no" Vital="no" RemoveOnUninstall="yes"/>
45 <util:User Id="TEST_USER43" Name="testName43" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="no" FailIfExists="no" Vital="no" RemoveOnUninstall="yes"/>
46 <util:User Id="TEST_USER44" Name="testName44" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="yes" FailIfExists="no" Vital="no" RemoveOnUninstall="yes"/>
47 <util:User Id="TEST_USER45" Name="testName45" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="yes" FailIfExists="no" Vital="no" RemoveOnUninstall="yes"/>
48 <util:User Id="TEST_USER46" Name="testName46" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="yes" FailIfExists="no" Vital="no" RemoveOnUninstall="yes"/>
49 <util:User Id="TEST_USER47" Name="testName47" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="yes" FailIfExists="no" Vital="no" RemoveOnUninstall="yes"/>
50 <util:User Id="TEST_USER50" Name="testName50" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="no" FailIfExists="yes" Vital="no" RemoveOnUninstall="yes"/>
51 <util:User Id="TEST_USER51" Name="testName51" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="no" FailIfExists="yes" Vital="no" RemoveOnUninstall="yes"/>
52 <util:User Id="TEST_USER52" Name="testName52" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="no" FailIfExists="yes" Vital="no" RemoveOnUninstall="yes"/>
53 <util:User Id="TEST_USER53" Name="testName53" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="no" FailIfExists="yes" Vital="no" RemoveOnUninstall="yes"/>
54 <util:User Id="TEST_USER54" Name="testName54" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="yes" FailIfExists="yes" Vital="no" RemoveOnUninstall="yes"/>
55 <util:User Id="TEST_USER55" Name="testName55" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="yes" FailIfExists="yes" Vital="no" RemoveOnUninstall="yes"/>
56 <util:User Id="TEST_USER56" Name="testName56" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="yes" FailIfExists="yes" Vital="no" RemoveOnUninstall="yes"/>
57 <util:User Id="TEST_USER57" Name="testName57" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="yes" FailIfExists="yes" Vital="no" RemoveOnUninstall="yes"/>
58 <util:User Id="TEST_USER60" Name="testName60" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="no" FailIfExists="no" Vital="yes" RemoveOnUninstall="yes"/>
59 <util:User Id="TEST_USER61" Name="testName61" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="no" FailIfExists="no" Vital="yes" RemoveOnUninstall="yes"/>
60 <util:User Id="TEST_USER62" Name="testName62" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="no" FailIfExists="no" Vital="yes" RemoveOnUninstall="yes"/>
61 <util:User Id="TEST_USER63" Name="testName63" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="no" FailIfExists="no" Vital="yes" RemoveOnUninstall="yes"/>
62 <util:User Id="TEST_USER64" Name="testName64" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="yes" FailIfExists="no" Vital="yes" RemoveOnUninstall="yes"/>
63 <util:User Id="TEST_USER65" Name="testName65" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="yes" FailIfExists="no" Vital="yes" RemoveOnUninstall="yes"/>
64 <util:User Id="TEST_USER66" Name="testName66" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="yes" FailIfExists="no" Vital="yes" RemoveOnUninstall="yes"/>
65 <util:User Id="TEST_USER67" Name="testName67" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="yes" FailIfExists="no" Vital="yes" RemoveOnUninstall="yes"/>
66 <util:User Id="TEST_USER70" Name="testName70" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="no" FailIfExists="yes" Vital="yes" RemoveOnUninstall="yes"/>
67 <util:User Id="TEST_USER71" Name="testName71" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="no" FailIfExists="yes" Vital="yes" RemoveOnUninstall="yes"/>
68 <util:User Id="TEST_USER72" Name="testName72" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="no" FailIfExists="yes" Vital="yes" RemoveOnUninstall="yes"/>
69 <util:User Id="TEST_USER73" Name="testName73" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="no" FailIfExists="yes" Vital="yes" RemoveOnUninstall="yes"/>
70 <util:User Id="TEST_USER74" Name="testName74" Password="test123!@#" PasswordExpired="no" Comment="Test Comment 1" CreateUser="yes" FailIfExists="yes" Vital="yes" RemoveOnUninstall="yes"/>
71 <util:User Id="TEST_USER75" Name="testName75" Password="test123!@#" PasswordExpired="yes" Comment="Test Comment 1" CreateUser="yes" FailIfExists="yes" Vital="yes" RemoveOnUninstall="yes"/>
72 <util:User Id="TEST_USER76" Name="testName76" Password="test123!@#" PasswordExpired="no" RemoveComment="yes" CreateUser="yes" FailIfExists="yes" Vital="yes" RemoveOnUninstall="yes"/>
73 <util:User Id="TEST_USER77" Name="testName77" Password="test123!@#" PasswordExpired="yes" RemoveComment="yes" CreateUser="yes" FailIfExists="yes" Vital="yes" RemoveOnUninstall="yes"/>
74 </Component>
75 </Fragment>
76</Wix>
diff --git a/src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs b/src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs
index 24641fce..3da5f671 100644
--- a/src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs
+++ b/src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs
@@ -297,6 +297,82 @@ namespace WixToolsetTest.Util
297 } 297 }
298 298
299 [Fact] 299 [Fact]
300 public void CanCreateUserAccountWithComment()
301 {
302 var folder = TestData.Get(@"TestData\CreateUser");
303 var build = new Builder(folder, typeof(UtilExtensionFactory), new[] { folder });
304
305 var results = build.BuildAndQuery(Build, "Wix4User");
306 WixAssert.CompareLineByLine(new[]
307 {
308 "Wix4User:TEST_USER00\tComponent1\ttestName00\t\ttest123!@#\tTest Comment 1\t1792",
309 "Wix4User:TEST_USER01\tComponent1\ttestName01\t\ttest123!@#\tTest Comment 1\t1796",
310 "Wix4User:TEST_USER02\tComponent1\ttestName02\t\ttest123!@#\t\t3840",
311 "Wix4User:TEST_USER03\tComponent1\ttestName03\t\ttest123!@#\t\t3844",
312 "Wix4User:TEST_USER04\tComponent1\ttestName04\t\ttest123!@#\tTest Comment 1\t1280",
313 "Wix4User:TEST_USER05\tComponent1\ttestName05\t\ttest123!@#\tTest Comment 1\t1284",
314 "Wix4User:TEST_USER06\tComponent1\ttestName06\t\ttest123!@#\t\t3328",
315 "Wix4User:TEST_USER07\tComponent1\ttestName07\t\ttest123!@#\t\t3332",
316 "Wix4User:TEST_USER10\tComponent1\ttestName10\t\ttest123!@#\tTest Comment 1\t1808",
317 "Wix4User:TEST_USER11\tComponent1\ttestName11\t\ttest123!@#\tTest Comment 1\t1812",
318 "Wix4User:TEST_USER12\tComponent1\ttestName12\t\ttest123!@#\t\t3856",
319 "Wix4User:TEST_USER13\tComponent1\ttestName13\t\ttest123!@#\t\t3860",
320 "Wix4User:TEST_USER14\tComponent1\ttestName14\t\ttest123!@#\tTest Comment 1\t1296",
321 "Wix4User:TEST_USER15\tComponent1\ttestName15\t\ttest123!@#\tTest Comment 1\t1300",
322 "Wix4User:TEST_USER16\tComponent1\ttestName16\t\ttest123!@#\t\t3344",
323 "Wix4User:TEST_USER17\tComponent1\ttestName17\t\ttest123!@#\t\t3348",
324 "Wix4User:TEST_USER20\tComponent1\ttestName20\t\ttest123!@#\tTest Comment 1\t768",
325 "Wix4User:TEST_USER21\tComponent1\ttestName21\t\ttest123!@#\tTest Comment 1\t772",
326 "Wix4User:TEST_USER22\tComponent1\ttestName22\t\ttest123!@#\t\t2816",
327 "Wix4User:TEST_USER23\tComponent1\ttestName23\t\ttest123!@#\t\t2820",
328 "Wix4User:TEST_USER24\tComponent1\ttestName24\t\ttest123!@#\tTest Comment 1\t256",
329 "Wix4User:TEST_USER25\tComponent1\ttestName25\t\ttest123!@#\tTest Comment 1\t260",
330 "Wix4User:TEST_USER26\tComponent1\ttestName26\t\ttest123!@#\t\t2304",
331 "Wix4User:TEST_USER27\tComponent1\ttestName27\t\ttest123!@#\t\t2308",
332 "Wix4User:TEST_USER30\tComponent1\ttestName30\t\ttest123!@#\tTest Comment 1\t784",
333 "Wix4User:TEST_USER31\tComponent1\ttestName31\t\ttest123!@#\tTest Comment 1\t788",
334 "Wix4User:TEST_USER32\tComponent1\ttestName32\t\ttest123!@#\t\t2832",
335 "Wix4User:TEST_USER33\tComponent1\ttestName33\t\ttest123!@#\t\t2836",
336 "Wix4User:TEST_USER34\tComponent1\ttestName34\t\ttest123!@#\tTest Comment 1\t272",
337 "Wix4User:TEST_USER35\tComponent1\ttestName35\t\ttest123!@#\tTest Comment 1\t276",
338 "Wix4User:TEST_USER36\tComponent1\ttestName36\t\ttest123!@#\t\t2320",
339 "Wix4User:TEST_USER37\tComponent1\ttestName37\t\ttest123!@#\t\t2324",
340 "Wix4User:TEST_USER40\tComponent1\ttestName40\t\ttest123!@#\tTest Comment 1\t1536",
341 "Wix4User:TEST_USER41\tComponent1\ttestName41\t\ttest123!@#\tTest Comment 1\t1540",
342 "Wix4User:TEST_USER42\tComponent1\ttestName42\t\ttest123!@#\t\t3584",
343 "Wix4User:TEST_USER43\tComponent1\ttestName43\t\ttest123!@#\t\t3588",
344 "Wix4User:TEST_USER44\tComponent1\ttestName44\t\ttest123!@#\tTest Comment 1\t1024",
345 "Wix4User:TEST_USER45\tComponent1\ttestName45\t\ttest123!@#\tTest Comment 1\t1028",
346 "Wix4User:TEST_USER46\tComponent1\ttestName46\t\ttest123!@#\t\t3072",
347 "Wix4User:TEST_USER47\tComponent1\ttestName47\t\ttest123!@#\t\t3076",
348 "Wix4User:TEST_USER50\tComponent1\ttestName50\t\ttest123!@#\tTest Comment 1\t1552",
349 "Wix4User:TEST_USER51\tComponent1\ttestName51\t\ttest123!@#\tTest Comment 1\t1556",
350 "Wix4User:TEST_USER52\tComponent1\ttestName52\t\ttest123!@#\t\t3600",
351 "Wix4User:TEST_USER53\tComponent1\ttestName53\t\ttest123!@#\t\t3604",
352 "Wix4User:TEST_USER54\tComponent1\ttestName54\t\ttest123!@#\tTest Comment 1\t1040",
353 "Wix4User:TEST_USER55\tComponent1\ttestName55\t\ttest123!@#\tTest Comment 1\t1044",
354 "Wix4User:TEST_USER56\tComponent1\ttestName56\t\ttest123!@#\t\t3088",
355 "Wix4User:TEST_USER57\tComponent1\ttestName57\t\ttest123!@#\t\t3092",
356 "Wix4User:TEST_USER60\tComponent1\ttestName60\t\ttest123!@#\tTest Comment 1\t512",
357 "Wix4User:TEST_USER61\tComponent1\ttestName61\t\ttest123!@#\tTest Comment 1\t516",
358 "Wix4User:TEST_USER62\tComponent1\ttestName62\t\ttest123!@#\t\t2560",
359 "Wix4User:TEST_USER63\tComponent1\ttestName63\t\ttest123!@#\t\t2564",
360 "Wix4User:TEST_USER64\tComponent1\ttestName64\t\ttest123!@#\tTest Comment 1\t0",
361 "Wix4User:TEST_USER65\tComponent1\ttestName65\t\ttest123!@#\tTest Comment 1\t4",
362 "Wix4User:TEST_USER66\tComponent1\ttestName66\t\ttest123!@#\t\t2048",
363 "Wix4User:TEST_USER67\tComponent1\ttestName67\t\ttest123!@#\t\t2052",
364 "Wix4User:TEST_USER70\tComponent1\ttestName70\t\ttest123!@#\tTest Comment 1\t528",
365 "Wix4User:TEST_USER71\tComponent1\ttestName71\t\ttest123!@#\tTest Comment 1\t532",
366 "Wix4User:TEST_USER72\tComponent1\ttestName72\t\ttest123!@#\t\t2576",
367 "Wix4User:TEST_USER73\tComponent1\ttestName73\t\ttest123!@#\t\t2580",
368 "Wix4User:TEST_USER74\tComponent1\ttestName74\t\ttest123!@#\tTest Comment 1\t16",
369 "Wix4User:TEST_USER75\tComponent1\ttestName75\t\ttest123!@#\tTest Comment 1\t20",
370 "Wix4User:TEST_USER76\tComponent1\ttestName76\t\ttest123!@#\t\t2064",
371 "Wix4User:TEST_USER77\tComponent1\ttestName77\t\ttest123!@#\t\t2068",
372 }, results.OrderBy(s => s).ToArray());
373 }
374
375 [Fact]
300 public void CanBuildBundleWithWarningsWithSearchesUsingDiscouragedVariableNames() 376 public void CanBuildBundleWithWarningsWithSearchesUsingDiscouragedVariableNames()
301 { 377 {
302 var folder = TestData.Get("TestData", "BundleWithSearches"); 378 var folder = TestData.Get("TestData", "BundleWithSearches");
diff --git a/src/ext/Util/wixext/Symbols/UserSymbol.cs b/src/ext/Util/wixext/Symbols/UserSymbol.cs
index 5f00064b..6ea810de 100644
--- a/src/ext/Util/wixext/Symbols/UserSymbol.cs
+++ b/src/ext/Util/wixext/Symbols/UserSymbol.cs
@@ -15,6 +15,7 @@ namespace WixToolset.Util
15 new IntermediateFieldDefinition(nameof(UserSymbolFields.Name), IntermediateFieldType.String), 15 new IntermediateFieldDefinition(nameof(UserSymbolFields.Name), IntermediateFieldType.String),
16 new IntermediateFieldDefinition(nameof(UserSymbolFields.Domain), IntermediateFieldType.String), 16 new IntermediateFieldDefinition(nameof(UserSymbolFields.Domain), IntermediateFieldType.String),
17 new IntermediateFieldDefinition(nameof(UserSymbolFields.Password), IntermediateFieldType.String), 17 new IntermediateFieldDefinition(nameof(UserSymbolFields.Password), IntermediateFieldType.String),
18 new IntermediateFieldDefinition(nameof(UserSymbolFields.Comment), IntermediateFieldType.String),
18 new IntermediateFieldDefinition(nameof(UserSymbolFields.Attributes), IntermediateFieldType.Number), 19 new IntermediateFieldDefinition(nameof(UserSymbolFields.Attributes), IntermediateFieldType.Number),
19 }, 20 },
20 typeof(UserSymbol)); 21 typeof(UserSymbol));
@@ -31,6 +32,7 @@ namespace WixToolset.Util.Symbols
31 Name, 32 Name,
32 Domain, 33 Domain,
33 Password, 34 Password,
35 Comment,
34 Attributes, 36 Attributes,
35 } 37 }
36 38
@@ -70,10 +72,16 @@ namespace WixToolset.Util.Symbols
70 set => this.Set((int)UserSymbolFields.Password, value); 72 set => this.Set((int)UserSymbolFields.Password, value);
71 } 73 }
72 74
75 public string Comment
76 {
77 get => this.Fields[(int)UserSymbolFields.Comment].AsString();
78 set => this.Set((int)UserSymbolFields.Comment, value);
79 }
80
73 public int Attributes 81 public int Attributes
74 { 82 {
75 get => this.Fields[(int)UserSymbolFields.Attributes].AsNumber(); 83 get => this.Fields[(int)UserSymbolFields.Attributes].AsNumber();
76 set => this.Set((int)UserSymbolFields.Attributes, value); 84 set => this.Set((int)UserSymbolFields.Attributes, value);
77 } 85 }
78 } 86 }
79} \ No newline at end of file 87}
diff --git a/src/ext/Util/wixext/UtilCompiler.cs b/src/ext/Util/wixext/UtilCompiler.cs
index a6e4b835..d937b4f1 100644
--- a/src/ext/Util/wixext/UtilCompiler.cs
+++ b/src/ext/Util/wixext/UtilCompiler.cs
@@ -34,6 +34,7 @@ namespace WixToolset.Util
34 internal const int UserDontRemoveOnUninstall = 0x00000100; 34 internal const int UserDontRemoveOnUninstall = 0x00000100;
35 internal const int UserDontCreateUser = 0x00000200; 35 internal const int UserDontCreateUser = 0x00000200;
36 internal const int UserNonVital = 0x00000400; 36 internal const int UserNonVital = 0x00000400;
37 internal const int UserRemoveComment = 0x00000800;
37 38
38 private static readonly Regex FindPropertyBrackets = new Regex(@"\[(?!\\|\])|(?<!\[\\\]|\[\\|\\\[)\]", RegexOptions.ExplicitCapture | RegexOptions.Compiled); 39 private static readonly Regex FindPropertyBrackets = new Regex(@"\[(?!\\|\])|(?<!\[\\\]|\[\\|\\\[)\]", RegexOptions.ExplicitCapture | RegexOptions.Compiled);
39 40
@@ -3251,6 +3252,7 @@ namespace WixToolset.Util
3251 int attributes = 0; 3252 int attributes = 0;
3252 string domain = null; 3253 string domain = null;
3253 string name = null; 3254 string name = null;
3255 string comment = null;
3254 string password = null; 3256 string password = null;
3255 3257
3256 foreach (var attrib in element.Attributes()) 3258 foreach (var attrib in element.Attributes())
@@ -3273,6 +3275,14 @@ namespace WixToolset.Util
3273 attributes |= UserPasswdCantChange; 3275 attributes |= UserPasswdCantChange;
3274 } 3276 }
3275 break; 3277 break;
3278 case "Comment":
3279 if (null == componentId)
3280 {
3281 this.Messaging.Write(UtilErrors.IllegalAttributeWithoutComponent(sourceLineNumbers, element.Name.LocalName, attrib.Name.LocalName));
3282 }
3283
3284 comment = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
3285 break;
3276 case "CreateUser": 3286 case "CreateUser":
3277 if (null == componentId) 3287 if (null == componentId)
3278 { 3288 {
@@ -3357,6 +3367,12 @@ namespace WixToolset.Util
3357 attributes |= UserDontExpirePasswrd; 3367 attributes |= UserDontExpirePasswrd;
3358 } 3368 }
3359 break; 3369 break;
3370 case "RemoveComment":
3371 if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
3372 {
3373 attributes |= UserRemoveComment;
3374 }
3375 break;
3360 case "RemoveOnUninstall": 3376 case "RemoveOnUninstall":
3361 if (null == componentId) 3377 if (null == componentId)
3362 { 3378 {
@@ -3411,6 +3427,11 @@ namespace WixToolset.Util
3411 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name")); 3427 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name"));
3412 } 3428 }
3413 3429
3430 if (null != comment && (UserRemoveComment & attributes) != 0)
3431 {
3432 this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "Comment", "RemoveComment"));
3433 }
3434
3414 foreach (var child in element.Elements()) 3435 foreach (var child in element.Elements())
3415 { 3436 {
3416 if (this.Namespace == child.Name.Namespace) 3437 if (this.Namespace == child.Name.Namespace)
@@ -3450,6 +3471,7 @@ namespace WixToolset.Util
3450 Name = name, 3471 Name = name,
3451 Domain = domain, 3472 Domain = domain,
3452 Password = password, 3473 Password = password,
3474 Comment = comment,
3453 Attributes = attributes, 3475 Attributes = attributes,
3454 }); 3476 });
3455 } 3477 }
diff --git a/src/ext/Util/wixext/UtilDecompiler.cs b/src/ext/Util/wixext/UtilDecompiler.cs
index 7d95fcef..1201fdd5 100644
--- a/src/ext/Util/wixext/UtilDecompiler.cs
+++ b/src/ext/Util/wixext/UtilDecompiler.cs
@@ -559,13 +559,14 @@ namespace WixToolset.Util
559 { 559 {
560 foreach (var row in table.Rows) 560 foreach (var row in table.Rows)
561 { 561 {
562 var attributes = row.FieldAsNullableInteger(5) ?? 0; 562 var attributes = row.FieldAsNullableInteger(6) ?? 0;
563 563
564 var user = new XElement(UtilConstants.UserName, 564 var user = new XElement(UtilConstants.UserName,
565 new XAttribute("Id", row.FieldAsString(0)), 565 new XAttribute("Id", row.FieldAsString(0)),
566 new XAttribute("Name", row.FieldAsString(2)), 566 new XAttribute("Name", row.FieldAsString(2)),
567 AttributeIfNotNull("Domain", row, 3), 567 AttributeIfNotNull("Domain", row, 3),
568 AttributeIfNotNull("Password", row, 4), 568 AttributeIfNotNull("Password", row, 4),
569 AttributeIfNotNull("Comment", row, 5),
569 AttributeIfTrue("PasswordNeverExpires", UtilCompiler.UserDontExpirePasswrd == (attributes & UtilCompiler.UserDontExpirePasswrd)), 570 AttributeIfTrue("PasswordNeverExpires", UtilCompiler.UserDontExpirePasswrd == (attributes & UtilCompiler.UserDontExpirePasswrd)),
570 AttributeIfTrue("CanNotChangePassword", UtilCompiler.UserPasswdCantChange == (attributes & UtilCompiler.UserPasswdCantChange)), 571 AttributeIfTrue("CanNotChangePassword", UtilCompiler.UserPasswdCantChange == (attributes & UtilCompiler.UserPasswdCantChange)),
571 AttributeIfTrue("PasswordExpired", UtilCompiler.UserPasswdChangeReqdOnLogin == (attributes & UtilCompiler.UserPasswdChangeReqdOnLogin)), 572 AttributeIfTrue("PasswordExpired", UtilCompiler.UserPasswdChangeReqdOnLogin == (attributes & UtilCompiler.UserPasswdChangeReqdOnLogin)),
@@ -573,7 +574,8 @@ namespace WixToolset.Util
573 AttributeIfTrue("FailIfExists", UtilCompiler.UserFailIfExists == (attributes & UtilCompiler.UserFailIfExists)), 574 AttributeIfTrue("FailIfExists", UtilCompiler.UserFailIfExists == (attributes & UtilCompiler.UserFailIfExists)),
574 AttributeIfTrue("UpdateIfExists", UtilCompiler.UserUpdateIfExists == (attributes & UtilCompiler.UserUpdateIfExists)), 575 AttributeIfTrue("UpdateIfExists", UtilCompiler.UserUpdateIfExists == (attributes & UtilCompiler.UserUpdateIfExists)),
575 AttributeIfTrue("LogonAsService", UtilCompiler.UserLogonAsService == (attributes & UtilCompiler.UserLogonAsService)), 576 AttributeIfTrue("LogonAsService", UtilCompiler.UserLogonAsService == (attributes & UtilCompiler.UserLogonAsService)),
576 AttributeIfTrue("LogonAsService", UtilCompiler.UserLogonAsService == (attributes & UtilCompiler.UserLogonAsService)) 577 AttributeIfTrue("LogonAsBatchJob", UtilCompiler.UserLogonAsBatchJob == (attributes & UtilCompiler.UserLogonAsBatchJob)),
578 AttributeIfTrue("RemoveComment", UtilCompiler.UserRemoveComment == (attributes & UtilCompiler.UserRemoveComment))
577 ); 579 );
578 580
579 if (UtilCompiler.UserDontRemoveOnUninstall == (attributes & UtilCompiler.UserDontRemoveOnUninstall)) 581 if (UtilCompiler.UserDontRemoveOnUninstall == (attributes & UtilCompiler.UserDontRemoveOnUninstall))
diff --git a/src/ext/Util/wixext/UtilTableDefinitions.cs b/src/ext/Util/wixext/UtilTableDefinitions.cs
index 57c18b6c..33e6d3d1 100644
--- a/src/ext/Util/wixext/UtilTableDefinitions.cs
+++ b/src/ext/Util/wixext/UtilTableDefinitions.cs
@@ -229,6 +229,7 @@ namespace WixToolset.Util
229 new ColumnDefinition("Name", ColumnType.String, 255, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "User name", modularizeType: ColumnModularizeType.Property), 229 new ColumnDefinition("Name", ColumnType.String, 255, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "User name", modularizeType: ColumnModularizeType.Property),
230 new ColumnDefinition("Domain", ColumnType.String, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "User domain", modularizeType: ColumnModularizeType.Property), 230 new ColumnDefinition("Domain", ColumnType.String, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "User domain", modularizeType: ColumnModularizeType.Property),
231 new ColumnDefinition("Password", ColumnType.String, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "User password", modularizeType: ColumnModularizeType.Property), 231 new ColumnDefinition("Password", ColumnType.String, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "User password", modularizeType: ColumnModularizeType.Property),
232 new ColumnDefinition("Comment", ColumnType.String, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "User comment", modularizeType: ColumnModularizeType.Property),
232 new ColumnDefinition("Attributes", ColumnType.Number, 4, primaryKey: false, nullable: true, ColumnCategory.Unknown, minValue: 0, maxValue: 65535, description: "Attributes describing how to create the user"), 233 new ColumnDefinition("Attributes", ColumnType.Number, 4, primaryKey: false, nullable: true, ColumnCategory.Unknown, minValue: 0, maxValue: 65535, description: "Attributes describing how to create the user"),
233 }, 234 },
234 symbolIdIsPrimaryKey: true 235 symbolIdIsPrimaryKey: true
diff --git a/src/libs/wcautil/WixToolset.WcaUtil/wcawrap.cpp b/src/libs/wcautil/WixToolset.WcaUtil/wcawrap.cpp
index 2b68f36f..86b7602e 100644
--- a/src/libs/wcautil/WixToolset.WcaUtil/wcawrap.cpp
+++ b/src/libs/wcautil/WixToolset.WcaUtil/wcawrap.cpp
@@ -906,7 +906,7 @@ static void RevealNulls(
906 906
907 907
908/******************************************************************** 908/********************************************************************
909WcaGetRecordFormattedString() - gets formatted string filed from record 909WcaGetRecordFormattedString() - gets formatted string field from record
910 910
911********************************************************************/ 911********************************************************************/
912extern "C" HRESULT WIXAPI WcaGetRecordFormattedString( 912extern "C" HRESULT WIXAPI WcaGetRecordFormattedString(
@@ -1391,9 +1391,14 @@ extern "C" HRESULT WIXAPI WcaWriteIntegerToCaData(
1391 ) 1391 )
1392{ 1392{
1393 WCHAR wzBuffer[13]; 1393 WCHAR wzBuffer[13];
1394 StringCchPrintfW(wzBuffer, countof(wzBuffer), L"%d", i); 1394 HRESULT hr = StringCchPrintfW(wzBuffer, countof(wzBuffer), L"%d", i);
1395 ExitOnFailure(hr, "failed to write integer to ca data");
1395 1396
1396 return WcaWriteStringToCaData(wzBuffer, ppwzCustomActionData); 1397 hr = WcaWriteStringToCaData(wzBuffer, ppwzCustomActionData);
1398 ExitOnFailure(hr, "failed to write integer to ca data");
1399
1400LExit:
1401 return hr;
1397} 1402}
1398 1403
1399 1404
diff --git a/src/test/burn/WixTestTools/MSIExec.cs b/src/test/burn/WixTestTools/MSIExec.cs
index a905ec5a..fb161495 100644
--- a/src/test/burn/WixTestTools/MSIExec.cs
+++ b/src/test/burn/WixTestTools/MSIExec.cs
@@ -259,7 +259,7 @@ namespace WixTestTools
259 arguments.Append(" /a "); 259 arguments.Append(" /a ");
260 break; 260 break;
261 case MSIExecMode.Repair: 261 case MSIExecMode.Repair:
262 arguments.Append(" /f "); 262 arguments.Append(" /fvomusa ");
263 break; 263 break;
264 case MSIExecMode.Cleanup: 264 case MSIExecMode.Cleanup:
265 case MSIExecMode.Uninstall: 265 case MSIExecMode.Uninstall:
diff --git a/src/test/burn/WixTestTools/UserVerifier.cs b/src/test/burn/WixTestTools/UserVerifier.cs
index b5218a79..51c6c31e 100644
--- a/src/test/burn/WixTestTools/UserVerifier.cs
+++ b/src/test/burn/WixTestTools/UserVerifier.cs
@@ -49,7 +49,7 @@ namespace WixTestTools
49 UserPrincipal newUser = new UserPrincipal(new PrincipalContext(ContextType.Machine)); 49 UserPrincipal newUser = new UserPrincipal(new PrincipalContext(ContextType.Machine));
50 newUser.SetPassword(password); 50 newUser.SetPassword(password);
51 newUser.Name = userName; 51 newUser.Name = userName;
52 newUser.Description = "New test User"; 52 newUser.Description = String.Empty;
53 newUser.UserCannotChangePassword = true; 53 newUser.UserCannotChangePassword = true;
54 newUser.PasswordNeverExpires = false; 54 newUser.PasswordNeverExpires = false;
55 newUser.Save(); 55 newUser.Save();
@@ -109,6 +109,24 @@ namespace WixTestTools
109 } 109 }
110 110
111 /// <summary> 111 /// <summary>
112 /// Sets the user comment for a given user
113 /// </summary>
114 /// <param name="domainName">domain name for the user, empty for local users</param>
115 /// <param name="userName">the user name</param>
116 /// <param name="comment">comment to be set for the user</param>
117 public static void SetUserComment(string domainName, string userName, string comment)
118 {
119 UserPrincipal user = GetUser(domainName, userName);
120
121 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
122
123 var directoryEntry = user.GetUnderlyingObject() as DirectoryEntry;
124 Assert.False(null == directoryEntry);
125 directoryEntry.Properties["Description"].Value = comment;
126 user.Save();
127 }
128
129 /// <summary>
112 /// Adds the specified user to the specified local group 130 /// Adds the specified user to the specified local group
113 /// </summary> 131 /// </summary>
114 /// <param name="userName">User to add</param> 132 /// <param name="userName">User to add</param>
@@ -162,7 +180,24 @@ namespace WixTestTools
162 } 180 }
163 181
164 /// <summary> 182 /// <summary>
165 /// Verify that a givin user is member of a local group 183 /// Verifies the user comment for a given user
184 /// </summary>
185 /// <param name="domainName">domain name for the user, empty for local users</param>
186 /// <param name="userName">the user name</param>
187 /// <param name="comment">the comment to be verified</param>
188 public static void VerifyUserComment(string domainName, string userName, string comment)
189 {
190 UserPrincipal user = GetUser(domainName, userName);
191
192 Assert.False(null == user, String.Format("User '{0}' was not found under domain '{1}'.", userName, domainName));
193
194 var directoryEntry = user.GetUnderlyingObject() as DirectoryEntry;
195 Assert.False(null == directoryEntry);
196 Assert.True(comment == (string)(directoryEntry.Properties["Description"].Value));
197 }
198
199 /// <summary>
200 /// Verify that a given user is member of a local group
166 /// </summary> 201 /// </summary>
167 /// <param name="domainName">domain name for the user, empty for local users</param> 202 /// <param name="domainName">domain name for the user, empty for local users</param>
168 /// <param name="userName">the user name</param> 203 /// <param name="userName">the user name</param>
@@ -322,7 +357,6 @@ namespace WixTestTools
322 missedAGroup = true; 357 missedAGroup = true;
323 message += String.Format("Local group '{0}' was not found. \r\n", groupName); 358 message += String.Format("Local group '{0}' was not found. \r\n", groupName);
324 } 359 }
325
326 } 360 }
327 Assert.False(missedAGroup, message); 361 Assert.False(missedAGroup, message);
328 } 362 }
@@ -346,3 +380,4 @@ namespace WixTestTools
346 } 380 }
347 } 381 }
348} 382}
383
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj
index fbc6f292..3895b853 100644
--- a/src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj
@@ -1,7 +1,7 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> 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"> 2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup> 3 <PropertyGroup>
4 <UpgradeCode>{1A1795A6-87C0-4A9A-ABD5-DF9BED697037}</UpgradeCode> 4 <UpgradeCode>{A3E0B539-63F9-4B43-9E34-F33AE1C6E06D}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef> 5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup> 6 </PropertyGroup>
7 <ItemGroup> 7 <ItemGroup>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs
index a7bec54e..be6e16a4 100644
--- a/src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs
@@ -15,10 +15,10 @@
15 <util:Group Id="ADMIN" Name="Administrators" /> 15 <util:Group Id="ADMIN" Name="Administrators" />
16 <util:Group Id="POWER_USER" Name="Power Users" /> 16 <util:Group Id="POWER_USER" Name="Power Users" />
17 17
18 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER"> 18 <Component Id="Component1" Guid="09624A9A-4BBC-4126-BBF9-0713C5217DB1" Directory="INSTALLFOLDER">
19 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" /> 19 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
20 20
21 <util:User Id="TEST_USER1" Name="testName1" Password="test123!@#" PasswordExpired="yes"> 21 <util:User Id="TEST_USER1" Name="testName1" Password="test123!@#" PasswordExpired="yes" CreateUser="yes" RemoveOnUninstall="yes">
22 <util:GroupRef Id="ADMIN" /> 22 <util:GroupRef Id="ADMIN" />
23 <util:GroupRef Id="POWER_USER" /> 23 <util:GroupRef Id="POWER_USER" />
24 </util:User> 24 </util:User>
@@ -27,7 +27,7 @@
27 <util:GroupRef Id="POWER_USER" /> 27 <util:GroupRef Id="POWER_USER" />
28 </util:User> 28 </util:User>
29 29
30 <util:User Id="TEST_USER3" Name="[TEMPUSERNAME]" Domain="[TEMPDOMAIN]" CreateUser="no"> 30 <util:User Id="TEST_USER3" Name="testName3" CreateUser="no">
31 <util:GroupRef Id="POWER_USER" /> 31 <util:GroupRef Id="POWER_USER" />
32 </util:User> 32 </util:User>
33 </Component> 33 </Component>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/ProductAddCommentToExistingUser.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/ProductAddCommentToExistingUser.wixproj
new file mode 100644
index 00000000..5938e525
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/ProductAddCommentToExistingUser.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>{B33D3140-4AA5-469D-9DEE-AAF8F0C626DA}</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>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/product.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/product.wxs
new file mode 100644
index 00000000..ce8c4cae
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/product.wxs
@@ -0,0 +1,25 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
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 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
13 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
14
15 <util:User Id="TEST_USER1"
16 Name="testName1"
17 Password="test123!@#"
18 PasswordExpired="yes"
19 CreateUser="yes"
20 UpdateIfExists="yes"
21 RemoveOnUninstall="yes"
22 Comment="testComment1"/>
23 </Component>
24 </Fragment>
25</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/ProductCommentDelete.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/ProductCommentDelete.wixproj
new file mode 100644
index 00000000..63bb2370
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/ProductCommentDelete.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>{9E4C301E-5F36-4A86-85BE-776E067D929D}</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>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/product.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/product.wxs
new file mode 100644
index 00000000..f0fbc55e
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/product.wxs
@@ -0,0 +1,18 @@
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 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
13 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
14
15 <util:User Id="TEST_USER1" Name="testName1" Password="test123!@#" PasswordExpired="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" RemoveComment="yes"/>
16 </Component>
17 </Fragment>
18</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/ProductCommentFail.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/ProductCommentFail.wixproj
new file mode 100644
index 00000000..66f308ae
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/ProductCommentFail.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>{85F698E0-F542-4CB4-80A1-6630D2DEB647}</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>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/product_fail.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/product_fail.wxs
new file mode 100644
index 00000000..f36d5bd5
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/product_fail.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
10 <InstallExecuteSequence>
11 <Custom Action="CaFail" After="Wix4ConfigureUsers_X86" />
12 </InstallExecuteSequence>
13 </Fragment>
14
15 <Fragment>
16 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
17 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
18
19 <util:User Id="TEST_USER1" Name="testName1" Password="test123!@#" PasswordExpired="yes" CreateUser="yes" RemoveOnUninstall="yes" Comment="testComment1"/>
20 </Component>
21 </Fragment>
22</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs
index c5da862c..82472d4e 100644
--- a/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs
@@ -31,7 +31,7 @@
31 <util:GroupRef Id="POWER_USER" /> 31 <util:GroupRef Id="POWER_USER" />
32 </util:User> 32 </util:User>
33 33
34 <util:User Id="TEST_USER3" Name="[TEMPUSERNAME]" Domain="[TEMPDOMAIN]" CreateUser="no"> 34 <util:User Id="TEST_USER3" Name="testName3" CreateUser="no">
35 <util:GroupRef Id="POWER_USER" /> 35 <util:GroupRef Id="POWER_USER" />
36 </util:User> 36 </util:User>
37 </Component> 37 </Component>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/ProductNewUserWithComment.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/ProductNewUserWithComment.wixproj
new file mode 100644
index 00000000..aeac903a
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/ProductNewUserWithComment.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>{549E1829-BBDE-42E1-968A-BEB8FC12BFC7}</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>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/product.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/product.wxs
new file mode 100644
index 00000000..dde23aab
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/product.wxs
@@ -0,0 +1,25 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
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 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
13 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
14 <util:User
15 Id="TEST_USER1"
16 Name="testName1"
17 Password="test123!@#"
18 PasswordExpired="yes"
19 CreateUser="yes"
20 UpdateIfExists="yes"
21 RemoveOnUninstall="yes"
22 Comment="testComment1" />
23 </Component>
24 </Fragment>
25</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wixproj b/src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wixproj
new file mode 100644
index 00000000..93a56216
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.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>{79F2CB65-1E71-42EB-AA30-51BD70C29B23}</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>
diff --git a/src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs b/src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs
new file mode 100644
index 00000000..564ce4f0
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs
@@ -0,0 +1,21 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentRef Id="Component1" />
6 </ComponentGroup>
7 </Fragment>
8
9 <Fragment>
10 <Component Id="Component1"
11 Guid="1FDC6C4D-7741-4BF1-A4F0-4231879CEC45"
12 Directory="INSTALLFOLDER">
13 <util:User Id="TEST_USER1"
14 Name="[TESTPARAMETER1]"
15 Password="test123!@#"
16 PasswordExpired="yes"
17 CreateUser="yes"
18 RemoveOnUninstall="yes" />
19 </Component>
20 </Fragment>
21</Wix>
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs
index fcdfde52..30bc53e8 100644
--- a/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs
+++ b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs
@@ -11,21 +11,14 @@ namespace WixToolsetTest.MsiE2E
11 { 11 {
12 public UtilExtensionUserTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } 12 public UtilExtensionUserTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { }
13 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. 14 // Verify that the users specified in the authoring are created as expected.
18 [RuntimeFact] 15 [RuntimeFact]
19 public void CanInstallAndUninstallUsers() 16 public void CanInstallAndUninstallUsers()
20 { 17 {
21 var arguments = new string[] 18 UserVerifier.CreateLocalUser("testName3", "test123!@#");
22 {
23 $"TEMPDOMAIN={Environment.GetEnvironmentVariable(TempDomain)}",
24 $"TEMPUSERNAME={Environment.GetEnvironmentVariable(TempUsername)}",
25 };
26 var productA = this.CreatePackageInstaller("ProductA"); 19 var productA = this.CreatePackageInstaller("ProductA");
27 20
28 productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments); 21 productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
29 22
30 // Validate New User Information. 23 // Validate New User Information.
31 UserVerifier.VerifyUserInformation(String.Empty, "testName1", true, false, false); 24 UserVerifier.VerifyUserInformation(String.Empty, "testName1", true, false, false);
@@ -34,62 +27,90 @@ namespace WixToolsetTest.MsiE2E
34 UserVerifier.VerifyUserInformation(String.Empty, "testName2", true, true, true); 27 UserVerifier.VerifyUserInformation(String.Empty, "testName2", true, true, true);
35 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName2", "Power Users"); 28 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName2", "Power Users");
36 29
37 UserVerifier.VerifyUserIsMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users"); 30 UserVerifier.VerifyUserIsMemberOf("", "testName3", "Power Users");
38 31
39 productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments); 32 productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
40 33
41 // Verify Users marked as RemoveOnUninstall were removed. 34 // 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")); 35 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")); 36 Assert.True(UserVerifier.UserExists(String.Empty, "testName2"), String.Format("User '{0}' was removed on Uninstall", "testName2"));
44 37
38 // Verify that user added to power users group is removed on uninstall.
39 UserVerifier.VerifyUserIsNotMemberOf("", "testName3", "Power Users");
40
45 // clean up 41 // clean up
42 UserVerifier.DeleteLocalUser("testName1");
46 UserVerifier.DeleteLocalUser("testName2"); 43 UserVerifier.DeleteLocalUser("testName2");
47 44 UserVerifier.DeleteLocalUser("testName3");
48 UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
49 } 45 }
50 46
51 // Verify the rollback action reverts all Users changes. 47 // Verify the rollback action reverts all Users changes.
52 [RuntimeFact] 48 [RuntimeFact]
53 public void CanRollbackUsers() 49 public void CanRollbackUsers()
54 { 50 {
55 var arguments = new string[] 51 UserVerifier.CreateLocalUser("testName3", "test123!@#");
56 {
57 $"TEMPDOMAIN={Environment.GetEnvironmentVariable(TempDomain)}",
58 $"TEMPUSERNAME={Environment.GetEnvironmentVariable(TempUsername)}",
59 };
60 var productFail = this.CreatePackageInstaller("ProductFail"); 52 var productFail = this.CreatePackageInstaller("ProductFail");
61 53
62 // make sure the user accounts are deleted before we start 54 // make sure the user accounts are deleted before we start
63 UserVerifier.DeleteLocalUser("testName1"); 55 UserVerifier.DeleteLocalUser("testName1");
64 UserVerifier.DeleteLocalUser("testName2"); 56 UserVerifier.DeleteLocalUser("testName2");
65 UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
66 57
67 productFail.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, arguments); 58 productFail.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE);
68 59
69 // Verify Users marked as RemoveOnUninstall were removed. 60 // Verify added Users were removed on rollback.
70 Assert.False(UserVerifier.UserExists(String.Empty, "testName1"), String.Format("User '{0}' was not removed on Rollback", "testName1")); 61 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")); 62 Assert.False(UserVerifier.UserExists(String.Empty, "testName2"), String.Format("User '{0}' was not removed on Rollback", "testName2"));
72 63
73 UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users"); 64 // Verify that user added to power users group is removed from power users group on rollback.
65 UserVerifier.VerifyUserIsNotMemberOf("", "testName3", "Power Users");
66
67 // clean up
68 UserVerifier.DeleteLocalUser("testName1");
69 UserVerifier.DeleteLocalUser("testName2");
70 UserVerifier.DeleteLocalUser("testName3");
74 } 71 }
75 72
76 // Verify that the users specified in the authoring are created as expected on repair. 73
77 [RuntimeFact(Skip = "Test demonstrates failure")] 74 // Verify that command-line parameters aer not blocked by repair switches.
78 public void CanRepairUsers() 75 // Original code signalled repair mode by using "-f ", which silently
76 // terminated the command-line parsing, ignoring any parameters that followed.
77 [RuntimeFact()]
78 public void CanRepairUsersWithCommandLineParameters()
79 { 79 {
80 var arguments = new string[] 80 var arguments = new string[]
81 { 81 {
82 $"TEMPDOMAIN={Environment.GetEnvironmentVariable(TempDomain)}", 82 "TESTPARAMETER1=testName1",
83 $"TEMPUSERNAME={Environment.GetEnvironmentVariable(TempUsername)}",
84 }; 83 };
84 var productWithCommandLineParameters = this.CreatePackageInstaller("ProductWithCommandLineParameters");
85
86 // Make sure that the user doesn't exist when we start the test.
87 UserVerifier.DeleteLocalUser("testName1");
88
89 // Install
90 productWithCommandLineParameters.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
91
92 // Repair
93 productWithCommandLineParameters.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
94
95 // Clean up
96 UserVerifier.DeleteLocalUser("testName1");
97 }
98
99
100 // Verify that the users specified in the authoring are created as expected on repair.
101 [RuntimeFact()]
102 public void CanRepairUsers()
103 {
104 UserVerifier.CreateLocalUser("testName3", "test123!@#");
85 var productA = this.CreatePackageInstaller("ProductA"); 105 var productA = this.CreatePackageInstaller("ProductA");
86 106
87 productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments); 107 productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
88 108
109 // Validate New User Information.
89 UserVerifier.DeleteLocalUser("testName1"); 110 UserVerifier.DeleteLocalUser("testName1");
90 UserVerifier.SetUserInformation(String.Empty, "testName2", true, false, false); 111 UserVerifier.SetUserInformation(String.Empty, "testName2", true, false, false);
91 112
92 productA.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments); 113 productA.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS);
93 114
94 // Validate New User Information. 115 // Validate New User Information.
95 UserVerifier.VerifyUserInformation(String.Empty, "testName1", true, false, false); 116 UserVerifier.VerifyUserInformation(String.Empty, "testName1", true, false, false);
@@ -98,21 +119,24 @@ namespace WixToolsetTest.MsiE2E
98 UserVerifier.VerifyUserInformation(String.Empty, "testName2", true, true, true); 119 UserVerifier.VerifyUserInformation(String.Empty, "testName2", true, true, true);
99 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName2", "Power Users"); 120 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName2", "Power Users");
100 121
101 UserVerifier.VerifyUserIsMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users"); 122 UserVerifier.VerifyUserIsMemberOf("", "testName3", "Power Users");
102 123
103 productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments); 124 productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
104 125
105 // Verify Users marked as RemoveOnUninstall were removed. 126 // 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")); 127 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")); 128 Assert.True(UserVerifier.UserExists(String.Empty, "testName2"), String.Format("User '{0}' was removed on Uninstall", "testName2"));
108 129
130 // Verify that user added to power users group is removed on uninstall.
131 UserVerifier.VerifyUserIsNotMemberOf("", "testName3", "Power Users");
132
109 // clean up 133 // clean up
134 UserVerifier.DeleteLocalUser("testName1");
110 UserVerifier.DeleteLocalUser("testName2"); 135 UserVerifier.DeleteLocalUser("testName2");
111 136 UserVerifier.DeleteLocalUser("testName3");
112 UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
113 } 137 }
114 138
115 // Verify that Installation fails if FailIfExisits is set. 139 // Verify that Installation fails if FailIfExists is set.
116 [RuntimeFact] 140 [RuntimeFact]
117 public void FailsIfUserExists() 141 public void FailsIfUserExists()
118 { 142 {
@@ -135,7 +159,6 @@ namespace WixToolsetTest.MsiE2E
135 // clean up 159 // clean up
136 UserVerifier.DeleteLocalUser("existinguser"); 160 UserVerifier.DeleteLocalUser("existinguser");
137 } 161 }
138
139 } 162 }
140 163
141 // Verify that a user cannot be created on a domain on which you dont have create user permission. 164 // Verify that a user cannot be created on a domain on which you dont have create user permission.
@@ -158,5 +181,98 @@ namespace WixToolsetTest.MsiE2E
158 181
159 productNonVitalGroup.InstallProduct(); 182 productNonVitalGroup.InstallProduct();
160 } 183 }
184
185 // Verify that a user can be created with a user comment
186 [RuntimeFact]
187 public void CanCreateNewUserWithComment()
188 {
189 var productNewUserWithComment = this.CreatePackageInstaller("ProductNewUserWithComment");
190
191 productNewUserWithComment.InstallProduct();
192 UserVerifier.VerifyUserComment(String.Empty, "testName1", "testComment1");
193
194 // clean up
195 UserVerifier.DeleteLocalUser("testName1");
196 }
197
198 // Verify that a comment can be added to an existing user
199 [RuntimeFact]
200 public void CanAddCommentToExistingUser()
201 {
202 UserVerifier.CreateLocalUser("testName1", "test123!@#");
203 var productAddCommentToExistingUser = this.CreatePackageInstaller("ProductAddCommentToExistingUser");
204
205 productAddCommentToExistingUser.InstallProduct();
206
207 UserVerifier.VerifyUserComment(String.Empty, "testName1", "testComment1");
208
209 // clean up
210 UserVerifier.DeleteLocalUser("testName1");
211 }
212
213 // Verify that a comment can be repaired for a new user
214 [RuntimeFact]
215 public void CanRepairCommentOfNewUser()
216 {
217 var productNewUserWithComment = this.CreatePackageInstaller("ProductNewUserWithComment");
218
219 productNewUserWithComment.InstallProduct();
220 UserVerifier.SetUserComment(String.Empty, "testName1", "");
221
222 productNewUserWithComment.RepairProduct();
223 UserVerifier.VerifyUserComment(String.Empty, "testName1", "testComment1");
224
225 // clean up
226 UserVerifier.DeleteLocalUser("testName1");
227 }
228
229 // Verify that a comment can be changed for an existing user
230 [RuntimeFact]
231 public void CanChangeCommentOfExistingUser()
232 {
233 UserVerifier.CreateLocalUser("testName1", "test123!@#");
234 UserVerifier.SetUserComment(String.Empty, "testName1", "initialTestComment1");
235 var productNewUserWithComment = this.CreatePackageInstaller("ProductNewUserWithComment");
236
237 productNewUserWithComment.InstallProduct();
238 UserVerifier.VerifyUserComment(String.Empty, "testName1", "testComment1");
239
240 // clean up
241 UserVerifier.DeleteLocalUser("testName1");
242 }
243
244 // Verify that a comment can be rolled back for an existing user
245 [RuntimeFact]
246 public void CanRollbackCommentOfExistingUser()
247 {
248 UserVerifier.CreateLocalUser("testName1", "test123!@#");
249 UserVerifier.SetUserComment(String.Empty, "testName1", "initialTestComment1");
250 var productCommentFail = this.CreatePackageInstaller("ProductCommentFail");
251
252 productCommentFail.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE);
253
254 // Verify that comment change was rolled back.
255 UserVerifier.VerifyUserComment(String.Empty, "testName1", "initialTestComment1");
256
257 // clean up
258 UserVerifier.DeleteLocalUser("testName1");
259 }
260
261 // Verify that a comment can be deleted for an existing user
262 [RuntimeFact]
263 public void CanDeleteCommentOfExistingUser()
264 {
265 UserVerifier.CreateLocalUser("testName1", "test123!@#");
266 UserVerifier.SetUserComment(String.Empty, "testName1", "testComment1");
267 var productCommentDelete = this.CreatePackageInstaller("ProductCommentDelete");
268
269 productCommentDelete.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
270
271 // Verify that comment was removed.
272 UserVerifier.VerifyUserComment(String.Empty, "testName1", "");
273
274 // clean up
275 UserVerifier.DeleteLocalUser("testName1");
276 }
161 } 277 }
162} 278}