diff --git a/src/ext/Util/ca/scaexec.cpp b/src/ext/Util/ca/scaexec.cpp index 64f4c823d..fea60b010 100644 --- a/src/ext/Util/ca/scaexec.cpp +++ b/src/ext/Util/ca/scaexec.cpp @@ -716,7 +716,7 @@ static HRESULT RemoveGroupInternal( // if (!(SCAG_DONT_CREATE_GROUP & iAttributes)) { - GetDomainServerName(wzDomain, &pwzServerName, DS_WRITABLE_REQUIRED); + hr = GetDomainFromServerName(&pwzServerName, wzDomain, DS_WRITABLE_REQUIRED); NET_API_STATUS er = ::NetLocalGroupDel(pwzServerName, wzName); hr = HRESULT_FROM_WIN32(er); @@ -1218,9 +1218,13 @@ extern "C" UINT __stdcall RemoveUser( /******************************************************************** - CreateGroup - CUSTOM ACTION ENTRY POINT for creating groups + CreateGroup - CUSTOM ACTION ENTRY POINT for creating groups + For domain parent group, must be run as Impersonated=true + For non-domain parent group, must be run as Impersonated=false (for elevation) - Input: deferred CustomActionData - GroupName\tDomain\tComment\tAttributes + Input: deferred CustomActionData - GroupName\tDomain\tComment\tAttributes\tScriptKey(empty for no rollback) + + Output: Script for RollbackCreateGroup - OriginalComment\tRollbackAttributes * *****************************************************************/ extern "C" UINT __stdcall CreateGroup( __in MSIHANDLE hInstall @@ -1281,8 +1285,8 @@ extern "C" UINT __stdcall CreateGroup( if (!(SCAG_DONT_CREATE_GROUP & iAttributes)) { - hr = GetDomainServerName(pwzDomain, &pwzServerName, DS_WRITABLE_REQUIRED); - ExitOnFailure(hr, "failed to find Domain %ls.", pwzDomain); + hr = GetDomainFromServerName(&pwzServerName, pwzDomain, DS_WRITABLE_REQUIRED); + ExitOnFailure(hr, "failed to find writable server for domain %ls.", pwzDomain); // Set the group's comment if (SCAG_REMOVE_COMMENT & iAttributes) @@ -1304,7 +1308,7 @@ extern "C" UINT __stdcall CreateGroup( { if (SCAG_FAIL_IF_EXISTS & iAttributes) { - MessageExitOnFailure(hr, msierrGRPFailedGroupCreateExists, "Group (%ls) was not supposed to exist, but does", pwzName); + MessageExitOnFailure(hr, msierrGRPFailedGroupCreateExists, "Group (%ls\\%ls) was not supposed to exist, but does", pwzDomain, pwzName); } hr = S_OK; // Make sure that we don't report this situation as an error @@ -1359,7 +1363,7 @@ extern "C" UINT __stdcall CreateGroup( hr = SetGroupComment(pwzServerName, pwzName, L""); if (FAILED(hr)) { - WcaLogError(hr, "failed to clear comment for group %ls\\%ls, continuing anyway.", pwzServerName, pwzName); + WcaLogError(hr, "failed to clear comment for group %ls\\%ls, continuing anyway.", pwzDomain, pwzName); hr = S_OK; } } @@ -1368,14 +1372,14 @@ extern "C" UINT __stdcall CreateGroup( 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); + WcaLogError(hr, "failed to set comment to '%ls' for group %ls\\%ls, continuing anyway.", pwzComment, pwzDomain, pwzName); hr = S_OK; } } } } } - MessageExitOnFailure(hr, msierrGRPFailedGroupCreate, "failed to create group: %ls", pwzName); + MessageExitOnFailure(hr, msierrGRPFailedGroupCreate, "failed to create group: %ls\\%ls", pwzDomain, pwzName); } LExit: @@ -1391,6 +1395,7 @@ extern "C" UINT __stdcall CreateGroup( ReleaseStr(pwzDomain); ReleaseStr(pwzComment); ReleaseStr(pwzScriptKey); + ReleaseStr(pwzServerName); if (fInitializedCom) { @@ -1412,6 +1417,11 @@ extern "C" UINT __stdcall CreateGroup( /******************************************************************** CreateGroupRollback - CUSTOM ACTION ENTRY POINT for CreateGroup rollback + For domain parent group, must be run as Impersonated=true + For non-domain parent group, must be run as Impersonated=false (for elevation) + + Input: rollback CustomActionData - ScriptKey\tGroupName\tDomain\tComment\tRollbackAttributes + rollback script - OriginalComment\tRollbackAttributes * *****************************************************************/ extern "C" UINT __stdcall CreateGroupRollback( @@ -1429,7 +1439,7 @@ extern "C" UINT __stdcall CreateGroupRollback( LPWSTR pwzName = NULL; LPWSTR pwzDomain = NULL; LPWSTR pwzComment = NULL; - int iAttributes = 0; + int iRollbackAttributes = 0; BOOL fInitializedCom = FALSE; WCA_CASCRIPT_HANDLE hRollbackScript = NULL; @@ -1454,7 +1464,7 @@ extern "C" UINT __stdcall CreateGroupRollback( // pwz = pwzData; hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey); - ExitOnFailure(hr, "failed to read encoding key from custom action data"); + ExitOnFailure(hr, "failed to read script key from custom action data"); hr = WcaReadStringFromCaData(&pwz, &pwzName); ExitOnFailure(hr, "failed to read name from custom action data"); @@ -1465,8 +1475,8 @@ extern "C" UINT __stdcall CreateGroupRollback( 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 = WcaReadIntegerFromCaData(&pwz, &iRollbackAttributes); + ExitOnFailure(hr, "failed to read rollback attributes from custom action data"); // Best effort to read original configuration from CreateUser. hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript); @@ -1502,12 +1512,12 @@ extern "C" UINT __stdcall CreateGroupRollback( } else { - iAttributes |= iOriginalAttributes; + iRollbackAttributes |= iOriginalAttributes; } } } - hr = RemoveGroupInternal(pwzDomain, pwzName, iAttributes); + hr = RemoveGroupInternal(pwzDomain, pwzName, iRollbackAttributes); LExit: WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_DELETE); @@ -1535,9 +1545,12 @@ extern "C" UINT __stdcall CreateGroupRollback( /******************************************************************** - RemoveGroup - CUSTOM ACTION ENTRY POINT for removing groups + RemoveGroup - CUSTOM ACTION ENTRY POINT for removing groups + For domain parent group, must be run as Impersonated=true + For non-domain parent group, must be run as Impersonated=false (for elevation) + NOTE: This action can't be rolled back, so should only be performed in commit phase - Input: deferred CustomActionData - Name\tDomain + Input: commit CustomActionData - Name\tDomain\tComment\tAttributes * *****************************************************************/ extern "C" UINT __stdcall RemoveGroup( MSIHANDLE hInstall @@ -1605,7 +1618,7 @@ extern "C" UINT __stdcall RemoveGroup( return WcaFinalize(er); } -HRESULT AlterGroupMembership(bool remove, bool isRollback = false) +HRESULT AlterGroupMembership(bool remove, bool isRollback) { HRESULT hr = S_OK; NET_API_STATUS er = ERROR_SUCCESS; @@ -1617,22 +1630,13 @@ HRESULT AlterGroupMembership(bool remove, bool isRollback = false) LPWSTR pwzChildName = NULL; LPWSTR pwzChildDomain = NULL; int iAttributes = 0; + LPWSTR pwzScriptKey = NULL; LPWSTR pwzChildFullName = NULL; LPWSTR pwzServerName = NULL; LOCALGROUP_MEMBERS_INFO_3 memberInfo3 = {}; - WCA_CASCRIPT_HANDLE phRollbackScript = NULL; + WCA_CASCRIPT_HANDLE hRollbackScript = NULL; - if (isRollback) - { - // Get a CaScript key - hr = WcaCaScriptOpen(WCA_ACTION_NONE, WCA_CASCRIPT_ROLLBACK, FALSE, remove ? L"AddGroupMembershipRollback" : L"RemoveGroupMembershipRollback", &phRollbackScript); - hr = WcaCaScriptReadAsCustomActionData(phRollbackScript, &pwzData); - } - else - { - hr = WcaCaScriptCreate(WCA_ACTION_NONE, WCA_CASCRIPT_ROLLBACK, FALSE, remove ? L"RemoveGroupMembershipRollback" : L"AddGroupMembershipRollback", TRUE, &phRollbackScript); - hr = WcaGetProperty(L"CustomActionData", &pwzData); - } + hr = WcaGetProperty(L"CustomActionData", &pwzData); ExitOnFailure(hr, "failed to get CustomActionData"); WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData); @@ -1656,8 +1660,28 @@ HRESULT AlterGroupMembership(bool remove, bool isRollback = false) hr = WcaReadIntegerFromCaData(&pwz, &iAttributes); ExitOnFailure(hr, "failed to read attributes from custom action data"); - hr = GetDomainServerName(pwzParentDomain, &pwzServerName, DS_WRITABLE_REQUIRED); - ExitOnFailure(hr, "failed to contact domain server %ls", pwzParentDomain); + hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey); + ExitOnFailure(hr, "failed to read scriptkey from custom action data"); + + if (isRollback) + { + // if the script file doesn't exist, then we'll abandon this rollback + hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript); + if (S_OK == hr) + { + WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_DELETE); + } + else + { + WcaLog(LOGMSG_VERBOSE, "Rollback of parent: %ls\\%ls, child: %ls\\%ls relationship not performed, rollback script not found", pwzParentDomain, pwzParentName, pwzChildDomain, pwzChildName); + hr = S_OK; + ExitFunction(); + } + } + + + hr = GetDomainFromServerName(&pwzServerName, pwzParentDomain, DS_WRITABLE_REQUIRED); + ExitOnFailure(hr, "failed to obtain writable server for domain %ls", pwzParentDomain); if (*pwzChildDomain) { @@ -1679,16 +1703,13 @@ HRESULT AlterGroupMembership(bool remove, bool isRollback = false) } hr = HRESULT_FROM_WIN32(er); + // if there was no error, the action succeeded, and we should flag that it's something which might need + // to be rolled back if (S_OK == hr && !isRollback) { - // we need to log rollback data, we can just use exactly the same data we used to do the initial action though - WcaCaScriptWriteString(phRollbackScript, pwzParentName); - WcaCaScriptWriteString(phRollbackScript, pwzParentDomain); - WcaCaScriptWriteString(phRollbackScript, pwzChildName); - WcaCaScriptWriteString(phRollbackScript, pwzChildDomain); - WcaCaScriptWriteNumber(phRollbackScript, iAttributes); - WcaCaScriptFlush(phRollbackScript); - WcaCaScriptClose(phRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE); + // we create a script file, the rollback matching this scriptkey will occur if the file exists + hr = WcaCaScriptCreate(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, FALSE, &hRollbackScript); + WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE); } if (remove) @@ -1716,6 +1737,7 @@ HRESULT AlterGroupMembership(bool remove, bool isRollback = false) ReleaseStr(pwzChildDomain); ReleaseStr(pwzChildFullName); ReleaseStr(pwzServerName); + ReleaseStr(pwzScriptKey); if (SCAG_NON_VITAL & iAttributes) { @@ -1725,10 +1747,12 @@ HRESULT AlterGroupMembership(bool remove, bool isRollback = false) } /******************************************************************** - AddGroupmembership - CUSTOM ACTION ENTRY POINT for creating groups + AddGroupMembership - CUSTOM ACTION ENTRY POINT for adding Group Membership + For domain parent group, must be run as Impersonated=true + For non-domain parent group, must be run as Impersonated=false (for elevation) Input: deferred CustomActionData - - ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey * *****************************************************************/ extern "C" UINT __stdcall AddGroupMembership( __in MSIHANDLE hInstall @@ -1758,10 +1782,13 @@ extern "C" UINT __stdcall AddGroupMembership( } /******************************************************************** - AddGroupmembership - CUSTOM ACTION ENTRY POINT for creating groups + AddGroupMembershipRollback - CUSTOM ACTION ENTRY POINT for rolling back + adding Group Membership + For domain parent group, must be run as Impersonated=true + For non-domain parent group, must be run as Impersonated=false (for elevation) Input: deferred CustomActionData - - ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey * *****************************************************************/ extern "C" UINT __stdcall AddGroupMembershipRollback( __in MSIHANDLE hInstall @@ -1791,10 +1818,12 @@ extern "C" UINT __stdcall AddGroupMembershipRollback( } /******************************************************************** - RemoveGroupMembership - CUSTOM ACTION ENTRY POINT for creating groups + RemoveGroupMembership - CUSTOM ACTION ENTRY POINT for removing group memberships + For domain parent group, must be run as Impersonated=true + For non-domain parent group, must be run as Impersonated=false (for elevation) Input: deferred CustomActionData - - ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey * *****************************************************************/ extern "C" UINT __stdcall RemoveGroupMembership( __in MSIHANDLE hInstall @@ -1824,10 +1853,13 @@ extern "C" UINT __stdcall RemoveGroupMembership( } /******************************************************************** - RemoveGroupMembershipRollback - CUSTOM ACTION ENTRY POINT for creating groups + RemoveGroupMembershipRollback - CUSTOM ACTION ENTRY POINT for rolling back + removing group memberships + For domain parent group, must be run as Impersonated=true + For non-domain parent group, must be run as Impersonated=false (for elevation) Input: deferred CustomActionData - - ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey * *****************************************************************/ extern "C" UINT __stdcall RemoveGroupMembershipRollback( __in MSIHANDLE hInstall diff --git a/src/ext/Util/ca/scagroup.cpp b/src/ext/Util/ca/scagroup.cpp index 699d9db7e..bab438eaa 100644 --- a/src/ext/Util/ca/scagroup.cpp +++ b/src/ext/Util/ca/scagroup.cpp @@ -220,13 +220,13 @@ HRESULT ScaGroupGetParents( ExitOnFailure(hr, "failed to get Component state for Wix4Group"); } - hr = WcaGetRecordString(hRec, vgpqParentName, &pwzTempStr); + hr = WcaGetRecordFormattedString(hRec, vgpqParentName, &pwzTempStr); ExitOnFailure(hr, "failed to get Wix4Group.Name"); wcsncpy_s(psgParent->wzName, pwzTempStr, MAX_DARWIN_COLUMN); ReleaseNullStr(pwzTempStr); - hr = WcaGetRecordString(hRec, vgpqParentDomain, &pwzTempStr); + hr = WcaGetRecordFormattedString(hRec, vgpqParentDomain, &pwzTempStr); ExitOnFailure(hr, "failed to get Wix4Group.Domain"); wcsncpy_s(psgParent->wzDomain, pwzTempStr, MAX_DARWIN_COLUMN); ReleaseNullStr(pwzTempStr); @@ -288,13 +288,13 @@ HRESULT ScaGroupGetChildren( ExitOnFailure(hr, "failed to get Component state for Wix4Group"); } - hr = WcaGetRecordString(hRec, vgcqChildName, &pwzTempStr); + hr = WcaGetRecordFormattedString(hRec, vgcqChildName, &pwzTempStr); ExitOnFailure(hr, "failed to get Wix4Group.Name"); wcsncpy_s(psgChild->wzName, pwzTempStr, MAX_DARWIN_COLUMN); ReleaseNullStr(pwzTempStr); - hr = WcaGetRecordString(hRec, vgcqChildDomain, &pwzTempStr); + hr = WcaGetRecordFormattedString(hRec, vgcqChildDomain, &pwzTempStr); ExitOnFailure(hr, "failed to get Wix4Group.Domain"); wcsncpy_s(psgChild->wzDomain, pwzTempStr, MAX_DARWIN_COLUMN); ReleaseNullStr(pwzTempStr); @@ -412,8 +412,11 @@ HRESULT ScaGroupRead( } /* **************************************************************** -ScaGroupExecute - Schedules group account creation or removal based on -component state. +ScaGroupExecute - Schedules group account creation or removal based on component state. + + Output: CustomData for CreateGroup - Name\tDomain\tComment\tAttributes\tScriptKey + CustomData for RollbackCreateGroup - ScriptKey\tName\tDomain\tComment\tRollbackAttributes + CustomData for RemoveGroup - Name\tDomain\tComment\tAttributes ******************************************************************/ HRESULT ScaGroupExecute( __in SCA_GROUP *psgList @@ -455,8 +458,7 @@ HRESULT ScaGroupExecute( // 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); + hr = GetDomainServerName(psg->wzDomain, &pwzServerName); er = ::NetLocalGroupGetInfo(pwzServerName, psg->wzName, 0, reinterpret_cast(&pGroupInfo)); if (NERR_Success == er) @@ -471,7 +473,7 @@ HRESULT ScaGroupExecute( { 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); + WcaLog(LOGMSG_VERBOSE, "Failed to check existence of group: %ls\\%ls (error code 0x%x) - continuing", psg->wzDomain, psg->wzName, hr); hr = S_OK; er = ERROR_SUCCESS; } @@ -498,7 +500,7 @@ HRESULT ScaGroupExecute( && !(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); + MessageExitOnFailure(hr, msierrGRPFailedGroupCreateExists, "Failed to create group: %ls\\%ls because group already exists.", psg->wzDomain, psg->wzName); } } @@ -537,10 +539,19 @@ HRESULT ScaGroupExecute( 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 = WcaWriteStringToCaData(psg->wzComment, &pwzRollbackData); + ExitOnFailure(hr, "Failed to add group comment to rollback custom action data: %ls", psg->wzComment); hr = WcaWriteIntegerToCaData(iRollbackUserAttributes, &pwzRollbackData); - ExitOnFailure(hr, "failed to add group attributes to rollback custom action data for group: %ls", psg->wzKey); + ExitOnFailure(hr, "failed to add group rollback attributes to rollback custom action data: %i", iRollbackUserAttributes); - hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"CreateGroupRollback"), pwzRollbackData, COST_GROUP_DELETE); + if (*psg->wzDomain) + { + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"CreateDomainGroupRollback"), pwzRollbackData, COST_GROUP_DELETE); + } + else + { + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"CreateGroupRollback"), pwzRollbackData, COST_GROUP_DELETE); + } ExitOnFailure(hr, "failed to schedule CreateGroupRollback"); } else @@ -571,11 +582,10 @@ HRESULT ScaGroupExecute( 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. + // 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. + // Note: We can't rollback the removal of a group which is why RemoveGroup is a commit CustomAction. if (psg->wzDomain && *psg->wzDomain) { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"RemoveDomainGroup"), pwzActionData, COST_GROUP_DELETE); @@ -631,15 +641,31 @@ static HRESULT AddGroupToList( /* **************************************************************** ScaGroupMembershipRemoveParentsExecute - Schedules group membership removal based on parent/child component state + + Output: deferred CustomActionData - + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey + rollback CustomActionData - + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey ******************************************************************/ HRESULT ScaGroupMembershipRemoveParentsExecute( - __in SCA_GROUP* psg + __in SCA_GROUP* psg, + __inout DWORD &cScriptIndex ) { HRESULT hr = S_OK; LPWSTR pwzActionData = NULL; + LPWSTR pwzBaseScriptKey = NULL; + LPWSTR pwzScriptKey = NULL; + SCA_GROUP* psgp = NULL; - for (SCA_GROUP* psgp = psg->psgParents; psgp; psgp = psgp->psgNext) + ++cScriptIndex; + // Get the base script key for this CustomAction. + hr = WcaCaScriptCreateKey(&pwzBaseScriptKey); + ExitOnFailure(hr, "Failed to get encoding key."); + hr = StrAllocFormatted(&pwzScriptKey, L"%ls_removemember_%u", pwzBaseScriptKey, cScriptIndex); + ExitOnFailure(hr, "Failed to create script key"); + + for (psgp = psg->psgParents; psgp; psgp = psgp->psgNext) { Assert(psgp->wzName); if (WcaIsUninstalling(psg->isInstalled, psg->isAction) @@ -655,18 +681,24 @@ HRESULT ScaGroupMembershipRemoveParentsExecute( ExitOnFailure(hr, "Failed to add child group domain to custom action data: %ls", psg->wzDomain); hr = WcaWriteIntegerToCaData(psg->iAttributes, &pwzActionData); ExitOnFailure(hr, "Failed to add group attributes to custom action data: %i", psg->iAttributes); + hr = WcaWriteStringToCaData(pwzScriptKey, &pwzActionData); + ExitOnFailure(hr, "Failed to add script key to custom action data: %i", pwzScriptKey); if (psgp->wzDomain && *psgp->wzDomain) { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"RemoveDomainGroupMembership"), pwzActionData, COST_GROUPMEMBERSHIP_DELETE); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"RemoveDomainGroupMembershipRollback"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); } else { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"RemoveGroupMembership"), pwzActionData, COST_GROUPMEMBERSHIP_DELETE); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"RemoveGroupMembershipRollback"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); } LExit: ReleaseNullStr(pwzActionData); + ReleaseNullStr(pwzBaseScriptKey); + ReleaseNullStr(pwzScriptKey); if (hr != S_OK && !(psg->iAttributes & SCAG_NON_VITAL)) { return hr; @@ -677,16 +709,32 @@ HRESULT ScaGroupMembershipRemoveParentsExecute( } /* **************************************************************** -ScaGroupMembershipRemoveChildrenExecute - +ScaGroupMembershipRemoveChildrenExecute - + + Output: deferred CustomActionData - + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey + rollback CustomActionData - + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey ******************************************************************/ HRESULT ScaGroupMembershipRemoveChildrenExecute( - __in SCA_GROUP* psg + __in SCA_GROUP* psg, + __inout DWORD &cScriptIndex ) { HRESULT hr = S_OK; LPWSTR pwzActionData = NULL; + LPWSTR pwzBaseScriptKey = NULL; + LPWSTR pwzScriptKey = NULL; + SCA_GROUP* psgc = NULL; - for (SCA_GROUP* psgc = psg->psgChildren; psgc; psgc = psgc->psgNext) + ++cScriptIndex; + // Get the base script key for this CustomAction. + hr = WcaCaScriptCreateKey(&pwzBaseScriptKey); + ExitOnFailure(hr, "Failed to get encoding key."); + hr = StrAllocFormatted(&pwzScriptKey, L"%ls_removemember_%u", pwzBaseScriptKey, cScriptIndex); + ExitOnFailure(hr, "Failed to create script key"); + + for (psgc = psg->psgChildren; psgc; psgc = psgc->psgNext) { Assert(psgc->wzName); if (WcaIsUninstalling(psg->isInstalled, psg->isAction) @@ -702,17 +750,23 @@ HRESULT ScaGroupMembershipRemoveChildrenExecute( ExitOnFailure(hr, "Failed to add child group domain to custom action data: %ls", psgc->wzDomain); hr = WcaWriteIntegerToCaData(psg->iAttributes, &pwzActionData); ExitOnFailure(hr, "Failed to add group attributes to custom action data: %i", psg->iAttributes); + hr = WcaWriteStringToCaData(pwzScriptKey, &pwzActionData); + ExitOnFailure(hr, "Failed to add script key to custom action data: %i", pwzScriptKey); if (psg->wzDomain && *psg->wzDomain) { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"RemoveDomainGroupMembership"), pwzActionData, COST_GROUPMEMBERSHIP_DELETE); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"RemoveDomainGroupMembershipRollback"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); } else { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"RemoveGroupMembership"), pwzActionData, COST_GROUPMEMBERSHIP_DELETE); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"RemoveGroupMembershipRollback"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); } LExit: ReleaseNullStr(pwzActionData); + ReleaseNullStr(pwzBaseScriptKey); + ReleaseNullStr(pwzScriptKey); if (hr != S_OK && !(psg->iAttributes & SCAG_NON_VITAL)) { @@ -726,23 +780,29 @@ HRESULT ScaGroupMembershipRemoveChildrenExecute( /* **************************************************************** ScaGroupMembershipRemoveExecute - Schedules group membership removal based on parent/child component state + + Output: deferred CustomActionData - + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey + rollback CustomActionData - + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey ******************************************************************/ HRESULT ScaGroupMembershipRemoveExecute( __in SCA_GROUP* psgList ) { HRESULT hr = S_OK; + static DWORD iScriptIndex = 0; // Loop through all the users to be configured. for (SCA_GROUP* psg = psgList; psg; psg = psg->psgNext) { Assert(psg->wzName); // first we loop through the Parents - hr = ScaGroupMembershipRemoveParentsExecute(psg); + hr = ScaGroupMembershipRemoveParentsExecute(psg, iScriptIndex); ExitOnFailure(hr, "Failed to remove parent membership for vital group: %ls", psg->wzKey); // then through the Children - hr = ScaGroupMembershipRemoveChildrenExecute(psg); + hr = ScaGroupMembershipRemoveChildrenExecute(psg, iScriptIndex); ExitOnFailure(hr, "Failed to remove child membership for vital group: %ls", psg->wzKey); } @@ -755,13 +815,24 @@ ScaGroupMembershipAddParentsExecute - Schedules group membership removal based on parent/child component state ******************************************************************/ HRESULT ScaGroupMembershipAddParentsExecute( - __in SCA_GROUP* psg + __in SCA_GROUP* psg, + __inout DWORD &cScriptIndex ) { HRESULT hr = S_OK; LPWSTR pwzActionData = NULL; + LPWSTR pwzBaseScriptKey = NULL; + LPWSTR pwzScriptKey = NULL; + SCA_GROUP* psgp = NULL; + + ++cScriptIndex; + // Get the base script key for this CustomAction. + hr = WcaCaScriptCreateKey(&pwzBaseScriptKey); + ExitOnFailure(hr, "Failed to get encoding key."); + hr = StrAllocFormatted(&pwzScriptKey, L"%ls_addmember_%u", pwzBaseScriptKey, cScriptIndex); + ExitOnFailure(hr, "Failed to create script key"); - for (SCA_GROUP* psgp = psg->psgParents; psgp; psgp = psgp->psgNext) + for (psgp = psg->psgParents; psgp; psgp = psgp->psgNext) { Assert(psgp->wzName); if (WcaIsInstalling(psg->isInstalled, psg->isAction) @@ -777,17 +848,23 @@ HRESULT ScaGroupMembershipAddParentsExecute( ExitOnFailure(hr, "Failed to add child group domain to custom action data: %ls", psg->wzDomain); hr = WcaWriteIntegerToCaData(psg->iAttributes, &pwzActionData); ExitOnFailure(hr, "Failed to add group attributes to custom action data: %i", psg->iAttributes); + hr = WcaWriteStringToCaData(pwzScriptKey, &pwzActionData); + ExitOnFailure(hr, "Failed to add script key to custom action data: %i", pwzScriptKey); if (psgp->wzDomain&&* psgp->wzDomain) { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"AddDomainGroupMembership"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"AddDomainGroupMembershipRollback"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); } else { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"AddGroupMembership"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"AddGroupMembershipRollback"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); } LExit: ReleaseNullStr(pwzActionData); + ReleaseNullStr(pwzBaseScriptKey); + ReleaseNullStr(pwzScriptKey); if (hr != S_OK && !(psg->iAttributes & SCAG_NON_VITAL)) { @@ -801,16 +878,32 @@ HRESULT ScaGroupMembershipAddParentsExecute( /* **************************************************************** ScaGroupMembershipAddChildrenExecute - Schedules group membership removal based on parent/child component state + + Output: deferred CustomActionData - + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey + rollback CustomActionData - + ParentGroupName\tParentGroupDomain\tChildGroupName\tChildGroupDomain\tAttributes\tScriptkey ******************************************************************/ HRESULT ScaGroupMembershipAddChildrenExecute( - __in SCA_GROUP* psg + __in SCA_GROUP* psg, + __inout DWORD &cScriptIndex ) { HRESULT hr = S_OK; LPWSTR pwzActionData = NULL; + LPWSTR pwzBaseScriptKey = NULL; + LPWSTR pwzScriptKey = NULL; + SCA_GROUP* psgc = NULL; + + ++cScriptIndex; + // Get the base script key for this CustomAction. + hr = WcaCaScriptCreateKey(&pwzBaseScriptKey); + ExitOnFailure(hr, "Failed to get encoding key."); + hr = StrAllocFormatted(&pwzScriptKey, L"%ls_addmember_%u", pwzBaseScriptKey, cScriptIndex); + ExitOnFailure(hr, "Failed to create script key"); // then through the Children - for (SCA_GROUP* psgc = psg->psgChildren; psgc; psgc = psgc->psgNext) + for (psgc = psg->psgChildren; psgc; psgc = psgc->psgNext) { Assert(psgc->wzName); if (WcaIsInstalling(psg->isInstalled, psg->isAction) @@ -826,17 +919,24 @@ HRESULT ScaGroupMembershipAddChildrenExecute( ExitOnFailure(hr, "Failed to add parent group domain to custom action data: %ls", psgc->wzDomain); hr = WcaWriteIntegerToCaData(psg->iAttributes, &pwzActionData); ExitOnFailure(hr, "Failed to add group attributes to custom action data: %i", psg->iAttributes); + hr = WcaWriteStringToCaData(pwzScriptKey, &pwzActionData); + ExitOnFailure(hr, "Failed to add script key to custom action data: %i", pwzScriptKey); if (psg->wzDomain && *psg->wzDomain) { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"AddDomainGroupMembership"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"AddDomainGroupMembershipRollback"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); } else { hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"AddGroupMembership"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); + hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION6(L"AddGroupMembershipRollback"), pwzActionData, COST_GROUPMEMBERSHIP_ADD); } LExit: ReleaseNullStr(pwzActionData); + ReleaseNullStr(pwzBaseScriptKey); + ReleaseNullStr(pwzScriptKey); + if (hr != S_OK && !(psg->iAttributes & SCAG_NON_VITAL)) { return hr; @@ -855,17 +955,18 @@ HRESULT ScaGroupMembershipAddExecute( ) { HRESULT hr = S_OK; + static DWORD iScriptIndex = 0; // Loop through all the users to be configured. for (SCA_GROUP* psg = psgList; psg; psg = psg->psgNext) { Assert(psg->wzName); // first we loop through the Parents - hr = ScaGroupMembershipAddParentsExecute(psg); + hr = ScaGroupMembershipAddParentsExecute(psg, iScriptIndex); ExitOnFailure(hr, "Failed to add parent membership for vital group: %ls", psg->wzKey); // then through the Children - hr = ScaGroupMembershipAddChildrenExecute(psg); + hr = ScaGroupMembershipAddChildrenExecute(psg, iScriptIndex); ExitOnFailure(hr, "Failed to add child membership for vital group: %ls", psg->wzKey); } diff --git a/src/ext/Util/ca/scasched.cpp b/src/ext/Util/ca/scasched.cpp index cef7fecba..d7378b13b 100644 --- a/src/ext/Util/ca/scasched.cpp +++ b/src/ext/Util/ca/scasched.cpp @@ -134,7 +134,7 @@ extern "C" UINT __stdcall ConfigureGroups( __in MSIHANDLE hInstall ) { - //AssertSz(0, "Debug ConfigureGroups"); + AssertSz(0, "Debug ConfigureGroups"); HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; diff --git a/src/test/burn/WixTestTools/RuntimeFactAttribute.cs b/src/test/burn/WixTestTools/RuntimeFactAttribute.cs index f73c87a29..02cec9e44 100644 --- a/src/test/burn/WixTestTools/RuntimeFactAttribute.cs +++ b/src/test/burn/WixTestTools/RuntimeFactAttribute.cs @@ -3,16 +3,21 @@ namespace WixTestTools { using System; + using System.DirectoryServices.ActiveDirectory; using System.Security.Principal; using WixInternal.TestSupport.XunitExtensions; public class RuntimeFactAttribute : SkippableFactAttribute { const string RequiredEnvironmentVariableName = "RuntimeTestsEnabled"; + const string RequiredDomainEnvironmentVariableName = "RuntimeDomainTestsEnabled"; public static bool RuntimeTestsEnabled { get; } public static bool RunningAsAdministrator { get; } + public static bool RuntimeDomainTestsEnabled { get; } + public static bool RunningInDomain { get; } + static RuntimeFactAttribute() { using var identity = WindowsIdentity.GetCurrent(); @@ -21,6 +26,33 @@ static RuntimeFactAttribute() var testsEnabledString = Environment.GetEnvironmentVariable(RequiredEnvironmentVariableName); RuntimeTestsEnabled = Boolean.TryParse(testsEnabledString, out var testsEnabled) && testsEnabled; + + RunningInDomain = false; + try + { + RunningInDomain = !String.IsNullOrEmpty(System.DirectoryServices.ActiveDirectory.Domain.GetComputerDomain().Name); + } + catch (ActiveDirectoryObjectNotFoundException) { } + + var domainTestsEnabledString = Environment.GetEnvironmentVariable(RequiredDomainEnvironmentVariableName); + RuntimeDomainTestsEnabled = Boolean.TryParse(domainTestsEnabledString, out var domainTestsEnabled) && domainTestsEnabled; + } + + private bool _domainRequired; + public bool DomainRequired + { + get + { + return _domainRequired; + } + set + { + _domainRequired = value; + if (_domainRequired && String.IsNullOrEmpty(this.Skip) && (!RunningInDomain || !RuntimeDomainTestsEnabled)) + { + this.Skip = $"These tests require the test host to be running as a domain member ({(RunningInDomain ? "passed" : "failed")}). These tests affect both MACHINE AND DOMAIN state. To accept the consequences, set the {RequiredDomainEnvironmentVariableName} environment variable to true ({(RuntimeDomainTestsEnabled ? "passed" : "failed")})."; + } + } } public RuntimeFactAttribute() diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/product.wxs index e3c143e65..f7f60fdb5 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/product.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/product.wxs @@ -7,19 +7,18 @@ - - + - + - + - + diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/product.wxs index e01707460..6c9d3be33 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/product.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/product.wxs @@ -6,18 +6,15 @@ + + - + diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/product.wxs index d18248901..a34a276b6 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/product.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/product.wxs @@ -6,13 +6,15 @@ + + - + diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/product_fail.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/product_fail.wxs index 4e70717f8..793968825 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/product_fail.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/product_fail.wxs @@ -10,13 +10,15 @@ + + - + diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/product_fail.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/product_fail.wxs index 3013e5a02..148f26cad 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/product_fail.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/product_fail.wxs @@ -13,6 +13,8 @@ + + diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/FailIfExists.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/FailIfExists.wxs index 00f8e12de..e7acb5e0d 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/FailIfExists.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/FailIfExists.wxs @@ -6,6 +6,8 @@ + + diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/product.wxs index c27eb27aa..f513e7c69 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/product.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/product.wxs @@ -7,13 +7,14 @@ - + - + + @@ -25,6 +26,8 @@ + + diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/product.wxs index 2d012b23b..2305a80b1 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/product.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/product.wxs @@ -6,18 +6,15 @@ + + + Id="TEST_GROUP1" Name="testName1" CreateGroup="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" Comment="testComment1" /> diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/NonVitalUserGroup.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/NonVitalUserGroup.wxs index a834c76b7..4922fcef3 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/NonVitalUserGroup.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/NonVitalUserGroup.wxs @@ -6,6 +6,8 @@ + + diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/RestrictedDomain.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/RestrictedDomain.wxs index edb3387c7..04a1ac4ee 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/RestrictedDomain.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/RestrictedDomain.wxs @@ -7,14 +7,14 @@ - + - + diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs index 059ecee8d..36d10aa3e 100644 --- a/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs +++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs @@ -4,16 +4,13 @@ + + - - + + diff --git a/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionGroupTests.cs b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionGroupTests.cs index d7cf31682..cee357a6b 100644 --- a/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionGroupTests.cs +++ b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionGroupTests.cs @@ -11,9 +11,10 @@ public class UtilExtensionGroupTests : MsiE2ETests { public UtilExtensionGroupTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + #region Non Domain // Verify that the users specified in the authoring are created as expected. [RuntimeFact] - public void CanInstallAndUninstallGroups() + public void CanInstallAndUninstallNonDomainGroups() { UserGroupVerifier.CreateLocalGroup("testName3"); var productA = this.CreatePackageInstaller("ProductA"); @@ -39,7 +40,7 @@ public void CanInstallAndUninstallGroups() // Verify the rollback action reverts all Users changes. [RuntimeFact] - public void CanRollbackGroups() + public void CanRollbackNonDomainGroups() { UserGroupVerifier.CreateLocalGroup("testName3"); var productFail = this.CreatePackageInstaller("ProductFail"); @@ -65,7 +66,7 @@ public void CanRollbackGroups() // Original code signalled repair mode by using "-f ", which silently // terminated the command-line parsing, ignoring any parameters that followed. [RuntimeFact()] - public void CanRepairGroupsWithCommandLineParameters() + public void CanRepairNonDomainGroupsWithCommandLineParameters() { var arguments = new string[] { @@ -82,6 +83,10 @@ public void CanRepairGroupsWithCommandLineParameters() // Repair productWithCommandLineParameters.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments); + + // Install + productWithCommandLineParameters.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments); + // Clean up UserGroupVerifier.DeleteLocalGroup("testName1"); } @@ -89,7 +94,7 @@ public void CanRepairGroupsWithCommandLineParameters() // Verify that the groups specified in the authoring are created as expected on repair. [RuntimeFact()] - public void CanRepairGroups() + public void CanRepairNonDomainGroups() { UserGroupVerifier.CreateLocalGroup("testName3"); var productA = this.CreatePackageInstaller("ProductA"); @@ -119,7 +124,7 @@ public void CanRepairGroups() // Verify that Installation fails if FailIfExists is set. [RuntimeFact] - public void FailsIfGroupExists() + public void FailsIfNonDomainGroupExists() { var productFailIfExists = this.CreatePackageInstaller("ProductFailIfExists"); @@ -148,7 +153,7 @@ public void FailsIfRestrictedDomain() { var productRestrictedDomain = this.CreatePackageInstaller("ProductRestrictedDomain"); - string logFile = productRestrictedDomain.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "TEMPDOMAIN=DOESNOTEXIST"); + string logFile = productRestrictedDomain.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "TESTDOMAIN=DOESNOTEXIST"); // Verify expected error message in the log file Assert.True(LogVerifier.MessageInLogFile(logFile, "CreateGroup: Error 0x8007054b: failed to find Domain DOESNOTEXIST.")); @@ -156,12 +161,13 @@ public void FailsIfRestrictedDomain() // Verify that a group can be created with a group comment [RuntimeFact] - public void CanCreateNewGroupWithComment() + public void CanCreateNewNonDomainGroupWithComment() { var productNewUserWithComment = this.CreatePackageInstaller("ProductNewGroupWithComment"); - productNewUserWithComment.InstallProduct(); + productNewUserWithComment.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "testComment1"); + productNewUserWithComment.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); // clean up UserGroupVerifier.DeleteLocalGroup("testName1"); @@ -169,30 +175,33 @@ public void CanCreateNewGroupWithComment() // Verify that a comment can be added to an existing group [RuntimeFact] - public void CanAddCommentToExistingGroup() + public void CanAddCommentToExistingNonDomainGroup() { UserGroupVerifier.CreateLocalGroup("testName1"); var productAddCommentToExistingUser = this.CreatePackageInstaller("ProductAddCommentToExistingGroup"); - productAddCommentToExistingUser.InstallProduct(); + productAddCommentToExistingUser.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "testComment1"); + productAddCommentToExistingUser.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + // clean up UserGroupVerifier.DeleteLocalGroup("testName1"); } // Verify that a comment can be repaired for a new group [RuntimeFact] - public void CanRepairCommentOfNewGroup() + public void CanRepairCommentOfNewNonDomainGroup() { var productNewUserWithComment = this.CreatePackageInstaller("ProductNewGroupWithComment"); - productNewUserWithComment.InstallProduct(); + productNewUserWithComment.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); UserGroupVerifier.SetGroupComment(String.Empty, "testName1", ""); - productNewUserWithComment.RepairProduct(); + productNewUserWithComment.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS); UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "testComment1"); + productNewUserWithComment.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); // clean up UserGroupVerifier.DeleteLocalGroup("testName1"); @@ -200,14 +209,15 @@ public void CanRepairCommentOfNewGroup() // Verify that a comment can be changed for an existing group [RuntimeFact] - public void CanChangeCommentOfExistingGroup() + public void CanChangeCommentOfExistingNonDomainGroup() { UserGroupVerifier.CreateLocalGroup("testName1"); UserGroupVerifier.SetGroupComment(String.Empty, "testName1", "initialTestComment1"); var productNewUserWithComment = this.CreatePackageInstaller("ProductNewGroupWithComment"); - productNewUserWithComment.InstallProduct(); + productNewUserWithComment.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "testComment1"); + productNewUserWithComment.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); // clean up UserGroupVerifier.DeleteLocalGroup("testName1"); @@ -215,7 +225,7 @@ public void CanChangeCommentOfExistingGroup() // Verify that a comment can be rolled back for an existing group [RuntimeFact] - public void CanRollbackCommentOfExistingGroup() + public void CanRollbackCommentOfExistingNonDomainGroup() { UserGroupVerifier.CreateLocalGroup("testName1"); UserGroupVerifier.SetGroupComment(String.Empty, "testName1", "initialTestComment1"); @@ -232,7 +242,7 @@ public void CanRollbackCommentOfExistingGroup() // Verify that a comment can be deleted for an existing group [RuntimeFact] - public void CanDeleteCommentOfExistingGroup() + public void CanDeleteCommentOfExistingNonDomainGroup() { UserGroupVerifier.CreateLocalGroup("testName1"); UserGroupVerifier.SetGroupComment(String.Empty, "testName1", "testComment1"); @@ -243,29 +253,64 @@ public void CanDeleteCommentOfExistingGroup() // Verify that comment was removed. UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", ""); + + productCommentDelete.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + // clean up UserGroupVerifier.DeleteLocalGroup("testName1"); } - // Verify that a comment can be deleted for an existing group - [RuntimeFact] - public void CanNestGroups() + #endregion + + #region Domain + // Verify that a domain group can be nested within a local group + [RuntimeFact(DomainRequired = true)] + public void CanNestDomainGroups() { + var testDomain = System.Environment.UserDomainName; var productNestedGroups = this.CreatePackageInstaller("ProductNestedGroups"); - productNestedGroups.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + productNestedGroups.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, $"TESTDOMAIN={testDomain}"); // Verify group nested membership - UserGroupVerifier.VerifyIsMemberOf(String.Empty, "Authenticated Users", new string[] { "testName1", "testName2" }); - UserGroupVerifier.VerifyIsMemberOf(String.Empty, "Everyone", new string[] { "testName1" }); + UserGroupVerifier.VerifyIsMemberOf(testDomain, "Domain Users", new string[] { "testName1", "testName2" }); + //UserGroupVerifier.VerifyIsMemberOf(String.Empty, "Everyone", new string[] { "testName1" }); + + UserGroupVerifier.VerifyIsNotMemberOf(testDomain, "Domain Users", new string[] { "testName3" }); + //UserGroupVerifier.VerifyIsNotMemberOf(String.Empty, "Everyone", new string[] { "testName2", "testName3" }); - UserGroupVerifier.VerifyIsNotMemberOf(String.Empty, "Authenticated Users", new string[] { "testName3" }); - UserGroupVerifier.VerifyIsNotMemberOf(String.Empty, "Everyone", new string[] { "testName2", "testName3" }); + productNestedGroups.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, $"TESTDOMAIN={testDomain}"); // clean up UserGroupVerifier.DeleteLocalGroup("testName1"); UserGroupVerifier.DeleteLocalGroup("testName2"); UserGroupVerifier.DeleteLocalGroup("testName3"); } + + // Verify the rollback action reverts all Users changes. + [RuntimeFact(DomainRequired = true)] + public void CanRollbackDomainGroups() + { + var testDomain = System.Environment.UserDomainName; + UserGroupVerifier.CreateLocalGroup("testName3"); + var productFail = this.CreatePackageInstaller("ProductFail"); + + // make sure the user accounts are deleted before we start + UserGroupVerifier.DeleteLocalGroup("testName1"); + UserGroupVerifier.DeleteLocalGroup("testName2"); + + productFail.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, $"TESTDOMAIN={testDomain}"); + + // Verify added Users were removed on rollback. + Assert.False(UserGroupVerifier.GroupExists(String.Empty, "testName1"), String.Format("Group '{0}' was not removed on Rollback", "testName1")); + Assert.False(UserGroupVerifier.GroupExists(String.Empty, "testName2"), String.Format("Group '{0}' was not removed on Rollback", "testName2")); + + // clean up + UserGroupVerifier.DeleteLocalGroup("testName1"); + UserGroupVerifier.DeleteLocalGroup("testName2"); + UserGroupVerifier.DeleteLocalGroup("testName3"); + } + + #endregion } } diff --git a/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd b/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd index a2e67c891..ed1d50b68 100644 --- a/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd +++ b/src/test/msi/WixToolsetTest.MsiE2E/runtests.cmd @@ -1,2 +1,3 @@ SET RuntimeTestsEnabled=true +SET RuntimeDomainTestsEnabled=true dotnet test WixToolsetTest.MsiE2E.dll -v normal --logger trx