From 97b80191048b23f2e870c9d6a27e368ebaaf594d Mon Sep 17 00:00:00 2001
From: Rob Mensching <rob@firegiant.com>
Date: Tue, 2 Jan 2018 23:14:58 -0800
Subject: Initial code commit

---
 src/ca/CustomMsiErrors.h |  130 ++++++
 src/ca/cost.h            |    5 +
 src/ca/dllmain.cpp       |   26 ++
 src/ca/firewall.cpp      | 1067 ++++++++++++++++++++++++++++++++++++++++++++++
 src/ca/fwca.def          |    9 +
 src/ca/fwca.vcxproj      |   72 ++++
 src/ca/packages.config   |    6 +
 src/ca/precomp.h         |   17 +
 8 files changed, 1332 insertions(+)
 create mode 100644 src/ca/CustomMsiErrors.h
 create mode 100644 src/ca/cost.h
 create mode 100644 src/ca/dllmain.cpp
 create mode 100644 src/ca/firewall.cpp
 create mode 100644 src/ca/fwca.def
 create mode 100644 src/ca/fwca.vcxproj
 create mode 100644 src/ca/packages.config
 create mode 100644 src/ca/precomp.h

(limited to 'src/ca')

diff --git a/src/ca/CustomMsiErrors.h b/src/ca/CustomMsiErrors.h
new file mode 100644
index 00000000..f149fb31
--- /dev/null
+++ b/src/ca/CustomMsiErrors.h
@@ -0,0 +1,130 @@
+#pragma once
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+
+#define GLOBAL_ERROR_BASE                 25501
+
+#define msierrSecureObjectsFailedCreateSD    25520
+#define msierrSecureObjectsFailedSet         25521
+#define msierrSecureObjectsUnknownType       25522
+
+#define msierrXmlFileFailedRead         25530
+#define msierrXmlFileFailedOpen         25531
+#define msierrXmlFileFailedSelect       25532
+#define msierrXmlFileFailedSave         25533
+
+#define msierrXmlConfigFailedRead         25540
+#define msierrXmlConfigFailedOpen         25541
+#define msierrXmlConfigFailedSelect       25542
+#define msierrXmlConfigFailedSave         25543
+
+#define msierrFirewallCannotConnect       25580
+
+//---------------------------------------------------------------------------
+// Server CustomAction Errors
+// SERVER range: 26001-26100
+#define SERVER_ERROR_BASE                      26000
+
+#define msierrIISCannotConnect                 26001
+#define msierrIISFailedReadWebSite             26002
+#define msierrIISFailedReadWebDirs             26003
+#define msierrIISFailedReadVDirs               26004
+#define msierrIISFailedReadFilters             26005
+#define msierrIISFailedReadAppPool             26006
+#define msierrIISFailedReadMimeMap             26007
+#define msierrIISFailedReadProp                26008
+#define msierrIISFailedReadWebSvcExt           26009
+#define msierrIISFailedReadWebError            26010
+#define msierrIISFailedReadHttpHeader          26011
+
+#define msierrIISFailedSchedTransaction        26031
+#define msierrIISFailedSchedInstallWebs        26032
+#define msierrIISFailedSchedInstallWebDirs     26033
+#define msierrIISFailedSchedInstallVDirs       26034
+#define msierrIISFailedSchedInstallFilters     26035
+#define msierrIISFailedSchedInstallAppPool     26036
+#define msierrIISFailedSchedInstallProp        26037
+#define msierrIISFailedSchedInstallWebSvcExt   26038
+
+#define msierrIISFailedSchedUninstallWebs      26051
+#define msierrIISFailedSchedUninstallWebDirs   26052
+#define msierrIISFailedSchedUninstallVDirs     26053
+#define msierrIISFailedSchedUninstallFilters   26054
+#define msierrIISFailedSchedUninstallAppPool   26055
+#define msierrIISFailedSchedUninstallProp      26056
+#define msierrIISFailedSchedUninstallWebSvcExt 26057
+
+#define msierrIISFailedStartTransaction        26101
+#define msierrIISFailedOpenKey                 26102
+#define msierrIISFailedCreateKey               26103
+#define msierrIISFailedWriteData               26104
+#define msierrIISFailedCreateApp               26105
+#define msierrIISFailedDeleteKey               26106
+#define msierrIISFailedDeleteApp               26107
+#define msierrIISFailedDeleteValue             26108
+#define msierrIISFailedCommitInUse             26109
+
+#define msierrSQLFailedCreateDatabase          26201
+#define msierrSQLFailedDropDatabase            26202
+#define msierrSQLFailedConnectDatabase         26203
+#define msierrSQLFailedExecString              26204
+#define msierrSQLDatabaseAlreadyExists         26205
+
+#define msierrPERFMONFailedRegisterDLL         26251
+#define msierrPERFMONFailedUnregisterDLL       26252
+#define msierrInstallPerfCounterData           26253
+#define msierrUninstallPerfCounterData         26254
+
+#define msierrSMBFailedCreate                  26301
+#define msierrSMBFailedDrop                    26302
+
+#define msierrCERTFailedOpen                   26351
+#define msierrCERTFailedAdd                    26352
+
+#define msierrUSRFailedUserCreate              26401
+#define msierrUSRFailedUserCreatePswd          26402
+#define msierrUSRFailedUserGroupAdd            26403
+#define msierrUSRFailedUserCreateExists        26404
+#define msierrUSRFailedGrantLogonAsService     26405
+
+#define msierrDependencyMissingDependencies    26451
+#define msierrDependencyHasDependents          26452
+
+//--------------------------------------------------------------------------
+// Managed code CustomAction Errors
+// MANAGED range: 27000-27100
+#define MANAGED_ERROR_BASE                     27000
+
+#define msierrDotNetRuntimeRequired            27000
+//---------------------------------------------------------------------------
+// Public CustomAction Errors
+// PUBLIC range: 28001-28100
+#define PUBLIC_ERROR_BASE                            28000
+
+#define msierrComPlusCannotConnect                   28001
+#define msierrComPlusPartitionReadFailed             28002
+#define msierrComPlusPartitionRoleReadFailed         28003
+#define msierrComPlusUserInPartitionRoleReadFailed   28004
+#define msierrComPlusPartitionUserReadFailed         28005
+#define msierrComPlusApplicationReadFailed           28006
+#define msierrComPlusApplicationRoleReadFailed       28007
+#define msierrComPlusUserInApplicationRoleReadFailed 28008
+#define msierrComPlusAssembliesReadFailed            28009
+#define msierrComPlusSubscriptionReadFailed          28010
+#define msierrComPlusPartitionDependency             28011
+#define msierrComPlusPartitionNotFound               28012
+#define msierrComPlusPartitionIdConflict             28013
+#define msierrComPlusPartitionNameConflict           28014
+#define msierrComPlusApplicationDependency           28015
+#define msierrComPlusApplicationNotFound             28016
+#define msierrComPlusApplicationIdConflict           28017
+#define msierrComPlusApplicationNameConflict         28018
+#define msierrComPlusApplicationRoleDependency       28019
+#define msierrComPlusApplicationRoleNotFound         28020
+#define msierrComPlusApplicationRoleConflict         28021
+#define msierrComPlusAssemblyDependency              28022
+#define msierrComPlusSubscriptionIdConflict          28023
+#define msierrComPlusSubscriptionNameConflict        28024
+#define msierrComPlusFailedLookupNames               28025
+
+#define msierrMsmqCannotConnect                      28101
diff --git a/src/ca/cost.h b/src/ca/cost.h
new file mode 100644
index 00000000..da68c667
--- /dev/null
+++ b/src/ca/cost.h
@@ -0,0 +1,5 @@
+#pragma once
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+
+const UINT COST_FIREWALL_EXCEPTION = 2000;
diff --git a/src/ca/dllmain.cpp b/src/ca/dllmain.cpp
new file mode 100644
index 00000000..df53f872
--- /dev/null
+++ b/src/ca/dllmain.cpp
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+
+/********************************************************************
+DllMain - standard entry point for all WiX CustomActions
+
+********************************************************************/
+extern "C" BOOL WINAPI DllMain(
+    IN HINSTANCE hInst,
+    IN ULONG ulReason,
+    IN LPVOID)
+{
+    switch(ulReason)
+    {
+    case DLL_PROCESS_ATTACH:
+        WcaGlobalInitialize(hInst);
+        break;
+
+    case DLL_PROCESS_DETACH:
+        WcaGlobalFinalize();
+        break;
+    }
+
+    return TRUE;
+}
diff --git a/src/ca/firewall.cpp b/src/ca/firewall.cpp
new file mode 100644
index 00000000..62a5b454
--- /dev/null
+++ b/src/ca/firewall.cpp
@@ -0,0 +1,1067 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+#include "precomp.h"
+
+LPCWSTR vcsFirewallExceptionQuery =
+    L"SELECT `Name`, `RemoteAddresses`, `Port`, `Protocol`, `Program`, `Attributes`, `Profile`, `Component_`, `Description` FROM `WixFirewallException`";
+enum eFirewallExceptionQuery { feqName = 1, feqRemoteAddresses, feqPort, feqProtocol, feqProgram, feqAttributes, feqProfile, feqComponent, feqDescription };
+enum eFirewallExceptionTarget { fetPort = 1, fetApplication, fetUnknown };
+enum eFirewallExceptionAttributes { feaIgnoreFailures = 1 };
+
+/******************************************************************
+ SchedFirewallExceptions - immediate custom action worker to 
+   register and remove firewall exceptions.
+
+********************************************************************/
+static UINT SchedFirewallExceptions(
+    __in MSIHANDLE hInstall,
+    WCA_TODO todoSched
+    )
+{
+    HRESULT hr = S_OK;
+    UINT er = ERROR_SUCCESS;
+    int cFirewallExceptions = 0;
+
+    PMSIHANDLE hView = NULL;
+    PMSIHANDLE hRec = NULL;
+
+    LPWSTR pwzCustomActionData = NULL;
+    LPWSTR pwzName = NULL;
+    LPWSTR pwzRemoteAddresses = NULL;
+    LPWSTR pwzPort = NULL;
+    int iProtocol = 0;
+    int iAttributes = 0;
+    int iProfile = 0;
+    LPWSTR pwzProgram = NULL;
+    LPWSTR pwzComponent = NULL;
+    LPWSTR pwzFormattedFile = NULL;
+    LPWSTR pwzDescription = NULL;
+
+    // initialize
+    hr = WcaInitialize(hInstall, "SchedFirewallExceptions");
+    ExitOnFailure(hr, "failed to initialize");
+
+    // anything to do?
+    if (S_OK != WcaTableExists(L"WixFirewallException"))
+    {
+        WcaLog(LOGMSG_STANDARD, "WixFirewallException table doesn't exist, so there are no firewall exceptions to configure.");
+        ExitFunction();
+    }
+
+    // query and loop through all the firewall exceptions
+    hr = WcaOpenExecuteView(vcsFirewallExceptionQuery, &hView);
+    ExitOnFailure(hr, "failed to open view on WixFirewallException table");
+
+    while (S_OK == (hr = WcaFetchRecord(hView, &hRec)))
+    {
+        hr = WcaGetRecordFormattedString(hRec, feqName, &pwzName);
+        ExitOnFailure(hr, "failed to get firewall exception name");
+
+        hr = WcaGetRecordFormattedString(hRec, feqRemoteAddresses, &pwzRemoteAddresses);
+        ExitOnFailure(hr, "failed to get firewall exception remote addresses");
+
+        hr = WcaGetRecordFormattedString(hRec, feqPort, &pwzPort);
+        ExitOnFailure(hr, "failed to get firewall exception port");
+
+        hr = WcaGetRecordInteger(hRec, feqProtocol, &iProtocol);
+        ExitOnFailure(hr, "failed to get firewall exception protocol");
+
+        hr = WcaGetRecordFormattedString(hRec, feqProgram, &pwzProgram);
+        ExitOnFailure(hr, "failed to get firewall exception program");
+
+        hr = WcaGetRecordInteger(hRec, feqAttributes, &iAttributes);
+        ExitOnFailure(hr, "failed to get firewall exception attributes");
+        
+        hr = WcaGetRecordInteger(hRec, feqProfile, &iProfile);
+        ExitOnFailure(hr, "failed to get firewall exception profile");
+
+        hr = WcaGetRecordString(hRec, feqComponent, &pwzComponent);
+        ExitOnFailure(hr, "failed to get firewall exception component");
+
+        hr = WcaGetRecordString(hRec, feqDescription, &pwzDescription);
+        ExitOnFailure(hr, "failed to get firewall description");
+
+        // figure out what we're doing for this exception, treating reinstall the same as install
+        WCA_TODO todoComponent = WcaGetComponentToDo(pwzComponent);
+        if ((WCA_TODO_REINSTALL == todoComponent ? WCA_TODO_INSTALL : todoComponent) != todoSched)
+        {
+            WcaLog(LOGMSG_STANDARD, "Component '%ls' action state (%d) doesn't match request (%d)", pwzComponent, todoComponent, todoSched);
+            continue;
+        }
+
+        // action :: name :: profile :: remoteaddresses :: attributes :: target :: {port::protocol | path}
+        ++cFirewallExceptions;
+        hr = WcaWriteIntegerToCaData(todoComponent, &pwzCustomActionData);
+        ExitOnFailure(hr, "failed to write exception action to custom action data");
+
+        hr = WcaWriteStringToCaData(pwzName, &pwzCustomActionData);
+        ExitOnFailure(hr, "failed to write exception name to custom action data");
+
+        hr = WcaWriteIntegerToCaData(iProfile, &pwzCustomActionData);
+        ExitOnFailure(hr, "failed to write exception profile to custom action data");
+
+        hr = WcaWriteStringToCaData(pwzRemoteAddresses, &pwzCustomActionData);
+        ExitOnFailure(hr, "failed to write exception remote addresses to custom action data");
+
+        hr = WcaWriteIntegerToCaData(iAttributes, &pwzCustomActionData);
+        ExitOnFailure(hr, "failed to write exception attributes to custom action data");
+
+        if (*pwzProgram)
+        {
+            // If program is defined, we have an application exception.
+            hr = WcaWriteIntegerToCaData(fetApplication, &pwzCustomActionData);
+            ExitOnFailure(hr, "failed to write exception target (application) to custom action data");
+
+            hr = WcaWriteStringToCaData(pwzProgram, &pwzCustomActionData);
+            ExitOnFailure(hr, "failed to write application path to custom action data");
+        }
+        else
+        {
+            // we have a port-only exception
+            hr = WcaWriteIntegerToCaData(fetPort, &pwzCustomActionData);
+            ExitOnFailure(hr, "failed to write exception target (port) to custom action data");
+        }
+
+        hr = WcaWriteStringToCaData(pwzPort, &pwzCustomActionData);
+        ExitOnFailure(hr, "failed to write application path to custom action data");
+
+        hr = WcaWriteIntegerToCaData(iProtocol, &pwzCustomActionData);
+        ExitOnFailure(hr, "failed to write exception protocol to custom action data");
+
+        hr = WcaWriteStringToCaData(pwzDescription, &pwzCustomActionData);
+        ExitOnFailure(hr, "failed to write firewall rule description to custom action data");
+    }
+
+    // reaching the end of the list is actually a good thing, not an error
+    if (E_NOMOREITEMS == hr)
+    {
+        hr = S_OK;
+    } 
+    ExitOnFailure(hr, "failure occured while processing WixFirewallException table");
+
+    // schedule ExecFirewallExceptions if there's anything to do
+    if (pwzCustomActionData && *pwzCustomActionData)
+    {
+        WcaLog(LOGMSG_STANDARD, "Scheduling firewall exception (%ls)", pwzCustomActionData);
+
+        if (WCA_TODO_INSTALL == todoSched)
+        {
+            hr = WcaDoDeferredAction(L"WixRollbackFirewallExceptionsInstall", pwzCustomActionData, cFirewallExceptions * COST_FIREWALL_EXCEPTION);
+            ExitOnFailure(hr, "failed to schedule firewall install exceptions rollback");            
+            hr = WcaDoDeferredAction(L"WixExecFirewallExceptionsInstall", pwzCustomActionData, cFirewallExceptions * COST_FIREWALL_EXCEPTION);
+            ExitOnFailure(hr, "failed to schedule firewall install exceptions execution");
+        }
+        else
+        {
+            hr = WcaDoDeferredAction(L"WixRollbackFirewallExceptionsUninstall", pwzCustomActionData, cFirewallExceptions * COST_FIREWALL_EXCEPTION);
+            ExitOnFailure(hr, "failed to schedule firewall uninstall exceptions rollback");    
+            hr = WcaDoDeferredAction(L"WixExecFirewallExceptionsUninstall", pwzCustomActionData, cFirewallExceptions * COST_FIREWALL_EXCEPTION);
+            ExitOnFailure(hr, "failed to schedule firewall uninstall exceptions execution");
+        }
+    }
+    else
+    {
+        WcaLog(LOGMSG_STANDARD, "No firewall exceptions scheduled");
+    }
+
+LExit:
+    ReleaseStr(pwzCustomActionData);
+    ReleaseStr(pwzName);
+    ReleaseStr(pwzRemoteAddresses);
+    ReleaseStr(pwzPort);
+    ReleaseStr(pwzProgram);
+    ReleaseStr(pwzComponent);
+    ReleaseStr(pwzDescription);
+    ReleaseStr(pwzFormattedFile);
+
+    return WcaFinalize(er = FAILED(hr) ? ERROR_INSTALL_FAILURE : er);
+}
+
+/******************************************************************
+ SchedFirewallExceptionsInstall - immediate custom action entry
+   point to register firewall exceptions.
+
+********************************************************************/
+extern "C" UINT __stdcall SchedFirewallExceptionsInstall(
+    __in MSIHANDLE hInstall
+    )
+{
+    return SchedFirewallExceptions(hInstall, WCA_TODO_INSTALL);
+}
+
+/******************************************************************
+ SchedFirewallExceptionsUninstall - immediate custom action entry
+   point to remove firewall exceptions.
+
+********************************************************************/
+extern "C" UINT __stdcall SchedFirewallExceptionsUninstall(
+    __in MSIHANDLE hInstall
+    )
+{
+    return SchedFirewallExceptions(hInstall, WCA_TODO_UNINSTALL);
+}
+
+/******************************************************************
+ GetFirewallRules - Get the collection of firewall rules.
+
+********************************************************************/
+static HRESULT GetFirewallRules(
+    __in BOOL fIgnoreFailures,
+    __out INetFwRules** ppNetFwRules
+    )
+{
+    HRESULT hr = S_OK;
+    INetFwPolicy2* pNetFwPolicy2 = NULL;
+    INetFwRules* pNetFwRules = NULL;
+    *ppNetFwRules = NULL;
+    
+    do
+    {
+        ReleaseNullObject(pNetFwPolicy2);
+        ReleaseNullObject(pNetFwRules);
+
+        if (SUCCEEDED(hr = ::CoCreateInstance(__uuidof(NetFwPolicy2), NULL, CLSCTX_ALL, __uuidof(INetFwPolicy2), (void**)&pNetFwPolicy2)) &&
+            SUCCEEDED(hr = pNetFwPolicy2->get_Rules(&pNetFwRules)))
+        {
+            break;
+        }
+        else if (fIgnoreFailures)
+        {
+            ExitFunction1(hr = S_FALSE);
+        }
+        else
+        {
+            WcaLog(LOGMSG_STANDARD, "Failed to connect to Windows Firewall");
+            UINT er = WcaErrorMessage(msierrFirewallCannotConnect, hr, INSTALLMESSAGE_ERROR | MB_ABORTRETRYIGNORE, 0);
+            switch (er)
+            {
+            case IDABORT: // exit with the current HRESULT
+                ExitFunction();
+            case IDRETRY: // clean up and retry the loop
+                hr = S_FALSE;
+                break;
+            case IDIGNORE: // pass S_FALSE back to the caller, who knows how to ignore the failure
+                ExitFunction1(hr = S_FALSE);
+            default: // No UI, so default is to fail.
+                ExitFunction();
+            }
+        }
+    } while (S_FALSE == hr);
+
+    *ppNetFwRules = pNetFwRules;
+    pNetFwRules = NULL;
+    
+LExit:
+    ReleaseObject(pNetFwPolicy2);
+    ReleaseObject(pNetFwRules);
+
+    return hr;
+}
+
+/******************************************************************
+ CreateFwRuleObject - CoCreate a firewall rule, and set the common set of properties which are shared
+ between port and application firewall rules
+
+********************************************************************/
+static HRESULT CreateFwRuleObject(
+    __in BSTR bstrName,
+    __in int iProfile,
+    __in_opt LPCWSTR wzRemoteAddresses,
+    __in LPCWSTR wzPort,
+    __in int iProtocol,
+    __in LPCWSTR wzDescription,
+    __out INetFwRule** ppNetFwRule
+    )
+{
+    HRESULT hr = S_OK;
+    BSTR bstrRemoteAddresses = NULL;
+    BSTR bstrPort = NULL;
+    BSTR bstrDescription = NULL;
+    INetFwRule* pNetFwRule = NULL;
+    *ppNetFwRule = NULL;
+
+    // convert to BSTRs to make COM happy
+    bstrRemoteAddresses = ::SysAllocString(wzRemoteAddresses);
+    ExitOnNull(bstrRemoteAddresses, hr, E_OUTOFMEMORY, "failed SysAllocString for remote addresses");
+    bstrPort = ::SysAllocString(wzPort);
+    ExitOnNull(bstrPort, hr, E_OUTOFMEMORY, "failed SysAllocString for port");
+    bstrDescription = ::SysAllocString(wzDescription);
+    ExitOnNull(bstrDescription, hr, E_OUTOFMEMORY, "failed SysAllocString for description");
+
+    hr = ::CoCreateInstance(__uuidof(NetFwRule), NULL, CLSCTX_ALL, __uuidof(INetFwRule), (void**)&pNetFwRule);
+    ExitOnFailure(hr, "failed to create NetFwRule object");
+
+    hr = pNetFwRule->put_Name(bstrName);
+    ExitOnFailure(hr, "failed to set exception name");
+
+    hr = pNetFwRule->put_Profiles(static_cast<NET_FW_PROFILE_TYPE2>(iProfile));
+    ExitOnFailure(hr, "failed to set exception profile");
+
+    if (MSI_NULL_INTEGER != iProtocol)
+    {
+        hr = pNetFwRule->put_Protocol(static_cast<NET_FW_IP_PROTOCOL>(iProtocol));
+        ExitOnFailure(hr, "failed to set exception protocol");
+    }
+
+    if (bstrPort && *bstrPort)
+    {
+        hr = pNetFwRule->put_LocalPorts(bstrPort);
+        ExitOnFailure(hr, "failed to set exception port");
+    }
+
+    if (bstrRemoteAddresses && *bstrRemoteAddresses)
+    {
+        hr = pNetFwRule->put_RemoteAddresses(bstrRemoteAddresses);
+        ExitOnFailure(hr, "failed to set exception remote addresses '%ls'", bstrRemoteAddresses);
+    }
+
+    if (bstrDescription && *bstrDescription)
+    {
+        hr = pNetFwRule->put_Description(bstrDescription);
+        ExitOnFailure(hr, "failed to set exception description '%ls'", bstrDescription);
+    }
+
+    *ppNetFwRule = pNetFwRule;
+    pNetFwRule = NULL;
+
+LExit:
+    ReleaseBSTR(bstrRemoteAddresses);
+    ReleaseBSTR(bstrPort);
+    ReleaseBSTR(bstrDescription);
+    ReleaseObject(pNetFwRule);
+
+    return hr;
+}
+
+/******************************************************************
+ FSupportProfiles - Returns true if we support profiles on this machine.
+  (Only on Vista or later)
+
+********************************************************************/
+static BOOL FSupportProfiles()
+{
+    BOOL fSupportProfiles = FALSE;
+    INetFwRules* pNetFwRules = NULL;
+
+    // We only support profiles if we can co-create an instance of NetFwPolicy2. 
+    // This will not work on pre-vista machines.
+    if (SUCCEEDED(GetFirewallRules(TRUE, &pNetFwRules)) &&  pNetFwRules != NULL)
+    {
+        fSupportProfiles = TRUE;
+        ReleaseObject(pNetFwRules);
+    }
+
+    return fSupportProfiles;
+}
+
+/******************************************************************
+ GetCurrentFirewallProfile - get the active firewall profile as an
+   INetFwProfile, which owns the lists of exceptions we're 
+   updating.
+
+********************************************************************/
+static HRESULT GetCurrentFirewallProfile(
+    __in BOOL fIgnoreFailures,
+    __out INetFwProfile** ppfwProfile
+    )
+{
+    HRESULT hr = S_OK;
+    INetFwMgr* pfwMgr = NULL;
+    INetFwPolicy* pfwPolicy = NULL;
+    INetFwProfile* pfwProfile = NULL;
+    *ppfwProfile = NULL;
+    
+    do
+    {
+        ReleaseNullObject(pfwPolicy);
+        ReleaseNullObject(pfwMgr);
+        ReleaseNullObject(pfwProfile);
+
+        if (SUCCEEDED(hr = ::CoCreateInstance(__uuidof(NetFwMgr), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwMgr), (void**)&pfwMgr)) &&
+            SUCCEEDED(hr = pfwMgr->get_LocalPolicy(&pfwPolicy)) &&
+            SUCCEEDED(hr = pfwPolicy->get_CurrentProfile(&pfwProfile)))
+        {
+            break;
+        }
+        else if (fIgnoreFailures)
+        {
+            ExitFunction1(hr = S_FALSE);
+        }
+        else
+        {
+            WcaLog(LOGMSG_STANDARD, "Failed to connect to Windows Firewall");
+            UINT er = WcaErrorMessage(msierrFirewallCannotConnect, hr, INSTALLMESSAGE_ERROR | MB_ABORTRETRYIGNORE, 0);
+            switch (er)
+            {
+            case IDABORT: // exit with the current HRESULT
+                ExitFunction();
+            case IDRETRY: // clean up and retry the loop
+                hr = S_FALSE;
+                break;
+            case IDIGNORE: // pass S_FALSE back to the caller, who knows how to ignore the failure
+                ExitFunction1(hr = S_FALSE);
+            default: // No UI, so default is to fail.
+                ExitFunction();
+            }
+        }
+    } while (S_FALSE == hr);
+
+    *ppfwProfile = pfwProfile;
+    pfwProfile = NULL;
+    
+LExit:
+    ReleaseObject(pfwPolicy);
+    ReleaseObject(pfwMgr);
+    ReleaseObject(pfwProfile);
+
+    return hr;
+}
+
+/******************************************************************
+ AddApplicationException
+
+********************************************************************/
+static HRESULT AddApplicationException(
+    __in LPCWSTR wzFile, 
+    __in LPCWSTR wzName,
+    __in int iProfile,
+    __in_opt LPCWSTR wzRemoteAddresses,
+    __in BOOL fIgnoreFailures,
+    __in LPCWSTR wzPort,
+    __in int iProtocol,
+    __in LPCWSTR wzDescription
+    )
+{
+    HRESULT hr = S_OK;
+    BSTR bstrFile = NULL;
+    BSTR bstrName = NULL;
+    INetFwRules* pNetFwRules = NULL;
+    INetFwRule* pNetFwRule = NULL;
+
+    // convert to BSTRs to make COM happy
+    bstrFile = ::SysAllocString(wzFile);
+    ExitOnNull(bstrFile, hr, E_OUTOFMEMORY, "failed SysAllocString for path");
+    bstrName = ::SysAllocString(wzName);
+    ExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString for name");
+
+    // get the collection of firewall rules
+    hr = GetFirewallRules(fIgnoreFailures, &pNetFwRules);
+    ExitOnFailure(hr, "failed to get firewall rules object");
+    if (S_FALSE == hr) // user or package author chose to ignore missing firewall
+    {
+        ExitFunction();
+    }
+
+    // try to find it (i.e., support reinstall)
+    hr = pNetFwRules->Item(bstrName, &pNetFwRule);
+    if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
+    {
+        hr = CreateFwRuleObject(bstrName, iProfile, wzRemoteAddresses, wzPort, iProtocol, wzDescription, &pNetFwRule);
+        ExitOnFailure(hr, "failed to create FwRule object");
+
+        // set edge traversal to true
+        hr = pNetFwRule->put_EdgeTraversal(VARIANT_TRUE);
+        ExitOnFailure(hr, "failed to set application exception edgetraversal property");
+        
+        // set path
+        hr = pNetFwRule->put_ApplicationName(bstrFile);
+        ExitOnFailure(hr, "failed to set application name");
+        
+        // enable it
+        hr = pNetFwRule->put_Enabled(VARIANT_TRUE);
+        ExitOnFailure(hr, "failed to to enable application exception");
+     
+        // add it to the list of authorized apps
+        hr = pNetFwRules->Add(pNetFwRule);
+        ExitOnFailure(hr, "failed to add app to the authorized apps list");
+    }
+    else
+    {
+        // we found an existing app exception (if we succeeded, that is)
+        ExitOnFailure(hr, "failed trying to find existing app");
+        
+        // enable it (just in case it was disabled)
+        pNetFwRule->put_Enabled(VARIANT_TRUE);
+    }
+
+LExit:
+    ReleaseBSTR(bstrName);
+    ReleaseBSTR(bstrFile);
+    ReleaseObject(pNetFwRules);
+    ReleaseObject(pNetFwRule);
+
+    return fIgnoreFailures ? S_OK : hr;
+}
+
+/******************************************************************
+ AddApplicationExceptionOnCurrentProfile
+
+********************************************************************/
+static HRESULT AddApplicationExceptionOnCurrentProfile(
+    __in LPCWSTR wzFile, 
+    __in LPCWSTR wzName, 
+    __in_opt LPCWSTR wzRemoteAddresses,
+    __in BOOL fIgnoreFailures
+    )
+{
+    HRESULT hr = S_OK;
+    BSTR bstrFile = NULL;
+    BSTR bstrName = NULL;
+    BSTR bstrRemoteAddresses = NULL;
+    INetFwProfile* pfwProfile = NULL;
+    INetFwAuthorizedApplications* pfwApps = NULL;
+    INetFwAuthorizedApplication* pfwApp = NULL;
+
+    // convert to BSTRs to make COM happy
+    bstrFile = ::SysAllocString(wzFile);
+    ExitOnNull(bstrFile, hr, E_OUTOFMEMORY, "failed SysAllocString for path");
+    bstrName = ::SysAllocString(wzName);
+    ExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString for name");
+    bstrRemoteAddresses = ::SysAllocString(wzRemoteAddresses);
+    ExitOnNull(bstrRemoteAddresses, hr, E_OUTOFMEMORY, "failed SysAllocString for remote addresses");
+
+    // get the firewall profile, which is our entry point for adding exceptions
+    hr = GetCurrentFirewallProfile(fIgnoreFailures, &pfwProfile);
+    ExitOnFailure(hr, "failed to get firewall profile");
+    if (S_FALSE == hr) // user or package author chose to ignore missing firewall
+    {
+        ExitFunction();
+    }
+
+    // first, let's see if the app is already on the exception list
+    hr = pfwProfile->get_AuthorizedApplications(&pfwApps);
+    ExitOnFailure(hr, "failed to get list of authorized apps");
+
+    // try to find it (i.e., support reinstall)
+    hr = pfwApps->Item(bstrFile, &pfwApp);
+    if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
+    {
+        // not found, so we get to add it
+        hr = ::CoCreateInstance(__uuidof(NetFwAuthorizedApplication), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwAuthorizedApplication), reinterpret_cast<void**>(&pfwApp));
+        ExitOnFailure(hr, "failed to create authorized app");
+
+        // set the display name
+        hr = pfwApp->put_Name(bstrName);
+        ExitOnFailure(hr, "failed to set authorized app name");
+
+        // set path
+        hr = pfwApp->put_ProcessImageFileName(bstrFile);
+        ExitOnFailure(hr, "failed to set authorized app path");
+
+        // set the allowed remote addresses
+        if (bstrRemoteAddresses && *bstrRemoteAddresses)
+        {
+            hr = pfwApp->put_RemoteAddresses(bstrRemoteAddresses);
+            ExitOnFailure(hr, "failed to set authorized app remote addresses");
+        }
+
+        // add it to the list of authorized apps
+        hr = pfwApps->Add(pfwApp);
+        ExitOnFailure(hr, "failed to add app to the authorized apps list");
+    }
+    else
+    {
+        // we found an existing app exception (if we succeeded, that is)
+        ExitOnFailure(hr, "failed trying to find existing app");
+
+        // enable it (just in case it was disabled)
+        pfwApp->put_Enabled(VARIANT_TRUE);
+    }
+
+LExit:
+    ReleaseBSTR(bstrRemoteAddresses);
+    ReleaseBSTR(bstrName);
+    ReleaseBSTR(bstrFile);
+    ReleaseObject(pfwApp);
+    ReleaseObject(pfwApps);
+    ReleaseObject(pfwProfile);
+
+    return fIgnoreFailures ? S_OK : hr;
+}
+
+/******************************************************************
+ AddPortException
+
+********************************************************************/
+static HRESULT AddPortException(
+    __in LPCWSTR wzName,
+    __in int iProfile,
+    __in_opt LPCWSTR wzRemoteAddresses,
+    __in BOOL fIgnoreFailures,
+    __in LPCWSTR wzPort,
+    __in int iProtocol,
+    __in LPCWSTR wzDescription
+    )
+{
+    HRESULT hr = S_OK;
+    BSTR bstrName = NULL;
+    INetFwRules* pNetFwRules = NULL;
+    INetFwRule* pNetFwRule = NULL;
+
+    // convert to BSTRs to make COM happy
+    bstrName = ::SysAllocString(wzName);
+    ExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString for name");
+
+    // get the collection of firewall rules
+    hr = GetFirewallRules(fIgnoreFailures, &pNetFwRules);
+    ExitOnFailure(hr, "failed to get firewall rules object");
+    if (S_FALSE == hr) // user or package author chose to ignore missing firewall
+    {
+        ExitFunction();
+    }
+
+    // try to find it (i.e., support reinstall)
+    hr = pNetFwRules->Item(bstrName, &pNetFwRule);
+    if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
+    {
+        hr = CreateFwRuleObject(bstrName, iProfile, wzRemoteAddresses, wzPort, iProtocol, wzDescription, &pNetFwRule);
+        ExitOnFailure(hr, "failed to create FwRule object");
+
+        // enable it
+        hr = pNetFwRule->put_Enabled(VARIANT_TRUE);
+        ExitOnFailure(hr, "failed to to enable port exception");
+
+        // add it to the list of authorized ports
+        hr = pNetFwRules->Add(pNetFwRule);
+        ExitOnFailure(hr, "failed to add app to the authorized ports list");
+    }
+    else
+    {
+        // we found an existing port exception (if we succeeded, that is)
+        ExitOnFailure(hr, "failed trying to find existing port rule");
+
+        // enable it (just in case it was disabled)
+        pNetFwRule->put_Enabled(VARIANT_TRUE);
+    }
+
+LExit:
+    ReleaseBSTR(bstrName);
+    ReleaseObject(pNetFwRules);
+    ReleaseObject(pNetFwRule);
+
+    return fIgnoreFailures ? S_OK : hr;
+}
+
+/******************************************************************
+ AddPortExceptionOnCurrentProfile
+
+********************************************************************/
+static HRESULT AddPortExceptionOnCurrentProfile(
+    __in LPCWSTR wzName,
+    __in_opt LPCWSTR wzRemoteAddresses,
+    __in BOOL fIgnoreFailures,
+    __in int iPort,
+    __in int iProtocol
+    )
+{
+    HRESULT hr = S_OK;
+    BSTR bstrName = NULL;
+    BSTR bstrRemoteAddresses = NULL;
+    INetFwProfile* pfwProfile = NULL;
+    INetFwOpenPorts* pfwPorts = NULL;
+    INetFwOpenPort* pfwPort = NULL;
+
+    // convert to BSTRs to make COM happy
+    bstrName = ::SysAllocString(wzName);
+    ExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString for name");
+    bstrRemoteAddresses = ::SysAllocString(wzRemoteAddresses);
+    ExitOnNull(bstrRemoteAddresses, hr, E_OUTOFMEMORY, "failed SysAllocString for remote addresses");
+
+    // create and initialize a new open port object
+    hr = ::CoCreateInstance(__uuidof(NetFwOpenPort), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwOpenPort), reinterpret_cast<void**>(&pfwPort));
+    ExitOnFailure(hr, "failed to create new open port");
+
+    hr = pfwPort->put_Port(iPort);
+    ExitOnFailure(hr, "failed to set exception port");
+
+    hr = pfwPort->put_Protocol(static_cast<NET_FW_IP_PROTOCOL>(iProtocol));
+    ExitOnFailure(hr, "failed to set exception protocol");
+
+    if (bstrRemoteAddresses && *bstrRemoteAddresses)
+    {
+        hr = pfwPort->put_RemoteAddresses(bstrRemoteAddresses);
+        ExitOnFailure(hr, "failed to set exception remote addresses '%ls'", bstrRemoteAddresses);
+    }
+
+    hr = pfwPort->put_Name(bstrName);
+    ExitOnFailure(hr, "failed to set exception name");
+
+    // get the firewall profile, its current list of open ports, and add ours
+    hr = GetCurrentFirewallProfile(fIgnoreFailures, &pfwProfile);
+    ExitOnFailure(hr, "failed to get firewall profile");
+    if (S_FALSE == hr) // user or package author chose to ignore missing firewall
+    {
+        ExitFunction();
+    }
+
+    hr = pfwProfile->get_GloballyOpenPorts(&pfwPorts);
+    ExitOnFailure(hr, "failed to get open ports");
+
+    hr = pfwPorts->Add(pfwPort);
+    ExitOnFailure(hr, "failed to add exception to global list");
+
+LExit:
+    ReleaseBSTR(bstrRemoteAddresses);
+    ReleaseBSTR(bstrName);
+    ReleaseObject(pfwProfile);
+    ReleaseObject(pfwPorts);
+    ReleaseObject(pfwPort);
+
+    return fIgnoreFailures ? S_OK : hr;
+}
+
+/******************************************************************
+ RemoveException - Removes the exception rule with the given name.
+
+********************************************************************/
+static HRESULT RemoveException(
+    __in LPCWSTR wzName, 
+    __in BOOL fIgnoreFailures
+    )
+{
+    HRESULT hr = S_OK;;
+    INetFwRules* pNetFwRules = NULL;
+
+    // convert to BSTRs to make COM happy
+    BSTR bstrName = ::SysAllocString(wzName);
+    ExitOnNull(bstrName, hr, E_OUTOFMEMORY, "failed SysAllocString for path");
+
+    // get the collection of firewall rules
+    hr = GetFirewallRules(fIgnoreFailures, &pNetFwRules);
+    ExitOnFailure(hr, "failed to get firewall rules object");
+    if (S_FALSE == hr) // user or package author chose to ignore missing firewall
+    {
+        ExitFunction();
+    }
+
+    hr = pNetFwRules->Remove(bstrName);
+    ExitOnFailure(hr, "failed to remove authorized app");
+
+LExit:
+    ReleaseBSTR(bstrName);
+    ReleaseObject(pNetFwRules);
+
+    return fIgnoreFailures ? S_OK : hr;
+}
+
+/******************************************************************
+ RemoveApplicationExceptionFromCurrentProfile
+
+********************************************************************/
+static HRESULT RemoveApplicationExceptionFromCurrentProfile(
+    __in LPCWSTR wzFile, 
+    __in BOOL fIgnoreFailures
+    )
+{
+    HRESULT hr = S_OK;
+    INetFwProfile* pfwProfile = NULL;
+    INetFwAuthorizedApplications* pfwApps = NULL;
+
+    // convert to BSTRs to make COM happy
+    BSTR bstrFile = ::SysAllocString(wzFile);
+    ExitOnNull(bstrFile, hr, E_OUTOFMEMORY, "failed SysAllocString for path");
+
+    // get the firewall profile, which is our entry point for removing exceptions
+    hr = GetCurrentFirewallProfile(fIgnoreFailures, &pfwProfile);
+    ExitOnFailure(hr, "failed to get firewall profile");
+    if (S_FALSE == hr) // user or package author chose to ignore missing firewall
+    {
+        ExitFunction();
+    }
+
+    // now get the list of app exceptions and remove the one
+    hr = pfwProfile->get_AuthorizedApplications(&pfwApps);
+    ExitOnFailure(hr, "failed to get list of authorized apps");
+
+    hr = pfwApps->Remove(bstrFile);
+    ExitOnFailure(hr, "failed to remove authorized app");
+
+LExit:
+    ReleaseBSTR(bstrFile);
+    ReleaseObject(pfwApps);
+    ReleaseObject(pfwProfile);
+
+    return fIgnoreFailures ? S_OK : hr;
+}
+
+/******************************************************************
+ RemovePortExceptionFromCurrentProfile
+
+********************************************************************/
+static HRESULT RemovePortExceptionFromCurrentProfile(
+    __in int iPort,
+    __in int iProtocol,
+    __in BOOL fIgnoreFailures
+    )
+{
+    HRESULT hr = S_OK;
+    INetFwProfile* pfwProfile = NULL;
+    INetFwOpenPorts* pfwPorts = NULL;
+
+    // get the firewall profile, which is our entry point for adding exceptions
+    hr = GetCurrentFirewallProfile(fIgnoreFailures, &pfwProfile);
+    ExitOnFailure(hr, "failed to get firewall profile");
+    if (S_FALSE == hr) // user or package author chose to ignore missing firewall
+    {
+        ExitFunction();
+    }
+
+    hr = pfwProfile->get_GloballyOpenPorts(&pfwPorts);
+    ExitOnFailure(hr, "failed to get open ports");
+
+    hr = pfwPorts->Remove(iPort, static_cast<NET_FW_IP_PROTOCOL>(iProtocol));
+    ExitOnFailure(hr, "failed to remove open port %d, protocol %d", iPort, iProtocol);
+
+LExit:
+    return fIgnoreFailures ? S_OK : hr;
+}
+
+static HRESULT AddApplicationException(
+    __in BOOL fSupportProfiles,
+    __in LPCWSTR wzFile, 
+    __in LPCWSTR wzName,
+    __in int iProfile,
+    __in_opt LPCWSTR wzRemoteAddresses,
+    __in BOOL fIgnoreFailures,
+    __in LPCWSTR wzPort,
+    __in int iProtocol,
+    __in LPCWSTR wzDescription
+    )
+{
+    HRESULT hr = S_OK;
+
+    if (fSupportProfiles)
+    {
+        hr = AddApplicationException(wzFile, wzName, iProfile, wzRemoteAddresses, fIgnoreFailures, wzPort, iProtocol, wzDescription);
+    }
+    else
+    {
+        if (0 != *wzPort || MSI_NULL_INTEGER != iProtocol)
+        {
+            // NOTE: This is treated as an error rather than either creating a rule based on just the application (no port), or 
+            // just the port because it is unclear what is the proper fall back. For example, suppose that you have code that 
+            // runs in dllhost.exe. Clearly falling back to opening all of dllhost is wrong. Because the firewall is a security
+            // feature, it seems better to require the MSI author to indicate the behavior that they want.
+            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);
+            return fIgnoreFailures ? S_OK : E_NOTIMPL;
+        }
+
+        hr = AddApplicationExceptionOnCurrentProfile(wzFile, wzName, wzRemoteAddresses, fIgnoreFailures);
+    }
+
+    return hr;
+}
+
+static HRESULT AddPortException(
+    __in BOOL fSupportProfiles,
+    __in LPCWSTR wzName,
+    __in int iProfile,
+    __in_opt LPCWSTR wzRemoteAddresses,
+    __in BOOL fIgnoreFailures,
+    __in LPCWSTR wzPort,
+    __in int iProtocol,
+    __in LPCWSTR wzDescription
+    )
+{
+    HRESULT hr = S_OK;
+
+    if (fSupportProfiles)
+    {
+        hr = AddPortException(wzName, iProfile, wzRemoteAddresses, fIgnoreFailures, wzPort, iProtocol, wzDescription);
+    }
+    else
+    {
+        hr = AddPortExceptionOnCurrentProfile(wzName, wzRemoteAddresses, fIgnoreFailures, wcstol(wzPort, NULL, 10), iProtocol);
+    }
+
+    return hr;
+}
+
+static HRESULT RemoveApplicationException(
+    __in BOOL fSupportProfiles,
+    __in LPCWSTR wzName,
+    __in LPCWSTR wzFile, 
+    __in BOOL fIgnoreFailures,
+    __in LPCWSTR wzPort,
+    __in int iProtocol
+    )
+{
+    HRESULT hr = S_OK;
+
+    if (fSupportProfiles)
+    {
+        hr = RemoveException(wzName, fIgnoreFailures);
+    }
+    else
+    {
+        if (0 != *wzPort || MSI_NULL_INTEGER != iProtocol)
+        {
+            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);
+            return S_OK;
+        }
+
+        hr = RemoveApplicationExceptionFromCurrentProfile(wzFile, fIgnoreFailures);
+    }
+
+    return hr;
+}
+
+static HRESULT RemovePortException(
+    __in BOOL fSupportProfiles,
+    __in LPCWSTR wzName,
+    __in LPCWSTR wzPort,
+    __in int iProtocol,
+    __in BOOL fIgnoreFailures
+    )
+{
+    HRESULT hr = S_OK;
+
+    if (fSupportProfiles)
+    {
+        hr = RemoveException(wzName, fIgnoreFailures);
+    }
+    else
+    {
+        hr = RemovePortExceptionFromCurrentProfile(wcstol(wzPort, NULL, 10), iProtocol, fIgnoreFailures);
+    }
+
+    return hr;
+}
+
+/******************************************************************
+ ExecFirewallExceptions - deferred custom action entry point to 
+   register and remove firewall exceptions.
+
+********************************************************************/
+extern "C" UINT __stdcall ExecFirewallExceptions(
+    __in MSIHANDLE hInstall
+    )
+{
+    HRESULT hr = S_OK;
+    BOOL fSupportProfiles = FALSE;
+    LPWSTR pwz = NULL;
+    LPWSTR pwzCustomActionData = NULL;
+    int iTodo = WCA_TODO_UNKNOWN;
+    LPWSTR pwzName = NULL;
+    LPWSTR pwzRemoteAddresses = NULL;
+    int iAttributes = 0;
+    int iTarget = fetUnknown;
+    LPWSTR pwzFile = NULL;
+    LPWSTR pwzPort = NULL;
+    LPWSTR pwzDescription = NULL;
+    int iProtocol = 0;
+    int iProfile = 0;
+
+    // initialize
+    hr = WcaInitialize(hInstall, "ExecFirewallExceptions");
+    ExitOnFailure(hr, "failed to initialize");
+
+    hr = WcaGetProperty( L"CustomActionData", &pwzCustomActionData);
+    ExitOnFailure(hr, "failed to get CustomActionData");
+    WcaLog(LOGMSG_TRACEONLY, "CustomActionData: %ls", pwzCustomActionData);
+
+    hr = ::CoInitialize(NULL);
+    ExitOnFailure(hr, "failed to initialize COM");
+
+    // Find out if we support profiles (only on Vista or later)
+    fSupportProfiles = FSupportProfiles();
+
+    // loop through all the passed in data
+    pwz = pwzCustomActionData;
+    while (pwz && *pwz)
+    {
+        // extract the custom action data and if rolling back, swap INSTALL and UNINSTALL
+        hr = WcaReadIntegerFromCaData(&pwz, &iTodo);
+        ExitOnFailure(hr, "failed to read todo from custom action data");
+        if (::MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK))
+        {
+            if (WCA_TODO_INSTALL == iTodo)
+            {
+                iTodo = WCA_TODO_UNINSTALL;
+            }
+            else if (WCA_TODO_UNINSTALL == iTodo)
+            {
+                iTodo = WCA_TODO_INSTALL;
+            }
+        }
+
+        hr = WcaReadStringFromCaData(&pwz, &pwzName);
+        ExitOnFailure(hr, "failed to read name from custom action data");
+
+        hr = WcaReadIntegerFromCaData(&pwz, &iProfile);
+        ExitOnFailure(hr, "failed to read profile from custom action data");
+
+        hr = WcaReadStringFromCaData(&pwz, &pwzRemoteAddresses);
+        ExitOnFailure(hr, "failed to read remote addresses from custom action data");
+
+        hr = WcaReadIntegerFromCaData(&pwz, &iAttributes);
+        ExitOnFailure(hr, "failed to read attributes from custom action data");
+        BOOL fIgnoreFailures = feaIgnoreFailures == (iAttributes & feaIgnoreFailures);
+
+        hr = WcaReadIntegerFromCaData(&pwz, &iTarget);
+        ExitOnFailure(hr, "failed to read target from custom action data");
+
+        if (iTarget == fetApplication)
+        {
+            hr = WcaReadStringFromCaData(&pwz, &pwzFile);
+            ExitOnFailure(hr, "failed to read file path from custom action data");
+        }
+
+        hr = WcaReadStringFromCaData(&pwz, &pwzPort);
+        ExitOnFailure(hr, "failed to read port from custom action data");
+        hr = WcaReadIntegerFromCaData(&pwz, &iProtocol);
+        ExitOnFailure(hr, "failed to read protocol from custom action data");
+        hr = WcaReadStringFromCaData(&pwz, &pwzDescription);
+        ExitOnFailure(hr, "failed to read protocol from custom action data");
+
+        switch (iTarget)
+        {
+        case fetPort:
+            switch (iTodo)
+            {
+            case WCA_TODO_INSTALL:
+            case WCA_TODO_REINSTALL:
+                WcaLog(LOGMSG_STANDARD, "Installing firewall exception2 %ls on port %ls, protocol %d", pwzName, pwzPort, iProtocol);
+                hr = AddPortException(fSupportProfiles, pwzName, iProfile, pwzRemoteAddresses, fIgnoreFailures, pwzPort, iProtocol, pwzDescription);
+                ExitOnFailure(hr, "failed to add/update port exception for name '%ls' on port %ls, protocol %d", pwzName, pwzPort, iProtocol);
+                break;
+
+            case WCA_TODO_UNINSTALL:
+                WcaLog(LOGMSG_STANDARD, "Uninstalling firewall exception2 %ls on port %ls, protocol %d", pwzName, pwzPort, iProtocol);
+                hr = RemovePortException(fSupportProfiles, pwzName, pwzPort, iProtocol, fIgnoreFailures);
+                ExitOnFailure(hr, "failed to remove port exception for name '%ls' on port %ls, protocol %d", pwzName, pwzPort, iProtocol);
+                break;
+            }
+            break;
+
+        case fetApplication:
+            switch (iTodo)
+            {
+            case WCA_TODO_INSTALL:
+            case WCA_TODO_REINSTALL:
+                WcaLog(LOGMSG_STANDARD, "Installing firewall exception2 %ls (%ls)", pwzName, pwzFile);
+                hr = AddApplicationException(fSupportProfiles, pwzFile, pwzName, iProfile, pwzRemoteAddresses, fIgnoreFailures, pwzPort, iProtocol, pwzDescription);
+                ExitOnFailure(hr, "failed to add/update application exception for name '%ls', file '%ls'", pwzName, pwzFile);
+                break;
+
+            case WCA_TODO_UNINSTALL:
+                WcaLog(LOGMSG_STANDARD, "Uninstalling firewall exception2 %ls (%ls)", pwzName, pwzFile);
+                hr = RemoveApplicationException(fSupportProfiles, pwzName, pwzFile, fIgnoreFailures, pwzPort, iProtocol);
+                ExitOnFailure(hr, "failed to remove application exception for name '%ls', file '%ls'", pwzName, pwzFile);
+                break;
+            }
+            break;
+        }
+    }
+
+LExit:
+    ReleaseStr(pwzCustomActionData);
+    ReleaseStr(pwzName);
+    ReleaseStr(pwzRemoteAddresses);
+    ReleaseStr(pwzFile);
+    ReleaseStr(pwzPort);
+    ReleaseStr(pwzDescription);
+    ::CoUninitialize();
+
+    return WcaFinalize(FAILED(hr) ? ERROR_INSTALL_FAILURE : ERROR_SUCCESS);
+}
diff --git a/src/ca/fwca.def b/src/ca/fwca.def
new file mode 100644
index 00000000..d32c5379
--- /dev/null
+++ b/src/ca/fwca.def
@@ -0,0 +1,9 @@
+; 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.
+
+
+LIBRARY      "fwca"
+
+EXPORTS
+	SchedFirewallExceptionsInstall
+	SchedFirewallExceptionsUninstall
+	ExecFirewallExceptions
diff --git a/src/ca/fwca.vcxproj b/src/ca/fwca.vcxproj
new file mode 100644
index 00000000..b6075790
--- /dev/null
+++ b/src/ca/fwca.vcxproj
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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. -->
+
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\..\packages\WixToolset.DUtil.4.0.6\build\WixToolset.DUtil.props" Condition="Exists('..\..\packages\WixToolset.DUtil.4.0.6\build\WixToolset.DUtil.props')" />
+  <Import Project="..\..\packages\WixToolset.WcaUtil.4.0.2\build\WixToolset.WcaUtil.props" Condition="Exists('..\..\packages\WixToolset.WcaUtil.4.0.2\build\WixToolset.WcaUtil.props')" />
+
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{F72D34CA-48DA-4DFD-91A9-A0C78BEF6981}</ProjectGuid>
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <TargetName>fwca</TargetName>
+    <PlatformToolset>v141</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+    <ProjectModuleDefinitionFile>fwca.def</ProjectModuleDefinitionFile>
+    <Description>WiX Toolset Firewall CustomAction</Description>
+  </PropertyGroup>
+
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+
+  <ImportGroup Label="Shared">
+    <Import Project="..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.1.14.114\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets" Condition="Exists('..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.1.14.114\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" />
+  </ImportGroup>
+
+  <PropertyGroup>
+    <ProjectAdditionalLinkLibraries>msi.lib</ProjectAdditionalLinkLibraries>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ClCompile Include="dllmain.cpp">
+      <PrecompiledHeader>Create</PrecompiledHeader>
+    </ClCompile>
+    <ClCompile Include="firewall.cpp" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ClInclude Include="cost.h" />
+    <ClInclude Include="CustomMsiErrors.h" />
+    <ClInclude Include="precomp.h" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Include="packages.config" />
+    <None Include="fwca.def" />
+  </ItemGroup>
+
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.1.14.114\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.1.14.114\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets'))" />
+    <Error Condition="!Exists('..\..\packages\WixToolset.DUtil.4.0.6\build\WixToolset.DUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\WixToolset.DUtil.4.0.6\build\WixToolset.DUtil.props'))" />
+    <Error Condition="!Exists('..\..\packages\WixToolset.WcaUtil.4.0.2\build\WixToolset.WcaUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\WixToolset.WcaUtil.4.0.2\build\WixToolset.WcaUtil.props'))" />
+  </Target>
+</Project>
+
diff --git a/src/ca/packages.config b/src/ca/packages.config
new file mode 100644
index 00000000..b74ff5d0
--- /dev/null
+++ b/src/ca/packages.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Microsoft.VisualStudio.Setup.Configuration.Native" version="1.14.114" targetFramework="native" developmentDependency="true" />
+  <package id="WixToolset.DUtil" version="4.0.6" targetFramework="native" />
+  <package id="WixToolset.WcaUtil" version="4.0.2" targetFramework="native" />
+</packages>
\ No newline at end of file
diff --git a/src/ca/precomp.h b/src/ca/precomp.h
new file mode 100644
index 00000000..a84bacff
--- /dev/null
+++ b/src/ca/precomp.h
@@ -0,0 +1,17 @@
+#pragma once
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
+
+
+#include <windows.h>
+#include <msidefs.h>
+#include <msiquery.h>
+#include <strsafe.h>
+#include <netfw.h>
+
+#include "wcautil.h"
+#include "fileutil.h"
+#include "pathutil.h"
+#include "strutil.h"
+
+#include "CustomMsiErrors.h"
+#include "cost.h"
-- 
cgit v1.2.3-55-g6feb