aboutsummaryrefslogtreecommitdiff
path: root/src/ext/Util/ca
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext/Util/ca')
-rw-r--r--src/ext/Util/ca/sca.h3
-rw-r--r--src/ext/Util/ca/scaexec.cpp375
-rw-r--r--src/ext/Util/ca/scauser.cpp62
-rw-r--r--src/ext/Util/ca/scauser.h7
4 files changed, 317 insertions, 130 deletions
diff --git a/src/ext/Util/ca/sca.h b/src/ext/Util/ca/sca.h
index 599122ff..84f5ffd9 100644
--- a/src/ext/Util/ca/sca.h
+++ b/src/ext/Util/ca/sca.h
@@ -16,4 +16,5 @@ enum SCAU_ATTRIBUTES
16 SCAU_DONT_REMOVE_ON_UNINSTALL = 0x00000100, 16 SCAU_DONT_REMOVE_ON_UNINSTALL = 0x00000100,
17 SCAU_DONT_CREATE_USER = 0x00000200, 17 SCAU_DONT_CREATE_USER = 0x00000200,
18 SCAU_NON_VITAL = 0x00000400, 18 SCAU_NON_VITAL = 0x00000400,
19}; \ No newline at end of file 19 SCAU_REMOVE_COMMENT = 0x00000800,
20};
diff --git a/src/ext/Util/ca/scaexec.cpp b/src/ext/Util/ca/scaexec.cpp
index 5845c1b4..7bd271d1 100644
--- a/src/ext/Util/ca/scaexec.cpp
+++ b/src/ext/Util/ca/scaexec.cpp
@@ -2,7 +2,6 @@
2 2
3#include "precomp.h" 3#include "precomp.h"
4 4
5
6/******************************************************************** 5/********************************************************************
7 * CreateSmb - CUSTOM ACTION ENTRY POINT for creating fileshares 6 * CreateSmb - CUSTOM ACTION ENTRY POINT for creating fileshares
8 * 7 *
@@ -520,55 +519,88 @@ static HRESULT ModifyUserLocalBatchRight(
520 return hr; 519 return hr;
521} 520}
522 521
523static void SetUserPasswordAndAttributes( 522static HRESULT ApplyAttributes(int iAttributes, DWORD* pFlags)
524 __in USER_INFO_1* puserInfo,
525 __in LPWSTR wzPassword,
526 __in int iAttributes
527 )
528{ 523{
529 Assert(puserInfo); 524 HRESULT hr = S_OK;
530
531 // Set the User's password
532 puserInfo->usri1_password = wzPassword;
533 525
534 // Apply the Attributes
535 if (SCAU_DONT_EXPIRE_PASSWRD & iAttributes) 526 if (SCAU_DONT_EXPIRE_PASSWRD & iAttributes)
536 { 527 {
537 puserInfo->usri1_flags |= UF_DONT_EXPIRE_PASSWD; 528 *pFlags |= UF_DONT_EXPIRE_PASSWD;
538 } 529 }
539 else 530 else
540 { 531 {
541 puserInfo->usri1_flags &= ~UF_DONT_EXPIRE_PASSWD; 532 *pFlags &= ~UF_DONT_EXPIRE_PASSWD;
542 } 533 }
543 534
544 if (SCAU_PASSWD_CANT_CHANGE & iAttributes) 535 if (SCAU_PASSWD_CANT_CHANGE & iAttributes)
545 { 536 {
546 puserInfo->usri1_flags |= UF_PASSWD_CANT_CHANGE; 537 *pFlags |= UF_PASSWD_CANT_CHANGE;
547 } 538 }
548 else 539 else
549 { 540 {
550 puserInfo->usri1_flags &= ~UF_PASSWD_CANT_CHANGE; 541 *pFlags &= ~UF_PASSWD_CANT_CHANGE;
551 } 542 }
552 543
553 if (SCAU_DISABLE_ACCOUNT & iAttributes) 544 if (SCAU_DISABLE_ACCOUNT & iAttributes)
554 { 545 {
555 puserInfo->usri1_flags |= UF_ACCOUNTDISABLE; 546 *pFlags |= UF_ACCOUNTDISABLE;
556 } 547 }
557 else 548 else
558 { 549 {
559 puserInfo->usri1_flags &= ~UF_ACCOUNTDISABLE; 550 *pFlags &= ~UF_ACCOUNTDISABLE;
560 } 551 }
561 552
562 if (SCAU_PASSWD_CHANGE_REQD_ON_LOGIN & iAttributes) // TODO: for some reason this doesn't work 553 if (SCAU_PASSWD_CHANGE_REQD_ON_LOGIN & iAttributes) // TODO: for some reason this doesn't work
563 { 554 {
564 puserInfo->usri1_flags |= UF_PASSWORD_EXPIRED; 555 *pFlags |= UF_PASSWORD_EXPIRED;
565 } 556 }
566 else 557 else
567 { 558 {
568 puserInfo->usri1_flags &= ~UF_PASSWORD_EXPIRED; 559 *pFlags &= ~UF_PASSWORD_EXPIRED;
560 }
561
562 return hr;
563}
564
565static HRESULT ApplyComment(int iAttributes, LPWSTR pwzComment, LPWSTR* ppComment)
566{
567 HRESULT hr = S_OK;
568
569 if (SCAU_REMOVE_COMMENT & iAttributes)
570 {
571 *ppComment = L"";
572 }
573 else if (pwzComment && *pwzComment)
574 {
575 *ppComment = pwzComment;
569 } 576 }
577
578 return hr;
579}
580
581static NET_API_STATUS SetUserPassword(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in LPWSTR pwzPassword)
582{
583 _USER_INFO_1003 userInfo1003;
584
585 userInfo1003.usri1003_password = pwzPassword;
586 return ::NetUserSetInfo(pwzServerName, pwzName, 1003, reinterpret_cast<LPBYTE>(&userInfo1003), NULL);
570} 587}
571 588
589static NET_API_STATUS SetUserComment(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in LPWSTR pwzComment)
590{
591 _USER_INFO_1007 userInfo1007;
592
593 userInfo1007.usri1007_comment = pwzComment;
594 return ::NetUserSetInfo(pwzServerName, pwzName, 1007, reinterpret_cast<LPBYTE>(&userInfo1007), NULL);
595}
596
597static NET_API_STATUS SetUserFlags(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in DWORD flags)
598{
599 _USER_INFO_1008 userInfo1008;
600
601 userInfo1008.usri1008_flags = flags;
602 return ::NetUserSetInfo(pwzServerName, pwzName, 1008, reinterpret_cast<LPBYTE>(&userInfo1008), NULL);
603}
572 604
573static HRESULT RemoveUserInternal( 605static HRESULT RemoveUserInternal(
574 LPWSTR wzGroupCaData, 606 LPWSTR wzGroupCaData,
@@ -624,7 +656,12 @@ static HRESULT RemoveUserInternal(
624 } 656 }
625 if (ERROR_SUCCESS == er) 657 if (ERROR_SUCCESS == er)
626 { 658 {
627 wz = pDomainControllerInfo->DomainControllerName + 2; //Add 2 so that we don't get the \\ prefix 659 if (2 <= wcslen(pDomainControllerInfo->DomainControllerName))
660 {
661 wz = pDomainControllerInfo->DomainControllerName + 2; // Add 2 so that we don't get the \\ prefix.
662 // Pass the entire string if it is too short
663 // to have a \\ prefix.
664 }
628 } 665 }
629 else 666 else
630 { 667 {
@@ -680,6 +717,41 @@ LExit:
680 return hr; 717 return hr;
681} 718}
682 719
720static HRESULT GetServerName(LPWSTR pwzDomain, LPWSTR* ppwzServerName)
721{
722 HRESULT hr = S_OK;
723
724 PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL;
725 UINT er;
726
727 if (pwzDomain && *pwzDomain)
728 {
729 er = ::DsGetDcNameW(NULL, (LPCWSTR)pwzDomain, NULL, NULL, NULL, &pDomainControllerInfo);
730 if (RPC_S_SERVER_UNAVAILABLE == er)
731 {
732 // MSDN says, if we get the above error code, try again with the "DS_FORCE_REDISCOVERY" flag
733 er = ::DsGetDcNameW(NULL, (LPCWSTR)pwzDomain, NULL, NULL, DS_FORCE_REDISCOVERY, &pDomainControllerInfo);
734 }
735 if (ERROR_SUCCESS == er
736 && 2 <= wcslen(pDomainControllerInfo->DomainControllerName)
737 && '\\' == *pDomainControllerInfo->DomainControllerName
738 && '\\' == *pDomainControllerInfo->DomainControllerName + 1)
739 {
740 *ppwzServerName = pDomainControllerInfo->DomainControllerName + 2; // Skip the \\ prefix
741 }
742 else
743 {
744 *ppwzServerName = pwzDomain;
745 }
746 }
747
748 if (pDomainControllerInfo)
749 {
750 ::NetApiBufferFree((LPVOID)pDomainControllerInfo);
751 }
752
753 return hr;
754}
683 755
684/******************************************************************** 756/********************************************************************
685 CreateUser - CUSTOM ACTION ENTRY POINT for creating users 757 CreateUser - CUSTOM ACTION ENTRY POINT for creating users
@@ -688,7 +760,7 @@ LExit:
688 * *****************************************************************/ 760 * *****************************************************************/
689extern "C" UINT __stdcall CreateUser( 761extern "C" UINT __stdcall CreateUser(
690 __in MSIHANDLE hInstall 762 __in MSIHANDLE hInstall
691 ) 763)
692{ 764{
693 //AssertSz(0, "Debug CreateUser"); 765 //AssertSz(0, "Debug CreateUser");
694 766
@@ -699,11 +771,11 @@ extern "C" UINT __stdcall CreateUser(
699 LPWSTR pwz = NULL; 771 LPWSTR pwz = NULL;
700 LPWSTR pwzName = NULL; 772 LPWSTR pwzName = NULL;
701 LPWSTR pwzDomain = NULL; 773 LPWSTR pwzDomain = NULL;
774 LPWSTR pwzComment = NULL;
702 LPWSTR pwzScriptKey = NULL; 775 LPWSTR pwzScriptKey = NULL;
703 LPWSTR pwzPassword = NULL; 776 LPWSTR pwzPassword = NULL;
704 LPWSTR pwzGroup = NULL; 777 LPWSTR pwzGroup = NULL;
705 LPWSTR pwzGroupDomain = NULL; 778 LPWSTR pwzGroupDomain = NULL;
706 PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL;
707 int iAttributes = 0; 779 int iAttributes = 0;
708 BOOL fInitializedCom = FALSE; 780 BOOL fInitializedCom = FALSE;
709 781
@@ -711,10 +783,10 @@ extern "C" UINT __stdcall CreateUser(
711 int iOriginalAttributes = 0; 783 int iOriginalAttributes = 0;
712 int iRollbackAttributes = 0; 784 int iRollbackAttributes = 0;
713 785
714 USER_INFO_1 userInfo; 786 USER_INFO_1 userInfo1;
715 USER_INFO_1* puserInfo = NULL; 787 USER_INFO_1* pUserInfo1 = NULL;
716 DWORD dw; 788 DWORD dw;
717 LPCWSTR wz = NULL; 789 LPWSTR pwzServerName = NULL;
718 790
719 hr = WcaInitialize(hInstall, "CreateUser"); 791 hr = WcaInitialize(hInstall, "CreateUser");
720 ExitOnFailure(hr, "failed to initialize"); 792 ExitOnFailure(hr, "failed to initialize");
@@ -723,7 +795,7 @@ extern "C" UINT __stdcall CreateUser(
723 ExitOnFailure(hr, "failed to initialize COM"); 795 ExitOnFailure(hr, "failed to initialize COM");
724 fInitializedCom = TRUE; 796 fInitializedCom = TRUE;
725 797
726 hr = WcaGetProperty( L"CustomActionData", &pwzData); 798 hr = WcaGetProperty(L"CustomActionData", &pwzData);
727 ExitOnFailure(hr, "failed to get CustomActionData"); 799 ExitOnFailure(hr, "failed to get CustomActionData");
728 800
729 WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); 801 WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData);
@@ -738,6 +810,9 @@ extern "C" UINT __stdcall CreateUser(
738 hr = WcaReadStringFromCaData(&pwz, &pwzDomain); 810 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
739 ExitOnFailure(hr, "failed to read domain from custom action data"); 811 ExitOnFailure(hr, "failed to read domain from custom action data");
740 812
813 hr = WcaReadStringFromCaData(&pwz, &pwzComment);
814 ExitOnFailure(hr, "failed to read user comment from custom action data");
815
741 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); 816 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
742 ExitOnFailure(hr, "failed to read attributes from custom action data"); 817 ExitOnFailure(hr, "failed to read attributes from custom action data");
743 818
@@ -747,96 +822,155 @@ extern "C" UINT __stdcall CreateUser(
747 hr = WcaReadStringFromCaData(&pwz, &pwzPassword); 822 hr = WcaReadStringFromCaData(&pwz, &pwzPassword);
748 ExitOnFailure(hr, "failed to read password from custom action data"); 823 ExitOnFailure(hr, "failed to read password from custom action data");
749 824
750 // There is no rollback scheduled if the key is empty.
751 // Best effort to get original configuration and save it in the script so rollback can restore it.
752 if (*pwzScriptKey)
753 {
754 hr = WcaCaScriptCreate(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, FALSE, &hRollbackScript);
755 ExitOnFailure(hr, "Failed to open rollback CustomAction script.");
756
757 iRollbackAttributes = 0;
758 hr = GetExistingUserRightsAssignments(pwzDomain, pwzName, &iOriginalAttributes);
759 if (FAILED(hr))
760 {
761 WcaLogError(hr, "failed to get existing user rights: %ls, continuing anyway.", pwzName);
762 }
763 else
764 {
765 if (!(SCAU_ALLOW_LOGON_AS_SERVICE & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes))
766 {
767 iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_SERVICE;
768 }
769 if (!(SCAU_ALLOW_LOGON_AS_BATCH & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_BATCH & iAttributes))
770 {
771 iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_BATCH;
772 }
773 }
774
775 hr = WcaCaScriptWriteNumber(hRollbackScript, iRollbackAttributes);
776 ExitOnFailure(hr, "Failed to add data to rollback script.");
777
778 // Nudge the system to get all our rollback data written to disk.
779 WcaCaScriptFlush(hRollbackScript);
780 }
781
782 if (!(SCAU_DONT_CREATE_USER & iAttributes)) 825 if (!(SCAU_DONT_CREATE_USER & iAttributes))
783 { 826 {
784 ::ZeroMemory(&userInfo, sizeof(USER_INFO_1)); 827 pUserInfo1 = &userInfo1;
785 userInfo.usri1_name = pwzName; 828 ::ZeroMemory(pUserInfo1, sizeof(USER_INFO_1));
786 userInfo.usri1_priv = USER_PRIV_USER; 829 pUserInfo1->usri1_name = pwzName;
787 userInfo.usri1_flags = UF_SCRIPT; 830 pUserInfo1->usri1_priv = USER_PRIV_USER;
788 userInfo.usri1_home_dir = NULL; 831 pUserInfo1->usri1_flags = UF_SCRIPT;
789 userInfo.usri1_comment = NULL; 832 pUserInfo1->usri1_home_dir = NULL;
790 userInfo.usri1_script_path = NULL; 833 pUserInfo1->usri1_comment = NULL;
791 834 pUserInfo1->usri1_script_path = NULL;
792 SetUserPasswordAndAttributes(&userInfo, pwzPassword, iAttributes); 835
836 // Set the user's password
837 pUserInfo1->usri1_password = pwzPassword;
838
839 // Set the user's comment
840 hr = ApplyComment(iAttributes, pwzComment, &pUserInfo1->usri1_comment);
841 ExitOnFailure(hr, "failed to apply comment");
842
843 // Set the user's flags
844 hr = ApplyAttributes(iAttributes, &pUserInfo1->usri1_flags);
845 ExitOnFailure(hr, "failed to apply attributes");
793 846
794 // 847 //
795 // Create the User 848 // Create the User
796 // 849 //
797 if (pwzDomain && *pwzDomain) 850 hr = GetServerName(pwzDomain, &pwzServerName);
851 ExitOnFailure(hr, "failed to get server name");
852
853 er = ::NetUserAdd(pwzServerName, 1, reinterpret_cast<LPBYTE>(pUserInfo1), &dw);
854 if (NERR_UserExists == er)
798 { 855 {
799 er = ::DsGetDcNameW( NULL, (LPCWSTR)pwzDomain, NULL, NULL, NULL, &pDomainControllerInfo ); 856 er = ERROR_SUCCESS; // Make sure that we don't report this situation as an error
800 if (RPC_S_SERVER_UNAVAILABLE == er) 857 // if we fall through the tests that follow.
801 { 858 if (SCAU_FAIL_IF_EXISTS & iAttributes)
802 // MSDN says, if we get the above error code, try again with the "DS_FORCE_REDISCOVERY" flag
803 er = ::DsGetDcNameW( NULL, (LPCWSTR)pwzDomain, NULL, NULL, DS_FORCE_REDISCOVERY, &pDomainControllerInfo );
804 }
805 if (ERROR_SUCCESS == er)
806 { 859 {
807 wz = pDomainControllerInfo->DomainControllerName + 2; //Add 2 so that we don't get the \\ prefix 860 hr = HRESULT_FROM_WIN32(er);
861 ExitOnFailure(hr, "User was not supposed to exist, but does.");
808 } 862 }
809 else
810 {
811 wz = pwzDomain;
812 }
813 }
814 863
815 er = ::NetUserAdd(wz, 1, reinterpret_cast<LPBYTE>(&userInfo), &dw);
816 if (NERR_UserExists == er)
817 {
818 if (SCAU_UPDATE_IF_EXISTS & iAttributes) 864 if (SCAU_UPDATE_IF_EXISTS & iAttributes)
819 { 865 {
820 er = ::NetUserGetInfo(wz, pwzName, 1, reinterpret_cast<LPBYTE*>(&puserInfo)); 866 pUserInfo1 = NULL;
821 if (NERR_Success == er) 867 er = ::NetUserGetInfo(pwzServerName, pwzName, 1, reinterpret_cast<LPBYTE*>(&pUserInfo1));
868 if (ERROR_SUCCESS == er)
822 { 869 {
823 // Change the existing user's password and attributes again then try 870 // There is no rollback scheduled if the key is empty.
824 // to update user with this new data 871 // Best effort to get original configuration and save it in the script so rollback can restore it.
825 SetUserPasswordAndAttributes(puserInfo, pwzPassword, iAttributes); 872
873 if (*pwzScriptKey)
874 {
875 // Try to open the rollback script
876 hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript);
877
878 if (INVALID_HANDLE_VALUE != hRollbackScript)
879 {
880 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE);
881 }
882 else
883 {
884 hRollbackScript = NULL;
885 hr = WcaCaScriptCreate(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, FALSE, &hRollbackScript);
886 ExitOnFailure(hr, "Failed to open rollback CustomAction script.");
887
888 iRollbackAttributes = 0;
889 hr = GetExistingUserRightsAssignments(pwzDomain, pwzName, &iOriginalAttributes);
890 if (FAILED(hr))
891 {
892 WcaLogError(hr, "failed to get existing user rights: %ls, continuing anyway.", pwzName);
893 }
894 else
895 {
896 if (!(SCAU_ALLOW_LOGON_AS_SERVICE & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes))
897 {
898 iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_SERVICE;
899 }
900
901 if (!(SCAU_ALLOW_LOGON_AS_BATCH & iOriginalAttributes) && (SCAU_ALLOW_LOGON_AS_BATCH & iAttributes))
902 {
903 iRollbackAttributes |= SCAU_ALLOW_LOGON_AS_BATCH;
904 }
905 }
906
907 hr = WcaCaScriptWriteString(hRollbackScript, pUserInfo1->usri1_comment);
908 ExitOnFailure(hr, "Failed to add rollback comment to rollback script.");
909
910 if (!pUserInfo1->usri1_comment || !*pUserInfo1->usri1_comment)
911 {
912 iRollbackAttributes |= SCAU_REMOVE_COMMENT;
913 }
914
915 hr = WcaCaScriptWriteNumber(hRollbackScript, iRollbackAttributes);
916 ExitOnFailure(hr, "Failed to add rollback attributes to rollback script.");
917
918 // Nudge the system to get all our rollback data written to disk.
919 WcaCaScriptFlush(hRollbackScript);
920 }
921 }
922 }
826 923
827 er = ::NetUserSetInfo(wz, pwzName, 1, reinterpret_cast<LPBYTE>(puserInfo), &dw); 924 if (ERROR_SUCCESS == er)
925 {
926 hr = HRESULT_FROM_WIN32(::SetUserPassword(pwzServerName, pwzName, pwzPassword));
927 if (FAILED(hr))
928 {
929 WcaLogError(hr, "failed to set user password for user %ls\\%ls, continuing anyway.", pwzServerName, pwzName);
930 }
931
932 if (SCAU_REMOVE_COMMENT & iAttributes)
933 {
934 hr = HRESULT_FROM_WIN32(SetUserComment(pwzServerName, pwzName, L""));
935 if (FAILED(hr))
936 {
937 WcaLogError(hr, "failed to clear user comment for user %ls\\%ls, continuing anyway.", pwzServerName, pwzName);
938 }
939 }
940 else if (pwzComment && *pwzComment)
941 {
942 hr = HRESULT_FROM_WIN32(SetUserComment(pwzServerName, pwzName, pwzComment));
943 if (FAILED(hr))
944 {
945 WcaLogError(hr, "failed to set user comment to %ls for user %ls\\%ls, continuing anyway.", pwzComment, pwzServerName, pwzName);
946 }
947 }
948
949 DWORD flags = pUserInfo1->usri1_flags;
950
951 hr = ApplyAttributes(iAttributes, &flags);
952 if (FAILED(hr))
953 {
954 WcaLogError(hr, "failed to apply attributes for user %ls\\%ls, continuing anyway.", pwzServerName, pwzName);
955 }
956
957 hr = HRESULT_FROM_WIN32(SetUserFlags(pwzServerName, pwzName, flags));
958 if (FAILED(hr))
959 {
960 WcaLogError(hr, "failed to set user flags for user %ls\\%ls, continuing anyway.", pwzServerName, pwzName);
961 }
828 } 962 }
829 } 963 }
830 else if (!(SCAU_FAIL_IF_EXISTS & iAttributes))
831 {
832 er = NERR_Success;
833 }
834 } 964 }
835 else if (NERR_PasswordTooShort == er || NERR_PasswordTooLong == er) 965 else if (NERR_PasswordTooShort == er || NERR_PasswordTooLong == er)
836 { 966 {
837 MessageExitOnFailure(hr = HRESULT_FROM_WIN32(er), msierrUSRFailedUserCreatePswd, "failed to create user: %ls due to invalid password.", pwzName); 967 MessageExitOnFailure(hr = HRESULT_FROM_WIN32(er), msierrUSRFailedUserCreatePswd, "failed to create user: %ls due to invalid password.", pwzName);
838 } 968 }
839 MessageExitOnFailure(hr = HRESULT_FROM_WIN32(er), msierrUSRFailedUserCreate, "failed to create user: %ls", pwzName); 969
970 if (ERROR_SUCCESS != er)
971 {
972 MessageExitOnFailure(hr = HRESULT_FROM_WIN32(er), msierrUSRFailedUserCreate, "failed to create user: %ls", pwzName);
973 }
840 } 974 }
841 975
842 if (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes) 976 if (SCAU_ALLOW_LOGON_AS_SERVICE & iAttributes)
@@ -851,14 +985,15 @@ extern "C" UINT __stdcall CreateUser(
851 MessageExitOnFailure(hr, msierrUSRFailedGrantLogonAsService, "Failed to grant logon as batch job rights to user: %ls", pwzName); 985 MessageExitOnFailure(hr, msierrUSRFailedGrantLogonAsService, "Failed to grant logon as batch job rights to user: %ls", pwzName);
852 } 986 }
853 987
854 // 988//
855 // Add the users to groups 989// Add the users to groups
856 // 990//
857 while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup))) 991while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup)))
858 { 992{
859 hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain); 993 hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain);
860 ExitOnFailure(hr, "failed to get domain for group: %ls", pwzGroup); 994 ExitOnFailure(hr, "failed to get domain for group: %ls", pwzGroup);
861 995
996 WcaLog(LOGMSG_STANDARD, "Adding user %ls\\%ls to group %ls\\%ls", pwzDomain, pwzName, pwzGroupDomain, pwzGroup);
862 hr = AddUserToGroup(pwzName, pwzDomain, pwzGroup, pwzGroupDomain); 997 hr = AddUserToGroup(pwzName, pwzDomain, pwzGroup, pwzGroupDomain);
863 MessageExitOnFailure(hr, msierrUSRFailedUserGroupAdd, "failed to add user: %ls to group %ls", pwzName, pwzGroup); 998 MessageExitOnFailure(hr, msierrUSRFailedUserGroupAdd, "failed to add user: %ls to group %ls", pwzName, pwzGroup);
864 } 999 }
@@ -866,24 +1001,23 @@ extern "C" UINT __stdcall CreateUser(
866 { 1001 {
867 hr = S_OK; 1002 hr = S_OK;
868 } 1003 }
1004
869 ExitOnFailure(hr, "failed to get next group in which to include user:%ls", pwzName); 1005 ExitOnFailure(hr, "failed to get next group in which to include user:%ls", pwzName);
870 1006
1007ExitOnFailure(hr, "failed to get next group in which to include user:%ls", pwzName);
1008
871LExit: 1009LExit:
872 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE); 1010 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE);
873 1011
874 if (puserInfo) 1012 if (pUserInfo1 && pUserInfo1 != &userInfo1)
875 { 1013 {
876 ::NetApiBufferFree((LPVOID)puserInfo); 1014 ::NetApiBufferFree((LPVOID)pUserInfo1);
877 }
878
879 if (pDomainControllerInfo)
880 {
881 ::NetApiBufferFree((LPVOID)pDomainControllerInfo);
882 } 1015 }
883 1016
884 ReleaseStr(pwzData); 1017 ReleaseStr(pwzData);
885 ReleaseStr(pwzName); 1018 ReleaseStr(pwzName);
886 ReleaseStr(pwzDomain); 1019 ReleaseStr(pwzDomain);
1020 ReleaseStr(pwzComment);
887 ReleaseStr(pwzScriptKey); 1021 ReleaseStr(pwzScriptKey);
888 ReleaseStr(pwzPassword); 1022 ReleaseStr(pwzPassword);
889 ReleaseStr(pwzGroup); 1023 ReleaseStr(pwzGroup);
@@ -922,15 +1056,17 @@ extern "C" UINT __stdcall CreateUserRollback(
922 1056
923 LPWSTR pwzData = NULL; 1057 LPWSTR pwzData = NULL;
924 LPWSTR pwz = NULL; 1058 LPWSTR pwz = NULL;
1059 LPWSTR pwzScriptKey = NULL;
925 LPWSTR pwzName = NULL; 1060 LPWSTR pwzName = NULL;
926 LPWSTR pwzDomain = NULL; 1061 LPWSTR pwzDomain = NULL;
927 LPWSTR pwzScriptKey = NULL; 1062 LPWSTR pwzComment = NULL;
928 int iAttributes = 0; 1063 int iAttributes = 0;
929 BOOL fInitializedCom = FALSE; 1064 BOOL fInitializedCom = FALSE;
930 1065
931 WCA_CASCRIPT_HANDLE hRollbackScript = NULL; 1066 WCA_CASCRIPT_HANDLE hRollbackScript = NULL;
932 LPWSTR pwzRollbackData = NULL; 1067 LPWSTR pwzRollbackData = NULL;
933 int iOriginalAttributes = 0; 1068 int iOriginalAttributes = 0;
1069 LPWSTR pwzOriginalComment = NULL;
934 1070
935 hr = WcaInitialize(hInstall, "CreateUserRollback"); 1071 hr = WcaInitialize(hInstall, "CreateUserRollback");
936 ExitOnFailure(hr, "failed to initialize"); 1072 ExitOnFailure(hr, "failed to initialize");
@@ -957,6 +1093,9 @@ extern "C" UINT __stdcall CreateUserRollback(
957 hr = WcaReadStringFromCaData(&pwz, &pwzDomain); 1093 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
958 ExitOnFailure(hr, "failed to read domain from custom action data"); 1094 ExitOnFailure(hr, "failed to read domain from custom action data");
959 1095
1096 hr = WcaReadStringFromCaData(&pwz, &pwzComment);
1097 ExitOnFailure(hr, "failed to read comment from custom action data");
1098
960 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); 1099 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
961 ExitOnFailure(hr, "failed to read attributes from custom action data"); 1100 ExitOnFailure(hr, "failed to read attributes from custom action data");
962 1101
@@ -978,6 +1117,15 @@ extern "C" UINT __stdcall CreateUserRollback(
978 WcaLog(LOGMSG_TRACEONLY, "Rollback Data: %ls", pwzRollbackData); 1117 WcaLog(LOGMSG_TRACEONLY, "Rollback Data: %ls", pwzRollbackData);
979 1118
980 pwz = pwzRollbackData; 1119 pwz = pwzRollbackData;
1120 hr = WcaReadStringFromCaData(&pwz, &pwzOriginalComment);
1121 if (FAILED(hr))
1122 {
1123 WcaLogError(hr, "failed to read comment from rollback data, continuing anyway");
1124 }
1125 else
1126 {
1127 pwzComment = pwzOriginalComment;
1128 }
981 hr = WcaReadIntegerFromCaData(&pwz, &iOriginalAttributes); 1129 hr = WcaReadIntegerFromCaData(&pwz, &iOriginalAttributes);
982 if (FAILED(hr)) 1130 if (FAILED(hr))
983 { 1131 {
@@ -998,8 +1146,10 @@ LExit:
998 ReleaseStr(pwzData); 1146 ReleaseStr(pwzData);
999 ReleaseStr(pwzName); 1147 ReleaseStr(pwzName);
1000 ReleaseStr(pwzDomain); 1148 ReleaseStr(pwzDomain);
1149 ReleaseStr(pwzComment);
1001 ReleaseStr(pwzScriptKey); 1150 ReleaseStr(pwzScriptKey);
1002 ReleaseStr(pwzRollbackData); 1151 ReleaseStr(pwzRollbackData);
1152 ReleaseStr(pwzOriginalComment);
1003 1153
1004 if (fInitializedCom) 1154 if (fInitializedCom)
1005 { 1155 {
@@ -1033,6 +1183,7 @@ extern "C" UINT __stdcall RemoveUser(
1033 LPWSTR pwz = NULL; 1183 LPWSTR pwz = NULL;
1034 LPWSTR pwzName = NULL; 1184 LPWSTR pwzName = NULL;
1035 LPWSTR pwzDomain = NULL; 1185 LPWSTR pwzDomain = NULL;
1186 LPWSTR pwzComment = NULL;
1036 int iAttributes = 0; 1187 int iAttributes = 0;
1037 BOOL fInitializedCom = FALSE; 1188 BOOL fInitializedCom = FALSE;
1038 1189
@@ -1058,6 +1209,9 @@ extern "C" UINT __stdcall RemoveUser(
1058 hr = WcaReadStringFromCaData(&pwz, &pwzDomain); 1209 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
1059 ExitOnFailure(hr, "failed to read domain from custom action data"); 1210 ExitOnFailure(hr, "failed to read domain from custom action data");
1060 1211
1212 hr = WcaReadStringFromCaData(&pwz, &pwzComment);
1213 ExitOnFailure(hr, "failed to read comment from custom action data");
1214
1061 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); 1215 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
1062 ExitOnFailure(hr, "failed to read attributes from custom action data"); 1216 ExitOnFailure(hr, "failed to read attributes from custom action data");
1063 1217
@@ -1067,6 +1221,7 @@ LExit:
1067 ReleaseStr(pwzData); 1221 ReleaseStr(pwzData);
1068 ReleaseStr(pwzName); 1222 ReleaseStr(pwzName);
1069 ReleaseStr(pwzDomain); 1223 ReleaseStr(pwzDomain);
1224 ReleaseStr(pwzComment);
1070 1225
1071 if (fInitializedCom) 1226 if (fInitializedCom)
1072 { 1227 {
diff --git a/src/ext/Util/ca/scauser.cpp b/src/ext/Util/ca/scauser.cpp
index f92ebf1b..dc5bebba 100644
--- a/src/ext/Util/ca/scauser.cpp
+++ b/src/ext/Util/ca/scauser.cpp
@@ -2,8 +2,8 @@
2 2
3#include "precomp.h" 3#include "precomp.h"
4 4
5LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Password` FROM `Wix4User` WHERE `User`=?"; 5LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Comment`, `Password` FROM `Wix4User` WHERE `User`=?";
6enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqPassword }; 6enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqComment, vuqPassword };
7 7
8LPCWSTR vcsGroupQuery = L"SELECT `Group`, `Component_`, `Name`, `Domain` FROM `Wix4Group` WHERE `Group`=?"; 8LPCWSTR vcsGroupQuery = L"SELECT `Group`, `Component_`, `Name`, `Domain` FROM `Wix4Group` WHERE `Group`=?";
9enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain }; 9enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain };
@@ -11,8 +11,8 @@ enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain };
11LPCWSTR vcsUserGroupQuery = L"SELECT `User_`, `Group_` FROM `Wix4UserGroup` WHERE `User_`=?"; 11LPCWSTR vcsUserGroupQuery = L"SELECT `User_`, `Group_` FROM `Wix4UserGroup` WHERE `User_`=?";
12enum eUserGroupQuery { vugqUser = 1, vugqGroup }; 12enum eUserGroupQuery { vugqUser = 1, vugqGroup };
13 13
14LPCWSTR vActionableQuery = L"SELECT `User`,`Component_`,`Name`,`Domain`,`Password`,`Attributes` FROM `Wix4User` WHERE `Component_` IS NOT NULL"; 14LPCWSTR vActionableQuery = L"SELECT `User`,`Component_`,`Name`,`Domain`,`Password`,`Comment`,`Attributes` FROM `Wix4User` WHERE `Component_` IS NOT NULL";
15enum eActionableQuery { vaqUser = 1, vaqComponent, vaqName, vaqDomain, vaqPassword, vaqAttributes }; 15enum eActionableQuery { vaqUser = 1, vaqComponent, vaqName, vaqDomain, vaqPassword, vaqComment, vaqAttributes };
16 16
17 17
18static HRESULT AddUserToList( 18static HRESULT AddUserToList(
@@ -78,6 +78,11 @@ HRESULT __stdcall ScaGetUser(
78 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData); 78 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData);
79 ExitOnFailure(hr, "Failed to copy domain string to user object"); 79 ExitOnFailure(hr, "Failed to copy domain string to user object");
80 80
81 hr = WcaGetRecordFormattedString(hRec, vuqComment, &pwzData);
82 ExitOnFailure(hr, "Failed to get Wix4User.Comment");
83 hr = ::StringCchCopyW(pscau->wzComment, countof(pscau->wzComment), pwzData);
84 ExitOnFailure(hr, "Failed to copy comment string to user object");
85
81 hr = WcaGetRecordFormattedString(hRec, vuqPassword, &pwzData); 86 hr = WcaGetRecordFormattedString(hRec, vuqPassword, &pwzData);
82 ExitOnFailure(hr, "Failed to get Wix4User.Password"); 87 ExitOnFailure(hr, "Failed to get Wix4User.Password");
83 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData); 88 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData);
@@ -154,6 +159,11 @@ HRESULT __stdcall ScaGetUserDeferred(
154 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData); 159 hr = ::StringCchCopyW(pscau->wzDomain, countof(pscau->wzDomain), pwzData);
155 ExitOnFailure(hr, "Failed to copy domain string to user object (in deferred CA)"); 160 ExitOnFailure(hr, "Failed to copy domain string to user object (in deferred CA)");
156 161
162 hr = WcaGetRecordString(hRec, vuqComment, &pwzData);
163 ExitOnFailure(hr, "Failed to get Wix4User.Comment");
164 hr = ::StringCchCopyW(pscau->wzComment, countof(pscau->wzComment), pwzData);
165 ExitOnFailure(hr, "Failed to copy comment string to user object (in deferred CA)");
166
157 hr = WcaGetRecordString(hRec, vuqPassword, &pwzData); 167 hr = WcaGetRecordString(hRec, vuqPassword, &pwzData);
158 ExitOnFailure(hr, "Failed to get Wix4User.Password"); 168 ExitOnFailure(hr, "Failed to get Wix4User.Password");
159 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData); 169 hr = ::StringCchCopyW(pscau->wzPassword, countof(pscau->wzPassword), pwzData);
@@ -316,7 +326,7 @@ HRESULT ScaUserRead(
316 ExitOnFailure(hr, "failed to get Component state for Wix4User"); 326 ExitOnFailure(hr, "failed to get Component state for Wix4User");
317 327
318 // don't bother if we aren't installing or uninstalling this component 328 // don't bother if we aren't installing or uninstalling this component
319 if (WcaIsInstalling(isInstalled, isAction) || WcaIsUninstalling(isInstalled, isAction)) 329 if (WcaIsInstalling(isInstalled, isAction) || WcaIsUninstalling(isInstalled, isAction))
320 { 330 {
321 // 331 //
322 // Add the user to the list and populate it's values 332 // Add the user to the list and populate it's values
@@ -345,6 +355,10 @@ HRESULT ScaUserRead(
345 ExitOnFailure(hr, "failed to get Wix4User.Domain"); 355 ExitOnFailure(hr, "failed to get Wix4User.Domain");
346 hr = ::StringCchCopyW(psu->wzDomain, countof(psu->wzDomain), pwzData); 356 hr = ::StringCchCopyW(psu->wzDomain, countof(psu->wzDomain), pwzData);
347 ExitOnFailure(hr, "failed to copy user domain: %ls", pwzData); 357 ExitOnFailure(hr, "failed to copy user domain: %ls", pwzData);
358 hr = WcaGetRecordFormattedString(hRec, vaqComment, &pwzData);
359 ExitOnFailure(hr, "failed to get Wix4User.Comment");
360 hr = ::StringCchCopyW(psu->wzComment, countof(psu->wzComment), pwzData);
361 ExitOnFailure(hr, "failed to copy user comment: %ls", pwzData);
348 362
349 hr = WcaGetRecordFormattedString(hRec, vaqPassword, &pwzData); 363 hr = WcaGetRecordFormattedString(hRec, vaqPassword, &pwzData);
350 ExitOnFailure(hr, "failed to get Wix4User.Password"); 364 ExitOnFailure(hr, "failed to get Wix4User.Password");
@@ -492,15 +506,16 @@ HRESULT ScaUserExecute(
492 { 506 {
493 USER_EXISTS ueUserExists = USER_EXISTS_INDETERMINATE; 507 USER_EXISTS ueUserExists = USER_EXISTS_INDETERMINATE;
494 508
495 // Always put the User Name and Domain plus Attributes on the front of the CustomAction 509 // Always put the User Name, Domain, and Comment on the front of the CustomAction data.
496 // data. Sometimes we'll add more data. 510 // The attributes will be added when we have finished adjusting them. Sometimes we'll
511 // add more data.
497 Assert(psu->wzName); 512 Assert(psu->wzName);
498 hr = WcaWriteStringToCaData(psu->wzName, &pwzActionData); 513 hr = WcaWriteStringToCaData(psu->wzName, &pwzActionData);
499 ExitOnFailure(hr, "Failed to add user name to custom action data: %ls", psu->wzName); 514 ExitOnFailure(hr, "Failed to add user name to custom action data: %ls", psu->wzName);
500 hr = WcaWriteStringToCaData(psu->wzDomain, &pwzActionData); 515 hr = WcaWriteStringToCaData(psu->wzDomain, &pwzActionData);
501 ExitOnFailure(hr, "Failed to add user domain to custom action data: %ls", psu->wzDomain); 516 ExitOnFailure(hr, "Failed to add user domain to custom action data: %ls", psu->wzDomain);
502 hr = WcaWriteIntegerToCaData(psu->iAttributes, &pwzActionData); 517 hr = WcaWriteStringToCaData(psu->wzComment, &pwzActionData);
503 ExitOnFailure(hr, "failed to add user attributes to custom action data for user: %ls", psu->wzKey); 518 ExitOnFailure(hr, "Failed to add user comment to custom action data: %ls", psu->wzComment);
504 519
505 // Check to see if the user already exists since we have to be very careful when adding 520 // Check to see if the user already exists since we have to be very careful when adding
506 // and removing users. Note: MSDN says that it is safe to call these APIs from any 521 // and removing users. Note: MSDN says that it is safe to call these APIs from any
@@ -520,7 +535,12 @@ HRESULT ScaUserExecute(
520 } 535 }
521 if (ERROR_SUCCESS == er) 536 if (ERROR_SUCCESS == er)
522 { 537 {
523 wzDomain = pDomainControllerInfo->DomainControllerName + 2; //Add 2 so that we don't get the \\ prefix 538 if (2 <= wcslen(pDomainControllerInfo->DomainControllerName))
539 {
540 wzDomain = pDomainControllerInfo->DomainControllerName + 2; // Add 2 so that we don't get the \\ prefix.
541 // Pass the entire string if it is too short
542 // to have a \\ prefix.
543 }
524 } 544 }
525 } 545 }
526 546
@@ -544,23 +564,32 @@ HRESULT ScaUserExecute(
544 564
545 if (WcaIsInstalling(psu->isInstalled, psu->isAction)) 565 if (WcaIsInstalling(psu->isInstalled, psu->isAction))
546 { 566 {
547 // If the user exists, check to see if we are supposed to fail if user the exists before 567 // If the user exists, check to see if we are supposed to fail if the user exists before
548 // the install. 568 // the install.
549 if (USER_EXISTS_YES == ueUserExists) 569 if (USER_EXISTS_YES == ueUserExists)
550 { 570 {
551 // Reinstalls will always fail if we don't remove the check for "fail if exists". 571 // Re-installs will always fail if we don't remove the check for "fail if exists".
552 if (WcaIsReInstalling(psu->isInstalled, psu->isAction)) 572 if (WcaIsReInstalling(psu->isInstalled, psu->isAction))
553 { 573 {
554 psu->iAttributes &= ~SCAU_FAIL_IF_EXISTS; 574 psu->iAttributes &= ~SCAU_FAIL_IF_EXISTS;
575
576 // If install would create the user, re-install should be able to update the user.
577 if (!(psu->iAttributes & SCAU_DONT_CREATE_USER))
578 {
579 psu->iAttributes |= SCAU_UPDATE_IF_EXISTS;
580 }
555 } 581 }
556 582
557 if ((SCAU_FAIL_IF_EXISTS & (psu->iAttributes)) && !(SCAU_UPDATE_IF_EXISTS & (psu->iAttributes))) 583 if (SCAU_FAIL_IF_EXISTS & psu->iAttributes && !(SCAU_UPDATE_IF_EXISTS & psu->iAttributes))
558 { 584 {
559 hr = HRESULT_FROM_WIN32(NERR_UserExists); 585 hr = HRESULT_FROM_WIN32(NERR_UserExists);
560 MessageExitOnFailure(hr, msierrUSRFailedUserCreateExists, "Failed to create user: %ls because user already exists.", psu->wzName); 586 MessageExitOnFailure(hr, msierrUSRFailedUserCreateExists, "Failed to create user: %ls because user already exists.", psu->wzName);
561 } 587 }
562 } 588 }
563 589
590 hr = WcaWriteIntegerToCaData(psu->iAttributes, &pwzActionData);
591 ExitOnFailure(hr, "failed to add user attributes to custom action data for user: %ls", psu->wzKey);
592
564 // Rollback only if the user already exists, we couldn't determine if the user exists, or we are going to create the user 593 // Rollback only if the user already exists, we couldn't determine if the user exists, or we are going to create the user
565 if ((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists) || !(psu->iAttributes & SCAU_DONT_CREATE_USER)) 594 if ((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists) || !(psu->iAttributes & SCAU_DONT_CREATE_USER))
566 { 595 {
@@ -597,7 +626,6 @@ HRESULT ScaUserExecute(
597 ExitOnFailure(hr, "Failed to add user domain to rollback custom action data: %ls", psu->wzDomain); 626 ExitOnFailure(hr, "Failed to add user domain to rollback custom action data: %ls", psu->wzDomain);
598 hr = WcaWriteIntegerToCaData(iRollbackUserAttributes, &pwzRollbackData); 627 hr = WcaWriteIntegerToCaData(iRollbackUserAttributes, &pwzRollbackData);
599 ExitOnFailure(hr, "failed to add user attributes to rollback custom action data for user: %ls", psu->wzKey); 628 ExitOnFailure(hr, "failed to add user attributes to rollback custom action data for user: %ls", psu->wzKey);
600
601 // If the user already exists, add relevant group information to rollback data 629 // If the user already exists, add relevant group information to rollback data
602 if (USER_EXISTS_YES == ueUserExists || USER_EXISTS_INDETERMINATE == ueUserExists) 630 if (USER_EXISTS_YES == ueUserExists || USER_EXISTS_INDETERMINATE == ueUserExists)
603 { 631 {
@@ -630,11 +658,13 @@ HRESULT ScaUserExecute(
630 } 658 }
631 else if (((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists)) && WcaIsUninstalling(psu->isInstalled, psu->isAction) && !(psu->iAttributes & SCAU_DONT_REMOVE_ON_UNINSTALL)) 659 else if (((USER_EXISTS_YES == ueUserExists) || (USER_EXISTS_INDETERMINATE == ueUserExists)) && WcaIsUninstalling(psu->isInstalled, psu->isAction) && !(psu->iAttributes & SCAU_DONT_REMOVE_ON_UNINSTALL))
632 { 660 {
661 hr = WcaWriteIntegerToCaData(psu->iAttributes, &pwzActionData);
662 ExitOnFailure(hr, "failed to add user attributes to custom action data for user: %ls", psu->wzKey);
663
633 // Add user's group information - this will ensure the user can be removed from any groups they were added to, if the user isn't be deleted 664 // Add user's group information - this will ensure the user can be removed from any groups they were added to, if the user isn't be deleted
634 hr = WriteGroupInfo(psu->psgGroups, &pwzActionData); 665 hr = WriteGroupInfo(psu->psgGroups, &pwzActionData);
635 ExitOnFailure(hr, "failed to add group information to custom action data"); 666 ExitOnFailure(hr, "failed to add group information to custom action data");
636 667
637 //
638 // Schedule the removal because the user exists and we don't have any flags set 668 // Schedule the removal because the user exists and we don't have any flags set
639 // that say, don't remove the user on uninstall. 669 // that say, don't remove the user on uninstall.
640 // 670 //
@@ -642,7 +672,7 @@ HRESULT ScaUserExecute(
642 // CustomAction. 672 // CustomAction.
643 hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RemoveUser"), pwzActionData, COST_USER_DELETE); 673 hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RemoveUser"), pwzActionData, COST_USER_DELETE);
644 ExitOnFailure(hr, "failed to schedule RemoveUser"); 674 ExitOnFailure(hr, "failed to schedule RemoveUser");
645 } 675 }
646 676
647 ReleaseNullStr(pwzScriptKey); 677 ReleaseNullStr(pwzScriptKey);
648 ReleaseNullStr(pwzActionData); 678 ReleaseNullStr(pwzActionData);
diff --git a/src/ext/Util/ca/scauser.h b/src/ext/Util/ca/scauser.h
index a5fd5ea8..3da847b5 100644
--- a/src/ext/Util/ca/scauser.h
+++ b/src/ext/Util/ca/scauser.h
@@ -31,6 +31,7 @@ struct SCA_USER
31 WCHAR wzDomain[MAX_DARWIN_COLUMN + 1]; 31 WCHAR wzDomain[MAX_DARWIN_COLUMN + 1];
32 WCHAR wzName[MAX_DARWIN_COLUMN + 1]; 32 WCHAR wzName[MAX_DARWIN_COLUMN + 1];
33 WCHAR wzPassword[MAX_DARWIN_COLUMN + 1]; 33 WCHAR wzPassword[MAX_DARWIN_COLUMN + 1];
34 WCHAR wzComment[MAX_DARWIN_COLUMN + 1];
34 INT iAttributes; 35 INT iAttributes;
35 36
36 SCA_GROUP *psgGroups; 37 SCA_GROUP *psgGroups;
@@ -41,16 +42,16 @@ struct SCA_USER
41 42
42// prototypes 43// prototypes
43HRESULT __stdcall ScaGetUser( 44HRESULT __stdcall ScaGetUser(
44 __in LPCWSTR wzUser, 45 __in LPCWSTR wzUser,
45 __out SCA_USER* pscau 46 __out SCA_USER* pscau
46 ); 47 );
47HRESULT __stdcall ScaGetUserDeferred( 48HRESULT __stdcall ScaGetUserDeferred(
48 __in LPCWSTR wzUser, 49 __in LPCWSTR wzUser,
49 __in WCA_WRAPQUERY_HANDLE hUserQuery, 50 __in WCA_WRAPQUERY_HANDLE hUserQuery,
50 __out SCA_USER* pscau 51 __out SCA_USER* pscau
51 ); 52 );
52HRESULT __stdcall ScaGetGroup( 53HRESULT __stdcall ScaGetGroup(
53 __in LPCWSTR wzGroup, 54 __in LPCWSTR wzGroup,
54 __out SCA_GROUP* pscag 55 __out SCA_GROUP* pscag
55 ); 56 );
56void ScaUserFreeList( 57void ScaUserFreeList(