@joebigelow / wix / commits / 08cdc6aa

Support add, modify and remove comments on user accounts

Fixes 5371

Ron Martin committed Aug 29, 2022 at 18:38 UTC 08cdc6aa2b9dd0e273a3c3a22893616d26342a0e
29 files changed +900 -180
src/ext/Iis/ca/sca.h
+1
@@ -121,4 +121,5 @@ enum SCAU_ATTRIBUTES
121 SCAU_DONT_REMOVE_ON_UNINSTALL = 0x00000100,
122 SCAU_DONT_CREATE_USER = 0x00000200,
123 SCAU_NON_VITAL = 0x00000400,
124 + SCAU_REMOVE_COMMENT = 0x00000800,
125 };
src/ext/Util/ca/sca.h
+2 -1
@@ -16,4 +16,5 @@ enum SCAU_ATTRIBUTES
16 SCAU_DONT_REMOVE_ON_UNINSTALL = 0x00000100,
17 SCAU_DONT_CREATE_USER = 0x00000200,
18 SCAU_NON_VITAL = 0x00000400,
19 -};
\ No newline at end of file
19 + SCAU_REMOVE_COMMENT = 0x00000800,
20 +};
src/ext/Util/ca/scaexec.cpp
+265 -110
@@ -2,7 +2,6 @@
2
3 #include "precomp.h"
4
5 -
5 /********************************************************************
6 * CreateSmb - CUSTOM ACTION ENTRY POINT for creating fileshares
7 *
@@ -520,55 +519,88 @@ static HRESULT ModifyUserLocalBatchRight(
519 return hr;
520 }
521
523 -static void SetUserPasswordAndAttributes(
524 - __in USER_INFO_1* puserInfo,
525 - __in LPWSTR wzPassword,
526 - __in int iAttributes
527 - )
522 +static HRESULT ApplyAttributes(int iAttributes, DWORD* pFlags)
523 {
529 - Assert(puserInfo);
530 -
531 - // Set the User's password
532 - puserInfo->usri1_password = wzPassword;
524 + HRESULT hr = S_OK;
525
534 - // Apply the Attributes
526 if (SCAU_DONT_EXPIRE_PASSWRD & iAttributes)
527 {
537 - puserInfo->usri1_flags |= UF_DONT_EXPIRE_PASSWD;
528 + *pFlags |= UF_DONT_EXPIRE_PASSWD;
529 }
530 else
531 {
541 - puserInfo->usri1_flags &= ~UF_DONT_EXPIRE_PASSWD;
532 + *pFlags &= ~UF_DONT_EXPIRE_PASSWD;
533 }
534
535 if (SCAU_PASSWD_CANT_CHANGE & iAttributes)
536 {
546 - puserInfo->usri1_flags |= UF_PASSWD_CANT_CHANGE;
537 + *pFlags |= UF_PASSWD_CANT_CHANGE;
538 }
539 else
540 {
550 - puserInfo->usri1_flags &= ~UF_PASSWD_CANT_CHANGE;
541 + *pFlags &= ~UF_PASSWD_CANT_CHANGE;
542 }
543
544 if (SCAU_DISABLE_ACCOUNT & iAttributes)
545 {
555 - puserInfo->usri1_flags |= UF_ACCOUNTDISABLE;
546 + *pFlags |= UF_ACCOUNTDISABLE;
547 }
548 else
549 {
559 - puserInfo->usri1_flags &= ~UF_ACCOUNTDISABLE;
550 + *pFlags &= ~UF_ACCOUNTDISABLE;
551 }
552
553 if (SCAU_PASSWD_CHANGE_REQD_ON_LOGIN & iAttributes) // TODO: for some reason this doesn't work
554 {
564 - puserInfo->usri1_flags |= UF_PASSWORD_EXPIRED;
555 + *pFlags |= UF_PASSWORD_EXPIRED;
556 }
557 else
558 {
568 - puserInfo->usri1_flags &= ~UF_PASSWORD_EXPIRED;
559 + *pFlags &= ~UF_PASSWORD_EXPIRED;
560 + }
561 +
562 + return hr;
563 +}
564 +
565 +static 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;
576 }
577 +
578 + return hr;
579 +}
580 +
581 +static 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);
587 }
588
589 +static 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 +
597 +static 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 +}
604
605 static HRESULT RemoveUserInternal(
606 LPWSTR wzGroupCaData,
@@ -624,7 +656,12 @@ static HRESULT RemoveUserInternal(
656 }
657 if (ERROR_SUCCESS == er)
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 + }
665 }
666 else
667 {
@@ -680,6 +717,41 @@ LExit:
717 return hr;
718 }
719
720 +static 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 +}
755
756 /********************************************************************
757 CreateUser - CUSTOM ACTION ENTRY POINT for creating users
@@ -688,7 +760,7 @@ LExit:
760 * *****************************************************************/
761 extern "C" UINT __stdcall CreateUser(
762 __in MSIHANDLE hInstall
691 - )
763 +)
764 {
765 //AssertSz(0, "Debug CreateUser");
766
@@ -699,11 +771,11 @@ extern "C" UINT __stdcall CreateUser(
771 LPWSTR pwz = NULL;
772 LPWSTR pwzName = NULL;
773 LPWSTR pwzDomain = NULL;
774 + LPWSTR pwzComment = NULL;
775 LPWSTR pwzScriptKey = NULL;
776 LPWSTR pwzPassword = NULL;
777 LPWSTR pwzGroup = NULL;
778 LPWSTR pwzGroupDomain = NULL;
706 - PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL;
779 int iAttributes = 0;
780 BOOL fInitializedCom = FALSE;
781
@@ -711,10 +783,10 @@ extern "C" UINT __stdcall CreateUser(
783 int iOriginalAttributes = 0;
784 int iRollbackAttributes = 0;
785
714 - USER_INFO_1 userInfo;
715 - USER_INFO_1* puserInfo = NULL;
786 + USER_INFO_1 userInfo1;
787 + USER_INFO_1* pUserInfo1 = NULL;
788 DWORD dw;
717 - LPCWSTR wz = NULL;
789 + LPWSTR pwzServerName = NULL;
790
791 hr = WcaInitialize(hInstall, "CreateUser");
792 ExitOnFailure(hr, "failed to initialize");
@@ -723,7 +795,7 @@ extern "C" UINT __stdcall CreateUser(
795 ExitOnFailure(hr, "failed to initialize COM");
796 fInitializedCom = TRUE;
797
726 - hr = WcaGetProperty( L"CustomActionData", &pwzData);
798 + hr = WcaGetProperty(L"CustomActionData", &pwzData);
799 ExitOnFailure(hr, "failed to get CustomActionData");
800
801 WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData);
@@ -738,6 +810,9 @@ extern "C" UINT __stdcall CreateUser(
810 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
811 ExitOnFailure(hr, "failed to read domain from custom action data");
812
813 + hr = WcaReadStringFromCaData(&pwz, &pwzComment);
814 + ExitOnFailure(hr, "failed to read user comment from custom action data");
815 +
816 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
817 ExitOnFailure(hr, "failed to read attributes from custom action data");
818
@@ -747,96 +822,155 @@ extern "C" UINT __stdcall CreateUser(
822 hr = WcaReadStringFromCaData(&pwz, &pwzPassword);
823 ExitOnFailure(hr, "failed to read password from custom action data");
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 -
825 if (!(SCAU_DONT_CREATE_USER & iAttributes))
826 {
784 - ::ZeroMemory(&userInfo, sizeof(USER_INFO_1));
785 - userInfo.usri1_name = pwzName;
786 - userInfo.usri1_priv = USER_PRIV_USER;
787 - userInfo.usri1_flags = UF_SCRIPT;
788 - userInfo.usri1_home_dir = NULL;
789 - userInfo.usri1_comment = NULL;
790 - userInfo.usri1_script_path = NULL;
791 -
792 - SetUserPasswordAndAttributes(&userInfo, pwzPassword, iAttributes);
827 + pUserInfo1 = &userInfo1;
828 + ::ZeroMemory(pUserInfo1, sizeof(USER_INFO_1));
829 + pUserInfo1->usri1_name = pwzName;
830 + pUserInfo1->usri1_priv = USER_PRIV_USER;
831 + pUserInfo1->usri1_flags = UF_SCRIPT;
832 + pUserInfo1->usri1_home_dir = NULL;
833 + pUserInfo1->usri1_comment = NULL;
834 + pUserInfo1->usri1_script_path = NULL;
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");
846
847 //
848 // Create the User
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)
855 {
799 - er = ::DsGetDcNameW( NULL, (LPCWSTR)pwzDomain, NULL, NULL, NULL, &pDomainControllerInfo );
800 - if (RPC_S_SERVER_UNAVAILABLE == er)
801 - {
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)
856 + er = ERROR_SUCCESS; // Make sure that we don't report this situation as an error
857 + // if we fall through the tests that follow.
858 + if (SCAU_FAIL_IF_EXISTS & iAttributes)
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.");
862 }
809 - else
810 - {
811 - wz = pwzDomain;
812 - }
813 - }
863
815 - er = ::NetUserAdd(wz, 1, reinterpret_cast<LPBYTE>(&userInfo), &dw);
816 - if (NERR_UserExists == er)
817 - {
864 if (SCAU_UPDATE_IF_EXISTS & iAttributes)
865 {
820 - er = ::NetUserGetInfo(wz, pwzName, 1, reinterpret_cast<LPBYTE*>(&puserInfo));
821 - if (NERR_Success == er)
866 + pUserInfo1 = NULL;
867 + er = ::NetUserGetInfo(pwzServerName, pwzName, 1, reinterpret_cast<LPBYTE*>(&pUserInfo1));
868 + if (ERROR_SUCCESS == er)
869 {
823 - // Change the existing user's password and attributes again then try
824 - // to update user with this new data
825 - SetUserPasswordAndAttributes(puserInfo, pwzPassword, iAttributes);
870 + // There is no rollback scheduled if the key is empty.
871 + // Best effort to get original configuration and save it in the script so rollback can restore it.
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 + }
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 + }
962 }
963 }
830 - else if (!(SCAU_FAIL_IF_EXISTS & iAttributes))
831 - {
832 - er = NERR_Success;
833 - }
964 }
965 else if (NERR_PasswordTooShort == er || NERR_PasswordTooLong == er)
966 {
967 MessageExitOnFailure(hr = HRESULT_FROM_WIN32(er), msierrUSRFailedUserCreatePswd, "failed to create user: %ls due to invalid password.", pwzName);
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 + }
974 }
975
976 if (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes)
@@ -851,14 +985,15 @@ extern "C" UINT __stdcall CreateUser(
985 MessageExitOnFailure(hr, msierrUSRFailedGrantLogonAsService, "Failed to grant logon as batch job rights to user: %ls", pwzName);
986 }
987
854 - //
855 - // Add the users to groups
856 - //
857 - while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup)))
858 - {
859 - hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain);
860 - ExitOnFailure(hr, "failed to get domain for group: %ls", pwzGroup);
988 +//
989 +// Add the users to groups
990 +//
991 +while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup)))
992 +{
993 + hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain);
994 + ExitOnFailure(hr, "failed to get domain for group: %ls", pwzGroup);
995
996 + WcaLog(LOGMSG_STANDARD, "Adding user %ls\\%ls to group %ls\\%ls", pwzDomain, pwzName, pwzGroupDomain, pwzGroup);
997 hr = AddUserToGroup(pwzName, pwzDomain, pwzGroup, pwzGroupDomain);
998 MessageExitOnFailure(hr, msierrUSRFailedUserGroupAdd, "failed to add user: %ls to group %ls", pwzName, pwzGroup);
999 }
@@ -866,24 +1001,23 @@ extern "C" UINT __stdcall CreateUser(
1001 {
1002 hr = S_OK;
1003 }
1004 +
1005 ExitOnFailure(hr, "failed to get next group in which to include user:%ls", pwzName);
1006
1007 +ExitOnFailure(hr, "failed to get next group in which to include user:%ls", pwzName);
1008 +
1009 LExit:
1010 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE);
1011
874 - if (puserInfo)
1012 + if (pUserInfo1 && pUserInfo1 != &userInfo1)
1013 {
876 - ::NetApiBufferFree((LPVOID)puserInfo);
877 - }
878 -
879 - if (pDomainControllerInfo)
880 - {
881 - ::NetApiBufferFree((LPVOID)pDomainControllerInfo);
1014 + ::NetApiBufferFree((LPVOID)pUserInfo1);
1015 }
1016
1017 ReleaseStr(pwzData);
1018 ReleaseStr(pwzName);
1019 ReleaseStr(pwzDomain);
1020 + ReleaseStr(pwzComment);
1021 ReleaseStr(pwzScriptKey);
1022 ReleaseStr(pwzPassword);
1023 ReleaseStr(pwzGroup);
@@ -922,15 +1056,17 @@ extern "C" UINT __stdcall CreateUserRollback(
1056
1057 LPWSTR pwzData = NULL;
1058 LPWSTR pwz = NULL;
1059 + LPWSTR pwzScriptKey = NULL;
1060 LPWSTR pwzName = NULL;
1061 LPWSTR pwzDomain = NULL;
927 - LPWSTR pwzScriptKey = NULL;
1062 + LPWSTR pwzComment = NULL;
1063 int iAttributes = 0;
1064 BOOL fInitializedCom = FALSE;
1065
1066 WCA_CASCRIPT_HANDLE hRollbackScript = NULL;
1067 LPWSTR pwzRollbackData = NULL;
1068 int iOriginalAttributes = 0;
1069 + LPWSTR pwzOriginalComment = NULL;
1070
1071 hr = WcaInitialize(hInstall, "CreateUserRollback");
1072 ExitOnFailure(hr, "failed to initialize");
@@ -957,6 +1093,9 @@ extern "C" UINT __stdcall CreateUserRollback(
1093 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
1094 ExitOnFailure(hr, "failed to read domain from custom action data");
1095
1096 + hr = WcaReadStringFromCaData(&pwz, &pwzComment);
1097 + ExitOnFailure(hr, "failed to read comment from custom action data");
1098 +
1099 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
1100 ExitOnFailure(hr, "failed to read attributes from custom action data");
1101
@@ -978,6 +1117,15 @@ extern "C" UINT __stdcall CreateUserRollback(
1117 WcaLog(LOGMSG_TRACEONLY, "Rollback Data: %ls", pwzRollbackData);
1118
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 + }
1129 hr = WcaReadIntegerFromCaData(&pwz, &iOriginalAttributes);
1130 if (FAILED(hr))
1131 {
@@ -998,8 +1146,10 @@ LExit:
1146 ReleaseStr(pwzData);
1147 ReleaseStr(pwzName);
1148 ReleaseStr(pwzDomain);
1149 + ReleaseStr(pwzComment);
1150 ReleaseStr(pwzScriptKey);
1151 ReleaseStr(pwzRollbackData);
1152 + ReleaseStr(pwzOriginalComment);
1153
1154 if (fInitializedCom)
1155 {
@@ -1033,6 +1183,7 @@ extern "C" UINT __stdcall RemoveUser(
1183 LPWSTR pwz = NULL;
1184 LPWSTR pwzName = NULL;
1185 LPWSTR pwzDomain = NULL;
1186 + LPWSTR pwzComment = NULL;
1187 int iAttributes = 0;
1188 BOOL fInitializedCom = FALSE;
1189
@@ -1058,6 +1209,9 @@ extern "C" UINT __stdcall RemoveUser(
1209 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
1210 ExitOnFailure(hr, "failed to read domain from custom action data");
1211
1212 + hr = WcaReadStringFromCaData(&pwz, &pwzComment);
1213 + ExitOnFailure(hr, "failed to read comment from custom action data");
1214 +
1215 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
1216 ExitOnFailure(hr, "failed to read attributes from custom action data");
1217
@@ -1067,6 +1221,7 @@ LExit:
1221 ReleaseStr(pwzData);
1222 ReleaseStr(pwzName);
1223 ReleaseStr(pwzDomain);
1224 + ReleaseStr(pwzComment);
1225
1226 if (fInitializedCom)
1227 {
src/ext/Util/ca/scauser.cpp
+46 -16
@@ -2,8 +2,8 @@
2
3 #include "precomp.h"
4
5 -LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Password` FROM `Wix4User` WHERE `User`=?";
6 -enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqPassword };
5 +LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Comment`, `Password` FROM `Wix4User` WHERE `User`=?";
6 +enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqComment, vuqPassword };
7
8 LPCWSTR vcsGroupQuery = L"SELECT `Group`, `Component_`, `Name`, `Domain` FROM `Wix4Group` WHERE `Group`=?";
9 enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain };
@@ -11,8 +11,8 @@ enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain };
11 LPCWSTR vcsUserGroupQuery = L"SELECT `User_`, `Group_` FROM `Wix4UserGroup` WHERE `User_`=?";
12 enum eUserGroupQuery { vugqUser = 1, vugqGroup };
13
14 -LPCWSTR vActionableQuery = L"SELECT `User`,`Component_`,`Name`,`Domain`,`Password`,`Attributes` FROM `Wix4User` WHERE `Component_` IS NOT NULL";
15 -enum eActionableQuery { vaqUser = 1, vaqComponent, vaqName, vaqDomain, vaqPassword, vaqAttributes };
14 +LPCWSTR vActionableQuery = L"SELECT `User`,`Component_`,`Name`,`Domain`,`Password`,`Comment`,`Attributes` FROM `Wix4User` WHERE `Component_` IS NOT NULL";
15 +enum eActionableQuery { vaqUser = 1, vaqComponent, vaqName, vaqDomain, vaqPassword, vaqComment, vaqAttributes };
16
17
18 static HRESULT AddUserToList(
@@ -78,6 +78,11 @@ HRESULT __stdcall ScaGetUser(
78 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData);
79 ExitOnFailure(hr, "Failed to copy domain string to user object");
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 +
86 hr = WcaGetRecordFormattedString(hRec, vuqPassword, &pwzData);
87 ExitOnFailure(hr, "Failed to get Wix4User.Password");
88 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData);
@@ -154,6 +159,11 @@ HRESULT __stdcall ScaGetUserDeferred(
159 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData);
160 ExitOnFailure(hr, "Failed to copy domain string to user object (in deferred CA)");
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 +
167 hr = WcaGetRecordString(hRec, vuqPassword, &pwzData);
168 ExitOnFailure(hr, "Failed to get Wix4User.Password");
169 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData);
@@ -316,7 +326,7 @@ HRESULT ScaUserRead(
326 ExitOnFailure(hr, "failed to get Component state for Wix4User");
327
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))
330 {
331 //
332 // Add the user to the list and populate it's values
@@ -345,6 +355,10 @@ HRESULT ScaUserRead(
355 ExitOnFailure(hr, "failed to get Wix4User.Domain");
356 hr = ::StringCchCopyW(psu->wzDomain, countof(psu->wzDomain), 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);
362
363 hr = WcaGetRecordFormattedString(hRec, vaqPassword, &pwzData);
364 ExitOnFailure(hr, "failed to get Wix4User.Password");
@@ -492,15 +506,16 @@ HRESULT ScaUserExecute(
506 {
507 USER_EXISTS ueUserExists = USER_EXISTS_INDETERMINATE;
508
495 - // Always put the User Name and Domain plus Attributes on the front of the CustomAction
496 - // data. Sometimes we'll add more data.
509 + // Always put the User Name, Domain, and Comment on the front of the CustomAction data.
510 + // The attributes will be added when we have finished adjusting them. Sometimes we'll
511 + // add more data.
512 Assert(psu->wzName);
513 hr = WcaWriteStringToCaData(psu->wzName, &pwzActionData);
514 ExitOnFailure(hr, "Failed to add user name to custom action data: %ls", psu->wzName);
515 hr = WcaWriteStringToCaData(psu->wzDomain, &pwzActionData);
516 ExitOnFailure(hr, "Failed to add user domain to custom action data: %ls", psu->wzDomain);
502 - hr = WcaWriteIntegerToCaData(psu->iAttributes, &pwzActionData);
503 - ExitOnFailure(hr, "failed to add user attributes to custom action data for user: %ls", psu->wzKey);
517 + hr = WcaWriteStringToCaData(psu->wzComment, &pwzActionData);
518 + ExitOnFailure(hr, "Failed to add user comment to custom action data: %ls", psu->wzComment);
519
520 // Check to see if the user already exists since we have to be very careful when adding
521 // and removing users. Note: MSDN says that it is safe to call these APIs from any
@@ -520,7 +535,12 @@ HRESULT ScaUserExecute(
535 }
536 if (ERROR_SUCCESS == er)
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 + }
544 }
545 }
546
@@ -544,23 +564,32 @@ HRESULT ScaUserExecute(
564
565 if (WcaIsInstalling(psu->isInstalled, psu->isAction))
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
568 // the install.
569 if (USER_EXISTS_YES == ueUserExists)
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".
572 if (WcaIsReInstalling(psu->isInstalled, psu->isAction))
573 {
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 + }
581 }
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))
584 {
585 hr = HRESULT_FROM_WIN32(NERR_UserExists);
586 MessageExitOnFailure(hr, msierrUSRFailedUserCreateExists, "Failed to create user: %ls because user already exists.", psu->wzName);
587 }
588 }
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 +
593 // Rollback only if the user already exists, we couldn't determine if the user exists, or we are going to create the user
594 if ((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists) || !(psu->iAttributes & SCAU_DONT_CREATE_USER))
595 {
@@ -597,7 +626,6 @@ HRESULT ScaUserExecute(
626 ExitOnFailure(hr, "Failed to add user domain to rollback custom action data: %ls", psu->wzDomain);
627 hr = WcaWriteIntegerToCaData(iRollbackUserAttributes, &pwzRollbackData);
628 ExitOnFailure(hr, "failed to add user attributes to rollback custom action data for user: %ls", psu->wzKey);
600 -
629 // If the user already exists, add relevant group information to rollback data
630 if (USER_EXISTS_YES == ueUserExists || USER_EXISTS_INDETERMINATE == ueUserExists)
631 {
@@ -630,11 +658,13 @@ HRESULT ScaUserExecute(
658 }
659 else if (((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists)) && WcaIsUninstalling(psu->isInstalled, psu->isAction) && !(psu->iAttributes & SCAU_DONT_REMOVE_ON_UNINSTALL))
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 +
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
665 hr = WriteGroupInfo(psu->psgGroups, &pwzActionData);
666 ExitOnFailure(hr, "failed to add group information to custom action data");
667
637 - //
668 // Schedule the removal because the user exists and we don't have any flags set
669 // that say, don't remove the user on uninstall.
670 //
@@ -642,7 +672,7 @@ HRESULT ScaUserExecute(
672 // CustomAction.
673 hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RemoveUser"), pwzActionData, COST_USER_DELETE);
674 ExitOnFailure(hr, "failed to schedule RemoveUser");
645 - }
675 + }
676
677 ReleaseNullStr(pwzScriptKey);
678 ReleaseNullStr(pwzActionData);
src/ext/Util/ca/scauser.h
+4 -3
@@ -31,6 +31,7 @@ struct SCA_USER
31 WCHAR wzDomain[MAX_DARWIN_COLUMN + 1];
32 WCHAR wzName[MAX_DARWIN_COLUMN + 1];
33 WCHAR wzPassword[MAX_DARWIN_COLUMN + 1];
34 + WCHAR wzComment[MAX_DARWIN_COLUMN + 1];
35 INT iAttributes;
36
37 SCA_GROUP *psgGroups;
@@ -41,16 +42,16 @@ struct SCA_USER
42
43 // prototypes
44 HRESULT __stdcall ScaGetUser(
44 - __in LPCWSTR wzUser,
45 + __in LPCWSTR wzUser,
46 __out SCA_USER* pscau
47 );
48 HRESULT __stdcall ScaGetUserDeferred(
48 - __in LPCWSTR wzUser,
49 + __in LPCWSTR wzUser,
50 __in WCA_WRAPQUERY_HANDLE hUserQuery,
51 __out SCA_USER* pscau
52 );
53 HRESULT __stdcall ScaGetGroup(
53 - __in LPCWSTR wzGroup,
54 + __in LPCWSTR wzGroup,
55 __out SCA_GROUP* pscag
56 );
57 void ScaUserFreeList(
src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/Package.wxs new
+15
@@ -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>
src/ext/Util/test/WixToolsetTest.Util/TestData/CreateUser/PackageComponents.wxs new
+76
@@ -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>
src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs
+76
@@ -296,6 +296,82 @@ namespace WixToolsetTest.Util
296 }
297 }
298
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]
376 public void CanBuildBundleWithWarningsWithSearchesUsingDiscouragedVariableNames()
377 {
src/ext/Util/wixext/Symbols/UserSymbol.cs
+9 -1
@@ -15,6 +15,7 @@ namespace WixToolset.Util
15 new IntermediateFieldDefinition(nameof(UserSymbolFields.Name), IntermediateFieldType.String),
16 new IntermediateFieldDefinition(nameof(UserSymbolFields.Domain), IntermediateFieldType.String),
17 new IntermediateFieldDefinition(nameof(UserSymbolFields.Password), IntermediateFieldType.String),
18 + new IntermediateFieldDefinition(nameof(UserSymbolFields.Comment), IntermediateFieldType.String),
19 new IntermediateFieldDefinition(nameof(UserSymbolFields.Attributes), IntermediateFieldType.Number),
20 },
21 typeof(UserSymbol));
@@ -31,6 +32,7 @@ namespace WixToolset.Util.Symbols
32 Name,
33 Domain,
34 Password,
35 + Comment,
36 Attributes,
37 }
38
@@ -70,10 +72,16 @@ namespace WixToolset.Util.Symbols
72 set => this.Set((int)UserSymbolFields.Password, value);
73 }
74
75 + public string Comment
76 + {
77 + get => this.Fields[(int)UserSymbolFields.Comment].AsString();
78 + set => this.Set((int)UserSymbolFields.Comment, value);
79 + }
80 +
81 public int Attributes
82 {
83 get => this.Fields[(int)UserSymbolFields.Attributes].AsNumber();
84 set => this.Set((int)UserSymbolFields.Attributes, value);
85 }
86 }
79 -}
\ No newline at end of file
87 +}
src/ext/Util/wixext/UtilCompiler.cs
+22
@@ -34,6 +34,7 @@ namespace WixToolset.Util
34 internal const int UserDontRemoveOnUninstall = 0x00000100;
35 internal const int UserDontCreateUser = 0x00000200;
36 internal const int UserNonVital = 0x00000400;
37 + internal const int UserRemoveComment = 0x00000800;
38
39 private static readonly Regex FindPropertyBrackets = new Regex(@"\[(?!\\|\])|(?<!\[\\\]|\[\\|\\\[)\]", RegexOptions.ExplicitCapture | RegexOptions.Compiled);
40
@@ -3251,6 +3252,7 @@ namespace WixToolset.Util
3252 int attributes = 0;
3253 string domain = null;
3254 string name = null;
3255 + string comment = null;
3256 string password = null;
3257
3258 foreach (var attrib in element.Attributes())
@@ -3273,6 +3275,14 @@ namespace WixToolset.Util
3275 attributes |= UserPasswdCantChange;
3276 }
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;
3286 case "CreateUser":
3287 if (null == componentId)
3288 {
@@ -3357,6 +3367,12 @@ namespace WixToolset.Util
3367 attributes |= UserDontExpirePasswrd;
3368 }
3369 break;
3370 + case "RemoveComment":
3371 + if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
3372 + {
3373 + attributes |= UserRemoveComment;
3374 + }
3375 + break;
3376 case "RemoveOnUninstall":
3377 if (null == componentId)
3378 {
@@ -3411,6 +3427,11 @@ namespace WixToolset.Util
3427 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name"));
3428 }
3429
3430 + if (null != comment && (UserRemoveComment & attributes) != 0)
3431 + {
3432 + this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "Comment", "RemoveComment"));
3433 + }
3434 +
3435 foreach (var child in element.Elements())
3436 {
3437 if (this.Namespace == child.Name.Namespace)
@@ -3450,6 +3471,7 @@ namespace WixToolset.Util
3471 Name = name,
3472 Domain = domain,
3473 Password = password,
3474 + Comment = comment,
3475 Attributes = attributes,
3476 });
3477 }
src/ext/Util/wixext/UtilDecompiler.cs
+4 -2
@@ -559,13 +559,14 @@ namespace WixToolset.Util
559 {
560 foreach (var row in table.Rows)
561 {
562 - var attributes = row.FieldAsNullableInteger(5) ?? 0;
562 + var attributes = row.FieldAsNullableInteger(6) ?? 0;
563
564 var user = new XElement(UtilConstants.UserName,
565 new XAttribute("Id", row.FieldAsString(0)),
566 new XAttribute("Name", row.FieldAsString(2)),
567 AttributeIfNotNull("Domain", row, 3),
568 AttributeIfNotNull("Password", row, 4),
569 + AttributeIfNotNull("Comment", row, 5),
570 AttributeIfTrue("PasswordNeverExpires", UtilCompiler.UserDontExpirePasswrd == (attributes & UtilCompiler.UserDontExpirePasswrd)),
571 AttributeIfTrue("CanNotChangePassword", UtilCompiler.UserPasswdCantChange == (attributes & UtilCompiler.UserPasswdCantChange)),
572 AttributeIfTrue("PasswordExpired", UtilCompiler.UserPasswdChangeReqdOnLogin == (attributes & UtilCompiler.UserPasswdChangeReqdOnLogin)),
@@ -573,7 +574,8 @@ namespace WixToolset.Util
574 AttributeIfTrue("FailIfExists", UtilCompiler.UserFailIfExists == (attributes & UtilCompiler.UserFailIfExists)),
575 AttributeIfTrue("UpdateIfExists", UtilCompiler.UserUpdateIfExists == (attributes & UtilCompiler.UserUpdateIfExists)),
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))
579 );
580
581 if (UtilCompiler.UserDontRemoveOnUninstall == (attributes & UtilCompiler.UserDontRemoveOnUninstall))
src/ext/Util/wixext/UtilTableDefinitions.cs
+1
@@ -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),
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),
232 + new ColumnDefinition("Comment", ColumnType.String, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "User comment", modularizeType: ColumnModularizeType.Property),
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"),
234 },
235 symbolIdIsPrimaryKey: true
src/libs/wcautil/WixToolset.WcaUtil/wcawrap.cpp
+8 -3
@@ -906,7 +906,7 @@ static void RevealNulls(
906
907
908 /********************************************************************
909 -WcaGetRecordFormattedString() - gets formatted string filed from record
909 +WcaGetRecordFormattedString() - gets formatted string field from record
910
911 ********************************************************************/
912 extern "C" HRESULT WIXAPI WcaGetRecordFormattedString(
@@ -1391,9 +1391,14 @@ extern "C" HRESULT WIXAPI WcaWriteIntegerToCaData(
1391 )
1392 {
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");
1396
1396 - return WcaWriteStringToCaData(wzBuffer, ppwzCustomActionData);
1397 + hr = WcaWriteStringToCaData(wzBuffer, ppwzCustomActionData);
1398 + ExitOnFailure(hr, "failed to write integer to ca data");
1399 +
1400 +LExit:
1401 + return hr;
1402 }
1403
1404
src/test/burn/WixTestTools/MSIExec.cs
+1 -1
@@ -259,7 +259,7 @@ namespace WixTestTools
259 arguments.Append(" /a ");
260 break;
261 case MSIExecMode.Repair:
262 - arguments.Append(" /f ");
262 + arguments.Append(" /fvomusa ");
263 break;
264 case MSIExecMode.Cleanup:
265 case MSIExecMode.Uninstall:
src/test/burn/WixTestTools/UserVerifier.cs
+38 -3
@@ -49,7 +49,7 @@ namespace WixTestTools
49 UserPrincipal newUser = new UserPrincipal(new PrincipalContext(ContextType.Machine));
50 newUser.SetPassword(password);
51 newUser.Name = userName;
52 - newUser.Description = "New test User";
52 + newUser.Description = String.Empty;
53 newUser.UserCannotChangePassword = true;
54 newUser.PasswordNeverExpires = false;
55 newUser.Save();
@@ -108,6 +108,24 @@ namespace WixTestTools
108 user.Save();
109 }
110
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>
130 /// Adds the specified user to the specified local group
131 /// </summary>
@@ -162,7 +180,24 @@ namespace WixTestTools
180 }
181
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
201 /// </summary>
202 /// <param name="domainName">domain name for the user, empty for local users</param>
203 /// <param name="userName">the user name</param>
@@ -322,7 +357,6 @@ namespace WixTestTools
357 missedAGroup = true;
358 message += String.Format("Local group '{0}' was not found. \r\n", groupName);
359 }
325 -
360 }
361 Assert.False(missedAGroup, message);
362 }
@@ -346,3 +380,4 @@ namespace WixTestTools
380 }
381 }
382 }
383 +
src/test/msi/TestData/UtilExtensionUserTests/ProductA/ProductA.wixproj
+1 -1
@@ -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. -->
2 <Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 - <UpgradeCode>{1A1795A6-87C0-4A9A-ABD5-DF9BED697037}</UpgradeCode>
4 + <UpgradeCode>{A3E0B539-63F9-4B43-9E34-F33AE1C6E06D}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
src/test/msi/TestData/UtilExtensionUserTests/ProductA/product.wxs
+3 -3
@@ -15,10 +15,10 @@
15 <util:Group Id="ADMIN" Name="Administrators" />
16 <util:Group Id="POWER_USER" Name="Power Users" />
17
18 - <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
18 + <Component Id="Component1" Guid="09624A9A-4BBC-4126-BBF9-0713C5217DB1" Directory="INSTALLFOLDER">
19 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
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" />
23 <util:GroupRef Id="POWER_USER" />
24 </util:User>
@@ -27,7 +27,7 @@
27 <util:GroupRef Id="POWER_USER" />
28 </util:User>
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" />
32 </util:User>
33 </Component>
src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/ProductAddCommentToExistingUser.wixproj new
+13
@@ -0,0 +1,13 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <UpgradeCode>{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>
src/test/msi/TestData/UtilExtensionUserTests/ProductAddCommentToExistingUser/product.wxs new
+25
@@ -0,0 +1,25 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +
3 +
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>
src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/ProductCommentDelete.wixproj new
+13
@@ -0,0 +1,13 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <UpgradeCode>{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>
src/test/msi/TestData/UtilExtensionUserTests/ProductCommentDelete/product.wxs new
+18
@@ -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>
src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/ProductCommentFail.wixproj new
+13
@@ -0,0 +1,13 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <UpgradeCode>{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>
src/test/msi/TestData/UtilExtensionUserTests/ProductCommentFail/product_fail.wxs new
+22
@@ -0,0 +1,22 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +
3 +
4 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 + <Fragment>
6 + <ComponentGroup Id="ProductComponents">
7 + <ComponentRef Id="Component1" />
8 + </ComponentGroup>
9 +
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>
src/test/msi/TestData/UtilExtensionUserTests/ProductFail/product_fail.wxs
+1 -1
@@ -31,7 +31,7 @@
31 <util:GroupRef Id="POWER_USER" />
32 </util:User>
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" />
36 </util:User>
37 </Component>
src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/ProductNewUserWithComment.wixproj new
+13
@@ -0,0 +1,13 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <UpgradeCode>{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>
src/test/msi/TestData/UtilExtensionUserTests/ProductNewUserWithComment/product.wxs new
+25
@@ -0,0 +1,25 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +
3 +
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>
src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wixproj new
+13
@@ -0,0 +1,13 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<Project Sdk="WixToolset.Sdk">
3 + <PropertyGroup>
4 + <UpgradeCode>{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>
src/test/msi/TestData/UtilExtensionUserTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs new
+21
@@ -0,0 +1,21 @@
1 +<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2 +<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>
src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionUserTests.cs
+151 -35
@@ -11,21 +11,14 @@ namespace WixToolsetTest.MsiE2E
11 {
12 public UtilExtensionUserTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { }
13
14 - const string TempDomain = "USERDOMAIN";
15 - const string TempUsername = "USERNAME";
16 -
14 // Verify that the users specified in the authoring are created as expected.
15 [RuntimeFact]
16 public void CanInstallAndUninstallUsers()
17 {
21 - var arguments = new string[]
22 - {
23 - $"TEMPDOMAIN={Environment.GetEnvironmentVariable(TempDomain)}",
24 - $"TEMPUSERNAME={Environment.GetEnvironmentVariable(TempUsername)}",
25 - };
18 + UserVerifier.CreateLocalUser("testName3", "test123!@#");
19 var productA = this.CreatePackageInstaller("ProductA");
20
28 - productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
21 + productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
22
23 // Validate New User Information.
24 UserVerifier.VerifyUserInformation(String.Empty, "testName1", true, false, false);
@@ -34,62 +27,90 @@ namespace WixToolsetTest.MsiE2E
27 UserVerifier.VerifyUserInformation(String.Empty, "testName2", true, true, true);
28 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName2", "Power Users");
29
37 - UserVerifier.VerifyUserIsMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
30 + UserVerifier.VerifyUserIsMemberOf("", "testName3", "Power Users");
31
39 - productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
32 + productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
33
34 // Verify Users marked as RemoveOnUninstall were removed.
35 Assert.False(UserVerifier.UserExists(String.Empty, "testName1"), String.Format("User '{0}' was not removed on Uninstall", "testName1"));
36 Assert.True(UserVerifier.UserExists(String.Empty, "testName2"), String.Format("User '{0}' was removed on Uninstall", "testName2"));
37
38 + // Verify that user added to power users group is removed on uninstall.
39 + UserVerifier.VerifyUserIsNotMemberOf("", "testName3", "Power Users");
40 +
41 // clean up
42 + UserVerifier.DeleteLocalUser("testName1");
43 UserVerifier.DeleteLocalUser("testName2");
47 -
48 - UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
44 + UserVerifier.DeleteLocalUser("testName3");
45 }
46
47 // Verify the rollback action reverts all Users changes.
48 [RuntimeFact]
49 public void CanRollbackUsers()
50 {
55 - var arguments = new string[]
56 - {
57 - $"TEMPDOMAIN={Environment.GetEnvironmentVariable(TempDomain)}",
58 - $"TEMPUSERNAME={Environment.GetEnvironmentVariable(TempUsername)}",
59 - };
51 + UserVerifier.CreateLocalUser("testName3", "test123!@#");
52 var productFail = this.CreatePackageInstaller("ProductFail");
53
54 // make sure the user accounts are deleted before we start
55 UserVerifier.DeleteLocalUser("testName1");
56 UserVerifier.DeleteLocalUser("testName2");
65 - UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
57
67 - productFail.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, arguments);
58 + productFail.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE);
59
69 - // Verify Users marked as RemoveOnUninstall were removed.
60 + // Verify added Users were removed on rollback.
61 Assert.False(UserVerifier.UserExists(String.Empty, "testName1"), String.Format("User '{0}' was not removed on Rollback", "testName1"));
62 Assert.False(UserVerifier.UserExists(String.Empty, "testName2"), String.Format("User '{0}' was not removed on Rollback", "testName2"));
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");
71 }
72
76 - // Verify that the users specified in the authoring are created as expected on repair.
77 - [RuntimeFact(Skip = "Test demonstrates failure")]
78 - public void CanRepairUsers()
73 +
74 + // Verify that command-line parameters aer not blocked by repair switches.
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 {
80 var arguments = new string[]
81 {
82 - $"TEMPDOMAIN={Environment.GetEnvironmentVariable(TempDomain)}",
83 - $"TEMPUSERNAME={Environment.GetEnvironmentVariable(TempUsername)}",
82 + "TESTPARAMETER1=testName1",
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!@#");
105 var productA = this.CreatePackageInstaller("ProductA");
106
87 - productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
107 + productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
108
109 + // Validate New User Information.
110 UserVerifier.DeleteLocalUser("testName1");
111 UserVerifier.SetUserInformation(String.Empty, "testName2", true, false, false);
112
92 - productA.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
113 + productA.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS);
114
115 // Validate New User Information.
116 UserVerifier.VerifyUserInformation(String.Empty, "testName1", true, false, false);
@@ -98,21 +119,24 @@ namespace WixToolsetTest.MsiE2E
119 UserVerifier.VerifyUserInformation(String.Empty, "testName2", true, true, true);
120 UserVerifier.VerifyUserIsMemberOf(String.Empty, "testName2", "Power Users");
121
101 - UserVerifier.VerifyUserIsMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
122 + UserVerifier.VerifyUserIsMemberOf("", "testName3", "Power Users");
123
103 - productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
124 + productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
125
126 // Verify Users marked as RemoveOnUninstall were removed.
127 Assert.False(UserVerifier.UserExists(String.Empty, "testName1"), String.Format("User '{0}' was not removed on Uninstall", "testName1"));
128 Assert.True(UserVerifier.UserExists(String.Empty, "testName2"), String.Format("User '{0}' was removed on Uninstall", "testName2"));
129
130 + // Verify that user added to power users group is removed on uninstall.
131 + UserVerifier.VerifyUserIsNotMemberOf("", "testName3", "Power Users");
132 +
133 // clean up
134 + UserVerifier.DeleteLocalUser("testName1");
135 UserVerifier.DeleteLocalUser("testName2");
111 -
112 - UserVerifier.VerifyUserIsNotMemberOf(Environment.GetEnvironmentVariable(TempDomain), Environment.GetEnvironmentVariable(TempUsername), "Power Users");
136 + UserVerifier.DeleteLocalUser("testName3");
137 }
138
115 - // Verify that Installation fails if FailIfExisits is set.
139 + // Verify that Installation fails if FailIfExists is set.
140 [RuntimeFact]
141 public void FailsIfUserExists()
142 {
@@ -135,7 +159,6 @@ namespace WixToolsetTest.MsiE2E
159 // clean up
160 UserVerifier.DeleteLocalUser("existinguser");
161 }
138 -
162 }
163
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
181
182 productNonVitalGroup.InstallProduct();
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 + }
277 }
278 }