From 7b1bb025dea1d1e9e144cce0dcbba2d86f053b8f Mon Sep 17 00:00:00 2001 From: Bevan Weiss Date: Tue, 18 Jun 2024 19:03:40 +1000 Subject: CreateGroups additions Signed-off-by: Bevan Weiss --- src/ext/Util/ca/CustomMsiErrors.h | 6 +- src/ext/Util/ca/sca.h | 12 + src/ext/Util/ca/scacost.h | 2 + src/ext/Util/ca/scaexec.cpp | 628 ++++++++++++++++++++++++++++++++++++++ src/ext/Util/ca/scagroup.cpp | 503 ++++++++++++++++++++++++++++++ src/ext/Util/ca/scagroup.h | 47 +++ src/ext/Util/ca/scanet.cpp | 50 +++ src/ext/Util/ca/scanet.h | 4 + src/ext/Util/ca/scasched.cpp | 47 ++- src/ext/Util/ca/scauser.cpp | 83 ----- src/ext/Util/ca/scauser.h | 20 +- src/ext/Util/ca/utilca.def | 4 + src/ext/Util/ca/utilca.vcxproj | 4 + 13 files changed, 1306 insertions(+), 104 deletions(-) create mode 100644 src/ext/Util/ca/scagroup.cpp create mode 100644 src/ext/Util/ca/scagroup.h create mode 100644 src/ext/Util/ca/scanet.cpp create mode 100644 src/ext/Util/ca/scanet.h (limited to 'src/ext/Util/ca') diff --git a/src/ext/Util/ca/CustomMsiErrors.h b/src/ext/Util/ca/CustomMsiErrors.h index 3218b61b..ac0c549f 100644 --- a/src/ext/Util/ca/CustomMsiErrors.h +++ b/src/ext/Util/ca/CustomMsiErrors.h @@ -29,4 +29,8 @@ #define msierrUSRFailedUserCreateExists 26404 #define msierrUSRFailedGrantLogonAsService 26405 -//Last available is 26450 \ No newline at end of file +#define msierrGRPFailedGroupCreate 26421 +#define msierrGRPFailedGroupGroupAdd 26422 +#define msierrGRPFailedGroupCreateExists 26423 + +//Last available is 26450 diff --git a/src/ext/Util/ca/sca.h b/src/ext/Util/ca/sca.h index 84f5ffd9..0e25a19a 100644 --- a/src/ext/Util/ca/sca.h +++ b/src/ext/Util/ca/sca.h @@ -18,3 +18,15 @@ enum SCAU_ATTRIBUTES SCAU_NON_VITAL = 0x00000400, SCAU_REMOVE_COMMENT = 0x00000800, }; + +// group creation attributes definitions +enum SCAG_ATTRIBUTES +{ + SCAG_FAIL_IF_EXISTS = 0x00000001, + SCAG_UPDATE_IF_EXISTS = 0x00000002, + + SCAG_DONT_REMOVE_ON_UNINSTALL = 0x00000004, + SCAG_DONT_CREATE_GROUP = 0x00000008, + SCAG_NON_VITAL = 0x00000010, + SCAG_REMOVE_COMMENT = 0x00000020, +}; diff --git a/src/ext/Util/ca/scacost.h b/src/ext/Util/ca/scacost.h index 5b215035..978e40bc 100644 --- a/src/ext/Util/ca/scacost.h +++ b/src/ext/Util/ca/scacost.h @@ -9,6 +9,8 @@ const UINT COST_SMB_CREATESMB = 10000; const UINT COST_SMB_DROPSMB = 5000; const UINT COST_USER_ADD = 10000; const UINT COST_USER_DELETE = 10000; +const UINT COST_GROUP_ADD = 10000; +const UINT COST_GROUP_DELETE = 10000; const UINT COST_PERFMONMANIFEST_REGISTER = 1000; const UINT COST_PERFMONMANIFEST_UNREGISTER = 1000; diff --git a/src/ext/Util/ca/scaexec.cpp b/src/ext/Util/ca/scaexec.cpp index 8579b8bb..5a750c6b 100644 --- a/src/ext/Util/ca/scaexec.cpp +++ b/src/ext/Util/ca/scaexec.cpp @@ -291,6 +291,145 @@ LExit: return hr; } +static HRESULT AddGroupToGroup( + __in LPWSTR wzMember, + __in LPCWSTR wzMemberDomain, + __in LPCWSTR wzGroup, + __in LPCWSTR wzGroupDomain +) +{ + Assert(wzMember && *wzMember && wzMemberDomain && wzGroup && *wzGroup && wzGroupDomain); + + HRESULT hr = S_OK; + IADsGroup* pGroup = NULL; + BSTR bstrMember = NULL; + BSTR bstrGroup = NULL; + LPWSTR pwzMember = NULL; + LPWSTR pwzServerName = NULL; + LOCALGROUP_MEMBERS_INFO_3 lgmi {}; + + GetDomainServerName(wzGroupDomain, &pwzServerName); + + // Try adding it to the local group + if (wzMemberDomain) + { + hr = StrAllocFormatted(&pwzMember, L"%s\\%s", wzMemberDomain, wzMember); + ExitOnFailure(hr, "failed to allocate group domain string"); + } + + lgmi.lgrmi3_domainandname = (NULL == pwzMember ? wzMember : pwzMember); + NET_API_STATUS ui = ::NetLocalGroupAddMembers(pwzServerName, wzGroup, 3, reinterpret_cast(&lgmi), 1); + hr = HRESULT_FROM_WIN32(ui); + if (HRESULT_FROM_WIN32(ERROR_MEMBER_IN_ALIAS) == hr) // if they're already a member of the group don't report an error + { + hr = S_OK; + } + + // + // If we failed, try active directory + // + if (FAILED(hr)) + { + WcaLog(LOGMSG_VERBOSE, "Failed to add group: %ls, domain %ls to group: %ls, domain: %ls with error 0x%x. Attempting to use Active Directory", wzMember, wzMemberDomain, wzGroup, wzGroupDomain, hr); + + hr = UserCreateADsPath(wzMemberDomain, wzMember, &bstrMember); + ExitOnFailure(hr, "failed to create group ADsPath for group: %ls domain: %ls", wzMember, wzMemberDomain); + + hr = UserCreateADsPath(wzGroupDomain, wzGroup, &bstrGroup); + ExitOnFailure(hr, "failed to create group ADsPath for group: %ls domain: %ls", wzGroup, wzGroupDomain); + + hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast(&pGroup)); + ExitOnFailure(hr, "Failed to get group '%ls'.", reinterpret_cast(bstrGroup)); + + hr = pGroup->Add(bstrMember); + if ((HRESULT_FROM_WIN32(ERROR_OBJECT_ALREADY_EXISTS) == hr) || (HRESULT_FROM_WIN32(ERROR_MEMBER_IN_ALIAS) == hr)) + hr = S_OK; + + ExitOnFailure(hr, "Failed to add group %ls to group '%ls'.", reinterpret_cast(bstrMember), reinterpret_cast(bstrGroup)); + } + +LExit: + ReleaseStr(pwzServerName); + ReleaseStr(pwzMember); + ReleaseBSTR(bstrMember); + ReleaseBSTR(bstrGroup); + ReleaseObject(pGroup); + + return hr; +} + +static HRESULT RemoveGroupFromGroup( + __in LPWSTR wzMember, + __in LPCWSTR wzMemberDomain, + __in LPCWSTR wzGroup, + __in LPCWSTR wzGroupDomain +) +{ + Assert(wzMember && *wzMember && wzMemberDomain && wzGroup && *wzGroup && wzGroupDomain); + + HRESULT hr = S_OK; + IADsGroup* pGroup = NULL; + BSTR bstrMember = NULL; + BSTR bstrGroup = NULL; + LPWSTR pwzMember = NULL; + LPWSTR pwzServerName = NULL; + LOCALGROUP_MEMBERS_INFO_3 lgmi {}; + + GetDomainServerName(wzGroupDomain, &pwzServerName, DS_WRITABLE_REQUIRED); + + // Try removing it from the local group + if (wzMemberDomain) + { + hr = StrAllocFormatted(&pwzMember, L"%s\\%s", wzMemberDomain, wzMember); + ExitOnFailure(hr, "failed to allocate group domain string"); + } + + lgmi.lgrmi3_domainandname = (NULL == pwzMember ? wzMember : pwzMember); + NET_API_STATUS ui = ::NetLocalGroupDelMembers(pwzServerName, wzGroup, 3, reinterpret_cast(&lgmi), 1); + hr = HRESULT_FROM_WIN32(ui); + if (HRESULT_FROM_WIN32(ERROR_MEMBER_NOT_IN_ALIAS) == hr + || HRESULT_FROM_WIN32(NERR_GroupNotFound) == hr + || HRESULT_FROM_WIN32(ERROR_NO_SUCH_MEMBER) == hr) // if they're already not a member of the group, or the group doesn't exist, don't report an error + { + hr = S_OK; + } + + // + // If we failed, try active directory + // + if (FAILED(hr)) + { + WcaLog(LOGMSG_VERBOSE, "Failed to remove group: %ls, domain %ls from group: %ls, domain: %ls with error 0x%x. Attempting to use Active Directory", wzMember, wzMemberDomain, wzGroup, wzGroupDomain, hr); + + hr = UserCreateADsPath(wzMemberDomain, wzMember, &bstrMember); + ExitOnFailure(hr, "failed to create group ADsPath in order to remove group: %ls domain: %ls from a group", wzMember, wzMemberDomain); + + hr = UserCreateADsPath(wzGroupDomain, wzGroup, &bstrGroup); + ExitOnFailure(hr, "failed to create group ADsPath in order to remove group from group: %ls domain: %ls", wzGroup, wzGroupDomain); + + hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast(&pGroup)); + if ((HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)) // if parent group not found, no need to remove membership from group + { + hr = S_OK; + ExitFunction(); + } + ExitOnFailure(hr, "Failed to get group '%ls'.", reinterpret_cast(bstrGroup)); + + hr = pGroup->Remove(bstrMember); + if ((HRESULT_FROM_WIN32(ERROR_MEMBER_NOT_IN_ALIAS) == hr)) // if already not a member, no need to worry about error + hr = S_OK; + ExitOnFailure(hr, "Failed to remove group %ls from group '%ls'.", reinterpret_cast(bstrMember), reinterpret_cast(bstrGroup)); + } + +LExit: + ReleaseStr(pwzServerName); + ReleaseStr(pwzMember); + ReleaseBSTR(bstrMember); + ReleaseBSTR(bstrGroup); + ReleaseObject(pGroup); + + return hr; +} static HRESULT GetUserHasRight( __in LSA_HANDLE hPolicy, @@ -590,6 +729,15 @@ static HRESULT SetUserComment(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __ return HRESULT_FROM_WIN32(er); } +static HRESULT SetGroupComment(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in LPWSTR pwzComment) +{ + _LOCALGROUP_INFO_1002 groupInfo1002 {}; + + groupInfo1002.lgrpi1002_comment = pwzComment; + NET_API_STATUS er = ::NetLocalGroupSetInfo(pwzServerName, pwzName, 1002, reinterpret_cast(&groupInfo1002), NULL); + return HRESULT_FROM_WIN32(er); +} + static HRESULT SetUserFlags(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in DWORD flags) { NET_API_STATUS er = NERR_Success; @@ -693,6 +841,76 @@ LExit: return hr; } +static HRESULT RemoveGroupInternal( + LPWSTR wzGroupCaData, + LPWSTR wzDomain, + LPWSTR wzName, + int iAttributes +) +{ + HRESULT hr = S_OK; + + LPWSTR pwz = NULL; + LPWSTR pwzGroup = NULL; + LPWSTR pwzGroupDomain = NULL; + LPWSTR pwzServerName = NULL; + + // + // Remove the Group if the group was created by us. + // + if (!(SCAG_DONT_CREATE_GROUP & iAttributes)) + { + GetDomainServerName(wzDomain, &pwzServerName, DS_WRITABLE_REQUIRED); + + NET_API_STATUS er = ::NetLocalGroupDel(pwzServerName, wzName); + hr = HRESULT_FROM_WIN32(er); + if (HRESULT_FROM_WIN32(ERROR_NO_SUCH_ALIAS) == hr + || HRESULT_FROM_WIN32(NERR_GroupNotFound) == hr) // we wanted to delete it.. and the group doesn't exist.. solved. + { + hr = S_OK; + } + ExitOnFailure(hr, "failed to delete group: %ls", wzName); + } + else + { + // + // Remove the group from other groups + // + pwz = wzGroupCaData; + while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup))) + { + hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain); + + if (FAILED(hr)) + { + WcaLogError(hr, "failed to get domain for group: %ls, continuing anyway.", pwzGroup); + } + else + { + hr = RemoveGroupFromGroup(wzName, wzDomain, pwzGroup, pwzGroupDomain); + if (FAILED(hr)) + { + WcaLogError(hr, "failed to remove group: %ls from group %ls, continuing anyway.", wzName, pwzGroup); + } + } + } + + if (E_NOMOREITEMS == hr) // if there are no more items, all is well + { + hr = S_OK; + } + + ExitOnFailure(hr, "failed to get next group from which to remove group:%ls", wzName); + } +LExit: + ReleaseStr(pwzServerName); + ReleaseStr(pwzGroup); + ReleaseStr(pwzGroupDomain); + + return hr; +} + + /******************************************************************** CreateUser - CUSTOM ACTION ENTRY POINT for creating users @@ -1173,3 +1391,413 @@ LExit: return WcaFinalize(er); } + +/******************************************************************** + CreateGroup - CUSTOM ACTION ENTRY POINT for creating groups + + Input: deferred CustomActionData - GroupName\tDomain\tComment\tAttributes + * *****************************************************************/ +extern "C" UINT __stdcall CreateGroup( + __in MSIHANDLE hInstall +) +{ + //AssertSz(0, "Debug CreateGroup"); + + HRESULT hr = S_OK; + NET_API_STATUS er = ERROR_SUCCESS; + + LPWSTR pwzData = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzName = NULL; + LPWSTR pwzDomain = NULL; + LPWSTR pwzComment = NULL; + LPWSTR pwzScriptKey = NULL; + LPWSTR pwzGroup = NULL; + LPWSTR pwzGroupDomain = NULL; + int iAttributes = 0; + BOOL fInitializedCom = FALSE; + + LOCALGROUP_INFO_1* pGroupInfo1 = NULL; + + WCA_CASCRIPT_HANDLE hRollbackScript = NULL; + int iRollbackAttributes = 0; + + DWORD dw; + LPWSTR pwzServerName = NULL; + + hr = WcaInitialize(hInstall, "CreateGroup"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to initialize COM"); + fInitializedCom = TRUE; + + hr = WcaGetProperty(L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); + + // + // Read in the CustomActionData + // + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &pwzName); + ExitOnFailure(hr, "failed to read group name from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &pwzDomain); + ExitOnFailure(hr, "failed to read domain from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &pwzComment); + ExitOnFailure(hr, "failed to read group comment from custom action data"); + + hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); + ExitOnFailure(hr, "failed to read attributes from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey); + ExitOnFailure(hr, "failed to read encoding key from custom action data"); + + if (!(SCAG_DONT_CREATE_GROUP & iAttributes)) + { + hr = GetDomainServerName(pwzDomain, &pwzServerName, DS_WRITABLE_REQUIRED); + ExitOnFailure(hr, "failed to find Domain %ls.", pwzDomain); + + // Set the group's comment + if (SCAG_REMOVE_COMMENT & iAttributes) + { + StrAllocString(&pwzComment, L"", 0); + } + + // + // Create the Group + // + LOCALGROUP_INFO_1 groupInfo1; + groupInfo1.lgrpi1_name = pwzName; + groupInfo1.lgrpi1_comment = pwzComment; + er = ::NetLocalGroupAdd(pwzServerName, 1, reinterpret_cast(&groupInfo1), &dw); + hr = HRESULT_FROM_WIN32(er); + + if (HRESULT_FROM_WIN32(ERROR_ALIAS_EXISTS) == hr + || HRESULT_FROM_WIN32(NERR_GroupExists) == hr) + { + if (SCAG_FAIL_IF_EXISTS & iAttributes) + { + MessageExitOnFailure(hr, msierrGRPFailedGroupCreateExists, "Group (%ls) was not supposed to exist, but does", pwzName); + } + + hr = S_OK; // Make sure that we don't report this situation as an error + // if we fall through the tests that follow. + + if (SCAG_UPDATE_IF_EXISTS & iAttributes) + { + er = ::NetLocalGroupGetInfo(pwzServerName, pwzName, 1, reinterpret_cast(&pGroupInfo1)); + hr = HRESULT_FROM_WIN32(er); + if (S_OK == hr) + { + // There is no rollback scheduled if the key is empty. + // Best effort to get original configuration and save it in the script so rollback can restore it. + if (*pwzScriptKey) + { + // Try to open the rollback script + hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript); + + if (hRollbackScript && INVALID_HANDLE_VALUE != hRollbackScript->hScriptFile) + { + WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE); + } + else + { + hRollbackScript = NULL; + hr = WcaCaScriptCreate(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, FALSE, &hRollbackScript); + ExitOnFailure(hr, "Failed to open rollback CustomAction script."); + + iRollbackAttributes = 0; + + hr = WcaCaScriptWriteString(hRollbackScript, pGroupInfo1->lgrpi1_comment); + ExitOnFailure(hr, "Failed to add rollback comment to rollback script."); + + if (!pGroupInfo1->lgrpi1_comment || !*pGroupInfo1->lgrpi1_comment) + { + iRollbackAttributes |= SCAG_REMOVE_COMMENT; + } + + hr = WcaCaScriptWriteNumber(hRollbackScript, iRollbackAttributes); + ExitOnFailure(hr, "Failed to add rollback attributes to rollback script."); + + // Nudge the system to get all our rollback data written to disk. + WcaCaScriptFlush(hRollbackScript); + } + } + } + + if (S_OK == hr) + { + if (SCAG_REMOVE_COMMENT & iAttributes) + { + hr = SetGroupComment(pwzServerName, pwzName, L""); + if (FAILED(hr)) + { + WcaLogError(hr, "failed to clear comment for group %ls\\%ls, continuing anyway.", pwzServerName, pwzName); + hr = S_OK; + } + } + else if (pwzComment && *pwzComment) + { + hr = SetGroupComment(pwzServerName, pwzName, pwzComment); + if (FAILED(hr)) + { + WcaLogError(hr, "failed to set comment to %ls for group %ls\\%ls, continuing anyway.", pwzComment, pwzServerName, pwzName); + hr = S_OK; + } + } + } + } + } + MessageExitOnFailure(hr, msierrGRPFailedGroupCreate, "failed to create group: %ls", pwzName); + + // + // Add the groups to groups + // + while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup))) + { + hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain); + ExitOnFailure(hr, "failed to get domain for group: %ls", pwzGroup); + + WcaLog(LOGMSG_STANDARD, "Adding group %ls\\%ls to group %ls\\%ls", pwzDomain, pwzName, pwzGroupDomain, pwzGroup); + hr = AddGroupToGroup(pwzName, pwzDomain, pwzGroup, pwzGroupDomain); + MessageExitOnFailure(hr, msierrUSRFailedUserGroupAdd, "failed to add group: %ls to group %ls", pwzName, pwzGroup); + } + if (E_NOMOREITEMS == hr) // if there are no more items, all is well + { + hr = S_OK; + } + ExitOnFailure(hr, "failed to get next group in which to include group: %ls", pwzName); + } + +LExit: + WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE); + + if (pGroupInfo1) + { + ::NetApiBufferFree((LPVOID)pGroupInfo1); + } + + ReleaseStr(pwzData); + ReleaseStr(pwzName); + ReleaseStr(pwzDomain); + ReleaseStr(pwzComment); + ReleaseStr(pwzScriptKey); + ReleaseStr(pwzGroup); + ReleaseStr(pwzGroupDomain); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (SCAG_NON_VITAL & iAttributes) + { + er = ERROR_SUCCESS; + } + else if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + + return WcaFinalize(er); +} + + +/******************************************************************** + CreateGroupRollback - CUSTOM ACTION ENTRY POINT for CreateGroup rollback + + * *****************************************************************/ +extern "C" UINT __stdcall CreateGroupRollback( + MSIHANDLE hInstall +) +{ + //AssertSz(0, "Debug CreateGroupRollback"); + + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + LPWSTR pwzData = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzScriptKey = NULL; + LPWSTR pwzName = NULL; + LPWSTR pwzDomain = NULL; + LPWSTR pwzComment = NULL; + int iAttributes = 0; + BOOL fInitializedCom = FALSE; + + WCA_CASCRIPT_HANDLE hRollbackScript = NULL; + LPWSTR pwzRollbackData = NULL; + int iOriginalAttributes = 0; + LPWSTR pwzOriginalComment = NULL; + + hr = WcaInitialize(hInstall, "CreateGroupRollback"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to initialize COM"); + fInitializedCom = TRUE; + + hr = WcaGetProperty(L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); + + // + // Read in the CustomActionData + // + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey); + ExitOnFailure(hr, "failed to read encoding key from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &pwzName); + ExitOnFailure(hr, "failed to read name from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &pwzDomain); + ExitOnFailure(hr, "failed to read domain from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &pwzComment); + ExitOnFailure(hr, "failed to read comment from custom action data"); + + hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); + ExitOnFailure(hr, "failed to read attributes from custom action data"); + + // Best effort to read original configuration from CreateUser. + hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript); + if (FAILED(hr)) + { + WcaLogError(hr, "Failed to open rollback CustomAction script, continuing anyway."); + } + else + { + hr = WcaCaScriptReadAsCustomActionData(hRollbackScript, &pwzRollbackData); + if (FAILED(hr)) + { + WcaLogError(hr, "Failed to read rollback script into CustomAction data, continuing anyway."); + } + else + { + WcaLog(LOGMSG_TRACEONLY, "Rollback Data: %ls", pwzRollbackData); + + pwz = pwzRollbackData; + hr = WcaReadStringFromCaData(&pwz, &pwzOriginalComment); + if (FAILED(hr)) + { + WcaLogError(hr, "failed to read comment from rollback data, continuing anyway"); + } + else + { + pwzComment = pwzOriginalComment; + } + hr = WcaReadIntegerFromCaData(&pwz, &iOriginalAttributes); + if (FAILED(hr)) + { + WcaLogError(hr, "failed to read attributes from rollback data, continuing anyway"); + } + else + { + iAttributes |= iOriginalAttributes; + } + } + } + + hr = RemoveGroupInternal(pwz, pwzDomain, pwzName, iAttributes); + +LExit: + WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_DELETE); + + ReleaseStr(pwzData); + ReleaseStr(pwzName); + ReleaseStr(pwzDomain); + ReleaseStr(pwzComment); + ReleaseStr(pwzScriptKey); + ReleaseStr(pwzRollbackData); + ReleaseStr(pwzOriginalComment); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + + return WcaFinalize(er); +} + + +/******************************************************************** + RemoveGroup - CUSTOM ACTION ENTRY POINT for removing groups + + Input: deferred CustomActionData - Name\tDomain + * *****************************************************************/ +extern "C" UINT __stdcall RemoveGroup( + MSIHANDLE hInstall +) +{ + //AssertSz(0, "Debug RemoveGroup"); + + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + LPWSTR pwzData = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzName = NULL; + LPWSTR pwzDomain = NULL; + LPWSTR pwzComment = NULL; + int iAttributes = 0; + BOOL fInitializedCom = FALSE; + + hr = WcaInitialize(hInstall, "RemoveGroup"); + ExitOnFailure(hr, "failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to initialize COM"); + fInitializedCom = TRUE; + + hr = WcaGetProperty(L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); + + // + // Read in the CustomActionData + // + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &pwzName); + ExitOnFailure(hr, "failed to read name from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &pwzDomain); + ExitOnFailure(hr, "failed to read domain from custom action data"); + + hr = WcaReadStringFromCaData(&pwz, &pwzComment); + ExitOnFailure(hr, "failed to read comment from custom action data"); + + hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); + ExitOnFailure(hr, "failed to read attributes from custom action data"); + + hr = RemoveGroupInternal(pwz, pwzDomain, pwzName, iAttributes); + +LExit: + ReleaseStr(pwzData); + ReleaseStr(pwzName); + ReleaseStr(pwzDomain); + ReleaseStr(pwzComment); + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + if (FAILED(hr)) + { + er = ERROR_INSTALL_FAILURE; + } + + return WcaFinalize(er); +} diff --git a/src/ext/Util/ca/scagroup.cpp b/src/ext/Util/ca/scagroup.cpp new file mode 100644 index 00000000..c484c1d2 --- /dev/null +++ b/src/ext/Util/ca/scagroup.cpp @@ -0,0 +1,503 @@ +// 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. + +#include "precomp.h" +#include "scanet.h" + +LPCWSTR vcsGroupQuery = L"SELECT `Group`, `Component_`, `Name`, `Domain` FROM `Wix4Group` WHERE `Group`=?"; +enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain }; + +LPCWSTR vcsGroupGroupQuery = L"SELECT `Parent_`, `Child_` FROM `Wix6GroupGroup` WHERE `Child_`=?"; +enum eGroupGroupQuery { vggqParent = 1, vggqChild }; + +LPCWSTR vActionableGroupQuery = L"SELECT `Group`,`Component_`,`Name`,`Domain`,`Comment`,`Attributes` FROM `Wix4Group`,`Wix6Group` WHERE `Component_` IS NOT NULL AND `Group`=`Group_`"; +enum eActionableGroupQuery { vagqGroup = 1, vagqComponent, vagqName, vagqDomain, vagqComment, vagqAttributes }; + +static HRESULT AddGroupToList( + __inout SCA_GROUP** ppsgList + ); + + +HRESULT __stdcall ScaGetGroup( + __in LPCWSTR wzGroup, + __out SCA_GROUP* pscag + ) +{ + if (!wzGroup || *wzGroup==0 || !pscag) + { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + PMSIHANDLE hView, hRec; + + LPWSTR pwzData = NULL; + + hRec = ::MsiCreateRecord(1); + hr = WcaSetRecordString(hRec, 1, wzGroup); + ExitOnFailure(hr, "Failed to look up Group"); + + hr = WcaOpenView(vcsGroupQuery, &hView); + ExitOnFailure(hr, "Failed to open view on Wix4Group table"); + hr = WcaExecuteView(hView, hRec); + ExitOnFailure(hr, "Failed to execute view on Wix4Group table"); + + hr = WcaFetchSingleRecord(hView, &hRec); + if (S_OK == hr) + { + hr = WcaGetRecordString(hRec, vgqGroup, &pwzData); + ExitOnFailure(hr, "Failed to get Wix4Group.Group"); + hr = ::StringCchCopyW(pscag->wzKey, countof(pscag->wzKey), pwzData); + ExitOnFailure(hr, "Failed to copy key string to group object"); + + hr = WcaGetRecordString(hRec, vgqComponent, &pwzData); + ExitOnFailure(hr, "Failed to get Wix4Group.Component_"); + hr = ::StringCchCopyW(pscag->wzComponent, countof(pscag->wzComponent), pwzData); + ExitOnFailure(hr, "Failed to copy component string to group object"); + + hr = WcaGetRecordFormattedString(hRec, vgqName, &pwzData); + ExitOnFailure(hr, "Failed to get Wix4Group.Name"); + hr = ::StringCchCopyW(pscag->wzName, countof(pscag->wzName), pwzData); + ExitOnFailure(hr, "Failed to copy name string to group object"); + + hr = WcaGetRecordFormattedString(hRec, vgqDomain, &pwzData); + ExitOnFailure(hr, "Failed to get Wix4Group.Domain"); + hr = ::StringCchCopyW(pscag->wzDomain, countof(pscag->wzDomain), pwzData); + ExitOnFailure(hr, "Failed to copy domain string to group object"); + } + else if (E_NOMOREITEMS == hr) + { + WcaLog(LOGMSG_STANDARD, "Error: Cannot locate Wix4Group.Group='%ls'", wzGroup); + hr = E_FAIL; + } + else + { + ExitOnFailure(hr, "Error or found multiple matching Wix4Group rows"); + } + +LExit: + ReleaseStr(pwzData); + + return hr; +} + +HRESULT __stdcall ScaGetGroupDeferred( + __in LPCWSTR wzGroup, + __in WCA_WRAPQUERY_HANDLE hGroupQuery, + __out SCA_USER* pscag + ) +{ + if (!wzGroup || !pscag) + { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + MSIHANDLE hRec, hRecTest; + + LPWSTR pwzData = NULL; + + // clear struct and bail right away if no group key was passed to search for + ::ZeroMemory(pscag, sizeof(*pscag)); + if (!*wzGroup) + { + ExitFunction1(hr = S_OK); + } + + // Reset back to the first record + WcaFetchWrappedReset(hGroupQuery); + + hr = WcaFetchWrappedRecordWhereString(hGroupQuery, vgqGroup, wzGroup, &hRec); + if (S_OK == hr) + { + hr = WcaFetchWrappedRecordWhereString(hGroupQuery, vgqGroup, wzGroup, &hRecTest); + if (S_OK == hr) + { + AssertSz(FALSE, "Found multiple matching Wix4Group rows"); + } + + hr = WcaGetRecordString(hRec, vgqGroup, &pwzData); + ExitOnFailure(hr, "Failed to get Wix4Group.Group"); + hr = ::StringCchCopyW(pscag->wzKey, countof(pscag->wzKey), pwzData); + ExitOnFailure(hr, "Failed to copy key string to group object (in deferred CA)"); + + hr = WcaGetRecordString(hRec, vgqComponent, &pwzData); + ExitOnFailure(hr, "Failed to get Wix4Group.Component_"); + hr = ::StringCchCopyW(pscag->wzComponent, countof(pscag->wzComponent), pwzData); + ExitOnFailure(hr, "Failed to copy component string to group object (in deferred CA)"); + + hr = WcaGetRecordString(hRec, vgqName, &pwzData); + ExitOnFailure(hr, "Failed to get Wix4Group.Name"); + hr = ::StringCchCopyW(pscag->wzName, countof(pscag->wzName), pwzData); + ExitOnFailure(hr, "Failed to copy name string to group object (in deferred CA)"); + + hr = WcaGetRecordString(hRec, vgqDomain, &pwzData); + ExitOnFailure(hr, "Failed to get Wix4Group.Domain"); + hr = ::StringCchCopyW(pscag->wzDomain, countof(pscag->wzDomain), pwzData); + ExitOnFailure(hr, "Failed to copy domain string to group object (in deferred CA)"); + } + else if (E_NOMOREITEMS == hr) + { + WcaLog(LOGMSG_STANDARD, "Error: Cannot locate Wix4Group.Group='%ls'", wzGroup); + hr = E_FAIL; + } + else + { + ExitOnFailure(hr, "Error fetching single Wix4Group row"); + } + +LExit: + ReleaseStr(pwzData); + + return hr; +} + +void ScaGroupFreeList( + __in SCA_GROUP* psgList + ) +{ + SCA_GROUP* psgDelete = psgList; + while (psgList) + { + psgDelete = psgList; + psgList = psgList->psgNext; + + MemFree(psgDelete); + } +} + + +HRESULT ScaGroupRead( + __out SCA_GROUP** ppsgList + ) +{ + //Assert(FALSE); + Assert(ppsgList); + + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + PMSIHANDLE hView, hRec, hGroupRec, hGroupGroupView; + + LPWSTR pwzData = NULL; + + BOOL fGroupGroupExists = FALSE; + + SCA_GROUP *psg = NULL; + + INSTALLSTATE isInstalled, isAction; + + if (S_OK != WcaTableExists(L"Wix4Group")) + { + WcaLog(LOGMSG_VERBOSE, "Wix4Group Table does not exist, exiting"); + ExitFunction1(hr = S_FALSE); + } + if (S_OK != WcaTableExists(L"Wix6Group")) + { + WcaLog(LOGMSG_VERBOSE, "Wix6Group Table does not exist, exiting"); + ExitFunction1(hr = S_FALSE); + } + + if (S_OK == WcaTableExists(L"Wix6GroupGroup")) + { + fGroupGroupExists = TRUE; + } + + // + // loop through all the groups + // + hr = WcaOpenExecuteView(vActionableGroupQuery, &hView); + ExitOnFailure(hr, "failed to open view on Wix4Group,Wix6Group table(s)"); + while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) + { + hr = WcaGetRecordString(hRec, vagqComponent, &pwzData); + ExitOnFailure(hr, "failed to get Wix4Group.Component"); + + er = ::MsiGetComponentStateW(WcaGetInstallHandle(), pwzData, &isInstalled, &isAction); + hr = HRESULT_FROM_WIN32(er); + ExitOnFailure(hr, "failed to get Component state for Wix4Group"); + + // don't bother if we aren't installing or uninstalling this component + if (WcaIsInstalling(isInstalled, isAction) || WcaIsUninstalling(isInstalled, isAction)) + { + // + // Add the group to the list and populate it's values + // + hr = AddGroupToList(ppsgList); + ExitOnFailure(hr, "failed to add group to list"); + + psg = *ppsgList; + + psg->isInstalled = isInstalled; + psg->isAction = isAction; + hr = ::StringCchCopyW(psg->wzComponent, countof(psg->wzComponent), pwzData); + ExitOnFailure(hr, "failed to copy component name: %ls", pwzData); + + hr = WcaGetRecordString(hRec, vagqGroup, &pwzData); + ExitOnFailure(hr, "failed to get Wix4Group.Group"); + hr = ::StringCchCopyW(psg->wzKey, countof(psg->wzKey), pwzData); + ExitOnFailure(hr, "failed to copy group key: %ls", pwzData); + + hr = WcaGetRecordFormattedString(hRec, vagqName, &pwzData); + ExitOnFailure(hr, "failed to get Wix4Group.Name"); + hr = ::StringCchCopyW(psg->wzName, countof(psg->wzName), pwzData); + ExitOnFailure(hr, "failed to copy group name: %ls", pwzData); + + hr = WcaGetRecordFormattedString(hRec, vagqDomain, &pwzData); + ExitOnFailure(hr, "failed to get Wix4Group.Domain"); + hr = ::StringCchCopyW(psg->wzDomain, countof(psg->wzDomain), pwzData); + ExitOnFailure(hr, "failed to copy group domain: %ls", pwzData); + hr = WcaGetRecordFormattedString(hRec, vagqComment, &pwzData); + ExitOnFailure(hr, "failed to get Wix6Group.Comment"); + hr = ::StringCchCopyW(psg->wzComment, countof(psg->wzComment), pwzData); + ExitOnFailure(hr, "failed to copy group comment: %ls", pwzData); + + hr = WcaGetRecordInteger(hRec, vagqAttributes, &psg->iAttributes); + ExitOnFailure(hr, "failed to get Wix6Group.Attributes"); + + // Check if this group is to be added to any other groups + if (fGroupGroupExists) + { + hGroupRec = ::MsiCreateRecord(1); + hr = WcaSetRecordString(hGroupRec, 1, psg->wzKey); + ExitOnFailure(hr, "Failed to create group record for querying Wix6GroupGroup table"); + + hr = WcaOpenExecuteView(vcsGroupGroupQuery, &hGroupGroupView); + ExitOnFailure(hr, "Failed to open view on Wix6GroupGroup table for group %ls", psg->wzKey);/* + hr = WcaExecuteView(hGroupGroupView, hGroupRec); + ExitOnFailure(hr, "Failed to execute view on Wix6GroupGroup table for group: %ls", psg->wzKey);*/ + + while (S_OK == (hr = WcaFetchRecord(hGroupGroupView, &hRec))) + { + hr = WcaGetRecordString(hRec, vggqParent, &pwzData); + ExitOnFailure(hr, "failed to get Wix6GroupGroup.Parent"); + + hr = AddGroupToList(&(psg->psgGroups)); + ExitOnFailure(hr, "failed to add group to list"); + + hr = ScaGetGroup(pwzData, psg->psgGroups); + ExitOnFailure(hr, "failed to get information for group: %ls", pwzData); + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "failed to enumerate selected rows from Wix4UserGroup table"); + } + } + } + + if (E_NOMOREITEMS == hr) + { + hr = S_OK; + } + ExitOnFailure(hr, "failed to enumerate selected rows from Wix4Group table"); + +LExit: + ReleaseStr(pwzData); + + return hr; +} + +/* **************************************************************** +ScaGroupExecute - Schedules group account creation or removal based on +component state. + +******************************************************************/ +HRESULT ScaGroupExecute( + __in SCA_GROUP *psgList + ) +{ + HRESULT hr = S_OK; + DWORD er = 0; + + LPWSTR pwzBaseScriptKey = NULL; + DWORD cScriptKey = 0; + + LOCALGROUP_INFO_0 *pGroupInfo = NULL; + LPWSTR pwzScriptKey = NULL; + LPWSTR pwzActionData = NULL; + LPWSTR pwzRollbackData = NULL; + LPWSTR pwzServerName = NULL; + + // Get the base script key for this CustomAction. + hr = WcaCaScriptCreateKey(&pwzBaseScriptKey); + ExitOnFailure(hr, "Failed to get encoding key."); + + // Loop through all the users to be configured. + for (SCA_GROUP *psg = psgList; psg; psg = psg->psgNext) + { + GROUP_EXISTS geGroupExists = GROUP_EXISTS_INDETERMINATE; + + // Always put the Group Name, Domain, and Comment on the front of the CustomAction data. + // The attributes will be added when we have finished adjusting them. Sometimes we'll + // add more data. + Assert(psg->wzName); + hr = WcaWriteStringToCaData(psg->wzName, &pwzActionData); + ExitOnFailure(hr, "Failed to add group name to custom action data: %ls", psg->wzName); + hr = WcaWriteStringToCaData(psg->wzDomain, &pwzActionData); + ExitOnFailure(hr, "Failed to add group domain to custom action data: %ls", psg->wzDomain); + hr = WcaWriteStringToCaData(psg->wzComment, &pwzActionData); + ExitOnFailure(hr, "Failed to add group comment to custom action data: %ls", psg->wzComment); + + // Check to see if the group already exists since we have to be very careful when adding + // and removing groups. Note: MSDN says that it is safe to call these APIs from any + // user, so we should be safe calling it during immediate mode. + + LPCWSTR wzDomain = psg->wzDomain; + hr = GetDomainServerName(wzDomain, &pwzServerName); + + er = ::NetLocalGroupGetInfo(pwzServerName, psg->wzName, 0, reinterpret_cast(&pGroupInfo)); + if (NERR_Success == er) + { + geGroupExists = GROUP_EXISTS_YES; + } + else if (NERR_GroupNotFound == er) + { + geGroupExists = GROUP_EXISTS_NO; + } + else + { + geGroupExists = GROUP_EXISTS_INDETERMINATE; + hr = HRESULT_FROM_WIN32(er); + WcaLog(LOGMSG_VERBOSE, "Failed to check existence of domain: %ls, group: %ls (error code 0x%x) - continuing", wzDomain, psg->wzName, hr); + hr = S_OK; + er = ERROR_SUCCESS; + } + + if (WcaIsInstalling(psg->isInstalled, psg->isAction)) + { + // If the group exists, check to see if we are supposed to fail if the group exists before + // the install. + if (GROUP_EXISTS_YES == geGroupExists) + { + // Re-installs will always fail if we don't remove the check for "fail if exists". + if (WcaIsReInstalling(psg->isInstalled, psg->isAction)) + { + psg->iAttributes &= ~SCAG_FAIL_IF_EXISTS; + + // If install would create the group, re-install should be able to update the group. + if (!(psg->iAttributes & SCAG_DONT_CREATE_GROUP)) + { + psg->iAttributes |= SCAG_UPDATE_IF_EXISTS; + } + } + + if (SCAG_FAIL_IF_EXISTS & psg->iAttributes + && !(SCAG_UPDATE_IF_EXISTS & psg->iAttributes)) + { + hr = HRESULT_FROM_WIN32(NERR_GroupExists); + MessageExitOnFailure(hr, msierrGRPFailedGroupCreateExists, "Failed to create group: %ls because group already exists.", psg->wzName); + } + } + + hr = WcaWriteIntegerToCaData(psg->iAttributes, &pwzActionData); + ExitOnFailure(hr, "failed to add group attributes to custom action data for group: %ls", psg->wzKey); + + // Rollback only if the group already exists, we couldn't determine if the group exists, or we are going to create the group + if ((GROUP_EXISTS_YES == geGroupExists) + || (GROUP_EXISTS_INDETERMINATE == geGroupExists) + || !(psg->iAttributes & SCAG_DONT_CREATE_GROUP)) + { + ++cScriptKey; + hr = StrAllocFormatted(&pwzScriptKey, L"%ls%u", pwzBaseScriptKey, cScriptKey); + ExitOnFailure(hr, "Failed to create encoding key."); + + // Write the script key to CustomActionData for install and rollback so information can be passed to rollback. + hr = WcaWriteStringToCaData(pwzScriptKey, &pwzActionData); + ExitOnFailure(hr, "Failed to add encoding key to custom action data."); + + hr = WcaWriteStringToCaData(pwzScriptKey, &pwzRollbackData); + ExitOnFailure(hr, "Failed to add encoding key to rollback custom action data."); + + INT iRollbackUserAttributes = psg->iAttributes; + + // If the user already exists, ensure this is accounted for in rollback + if (GROUP_EXISTS_YES == geGroupExists) + { + iRollbackUserAttributes |= SCAG_DONT_CREATE_GROUP; + } + else + { + iRollbackUserAttributes &= ~SCAG_DONT_CREATE_GROUP; + } + + hr = WcaWriteStringToCaData(psg->wzName, &pwzRollbackData); + ExitOnFailure(hr, "Failed to add group name to rollback custom action data: %ls", psg->wzName); + hr = WcaWriteStringToCaData(psg->wzDomain, &pwzRollbackData); + ExitOnFailure(hr, "Failed to add group domain to rollback custom action data: %ls", psg->wzDomain); + hr = WcaWriteIntegerToCaData(iRollbackUserAttributes, &pwzRollbackData); + ExitOnFailure(hr, "failed to add group attributes to rollback custom action data for group: %ls", psg->wzKey); + + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"CreateGroupRollback"), pwzRollbackData, COST_GROUP_DELETE); + ExitOnFailure(hr, "failed to schedule CreateGroupRollback"); + } + else + { + // Write empty script key to CustomActionData since there is no rollback. + hr = WcaWriteStringToCaData(L"", &pwzActionData); + ExitOnFailure(hr, "Failed to add empty encoding key to custom action data."); + } + + // + // Schedule the creation now. + // + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"CreateGroup"), pwzActionData, COST_GROUP_ADD); + ExitOnFailure(hr, "failed to schedule CreateGroup"); + } + else if (((GROUP_EXISTS_YES == geGroupExists) + || (GROUP_EXISTS_INDETERMINATE == geGroupExists)) + && WcaIsUninstalling(psg->isInstalled, psg->isAction) + && !(psg->iAttributes & SCAG_DONT_REMOVE_ON_UNINSTALL)) + { + hr = WcaWriteIntegerToCaData(psg->iAttributes, &pwzActionData); + ExitOnFailure(hr, "failed to add group attributes to custom action data for group: %ls", psg->wzKey); + + // Schedule the removal because the group exists and we don't have any flags set + // that say not to remove the group on uninstall. + // + // Note: We can't rollback the removal of a group which is why RemoveGroup is a commit + // CustomAction. + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RemoveGroup"), pwzActionData, COST_GROUP_DELETE); + ExitOnFailure(hr, "failed to schedule RemoveGroup"); + } + + ReleaseNullStr(pwzScriptKey); + ReleaseNullStr(pwzActionData); + ReleaseNullStr(pwzRollbackData); + ReleaseNullStr(pwzServerName); + if (pGroupInfo) + { + ::NetApiBufferFree(static_cast(pGroupInfo)); + pGroupInfo = NULL; + } + } + +LExit: + ReleaseStr(pwzBaseScriptKey); + ReleaseStr(pwzScriptKey); + ReleaseStr(pwzActionData); + ReleaseStr(pwzRollbackData); + ReleaseStr(pwzServerName); + if (pGroupInfo) + { + ::NetApiBufferFree(static_cast(pGroupInfo)); + pGroupInfo = NULL; + } + + return hr; +} + +static HRESULT AddGroupToList( + __inout SCA_GROUP** ppsgList + ) +{ + HRESULT hr = S_OK; + SCA_GROUP* psg = static_cast(MemAlloc(sizeof(SCA_GROUP), TRUE)); + ExitOnNull(psg, hr, E_OUTOFMEMORY, "failed to allocate memory for new group list element"); + + psg->psgNext = *ppsgList; + *ppsgList = psg; + +LExit: + return hr; +} diff --git a/src/ext/Util/ca/scagroup.h b/src/ext/Util/ca/scagroup.h new file mode 100644 index 00000000..8666d852 --- /dev/null +++ b/src/ext/Util/ca/scagroup.h @@ -0,0 +1,47 @@ +#pragma once +// 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. + +enum GROUP_EXISTS +{ + GROUP_EXISTS_YES, + GROUP_EXISTS_NO, + GROUP_EXISTS_INDETERMINATE +}; + +// structs +struct SCA_GROUP +{ + WCHAR wzKey[MAX_DARWIN_KEY + 1]; + WCHAR wzComponent[MAX_DARWIN_KEY + 1]; + INSTALLSTATE isInstalled; + INSTALLSTATE isAction; + + WCHAR wzDomain[MAX_DARWIN_COLUMN + 1]; + WCHAR wzName[MAX_DARWIN_COLUMN + 1]; + WCHAR wzComment[MAX_DARWIN_COLUMN + 1]; + INT iAttributes; + + SCA_GROUP* psgGroups; + + SCA_GROUP *psgNext; +}; + +// prototypes +HRESULT __stdcall ScaGetGroup( + __in LPCWSTR wzGroup, + __out SCA_GROUP* pscag + ); +HRESULT __stdcall ScaGetGroupDeferred( + __in LPCWSTR wzGroup, + __in WCA_WRAPQUERY_HANDLE hGroupQuery, + __out SCA_GROUP* pscag + ); +void ScaGroupFreeList( + __in SCA_GROUP* psgList + ); +HRESULT ScaGroupRead( + __inout SCA_GROUP** ppsgList + ); +HRESULT ScaGroupExecute( + __in SCA_GROUP*psgList + ); diff --git a/src/ext/Util/ca/scanet.cpp b/src/ext/Util/ca/scanet.cpp new file mode 100644 index 00000000..11ee487d --- /dev/null +++ b/src/ext/Util/ca/scanet.cpp @@ -0,0 +1,50 @@ +// 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. + +#include "precomp.h" +#include "scanet.h" + + +HRESULT GetDomainServerName(LPCWSTR pwzDomain, LPWSTR* ppwzServerName, ULONG flags) +{ + DWORD er = ERROR_SUCCESS; + PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL; + HRESULT hr = S_OK; + + if (pwzDomain && *pwzDomain) + { + er = ::DsGetDcNameW(NULL, pwzDomain, NULL, NULL, flags, &pDomainControllerInfo); + if (RPC_S_SERVER_UNAVAILABLE == er) + { + // MSDN says, if we get the above error code, try again with the "DS_FORCE_REDISCOVERY" flag + er = ::DsGetDcNameW(NULL, pwzDomain, NULL, NULL, flags | DS_FORCE_REDISCOVERY, &pDomainControllerInfo); + } + + if (ERROR_SUCCESS == er && pDomainControllerInfo->DomainControllerName) + { + // Skip the \\ prefix if present. + if ('\\' == *pDomainControllerInfo->DomainControllerName && '\\' == *pDomainControllerInfo->DomainControllerName + 1) + { + hr = StrAllocString(ppwzServerName, pDomainControllerInfo->DomainControllerName + 2, 0); + ExitOnFailure(hr, "failed to allocate memory for string"); + } + else + { + hr = StrAllocString(ppwzServerName, pDomainControllerInfo->DomainControllerName, 0); + ExitOnFailure(hr, "failed to allocate memory for string"); + } + } + else + { + StrAllocString(ppwzServerName, pwzDomain, 0); + hr = HRESULT_FROM_WIN32(er); + ExitOnFailure(hr, "failed to contact domain %ls", pwzDomain); + } + } + +LExit: + if (pDomainControllerInfo) + { + ::NetApiBufferFree((LPVOID)pDomainControllerInfo); + } + return hr; +} diff --git a/src/ext/Util/ca/scanet.h b/src/ext/Util/ca/scanet.h new file mode 100644 index 00000000..1fee61f8 --- /dev/null +++ b/src/ext/Util/ca/scanet.h @@ -0,0 +1,4 @@ +#pragma once +// 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. + +HRESULT GetDomainServerName(LPCWSTR pwzDomain, LPWSTR* ppwzServerName, ULONG flags = 0); diff --git a/src/ext/Util/ca/scasched.cpp b/src/ext/Util/ca/scasched.cpp index d81b1f14..1351fbfd 100644 --- a/src/ext/Util/ca/scasched.cpp +++ b/src/ext/Util/ca/scasched.cpp @@ -124,4 +124,49 @@ LExit: er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; return WcaFinalize(er); -} \ No newline at end of file +} + +/******************************************************************** +ConfigureGroups - CUSTOM ACTION ENTRY POINT for installing groups + +********************************************************************/ +extern "C" UINT __stdcall ConfigureGroups( + __in MSIHANDLE hInstall +) +{ + //AssertSz(0, "Debug ConfigureGroups"); + + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + BOOL fInitializedCom = FALSE; + SCA_GROUP* psgList = NULL; + + // initialize + hr = WcaInitialize(hInstall, "ConfigureGroups"); + ExitOnFailure(hr, "Failed to initialize"); + + hr = ::CoInitialize(NULL); + ExitOnFailure(hr, "failed to initialize COM"); + fInitializedCom = TRUE; + + hr = ScaGroupRead(&psgList); + ExitOnFailure(hr, "failed to read Wix4Group,Wix6Group table(s)"); + + hr = ScaGroupExecute(psgList); + ExitOnFailure(hr, "failed to add/remove Group actions"); + +LExit: + if (psgList) + { + ScaGroupFreeList(psgList); + } + + if (fInitializedCom) + { + ::CoUninitialize(); + } + + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} diff --git a/src/ext/Util/ca/scauser.cpp b/src/ext/Util/ca/scauser.cpp index 79da155f..21911e48 100644 --- a/src/ext/Util/ca/scauser.cpp +++ b/src/ext/Util/ca/scauser.cpp @@ -5,9 +5,6 @@ LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Comment`, `Password` FROM `Wix4User` WHERE `User`=?"; enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqComment, vuqPassword }; -LPCWSTR vcsGroupQuery = L"SELECT `Group`, `Component_`, `Name`, `Domain` FROM `Wix4Group` WHERE `Group`=?"; -enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain }; - LPCWSTR vcsUserGroupQuery = L"SELECT `User_`, `Group_` FROM `Wix4UserGroup` WHERE `User_`=?"; enum eUserGroupQuery { vugqUser = 1, vugqGroup }; @@ -185,71 +182,6 @@ LExit: return hr; } - -HRESULT __stdcall ScaGetGroup( - __in LPCWSTR wzGroup, - __out SCA_GROUP* pscag - ) -{ - if (!wzGroup || !pscag) - { - return E_INVALIDARG; - } - - HRESULT hr = S_OK; - PMSIHANDLE hView, hRec; - - LPWSTR pwzData = NULL; - - hRec = ::MsiCreateRecord(1); - hr = WcaSetRecordString(hRec, 1, wzGroup); - ExitOnFailure(hr, "Failed to look up Group"); - - hr = WcaOpenView(vcsGroupQuery, &hView); - ExitOnFailure(hr, "Failed to open view on Wix4Group table"); - hr = WcaExecuteView(hView, hRec); - ExitOnFailure(hr, "Failed to execute view on Wix4Group table"); - - hr = WcaFetchSingleRecord(hView, &hRec); - if (S_OK == hr) - { - hr = WcaGetRecordString(hRec, vgqGroup, &pwzData); - ExitOnFailure(hr, "Failed to get Wix4Group.Group."); - hr = ::StringCchCopyW(pscag->wzKey, countof(pscag->wzKey), pwzData); - ExitOnFailure(hr, "Failed to copy Wix4Group.Group."); - - hr = WcaGetRecordString(hRec, vgqComponent, &pwzData); - ExitOnFailure(hr, "Failed to get Wix4Group.Component_"); - hr = ::StringCchCopyW(pscag->wzComponent, countof(pscag->wzComponent), pwzData); - ExitOnFailure(hr, "Failed to copy Wix4Group.Component_."); - - hr = WcaGetRecordFormattedString(hRec, vgqName, &pwzData); - ExitOnFailure(hr, "Failed to get Wix4Group.Name"); - hr = ::StringCchCopyW(pscag->wzName, countof(pscag->wzName), pwzData); - ExitOnFailure(hr, "Failed to copy Wix4Group.Name."); - - hr = WcaGetRecordFormattedString(hRec, vgqDomain, &pwzData); - ExitOnFailure(hr, "Failed to get Wix4Group.Domain"); - hr = ::StringCchCopyW(pscag->wzDomain, countof(pscag->wzDomain), pwzData); - ExitOnFailure(hr, "Failed to copy Wix4Group.Domain."); - } - else if (E_NOMOREITEMS == hr) - { - WcaLog(LOGMSG_STANDARD, "Error: Cannot locate Wix4Group.Group='%ls'", wzGroup); - hr = E_FAIL; - } - else - { - ExitOnFailure(hr, "Error or found multiple matching Wix4Group rows"); - } - -LExit: - ReleaseStr(pwzData); - - return hr; -} - - void ScaUserFreeList( __in SCA_USER* psuList ) @@ -266,21 +198,6 @@ void ScaUserFreeList( } -void ScaGroupFreeList( - __in SCA_GROUP* psgList - ) -{ - SCA_GROUP* psgDelete = psgList; - while (psgList) - { - psgDelete = psgList; - psgList = psgList->psgNext; - - MemFree(psgDelete); - } -} - - HRESULT ScaUserRead( __out SCA_USER** ppsuList ) diff --git a/src/ext/Util/ca/scauser.h b/src/ext/Util/ca/scauser.h index 3da847b5..de690086 100644 --- a/src/ext/Util/ca/scauser.h +++ b/src/ext/Util/ca/scauser.h @@ -1,6 +1,6 @@ #pragma once // 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. - +#include "scagroup.h" enum USER_EXISTS { @@ -9,17 +9,6 @@ enum USER_EXISTS USER_EXISTS_INDETERMINATE }; -// structs -struct SCA_GROUP -{ - WCHAR wzKey[MAX_DARWIN_KEY + 1]; - WCHAR wzComponent[MAX_DARWIN_KEY + 1]; - - WCHAR wzDomain[MAX_DARWIN_COLUMN + 1]; - WCHAR wzName[MAX_DARWIN_COLUMN + 1]; - - SCA_GROUP *psgNext; -}; struct SCA_USER { @@ -50,16 +39,9 @@ HRESULT __stdcall ScaGetUserDeferred( __in WCA_WRAPQUERY_HANDLE hUserQuery, __out SCA_USER* pscau ); -HRESULT __stdcall ScaGetGroup( - __in LPCWSTR wzGroup, - __out SCA_GROUP* pscag - ); void ScaUserFreeList( __in SCA_USER* psuList ); -void ScaGroupFreeList( - __in SCA_GROUP* psgList - ); HRESULT ScaUserRead( __inout SCA_USER** ppsuList ); diff --git a/src/ext/Util/ca/utilca.def b/src/ext/Util/ca/utilca.def index 96545566..18a19d12 100644 --- a/src/ext/Util/ca/utilca.def +++ b/src/ext/Util/ca/utilca.def @@ -43,6 +43,9 @@ EXPORTS UnregisterPerfmon CreateSmb DropSmb + CreateGroup + CreateGroupRollback + RemoveGroup CreateUser CreateUserRollback RemoveUser @@ -51,6 +54,7 @@ EXPORTS ConfigurePerfmonUninstall ConfigureSmbInstall ConfigureSmbUninstall + ConfigureGroups ConfigureUsers InstallPerfCounterData UninstallPerfCounterData diff --git a/src/ext/Util/ca/utilca.vcxproj b/src/ext/Util/ca/utilca.vcxproj index 758f075c..5dbe2792 100644 --- a/src/ext/Util/ca/utilca.vcxproj +++ b/src/ext/Util/ca/utilca.vcxproj @@ -61,7 +61,9 @@ + + @@ -84,6 +86,8 @@ + + -- cgit v1.2.3-55-g6feb