aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchris_bednarski <Chris.Bednarski@minfos.com.au>2023-08-21 11:34:51 +1000
committerBob Arnson <github@bobs.org>2023-08-26 19:09:59 -0400
commite29b4aacf89ce060509b20a5f81df200f7d2fbc1 (patch)
tree974188334fce21c60396400075579060ce2df62d
parentb3deb46def7d303a79f4a5eba6a921e45d5f16ef (diff)
downloadwix-e29b4aacf89ce060509b20a5f81df200f7d2fbc1.tar.gz
wix-e29b4aacf89ce060509b20a5f81df200f7d2fbc1.tar.bz2
wix-e29b4aacf89ce060509b20a5f81df200f7d2fbc1.zip
remove windows xp specific code from firewall extension
-rw-r--r--src/ext/Firewall/ca/firewall.cpp442
-rw-r--r--src/test/burn/WixTestTools/Firewall/Verifier.cs8
-rw-r--r--src/test/msi/TestData/FirewallExtensionTests/IgnoreFailedFirewallRules/IgnoreFailedFirewallRules.wixproj13
-rw-r--r--src/test/msi/TestData/FirewallExtensionTests/IgnoreFailedFirewallRules/product.wxs35
-rw-r--r--src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs42
5 files changed, 102 insertions, 438 deletions
diff --git a/src/ext/Firewall/ca/firewall.cpp b/src/ext/Firewall/ca/firewall.cpp
index 35c8be6e..491b10fa 100644
--- a/src/ext/Firewall/ca/firewall.cpp
+++ b/src/ext/Firewall/ca/firewall.cpp
@@ -348,90 +348,6 @@ LExit:
348} 348}
349 349
350/****************************************************************** 350/******************************************************************
351 FSupportProfiles - Returns true if we support profiles on this machine.
352 (Only on Vista or later)
353
354********************************************************************/
355static BOOL FSupportProfiles()
356{
357 BOOL fSupportProfiles = FALSE;
358 INetFwRules* pNetFwRules = NULL;
359
360 // We only support profiles if we can co-create an instance of NetFwPolicy2.
361 // This will not work on pre-vista machines.
362 if (SUCCEEDED(GetFirewallRules(TRUE, &pNetFwRules)) && pNetFwRules != NULL)
363 {
364 fSupportProfiles = TRUE;
365 ReleaseObject(pNetFwRules);
366 }
367
368 return fSupportProfiles;
369}
370
371/******************************************************************
372 GetCurrentFirewallProfile - get the active firewall profile as an
373 INetFwProfile, which owns the lists of exceptions we're
374 updating.
375
376********************************************************************/
377static HRESULT GetCurrentFirewallProfile(
378 __in BOOL fIgnoreFailures,
379 __out INetFwProfile** ppfwProfile
380 )
381{
382 HRESULT hr = S_OK;
383 INetFwMgr* pfwMgr = NULL;
384 INetFwPolicy* pfwPolicy = NULL;
385 INetFwProfile* pfwProfile = NULL;
386 *ppfwProfile = NULL;
387
388 do
389 {
390 ReleaseNullObject(pfwPolicy);
391 ReleaseNullObject(pfwMgr);
392 ReleaseNullObject(pfwProfile);
393
394 if (SUCCEEDED(hr = ::CoCreateInstance(__uuidof(NetFwMgr), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwMgr), (void**)&pfwMgr)) &&
395 SUCCEEDED(hr = pfwMgr->get_LocalPolicy(&pfwPolicy)) &&
396 SUCCEEDED(hr = pfwPolicy->get_CurrentProfile(&pfwProfile)))
397 {
398 break;
399 }
400 else if (fIgnoreFailures)
401 {
402 ExitFunction1(hr = S_FALSE);
403 }
404 else
405 {
406 WcaLog(LOGMSG_STANDARD, "Failed to connect to Windows Firewall");
407 UINT er = WcaErrorMessage(msierrFirewallCannotConnect, hr, INSTALLMESSAGE_ERROR | MB_ABORTRETRYIGNORE, 0);
408 switch (er)
409 {
410 case IDABORT: // exit with the current HRESULT
411 ExitFunction();
412 case IDRETRY: // clean up and retry the loop
413 hr = S_FALSE;
414 break;
415 case IDIGNORE: // pass S_FALSE back to the caller, who knows how to ignore the failure
416 ExitFunction1(hr = S_FALSE);
417 default: // No UI, so default is to fail.
418 ExitFunction();
419 }
420 }
421 } while (S_FALSE == hr);
422
423 *ppfwProfile = pfwProfile;
424 pfwProfile = NULL;
425
426LExit:
427 ReleaseObject(pfwPolicy);
428 ReleaseObject(pfwMgr);
429 ReleaseObject(pfwProfile);
430
431 return hr;
432}
433
434/******************************************************************
435 AddApplicationException 351 AddApplicationException
436 352
437********************************************************************/ 353********************************************************************/
@@ -509,92 +425,6 @@ LExit:
509} 425}
510 426
511/****************************************************************** 427/******************************************************************
512 AddApplicationExceptionOnCurrentProfile
513
514********************************************************************/
515static HRESULT AddApplicationExceptionOnCurrentProfile(
516 __in LPCWSTR wzFile,
517 __in LPCWSTR wzName,
518 __in_opt LPCWSTR wzRemoteAddresses,
519 __in BOOL fIgnoreFailures
520 )
521{
522 HRESULT hr = S_OK;
523 BSTR bstrFile = NULL;
524 BSTR bstrName = NULL;
525 BSTR bstrRemoteAddresses = NULL;
526 INetFwProfile* pfwProfile = NULL;
527 INetFwAuthorizedApplications* pfwApps = NULL;
528 INetFwAuthorizedApplication* pfwApp = NULL;
529
530 // convert to BSTRs to make COM happy
531 bstrFile = ::SysAllocString(wzFile);
532 ExitOnNull(bstrFile, hr, E_OUTOFMEMORY, "failed SysAllocString for path");
533 bstrName = ::SysAllocString(wzName);
534 ExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString for name");
535 bstrRemoteAddresses = ::SysAllocString(wzRemoteAddresses);
536 ExitOnNull(bstrRemoteAddresses, hr, E_OUTOFMEMORY, "failed SysAllocString for remote addresses");
537
538 // get the firewall profile, which is our entry point for adding exceptions
539 hr = GetCurrentFirewallProfile(fIgnoreFailures, &pfwProfile);
540 ExitOnFailure(hr, "failed to get firewall profile");
541 if (S_FALSE == hr) // user or package author chose to ignore missing firewall
542 {
543 ExitFunction();
544 }
545
546 // first, let's see if the app is already on the exception list
547 hr = pfwProfile->get_AuthorizedApplications(&pfwApps);
548 ExitOnFailure(hr, "failed to get list of authorized apps");
549
550 // try to find it (i.e., support reinstall)
551 hr = pfwApps->Item(bstrFile, &pfwApp);
552 if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
553 {
554 // not found, so we get to add it
555 hr = ::CoCreateInstance(__uuidof(NetFwAuthorizedApplication), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwAuthorizedApplication), reinterpret_cast<void**>(&pfwApp));
556 ExitOnFailure(hr, "failed to create authorized app");
557
558 // set the display name
559 hr = pfwApp->put_Name(bstrName);
560 ExitOnFailure(hr, "failed to set authorized app name");
561
562 // set path
563 hr = pfwApp->put_ProcessImageFileName(bstrFile);
564 ExitOnFailure(hr, "failed to set authorized app path");
565
566 // set the allowed remote addresses
567 if (bstrRemoteAddresses && *bstrRemoteAddresses)
568 {
569 hr = pfwApp->put_RemoteAddresses(bstrRemoteAddresses);
570 ExitOnFailure(hr, "failed to set authorized app remote addresses");
571 }
572
573 // add it to the list of authorized apps
574 hr = pfwApps->Add(pfwApp);
575 ExitOnFailure(hr, "failed to add app to the authorized apps list");
576 }
577 else
578 {
579 // we found an existing app exception (if we succeeded, that is)
580 ExitOnFailure(hr, "failed trying to find existing app");
581
582 // enable it (just in case it was disabled)
583 pfwApp->put_Enabled(VARIANT_TRUE);
584 }
585
586LExit:
587 ReleaseBSTR(bstrRemoteAddresses);
588 ReleaseBSTR(bstrName);
589 ReleaseBSTR(bstrFile);
590 ReleaseObject(pfwApp);
591 ReleaseObject(pfwApps);
592 ReleaseObject(pfwProfile);
593
594 return fIgnoreFailures ? S_OK : hr;
595}
596
597/******************************************************************
598 AddPortException 428 AddPortException
599 429
600********************************************************************/ 430********************************************************************/
@@ -659,79 +489,11 @@ LExit:
659} 489}
660 490
661/****************************************************************** 491/******************************************************************
662 AddPortExceptionOnCurrentProfile 492 RemoveException - Removes all exception rules with the given name.
663
664********************************************************************/
665static HRESULT AddPortExceptionOnCurrentProfile(
666 __in LPCWSTR wzName,
667 __in_opt LPCWSTR wzRemoteAddresses,
668 __in BOOL fIgnoreFailures,
669 __in int iPort,
670 __in int iProtocol
671 )
672{
673 HRESULT hr = S_OK;
674 BSTR bstrName = NULL;
675 BSTR bstrRemoteAddresses = NULL;
676 INetFwProfile* pfwProfile = NULL;
677 INetFwOpenPorts* pfwPorts = NULL;
678 INetFwOpenPort* pfwPort = NULL;
679
680 // convert to BSTRs to make COM happy
681 bstrName = ::SysAllocString(wzName);
682 ExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString for name");
683 bstrRemoteAddresses = ::SysAllocString(wzRemoteAddresses);
684 ExitOnNull(bstrRemoteAddresses, hr, E_OUTOFMEMORY, "failed SysAllocString for remote addresses");
685
686 // create and initialize a new open port object
687 hr = ::CoCreateInstance(__uuidof(NetFwOpenPort), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwOpenPort), reinterpret_cast<void**>(&pfwPort));
688 ExitOnFailure(hr, "failed to create new open port");
689
690 hr = pfwPort->put_Port(iPort);
691 ExitOnFailure(hr, "failed to set exception port");
692
693 hr = pfwPort->put_Protocol(static_cast<NET_FW_IP_PROTOCOL>(iProtocol));
694 ExitOnFailure(hr, "failed to set exception protocol");
695
696 if (bstrRemoteAddresses && *bstrRemoteAddresses)
697 {
698 hr = pfwPort->put_RemoteAddresses(bstrRemoteAddresses);
699 ExitOnFailure(hr, "failed to set exception remote addresses '%ls'", bstrRemoteAddresses);
700 }
701
702 hr = pfwPort->put_Name(bstrName);
703 ExitOnFailure(hr, "failed to set exception name");
704
705 // get the firewall profile, its current list of open ports, and add ours
706 hr = GetCurrentFirewallProfile(fIgnoreFailures, &pfwProfile);
707 ExitOnFailure(hr, "failed to get firewall profile");
708 if (S_FALSE == hr) // user or package author chose to ignore missing firewall
709 {
710 ExitFunction();
711 }
712
713 hr = pfwProfile->get_GloballyOpenPorts(&pfwPorts);
714 ExitOnFailure(hr, "failed to get open ports");
715
716 hr = pfwPorts->Add(pfwPort);
717 ExitOnFailure(hr, "failed to add exception to global list");
718
719LExit:
720 ReleaseBSTR(bstrRemoteAddresses);
721 ReleaseBSTR(bstrName);
722 ReleaseObject(pfwProfile);
723 ReleaseObject(pfwPorts);
724 ReleaseObject(pfwPort);
725
726 return fIgnoreFailures ? S_OK : hr;
727}
728
729/******************************************************************
730 RemoveException - Removes the exception rule with the given name.
731 493
732********************************************************************/ 494********************************************************************/
733static HRESULT RemoveException( 495static HRESULT RemoveException(
734 __in LPCWSTR wzName, 496 __in LPCWSTR wzName,
735 __in BOOL fIgnoreFailures 497 __in BOOL fIgnoreFailures
736 ) 498 )
737{ 499{
@@ -751,7 +513,7 @@ static HRESULT RemoveException(
751 } 513 }
752 514
753 hr = pNetFwRules->Remove(bstrName); 515 hr = pNetFwRules->Remove(bstrName);
754 ExitOnFailure(hr, "failed to remove authorized app"); 516 ExitOnFailure(hr, "failed to remove firewall rule");
755 517
756LExit: 518LExit:
757 ReleaseBSTR(bstrName); 519 ReleaseBSTR(bstrName);
@@ -761,192 +523,6 @@ LExit:
761} 523}
762 524
763/****************************************************************** 525/******************************************************************
764 RemoveApplicationExceptionFromCurrentProfile
765
766********************************************************************/
767static HRESULT RemoveApplicationExceptionFromCurrentProfile(
768 __in LPCWSTR wzFile,
769 __in BOOL fIgnoreFailures
770 )
771{
772 HRESULT hr = S_OK;
773 INetFwProfile* pfwProfile = NULL;
774 INetFwAuthorizedApplications* pfwApps = NULL;
775
776 // convert to BSTRs to make COM happy
777 BSTR bstrFile = ::SysAllocString(wzFile);
778 ExitOnNull(bstrFile, hr, E_OUTOFMEMORY, "failed SysAllocString for path");
779
780 // get the firewall profile, which is our entry point for removing exceptions
781 hr = GetCurrentFirewallProfile(fIgnoreFailures, &pfwProfile);
782 ExitOnFailure(hr, "failed to get firewall profile");
783 if (S_FALSE == hr) // user or package author chose to ignore missing firewall
784 {
785 ExitFunction();
786 }
787
788 // now get the list of app exceptions and remove the one
789 hr = pfwProfile->get_AuthorizedApplications(&pfwApps);
790 ExitOnFailure(hr, "failed to get list of authorized apps");
791
792 hr = pfwApps->Remove(bstrFile);
793 ExitOnFailure(hr, "failed to remove authorized app");
794
795LExit:
796 ReleaseBSTR(bstrFile);
797 ReleaseObject(pfwApps);
798 ReleaseObject(pfwProfile);
799
800 return fIgnoreFailures ? S_OK : hr;
801}
802
803/******************************************************************
804 RemovePortExceptionFromCurrentProfile
805
806********************************************************************/
807static HRESULT RemovePortExceptionFromCurrentProfile(
808 __in int iPort,
809 __in int iProtocol,
810 __in BOOL fIgnoreFailures
811 )
812{
813 HRESULT hr = S_OK;
814 INetFwProfile* pfwProfile = NULL;
815 INetFwOpenPorts* pfwPorts = NULL;
816
817 // get the firewall profile, which is our entry point for adding exceptions
818 hr = GetCurrentFirewallProfile(fIgnoreFailures, &pfwProfile);
819 ExitOnFailure(hr, "failed to get firewall profile");
820 if (S_FALSE == hr) // user or package author chose to ignore missing firewall
821 {
822 ExitFunction();
823 }
824
825 hr = pfwProfile->get_GloballyOpenPorts(&pfwPorts);
826 ExitOnFailure(hr, "failed to get open ports");
827
828 hr = pfwPorts->Remove(iPort, static_cast<NET_FW_IP_PROTOCOL>(iProtocol));
829 ExitOnFailure(hr, "failed to remove open port %d, protocol %d", iPort, iProtocol);
830
831LExit:
832 return fIgnoreFailures ? S_OK : hr;
833}
834
835static HRESULT AddApplicationException(
836 __in BOOL fSupportProfiles,
837 __in LPCWSTR wzFile,
838 __in LPCWSTR wzName,
839 __in int iProfile,
840 __in_opt LPCWSTR wzRemoteAddresses,
841 __in BOOL fIgnoreFailures,
842 __in LPCWSTR wzPort,
843 __in int iProtocol,
844 __in LPCWSTR wzDescription,
845 __in int iDirection
846)
847{
848 HRESULT hr = S_OK;
849
850 if (fSupportProfiles)
851 {
852 hr = AddApplicationException(wzFile, wzName, iProfile, wzRemoteAddresses, fIgnoreFailures, wzPort, iProtocol, wzDescription, iDirection);
853 }
854 else
855 {
856 if (0 != *wzPort || MSI_NULL_INTEGER != iProtocol)
857 {
858 // NOTE: This is treated as an error rather than either creating a rule based on just the application (no port), or
859 // just the port because it is unclear what is the proper fall back. For example, suppose that you have code that
860 // runs in dllhost.exe. Clearly falling back to opening all of dllhost is wrong. Because the firewall is a security
861 // feature, it seems better to require the MSI author to indicate the behavior that they want.
862 WcaLog(LOGMSG_STANDARD, "FirewallExtension: Cannot add firewall rule '%ls', which defines both an application and a port or protocol. Such a rule requires Microsoft Windows Vista or later.", wzName);
863 return fIgnoreFailures ? S_OK : E_NOTIMPL;
864 }
865
866 hr = AddApplicationExceptionOnCurrentProfile(wzFile, wzName, wzRemoteAddresses, fIgnoreFailures);
867 }
868
869 return hr;
870}
871
872static HRESULT AddPortException(
873 __in BOOL fSupportProfiles,
874 __in LPCWSTR wzName,
875 __in int iProfile,
876 __in_opt LPCWSTR wzRemoteAddresses,
877 __in BOOL fIgnoreFailures,
878 __in LPCWSTR wzPort,
879 __in int iProtocol,
880 __in LPCWSTR wzDescription,
881 __in int iDirection
882)
883{
884 HRESULT hr = S_OK;
885
886 if (fSupportProfiles)
887 {
888 hr = AddPortException(wzName, iProfile, wzRemoteAddresses, fIgnoreFailures, wzPort, iProtocol, wzDescription, iDirection);
889 }
890 else
891 {
892 hr = AddPortExceptionOnCurrentProfile(wzName, wzRemoteAddresses, fIgnoreFailures, wcstol(wzPort, NULL, 10), iProtocol);
893 }
894
895 return hr;
896}
897
898static HRESULT RemoveApplicationException(
899 __in BOOL fSupportProfiles,
900 __in LPCWSTR wzName,
901 __in LPCWSTR wzFile,
902 __in BOOL fIgnoreFailures,
903 __in LPCWSTR wzPort,
904 __in int iProtocol
905 )
906{
907 HRESULT hr = S_OK;
908
909 if (fSupportProfiles)
910 {
911 hr = RemoveException(wzName, fIgnoreFailures);
912 }
913 else
914 {
915 if (0 != *wzPort || MSI_NULL_INTEGER != iProtocol)
916 {
917 WcaLog(LOGMSG_STANDARD, "FirewallExtension: Cannot remove firewall rule '%ls', which defines both an application and a port or protocol. Such a rule requires Microsoft Windows Vista or later.", wzName);
918 return S_OK;
919 }
920
921 hr = RemoveApplicationExceptionFromCurrentProfile(wzFile, fIgnoreFailures);
922 }
923
924 return hr;
925}
926
927static HRESULT RemovePortException(
928 __in BOOL fSupportProfiles,
929 __in LPCWSTR wzName,
930 __in LPCWSTR wzPort,
931 __in int iProtocol,
932 __in BOOL fIgnoreFailures
933 )
934{
935 HRESULT hr = S_OK;
936
937 if (fSupportProfiles)
938 {
939 hr = RemoveException(wzName, fIgnoreFailures);
940 }
941 else
942 {
943 hr = RemovePortExceptionFromCurrentProfile(wcstol(wzPort, NULL, 10), iProtocol, fIgnoreFailures);
944 }
945
946 return hr;
947}
948
949/******************************************************************
950 ExecFirewallExceptions - deferred custom action entry point to 526 ExecFirewallExceptions - deferred custom action entry point to
951 register and remove firewall exceptions. 527 register and remove firewall exceptions.
952 528
@@ -956,7 +532,6 @@ extern "C" UINT __stdcall ExecFirewallExceptions(
956 ) 532 )
957{ 533{
958 HRESULT hr = S_OK; 534 HRESULT hr = S_OK;
959 BOOL fSupportProfiles = FALSE;
960 LPWSTR pwz = NULL; 535 LPWSTR pwz = NULL;
961 LPWSTR pwzCustomActionData = NULL; 536 LPWSTR pwzCustomActionData = NULL;
962 int iTodo = WCA_TODO_UNKNOWN; 537 int iTodo = WCA_TODO_UNKNOWN;
@@ -982,9 +557,6 @@ extern "C" UINT __stdcall ExecFirewallExceptions(
982 hr = ::CoInitialize(NULL); 557 hr = ::CoInitialize(NULL);
983 ExitOnFailure(hr, "failed to initialize COM"); 558 ExitOnFailure(hr, "failed to initialize COM");
984 559
985 // Find out if we support profiles (only on Vista or later)
986 fSupportProfiles = FSupportProfiles();
987
988 // loop through all the passed in data 560 // loop through all the passed in data
989 pwz = pwzCustomActionData; 561 pwz = pwzCustomActionData;
990 while (pwz && *pwz) 562 while (pwz && *pwz)
@@ -1043,13 +615,13 @@ extern "C" UINT __stdcall ExecFirewallExceptions(
1043 case WCA_TODO_INSTALL: 615 case WCA_TODO_INSTALL:
1044 case WCA_TODO_REINSTALL: 616 case WCA_TODO_REINSTALL:
1045 WcaLog(LOGMSG_STANDARD, "Installing firewall exception2 %ls on port %ls, protocol %d", pwzName, pwzPort, iProtocol); 617 WcaLog(LOGMSG_STANDARD, "Installing firewall exception2 %ls on port %ls, protocol %d", pwzName, pwzPort, iProtocol);
1046 hr = AddPortException(fSupportProfiles, pwzName, iProfile, pwzRemoteAddresses, fIgnoreFailures, pwzPort, iProtocol, pwzDescription, iDirection); 618 hr = AddPortException(pwzName, iProfile, pwzRemoteAddresses, fIgnoreFailures, pwzPort, iProtocol, pwzDescription, iDirection);
1047 ExitOnFailure(hr, "failed to add/update port exception for name '%ls' on port %ls, protocol %d", pwzName, pwzPort, iProtocol); 619 ExitOnFailure(hr, "failed to add/update port exception for name '%ls' on port %ls, protocol %d", pwzName, pwzPort, iProtocol);
1048 break; 620 break;
1049 621
1050 case WCA_TODO_UNINSTALL: 622 case WCA_TODO_UNINSTALL:
1051 WcaLog(LOGMSG_STANDARD, "Uninstalling firewall exception2 %ls on port %ls, protocol %d", pwzName, pwzPort, iProtocol); 623 WcaLog(LOGMSG_STANDARD, "Uninstalling firewall exception2 %ls on port %ls, protocol %d", pwzName, pwzPort, iProtocol);
1052 hr = RemovePortException(fSupportProfiles, pwzName, pwzPort, iProtocol, fIgnoreFailures); 624 hr = RemoveException(pwzName, fIgnoreFailures);
1053 ExitOnFailure(hr, "failed to remove port exception for name '%ls' on port %ls, protocol %d", pwzName, pwzPort, iProtocol); 625 ExitOnFailure(hr, "failed to remove port exception for name '%ls' on port %ls, protocol %d", pwzName, pwzPort, iProtocol);
1054 break; 626 break;
1055 } 627 }
@@ -1061,13 +633,13 @@ extern "C" UINT __stdcall ExecFirewallExceptions(
1061 case WCA_TODO_INSTALL: 633 case WCA_TODO_INSTALL:
1062 case WCA_TODO_REINSTALL: 634 case WCA_TODO_REINSTALL:
1063 WcaLog(LOGMSG_STANDARD, "Installing firewall exception2 %ls (%ls)", pwzName, pwzFile); 635 WcaLog(LOGMSG_STANDARD, "Installing firewall exception2 %ls (%ls)", pwzName, pwzFile);
1064 hr = AddApplicationException(fSupportProfiles, pwzFile, pwzName, iProfile, pwzRemoteAddresses, fIgnoreFailures, pwzPort, iProtocol, pwzDescription, iDirection); 636 hr = AddApplicationException(pwzFile, pwzName, iProfile, pwzRemoteAddresses, fIgnoreFailures, pwzPort, iProtocol, pwzDescription, iDirection);
1065 ExitOnFailure(hr, "failed to add/update application exception for name '%ls', file '%ls'", pwzName, pwzFile); 637 ExitOnFailure(hr, "failed to add/update application exception for name '%ls', file '%ls'", pwzName, pwzFile);
1066 break; 638 break;
1067 639
1068 case WCA_TODO_UNINSTALL: 640 case WCA_TODO_UNINSTALL:
1069 WcaLog(LOGMSG_STANDARD, "Uninstalling firewall exception2 %ls (%ls)", pwzName, pwzFile); 641 WcaLog(LOGMSG_STANDARD, "Uninstalling firewall exception2 %ls (%ls)", pwzName, pwzFile);
1070 hr = RemoveApplicationException(fSupportProfiles, pwzName, pwzFile, fIgnoreFailures, pwzPort, iProtocol); 642 hr = RemoveException(pwzName, fIgnoreFailures);
1071 ExitOnFailure(hr, "failed to remove application exception for name '%ls', file '%ls'", pwzName, pwzFile); 643 ExitOnFailure(hr, "failed to remove application exception for name '%ls', file '%ls'", pwzName, pwzFile);
1072 break; 644 break;
1073 } 645 }
diff --git a/src/test/burn/WixTestTools/Firewall/Verifier.cs b/src/test/burn/WixTestTools/Firewall/Verifier.cs
index c1bf3219..2c273e7a 100644
--- a/src/test/burn/WixTestTools/Firewall/Verifier.cs
+++ b/src/test/burn/WixTestTools/Firewall/Verifier.cs
@@ -254,7 +254,13 @@ namespace WixTestTools.Firewall
254 rule.Enabled = false; 254 rule.Enabled = false;
255 } 255 }
256 256
257 public static void RemoveFirewallRulesByName(string name) 257 /// <summary>
258 /// Removes a firewall rule by name. If multiple rules with the same name exist, only one of them is removed.<br/>
259 /// This behavior is different from <b>netsh advfirewall firewall delete rule</b> where all matching rules are deleted if multiple matches are found.<br/>
260 /// The firewall rule name cannot be null or an empty string.
261 /// </summary>
262 /// <param name="name">Name of the firewall rule to be removed.</param>
263 public static void RemoveFirewallRuleByName(string name)
258 { 264 {
259 var rules = GetINetFwRules(); 265 var rules = GetINetFwRules();
260 rules.Remove(name); 266 rules.Remove(name);
diff --git a/src/test/msi/TestData/FirewallExtensionTests/IgnoreFailedFirewallRules/IgnoreFailedFirewallRules.wixproj b/src/test/msi/TestData/FirewallExtensionTests/IgnoreFailedFirewallRules/IgnoreFailedFirewallRules.wixproj
new file mode 100644
index 00000000..b1770b0f
--- /dev/null
+++ b/src/test/msi/TestData/FirewallExtensionTests/IgnoreFailedFirewallRules/IgnoreFailedFirewallRules.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>{4D188568-1CCF-4EEE-BC27-17C3DCC83E58}</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.Firewall.wixext" />
12 </ItemGroup>
13</Project> \ No newline at end of file
diff --git a/src/test/msi/TestData/FirewallExtensionTests/IgnoreFailedFirewallRules/product.wxs b/src/test/msi/TestData/FirewallExtensionTests/IgnoreFailedFirewallRules/product.wxs
new file mode 100644
index 00000000..d615f0c6
--- /dev/null
+++ b/src/test/msi/TestData/FirewallExtensionTests/IgnoreFailedFirewallRules/product.wxs
@@ -0,0 +1,35 @@
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<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:fw="http://wixtoolset.org/schemas/v4/wxs/firewall">
4 <Fragment>
5 <ComponentGroup Id="ProductComponents">
6 <ComponentRef Id="FirewallComponent3"/>
7 </ComponentGroup>
8
9 <Property Id="RULENAME" Secure="yes" />
10 <CustomAction Id="SetRuleNameDefaultValue" Property="RULENAME" Value="WiXToolset401 Test - 0008 removal" Execute="firstSequence" />
11
12 <InstallExecuteSequence>
13 <Custom Action="SetRuleNameDefaultValue" After="AppSearch" Condition="NOT NORULENAME" />
14 </InstallExecuteSequence>
15
16 </Fragment>
17
18 <Fragment>
19 <Component Id="FirewallComponent3" Guid="AA693149-B39C-4012-9DDE-92AB0CEA2386" Directory="INSTALLFOLDER" Transitive="yes">
20 <File Source="$(sys.SOURCEFILEPATH)" KeyPath="yes" >
21 <fw:FirewallException Id="FirewallException6"
22 Description="WiX Toolset firewall exception rule integration test | ignore failed app properties"
23 Name="WiXToolset401 Test - 0006 pipe" Scope="any" IgnoreFailure="yes" />
24 </File>
25 <fw:FirewallException Id="FirewallException7"
26 Description="WiX Toolset firewall exception rule integration test | ignore failed port properties"
27 Name="WiXToolset401 Test - 0007 pipe" Scope="any" Port="65123" IgnoreFailure="yes" />
28
29 <fw:FirewallException Id="FirewallException8"
30 Description="WiX Toolset firewall exception rule integration test - removal test"
31 Name="[RULENAME]" Scope="any" Port="52390" Program="test.exe"
32 IgnoreFailure="yes" />
33 </Component>
34 </Fragment>
35</Wix>
diff --git a/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs b/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs
index 3e605d7a..4106cd72 100644
--- a/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs
+++ b/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs
@@ -144,7 +144,7 @@ namespace WixToolsetTest.MsiE2E
144 var product = this.CreatePackageInstaller("FirewallRules"); 144 var product = this.CreatePackageInstaller("FirewallRules");
145 product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); 145 product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
146 146
147 Verifier.RemoveFirewallRulesByName("WiXToolset401 Test - 0002"); 147 Verifier.RemoveFirewallRuleByName("WiXToolset401 Test - 0002");
148 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0002")); 148 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0002"));
149 149
150 product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS); 150 product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS);
@@ -176,7 +176,7 @@ namespace WixToolsetTest.MsiE2E
176 var product = this.CreatePackageInstaller("FirewallRules"); 176 var product = this.CreatePackageInstaller("FirewallRules");
177 product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); 177 product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
178 178
179 Verifier.RemoveFirewallRulesByName("WiXToolset401 Test - 0001"); 179 Verifier.RemoveFirewallRuleByName("WiXToolset401 Test - 0001");
180 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0001")); 180 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0001"));
181 181
182 product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS); 182 product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS);
@@ -277,5 +277,43 @@ namespace WixToolsetTest.MsiE2E
277 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0004")); 277 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0004"));
278 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0005 - 9999")); 278 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0005 - 9999"));
279 } 279 }
280
281 [RuntimeFact]
282 public void SucceedWhenIgnoreOnFailureIsSet()
283 {
284 var product = this.CreatePackageInstaller("IgnoreFailedFirewallRules");
285 var log1 = product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS);
286
287 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0006 pipe"));
288 Assert.True(LogVerifier.MessageInLogFile(log1, "failed to add app to the authorized apps list"));
289
290 Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0007 pipe"));
291 Assert.True(LogVerifier.MessageInLogFile(log1, "failed to add app to the authorized ports list"));
292
293 var expected = new RuleDetails("WiXToolset401 Test - 0008 removal")
294 {
295 Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW,
296 ApplicationName = "test.exe",
297 Description = "WiX Toolset firewall exception rule integration test - removal test",
298 Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN,
299 EdgeTraversal = true,
300 EdgeTraversalOptions = 1,
301 Enabled = true,
302 InterfaceTypes = "All",
303 LocalPorts = "52390",
304 LocalAddresses = "*",
305 Profiles = Int32.MaxValue,
306 Protocol = 6,
307 RemoteAddresses = "*",
308 RemotePorts = "*",
309 SecureFlags = 0,
310 };
311
312 Verifier.VerifyFirewallRule("WiXToolset401 Test - 0008 removal", expected);
313 Verifier.RemoveFirewallRuleByName("WiXToolset401 Test - 0008 removal");
314
315 var log2 = product.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS, "NORULENAME=1");
316 Assert.True(LogVerifier.MessageInLogFile(log2, "failed to remove firewall rule"));
317 }
280 } 318 }
281} 319}