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
}