aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBevan Weiss <bevan.weiss@gmail.com>2024-06-18 19:03:40 +1000
committerRob Mensching <rob@firegiant.com>2025-02-11 23:14:49 -0800
commit7b1bb025dea1d1e9e144cce0dcbba2d86f053b8f (patch)
treec2fc969615d858ee40f54cfba406648e9c2743c3
parent040e50ec2859c1de70cd8e9f957474321774f293 (diff)
downloadwix-7b1bb025dea1d1e9e144cce0dcbba2d86f053b8f.tar.gz
wix-7b1bb025dea1d1e9e144cce0dcbba2d86f053b8f.tar.bz2
wix-7b1bb025dea1d1e9e144cce0dcbba2d86f053b8f.zip
CreateGroups additions
Signed-off-by: Bevan Weiss <bevan.weiss@gmail.com>
-rw-r--r--src/ext/Firewall/ca/CustomMsiErrors.h4
-rw-r--r--src/ext/Iis/ca/sca.h12
-rw-r--r--src/ext/Util/ca/CustomMsiErrors.h6
-rw-r--r--src/ext/Util/ca/sca.h12
-rw-r--r--src/ext/Util/ca/scacost.h2
-rw-r--r--src/ext/Util/ca/scaexec.cpp628
-rw-r--r--src/ext/Util/ca/scagroup.cpp503
-rw-r--r--src/ext/Util/ca/scagroup.h47
-rw-r--r--src/ext/Util/ca/scanet.cpp50
-rw-r--r--src/ext/Util/ca/scanet.h4
-rw-r--r--src/ext/Util/ca/scasched.cpp47
-rw-r--r--src/ext/Util/ca/scauser.cpp83
-rw-r--r--src/ext/Util/ca/scauser.h20
-rw-r--r--src/ext/Util/ca/utilca.def4
-rw-r--r--src/ext/Util/ca/utilca.vcxproj4
-rw-r--r--src/ext/Util/test/WixToolsetTest.Util/TestData/CreateGroup/Package.wxs15
-rw-r--r--src/ext/Util/test/WixToolsetTest.Util/TestData/CreateGroup/PackageComponents.wxs80
-rw-r--r--src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs153
-rw-r--r--src/ext/Util/wixext/Symbols/GroupGroupSymbol.cs56
-rw-r--r--src/ext/Util/wixext/Symbols/GroupSymbol.cs97
-rw-r--r--src/ext/Util/wixext/Symbols/UtilSymbolDefinitions.cs8
-rw-r--r--src/ext/Util/wixext/UtilCompiler.cs141
-rw-r--r--src/ext/Util/wixext/UtilDecompiler.cs62
-rw-r--r--src/ext/Util/wixext/UtilTableDefinitions.cs25
-rw-r--r--src/ext/Util/wixlib/UtilExtension.wxs8
-rw-r--r--src/ext/Util/wixlib/UtilExtension_Platform.wxi14
-rw-r--r--src/ext/Util/wixlib/de-de.wxl4
-rw-r--r--src/ext/Util/wixlib/en-us.wxl4
-rw-r--r--src/ext/Util/wixlib/es-es.wxl4
-rw-r--r--src/ext/Util/wixlib/fr-fr.wxl4
-rw-r--r--src/ext/Util/wixlib/it-it.wxl4
-rw-r--r--src/ext/Util/wixlib/ja-jp.wxl4
-rw-r--r--src/ext/Util/wixlib/nl-nl.wxl4
-rw-r--r--src/ext/Util/wixlib/pt-br.wxl4
-rw-r--r--src/ext/caerr.wxi3
-rw-r--r--src/libs/wcautil/WixToolset.WcaUtil/custommsierrors.h3
-rw-r--r--src/test/burn/WixTestTools/UserGroupVerifier.cs198
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductA/ProductA.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductA/product.wxs25
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/ProductAddCommentToExistingGroup.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/product.wxs23
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/ProductCommentDelete.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/product.wxs18
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/ProductCommentFail.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/product_fail.wxs22
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/ProductFail.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/product_fail.wxs29
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/FailIfExists.wxs18
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/ProductFailIfExists.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/ProductNestedGroups.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/product.wxs33
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/ProductNewGroupWithComment.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/product.wxs23
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/NonVitalUserGroup.wxs19
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/ProductNonVitalUserGroup.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/ProductRestrictedDomain.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/RestrictedDomain.wxs20
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wixproj13
-rw-r--r--src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs19
-rw-r--r--src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionGroupTests.cs271
60 files changed, 2848 insertions, 136 deletions
diff --git a/src/ext/Firewall/ca/CustomMsiErrors.h b/src/ext/Firewall/ca/CustomMsiErrors.h
index f149fb31..6a7f4b3c 100644
--- a/src/ext/Firewall/ca/CustomMsiErrors.h
+++ b/src/ext/Firewall/ca/CustomMsiErrors.h
@@ -87,6 +87,10 @@
87#define msierrUSRFailedUserCreateExists 26404 87#define msierrUSRFailedUserCreateExists 26404
88#define msierrUSRFailedGrantLogonAsService 26405 88#define msierrUSRFailedGrantLogonAsService 26405
89 89
90#define msierrGRPFailedGroupCreate 26421
91#define msierrGRPFailedGroupGroupAdd 26422
92#define msierrGRPFailedGroupCreateExists 26423
93
90#define msierrDependencyMissingDependencies 26451 94#define msierrDependencyMissingDependencies 26451
91#define msierrDependencyHasDependents 26452 95#define msierrDependencyHasDependents 26452
92 96
diff --git a/src/ext/Iis/ca/sca.h b/src/ext/Iis/ca/sca.h
index 6921613b..b97e9a7e 100644
--- a/src/ext/Iis/ca/sca.h
+++ b/src/ext/Iis/ca/sca.h
@@ -123,3 +123,15 @@ enum SCAU_ATTRIBUTES
123 SCAU_NON_VITAL = 0x00000400, 123 SCAU_NON_VITAL = 0x00000400,
124 SCAU_REMOVE_COMMENT = 0x00000800, 124 SCAU_REMOVE_COMMENT = 0x00000800,
125}; 125};
126
127// group creation attributes definitions
128enum SCAG_ATTRIBUTES
129{
130 SCAG_FAIL_IF_EXISTS = 0x00000001,
131 SCAG_UPDATE_IF_EXISTS = 0x00000002,
132
133 SCAG_DONT_REMOVE_ON_UNINSTALL = 0x00000004,
134 SCAG_DONT_CREATE_GROUP = 0x00000008,
135 SCAG_NON_VITAL = 0x00000010,
136 SCAG_REMOVE_COMMENT = 0x00000020,
137};
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 @@
29#define msierrUSRFailedUserCreateExists 26404 29#define msierrUSRFailedUserCreateExists 26404
30#define msierrUSRFailedGrantLogonAsService 26405 30#define msierrUSRFailedGrantLogonAsService 26405
31 31
32//Last available is 26450 \ No newline at end of file 32#define msierrGRPFailedGroupCreate 26421
33#define msierrGRPFailedGroupGroupAdd 26422
34#define msierrGRPFailedGroupCreateExists 26423
35
36//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
18 SCAU_NON_VITAL = 0x00000400, 18 SCAU_NON_VITAL = 0x00000400,
19 SCAU_REMOVE_COMMENT = 0x00000800, 19 SCAU_REMOVE_COMMENT = 0x00000800,
20}; 20};
21
22// group creation attributes definitions
23enum SCAG_ATTRIBUTES
24{
25 SCAG_FAIL_IF_EXISTS = 0x00000001,
26 SCAG_UPDATE_IF_EXISTS = 0x00000002,
27
28 SCAG_DONT_REMOVE_ON_UNINSTALL = 0x00000004,
29 SCAG_DONT_CREATE_GROUP = 0x00000008,
30 SCAG_NON_VITAL = 0x00000010,
31 SCAG_REMOVE_COMMENT = 0x00000020,
32};
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;
9const UINT COST_SMB_DROPSMB = 5000; 9const UINT COST_SMB_DROPSMB = 5000;
10const UINT COST_USER_ADD = 10000; 10const UINT COST_USER_ADD = 10000;
11const UINT COST_USER_DELETE = 10000; 11const UINT COST_USER_DELETE = 10000;
12const UINT COST_GROUP_ADD = 10000;
13const UINT COST_GROUP_DELETE = 10000;
12 14
13const UINT COST_PERFMONMANIFEST_REGISTER = 1000; 15const UINT COST_PERFMONMANIFEST_REGISTER = 1000;
14const UINT COST_PERFMONMANIFEST_UNREGISTER = 1000; 16const 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:
291 return hr; 291 return hr;
292} 292}
293 293
294static HRESULT AddGroupToGroup(
295 __in LPWSTR wzMember,
296 __in LPCWSTR wzMemberDomain,
297 __in LPCWSTR wzGroup,
298 __in LPCWSTR wzGroupDomain
299)
300{
301 Assert(wzMember && *wzMember && wzMemberDomain && wzGroup && *wzGroup && wzGroupDomain);
302
303 HRESULT hr = S_OK;
304 IADsGroup* pGroup = NULL;
305 BSTR bstrMember = NULL;
306 BSTR bstrGroup = NULL;
307 LPWSTR pwzMember = NULL;
308 LPWSTR pwzServerName = NULL;
309 LOCALGROUP_MEMBERS_INFO_3 lgmi {};
310
311 GetDomainServerName(wzGroupDomain, &pwzServerName);
312
313 // Try adding it to the local group
314 if (wzMemberDomain)
315 {
316 hr = StrAllocFormatted(&pwzMember, L"%s\\%s", wzMemberDomain, wzMember);
317 ExitOnFailure(hr, "failed to allocate group domain string");
318 }
319
320 lgmi.lgrmi3_domainandname = (NULL == pwzMember ? wzMember : pwzMember);
321 NET_API_STATUS ui = ::NetLocalGroupAddMembers(pwzServerName, wzGroup, 3, reinterpret_cast<LPBYTE>(&lgmi), 1);
322 hr = HRESULT_FROM_WIN32(ui);
323 if (HRESULT_FROM_WIN32(ERROR_MEMBER_IN_ALIAS) == hr) // if they're already a member of the group don't report an error
324 {
325 hr = S_OK;
326 }
327
328 //
329 // If we failed, try active directory
330 //
331 if (FAILED(hr))
332 {
333 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);
334
335 hr = UserCreateADsPath(wzMemberDomain, wzMember, &bstrMember);
336 ExitOnFailure(hr, "failed to create group ADsPath for group: %ls domain: %ls", wzMember, wzMemberDomain);
337
338 hr = UserCreateADsPath(wzGroupDomain, wzGroup, &bstrGroup);
339 ExitOnFailure(hr, "failed to create group ADsPath for group: %ls domain: %ls", wzGroup, wzGroupDomain);
340
341 hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast<void**>(&pGroup));
342 ExitOnFailure(hr, "Failed to get group '%ls'.", reinterpret_cast<WCHAR*>(bstrGroup));
343
344 hr = pGroup->Add(bstrMember);
345 if ((HRESULT_FROM_WIN32(ERROR_OBJECT_ALREADY_EXISTS) == hr) || (HRESULT_FROM_WIN32(ERROR_MEMBER_IN_ALIAS) == hr))
346 hr = S_OK;
347
348 ExitOnFailure(hr, "Failed to add group %ls to group '%ls'.", reinterpret_cast<WCHAR*>(bstrMember), reinterpret_cast<WCHAR*>(bstrGroup));
349 }
350
351LExit:
352 ReleaseStr(pwzServerName);
353 ReleaseStr(pwzMember);
354 ReleaseBSTR(bstrMember);
355 ReleaseBSTR(bstrGroup);
356 ReleaseObject(pGroup);
357
358 return hr;
359}
360
361static HRESULT RemoveGroupFromGroup(
362 __in LPWSTR wzMember,
363 __in LPCWSTR wzMemberDomain,
364 __in LPCWSTR wzGroup,
365 __in LPCWSTR wzGroupDomain
366)
367{
368 Assert(wzMember && *wzMember && wzMemberDomain && wzGroup && *wzGroup && wzGroupDomain);
369
370 HRESULT hr = S_OK;
371 IADsGroup* pGroup = NULL;
372 BSTR bstrMember = NULL;
373 BSTR bstrGroup = NULL;
374 LPWSTR pwzMember = NULL;
375 LPWSTR pwzServerName = NULL;
376 LOCALGROUP_MEMBERS_INFO_3 lgmi {};
377
378 GetDomainServerName(wzGroupDomain, &pwzServerName, DS_WRITABLE_REQUIRED);
379
380 // Try removing it from the local group
381 if (wzMemberDomain)
382 {
383 hr = StrAllocFormatted(&pwzMember, L"%s\\%s", wzMemberDomain, wzMember);
384 ExitOnFailure(hr, "failed to allocate group domain string");
385 }
386
387 lgmi.lgrmi3_domainandname = (NULL == pwzMember ? wzMember : pwzMember);
388 NET_API_STATUS ui = ::NetLocalGroupDelMembers(pwzServerName, wzGroup, 3, reinterpret_cast<LPBYTE>(&lgmi), 1);
389 hr = HRESULT_FROM_WIN32(ui);
390 if (HRESULT_FROM_WIN32(ERROR_MEMBER_NOT_IN_ALIAS) == hr
391 || HRESULT_FROM_WIN32(NERR_GroupNotFound) == hr
392 || 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
393 {
394 hr = S_OK;
395 }
396
397 //
398 // If we failed, try active directory
399 //
400 if (FAILED(hr))
401 {
402 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);
403
404 hr = UserCreateADsPath(wzMemberDomain, wzMember, &bstrMember);
405 ExitOnFailure(hr, "failed to create group ADsPath in order to remove group: %ls domain: %ls from a group", wzMember, wzMemberDomain);
406
407 hr = UserCreateADsPath(wzGroupDomain, wzGroup, &bstrGroup);
408 ExitOnFailure(hr, "failed to create group ADsPath in order to remove group from group: %ls domain: %ls", wzGroup, wzGroupDomain);
409
410 hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast<void**>(&pGroup));
411 if ((HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)) // if parent group not found, no need to remove membership from group
412 {
413 hr = S_OK;
414 ExitFunction();
415 }
416 ExitOnFailure(hr, "Failed to get group '%ls'.", reinterpret_cast<WCHAR*>(bstrGroup));
417
418 hr = pGroup->Remove(bstrMember);
419 if ((HRESULT_FROM_WIN32(ERROR_MEMBER_NOT_IN_ALIAS) == hr)) // if already not a member, no need to worry about error
420 hr = S_OK;
421 ExitOnFailure(hr, "Failed to remove group %ls from group '%ls'.", reinterpret_cast<WCHAR*>(bstrMember), reinterpret_cast<WCHAR*>(bstrGroup));
422 }
423
424LExit:
425 ReleaseStr(pwzServerName);
426 ReleaseStr(pwzMember);
427 ReleaseBSTR(bstrMember);
428 ReleaseBSTR(bstrGroup);
429 ReleaseObject(pGroup);
430
431 return hr;
432}
294 433
295static HRESULT GetUserHasRight( 434static HRESULT GetUserHasRight(
296 __in LSA_HANDLE hPolicy, 435 __in LSA_HANDLE hPolicy,
@@ -590,6 +729,15 @@ static HRESULT SetUserComment(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __
590 return HRESULT_FROM_WIN32(er); 729 return HRESULT_FROM_WIN32(er);
591} 730}
592 731
732static HRESULT SetGroupComment(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in LPWSTR pwzComment)
733{
734 _LOCALGROUP_INFO_1002 groupInfo1002 {};
735
736 groupInfo1002.lgrpi1002_comment = pwzComment;
737 NET_API_STATUS er = ::NetLocalGroupSetInfo(pwzServerName, pwzName, 1002, reinterpret_cast<LPBYTE>(&groupInfo1002), NULL);
738 return HRESULT_FROM_WIN32(er);
739}
740
593static HRESULT SetUserFlags(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in DWORD flags) 741static HRESULT SetUserFlags(__in LPWSTR pwzServerName, __in LPWSTR pwzName, __in DWORD flags)
594{ 742{
595 NET_API_STATUS er = NERR_Success; 743 NET_API_STATUS er = NERR_Success;
@@ -693,6 +841,76 @@ LExit:
693 return hr; 841 return hr;
694} 842}
695 843
844static HRESULT RemoveGroupInternal(
845 LPWSTR wzGroupCaData,
846 LPWSTR wzDomain,
847 LPWSTR wzName,
848 int iAttributes
849)
850{
851 HRESULT hr = S_OK;
852
853 LPWSTR pwz = NULL;
854 LPWSTR pwzGroup = NULL;
855 LPWSTR pwzGroupDomain = NULL;
856 LPWSTR pwzServerName = NULL;
857
858 //
859 // Remove the Group if the group was created by us.
860 //
861 if (!(SCAG_DONT_CREATE_GROUP & iAttributes))
862 {
863 GetDomainServerName(wzDomain, &pwzServerName, DS_WRITABLE_REQUIRED);
864
865 NET_API_STATUS er = ::NetLocalGroupDel(pwzServerName, wzName);
866 hr = HRESULT_FROM_WIN32(er);
867 if (HRESULT_FROM_WIN32(ERROR_NO_SUCH_ALIAS) == hr
868 || HRESULT_FROM_WIN32(NERR_GroupNotFound) == hr) // we wanted to delete it.. and the group doesn't exist.. solved.
869 {
870 hr = S_OK;
871 }
872 ExitOnFailure(hr, "failed to delete group: %ls", wzName);
873 }
874 else
875 {
876 //
877 // Remove the group from other groups
878 //
879 pwz = wzGroupCaData;
880 while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup)))
881 {
882 hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain);
883
884 if (FAILED(hr))
885 {
886 WcaLogError(hr, "failed to get domain for group: %ls, continuing anyway.", pwzGroup);
887 }
888 else
889 {
890 hr = RemoveGroupFromGroup(wzName, wzDomain, pwzGroup, pwzGroupDomain);
891 if (FAILED(hr))
892 {
893 WcaLogError(hr, "failed to remove group: %ls from group %ls, continuing anyway.", wzName, pwzGroup);
894 }
895 }
896 }
897
898 if (E_NOMOREITEMS == hr) // if there are no more items, all is well
899 {
900 hr = S_OK;
901 }
902
903 ExitOnFailure(hr, "failed to get next group from which to remove group:%ls", wzName);
904 }
905LExit:
906 ReleaseStr(pwzServerName);
907 ReleaseStr(pwzGroup);
908 ReleaseStr(pwzGroupDomain);
909
910 return hr;
911}
912
913
696/******************************************************************** 914/********************************************************************
697 CreateUser - CUSTOM ACTION ENTRY POINT for creating users 915 CreateUser - CUSTOM ACTION ENTRY POINT for creating users
698 916
@@ -1173,3 +1391,413 @@ LExit:
1173 1391
1174 return WcaFinalize(er); 1392 return WcaFinalize(er);
1175} 1393}
1394
1395/********************************************************************
1396 CreateGroup - CUSTOM ACTION ENTRY POINT for creating groups
1397
1398 Input: deferred CustomActionData - GroupName\tDomain\tComment\tAttributes
1399 * *****************************************************************/
1400extern "C" UINT __stdcall CreateGroup(
1401 __in MSIHANDLE hInstall
1402)
1403{
1404 //AssertSz(0, "Debug CreateGroup");
1405
1406 HRESULT hr = S_OK;
1407 NET_API_STATUS er = ERROR_SUCCESS;
1408
1409 LPWSTR pwzData = NULL;
1410 LPWSTR pwz = NULL;
1411 LPWSTR pwzName = NULL;
1412 LPWSTR pwzDomain = NULL;
1413 LPWSTR pwzComment = NULL;
1414 LPWSTR pwzScriptKey = NULL;
1415 LPWSTR pwzGroup = NULL;
1416 LPWSTR pwzGroupDomain = NULL;
1417 int iAttributes = 0;
1418 BOOL fInitializedCom = FALSE;
1419
1420 LOCALGROUP_INFO_1* pGroupInfo1 = NULL;
1421
1422 WCA_CASCRIPT_HANDLE hRollbackScript = NULL;
1423 int iRollbackAttributes = 0;
1424
1425 DWORD dw;
1426 LPWSTR pwzServerName = NULL;
1427
1428 hr = WcaInitialize(hInstall, "CreateGroup");
1429 ExitOnFailure(hr, "failed to initialize");
1430
1431 hr = ::CoInitialize(NULL);
1432 ExitOnFailure(hr, "failed to initialize COM");
1433 fInitializedCom = TRUE;
1434
1435 hr = WcaGetProperty(L"CustomActionData", &pwzData);
1436 ExitOnFailure(hr, "failed to get CustomActionData");
1437
1438 WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData);
1439
1440 //
1441 // Read in the CustomActionData
1442 //
1443 pwz = pwzData;
1444 hr = WcaReadStringFromCaData(&pwz, &pwzName);
1445 ExitOnFailure(hr, "failed to read group name from custom action data");
1446
1447 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
1448 ExitOnFailure(hr, "failed to read domain from custom action data");
1449
1450 hr = WcaReadStringFromCaData(&pwz, &pwzComment);
1451 ExitOnFailure(hr, "failed to read group comment from custom action data");
1452
1453 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
1454 ExitOnFailure(hr, "failed to read attributes from custom action data");
1455
1456 hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey);
1457 ExitOnFailure(hr, "failed to read encoding key from custom action data");
1458
1459 if (!(SCAG_DONT_CREATE_GROUP & iAttributes))
1460 {
1461 hr = GetDomainServerName(pwzDomain, &pwzServerName, DS_WRITABLE_REQUIRED);
1462 ExitOnFailure(hr, "failed to find Domain %ls.", pwzDomain);
1463
1464 // Set the group's comment
1465 if (SCAG_REMOVE_COMMENT & iAttributes)
1466 {
1467 StrAllocString(&pwzComment, L"", 0);
1468 }
1469
1470 //
1471 // Create the Group
1472 //
1473 LOCALGROUP_INFO_1 groupInfo1;
1474 groupInfo1.lgrpi1_name = pwzName;
1475 groupInfo1.lgrpi1_comment = pwzComment;
1476 er = ::NetLocalGroupAdd(pwzServerName, 1, reinterpret_cast<LPBYTE>(&groupInfo1), &dw);
1477 hr = HRESULT_FROM_WIN32(er);
1478
1479 if (HRESULT_FROM_WIN32(ERROR_ALIAS_EXISTS) == hr
1480 || HRESULT_FROM_WIN32(NERR_GroupExists) == hr)
1481 {
1482 if (SCAG_FAIL_IF_EXISTS & iAttributes)
1483 {
1484 MessageExitOnFailure(hr, msierrGRPFailedGroupCreateExists, "Group (%ls) was not supposed to exist, but does", pwzName);
1485 }
1486
1487 hr = S_OK; // Make sure that we don't report this situation as an error
1488 // if we fall through the tests that follow.
1489
1490 if (SCAG_UPDATE_IF_EXISTS & iAttributes)
1491 {
1492 er = ::NetLocalGroupGetInfo(pwzServerName, pwzName, 1, reinterpret_cast<LPBYTE*>(&pGroupInfo1));
1493 hr = HRESULT_FROM_WIN32(er);
1494 if (S_OK == hr)
1495 {
1496 // There is no rollback scheduled if the key is empty.
1497 // Best effort to get original configuration and save it in the script so rollback can restore it.
1498 if (*pwzScriptKey)
1499 {
1500 // Try to open the rollback script
1501 hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript);
1502
1503 if (hRollbackScript && INVALID_HANDLE_VALUE != hRollbackScript->hScriptFile)
1504 {
1505 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE);
1506 }
1507 else
1508 {
1509 hRollbackScript = NULL;
1510 hr = WcaCaScriptCreate(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, FALSE, &hRollbackScript);
1511 ExitOnFailure(hr, "Failed to open rollback CustomAction script.");
1512
1513 iRollbackAttributes = 0;
1514
1515 hr = WcaCaScriptWriteString(hRollbackScript, pGroupInfo1->lgrpi1_comment);
1516 ExitOnFailure(hr, "Failed to add rollback comment to rollback script.");
1517
1518 if (!pGroupInfo1->lgrpi1_comment || !*pGroupInfo1->lgrpi1_comment)
1519 {
1520 iRollbackAttributes |= SCAG_REMOVE_COMMENT;
1521 }
1522
1523 hr = WcaCaScriptWriteNumber(hRollbackScript, iRollbackAttributes);
1524 ExitOnFailure(hr, "Failed to add rollback attributes to rollback script.");
1525
1526 // Nudge the system to get all our rollback data written to disk.
1527 WcaCaScriptFlush(hRollbackScript);
1528 }
1529 }
1530 }
1531
1532 if (S_OK == hr)
1533 {
1534 if (SCAG_REMOVE_COMMENT & iAttributes)
1535 {
1536 hr = SetGroupComment(pwzServerName, pwzName, L"");
1537 if (FAILED(hr))
1538 {
1539 WcaLogError(hr, "failed to clear comment for group %ls\\%ls, continuing anyway.", pwzServerName, pwzName);
1540 hr = S_OK;
1541 }
1542 }
1543 else if (pwzComment && *pwzComment)
1544 {
1545 hr = SetGroupComment(pwzServerName, pwzName, pwzComment);
1546 if (FAILED(hr))
1547 {
1548 WcaLogError(hr, "failed to set comment to %ls for group %ls\\%ls, continuing anyway.", pwzComment, pwzServerName, pwzName);
1549 hr = S_OK;
1550 }
1551 }
1552 }
1553 }
1554 }
1555 MessageExitOnFailure(hr, msierrGRPFailedGroupCreate, "failed to create group: %ls", pwzName);
1556
1557 //
1558 // Add the groups to groups
1559 //
1560 while (S_OK == (hr = WcaReadStringFromCaData(&pwz, &pwzGroup)))
1561 {
1562 hr = WcaReadStringFromCaData(&pwz, &pwzGroupDomain);
1563 ExitOnFailure(hr, "failed to get domain for group: %ls", pwzGroup);
1564
1565 WcaLog(LOGMSG_STANDARD, "Adding group %ls\\%ls to group %ls\\%ls", pwzDomain, pwzName, pwzGroupDomain, pwzGroup);
1566 hr = AddGroupToGroup(pwzName, pwzDomain, pwzGroup, pwzGroupDomain);
1567 MessageExitOnFailure(hr, msierrUSRFailedUserGroupAdd, "failed to add group: %ls to group %ls", pwzName, pwzGroup);
1568 }
1569 if (E_NOMOREITEMS == hr) // if there are no more items, all is well
1570 {
1571 hr = S_OK;
1572 }
1573 ExitOnFailure(hr, "failed to get next group in which to include group: %ls", pwzName);
1574 }
1575
1576LExit:
1577 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_PRESERVE);
1578
1579 if (pGroupInfo1)
1580 {
1581 ::NetApiBufferFree((LPVOID)pGroupInfo1);
1582 }
1583
1584 ReleaseStr(pwzData);
1585 ReleaseStr(pwzName);
1586 ReleaseStr(pwzDomain);
1587 ReleaseStr(pwzComment);
1588 ReleaseStr(pwzScriptKey);
1589 ReleaseStr(pwzGroup);
1590 ReleaseStr(pwzGroupDomain);
1591
1592 if (fInitializedCom)
1593 {
1594 ::CoUninitialize();
1595 }
1596
1597 if (SCAG_NON_VITAL & iAttributes)
1598 {
1599 er = ERROR_SUCCESS;
1600 }
1601 else if (FAILED(hr))
1602 {
1603 er = ERROR_INSTALL_FAILURE;
1604 }
1605
1606 return WcaFinalize(er);
1607}
1608
1609
1610/********************************************************************
1611 CreateGroupRollback - CUSTOM ACTION ENTRY POINT for CreateGroup rollback
1612
1613 * *****************************************************************/
1614extern "C" UINT __stdcall CreateGroupRollback(
1615 MSIHANDLE hInstall
1616)
1617{
1618 //AssertSz(0, "Debug CreateGroupRollback");
1619
1620 HRESULT hr = S_OK;
1621 UINT er = ERROR_SUCCESS;
1622
1623 LPWSTR pwzData = NULL;
1624 LPWSTR pwz = NULL;
1625 LPWSTR pwzScriptKey = NULL;
1626 LPWSTR pwzName = NULL;
1627 LPWSTR pwzDomain = NULL;
1628 LPWSTR pwzComment = NULL;
1629 int iAttributes = 0;
1630 BOOL fInitializedCom = FALSE;
1631
1632 WCA_CASCRIPT_HANDLE hRollbackScript = NULL;
1633 LPWSTR pwzRollbackData = NULL;
1634 int iOriginalAttributes = 0;
1635 LPWSTR pwzOriginalComment = NULL;
1636
1637 hr = WcaInitialize(hInstall, "CreateGroupRollback");
1638 ExitOnFailure(hr, "failed to initialize");
1639
1640 hr = ::CoInitialize(NULL);
1641 ExitOnFailure(hr, "failed to initialize COM");
1642 fInitializedCom = TRUE;
1643
1644 hr = WcaGetProperty(L"CustomActionData", &pwzData);
1645 ExitOnFailure(hr, "failed to get CustomActionData");
1646
1647 WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData);
1648
1649 //
1650 // Read in the CustomActionData
1651 //
1652 pwz = pwzData;
1653 hr = WcaReadStringFromCaData(&pwz, &pwzScriptKey);
1654 ExitOnFailure(hr, "failed to read encoding key from custom action data");
1655
1656 hr = WcaReadStringFromCaData(&pwz, &pwzName);
1657 ExitOnFailure(hr, "failed to read name from custom action data");
1658
1659 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
1660 ExitOnFailure(hr, "failed to read domain from custom action data");
1661
1662 hr = WcaReadStringFromCaData(&pwz, &pwzComment);
1663 ExitOnFailure(hr, "failed to read comment from custom action data");
1664
1665 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
1666 ExitOnFailure(hr, "failed to read attributes from custom action data");
1667
1668 // Best effort to read original configuration from CreateUser.
1669 hr = WcaCaScriptOpen(WCA_ACTION_INSTALL, WCA_CASCRIPT_ROLLBACK, FALSE, pwzScriptKey, &hRollbackScript);
1670 if (FAILED(hr))
1671 {
1672 WcaLogError(hr, "Failed to open rollback CustomAction script, continuing anyway.");
1673 }
1674 else
1675 {
1676 hr = WcaCaScriptReadAsCustomActionData(hRollbackScript, &pwzRollbackData);
1677 if (FAILED(hr))
1678 {
1679 WcaLogError(hr, "Failed to read rollback script into CustomAction data, continuing anyway.");
1680 }
1681 else
1682 {
1683 WcaLog(LOGMSG_TRACEONLY, "Rollback Data: %ls", pwzRollbackData);
1684
1685 pwz = pwzRollbackData;
1686 hr = WcaReadStringFromCaData(&pwz, &pwzOriginalComment);
1687 if (FAILED(hr))
1688 {
1689 WcaLogError(hr, "failed to read comment from rollback data, continuing anyway");
1690 }
1691 else
1692 {
1693 pwzComment = pwzOriginalComment;
1694 }
1695 hr = WcaReadIntegerFromCaData(&pwz, &iOriginalAttributes);
1696 if (FAILED(hr))
1697 {
1698 WcaLogError(hr, "failed to read attributes from rollback data, continuing anyway");
1699 }
1700 else
1701 {
1702 iAttributes |= iOriginalAttributes;
1703 }
1704 }
1705 }
1706
1707 hr = RemoveGroupInternal(pwz, pwzDomain, pwzName, iAttributes);
1708
1709LExit:
1710 WcaCaScriptClose(hRollbackScript, WCA_CASCRIPT_CLOSE_DELETE);
1711
1712 ReleaseStr(pwzData);
1713 ReleaseStr(pwzName);
1714 ReleaseStr(pwzDomain);
1715 ReleaseStr(pwzComment);
1716 ReleaseStr(pwzScriptKey);
1717 ReleaseStr(pwzRollbackData);
1718 ReleaseStr(pwzOriginalComment);
1719
1720 if (fInitializedCom)
1721 {
1722 ::CoUninitialize();
1723 }
1724
1725 if (FAILED(hr))
1726 {
1727 er = ERROR_INSTALL_FAILURE;
1728 }
1729
1730 return WcaFinalize(er);
1731}
1732
1733
1734/********************************************************************
1735 RemoveGroup - CUSTOM ACTION ENTRY POINT for removing groups
1736
1737 Input: deferred CustomActionData - Name\tDomain
1738 * *****************************************************************/
1739extern "C" UINT __stdcall RemoveGroup(
1740 MSIHANDLE hInstall
1741)
1742{
1743 //AssertSz(0, "Debug RemoveGroup");
1744
1745 HRESULT hr = S_OK;
1746 UINT er = ERROR_SUCCESS;
1747
1748 LPWSTR pwzData = NULL;
1749 LPWSTR pwz = NULL;
1750 LPWSTR pwzName = NULL;
1751 LPWSTR pwzDomain = NULL;
1752 LPWSTR pwzComment = NULL;
1753 int iAttributes = 0;
1754 BOOL fInitializedCom = FALSE;
1755
1756 hr = WcaInitialize(hInstall, "RemoveGroup");
1757 ExitOnFailure(hr, "failed to initialize");
1758
1759 hr = ::CoInitialize(NULL);
1760 ExitOnFailure(hr, "failed to initialize COM");
1761 fInitializedCom = TRUE;
1762
1763 hr = WcaGetProperty(L"CustomActionData", &pwzData);
1764 ExitOnFailure(hr, "failed to get CustomActionData");
1765
1766 WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzData);
1767
1768 //
1769 // Read in the CustomActionData
1770 //
1771 pwz = pwzData;
1772 hr = WcaReadStringFromCaData(&pwz, &pwzName);
1773 ExitOnFailure(hr, "failed to read name from custom action data");
1774
1775 hr = WcaReadStringFromCaData(&pwz, &pwzDomain);
1776 ExitOnFailure(hr, "failed to read domain from custom action data");
1777
1778 hr = WcaReadStringFromCaData(&pwz, &pwzComment);
1779 ExitOnFailure(hr, "failed to read comment from custom action data");
1780
1781 hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
1782 ExitOnFailure(hr, "failed to read attributes from custom action data");
1783
1784 hr = RemoveGroupInternal(pwz, pwzDomain, pwzName, iAttributes);
1785
1786LExit:
1787 ReleaseStr(pwzData);
1788 ReleaseStr(pwzName);
1789 ReleaseStr(pwzDomain);
1790 ReleaseStr(pwzComment);
1791
1792 if (fInitializedCom)
1793 {
1794 ::CoUninitialize();
1795 }
1796
1797 if (FAILED(hr))
1798 {
1799 er = ERROR_INSTALL_FAILURE;
1800 }
1801
1802 return WcaFinalize(er);
1803}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "scanet.h"
5
6LPCWSTR vcsGroupQuery = L"SELECT `Group`, `Component_`, `Name`, `Domain` FROM `Wix4Group` WHERE `Group`=?";
7enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain };
8
9LPCWSTR vcsGroupGroupQuery = L"SELECT `Parent_`, `Child_` FROM `Wix6GroupGroup` WHERE `Child_`=?";
10enum eGroupGroupQuery { vggqParent = 1, vggqChild };
11
12LPCWSTR vActionableGroupQuery = L"SELECT `Group`,`Component_`,`Name`,`Domain`,`Comment`,`Attributes` FROM `Wix4Group`,`Wix6Group` WHERE `Component_` IS NOT NULL AND `Group`=`Group_`";
13enum eActionableGroupQuery { vagqGroup = 1, vagqComponent, vagqName, vagqDomain, vagqComment, vagqAttributes };
14
15static HRESULT AddGroupToList(
16 __inout SCA_GROUP** ppsgList
17 );
18
19
20HRESULT __stdcall ScaGetGroup(
21 __in LPCWSTR wzGroup,
22 __out SCA_GROUP* pscag
23 )
24{
25 if (!wzGroup || *wzGroup==0 || !pscag)
26 {
27 return E_INVALIDARG;
28 }
29
30 HRESULT hr = S_OK;
31 PMSIHANDLE hView, hRec;
32
33 LPWSTR pwzData = NULL;
34
35 hRec = ::MsiCreateRecord(1);
36 hr = WcaSetRecordString(hRec, 1, wzGroup);
37 ExitOnFailure(hr, "Failed to look up Group");
38
39 hr = WcaOpenView(vcsGroupQuery, &hView);
40 ExitOnFailure(hr, "Failed to open view on Wix4Group table");
41 hr = WcaExecuteView(hView, hRec);
42 ExitOnFailure(hr, "Failed to execute view on Wix4Group table");
43
44 hr = WcaFetchSingleRecord(hView, &hRec);
45 if (S_OK == hr)
46 {
47 hr = WcaGetRecordString(hRec, vgqGroup, &pwzData);
48 ExitOnFailure(hr, "Failed to get Wix4Group.Group");
49 hr = ::StringCchCopyW(pscag->wzKey, countof(pscag->wzKey), pwzData);
50 ExitOnFailure(hr, "Failed to copy key string to group object");
51
52 hr = WcaGetRecordString(hRec, vgqComponent, &pwzData);
53 ExitOnFailure(hr, "Failed to get Wix4Group.Component_");
54 hr = ::StringCchCopyW(pscag->wzComponent, countof(pscag->wzComponent), pwzData);
55 ExitOnFailure(hr, "Failed to copy component string to group object");
56
57 hr = WcaGetRecordFormattedString(hRec, vgqName, &pwzData);
58 ExitOnFailure(hr, "Failed to get Wix4Group.Name");
59 hr = ::StringCchCopyW(pscag->wzName, countof(pscag->wzName), pwzData);
60 ExitOnFailure(hr, "Failed to copy name string to group object");
61
62 hr = WcaGetRecordFormattedString(hRec, vgqDomain, &pwzData);
63 ExitOnFailure(hr, "Failed to get Wix4Group.Domain");
64 hr = ::StringCchCopyW(pscag->wzDomain, countof(pscag->wzDomain), pwzData);
65 ExitOnFailure(hr, "Failed to copy domain string to group object");
66 }
67 else if (E_NOMOREITEMS == hr)
68 {
69 WcaLog(LOGMSG_STANDARD, "Error: Cannot locate Wix4Group.Group='%ls'", wzGroup);
70 hr = E_FAIL;
71 }
72 else
73 {
74 ExitOnFailure(hr, "Error or found multiple matching Wix4Group rows");
75 }
76
77LExit:
78 ReleaseStr(pwzData);
79
80 return hr;
81}
82
83HRESULT __stdcall ScaGetGroupDeferred(
84 __in LPCWSTR wzGroup,
85 __in WCA_WRAPQUERY_HANDLE hGroupQuery,
86 __out SCA_USER* pscag
87 )
88{
89 if (!wzGroup || !pscag)
90 {
91 return E_INVALIDARG;
92 }
93
94 HRESULT hr = S_OK;
95 MSIHANDLE hRec, hRecTest;
96
97 LPWSTR pwzData = NULL;
98
99 // clear struct and bail right away if no group key was passed to search for
100 ::ZeroMemory(pscag, sizeof(*pscag));
101 if (!*wzGroup)
102 {
103 ExitFunction1(hr = S_OK);
104 }
105
106 // Reset back to the first record
107 WcaFetchWrappedReset(hGroupQuery);
108
109 hr = WcaFetchWrappedRecordWhereString(hGroupQuery, vgqGroup, wzGroup, &hRec);
110 if (S_OK == hr)
111 {
112 hr = WcaFetchWrappedRecordWhereString(hGroupQuery, vgqGroup, wzGroup, &hRecTest);
113 if (S_OK == hr)
114 {
115 AssertSz(FALSE, "Found multiple matching Wix4Group rows");
116 }
117
118 hr = WcaGetRecordString(hRec, vgqGroup, &pwzData);
119 ExitOnFailure(hr, "Failed to get Wix4Group.Group");
120 hr = ::StringCchCopyW(pscag->wzKey, countof(pscag->wzKey), pwzData);
121 ExitOnFailure(hr, "Failed to copy key string to group object (in deferred CA)");
122
123 hr = WcaGetRecordString(hRec, vgqComponent, &pwzData);
124 ExitOnFailure(hr, "Failed to get Wix4Group.Component_");
125 hr = ::StringCchCopyW(pscag->wzComponent, countof(pscag->wzComponent), pwzData);
126 ExitOnFailure(hr, "Failed to copy component string to group object (in deferred CA)");
127
128 hr = WcaGetRecordString(hRec, vgqName, &pwzData);
129 ExitOnFailure(hr, "Failed to get Wix4Group.Name");
130 hr = ::StringCchCopyW(pscag->wzName, countof(pscag->wzName), pwzData);
131 ExitOnFailure(hr, "Failed to copy name string to group object (in deferred CA)");
132
133 hr = WcaGetRecordString(hRec, vgqDomain, &pwzData);
134 ExitOnFailure(hr, "Failed to get Wix4Group.Domain");
135 hr = ::StringCchCopyW(pscag->wzDomain, countof(pscag->wzDomain), pwzData);
136 ExitOnFailure(hr, "Failed to copy domain string to group object (in deferred CA)");
137 }
138 else if (E_NOMOREITEMS == hr)
139 {
140 WcaLog(LOGMSG_STANDARD, "Error: Cannot locate Wix4Group.Group='%ls'", wzGroup);
141 hr = E_FAIL;
142 }
143 else
144 {
145 ExitOnFailure(hr, "Error fetching single Wix4Group row");
146 }
147
148LExit:
149 ReleaseStr(pwzData);
150
151 return hr;
152}
153
154void ScaGroupFreeList(
155 __in SCA_GROUP* psgList
156 )
157{
158 SCA_GROUP* psgDelete = psgList;
159 while (psgList)
160 {
161 psgDelete = psgList;
162 psgList = psgList->psgNext;
163
164 MemFree(psgDelete);
165 }
166}
167
168
169HRESULT ScaGroupRead(
170 __out SCA_GROUP** ppsgList
171 )
172{
173 //Assert(FALSE);
174 Assert(ppsgList);
175
176 HRESULT hr = S_OK;
177 UINT er = ERROR_SUCCESS;
178 PMSIHANDLE hView, hRec, hGroupRec, hGroupGroupView;
179
180 LPWSTR pwzData = NULL;
181
182 BOOL fGroupGroupExists = FALSE;
183
184 SCA_GROUP *psg = NULL;
185
186 INSTALLSTATE isInstalled, isAction;
187
188 if (S_OK != WcaTableExists(L"Wix4Group"))
189 {
190 WcaLog(LOGMSG_VERBOSE, "Wix4Group Table does not exist, exiting");
191 ExitFunction1(hr = S_FALSE);
192 }
193 if (S_OK != WcaTableExists(L"Wix6Group"))
194 {
195 WcaLog(LOGMSG_VERBOSE, "Wix6Group Table does not exist, exiting");
196 ExitFunction1(hr = S_FALSE);
197 }
198
199 if (S_OK == WcaTableExists(L"Wix6GroupGroup"))
200 {
201 fGroupGroupExists = TRUE;
202 }
203
204 //
205 // loop through all the groups
206 //
207 hr = WcaOpenExecuteView(vActionableGroupQuery, &hView);
208 ExitOnFailure(hr, "failed to open view on Wix4Group,Wix6Group table(s)");
209 while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
210 {
211 hr = WcaGetRecordString(hRec, vagqComponent, &pwzData);
212 ExitOnFailure(hr, "failed to get Wix4Group.Component");
213
214 er = ::MsiGetComponentStateW(WcaGetInstallHandle(), pwzData, &isInstalled, &isAction);
215 hr = HRESULT_FROM_WIN32(er);
216 ExitOnFailure(hr, "failed to get Component state for Wix4Group");
217
218 // don't bother if we aren't installing or uninstalling this component
219 if (WcaIsInstalling(isInstalled, isAction) || WcaIsUninstalling(isInstalled, isAction))
220 {
221 //
222 // Add the group to the list and populate it's values
223 //
224 hr = AddGroupToList(ppsgList);
225 ExitOnFailure(hr, "failed to add group to list");
226
227 psg = *ppsgList;
228
229 psg->isInstalled = isInstalled;
230 psg->isAction = isAction;
231 hr = ::StringCchCopyW(psg->wzComponent, countof(psg->wzComponent), pwzData);
232 ExitOnFailure(hr, "failed to copy component name: %ls", pwzData);
233
234 hr = WcaGetRecordString(hRec, vagqGroup, &pwzData);
235 ExitOnFailure(hr, "failed to get Wix4Group.Group");
236 hr = ::StringCchCopyW(psg->wzKey, countof(psg->wzKey), pwzData);
237 ExitOnFailure(hr, "failed to copy group key: %ls", pwzData);
238
239 hr = WcaGetRecordFormattedString(hRec, vagqName, &pwzData);
240 ExitOnFailure(hr, "failed to get Wix4Group.Name");
241 hr = ::StringCchCopyW(psg->wzName, countof(psg->wzName), pwzData);
242 ExitOnFailure(hr, "failed to copy group name: %ls", pwzData);
243
244 hr = WcaGetRecordFormattedString(hRec, vagqDomain, &pwzData);
245 ExitOnFailure(hr, "failed to get Wix4Group.Domain");
246 hr = ::StringCchCopyW(psg->wzDomain, countof(psg->wzDomain), pwzData);
247 ExitOnFailure(hr, "failed to copy group domain: %ls", pwzData);
248 hr = WcaGetRecordFormattedString(hRec, vagqComment, &pwzData);
249 ExitOnFailure(hr, "failed to get Wix6Group.Comment");
250 hr = ::StringCchCopyW(psg->wzComment, countof(psg->wzComment), pwzData);
251 ExitOnFailure(hr, "failed to copy group comment: %ls", pwzData);
252
253 hr = WcaGetRecordInteger(hRec, vagqAttributes, &psg->iAttributes);
254 ExitOnFailure(hr, "failed to get Wix6Group.Attributes");
255
256 // Check if this group is to be added to any other groups
257 if (fGroupGroupExists)
258 {
259 hGroupRec = ::MsiCreateRecord(1);
260 hr = WcaSetRecordString(hGroupRec, 1, psg->wzKey);
261 ExitOnFailure(hr, "Failed to create group record for querying Wix6GroupGroup table");
262
263 hr = WcaOpenExecuteView(vcsGroupGroupQuery, &hGroupGroupView);
264 ExitOnFailure(hr, "Failed to open view on Wix6GroupGroup table for group %ls", psg->wzKey);/*
265 hr = WcaExecuteView(hGroupGroupView, hGroupRec);
266 ExitOnFailure(hr, "Failed to execute view on Wix6GroupGroup table for group: %ls", psg->wzKey);*/
267
268 while (S_OK == (hr = WcaFetchRecord(hGroupGroupView, &hRec)))
269 {
270 hr = WcaGetRecordString(hRec, vggqParent, &pwzData);
271 ExitOnFailure(hr, "failed to get Wix6GroupGroup.Parent");
272
273 hr = AddGroupToList(&(psg->psgGroups));
274 ExitOnFailure(hr, "failed to add group to list");
275
276 hr = ScaGetGroup(pwzData, psg->psgGroups);
277 ExitOnFailure(hr, "failed to get information for group: %ls", pwzData);
278 }
279
280 if (E_NOMOREITEMS == hr)
281 {
282 hr = S_OK;
283 }
284 ExitOnFailure(hr, "failed to enumerate selected rows from Wix4UserGroup table");
285 }
286 }
287 }
288
289 if (E_NOMOREITEMS == hr)
290 {
291 hr = S_OK;
292 }
293 ExitOnFailure(hr, "failed to enumerate selected rows from Wix4Group table");
294
295LExit:
296 ReleaseStr(pwzData);
297
298 return hr;
299}
300
301/* ****************************************************************
302ScaGroupExecute - Schedules group account creation or removal based on
303component state.
304
305******************************************************************/
306HRESULT ScaGroupExecute(
307 __in SCA_GROUP *psgList
308 )
309{
310 HRESULT hr = S_OK;
311 DWORD er = 0;
312
313 LPWSTR pwzBaseScriptKey = NULL;
314 DWORD cScriptKey = 0;
315
316 LOCALGROUP_INFO_0 *pGroupInfo = NULL;
317 LPWSTR pwzScriptKey = NULL;
318 LPWSTR pwzActionData = NULL;
319 LPWSTR pwzRollbackData = NULL;
320 LPWSTR pwzServerName = NULL;
321
322 // Get the base script key for this CustomAction.
323 hr = WcaCaScriptCreateKey(&pwzBaseScriptKey);
324 ExitOnFailure(hr, "Failed to get encoding key.");
325
326 // Loop through all the users to be configured.
327 for (SCA_GROUP *psg = psgList; psg; psg = psg->psgNext)
328 {
329 GROUP_EXISTS geGroupExists = GROUP_EXISTS_INDETERMINATE;
330
331 // Always put the Group Name, Domain, and Comment on the front of the CustomAction data.
332 // The attributes will be added when we have finished adjusting them. Sometimes we'll
333 // add more data.
334 Assert(psg->wzName);
335 hr = WcaWriteStringToCaData(psg->wzName, &pwzActionData);
336 ExitOnFailure(hr, "Failed to add group name to custom action data: %ls", psg->wzName);
337 hr = WcaWriteStringToCaData(psg->wzDomain, &pwzActionData);
338 ExitOnFailure(hr, "Failed to add group domain to custom action data: %ls", psg->wzDomain);
339 hr = WcaWriteStringToCaData(psg->wzComment, &pwzActionData);
340 ExitOnFailure(hr, "Failed to add group comment to custom action data: %ls", psg->wzComment);
341
342 // Check to see if the group already exists since we have to be very careful when adding
343 // and removing groups. Note: MSDN says that it is safe to call these APIs from any
344 // user, so we should be safe calling it during immediate mode.
345
346 LPCWSTR wzDomain = psg->wzDomain;
347 hr = GetDomainServerName(wzDomain, &pwzServerName);
348
349 er = ::NetLocalGroupGetInfo(pwzServerName, psg->wzName, 0, reinterpret_cast<LPBYTE*>(&pGroupInfo));
350 if (NERR_Success == er)
351 {
352 geGroupExists = GROUP_EXISTS_YES;
353 }
354 else if (NERR_GroupNotFound == er)
355 {
356 geGroupExists = GROUP_EXISTS_NO;
357 }
358 else
359 {
360 geGroupExists = GROUP_EXISTS_INDETERMINATE;
361 hr = HRESULT_FROM_WIN32(er);
362 WcaLog(LOGMSG_VERBOSE, "Failed to check existence of domain: %ls, group: %ls (error code 0x%x) - continuing", wzDomain, psg->wzName, hr);
363 hr = S_OK;
364 er = ERROR_SUCCESS;
365 }
366
367 if (WcaIsInstalling(psg->isInstalled, psg->isAction))
368 {
369 // If the group exists, check to see if we are supposed to fail if the group exists before
370 // the install.
371 if (GROUP_EXISTS_YES == geGroupExists)
372 {
373 // Re-installs will always fail if we don't remove the check for "fail if exists".
374 if (WcaIsReInstalling(psg->isInstalled, psg->isAction))
375 {
376 psg->iAttributes &= ~SCAG_FAIL_IF_EXISTS;
377
378 // If install would create the group, re-install should be able to update the group.
379 if (!(psg->iAttributes & SCAG_DONT_CREATE_GROUP))
380 {
381 psg->iAttributes |= SCAG_UPDATE_IF_EXISTS;
382 }
383 }
384
385 if (SCAG_FAIL_IF_EXISTS & psg->iAttributes
386 && !(SCAG_UPDATE_IF_EXISTS & psg->iAttributes))
387 {
388 hr = HRESULT_FROM_WIN32(NERR_GroupExists);
389 MessageExitOnFailure(hr, msierrGRPFailedGroupCreateExists, "Failed to create group: %ls because group already exists.", psg->wzName);
390 }
391 }
392
393 hr = WcaWriteIntegerToCaData(psg->iAttributes, &pwzActionData);
394 ExitOnFailure(hr, "failed to add group attributes to custom action data for group: %ls", psg->wzKey);
395
396 // Rollback only if the group already exists, we couldn't determine if the group exists, or we are going to create the group
397 if ((GROUP_EXISTS_YES == geGroupExists)
398 || (GROUP_EXISTS_INDETERMINATE == geGroupExists)
399 || !(psg->iAttributes & SCAG_DONT_CREATE_GROUP))
400 {
401 ++cScriptKey;
402 hr = StrAllocFormatted(&pwzScriptKey, L"%ls%u", pwzBaseScriptKey, cScriptKey);
403 ExitOnFailure(hr, "Failed to create encoding key.");
404
405 // Write the script key to CustomActionData for install and rollback so information can be passed to rollback.
406 hr = WcaWriteStringToCaData(pwzScriptKey, &pwzActionData);
407 ExitOnFailure(hr, "Failed to add encoding key to custom action data.");
408
409 hr = WcaWriteStringToCaData(pwzScriptKey, &pwzRollbackData);
410 ExitOnFailure(hr, "Failed to add encoding key to rollback custom action data.");
411
412 INT iRollbackUserAttributes = psg->iAttributes;
413
414 // If the user already exists, ensure this is accounted for in rollback
415 if (GROUP_EXISTS_YES == geGroupExists)
416 {
417 iRollbackUserAttributes |= SCAG_DONT_CREATE_GROUP;
418 }
419 else
420 {
421 iRollbackUserAttributes &= ~SCAG_DONT_CREATE_GROUP;
422 }
423
424 hr = WcaWriteStringToCaData(psg->wzName, &pwzRollbackData);
425 ExitOnFailure(hr, "Failed to add group name to rollback custom action data: %ls", psg->wzName);
426 hr = WcaWriteStringToCaData(psg->wzDomain, &pwzRollbackData);
427 ExitOnFailure(hr, "Failed to add group domain to rollback custom action data: %ls", psg->wzDomain);
428 hr = WcaWriteIntegerToCaData(iRollbackUserAttributes, &pwzRollbackData);
429 ExitOnFailure(hr, "failed to add group attributes to rollback custom action data for group: %ls", psg->wzKey);
430
431 hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"CreateGroupRollback"), pwzRollbackData, COST_GROUP_DELETE);
432 ExitOnFailure(hr, "failed to schedule CreateGroupRollback");
433 }
434 else
435 {
436 // Write empty script key to CustomActionData since there is no rollback.
437 hr = WcaWriteStringToCaData(L"", &pwzActionData);
438 ExitOnFailure(hr, "Failed to add empty encoding key to custom action data.");
439 }
440
441 //
442 // Schedule the creation now.
443 //
444 hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"CreateGroup"), pwzActionData, COST_GROUP_ADD);
445 ExitOnFailure(hr, "failed to schedule CreateGroup");
446 }
447 else if (((GROUP_EXISTS_YES == geGroupExists)
448 || (GROUP_EXISTS_INDETERMINATE == geGroupExists))
449 && WcaIsUninstalling(psg->isInstalled, psg->isAction)
450 && !(psg->iAttributes & SCAG_DONT_REMOVE_ON_UNINSTALL))
451 {
452 hr = WcaWriteIntegerToCaData(psg->iAttributes, &pwzActionData);
453 ExitOnFailure(hr, "failed to add group attributes to custom action data for group: %ls", psg->wzKey);
454
455 // Schedule the removal because the group exists and we don't have any flags set
456 // that say not to remove the group on uninstall.
457 //
458 // Note: We can't rollback the removal of a group which is why RemoveGroup is a commit
459 // CustomAction.
460 hr = WcaDoDeferredAction(CUSTOM_ACTION_DECORATION(L"RemoveGroup"), pwzActionData, COST_GROUP_DELETE);
461 ExitOnFailure(hr, "failed to schedule RemoveGroup");
462 }
463
464 ReleaseNullStr(pwzScriptKey);
465 ReleaseNullStr(pwzActionData);
466 ReleaseNullStr(pwzRollbackData);
467 ReleaseNullStr(pwzServerName);
468 if (pGroupInfo)
469 {
470 ::NetApiBufferFree(static_cast<LPVOID>(pGroupInfo));
471 pGroupInfo = NULL;
472 }
473 }
474
475LExit:
476 ReleaseStr(pwzBaseScriptKey);
477 ReleaseStr(pwzScriptKey);
478 ReleaseStr(pwzActionData);
479 ReleaseStr(pwzRollbackData);
480 ReleaseStr(pwzServerName);
481 if (pGroupInfo)
482 {
483 ::NetApiBufferFree(static_cast<LPVOID>(pGroupInfo));
484 pGroupInfo = NULL;
485 }
486
487 return hr;
488}
489
490static HRESULT AddGroupToList(
491 __inout SCA_GROUP** ppsgList
492 )
493{
494 HRESULT hr = S_OK;
495 SCA_GROUP* psg = static_cast<SCA_GROUP*>(MemAlloc(sizeof(SCA_GROUP), TRUE));
496 ExitOnNull(psg, hr, E_OUTOFMEMORY, "failed to allocate memory for new group list element");
497
498 psg->psgNext = *ppsgList;
499 *ppsgList = psg;
500
501LExit:
502 return hr;
503}
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 @@
1#pragma once
2// 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.
3
4enum GROUP_EXISTS
5{
6 GROUP_EXISTS_YES,
7 GROUP_EXISTS_NO,
8 GROUP_EXISTS_INDETERMINATE
9};
10
11// structs
12struct SCA_GROUP
13{
14 WCHAR wzKey[MAX_DARWIN_KEY + 1];
15 WCHAR wzComponent[MAX_DARWIN_KEY + 1];
16 INSTALLSTATE isInstalled;
17 INSTALLSTATE isAction;
18
19 WCHAR wzDomain[MAX_DARWIN_COLUMN + 1];
20 WCHAR wzName[MAX_DARWIN_COLUMN + 1];
21 WCHAR wzComment[MAX_DARWIN_COLUMN + 1];
22 INT iAttributes;
23
24 SCA_GROUP* psgGroups;
25
26 SCA_GROUP *psgNext;
27};
28
29// prototypes
30HRESULT __stdcall ScaGetGroup(
31 __in LPCWSTR wzGroup,
32 __out SCA_GROUP* pscag
33 );
34HRESULT __stdcall ScaGetGroupDeferred(
35 __in LPCWSTR wzGroup,
36 __in WCA_WRAPQUERY_HANDLE hGroupQuery,
37 __out SCA_GROUP* pscag
38 );
39void ScaGroupFreeList(
40 __in SCA_GROUP* psgList
41 );
42HRESULT ScaGroupRead(
43 __inout SCA_GROUP** ppsgList
44 );
45HRESULT ScaGroupExecute(
46 __in SCA_GROUP*psgList
47 );
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "scanet.h"
5
6
7HRESULT GetDomainServerName(LPCWSTR pwzDomain, LPWSTR* ppwzServerName, ULONG flags)
8{
9 DWORD er = ERROR_SUCCESS;
10 PDOMAIN_CONTROLLER_INFOW pDomainControllerInfo = NULL;
11 HRESULT hr = S_OK;
12
13 if (pwzDomain && *pwzDomain)
14 {
15 er = ::DsGetDcNameW(NULL, pwzDomain, NULL, NULL, flags, &pDomainControllerInfo);
16 if (RPC_S_SERVER_UNAVAILABLE == er)
17 {
18 // MSDN says, if we get the above error code, try again with the "DS_FORCE_REDISCOVERY" flag
19 er = ::DsGetDcNameW(NULL, pwzDomain, NULL, NULL, flags | DS_FORCE_REDISCOVERY, &pDomainControllerInfo);
20 }
21
22 if (ERROR_SUCCESS == er && pDomainControllerInfo->DomainControllerName)
23 {
24 // Skip the \\ prefix if present.
25 if ('\\' == *pDomainControllerInfo->DomainControllerName && '\\' == *pDomainControllerInfo->DomainControllerName + 1)
26 {
27 hr = StrAllocString(ppwzServerName, pDomainControllerInfo->DomainControllerName + 2, 0);
28 ExitOnFailure(hr, "failed to allocate memory for string");
29 }
30 else
31 {
32 hr = StrAllocString(ppwzServerName, pDomainControllerInfo->DomainControllerName, 0);
33 ExitOnFailure(hr, "failed to allocate memory for string");
34 }
35 }
36 else
37 {
38 StrAllocString(ppwzServerName, pwzDomain, 0);
39 hr = HRESULT_FROM_WIN32(er);
40 ExitOnFailure(hr, "failed to contact domain %ls", pwzDomain);
41 }
42 }
43
44LExit:
45 if (pDomainControllerInfo)
46 {
47 ::NetApiBufferFree((LPVOID)pDomainControllerInfo);
48 }
49 return hr;
50}
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 @@
1#pragma once
2// 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.
3
4HRESULT 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:
124 124
125 er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; 125 er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
126 return WcaFinalize(er); 126 return WcaFinalize(er);
127} \ No newline at end of file 127}
128
129/********************************************************************
130ConfigureGroups - CUSTOM ACTION ENTRY POINT for installing groups
131
132********************************************************************/
133extern "C" UINT __stdcall ConfigureGroups(
134 __in MSIHANDLE hInstall
135)
136{
137 //AssertSz(0, "Debug ConfigureGroups");
138
139 HRESULT hr = S_OK;
140 UINT er = ERROR_SUCCESS;
141
142 BOOL fInitializedCom = FALSE;
143 SCA_GROUP* psgList = NULL;
144
145 // initialize
146 hr = WcaInitialize(hInstall, "ConfigureGroups");
147 ExitOnFailure(hr, "Failed to initialize");
148
149 hr = ::CoInitialize(NULL);
150 ExitOnFailure(hr, "failed to initialize COM");
151 fInitializedCom = TRUE;
152
153 hr = ScaGroupRead(&psgList);
154 ExitOnFailure(hr, "failed to read Wix4Group,Wix6Group table(s)");
155
156 hr = ScaGroupExecute(psgList);
157 ExitOnFailure(hr, "failed to add/remove Group actions");
158
159LExit:
160 if (psgList)
161 {
162 ScaGroupFreeList(psgList);
163 }
164
165 if (fInitializedCom)
166 {
167 ::CoUninitialize();
168 }
169
170 er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
171 return WcaFinalize(er);
172}
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 @@
5LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Comment`, `Password` FROM `Wix4User` WHERE `User`=?"; 5LPCWSTR vcsUserQuery = L"SELECT `User`, `Component_`, `Name`, `Domain`, `Comment`, `Password` FROM `Wix4User` WHERE `User`=?";
6enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqComment, vuqPassword }; 6enum eUserQuery { vuqUser = 1, vuqComponent, vuqName, vuqDomain, vuqComment, vuqPassword };
7 7
8LPCWSTR vcsGroupQuery = L"SELECT `Group`, `Component_`, `Name`, `Domain` FROM `Wix4Group` WHERE `Group`=?";
9enum eGroupQuery { vgqGroup = 1, vgqComponent, vgqName, vgqDomain };
10
11LPCWSTR vcsUserGroupQuery = L"SELECT `User_`, `Group_` FROM `Wix4UserGroup` WHERE `User_`=?"; 8LPCWSTR vcsUserGroupQuery = L"SELECT `User_`, `Group_` FROM `Wix4UserGroup` WHERE `User_`=?";
12enum eUserGroupQuery { vugqUser = 1, vugqGroup }; 9enum eUserGroupQuery { vugqUser = 1, vugqGroup };
13 10
@@ -185,71 +182,6 @@ LExit:
185 return hr; 182 return hr;
186} 183}
187 184
188
189HRESULT __stdcall ScaGetGroup(
190 __in LPCWSTR wzGroup,
191 __out SCA_GROUP* pscag
192 )
193{
194 if (!wzGroup || !pscag)
195 {
196 return E_INVALIDARG;
197 }
198
199 HRESULT hr = S_OK;
200 PMSIHANDLE hView, hRec;
201
202 LPWSTR pwzData = NULL;
203
204 hRec = ::MsiCreateRecord(1);
205 hr = WcaSetRecordString(hRec, 1, wzGroup);
206 ExitOnFailure(hr, "Failed to look up Group");
207
208 hr = WcaOpenView(vcsGroupQuery, &hView);
209 ExitOnFailure(hr, "Failed to open view on Wix4Group table");
210 hr = WcaExecuteView(hView, hRec);
211 ExitOnFailure(hr, "Failed to execute view on Wix4Group table");
212
213 hr = WcaFetchSingleRecord(hView, &hRec);
214 if (S_OK == hr)
215 {
216 hr = WcaGetRecordString(hRec, vgqGroup, &pwzData);
217 ExitOnFailure(hr, "Failed to get Wix4Group.Group.");
218 hr = ::StringCchCopyW(pscag->wzKey, countof(pscag->wzKey), pwzData);
219 ExitOnFailure(hr, "Failed to copy Wix4Group.Group.");
220
221 hr = WcaGetRecordString(hRec, vgqComponent, &pwzData);
222 ExitOnFailure(hr, "Failed to get Wix4Group.Component_");
223 hr = ::StringCchCopyW(pscag->wzComponent, countof(pscag->wzComponent), pwzData);
224 ExitOnFailure(hr, "Failed to copy Wix4Group.Component_.");
225
226 hr = WcaGetRecordFormattedString(hRec, vgqName, &pwzData);
227 ExitOnFailure(hr, "Failed to get Wix4Group.Name");
228 hr = ::StringCchCopyW(pscag->wzName, countof(pscag->wzName), pwzData);
229 ExitOnFailure(hr, "Failed to copy Wix4Group.Name.");
230
231 hr = WcaGetRecordFormattedString(hRec, vgqDomain, &pwzData);
232 ExitOnFailure(hr, "Failed to get Wix4Group.Domain");
233 hr = ::StringCchCopyW(pscag->wzDomain, countof(pscag->wzDomain), pwzData);
234 ExitOnFailure(hr, "Failed to copy Wix4Group.Domain.");
235 }
236 else if (E_NOMOREITEMS == hr)
237 {
238 WcaLog(LOGMSG_STANDARD, "Error: Cannot locate Wix4Group.Group='%ls'", wzGroup);
239 hr = E_FAIL;
240 }
241 else
242 {
243 ExitOnFailure(hr, "Error or found multiple matching Wix4Group rows");
244 }
245
246LExit:
247 ReleaseStr(pwzData);
248
249 return hr;
250}
251
252
253void ScaUserFreeList( 185void ScaUserFreeList(
254 __in SCA_USER* psuList 186 __in SCA_USER* psuList
255 ) 187 )
@@ -266,21 +198,6 @@ void ScaUserFreeList(
266} 198}
267 199
268 200
269void ScaGroupFreeList(
270 __in SCA_GROUP* psgList
271 )
272{
273 SCA_GROUP* psgDelete = psgList;
274 while (psgList)
275 {
276 psgDelete = psgList;
277 psgList = psgList->psgNext;
278
279 MemFree(psgDelete);
280 }
281}
282
283
284HRESULT ScaUserRead( 201HRESULT ScaUserRead(
285 __out SCA_USER** ppsuList 202 __out SCA_USER** ppsuList
286 ) 203 )
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 @@
1#pragma once 1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. 2// 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.
3 3#include "scagroup.h"
4 4
5enum USER_EXISTS 5enum USER_EXISTS
6{ 6{
@@ -9,17 +9,6 @@ enum USER_EXISTS
9 USER_EXISTS_INDETERMINATE 9 USER_EXISTS_INDETERMINATE
10}; 10};
11 11
12// structs
13struct SCA_GROUP
14{
15 WCHAR wzKey[MAX_DARWIN_KEY + 1];
16 WCHAR wzComponent[MAX_DARWIN_KEY + 1];
17
18 WCHAR wzDomain[MAX_DARWIN_COLUMN + 1];
19 WCHAR wzName[MAX_DARWIN_COLUMN + 1];
20
21 SCA_GROUP *psgNext;
22};
23 12
24struct SCA_USER 13struct SCA_USER
25{ 14{
@@ -50,16 +39,9 @@ HRESULT __stdcall ScaGetUserDeferred(
50 __in WCA_WRAPQUERY_HANDLE hUserQuery, 39 __in WCA_WRAPQUERY_HANDLE hUserQuery,
51 __out SCA_USER* pscau 40 __out SCA_USER* pscau
52 ); 41 );
53HRESULT __stdcall ScaGetGroup(
54 __in LPCWSTR wzGroup,
55 __out SCA_GROUP* pscag
56 );
57void ScaUserFreeList( 42void ScaUserFreeList(
58 __in SCA_USER* psuList 43 __in SCA_USER* psuList
59 ); 44 );
60void ScaGroupFreeList(
61 __in SCA_GROUP* psgList
62 );
63HRESULT ScaUserRead( 45HRESULT ScaUserRead(
64 __inout SCA_USER** ppsuList 46 __inout SCA_USER** ppsuList
65 ); 47 );
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
43 UnregisterPerfmon 43 UnregisterPerfmon
44 CreateSmb 44 CreateSmb
45 DropSmb 45 DropSmb
46 CreateGroup
47 CreateGroupRollback
48 RemoveGroup
46 CreateUser 49 CreateUser
47 CreateUserRollback 50 CreateUserRollback
48 RemoveUser 51 RemoveUser
@@ -51,6 +54,7 @@ EXPORTS
51 ConfigurePerfmonUninstall 54 ConfigurePerfmonUninstall
52 ConfigureSmbInstall 55 ConfigureSmbInstall
53 ConfigureSmbUninstall 56 ConfigureSmbUninstall
57 ConfigureGroups
54 ConfigureUsers 58 ConfigureUsers
55 InstallPerfCounterData 59 InstallPerfCounterData
56 UninstallPerfCounterData 60 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 @@
61 <ClCompile Include="RemoveRegistryKeysEx.cpp" /> 61 <ClCompile Include="RemoveRegistryKeysEx.cpp" />
62 <ClCompile Include="RestartManager.cpp" /> 62 <ClCompile Include="RestartManager.cpp" />
63 <ClCompile Include="scaexec.cpp" /> 63 <ClCompile Include="scaexec.cpp" />
64 <ClCompile Include="scagroup.cpp" />
64 <ClCompile Include="scamanifest.cpp" /> 65 <ClCompile Include="scamanifest.cpp" />
66 <ClCompile Include="scanet.cpp" />
65 <ClCompile Include="scaperf.cpp" /> 67 <ClCompile Include="scaperf.cpp" />
66 <ClCompile Include="scaperfexec.cpp" /> 68 <ClCompile Include="scaperfexec.cpp" />
67 <ClCompile Include="scasched.cpp" /> 69 <ClCompile Include="scasched.cpp" />
@@ -84,6 +86,8 @@
84 <ClInclude Include="precomp.h" /> 86 <ClInclude Include="precomp.h" />
85 <ClInclude Include="sca.h" /> 87 <ClInclude Include="sca.h" />
86 <ClInclude Include="scacost.h" /> 88 <ClInclude Include="scacost.h" />
89 <ClInclude Include="scagroup.h" />
90 <ClInclude Include="scanet.h" />
87 <ClInclude Include="scasmb.h" /> 91 <ClInclude Include="scasmb.h" />
88 <ClInclude Include="scasmbexec.h" /> 92 <ClInclude Include="scasmbexec.h" />
89 <ClInclude Include="scauser.h" /> 93 <ClInclude Include="scauser.h" />
diff --git a/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateGroup/Package.wxs b/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateGroup/Package.wxs
new file mode 100644
index 00000000..fdbbb9cc
--- /dev/null
+++ b/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateGroup/Package.wxs
@@ -0,0 +1,15 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="CreateGroup" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
4
5 <Feature Id="ProductFeature" Title="MsiPackage">
6 <ComponentGroupRef Id="ProductComponents" />
7 </Feature>
8 </Package>
9
10 <Fragment>
11 <StandardDirectory Id="ProgramFilesFolder">
12 <Directory Id="INSTALLFOLDER" Name="CreateGroup" />
13 </StandardDirectory>
14 </Fragment>
15</Wix>
diff --git a/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateGroup/PackageComponents.wxs b/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateGroup/PackageComponents.wxs
new file mode 100644
index 00000000..ce9ab418
--- /dev/null
+++ b/src/ext/Util/test/WixToolsetTest.Util/TestData/CreateGroup/PackageComponents.wxs
@@ -0,0 +1,80 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentRef Id="Component1" />
6 </ComponentGroup>
7 </Fragment>
8 <Fragment>
9 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
10 <util:Group Id="TEST_GROUP00" Name="testName00" Comment="Test Comment 1" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="no" />
11 <util:Group Id="TEST_GROUP01" Name="testName01" Comment="Test Comment 1" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="no" />
12 <util:Group Id="TEST_GROUP02" Name="testName02" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="no"/>
13 <util:Group Id="TEST_GROUP03" Name="testName03" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="no" />
14 <util:Group Id="TEST_GROUP04" Name="testName04" Comment="Test Comment 1" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="yes" Vital="yes" RemoveComment="no" />
15 <util:Group Id="TEST_GROUP05" Name="testName05" Comment="Test Comment 1" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="yes" Vital="yes" RemoveComment="no" />
16 <util:Group Id="TEST_GROUP06" Name="testName06" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="yes" Vital="yes" RemoveComment="no" />
17 <util:Group Id="TEST_GROUP07" Name="testName07" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="yes" Vital="yes" RemoveComment="no" />
18 <util:Group Id="TEST_GROUP08" Name="testName08" Comment="Test Comment 1" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="no" Vital="yes" RemoveComment="no" />
19 <util:Group Id="TEST_GROUP09" Name="testName09" Comment="Test Comment 1" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="no" Vital="yes" RemoveComment="no" />
20 <util:Group Id="TEST_GROUP10" Name="testName10" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="no" Vital="yes" RemoveComment="no" />
21 <util:Group Id="TEST_GROUP11" Name="testName11" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="no" Vital="yes" RemoveComment="no" />
22 <util:Group Id="TEST_GROUP12" Name="testName12" Comment="Test Comment 1" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="no" Vital="yes" RemoveComment="no" />
23 <util:Group Id="TEST_GROUP13" Name="testName13" Comment="Test Comment 1" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="no" Vital="yes" RemoveComment="no" />
24 <util:Group Id="TEST_GROUP14" Name="testName14" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="no" Vital="yes" RemoveComment="no" />
25 <util:Group Id="TEST_GROUP15" Name="testName15" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="no" Vital="yes" RemoveComment="no" />
26 <util:Group Id="TEST_GROUP16" Name="testName16" Comment="Test Comment 1" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="no" RemoveComment="no" />
27 <util:Group Id="TEST_GROUP17" Name="testName17" Comment="Test Comment 1" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="no" RemoveComment="no" />
28 <util:Group Id="TEST_GROUP18" Name="testName18" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="no" RemoveComment="no" />
29 <util:Group Id="TEST_GROUP19" Name="testName19" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="no" RemoveComment="no" />
30 <util:Group Id="TEST_GROUP20" Name="testName20" Comment="Test Comment 1" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="yes" Vital="no" RemoveComment="no" />
31 <util:Group Id="TEST_GROUP21" Name="testName21" Comment="Test Comment 1" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="yes" Vital="no" RemoveComment="no" />
32 <util:Group Id="TEST_GROUP22" Name="testName22" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="yes" Vital="no" RemoveComment="no" />
33 <util:Group Id="TEST_GROUP23" Name="testName23" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="yes" Vital="no" RemoveComment="no" />
34 <util:Group Id="TEST_GROUP24" Name="testName24" Comment="Test Comment 1" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="no" Vital="no" RemoveComment="no" />
35 <util:Group Id="TEST_GROUP25" Name="testName25" Comment="Test Comment 1" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="no" Vital="no" RemoveComment="no" />
36 <util:Group Id="TEST_GROUP26" Name="testName26" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="no" Vital="no" RemoveComment="no" />
37 <util:Group Id="TEST_GROUP27" Name="testName27" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="no" Vital="no" RemoveComment="no" />
38 <util:Group Id="TEST_GROUP28" Name="testName28" Comment="Test Comment 1" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="no" Vital="no" RemoveComment="no" />
39 <util:Group Id="TEST_GROUP29" Name="testName29" Comment="Test Comment 1" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="no" Vital="no" RemoveComment="no" />
40 <util:Group Id="TEST_GROUP30" Name="testName30" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="no" Vital="no" RemoveComment="no" />
41 <util:Group Id="TEST_GROUP31" Name="testName31" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="no" Vital="no" RemoveComment="no" />
42 <util:Group Id="TEST_GROUP32" Name="testName32" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="yes" />
43 <util:Group Id="TEST_GROUP33" Name="testName33" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="yes" />
44 <util:Group Id="TEST_GROUP34" Name="testName34" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="yes" />
45 <util:Group Id="TEST_GROUP35" Name="testName35" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="yes" />
46 <util:Group Id="TEST_GROUP36" Name="testName36" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="yes" Vital="yes" RemoveComment="yes" />
47 <util:Group Id="TEST_GROUP37" Name="testName37" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="yes" Vital="yes" RemoveComment="yes" />
48 <util:Group Id="TEST_GROUP38" Name="testName38" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="yes" Vital="yes" RemoveComment="yes" />
49 <util:Group Id="TEST_GROUP39" Name="testName39" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="yes" Vital="yes" RemoveComment="yes" />
50 <util:Group Id="TEST_GROUP40" Name="testName40" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="no" Vital="yes" RemoveComment="yes" />
51 <util:Group Id="TEST_GROUP41" Name="testName41" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="no" Vital="yes" RemoveComment="yes" />
52 <util:Group Id="TEST_GROUP42" Name="testName42" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="no" Vital="yes" RemoveComment="yes" />
53 <util:Group Id="TEST_GROUP43" Name="testName43" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="no" Vital="yes" RemoveComment="yes" />
54 <util:Group Id="TEST_GROUP44" Name="testName44" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="no" Vital="yes" RemoveComment="yes" />
55 <util:Group Id="TEST_GROUP45" Name="testName45" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="no" Vital="yes" RemoveComment="yes" />
56 <util:Group Id="TEST_GROUP46" Name="testName46" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="no" Vital="yes" RemoveComment="yes" />
57 <util:Group Id="TEST_GROUP47" Name="testName47" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="no" Vital="yes" RemoveComment="yes" />
58 <util:Group Id="TEST_GROUP48" Name="testName48" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="no" RemoveComment="yes" />
59 <util:Group Id="TEST_GROUP49" Name="testName49" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="no" RemoveComment="yes" />
60 <util:Group Id="TEST_GROUP50" Name="testName50" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="no" RemoveComment="yes" />
61 <util:Group Id="TEST_GROUP51" Name="testName51" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="no" RemoveComment="yes" />
62 <util:Group Id="TEST_GROUP52" Name="testName52" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="yes" Vital="no" RemoveComment="yes" />
63 <util:Group Id="TEST_GROUP53" Name="testName53" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="yes" Vital="no" RemoveComment="yes" />
64 <util:Group Id="TEST_GROUP54" Name="testName54" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="yes" Vital="no" RemoveComment="yes" />
65 <util:Group Id="TEST_GROUP55" Name="testName55" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="yes" Vital="no" RemoveComment="yes" />
66 <util:Group Id="TEST_GROUP56" Name="testName56" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="no" Vital="no" RemoveComment="yes" />
67 <util:Group Id="TEST_GROUP57" Name="testName57" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="no" Vital="no" RemoveComment="yes" />
68 <util:Group Id="TEST_GROUP58" Name="testName58" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="no" Vital="no" RemoveComment="yes" />
69 <util:Group Id="TEST_GROUP59" Name="testName59" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="no" Vital="no" RemoveComment="yes" />
70 <util:Group Id="TEST_GROUP60" Name="testName60" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="no" Vital="no" RemoveComment="yes" />
71 <util:Group Id="TEST_GROUP61" Name="testName61" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="no" CreateGroup="no" Vital="no" RemoveComment="yes" />
72 <util:Group Id="TEST_GROUP62" Name="testName62" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="no" Vital="no" RemoveComment="yes" />
73 <util:Group Id="TEST_GROUP63" Name="testName63" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="no" CreateGroup="no" Vital="no" RemoveComment="yes" />
74 <util:Group Id="TEST_GROUP64" Name="testName64" Comment="Test Comment 1" FailIfExists="no" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="no" Domain="testDomain00" />
75 <util:Group Id="TEST_GROUP65" Name="testName65" Comment="Test Comment 1" FailIfExists="yes" UpdateIfExists="no" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="no" Domain="testDomain01" />
76 <util:Group Id="TEST_GROUP66" Name="testName66" FailIfExists="no" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="no" Domain="testDomain02" />
77 <util:Group Id="TEST_GROUP67" Name="testName67" FailIfExists="yes" UpdateIfExists="yes" RemoveOnUninstall="yes" CreateGroup="yes" Vital="yes" RemoveComment="no" Domain="testDomain03" />
78 </Component>
79 </Fragment>
80</Wix>
diff --git a/src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs b/src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs
index 0a93f3a4..d71dd824 100644
--- a/src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs
+++ b/src/ext/Util/test/WixToolsetTest.Util/UtilExtensionFixture.cs
@@ -395,6 +395,159 @@ namespace WixToolsetTest.Util
395 } 395 }
396 396
397 [Fact] 397 [Fact]
398 public void CanCreateUserGroupWithComment()
399 {
400 var folder = TestData.Get(@"TestData\CreateGroup");
401 var build = new Builder(folder, typeof(UtilExtensionFactory), new[] { folder });
402
403 var results = build.BuildAndQuery(BuildX64, "Binary", "CustomAction", "Wix4Group", "Wix6Group");
404 WixAssert.CompareLineByLine(new[]
405 {
406 "Binary:Wix4UtilCA_X64\t[Binary data]",
407 "CustomAction:Wix4ConfigureGroups_X64\t1\tWix4UtilCA_X64\tConfigureGroups\t",
408 "CustomAction:Wix4CreateGroup_X64\t11265\tWix4UtilCA_X64\tCreateGroup\t",
409 "CustomAction:Wix4CreateGroupRollback_X64\t11521\tWix4UtilCA_X64\tCreateGroupRollback\t",
410 "CustomAction:Wix4RemoveGroup_X64\t11841\tWix4UtilCA_X64\tRemoveGroup\t",
411 "Wix4Group:TEST_GROUP00\tComponent1\ttestName00\t",
412 "Wix4Group:TEST_GROUP01\tComponent1\ttestName01\t",
413 "Wix4Group:TEST_GROUP02\tComponent1\ttestName02\t",
414 "Wix4Group:TEST_GROUP03\tComponent1\ttestName03\t",
415 "Wix4Group:TEST_GROUP04\tComponent1\ttestName04\t",
416 "Wix4Group:TEST_GROUP05\tComponent1\ttestName05\t",
417 "Wix4Group:TEST_GROUP06\tComponent1\ttestName06\t",
418 "Wix4Group:TEST_GROUP07\tComponent1\ttestName07\t",
419 "Wix4Group:TEST_GROUP08\tComponent1\ttestName08\t",
420 "Wix4Group:TEST_GROUP09\tComponent1\ttestName09\t",
421 "Wix4Group:TEST_GROUP10\tComponent1\ttestName10\t",
422 "Wix4Group:TEST_GROUP11\tComponent1\ttestName11\t",
423 "Wix4Group:TEST_GROUP12\tComponent1\ttestName12\t",
424 "Wix4Group:TEST_GROUP13\tComponent1\ttestName13\t",
425 "Wix4Group:TEST_GROUP14\tComponent1\ttestName14\t",
426 "Wix4Group:TEST_GROUP15\tComponent1\ttestName15\t",
427 "Wix4Group:TEST_GROUP16\tComponent1\ttestName16\t",
428 "Wix4Group:TEST_GROUP17\tComponent1\ttestName17\t",
429 "Wix4Group:TEST_GROUP18\tComponent1\ttestName18\t",
430 "Wix4Group:TEST_GROUP19\tComponent1\ttestName19\t",
431 "Wix4Group:TEST_GROUP20\tComponent1\ttestName20\t",
432 "Wix4Group:TEST_GROUP21\tComponent1\ttestName21\t",
433 "Wix4Group:TEST_GROUP22\tComponent1\ttestName22\t",
434 "Wix4Group:TEST_GROUP23\tComponent1\ttestName23\t",
435 "Wix4Group:TEST_GROUP24\tComponent1\ttestName24\t",
436 "Wix4Group:TEST_GROUP25\tComponent1\ttestName25\t",
437 "Wix4Group:TEST_GROUP26\tComponent1\ttestName26\t",
438 "Wix4Group:TEST_GROUP27\tComponent1\ttestName27\t",
439 "Wix4Group:TEST_GROUP28\tComponent1\ttestName28\t",
440 "Wix4Group:TEST_GROUP29\tComponent1\ttestName29\t",
441 "Wix4Group:TEST_GROUP30\tComponent1\ttestName30\t",
442 "Wix4Group:TEST_GROUP31\tComponent1\ttestName31\t",
443 "Wix4Group:TEST_GROUP32\tComponent1\ttestName32\t",
444 "Wix4Group:TEST_GROUP33\tComponent1\ttestName33\t",
445 "Wix4Group:TEST_GROUP34\tComponent1\ttestName34\t",
446 "Wix4Group:TEST_GROUP35\tComponent1\ttestName35\t",
447 "Wix4Group:TEST_GROUP36\tComponent1\ttestName36\t",
448 "Wix4Group:TEST_GROUP37\tComponent1\ttestName37\t",
449 "Wix4Group:TEST_GROUP38\tComponent1\ttestName38\t",
450 "Wix4Group:TEST_GROUP39\tComponent1\ttestName39\t",
451 "Wix4Group:TEST_GROUP40\tComponent1\ttestName40\t",
452 "Wix4Group:TEST_GROUP41\tComponent1\ttestName41\t",
453 "Wix4Group:TEST_GROUP42\tComponent1\ttestName42\t",
454 "Wix4Group:TEST_GROUP43\tComponent1\ttestName43\t",
455 "Wix4Group:TEST_GROUP44\tComponent1\ttestName44\t",
456 "Wix4Group:TEST_GROUP45\tComponent1\ttestName45\t",
457 "Wix4Group:TEST_GROUP46\tComponent1\ttestName46\t",
458 "Wix4Group:TEST_GROUP47\tComponent1\ttestName47\t",
459 "Wix4Group:TEST_GROUP48\tComponent1\ttestName48\t",
460 "Wix4Group:TEST_GROUP49\tComponent1\ttestName49\t",
461 "Wix4Group:TEST_GROUP50\tComponent1\ttestName50\t",
462 "Wix4Group:TEST_GROUP51\tComponent1\ttestName51\t",
463 "Wix4Group:TEST_GROUP52\tComponent1\ttestName52\t",
464 "Wix4Group:TEST_GROUP53\tComponent1\ttestName53\t",
465 "Wix4Group:TEST_GROUP54\tComponent1\ttestName54\t",
466 "Wix4Group:TEST_GROUP55\tComponent1\ttestName55\t",
467 "Wix4Group:TEST_GROUP56\tComponent1\ttestName56\t",
468 "Wix4Group:TEST_GROUP57\tComponent1\ttestName57\t",
469 "Wix4Group:TEST_GROUP58\tComponent1\ttestName58\t",
470 "Wix4Group:TEST_GROUP59\tComponent1\ttestName59\t",
471 "Wix4Group:TEST_GROUP60\tComponent1\ttestName60\t",
472 "Wix4Group:TEST_GROUP61\tComponent1\ttestName61\t",
473 "Wix4Group:TEST_GROUP62\tComponent1\ttestName62\t",
474 "Wix4Group:TEST_GROUP63\tComponent1\ttestName63\t",
475 "Wix4Group:TEST_GROUP64\tComponent1\ttestName64\ttestDomain00",
476 "Wix4Group:TEST_GROUP65\tComponent1\ttestName65\ttestDomain01",
477 "Wix4Group:TEST_GROUP66\tComponent1\ttestName66\ttestDomain02",
478 "Wix4Group:TEST_GROUP67\tComponent1\ttestName67\ttestDomain03",
479 "Wix6Group:TEST_GROUP00\tTest Comment 1\t0",
480 "Wix6Group:TEST_GROUP01\tTest Comment 1\t1",
481 "Wix6Group:TEST_GROUP02\t\t2",
482 "Wix6Group:TEST_GROUP03\t\t3",
483 "Wix6Group:TEST_GROUP04\tTest Comment 1\t4",
484 "Wix6Group:TEST_GROUP05\tTest Comment 1\t5",
485 "Wix6Group:TEST_GROUP06\t\t6",
486 "Wix6Group:TEST_GROUP07\t\t7",
487 "Wix6Group:TEST_GROUP08\tTest Comment 1\t8",
488 "Wix6Group:TEST_GROUP09\tTest Comment 1\t9",
489 "Wix6Group:TEST_GROUP10\t\t10",
490 "Wix6Group:TEST_GROUP11\t\t11",
491 "Wix6Group:TEST_GROUP12\tTest Comment 1\t12",
492 "Wix6Group:TEST_GROUP13\tTest Comment 1\t13",
493 "Wix6Group:TEST_GROUP14\t\t14",
494 "Wix6Group:TEST_GROUP15\t\t15",
495 "Wix6Group:TEST_GROUP16\tTest Comment 1\t16",
496 "Wix6Group:TEST_GROUP17\tTest Comment 1\t17",
497 "Wix6Group:TEST_GROUP18\t\t18",
498 "Wix6Group:TEST_GROUP19\t\t19",
499 "Wix6Group:TEST_GROUP20\tTest Comment 1\t20",
500 "Wix6Group:TEST_GROUP21\tTest Comment 1\t21",
501 "Wix6Group:TEST_GROUP22\t\t22",
502 "Wix6Group:TEST_GROUP23\t\t23",
503 "Wix6Group:TEST_GROUP24\tTest Comment 1\t24",
504 "Wix6Group:TEST_GROUP25\tTest Comment 1\t25",
505 "Wix6Group:TEST_GROUP26\t\t26",
506 "Wix6Group:TEST_GROUP27\t\t27",
507 "Wix6Group:TEST_GROUP28\tTest Comment 1\t28",
508 "Wix6Group:TEST_GROUP29\tTest Comment 1\t29",
509 "Wix6Group:TEST_GROUP30\t\t30",
510 "Wix6Group:TEST_GROUP31\t\t31",
511 "Wix6Group:TEST_GROUP32\t\t32",
512 "Wix6Group:TEST_GROUP33\t\t33",
513 "Wix6Group:TEST_GROUP34\t\t34",
514 "Wix6Group:TEST_GROUP35\t\t35",
515 "Wix6Group:TEST_GROUP36\t\t36",
516 "Wix6Group:TEST_GROUP37\t\t37",
517 "Wix6Group:TEST_GROUP38\t\t38",
518 "Wix6Group:TEST_GROUP39\t\t39",
519 "Wix6Group:TEST_GROUP40\t\t40",
520 "Wix6Group:TEST_GROUP41\t\t41",
521 "Wix6Group:TEST_GROUP42\t\t42",
522 "Wix6Group:TEST_GROUP43\t\t43",
523 "Wix6Group:TEST_GROUP44\t\t44",
524 "Wix6Group:TEST_GROUP45\t\t45",
525 "Wix6Group:TEST_GROUP46\t\t46",
526 "Wix6Group:TEST_GROUP47\t\t47",
527 "Wix6Group:TEST_GROUP48\t\t48",
528 "Wix6Group:TEST_GROUP49\t\t49",
529 "Wix6Group:TEST_GROUP50\t\t50",
530 "Wix6Group:TEST_GROUP51\t\t51",
531 "Wix6Group:TEST_GROUP52\t\t52",
532 "Wix6Group:TEST_GROUP53\t\t53",
533 "Wix6Group:TEST_GROUP54\t\t54",
534 "Wix6Group:TEST_GROUP55\t\t55",
535 "Wix6Group:TEST_GROUP56\t\t56",
536 "Wix6Group:TEST_GROUP57\t\t57",
537 "Wix6Group:TEST_GROUP58\t\t58",
538 "Wix6Group:TEST_GROUP59\t\t59",
539 "Wix6Group:TEST_GROUP60\t\t60",
540 "Wix6Group:TEST_GROUP61\t\t61",
541 "Wix6Group:TEST_GROUP62\t\t62",
542 "Wix6Group:TEST_GROUP63\t\t63",
543 "Wix6Group:TEST_GROUP64\tTest Comment 1\t0",
544 "Wix6Group:TEST_GROUP65\tTest Comment 1\t1",
545 "Wix6Group:TEST_GROUP66\t\t2",
546 "Wix6Group:TEST_GROUP67\t\t3",
547 }, results.OrderBy(s => s).ToArray());
548 }
549
550 [Fact]
398 public void CanCreateUserAccountWithComment() 551 public void CanCreateUserAccountWithComment()
399 { 552 {
400 var folder = TestData.Get(@"TestData\CreateUser"); 553 var folder = TestData.Get(@"TestData\CreateUser");
diff --git a/src/ext/Util/wixext/Symbols/GroupGroupSymbol.cs b/src/ext/Util/wixext/Symbols/GroupGroupSymbol.cs
new file mode 100644
index 00000000..fdd1ee76
--- /dev/null
+++ b/src/ext/Util/wixext/Symbols/GroupGroupSymbol.cs
@@ -0,0 +1,56 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Util
4{
5 using WixToolset.Data;
6 using WixToolset.Util.Symbols;
7
8 public static partial class UtilSymbolDefinitions
9 {
10 public static readonly IntermediateSymbolDefinition GroupGroup = new IntermediateSymbolDefinition(
11 UtilSymbolDefinitionType.GroupGroup.ToString(),
12 new[]
13 {
14 new IntermediateFieldDefinition(nameof(GroupGroupSymbol.SymbolFields.ParentGroupRef), IntermediateFieldType.String),
15 new IntermediateFieldDefinition(nameof(GroupGroupSymbol.SymbolFields.ChildGroupRef), IntermediateFieldType.String),
16 },
17 typeof(UserGroupSymbol));
18 }
19}
20
21namespace WixToolset.Util.Symbols
22{
23 using WixToolset.Data;
24
25 public class GroupGroupSymbol : IntermediateSymbol
26 {
27 public enum SymbolFields
28 {
29 ParentGroupRef,
30 ChildGroupRef,
31 }
32
33 public GroupGroupSymbol() : base(UtilSymbolDefinitions.GroupGroup, null, null)
34 {
35 }
36
37 public GroupGroupSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(UtilSymbolDefinitions.GroupGroup, sourceLineNumber, id)
38 {
39 }
40
41 public IntermediateField this[GroupGroupSymbol.SymbolFields index] => this.Fields[(int)index];
42
43 public string ParentGroupRef
44 {
45 get => this.Fields[(int)GroupGroupSymbol.SymbolFields.ParentGroupRef].AsString();
46 set => this.Set((int)GroupGroupSymbol.SymbolFields.ParentGroupRef, value);
47 }
48
49 public string ChildGroupRef
50 {
51 get => this.Fields[(int)GroupGroupSymbol.SymbolFields.ChildGroupRef].AsString();
52 set => this.Set((int)GroupGroupSymbol.SymbolFields.ChildGroupRef, value);
53 }
54
55 }
56}
diff --git a/src/ext/Util/wixext/Symbols/GroupSymbol.cs b/src/ext/Util/wixext/Symbols/GroupSymbol.cs
index b378db44..ef1dc33f 100644
--- a/src/ext/Util/wixext/Symbols/GroupSymbol.cs
+++ b/src/ext/Util/wixext/Symbols/GroupSymbol.cs
@@ -11,27 +11,38 @@ namespace WixToolset.Util
11 UtilSymbolDefinitionType.Group.ToString(), 11 UtilSymbolDefinitionType.Group.ToString(),
12 new[] 12 new[]
13 { 13 {
14 new IntermediateFieldDefinition(nameof(GroupSymbolFields.ComponentRef), IntermediateFieldType.String), 14 new IntermediateFieldDefinition(nameof(GroupSymbol.SymbolFields.ComponentRef), IntermediateFieldType.String),
15 new IntermediateFieldDefinition(nameof(GroupSymbolFields.Name), IntermediateFieldType.String), 15 new IntermediateFieldDefinition(nameof(GroupSymbol.SymbolFields.Name), IntermediateFieldType.String),
16 new IntermediateFieldDefinition(nameof(GroupSymbolFields.Domain), IntermediateFieldType.String), 16 new IntermediateFieldDefinition(nameof(GroupSymbol.SymbolFields.Domain), IntermediateFieldType.String),
17 }, 17 },
18 typeof(GroupSymbol)); 18 typeof(GroupSymbol));
19
20 public static readonly IntermediateSymbolDefinition Group6 = new IntermediateSymbolDefinition(
21 UtilSymbolDefinitionType.Group6.ToString(),
22 new[]
23 {
24 new IntermediateFieldDefinition(nameof(Group6Symbol.SymbolFields.GroupRef), IntermediateFieldType.String),
25 new IntermediateFieldDefinition(nameof(Group6Symbol.SymbolFields.Comment), IntermediateFieldType.String),
26 new IntermediateFieldDefinition(nameof(Group6Symbol.SymbolFields.Attributes), IntermediateFieldType.Number),
27 },
28 typeof(Group6Symbol));
19 } 29 }
20} 30}
21 31
22namespace WixToolset.Util.Symbols 32namespace WixToolset.Util.Symbols
23{ 33{
34 using System;
24 using WixToolset.Data; 35 using WixToolset.Data;
25 36
26 public enum GroupSymbolFields
27 {
28 ComponentRef,
29 Name,
30 Domain,
31 }
32
33 public class GroupSymbol : IntermediateSymbol 37 public class GroupSymbol : IntermediateSymbol
34 { 38 {
39 public enum SymbolFields
40 {
41 ComponentRef,
42 Name,
43 Domain,
44 }
45
35 public GroupSymbol() : base(UtilSymbolDefinitions.Group, null, null) 46 public GroupSymbol() : base(UtilSymbolDefinitions.Group, null, null)
36 { 47 {
37 } 48 }
@@ -40,24 +51,74 @@ namespace WixToolset.Util.Symbols
40 { 51 {
41 } 52 }
42 53
43 public IntermediateField this[GroupSymbolFields index] => this.Fields[(int)index]; 54 public IntermediateField this[GroupSymbol.SymbolFields index] => this.Fields[(int)index];
44 55
45 public string ComponentRef 56 public string ComponentRef
46 { 57 {
47 get => this.Fields[(int)GroupSymbolFields.ComponentRef].AsString(); 58 get => this.Fields[(int)GroupSymbol.SymbolFields.ComponentRef].AsString();
48 set => this.Set((int)GroupSymbolFields.ComponentRef, value); 59 set => this.Set((int)GroupSymbol.SymbolFields.ComponentRef, value);
49 } 60 }
50 61
51 public string Name 62 public string Name
52 { 63 {
53 get => this.Fields[(int)GroupSymbolFields.Name].AsString(); 64 get => this.Fields[(int)GroupSymbol.SymbolFields.Name].AsString();
54 set => this.Set((int)GroupSymbolFields.Name, value); 65 set => this.Set((int)GroupSymbol.SymbolFields.Name, value);
55 } 66 }
56 67
57 public string Domain 68 public string Domain
58 { 69 {
59 get => this.Fields[(int)GroupSymbolFields.Domain].AsString(); 70 get => this.Fields[(int)GroupSymbol.SymbolFields.Domain].AsString();
60 set => this.Set((int)GroupSymbolFields.Domain, value); 71 set => this.Set((int)GroupSymbol.SymbolFields.Domain, value);
61 } 72 }
62 } 73 }
63} \ No newline at end of file 74
75 public class Group6Symbol : IntermediateSymbol
76 {
77 [Flags]
78 public enum SymbolAttributes
79 {
80 None = 0x00000000,
81 FailIfExists = 0x00000001,
82 UpdateIfExists = 0x00000002,
83 DontRemoveOnUninstall = 0x00000004,
84 DontCreateGroup = 0x00000008,
85 NonVital = 0x00000010,
86 RemoveComment = 0x00000020,
87 }
88
89 public enum SymbolFields
90 {
91 GroupRef,
92 Comment,
93 Attributes,
94 }
95
96 public Group6Symbol() : base(UtilSymbolDefinitions.Group6, null, null)
97 {
98 }
99
100 public Group6Symbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(UtilSymbolDefinitions.Group6, sourceLineNumber, id)
101 {
102 }
103
104 public IntermediateField this[Group6Symbol.SymbolFields index] => this.Fields[(int)index];
105
106 public string GroupRef
107 {
108 get => this.Fields[(int)Group6Symbol.SymbolFields.GroupRef].AsString();
109 set => this.Set((int)Group6Symbol.SymbolFields.GroupRef, value);
110 }
111
112 public string Comment
113 {
114 get => this.Fields[(int)Group6Symbol.SymbolFields.Comment].AsString();
115 set => this.Set((int)Group6Symbol.SymbolFields.Comment, value);
116 }
117
118 public SymbolAttributes Attributes
119 {
120 get => (SymbolAttributes)this.Fields[(int)Group6Symbol.SymbolFields.Attributes].AsNumber();
121 set => this.Set((int)Group6Symbol.SymbolFields.Attributes, (int)value);
122 }
123 }
124}
diff --git a/src/ext/Util/wixext/Symbols/UtilSymbolDefinitions.cs b/src/ext/Util/wixext/Symbols/UtilSymbolDefinitions.cs
index 8152868f..43d0fca0 100644
--- a/src/ext/Util/wixext/Symbols/UtilSymbolDefinitions.cs
+++ b/src/ext/Util/wixext/Symbols/UtilSymbolDefinitions.cs
@@ -12,6 +12,8 @@ namespace WixToolset.Util
12 FileShare, 12 FileShare,
13 FileSharePermissions, 13 FileSharePermissions,
14 Group, 14 Group,
15 Group6,
16 GroupGroup,
15 Perfmon, 17 Perfmon,
16 PerfmonManifest, 18 PerfmonManifest,
17 PerformanceCategory, 19 PerformanceCategory,
@@ -59,6 +61,12 @@ namespace WixToolset.Util
59 case UtilSymbolDefinitionType.Group: 61 case UtilSymbolDefinitionType.Group:
60 return UtilSymbolDefinitions.Group; 62 return UtilSymbolDefinitions.Group;
61 63
64 case UtilSymbolDefinitionType.Group6:
65 return UtilSymbolDefinitions.Group6;
66
67 case UtilSymbolDefinitionType.GroupGroup:
68 return UtilSymbolDefinitions.GroupGroup;
69
62 case UtilSymbolDefinitionType.Perfmon: 70 case UtilSymbolDefinitionType.Perfmon:
63 return UtilSymbolDefinitions.Perfmon; 71 return UtilSymbolDefinitions.Perfmon;
64 72
diff --git a/src/ext/Util/wixext/UtilCompiler.cs b/src/ext/Util/wixext/UtilCompiler.cs
index 3bcd2c0b..aff7dd0d 100644
--- a/src/ext/Util/wixext/UtilCompiler.cs
+++ b/src/ext/Util/wixext/UtilCompiler.cs
@@ -139,6 +139,9 @@ namespace WixToolset.Util
139 case "TouchFile": 139 case "TouchFile":
140 this.ParseTouchFileElement(intermediate, section, element, componentId, componentWin64); 140 this.ParseTouchFileElement(intermediate, section, element, componentId, componentWin64);
141 break; 141 break;
142 case "Group":
143 this.ParseGroupElement(intermediate, section, element, componentId);
144 break;
142 case "User": 145 case "User":
143 this.ParseUserElement(intermediate, section, element, componentId); 146 this.ParseUserElement(intermediate, section, element, componentId);
144 break; 147 break;
@@ -1357,6 +1360,8 @@ namespace WixToolset.Util
1357 Identifier id = null; 1360 Identifier id = null;
1358 string domain = null; 1361 string domain = null;
1359 string name = null; 1362 string name = null;
1363 string comment = null;
1364 Group6Symbol.SymbolAttributes attributes = Group6Symbol.SymbolAttributes.None;
1360 1365
1361 foreach (var attrib in element.Attributes()) 1366 foreach (var attrib in element.Attributes())
1362 { 1367 {
@@ -1373,6 +1378,75 @@ namespace WixToolset.Util
1373 case "Domain": 1378 case "Domain":
1374 domain = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); 1379 domain = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
1375 break; 1380 break;
1381 case "Comment":
1382 if (null == componentId)
1383 {
1384 this.Messaging.Write(UtilErrors.IllegalAttributeWithoutComponent(sourceLineNumbers, element.Name.LocalName, attrib.Name.LocalName));
1385 }
1386
1387 comment = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
1388 break;
1389 case "CreateGroup":
1390 if (null == componentId)
1391 {
1392 this.Messaging.Write(UtilErrors.IllegalAttributeWithoutComponent(sourceLineNumbers, element.Name.LocalName, attrib.Name.LocalName));
1393 }
1394
1395 if (YesNoType.No == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1396 {
1397 attributes |= Group6Symbol.SymbolAttributes.DontCreateGroup;
1398 }
1399 break;
1400 case "FailIfExists":
1401 if (null == componentId)
1402 {
1403 this.Messaging.Write(UtilErrors.IllegalAttributeWithoutComponent(sourceLineNumbers, element.Name.LocalName, attrib.Name.LocalName));
1404 }
1405
1406 if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1407 {
1408 attributes |= Group6Symbol.SymbolAttributes.FailIfExists;
1409 }
1410 break;
1411 case "UpdateIfExists":
1412 if (null == componentId)
1413 {
1414 this.Messaging.Write(UtilErrors.IllegalAttributeWithoutComponent(sourceLineNumbers, element.Name.LocalName, attrib.Name.LocalName));
1415 }
1416
1417 if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1418 {
1419 attributes |= Group6Symbol.SymbolAttributes.UpdateIfExists;
1420 }
1421 break;
1422 case "RemoveComment":
1423 if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1424 {
1425 attributes |= Group6Symbol.SymbolAttributes.RemoveComment;
1426 }
1427 break;
1428 case "RemoveOnUninstall":
1429 if (null == componentId)
1430 {
1431 this.Messaging.Write(UtilErrors.IllegalAttributeWithoutComponent(sourceLineNumbers, element.Name.LocalName, attrib.Name.LocalName));
1432 }
1433
1434 if (YesNoType.No == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1435 {
1436 attributes |= Group6Symbol.SymbolAttributes.DontRemoveOnUninstall;
1437 }
1438 break;
1439 case "Vital":
1440 if (null == componentId)
1441 {
1442 this.Messaging.Write(UtilErrors.IllegalAttributeWithoutComponent(sourceLineNumbers, element.Name.LocalName, attrib.Name.LocalName));
1443 }
1444
1445 if (YesNoType.No == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1446 {
1447 attributes |= Group6Symbol.SymbolAttributes.NonVital;
1448 }
1449 break;
1376 default: 1450 default:
1377 this.ParseHelper.UnexpectedAttribute(element, attrib); 1451 this.ParseHelper.UnexpectedAttribute(element, attrib);
1378 break; 1452 break;
@@ -1389,7 +1463,40 @@ namespace WixToolset.Util
1389 id = this.ParseHelper.CreateIdentifier("ugr", componentId, domain, name); 1463 id = this.ParseHelper.CreateIdentifier("ugr", componentId, domain, name);
1390 } 1464 }
1391 1465
1392 this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); 1466 if (null == name)
1467 {
1468 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name"));
1469 }
1470
1471 if (null != comment && (Group6Symbol.SymbolAttributes.RemoveComment & attributes) != 0)
1472 {
1473 this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "Comment", "RemoveComment"));
1474 }
1475
1476 if (null != componentId)
1477 {
1478 this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4ConfigureGroups", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64);
1479 }
1480
1481 foreach (var child in element.Elements())
1482 {
1483 if (this.Namespace == child.Name.Namespace)
1484 {
1485 switch (child.Name.LocalName)
1486 {
1487 case "GroupRef":
1488 this.ParseGroupRefElement(intermediate, section, child, id.Id, groupType:true);
1489 break;
1490 default:
1491 //this.ParseHelper.UnexpectedElement(element, child);
1492 break;
1493 }
1494 }
1495 else
1496 {
1497 this.ParseHelper.ParseExtensionElement(this.Context.Extensions, intermediate, section, element, child);
1498 }
1499 }
1393 1500
1394 if (!this.Messaging.EncounteredError) 1501 if (!this.Messaging.EncounteredError)
1395 { 1502 {
@@ -1399,6 +1506,12 @@ namespace WixToolset.Util
1399 Name = name, 1506 Name = name,
1400 Domain = domain, 1507 Domain = domain,
1401 }); 1508 });
1509 section.AddSymbol(new Group6Symbol(sourceLineNumbers, id)
1510 {
1511 GroupRef = id.Id,
1512 Comment = comment,
1513 Attributes = attributes,
1514 });
1402 } 1515 }
1403 } 1516 }
1404 1517
@@ -1406,8 +1519,9 @@ namespace WixToolset.Util
1406 /// Parses a GroupRef element 1519 /// Parses a GroupRef element
1407 /// </summary> 1520 /// </summary>
1408 /// <param name="element">Element to parse.</param> 1521 /// <param name="element">Element to parse.</param>
1409 /// <param name="userId">Required user id to be joined to the group.</param> 1522 /// <param name="childId">Required child id to be joined to the group.</param>
1410 private void ParseGroupRefElement(Intermediate intermediate, IntermediateSection section, XElement element, string userId) 1523 /// <param name="groupType">whether the child is a group (true) or a user (false)</param>
1524 private void ParseGroupRefElement(Intermediate intermediate, IntermediateSection section, XElement element, string childId, bool groupType=false)
1411 { 1525 {
1412 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); 1526 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
1413 string groupId = null; 1527 string groupId = null;
@@ -1437,11 +1551,22 @@ namespace WixToolset.Util
1437 1551
1438 if (!this.Messaging.EncounteredError) 1552 if (!this.Messaging.EncounteredError)
1439 { 1553 {
1440 section.AddSymbol(new UserGroupSymbol(sourceLineNumbers) 1554 if (!groupType)
1441 { 1555 {
1442 UserRef = userId, 1556 section.AddSymbol(new UserGroupSymbol(sourceLineNumbers)
1443 GroupRef = groupId, 1557 {
1444 }); 1558 UserRef = childId,
1559 GroupRef = groupId,
1560 });
1561 }
1562 else
1563 {
1564 section.AddSymbol(new GroupGroupSymbol(sourceLineNumbers)
1565 {
1566 ChildGroupRef = childId,
1567 ParentGroupRef = groupId,
1568 });
1569 }
1445 } 1570 }
1446 } 1571 }
1447 1572
@@ -3460,7 +3585,7 @@ namespace WixToolset.Util
3460 this.Messaging.Write(UtilErrors.IllegalElementWithoutComponent(childSourceLineNumbers, child.Name.LocalName)); 3585 this.Messaging.Write(UtilErrors.IllegalElementWithoutComponent(childSourceLineNumbers, child.Name.LocalName));
3461 } 3586 }
3462 3587
3463 this.ParseGroupRefElement(intermediate, section, child, id.Id); 3588 this.ParseGroupRefElement(intermediate, section, child, id.Id, groupType:false);
3464 break; 3589 break;
3465 default: 3590 default:
3466 this.ParseHelper.UnexpectedElement(element, child); 3591 this.ParseHelper.UnexpectedElement(element, child);
diff --git a/src/ext/Util/wixext/UtilDecompiler.cs b/src/ext/Util/wixext/UtilDecompiler.cs
index 52b64889..53b75b8d 100644
--- a/src/ext/Util/wixext/UtilDecompiler.cs
+++ b/src/ext/Util/wixext/UtilDecompiler.cs
@@ -176,6 +176,14 @@ namespace WixToolset.Util
176 case "Wix4Group": 176 case "Wix4Group":
177 this.DecompileGroupTable(table); 177 this.DecompileGroupTable(table);
178 break; 178 break;
179 case "Group6":
180 case "Wix6Group":
181 this.DecompileGroup6Table(table);
182 break;
183 case "GroupGroup":
184 case "Wix6GroupGroup":
185 this.DecompileGroupGroup6Table(table);
186 break;
179 case "Perfmon": 187 case "Perfmon":
180 case "Wix4Perfmon": 188 case "Wix4Perfmon":
181 this.DecompilePerfmonTable(table); 189 this.DecompilePerfmonTable(table);
@@ -427,18 +435,60 @@ namespace WixToolset.Util
427 { 435 {
428 foreach (var row in table.Rows) 436 foreach (var row in table.Rows)
429 { 437 {
430 if (null != row[1])
431 {
432 this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, "Component_", (string)row[1]));
433 }
434
435 this.DecompilerHelper.AddElementToRoot(UtilConstants.GroupName, 438 this.DecompilerHelper.AddElementToRoot(UtilConstants.GroupName,
436 new XAttribute("Id", row.FieldAsString(0)), 439 new XAttribute("Id", row.FieldAsString(0)),
437 new XAttribute("Name", row.FieldAsString(1)), 440 new XAttribute("Name", row.FieldAsString(2)),
438 AttributeIfNotNull("Domain", row, 3) 441 AttributeIfNotNull("Domain", row, 3)
439 ); 442 );
440 } 443 }
441 } 444 }
445 /// <summary>
446 /// Decompile the Group6 table.
447 /// </summary>
448 /// <param name="table">The table to decompile.</param>
449 private void DecompileGroup6Table(Table table)
450 {
451 foreach (var row in table.Rows)
452 {
453 var groupId = row.FieldAsString(0);
454 if (this.DecompilerHelper.TryGetIndexedElement("Group", groupId, out var group))
455 {
456 var attributes = (Group6Symbol.SymbolAttributes)(row.FieldAsNullableInteger(2) ?? 0);
457 group.Add(AttributeIfNotNull("Comment", row, 1));
458 group.Add(AttributeIfTrue("FailIfExists", ((attributes & Group6Symbol.SymbolAttributes.FailIfExists) != 0)));
459 group.Add(AttributeIfTrue("UpdateIfExists", ((attributes & Group6Symbol.SymbolAttributes.UpdateIfExists) != 0)));
460 group.Add(AttributeIfTrue("DontRemoveOnUninstall", ((attributes & Group6Symbol.SymbolAttributes.DontRemoveOnUninstall) != 0)));
461 group.Add(AttributeIfTrue("DontCreateGroup", ((attributes & Group6Symbol.SymbolAttributes.DontCreateGroup) != 0)));
462 group.Add(AttributeIfTrue("NonVital", ((attributes & Group6Symbol.SymbolAttributes.NonVital) != 0)));
463 group.Add(AttributeIfTrue("RemoveComment", ((attributes & Group6Symbol.SymbolAttributes.RemoveComment) != 0)));
464 }
465 else
466 {
467 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(), "Group_", groupId, "Group"));
468 }
469 }
470 }
471
472
473 /// <summary>
474 /// Decompile the GroupGroup6 table.
475 /// </summary>
476 /// <param name="table">The table to decompile.</param>
477 private void DecompileGroupGroup6Table(Table table)
478 {
479 foreach (var row in table.Rows)
480 {
481 var childId = row.FieldAsString(1);
482 if (this.DecompilerHelper.TryGetIndexedElement("Group", childId, out var group))
483 {
484 group.Add(new XElement(UtilConstants.GroupRefName, new XAttribute("Id", row.FieldAsString(0))));
485 }
486 else
487 {
488 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(), "Parent_", childId, "Group"));
489 }
490 }
491 }
442 492
443 /// <summary> 493 /// <summary>
444 /// Decompile the WixInternetShortcut table. 494 /// Decompile the WixInternetShortcut table.
diff --git a/src/ext/Util/wixext/UtilTableDefinitions.cs b/src/ext/Util/wixext/UtilTableDefinitions.cs
index baa1d25b..908b7eea 100644
--- a/src/ext/Util/wixext/UtilTableDefinitions.cs
+++ b/src/ext/Util/wixext/UtilTableDefinitions.cs
@@ -105,6 +105,29 @@ namespace WixToolset.Util
105 symbolIdIsPrimaryKey: true 105 symbolIdIsPrimaryKey: true
106 ); 106 );
107 107
108 public static readonly TableDefinition Wix6Group = new TableDefinition(
109 "Wix6Group",
110 UtilSymbolDefinitions.Group6,
111 new[]
112 {
113 new ColumnDefinition("Group_", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, keyTable: "Wix4Group", keyColumn: 1, description: "Primary key, non-localized token", modularizeType: ColumnModularizeType.Column),
114 new ColumnDefinition("Comment", ColumnType.String, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "Group comment", modularizeType: ColumnModularizeType.Property),
115 new ColumnDefinition("Attributes", ColumnType.Number, 4, primaryKey: false, nullable: true, ColumnCategory.Unknown, minValue: 0, maxValue: 65535, description: "Attributes describing how to create the group"),
116 },
117 symbolIdIsPrimaryKey: false
118 );
119
120 public static readonly TableDefinition Wix6GroupGroup = new TableDefinition(
121 "Wix6GroupGroup",
122 UtilSymbolDefinitions.GroupGroup,
123 new[]
124 {
125 new ColumnDefinition("Parent_", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, keyTable: "Wix4Group", keyColumn: 1, description: "Parent Group", modularizeType: ColumnModularizeType.Column),
126 new ColumnDefinition("Child_", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, keyTable: "Wix4Group", keyColumn: 1, description: "Child Group, a member of the Parent Group", modularizeType: ColumnModularizeType.Column),
127 },
128 symbolIdIsPrimaryKey: false
129 );
130
108 public static readonly TableDefinition Wix4InternetShortcut = new TableDefinition( 131 public static readonly TableDefinition Wix4InternetShortcut = new TableDefinition(
109 "Wix4InternetShortcut", 132 "Wix4InternetShortcut",
110 UtilSymbolDefinitions.WixInternetShortcut, 133 UtilSymbolDefinitions.WixInternetShortcut,
@@ -302,6 +325,8 @@ namespace WixToolset.Util
302 Wix4FileShare, 325 Wix4FileShare,
303 Wix4FileSharePermissions, 326 Wix4FileSharePermissions,
304 Wix4Group, 327 Wix4Group,
328 Wix6Group,
329 Wix6GroupGroup,
305 Wix4InternetShortcut, 330 Wix4InternetShortcut,
306 Wix4PerformanceCategory, 331 Wix4PerformanceCategory,
307 Wix4Perfmon, 332 Wix4Perfmon,
diff --git a/src/ext/Util/wixlib/UtilExtension.wxs b/src/ext/Util/wixlib/UtilExtension.wxs
index 7bc50c96..bc0f651d 100644
--- a/src/ext/Util/wixlib/UtilExtension.wxs
+++ b/src/ext/Util/wixlib/UtilExtension.wxs
@@ -15,6 +15,14 @@
15 </Fragment> 15 </Fragment>
16 16
17 <Fragment> 17 <Fragment>
18 <UI Id="ConfigureGroupsErrorText">
19 <Error Id="$(var.msierrGRPFailedGroupCreate)" Message="!(loc.msierrGRPFailedGroupCreate)" />
20 <Error Id="$(var.msierrGRPFailedGroupGroupAdd)" Message="!(loc.msierrGRPFailedGroupGroupAdd)" />
21 <Error Id="$(var.msierrGRPFailedGroupCreateExists)" Message="!(loc.msierrGRPFailedGroupCreateExists)" />
22 </UI>
23 </Fragment>
24
25 <Fragment>
18 <UI Id="ConfigureSmbErrorsText"> 26 <UI Id="ConfigureSmbErrorsText">
19 <Error Id="$(var.msierrSMBFailedCreate)" Message="!(loc.msierrSMBFailedCreate)" /> 27 <Error Id="$(var.msierrSMBFailedCreate)" Message="!(loc.msierrSMBFailedCreate)" />
20 <Error Id="$(var.msierrSMBFailedDrop)" Message="!(loc.msierrSMBFailedDrop)" /> 28 <Error Id="$(var.msierrSMBFailedDrop)" Message="!(loc.msierrSMBFailedDrop)" />
diff --git a/src/ext/Util/wixlib/UtilExtension_Platform.wxi b/src/ext/Util/wixlib/UtilExtension_Platform.wxi
index 690c76c5..df53c7d4 100644
--- a/src/ext/Util/wixlib/UtilExtension_Platform.wxi
+++ b/src/ext/Util/wixlib/UtilExtension_Platform.wxi
@@ -133,6 +133,20 @@
133 </Fragment> 133 </Fragment>
134 134
135 <Fragment> 135 <Fragment>
136 <UIRef Id="ConfigureGroupsErrorText" />
137
138 <CustomAction Id="$(var.Prefix)ConfigureGroups$(var.Suffix)" DllEntry="ConfigureGroups" Execute="immediate" Return="check" SuppressModularization="yes" BinaryRef="$(var.Prefix)UtilCA$(var.Suffix)" />
139 <CustomAction Id="$(var.Prefix)CreateGroup$(var.Suffix)" DllEntry="CreateGroup" Impersonate="no" Execute="deferred" Return="check" HideTarget="yes" SuppressModularization="yes" BinaryRef="$(var.Prefix)UtilCA$(var.Suffix)" />
140 <CustomAction Id="$(var.Prefix)CreateGroupRollback$(var.Suffix)" DllEntry="CreateGroupRollback" Impersonate="no" Execute="rollback" Return="check" HideTarget="yes" SuppressModularization="yes" BinaryRef="$(var.Prefix)UtilCA$(var.Suffix)" />
141 <!-- RemoveGroup is a type commit action because it is not possible to rollback the removal of a group -->
142 <CustomAction Id="$(var.Prefix)RemoveGroup$(var.Suffix)" DllEntry="RemoveGroup" Impersonate="no" Execute="commit" Return="ignore" HideTarget="yes" SuppressModularization="yes" BinaryRef="$(var.Prefix)UtilCA$(var.Suffix)" />
143
144 <InstallExecuteSequence>
145 <Custom Action="virtual $(var.Prefix)ConfigureGroups$(var.Suffix)" Before="InstallFiles" Condition="VersionNT &gt; 400" />
146 </InstallExecuteSequence>
147 </Fragment>
148
149 <Fragment>
136 <UIRef Id="ConfigureUsersErrorText" /> 150 <UIRef Id="ConfigureUsersErrorText" />
137 151
138 <CustomAction Id="$(var.Prefix)ConfigureUsers$(var.Suffix)" DllEntry="ConfigureUsers" Execute="immediate" Return="check" SuppressModularization="yes" BinaryRef="$(var.Prefix)UtilCA$(var.Suffix)" /> 152 <CustomAction Id="$(var.Prefix)ConfigureUsers$(var.Suffix)" DllEntry="ConfigureUsers" Execute="immediate" Return="check" SuppressModularization="yes" BinaryRef="$(var.Prefix)UtilCA$(var.Suffix)" />
diff --git a/src/ext/Util/wixlib/de-de.wxl b/src/ext/Util/wixlib/de-de.wxl
index 4f9dcfdd..37d5e620 100644
--- a/src/ext/Util/wixlib/de-de.wxl
+++ b/src/ext/Util/wixlib/de-de.wxl
@@ -8,6 +8,10 @@
8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Konnte den Benutzer nicht gewähren 'Anmelden als ein Service' Rechte. ([2] [3] [4] [5])" /> 8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Konnte den Benutzer nicht gewähren 'Anmelden als ein Service' Rechte. ([2] [3] [4] [5])" />
9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="Konnte den Benutzer nicht anlegen, da er bereits existierte. ([2] [3] [4] [5])" /> 9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="Konnte den Benutzer nicht anlegen, da er bereits existierte. ([2] [3] [4] [5])" />
10 10
11 <String Id="msierrGRPFailedGroupCreate" Overridable="yes" Value="Könnte die Gruppe nicht anlegen. ([2] [3] [4] [5])" />
12 <String Id="msierrGRPFailedGroupGroupAdd" Overridable="yes" Value="Könnte die Gruppe nicht zur Gruppe hinzufügen. ([2] [3] [4] [5])" />
13 <String Id="msierrGRPFailedGroupCreateExists" Overridable="yes" Value="Könnte die Gruppe nicht anlegen, da er bereits existierte. ([2] [3] [4] [5])" />
14
11 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Konnte Netzwerkfreigabe nicht anlegen. ([2] [3] [4] [5])" /> 15 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Konnte Netzwerkfreigabe nicht anlegen. ([2] [3] [4] [5])" />
12 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Konnte Netzwerkfreigabe nicht entfernen. ([2] [3] [4] [5])" /> 16 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Konnte Netzwerkfreigabe nicht entfernen. ([2] [3] [4] [5])" />
13 17
diff --git a/src/ext/Util/wixlib/en-us.wxl b/src/ext/Util/wixlib/en-us.wxl
index 8fe7a75e..a0ae0265 100644
--- a/src/ext/Util/wixlib/en-us.wxl
+++ b/src/ext/Util/wixlib/en-us.wxl
@@ -8,6 +8,10 @@
8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Failed to grant 'logon as service' rights to user. ([2] [3] [4] [5])" /> 8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Failed to grant 'logon as service' rights to user. ([2] [3] [4] [5])" />
9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="Failed to create user because it already exists. ([2] [3] [4] [5])" /> 9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="Failed to create user because it already exists. ([2] [3] [4] [5])" />
10 10
11 <String Id="msierrGRPFailedGroupCreate" Overridable="yes" Value="Failed to create group. ([2] [3] [4] [5])" />
12 <String Id="msierrGRPFailedGroupGroupAdd" Overridable="yes" Value="Failed to add group to group. ([2] [3] [4] [5])" />
13 <String Id="msierrGRPFailedGroupCreateExists" Overridable="yes" Value="Failed to create group because it already exists. ([2] [3] [4] [5])" />
14
11 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Failed to create network share. ([2] [3] [4] [5])" /> 15 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Failed to create network share. ([2] [3] [4] [5])" />
12 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Failed to drop network share. ([2] [3] [4] [5])" /> 16 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Failed to drop network share. ([2] [3] [4] [5])" />
13 17
diff --git a/src/ext/Util/wixlib/es-es.wxl b/src/ext/Util/wixlib/es-es.wxl
index 7b87ae50..3756027a 100644
--- a/src/ext/Util/wixlib/es-es.wxl
+++ b/src/ext/Util/wixlib/es-es.wxl
@@ -7,6 +7,10 @@
7 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="No se pudieron otorgar derechos de 'iniciar sesión como servicio' al usuario. ([2] [3] [4] [5])" /> 7 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="No se pudieron otorgar derechos de 'iniciar sesión como servicio' al usuario. ([2] [3] [4] [5])" />
8 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="La creación del usuario ha fracasado porque ya existe. ([2] [3] [4] [5])" /> 8 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="La creación del usuario ha fracasado porque ya existe. ([2] [3] [4] [5])" />
9 9
10 <String Id="msierrGRPFailedGroupCreate" Overridable="yes" Value="La creación del grupo ha fracasado. ([2] [3] [4] [5])" />
11 <String Id="msierrGRPFailedGroupGroupAdd" Overridable="yes" Value="El aditamento del grupo al grupo ha fracasado. ([2] [3] [4] [5])" />
12 <String Id="msierrGRPFailedGroupCreateExists" Overridable="yes" Value="La creación del grupo ha fracasado porque ya existe. ([2] [3] [4] [5])" />
13
10 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="La creación de la red compartida ha fracasado. ([2] [3] [4] [5])" /> 14 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="La creación de la red compartida ha fracasado. ([2] [3] [4] [5])" />
11 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="La eliminación de la red compartida ha fracasado. ([2] [3] [4] [5])" /> 15 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="La eliminación de la red compartida ha fracasado. ([2] [3] [4] [5])" />
12 16
diff --git a/src/ext/Util/wixlib/fr-fr.wxl b/src/ext/Util/wixlib/fr-fr.wxl
index 54ff5162..4b9aa9ad 100644
--- a/src/ext/Util/wixlib/fr-fr.wxl
+++ b/src/ext/Util/wixlib/fr-fr.wxl
@@ -7,6 +7,10 @@
7 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Échec de l'octroi des droits de « connexion en tant que service » à l'utilisateur. ([2] [3] [4] [5])" /> 7 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Échec de l'octroi des droits de « connexion en tant que service » à l'utilisateur. ([2] [3] [4] [5])" />
8 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="La création de l'utilisateur a échoué car il existe dejà. ([2] [3] [4] [5])" /> 8 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="La création de l'utilisateur a échoué car il existe dejà. ([2] [3] [4] [5])" />
9 9
10 <String Id="msierrGRPFailedGroupCreate" Overridable="yes" Value="La création du groupe a échoué. ([2] [3] [4] [5])" />
11 <String Id="msierrGRPFailedGroupGroupAdd" Overridable="yes" Value="L'ajout du groupe au groupe a échoué. ([2] [3] [4] [5])" />
12 <String Id="msierrGRPFailedGroupCreateExists" Overridable="yes" Value="La création du groupe a échoué car il existe dejà. ([2] [3] [4] [5])" />
13
10 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="La création du partage reseau a échoué. ([2] [3] [4] [5])" /> 14 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="La création du partage reseau a échoué. ([2] [3] [4] [5])" />
11 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="La suppression du partage reseau a échoué. ([2] [3] [4] [5])" /> 15 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="La suppression du partage reseau a échoué. ([2] [3] [4] [5])" />
12 16
diff --git a/src/ext/Util/wixlib/it-it.wxl b/src/ext/Util/wixlib/it-it.wxl
index 350cfe9e..9d41aad4 100644
--- a/src/ext/Util/wixlib/it-it.wxl
+++ b/src/ext/Util/wixlib/it-it.wxl
@@ -8,6 +8,10 @@
8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Impossibile concedere i 'accedere come servizio' diritti all'utente. ([2] [3] [4] [5])" /> 8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Impossibile concedere i 'accedere come servizio' diritti all'utente. ([2] [3] [4] [5])" />
9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="Impossibile creare l'utente perchè già esistente. ([2] [3] [4] [5])" /> 9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="Impossibile creare l'utente perchè già esistente. ([2] [3] [4] [5])" />
10 10
11 <String Id="msierrGRPFailedGroupCreate" Overridable="yes" Value="Impossibile creare il gruppo. ([2] [3] [4] [5])" />
12 <String Id="msierrGRPFailedGroupGroupAdd" Overridable="yes" Value="Impossibile aggiungere il gruppo al gruppo. ([2] [3] [4] [5])" />
13 <String Id="msierrGRPFailedGroupCreateExists" Overridable="yes" Value="Impossibile creare il gruppo perchè già esistente. ([2] [3] [4] [5])" />
14
11 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Impossibile creare la risorsa di rete. ([2] [3] [4] [5])" /> 15 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Impossibile creare la risorsa di rete. ([2] [3] [4] [5])" />
12 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Impossibile eliminare la risorsa di rete. ([2] [3] [4] [5])" /> 16 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Impossibile eliminare la risorsa di rete. ([2] [3] [4] [5])" />
13 17
diff --git a/src/ext/Util/wixlib/ja-jp.wxl b/src/ext/Util/wixlib/ja-jp.wxl
index 0a32f93c..64f3a36d 100644
--- a/src/ext/Util/wixlib/ja-jp.wxl
+++ b/src/ext/Util/wixlib/ja-jp.wxl
@@ -8,6 +8,10 @@
8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="ユーザーに「サービスとしてログオン」権限を付与できませんでした。 ([2] [3] [4] [5])" /> 8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="ユーザーに「サービスとしてログオン」権限を付与できませんでした。 ([2] [3] [4] [5])" />
9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="ユーザーが既に存在するため作成できませんでした。 ([2] [3] [4] [5])" /> 9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="ユーザーが既に存在するため作成できませんでした。 ([2] [3] [4] [5])" />
10 10
11 <String Id="msierrGRPFailedGroupCreate" Overridable="yes" Value="グループの作成に失敗しました。 ([2] [3] [4] [5])" />
12 <String Id="msierrGRPFailedGroupGroupAdd" Overridable="yes" Value="グループをグループに追加でいませんでした。 ([2] [3] [4] [5])" />
13 <String Id="msierrGRPFailedGroupCreateExists" Overridable="yes" Value="グループがすでに存在するため、作成できませんでした。 ([2] [3] [4] [5])" />
14
11 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="ネットワーク共有の作成に失敗しました。 ([2] [3] [4] [5])" /> 15 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="ネットワーク共有の作成に失敗しました。 ([2] [3] [4] [5])" />
12 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="ネットワーク共有の削除に失敗しました。 ([2] [3] [4] [5])" /> 16 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="ネットワーク共有の削除に失敗しました。 ([2] [3] [4] [5])" />
13 17
diff --git a/src/ext/Util/wixlib/nl-nl.wxl b/src/ext/Util/wixlib/nl-nl.wxl
index 0fa25069..d1728ca6 100644
--- a/src/ext/Util/wixlib/nl-nl.wxl
+++ b/src/ext/Util/wixlib/nl-nl.wxl
@@ -8,6 +8,10 @@
8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="De gebruiker kon niet worden rechten verlenen 'aanmelden als service'. ([2] [3] [4] [5])" /> 8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="De gebruiker kon niet worden rechten verlenen 'aanmelden als service'. ([2] [3] [4] [5])" />
9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="De gebruiker kon niet worden aangemaakt omdat hij al bestond. ([2] [3] [4] [5])" /> 9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="De gebruiker kon niet worden aangemaakt omdat hij al bestond. ([2] [3] [4] [5])" />
10 10
11 <String Id="msierrGRPFailedGroupCreate" Overridable="yes" Value="De groep kan niet worden aangemaakt. ([2] [3] [4] [5])" />
12 <String Id="msierrGRPFailedGroupGroupAdd" Overridable="yes" Value="De groep kan niet worden toegevoegd. ([2] [3] [4] [5])" />
13 <String Id="msierrGRPFailedGroupCreateExists" Overridable="yes" Value="De groep kan niet worden aangemaakt omdat hij al bestond. ([2] [3] [4] [5])" />
14
11 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Netwerk delen kon niet worden aangemaakt. ([2] [3] [4] [5])" /> 15 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Netwerk delen kon niet worden aangemaakt. ([2] [3] [4] [5])" />
12 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Netwerk delen kon niet worden verwijderd. ([2] [3] [4] [5])" /> 16 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Netwerk delen kon niet worden verwijderd. ([2] [3] [4] [5])" />
13 17
diff --git a/src/ext/Util/wixlib/pt-br.wxl b/src/ext/Util/wixlib/pt-br.wxl
index 7e841698..a1c50e53 100644
--- a/src/ext/Util/wixlib/pt-br.wxl
+++ b/src/ext/Util/wixlib/pt-br.wxl
@@ -8,6 +8,10 @@
8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Não foi possível conceder ao usuário direitos de 'fazer logon como serviço'. ([2] [3] [4] [5])" /> 8 <String Id="msierrUSRFailedGrantLogonAsService" Overridable="yes" Value="Não foi possível conceder ao usuário direitos de 'fazer logon como serviço'. ([2] [3] [4] [5])" />
9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="Falha ao criar o usuário, porque ele já existe. ([2] [3] [4] [5])" /> 9 <String Id="msierrUSRFailedUserCreateExists" Overridable="yes" Value="Falha ao criar o usuário, porque ele já existe. ([2] [3] [4] [5])" />
10 10
11 <String Id="msierrGRPFailedGroupCreate" Overridable="yes" Value="Falha ao criar grupo. ([2] [3] [4] [5])" />
12 <String Id="msierrGRPFailedGroupGroupAdd" Overridable="yes" Value="Falha ao adicionar o grupo ao grupo. ([2] [3] [4] [5])" />
13 <String Id="msierrGRPFailedGroupCreateExists" Overridable="yes" Value="Falha ao criar o grupo, porque ele já existe. ([2] [3] [4] [5])" />
14
11 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Falha ao criar o compartilhamento de rede. ([2] [3] [4] [5])" /> 15 <String Id="msierrSMBFailedCreate" Overridable="yes" Value="Falha ao criar o compartilhamento de rede. ([2] [3] [4] [5])" />
12 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Falha ao cair compartilhamento de rede. ([2] [3] [4] [5])" /> 16 <String Id="msierrSMBFailedDrop" Overridable="yes" Value="Falha ao cair compartilhamento de rede. ([2] [3] [4] [5])" />
13 17
diff --git a/src/ext/caerr.wxi b/src/ext/caerr.wxi
index ff7ec121..a6f18f0c 100644
--- a/src/ext/caerr.wxi
+++ b/src/ext/caerr.wxi
@@ -64,6 +64,9 @@
64 <?define msierrUSRFailedUserGroupAdd = 26403?> 64 <?define msierrUSRFailedUserGroupAdd = 26403?>
65 <?define msierrUSRFailedUserCreateExists = 26404?> 65 <?define msierrUSRFailedUserCreateExists = 26404?>
66 <?define msierrUSRFailedGrantLogonAsService = 26405?> 66 <?define msierrUSRFailedGrantLogonAsService = 26405?>
67 <?define msierrGRPFailedGroupCreate = 26421?>
68 <?define msierrGRPFailedGroupGroupAdd = 26422?>
69 <?define msierrGRPFailedGroupCreateExists = 26423?>
67 <?define msierrDependencyMissingDependencies = 26451?> 70 <?define msierrDependencyMissingDependencies = 26451?>
68 <?define msierrDependencyHasDependents = 26452?> 71 <?define msierrDependencyHasDependents = 26452?>
69 <?define msierrDotNetRuntimeRequired = 27000?> 72 <?define msierrDotNetRuntimeRequired = 27000?>
diff --git a/src/libs/wcautil/WixToolset.WcaUtil/custommsierrors.h b/src/libs/wcautil/WixToolset.WcaUtil/custommsierrors.h
index f149fb31..d4bb991c 100644
--- a/src/libs/wcautil/WixToolset.WcaUtil/custommsierrors.h
+++ b/src/libs/wcautil/WixToolset.WcaUtil/custommsierrors.h
@@ -87,6 +87,9 @@
87#define msierrUSRFailedUserCreateExists 26404 87#define msierrUSRFailedUserCreateExists 26404
88#define msierrUSRFailedGrantLogonAsService 26405 88#define msierrUSRFailedGrantLogonAsService 26405
89 89
90#define msierrGRPFailedGroupCreate 26421
91#define msierrGRPFailedGroupCreateExists 26422
92
90#define msierrDependencyMissingDependencies 26451 93#define msierrDependencyMissingDependencies 26451
91#define msierrDependencyHasDependents 26452 94#define msierrDependencyHasDependents 26452
92 95
diff --git a/src/test/burn/WixTestTools/UserGroupVerifier.cs b/src/test/burn/WixTestTools/UserGroupVerifier.cs
new file mode 100644
index 00000000..2f874057
--- /dev/null
+++ b/src/test/burn/WixTestTools/UserGroupVerifier.cs
@@ -0,0 +1,198 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixTestTools
4{
5 using System;
6 using System.Text;
7 using System.DirectoryServices;
8 using System.DirectoryServices.AccountManagement;
9 using System.Security.Principal;
10 using Xunit;
11
12 /// <summary>
13 /// Contains methods for User Group verification
14 /// </summary>
15 public static class UserGroupVerifier
16 {
17 /// <summary>
18 /// Create a local group on the machine
19 /// </summary>
20 /// <param name="groupName"></param>
21 /// <remarks>Has to be run as an Admin</remarks>
22 public static void CreateLocalGroup(string groupName)
23 {
24 DeleteLocalGroup(groupName);
25 GroupPrincipal newGroup = new GroupPrincipal(new PrincipalContext(ContextType.Machine));
26 newGroup.Name = groupName;
27 newGroup.Description = String.Empty;
28 newGroup.Save();
29 }
30
31 /// <summary>
32 /// Deletes a local gorup from the machine
33 /// </summary>
34 /// <param name="groupName">group name to delete</param>
35 /// <remarks>Has to be run as an Admin</remarks>
36 public static void DeleteLocalGroup(string groupName)
37 {
38 GroupPrincipal newGroup = GetGroup(String.Empty, groupName);
39 if (null != newGroup)
40 {
41 newGroup.Delete();
42 }
43 }
44
45 /// <summary>
46 /// Verifies that a group exists or not
47 /// </summary>
48 /// <param name="domainName">domain name for the group, empty for local groups</param>
49 /// <param name="groupName">the group name</param>
50 public static bool GroupExists(string domainName, string groupName)
51 {
52 GroupPrincipal group = GetGroup(domainName, groupName);
53
54 return null != group;
55 }
56
57 /// <summary>
58 /// Sets the group comment for a given group
59 /// </summary>
60 /// <param name="domainName">domain name for the group, empty for local users</param>
61 /// <param name="groupName">the group name</param>
62 /// <param name="comment">comment to be set for the group</param>
63 public static void SetGroupComment(string domainName, string groupName, string comment)
64 {
65 GroupPrincipal group = GetGroup(domainName, groupName);
66
67 Assert.False(null == group, String.Format("Group '{0}' was not found under domain '{1}'.", groupName, domainName));
68
69 var directoryEntry = group.GetUnderlyingObject() as DirectoryEntry;
70 Assert.False(null == directoryEntry);
71 directoryEntry.Properties["Description"].Value = comment;
72 group.Save();
73 }
74
75 /// <summary>
76 /// Adds the specified group to the specified local group
77 /// </summary>
78 /// <param name="memberName">Member to add</param>
79 /// <param name="groupName">Group to add too</param>
80 public static void AddGroupToGroup(string memberName, string groupName)
81 {
82 DirectoryEntry localMachine;
83 DirectoryEntry localGroup;
84
85 localMachine = new DirectoryEntry("WinNT://" + Environment.MachineName.ToString());
86 localGroup = localMachine.Children.Find(groupName, "group");
87 Assert.False(null == localGroup, String.Format("Group '{0}' was not found.", groupName));
88 DirectoryEntry group = FindActiveDirectoryGroup(memberName);
89 localGroup.Invoke("Add", new object[] { group.Path.ToString() });
90 }
91
92 /// <summary>
93 /// Find the specified group in AD
94 /// </summary>
95 /// <param name="groupName">group name to lookup</param>
96 /// <returns>DirectoryEntry of the group</returns>
97 private static DirectoryEntry FindActiveDirectoryGroup(string groupName)
98 {
99 var mLocalMachine = new DirectoryEntry("WinNT://" + Environment.MachineName.ToString());
100 var mLocalEntries = mLocalMachine.Children;
101
102 var theGroup = mLocalEntries.Find(groupName);
103 return theGroup;
104 }
105
106 /// <summary>
107 /// Verifies the group comment for a given group
108 /// </summary>
109 /// <param name="domainName">domain name for the group, empty for local users</param>
110 /// <param name="groupName">the group name</param>
111 /// <param name="comment">the comment to be verified</param>
112 public static void VerifyGroupComment(string domainName, string groupName, string comment)
113 {
114 GroupPrincipal group = GetGroup(domainName, groupName);
115
116 Assert.False(null == group, String.Format("Group '{0}' was not found under domain '{1}'.", groupName, domainName));
117
118 var directoryEntry = group.GetUnderlyingObject() as DirectoryEntry;
119 Assert.False(null == directoryEntry);
120 Assert.True(comment == (string)(directoryEntry.Properties["Description"].Value));
121 }
122
123 /// <summary>
124 /// Verify that a given group is member of a local group
125 /// </summary>
126 /// <param name="domainName">domain name for the group, empty for local groups</param>
127 /// <param name="memberName">the member name</param>
128 /// <param name="groupNames">list of groups to check for membership</param>
129 public static void VerifyIsMemberOf(string domainName, string memberName, params string[] groupNames)
130 {
131 IsMemberOf(domainName, memberName, true, groupNames);
132 }
133
134 /// <summary>
135 /// Verify that a given group is NOT member of a local group
136 /// </summary>
137 /// <param name="domainName">domain name for the group, empty for local groups</param>
138 /// <param name="memberName">the member name</param>
139 /// <param name="groupNames">list of groups to check for membership</param>
140 public static void VerifyIsNotMemberOf(string domainName, string memberName, params string[] groupNames)
141 {
142 IsMemberOf(domainName, memberName, false, groupNames);
143 }
144
145 /// <summary>
146 /// Verify that a given user is member of a local group
147 /// </summary>
148 /// <param name="domainName">domain name for the group, empty for local groups</param>
149 /// <param name="memberName">the member name</param>
150 /// <param name="shouldBeMember">whether the group is expected to be a member of the groups or not</param>
151 /// <param name="groupNames">list of groups to check for membership</param>
152 private static void IsMemberOf(string domainName, string memberName, bool shouldBeMember, params string[] groupNames)
153 {
154 GroupPrincipal group = GetGroup(domainName, memberName);
155 Assert.False(null == group, String.Format("Group '{0}' was not found under domain '{1}'.", memberName, domainName));
156
157 bool missedAGroup = false;
158 string message = String.Empty;
159 foreach (string groupName in groupNames)
160 {
161 try
162 {
163 bool found = group.IsMemberOf(new PrincipalContext(ContextType.Machine), IdentityType.Name, groupName);
164 if (found != shouldBeMember)
165 {
166 missedAGroup = true;
167 message += String.Format("Group '{0}/{1}' is {2} a member of local group '{3}'. \r\n", domainName, memberName, found ? String.Empty : "NOT", groupName);
168 }
169 }
170 catch (System.DirectoryServices.AccountManagement.PrincipalOperationException)
171 {
172 missedAGroup = true;
173 message += String.Format("Local group '{0}' was not found. \r\n", groupName);
174 }
175 }
176 Assert.False(missedAGroup, message);
177 }
178
179 /// <summary>
180 /// Returns the GroupPrincipal object for a given group
181 /// </summary>
182 /// <param name="domainName">Domain name to look under, if Empty the LocalMachine is assumed as the domain</param>
183 /// <param name="groupName"></param>
184 /// <returns>UserPrincipal Object for the group if found, or null other wise</returns>
185 private static GroupPrincipal GetGroup(string domainName, string groupName)
186 {
187 if (String.IsNullOrEmpty(domainName))
188 {
189 return GroupPrincipal.FindByIdentity(new PrincipalContext(ContextType.Machine), IdentityType.Name, groupName);
190 }
191 else
192 {
193 return GroupPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain,domainName), IdentityType.Name, groupName);
194 }
195 }
196 }
197}
198
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/ProductA.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/ProductA.wixproj
new file mode 100644
index 00000000..3895b853
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/ProductA.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{A3E0B539-63F9-4B43-9E34-F33AE1C6E06D}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/product.wxs
new file mode 100644
index 00000000..e3c143e6
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductA/product.wxs
@@ -0,0 +1,25 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9
10 <Property Id="TEMPDOMAIN" Secure="yes" />
11 <Property Id="TEMPGROUPNAME" Secure="yes" />
12 </Fragment>
13
14 <Fragment>
15 <Component Id="Component1" Guid="09624A9A-4BBC-4126-BBF9-0713C5217DB1" Directory="INSTALLFOLDER">
16 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
17
18 <util:Group Id="TEST_GROUP1" Name="testName1" Comment="Group1" CreateGroup="yes" RemoveOnUninstall="yes" />
19
20 <util:Group Id="TEST_GROUP2" Name="testName2" Comment="Group2" RemoveOnUninstall="no" UpdateIfExists="yes" />
21
22 <util:Group Id="TEST_GROUP3" Name="testName3" Comment="Group3" CreateGroup="no" />
23 </Component>
24 </Fragment>
25</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/ProductAddCommentToExistingGroup.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/ProductAddCommentToExistingGroup.wixproj
new file mode 100644
index 00000000..5938e525
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/ProductAddCommentToExistingGroup.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{B33D3140-4AA5-469D-9DEE-AAF8F0C626DA}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/product.wxs
new file mode 100644
index 00000000..e0170746
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductAddCommentToExistingGroup/product.wxs
@@ -0,0 +1,23 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9 </Fragment>
10
11 <Fragment>
12 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
13 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
14
15 <util:Group Id="TEST_GROUP1"
16 Name="testName1"
17 CreateGroup="yes"
18 UpdateIfExists="yes"
19 RemoveOnUninstall="yes"
20 Comment="testComment1"/>
21 </Component>
22 </Fragment>
23</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/ProductCommentDelete.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/ProductCommentDelete.wixproj
new file mode 100644
index 00000000..63bb2370
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/ProductCommentDelete.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{9E4C301E-5F36-4A86-85BE-776E067D929D}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/product.wxs
new file mode 100644
index 00000000..d1824890
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentDelete/product.wxs
@@ -0,0 +1,18 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9 </Fragment>
10
11 <Fragment>
12 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
13 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
14
15 <util:Group Id="TEST_GROUP1" Name="testName1" UpdateIfExists="yes" RemoveOnUninstall="yes" RemoveComment="yes"/>
16 </Component>
17 </Fragment>
18</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/ProductCommentFail.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/ProductCommentFail.wixproj
new file mode 100644
index 00000000..66f308ae
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/ProductCommentFail.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{85F698E0-F542-4CB4-80A1-6630D2DEB647}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/product_fail.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/product_fail.wxs
new file mode 100644
index 00000000..29b908da
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductCommentFail/product_fail.wxs
@@ -0,0 +1,22 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9
10 <InstallExecuteSequence>
11 <Custom Action="CaFail" After="Wix4ConfigureGroups_X86" />
12 </InstallExecuteSequence>
13 </Fragment>
14
15 <Fragment>
16 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
17 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
18
19 <util:Group Id="TEST_GROUP1" Name="testName1" CreateGroup="yes" RemoveOnUninstall="yes" Comment="testComment1"/>
20 </Component>
21 </Fragment>
22</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/ProductFail.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/ProductFail.wixproj
new file mode 100644
index 00000000..e2fe3aa8
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/ProductFail.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{91D27DAC-04C1-4160-914E-343676D36CAA}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/product_fail.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/product_fail.wxs
new file mode 100644
index 00000000..fb35bc1e
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFail/product_fail.wxs
@@ -0,0 +1,29 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9
10 <Property Id="TEMPDOMAIN" Secure="yes" />
11 <Property Id="TEMPUSERNAME" Secure="yes" />
12
13 <InstallExecuteSequence>
14 <Custom Action="CaFail" After="Wix4ConfigureGroups_X86" />
15 </InstallExecuteSequence>
16 </Fragment>
17
18 <Fragment>
19 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
20 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
21
22 <util:Group Id="TEST_GROUP1" Name="testName1" />
23
24 <util:Group Id="TEST_GROUP2" Name="testName2" RemoveOnUninstall="no" UpdateIfExists="yes" />
25
26 <util:Group Id="TEST_GROUP3" Name="testName3" CreateGroup="no" />
27 </Component>
28 </Fragment>
29</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/FailIfExists.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/FailIfExists.wxs
new file mode 100644
index 00000000..00f8e12d
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/FailIfExists.wxs
@@ -0,0 +1,18 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9 </Fragment>
10
11 <Fragment>
12 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
13 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
14
15 <util:Group Id="TEST_USER4" Name="existinggroup" FailIfExists="yes" RemoveOnUninstall="yes" />
16 </Component>
17 </Fragment>
18</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/ProductFailIfExists.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/ProductFailIfExists.wixproj
new file mode 100644
index 00000000..9e1a836f
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductFailIfExists/ProductFailIfExists.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{BC803822-929E-47DA-AB3A-3A62EEEA2BFB}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/ProductNestedGroups.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/ProductNestedGroups.wixproj
new file mode 100644
index 00000000..3b2e3942
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/ProductNestedGroups.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{8B6C2900-44C4-42C9-879F-82F551B10C15}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/product.wxs
new file mode 100644
index 00000000..191d605c
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNestedGroups/product.wxs
@@ -0,0 +1,33 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9
10 <Property Id="TEMPDOMAIN" Secure="yes" />
11 <Property Id="TEMPGROUPNAME" Secure="yes" />
12 </Fragment>
13
14 <Fragment>
15 <util:Group Id="ADMIN" Name="Administrators" >
16 <util:GroupRef Id="TEST_GROUP1" />
17 <util:GroupRef Id="TEST_GROUP2" />
18 </util:Group>
19 <util:Group Id="POWER_USERS" Name="Power Users" >
20 <util:GroupRef Id="TEST_GROUP1" />
21 </util:Group>
22
23 <Component Id="Component1" Guid="09624A9A-4BBC-4126-BBF9-0713C5217DB1" Directory="INSTALLFOLDER">
24 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
25
26 <util:Group Id="TEST_GROUP1" Name="testName1" Comment="Group1" CreateGroup="yes" RemoveOnUninstall="yes" />
27
28 <util:Group Id="TEST_GROUP2" Name="testName2" Comment="Group2" RemoveOnUninstall="no" UpdateIfExists="yes" />
29
30 <util:Group Id="TEST_GROUP3" Name="testName3" Comment="Group3" />
31 </Component>
32 </Fragment>
33</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/ProductNewGroupWithComment.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/ProductNewGroupWithComment.wixproj
new file mode 100644
index 00000000..aeac903a
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/ProductNewGroupWithComment.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{549E1829-BBDE-42E1-968A-BEB8FC12BFC7}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/product.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/product.wxs
new file mode 100644
index 00000000..2d012b23
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNewGroupWithComment/product.wxs
@@ -0,0 +1,23 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9 </Fragment>
10
11 <Fragment>
12 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
13 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
14 <util:Group
15 Id="TEST_GROUP1"
16 Name="testName1"
17 CreateGroup="yes"
18 UpdateIfExists="yes"
19 RemoveOnUninstall="yes"
20 Comment="testComment1" />
21 </Component>
22 </Fragment>
23</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/NonVitalUserGroup.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/NonVitalUserGroup.wxs
new file mode 100644
index 00000000..a834c76b
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/NonVitalUserGroup.wxs
@@ -0,0 +1,19 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9 </Fragment>
10
11 <Fragment>
12
13 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
14 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
15
16 <util:Group Id="CurrentGroup" Name="[LogonUser]" Domain="[%USERDOMAIN]" RemoveOnUninstall="no" Vital="no" />
17 </Component>
18 </Fragment>
19</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/ProductNonVitalUserGroup.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/ProductNonVitalUserGroup.wixproj
new file mode 100644
index 00000000..8734224d
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductNonVitalGroup/ProductNonVitalUserGroup.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{455C8D4F-6D59-405C-AD51-0ACC7FB91A26}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/ProductRestrictedDomain.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/ProductRestrictedDomain.wixproj
new file mode 100644
index 00000000..e4a01a3a
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/ProductRestrictedDomain.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{50CF526C-A862-4327-9EA3-C96AAB6FABCE}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/RestrictedDomain.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/RestrictedDomain.wxs
new file mode 100644
index 00000000..edb3387c
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductRestrictedDomain/RestrictedDomain.wxs
@@ -0,0 +1,20 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2
3
4<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
5 <Fragment>
6 <ComponentGroup Id="ProductComponents">
7 <ComponentRef Id="Component1" />
8 </ComponentGroup>
9
10 <Property Id="TEMPDOMAIN" Secure="yes" />
11 </Fragment>
12
13 <Fragment>
14 <Component Id="Component1" Guid="00030829-0000-0000-C000-000000000046" Directory="INSTALLFOLDER">
15 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" />
16
17 <util:Group Id="TEST_GROUP_test" Name="testName1" Domain="[TEMPDOMAIN]" />
18 </Component>
19 </Fragment>
20</Wix>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wixproj b/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wixproj
new file mode 100644
index 00000000..93a56216
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wixproj
@@ -0,0 +1,13 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project Sdk="WixToolset.Sdk">
3 <PropertyGroup>
4 <UpgradeCode>{79F2CB65-1E71-42EB-AA30-51BD70C29B23}</UpgradeCode>
5 <ProductComponentsRef>true</ProductComponentsRef>
6 </PropertyGroup>
7 <ItemGroup>
8 <Compile Include="..\..\Templates\Product.wxs" Link="Product.wxs" />
9 </ItemGroup>
10 <ItemGroup>
11 <PackageReference Include="WixToolset.Util.wixext" />
12 </ItemGroup>
13</Project>
diff --git a/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs b/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs
new file mode 100644
index 00000000..059ecee8
--- /dev/null
+++ b/src/test/msi/TestData/UtilExtensionGroupTests/ProductWithCommandLineParameters/ProductWithCommandLineParameters.wxs
@@ -0,0 +1,19 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentRef Id="Component1" />
6 </ComponentGroup>
7 </Fragment>
8
9 <Fragment>
10 <Component Id="Component1"
11 Guid="1FDC6C4D-7741-4BF1-A4F0-4231879CEC45"
12 Directory="INSTALLFOLDER">
13 <util:Group Id="TEST_GROUP1"
14 Name="[TESTPARAMETER1]"
15 CreateGroup="yes"
16 RemoveOnUninstall="yes" />
17 </Component>
18 </Fragment>
19</Wix>
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionGroupTests.cs b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionGroupTests.cs
new file mode 100644
index 00000000..796c4ecd
--- /dev/null
+++ b/src/test/msi/WixToolsetTest.MsiE2E/UtilExtensionGroupTests.cs
@@ -0,0 +1,271 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.MsiE2E
4{
5 using System;
6 using WixTestTools;
7 using Xunit;
8 using Xunit.Abstractions;
9
10 public class UtilExtensionGroupTests : MsiE2ETests
11 {
12 public UtilExtensionGroupTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { }
13
14 // Verify that the users specified in the authoring are created as expected.
15 [RuntimeFact]
16 public void CanInstallAndUninstallGroups()
17 {
18 UserGroupVerifier.CreateLocalGroup("testName3");
19 var productA = this.CreatePackageInstaller("ProductA");
20
21 productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
22
23 // Validate New User Information.
24 Assert.True(UserGroupVerifier.GroupExists(String.Empty, "testName1"), String.Format("Group '{0}' was not created on Install", "testName1"));
25 Assert.True(UserGroupVerifier.GroupExists(String.Empty, "testName2"), String.Format("Group '{0}' was not created on Install", "testName2"));
26 Assert.True(UserGroupVerifier.GroupExists(String.Empty, "testName3"), String.Format("Group '{0}' was not created on Install", "testName3"));
27
28 productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
29
30 // Verify Users marked as RemoveOnUninstall were removed.
31 Assert.False(UserGroupVerifier.GroupExists(String.Empty, "testName1"), String.Format("Group '{0}' was not removed on Uninstall", "testName1"));
32 Assert.True(UserGroupVerifier.GroupExists(String.Empty, "testName2"), String.Format("Group '{0}' was removed on Uninstall", "testName2"));
33
34 // clean up
35 UserGroupVerifier.DeleteLocalGroup("testName1");
36 UserGroupVerifier.DeleteLocalGroup("testName2");
37 UserGroupVerifier.DeleteLocalGroup("testName3");
38 }
39
40 // Verify the rollback action reverts all Users changes.
41 [RuntimeFact]
42 public void CanRollbackGroups()
43 {
44 UserGroupVerifier.CreateLocalGroup("testName3");
45 var productFail = this.CreatePackageInstaller("ProductFail");
46
47 // make sure the user accounts are deleted before we start
48 UserGroupVerifier.DeleteLocalGroup("testName1");
49 UserGroupVerifier.DeleteLocalGroup("testName2");
50
51 productFail.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE);
52
53 // Verify added Users were removed on rollback.
54 Assert.False(UserGroupVerifier.GroupExists(String.Empty, "testName1"), String.Format("Group '{0}' was not removed on Rollback", "testName1"));
55 Assert.False(UserGroupVerifier.GroupExists(String.Empty, "testName2"), String.Format("Group '{0}' was not removed on Rollback", "testName2"));
56
57 // clean up
58 UserGroupVerifier.DeleteLocalGroup("testName1");
59 UserGroupVerifier.DeleteLocalGroup("testName2");
60 UserGroupVerifier.DeleteLocalGroup("testName3");
61 }
62
63
64 // Verify that command-line parameters aer not blocked by repair switches.
65 // Original code signalled repair mode by using "-f ", which silently
66 // terminated the command-line parsing, ignoring any parameters that followed.
67 [RuntimeFact()]
68 public void CanRepairGroupsWithCommandLineParameters()
69 {
70 var arguments = new string[]
71 {
72 "TESTPARAMETER1=testName1",
73 };
74 var productWithCommandLineParameters = this.CreatePackageInstaller("ProductWithCommandLineParameters");
75
76 // Make sure that the user doesn't exist when we start the test.
77 UserGroupVerifier.DeleteLocalGroup("testName1");
78
79 // Install
80 productWithCommandLineParameters.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
81
82 // Repair
83 productWithCommandLineParameters.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS, arguments);
84
85 // Clean up
86 UserGroupVerifier.DeleteLocalGroup("testName1");
87 }
88
89
90 // Verify that the groups specified in the authoring are created as expected on repair.
91 [RuntimeFact()]
92 public void CanRepairGroups()
93 {
94 UserGroupVerifier.CreateLocalGroup("testName3");
95 var productA = this.CreatePackageInstaller("ProductA");
96
97 productA.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
98
99 // Validate New User Information.
100 UserGroupVerifier.DeleteLocalGroup("testName1");
101
102 productA.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS);
103
104 // Validate New User Information.
105 Assert.True(UserGroupVerifier.GroupExists(String.Empty, "testName1"), String.Format("User '{0}' was not installed on Repair", "testName1"));
106 Assert.True(UserGroupVerifier.GroupExists(String.Empty, "testName2"), String.Format("User '{0}' was not installed after Repair", "testName2"));
107
108 productA.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
109
110 // Verify Users marked as RemoveOnUninstall were removed.
111 Assert.False(UserGroupVerifier.GroupExists(String.Empty, "testName1"), String.Format("User '{0}' was not removed on Uninstall", "testName1"));
112 Assert.True(UserGroupVerifier.GroupExists(String.Empty, "testName2"), String.Format("User '{0}' was removed on Uninstall", "testName2"));
113
114 // clean up
115 UserGroupVerifier.DeleteLocalGroup("testName1");
116 UserGroupVerifier.DeleteLocalGroup("testName2");
117 UserGroupVerifier.DeleteLocalGroup("testName3");
118 }
119
120 // Verify that Installation fails if FailIfExists is set.
121 [RuntimeFact]
122 public void FailsIfGroupExists()
123 {
124 var productFailIfExists = this.CreatePackageInstaller("ProductFailIfExists");
125
126 // Create 'existinggroup'
127 UserGroupVerifier.CreateLocalGroup("existinggroup");
128
129 try
130 {
131 productFailIfExists.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE);
132
133 // Verify User still exists.
134 bool userExists = UserGroupVerifier.GroupExists(String.Empty, "existinggroup");
135
136 Assert.True(userExists, String.Format("Group '{0}' was removed on Rollback", "existinggroup"));
137 }
138 finally
139 {
140 // clean up
141 UserGroupVerifier.DeleteLocalGroup("existinggroup");
142 }
143 }
144
145 // Verify that a group cannot be created on a domain on which you don't have create user permission.
146 [RuntimeFact]
147 public void FailsIfRestrictedDomain()
148 {
149 var productRestrictedDomain = this.CreatePackageInstaller("ProductRestrictedDomain");
150
151 string logFile = productRestrictedDomain.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE, "TEMPDOMAIN=DOESNOTEXIST");
152
153 // Verify expected error message in the log file
154 Assert.True(LogVerifier.MessageInLogFile(logFile, "CreateGroup: Error 0x8007054b: failed to find Domain DOESNOTEXIST."));
155 }
156
157 // Verify that a group can be created with a group comment
158 [RuntimeFact]
159 public void CanCreateNewGroupWithComment()
160 {
161 var productNewUserWithComment = this.CreatePackageInstaller("ProductNewGroupWithComment");
162
163 productNewUserWithComment.InstallProduct();
164 UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "testComment1");
165
166 // clean up
167 UserGroupVerifier.DeleteLocalGroup("testName1");
168 }
169
170 // Verify that a comment can be added to an existing group
171 [RuntimeFact]
172 public void CanAddCommentToExistingGroup()
173 {
174 UserGroupVerifier.CreateLocalGroup("testName1");
175 var productAddCommentToExistingUser = this.CreatePackageInstaller("ProductAddCommentToExistingGroup");
176
177 productAddCommentToExistingUser.InstallProduct();
178
179 UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "testComment1");
180
181 // clean up
182 UserGroupVerifier.DeleteLocalGroup("testName1");
183 }
184
185 // Verify that a comment can be repaired for a new group
186 [RuntimeFact]
187 public void CanRepairCommentOfNewGroup()
188 {
189 var productNewUserWithComment = this.CreatePackageInstaller("ProductNewGroupWithComment");
190
191 productNewUserWithComment.InstallProduct();
192 UserGroupVerifier.SetGroupComment(String.Empty, "testName1", "");
193
194 productNewUserWithComment.RepairProduct();
195 UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "testComment1");
196
197 // clean up
198 UserGroupVerifier.DeleteLocalGroup("testName1");
199 }
200
201 // Verify that a comment can be changed for an existing group
202 [RuntimeFact]
203 public void CanChangeCommentOfExistingGroup()
204 {
205 UserGroupVerifier.CreateLocalGroup("testName1");
206 UserGroupVerifier.SetGroupComment(String.Empty, "testName1", "initialTestComment1");
207 var productNewUserWithComment = this.CreatePackageInstaller("ProductNewGroupWithComment");
208
209 productNewUserWithComment.InstallProduct();
210 UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "testComment1");
211
212 // clean up
213 UserGroupVerifier.DeleteLocalGroup("testName1");
214 }
215
216 // Verify that a comment can be rolled back for an existing group
217 [RuntimeFact]
218 public void CanRollbackCommentOfExistingGroup()
219 {
220 UserGroupVerifier.CreateLocalGroup("testName1");
221 UserGroupVerifier.SetGroupComment(String.Empty, "testName1", "initialTestComment1");
222 var productCommentFail = this.CreatePackageInstaller("ProductCommentFail");
223
224 productCommentFail.InstallProduct(MSIExec.MSIExecReturnCode.ERROR_INSTALL_FAILURE);
225
226 // Verify that comment change was rolled back.
227 UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "initialTestComment1");
228
229 // clean up
230 UserGroupVerifier.DeleteLocalGroup("testName1");
231 }
232
233 // Verify that a comment can be deleted for an existing group
234 [RuntimeFact]
235 public void CanDeleteCommentOfExistingGroup()
236 {
237 UserGroupVerifier.CreateLocalGroup("testName1");
238 UserGroupVerifier.SetGroupComment(String.Empty, "testName1", "testComment1");
239 var productCommentDelete = this.CreatePackageInstaller("ProductCommentDelete");
240
241 productCommentDelete.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
242
243 // Verify that comment was removed.
244 UserGroupVerifier.VerifyGroupComment(String.Empty, "testName1", "");
245
246 // clean up
247 UserGroupVerifier.DeleteLocalGroup("testName1");
248 }
249
250 // Verify that a comment can be deleted for an existing group
251 [RuntimeFact]
252 public void CanNestGroups()
253 {
254 var productNestedGroups = this.CreatePackageInstaller("ProductNestedGroups");
255
256 productNestedGroups.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
257
258 // Verify group nested membership
259 UserGroupVerifier.VerifyIsMemberOf(String.Empty, "Administrators", new string[] { "testName1", "testName2" });
260 UserGroupVerifier.VerifyIsMemberOf(String.Empty, "Power Users", new string[] { "testName1" });
261
262 UserGroupVerifier.VerifyIsNotMemberOf(String.Empty, "Administrators", new string[] { "testName3" });
263 UserGroupVerifier.VerifyIsNotMemberOf(String.Empty, "Power Users", new string[] { "testName2", "testName3" });
264
265 // clean up
266 UserGroupVerifier.DeleteLocalGroup("testName1");
267 UserGroupVerifier.DeleteLocalGroup("testName2");
268 UserGroupVerifier.DeleteLocalGroup("testName3");
269 }
270 }
271}