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/Cpp.Build.props                                |  101 ++
 src/Directory.Build.props                          |   22 +
 src/FindLocalWix.props                             |    8 +
 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 +
 .../FirewallExtensionFixture.cs                    |   33 +
 .../TestData/UsingFirewall/Package.en-us.wxl       |   11 +
 .../TestData/UsingFirewall/Package.wxs             |   22 +
 .../TestData/UsingFirewall/PackageComponents.wxs   |   14 +
 .../TestData/UsingFirewall/example.txt             |    1 +
 .../WixToolsetTest.Firewall.csproj                 |   40 +
 src/wixext/FirewallCompiler.cs                     |  356 +++++++
 src/wixext/FirewallConstants.cs                    |   21 +
 src/wixext/FirewallDecompiler.cs                   |  169 ++++
 src/wixext/FirewallErrors.cs                       |   42 +
 src/wixext/FirewallExtensionData.cs                |   24 +
 src/wixext/FirewallExtensionFactory.cs             |   18 +
 .../FirewallWindowsInstallerBackendExtension.cs    |   26 +
 src/wixext/Tuples/FirewallTupleDefinitions.cs      |   31 +
 src/wixext/Tuples/WixFirewallExceptionTuple.cs     |   93 ++
 src/wixext/WixToolset.Firewall.wixext.csproj       |   36 +
 src/wixext/WixToolset.Firewall.wixext.targets      |   11 +
 src/wixext/firewall.xsd                            |  211 ++++
 src/wixext/tables.xml                              |   28 +
 src/wixlib/FirewallExtension.wxs                   |   12 +
 src/wixlib/FirewallExtension_Platform.wxi          |   41 +
 src/wixlib/FirewallExtension_x86.wxs               |    8 +
 src/wixlib/caSuffix.wxi                            |   28 +
 src/wixlib/caerr.wxi                               |   96 ++
 src/wixlib/en-us.wxl                               |   14 +
 src/wixlib/es-es.wxl                               |   13 +
 src/wixlib/firewall.wixproj                        |   44 +
 src/wixlib/ja-jp.wxl                               |   14 +
 src/wixlib/packages.config                         |    5 +
 src/wixlib/pl-pl.wxl                               |   14 +
 41 files changed, 2939 insertions(+)
 create mode 100644 src/Cpp.Build.props
 create mode 100644 src/Directory.Build.props
 create mode 100644 src/FindLocalWix.props
 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
 create mode 100644 src/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs
 create mode 100644 src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/Package.en-us.wxl
 create mode 100644 src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/Package.wxs
 create mode 100644 src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs
 create mode 100644 src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/example.txt
 create mode 100644 src/test/WixToolsetTest.Firewall/WixToolsetTest.Firewall.csproj
 create mode 100644 src/wixext/FirewallCompiler.cs
 create mode 100644 src/wixext/FirewallConstants.cs
 create mode 100644 src/wixext/FirewallDecompiler.cs
 create mode 100644 src/wixext/FirewallErrors.cs
 create mode 100644 src/wixext/FirewallExtensionData.cs
 create mode 100644 src/wixext/FirewallExtensionFactory.cs
 create mode 100644 src/wixext/FirewallWindowsInstallerBackendExtension.cs
 create mode 100644 src/wixext/Tuples/FirewallTupleDefinitions.cs
 create mode 100644 src/wixext/Tuples/WixFirewallExceptionTuple.cs
 create mode 100644 src/wixext/WixToolset.Firewall.wixext.csproj
 create mode 100644 src/wixext/WixToolset.Firewall.wixext.targets
 create mode 100644 src/wixext/firewall.xsd
 create mode 100644 src/wixext/tables.xml
 create mode 100644 src/wixlib/FirewallExtension.wxs
 create mode 100644 src/wixlib/FirewallExtension_Platform.wxi
 create mode 100644 src/wixlib/FirewallExtension_x86.wxs
 create mode 100644 src/wixlib/caSuffix.wxi
 create mode 100644 src/wixlib/caerr.wxi
 create mode 100644 src/wixlib/en-us.wxl
 create mode 100644 src/wixlib/es-es.wxl
 create mode 100644 src/wixlib/firewall.wixproj
 create mode 100644 src/wixlib/ja-jp.wxl
 create mode 100644 src/wixlib/packages.config
 create mode 100644 src/wixlib/pl-pl.wxl

(limited to 'src')

diff --git a/src/Cpp.Build.props b/src/Cpp.Build.props
new file mode 100644
index 00000000..453aa442
--- /dev/null
+++ b/src/Cpp.Build.props
@@ -0,0 +1,101 @@
+<?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>
+  <PropertyGroup>
+    <Platform Condition=" '$(Platform)' == 'AnyCPU' ">Win32</Platform>
+    <BaseOutputPath>$(OutputPath)</BaseOutputPath>
+    <IntDir>$(BaseIntermediateOutputPath)$(Platform)\</IntDir>
+    <OutDir>$(OutputPath)$(Platform)\</OutDir>
+  </PropertyGroup>
+
+  <ItemDefinitionGroup>
+    <ClCompile>
+      <DisableSpecificWarnings>$(DisableSpecificCompilerWarnings)</DisableSpecificWarnings>
+      <WarningLevel>Level4</WarningLevel>
+      <AdditionalIncludeDirectories>$(ProjectDir)inc;$(MSBuildProjectDirectory);$(IntDir);$(SqlCESdkIncludePath);$(ProjectAdditionalIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PreprocessorDefinitions>WIN32;_WINDOWS;_WIN32_MSI=500;_WIN32_WINNT=0x0501;$(ArmPreprocessorDefinitions);$(UnicodePreprocessorDefinitions);_CRT_STDIO_LEGACY_WIDE_SPECIFIERS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PrecompiledHeader>Use</PrecompiledHeader>
+      <PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
+      <CallingConvention>StdCall</CallingConvention>
+      <TreatWarningAsError>true</TreatWarningAsError>
+      <ExceptionHandling>false</ExceptionHandling>
+      <AdditionalOptions>-YlprecompDefine</AdditionalOptions>
+      <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/Zc:threadSafeInit- %(AdditionalOptions)</AdditionalOptions>
+      <MultiProcessorCompilation Condition=" $(NUMBER_OF_PROCESSORS) &gt; 4 ">true</MultiProcessorCompilation>
+    </ClCompile>
+    <ResourceCompile>
+      <PreprocessorDefinitions>$(ArmPreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>$(ProjectAdditionalResourceIncludeDirectories);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ResourceCompile>
+    <Lib>
+      <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ProjectAdditionalLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+    </Lib>
+    <Link>
+      <SubSystem>$(ProjectSubSystem)</SubSystem>
+      <ModuleDefinitionFile>$(ProjectModuleDefinitionFile)</ModuleDefinitionFile>
+      <NoEntryPoint>$(ResourceOnlyDll)</NoEntryPoint>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>$(ProjectAdditionalLinkLibraries);advapi32.lib;comdlg32.lib;user32.lib;oleaut32.lib;gdi32.lib;shell32.lib;ole32.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir);$(AdditionalMultiTargetLibraryPath);$(ArmLibraryDirectories);$(ProjectAdditionalLinkLibraryDirectories);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <AdditionalOptions Condition=" $(PlatformToolset.StartsWith('v14')) ">/IGNORE:4099 %(AdditionalOptions)</AdditionalOptions>
+    </Link>
+  </ItemDefinitionGroup>
+
+  <ItemDefinitionGroup Condition=" '$(Platform)'=='Win32' and '$(PlatformToolset)'!='v100'">
+    <ClCompile>
+      <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition=" '$(Platform)'=='arm' ">
+    <ClCompile>
+      <CallingConvention>CDecl</CallingConvention>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition=" '$(ConfigurationType)'=='StaticLibrary' ">
+    <ClCompile>
+      <DebugInformationFormat>OldStyle</DebugInformationFormat>
+      <OmitDefaultLibName>true</OmitDefaultLibName>
+      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' ">
+    <ClCompile>
+      <Optimization>Disabled</Optimization>
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+      <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition=" '$(Configuration)'=='Debug' and '$(CLRSupport)'=='true' ">
+    <ClCompile>
+      <BasicRuntimeChecks></BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDebugDll</RuntimeLibrary>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' ">
+    <ClCompile>
+      <Optimization>MinSpace</Optimization>
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+    </ClCompile>
+    <Link>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition=" '$(Configuration)'=='Release' and '$(CLRSupport)'=='true' ">
+    <ClCompile>
+      <BasicRuntimeChecks></BasicRuntimeChecks>
+      <RuntimeLibrary>MultiThreadedDll</RuntimeLibrary>
+    </ClCompile>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition=" '$(CLRSupport)'=='true' ">
+    <Link>
+      <KeyFile>$(LinkKeyFile)</KeyFile>
+      <DelaySign>$(LinkDelaySign)</DelaySign>
+    </Link>
+  </ItemDefinitionGroup>
+</Project>
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 00000000..63ad5d6e
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,22 @@
+<?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>
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)..\build\obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
+    <OutputPath>$(MSBuildThisFileDirectory)..\build\$(Configuration)\</OutputPath>
+
+    <Authors>WiX Toolset Team</Authors>
+    <Company>WiX Toolset</Company>
+    <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <WixToolsetRootFolder>$(MSBuildThisFileDirectory)..\..\</WixToolsetRootFolder>
+  </PropertyGroup>
+
+  <Import Project="Cpp.Build.props" Condition=" '$(MSBuildProjectExtension)'=='.vcxproj' " />
+  <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " />
+</Project>
diff --git a/src/FindLocalWix.props b/src/FindLocalWix.props
new file mode 100644
index 00000000..1301b0e5
--- /dev/null
+++ b/src/FindLocalWix.props
@@ -0,0 +1,8 @@
+<?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 xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <WixTargetsPath Condition=" '$(Configuration)' != 'Release' And Exists('$(MSBuildThisFileDirectory)..\..\Core\README.md') ">$(MSBuildThisFileDirectory)..\..\Core\build\Debug\net461\wix.targets</WixTargetsPath>
+  </PropertyGroup>
+</Project>
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"
diff --git a/src/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs b/src/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs
new file mode 100644
index 00000000..0d7eb6ad
--- /dev/null
+++ b/src/test/WixToolsetTest.Firewall/FirewallExtensionFixture.cs
@@ -0,0 +1,33 @@
+// 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.
+
+namespace WixToolsetTest.Firewall
+{
+    using System.Linq;
+    using WixBuildTools.TestSupport;
+    using WixToolset.Core.TestPackage;
+    using WixToolset.Firewall;
+    using Xunit;
+
+    public class FirewallExtensionFixture
+    {
+        [Fact]
+        public void CanBuildUsingFileShare()
+        {
+            var folder = TestData.Get(@"TestData\UsingFirewall");
+            var build = new Builder(folder, typeof(FirewallExtensionFactory), new[] { folder });
+
+            var results = build.BuildAndQuery(Build, "WixFirewallException");
+            Assert.Equal(new[]
+            {
+                "WixFirewallException:ExampleFirewall\texample\t*\t42\t6\t\t0\t2147483647\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tAn example firewall",
+            }, results.OrderBy(s => s).ToArray());
+        }
+
+        private static void Build(string[] args)
+        {
+            var result = WixRunner.Execute(args, out var messages);
+            Assert.Equal(0, result);
+            Assert.Empty(messages);
+        }
+    }
+}
diff --git a/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/Package.en-us.wxl b/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/Package.en-us.wxl
new file mode 100644
index 00000000..38c12ac1
--- /dev/null
+++ b/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/Package.en-us.wxl
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+This file contains the declaration of all the localizable strings.
+-->
+<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
+
+  <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
+  <String Id="FeatureTitle">MsiPackage</String>
+
+</WixLocalization>
diff --git a/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/Package.wxs b/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/Package.wxs
new file mode 100644
index 00000000..68ff98fd
--- /dev/null
+++ b/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/Package.wxs
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+  <Product Id="*" Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
+    <Package InstallerVersion="200" Compressed="no" InstallScope="perMachine" />
+
+    <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
+    <MediaTemplate />
+
+    <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
+      <ComponentGroupRef Id="ProductComponents" />
+    </Feature>
+    
+  </Product>
+
+  <Fragment>
+    <Directory Id="TARGETDIR" Name="SourceDir">
+      <Directory Id="ProgramFilesFolder">
+        <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
+      </Directory>
+    </Directory>
+  </Fragment>
+</Wix>
diff --git a/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs b/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs
new file mode 100644
index 00000000..910cca2d
--- /dev/null
+++ b/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/PackageComponents.wxs
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
+     xmlns:fw="http://wixtoolset.org/schemas/v4/wxs/firewall">
+  <Fragment>
+    <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
+       <Component>
+         <File Source="example.txt" />
+         <fw:FirewallException Id="ExampleFirewall" Description="An example firewall" Name="example" Port="42">
+           <fw:RemoteAddress>*</fw:RemoteAddress>
+         </fw:FirewallException>
+       </Component>
+    </ComponentGroup>
+  </Fragment>
+</Wix>
diff --git a/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/example.txt b/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/example.txt
new file mode 100644
index 00000000..1b4ffe8a
--- /dev/null
+++ b/src/test/WixToolsetTest.Firewall/TestData/UsingFirewall/example.txt
@@ -0,0 +1 @@
+This is example.txt.
\ No newline at end of file
diff --git a/src/test/WixToolsetTest.Firewall/WixToolsetTest.Firewall.csproj b/src/test/WixToolsetTest.Firewall/WixToolsetTest.Firewall.csproj
new file mode 100644
index 00000000..8925cbc7
--- /dev/null
+++ b/src/test/WixToolsetTest.Firewall/WixToolsetTest.Firewall.csproj
@@ -0,0 +1,40 @@
+<?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 Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <NoWarn>NU1701</NoWarn>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Content Include="TestData\UsingFirewall\example.txt" CopyToOutputDirectory="PreserveNewest" />
+    <Content Include="TestData\UsingFirewall\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
+    <Content Include="TestData\UsingFirewall\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
+    <Content Include="TestData\UsingFirewall\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\wixext\WixToolset.Firewall.wixext.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="$(WixToolsetRootFolder)\Core\src\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core\README.md') " />
+    <ProjectReference Include="$(WixToolsetRootFolder)\Core\src\wix\wix.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core\README.md') " />
+    <PackageReference Include="WixToolset.Core.TestPackage" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core\README.md') " PrivateAssets="all" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="WixBuildTools.TestSupport" Version="4.0.*" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
+    <PackageReference Include="xunit" Version="2.2.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
+  </ItemGroup>
+</Project>
diff --git a/src/wixext/FirewallCompiler.cs b/src/wixext/FirewallCompiler.cs
new file mode 100644
index 00000000..0696b4b1
--- /dev/null
+++ b/src/wixext/FirewallCompiler.cs
@@ -0,0 +1,356 @@
+// 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.
+
+namespace WixToolset.Firewall
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Globalization;
+    using System.Xml.Linq;
+    using WixToolset.Data;
+    using WixToolset.Extensibility;
+
+    /// <summary>
+    /// The compiler for the WiX Toolset Firewall Extension.
+    /// </summary>
+    public sealed class FirewallCompiler : BaseCompilerExtension
+    {
+        public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/firewall";
+
+        /// <summary>
+        /// Processes an element for the Compiler.
+        /// </summary>
+        /// <param name="sourceLineNumbers">Source line number for the parent element.</param>
+        /// <param name="parentElement">Parent element of element to process.</param>
+        /// <param name="element">Element to process.</param>
+        /// <param name="contextValues">Extra information about the context in which this element is being parsed.</param>
+        public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context)
+        {
+            switch (parentElement.Name.LocalName)
+            {
+                case "File":
+                    string fileId = context["FileId"];
+                    string fileComponentId = context["ComponentId"];
+
+                    switch (element.Name.LocalName)
+                    {
+                        case "FirewallException":
+                            this.ParseFirewallExceptionElement(intermediate, section, element, fileComponentId, fileId);
+                            break;
+                        default:
+                            this.ParseHelper.UnexpectedElement(parentElement, element);
+                            break;
+                    }
+                    break;
+                case "Component":
+                    string componentId = context["ComponentId"];
+
+                    switch (element.Name.LocalName)
+                    {
+                        case "FirewallException":
+                            this.ParseFirewallExceptionElement(intermediate, section, element, componentId, null);
+                            break;
+                        default:
+                            this.ParseHelper.UnexpectedElement(parentElement, element);
+                            break;
+                    }
+                    break;
+                default:
+                    this.ParseHelper.UnexpectedElement(parentElement, element);
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Parses a FirewallException element.
+        /// </summary>
+        /// <param name="element">The element to parse.</param>
+        /// <param name="componentId">Identifier of the component that owns this firewall exception.</param>
+        /// <param name="fileId">The file identifier of the parent element (null if nested under Component).</param>
+        private void ParseFirewallExceptionElement(Intermediate intermediate, IntermediateSection section, XElement element, string componentId, string fileId)
+        {
+            SourceLineNumber sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
+            Identifier id = null;
+            string name = null;
+            int attributes = 0;
+            string file = null;
+            string program = null;
+            string port = null;
+            string protocolValue = null;
+            int? protocol = null;
+            string profileValue = null;
+            int? profile = null;
+            string scope = null;
+            string remoteAddresses = null;
+            string description = null;
+
+            foreach (XAttribute attrib in element.Attributes())
+            {
+                if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
+                {
+                    switch (attrib.Name.LocalName)
+                    {
+                        case "Id":
+                            id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib);
+                            break;
+                        case "Name":
+                            name = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
+                            break;
+                        case "File":
+                            if (null != fileId)
+                            {
+                                this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "File", "File"));
+                            }
+                            else
+                            {
+                                file = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
+                            }
+                            break;
+                        case "IgnoreFailure":
+                            if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
+                            {
+                                attributes |= 0x1; // feaIgnoreFailures
+                            }
+                            break;
+                        case "Program":
+                            if (null != fileId)
+                            {
+                                this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "Program", "File"));
+                            }
+                            else
+                            {
+                                program = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
+                            }
+                            break;
+                        case "Port":
+                            port = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
+                            break;
+                        case "Protocol":
+                            protocolValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
+                            switch (protocolValue)
+                            {
+                                case "tcp":
+                                    protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP;
+                                    break;
+                                case "udp":
+                                    protocol = FirewallConstants.NET_FW_IP_PROTOCOL_UDP;
+                                    break;
+                                default:
+                                    this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Protocol", protocolValue, "tcp", "udp"));
+                                    break;
+                            }
+                            break;
+                        case "Scope":
+                            scope = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
+                            switch (scope)
+                            {
+                                case "any":
+                                    remoteAddresses = "*";
+                                    break;
+                                case "localSubnet":
+                                    remoteAddresses = "LocalSubnet";
+                                    break;
+                                default:
+                                    this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Scope", scope, "any", "localSubnet"));
+                                    break;
+                            }
+                            break;
+                        case "Profile":
+                            profileValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
+                            switch (profileValue)
+                            {
+                                case "domain":
+                                    profile = FirewallConstants.NET_FW_PROFILE2_DOMAIN;
+                                    break;
+                                case "private":
+                                    profile = FirewallConstants.NET_FW_PROFILE2_PRIVATE;
+                                    break;
+                                case "public":
+                                    profile = FirewallConstants.NET_FW_PROFILE2_PUBLIC;
+                                    break;
+                                case "all":
+                                    profile = FirewallConstants.NET_FW_PROFILE2_ALL;
+                                    break;
+                                default:
+                                    this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Profile", profileValue, "domain", "private", "public", "all"));
+                                    break;
+                            }
+                            break;
+                        case "Description":
+                            description = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
+                            break;
+                        default:
+                            this.ParseHelper.UnexpectedAttribute(element, attrib);
+                            break;
+                    }
+                }
+                else
+                {
+                    this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib);
+                }
+            }
+
+            // parse RemoteAddress children
+            foreach (XElement child in element.Elements())
+            {
+                if (this.Namespace == child.Name.Namespace)
+                {
+                    SourceLineNumber childSourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(child);
+                    switch (child.Name.LocalName)
+                    {
+                        case "RemoteAddress":
+                            if (null != scope)
+                            {
+                                this.Messaging.Write(FirewallErrors.IllegalRemoteAddressWithScopeAttribute(sourceLineNumbers));
+                            }
+                            else
+                            {
+                                this.ParseRemoteAddressElement(intermediate, section, child, ref remoteAddresses);
+                            }
+                            break;
+                        default:
+                            this.ParseHelper.UnexpectedElement(element, child);
+                            break;
+                    }
+                }
+                else
+                {
+                    this.ParseHelper.ParseExtensionElement(this.Context.Extensions, intermediate, section, element, child);
+                }
+            }
+
+            // Id and Name are required
+            if (null == id)
+            {
+                this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id"));
+            }
+
+            if (null == name)
+            {
+                this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name"));
+            }
+
+            // Scope or child RemoteAddress(es) are required
+            if (null == remoteAddresses)
+            {
+                this.Messaging.Write(ErrorMessages.ExpectedAttributeOrElement(sourceLineNumbers, element.Name.LocalName, "Scope", "RemoteAddress"));
+            }
+
+            // can't have both Program and File
+            if (null != program && null != file)
+            {
+                this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "File", "Program"));
+            }
+
+            // must be nested under File, have File or Program attributes, or have Port attribute
+            if (String.IsNullOrEmpty(fileId) && String.IsNullOrEmpty(file) && String.IsNullOrEmpty(program) && String.IsNullOrEmpty(port))
+            {
+                this.Messaging.Write(FirewallErrors.NoExceptionSpecified(sourceLineNumbers));
+            }
+
+            if (!this.Messaging.EncounteredError)
+            {
+                // at this point, File attribute and File parent element are treated the same
+                if (null != file)
+                {
+                    fileId = file;
+                }
+
+                var row = this.ParseHelper.CreateRow(section, sourceLineNumbers, "WixFirewallException", id);
+                row.Set(1, name);
+                row.Set(2, remoteAddresses);
+
+                if (!String.IsNullOrEmpty(port))
+                {
+                    row.Set(3, port);
+
+                    if (!protocol.HasValue)
+                    {
+                        // default protocol is "TCP"
+                        protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP;
+                    }
+                }
+
+                if (protocol.HasValue)
+                {
+                    row.Set(4, protocol);
+                }
+
+                if (!String.IsNullOrEmpty(fileId))
+                {
+                    row.Set(5, $"[#{fileId}]");
+                    this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "File", fileId);
+                }
+                else if (!String.IsNullOrEmpty(program))
+                {
+                    row.Set(5, program);
+                }
+
+                if (CompilerConstants.IntegerNotSet != attributes)
+                {
+                    row.Set(6, attributes);
+                }
+
+                // Default is "all"
+                row.Set(7, profile ?? FirewallConstants.NET_FW_PROFILE2_ALL);
+
+                row.Set(8, componentId);
+
+                row.Set(9, description);
+
+                if (this.Context.Platform == Platform.ARM)
+                {
+                    // Ensure ARM version of the CA is referenced
+                    this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "CustomAction", "WixSchedFirewallExceptionsInstall_ARM");
+                    this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "CustomAction", "WixSchedFirewallExceptionsUninstall_ARM");
+                }
+                else
+                {
+                    // All other supported platforms use x86
+                    this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "CustomAction", "WixSchedFirewallExceptionsInstall");
+                    this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "CustomAction", "WixSchedFirewallExceptionsUninstall");
+                }
+            }
+        }
+
+        /// <summary>
+        /// Parses a RemoteAddress element
+        /// </summary>
+        /// <param name="element">The element to parse.</param>
+        private void ParseRemoteAddressElement(Intermediate intermediate, IntermediateSection section, XElement element, ref string remoteAddresses)
+        {
+            SourceLineNumber sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
+
+            // no attributes
+            foreach (XAttribute attrib in element.Attributes())
+            {
+                if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
+                {
+                    this.ParseHelper.UnexpectedAttribute(element, attrib);
+                }
+                else
+                {
+                    this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib);
+                }
+            }
+
+            this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element);
+
+            string address = this.ParseHelper.GetTrimmedInnerText(element);
+            if (String.IsNullOrEmpty(address))
+            {
+                this.Messaging.Write(FirewallErrors.IllegalEmptyRemoteAddress(sourceLineNumbers));
+            }
+            else
+            {
+                if (String.IsNullOrEmpty(remoteAddresses))
+                {
+                    remoteAddresses = address;
+                }
+                else
+                {
+                    remoteAddresses = String.Concat(remoteAddresses, ",", address);
+                }
+            }
+        }
+    }
+}
diff --git a/src/wixext/FirewallConstants.cs b/src/wixext/FirewallConstants.cs
new file mode 100644
index 00000000..16caa5b4
--- /dev/null
+++ b/src/wixext/FirewallConstants.cs
@@ -0,0 +1,21 @@
+// 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.
+
+namespace WixToolset.Firewall
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+
+    static class FirewallConstants
+    {
+        // from icftypes.h
+        public const int NET_FW_IP_PROTOCOL_TCP = 6;
+        public const int NET_FW_IP_PROTOCOL_UDP = 17;
+
+        // from icftypes.h
+        public const int NET_FW_PROFILE2_DOMAIN = 0x0001;
+        public const int NET_FW_PROFILE2_PRIVATE = 0x0002;
+        public const int NET_FW_PROFILE2_PUBLIC = 0x0004;
+        public const int NET_FW_PROFILE2_ALL = 0x7FFFFFFF;
+    }
+}
diff --git a/src/wixext/FirewallDecompiler.cs b/src/wixext/FirewallDecompiler.cs
new file mode 100644
index 00000000..b060f8e2
--- /dev/null
+++ b/src/wixext/FirewallDecompiler.cs
@@ -0,0 +1,169 @@
+// 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.
+
+namespace WixToolset.Firewall
+{
+#if TODO_CONSIDER_DECOMPILER
+    using System;
+    using System.Collections;
+    using System.Diagnostics;
+    using System.Globalization;
+    using WixToolset.Data;
+    using WixToolset.Extensibility;
+    using Firewall = WixToolset.Extensions.Serialize.Firewall;
+    using Wix = WixToolset.Data.Serialize;
+
+    /// <summary>
+    /// The decompiler for the WiX Toolset Firewall Extension.
+    /// </summary>
+    public sealed class FirewallDecompiler : DecompilerExtension
+    {
+        /// <summary>
+        /// Creates a decompiler for Firewall Extension.
+        /// </summary>
+        public FirewallDecompiler()
+        {
+            this.TableDefinitions = FirewallExtensionData.GetExtensionTableDefinitions();
+        }
+
+        /// <summary>
+        /// Get the extensions library to be removed.
+        /// </summary>
+        /// <param name="tableDefinitions">Table definitions for library.</param>
+        /// <returns>Library to remove from decompiled output.</returns>
+        public override Library GetLibraryToRemove(TableDefinitionCollection tableDefinitions)
+        {
+            return FirewallExtensionData.GetExtensionLibrary(tableDefinitions);
+        }
+
+        /// <summary>
+        /// Decompiles an extension table.
+        /// </summary>
+        /// <param name="table">The table to decompile.</param>
+        public override void DecompileTable(Table table)
+        {
+            switch (table.Name)
+            {
+                case "WixFirewallException":
+                    this.DecompileWixFirewallExceptionTable(table);
+                    break;
+                default:
+                    base.DecompileTable(table);
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Decompile the WixFirewallException table.
+        /// </summary>
+        /// <param name="table">The table to decompile.</param>
+        private void DecompileWixFirewallExceptionTable(Table table)
+        {
+            foreach (Row row in table.Rows)
+            {
+                Firewall.FirewallException fire = new Firewall.FirewallException();
+                fire.Id = (string)row[0];
+                fire.Name = (string)row[1];
+
+                string[] addresses = ((string)row[2]).Split(',');
+                if (1 == addresses.Length)
+                {
+                    // special-case the Scope attribute values
+                    if ("*" == addresses[0])
+                    {
+                        fire.Scope = Firewall.FirewallException.ScopeType.any;
+                    }
+                    else if ("LocalSubnet" == addresses[0])
+                    {
+                        fire.Scope = Firewall.FirewallException.ScopeType.localSubnet;
+                    }
+                    else
+                    {
+                        FirewallDecompiler.AddRemoteAddress(fire, addresses[0]);
+                    }
+                }
+                else
+                {
+                    foreach (string address in addresses)
+                    {
+                        FirewallDecompiler.AddRemoteAddress(fire, address);
+                    }
+                }
+
+                if (!row.IsColumnEmpty(3))
+                {
+                    fire.Port = (string)row[3];
+                }
+
+                if (!row.IsColumnEmpty(4))
+                {
+                    switch (Convert.ToInt32(row[4]))
+                    {
+                        case FirewallConstants.NET_FW_IP_PROTOCOL_TCP:
+                            fire.Protocol = Firewall.FirewallException.ProtocolType.tcp;
+                            break;
+                        case FirewallConstants.NET_FW_IP_PROTOCOL_UDP:
+                            fire.Protocol = Firewall.FirewallException.ProtocolType.udp;
+                            break;
+                    }
+                }
+
+                if (!row.IsColumnEmpty(5))
+                {
+                    fire.Program = (string)row[5];
+                }
+
+                if (!row.IsColumnEmpty(6))
+                {
+                    int attr = Convert.ToInt32(row[6]);
+                    if (0x1 == (attr & 0x1)) // feaIgnoreFailures
+                    {
+                        fire.IgnoreFailure = Firewall.YesNoType.yes;
+                    }
+                }
+
+                if (!row.IsColumnEmpty(7))
+                {
+                    switch (Convert.ToInt32(row[7]))
+                    {
+                        case FirewallConstants.NET_FW_PROFILE2_DOMAIN:
+                            fire.Profile = Firewall.FirewallException.ProfileType.domain;
+                            break;
+                        case FirewallConstants.NET_FW_PROFILE2_PRIVATE:
+                            fire.Profile = Firewall.FirewallException.ProfileType.@private;
+                            break;
+                        case FirewallConstants.NET_FW_PROFILE2_PUBLIC:
+                            fire.Profile = Firewall.FirewallException.ProfileType.@public;
+                            break;
+                        case FirewallConstants.NET_FW_PROFILE2_ALL:
+                            fire.Profile = Firewall.FirewallException.ProfileType.all;
+                            break;
+                    }
+                }
+
+                // Description column is new in v3.6
+                if (9 < row.Fields.Length && !row.IsColumnEmpty(9))
+                {
+                    fire.Description = (string)row[9];
+                }
+
+                Wix.Component component = (Wix.Component)this.Core.GetIndexedElement("Component", (string)row[8]);
+                if (null != component)
+                {
+                    component.AddChild(fire);
+                }
+                else
+                {
+                    this.Core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", (string)row[6], "Component"));
+                }
+            }
+        }
+
+        private static void AddRemoteAddress(Firewall.FirewallException fire, string address)
+        {
+            Firewall.RemoteAddress remote = new Firewall.RemoteAddress();
+            remote.Content = address;
+            fire.AddChild(remote);
+        }
+    }
+#endif
+}
diff --git a/src/wixext/FirewallErrors.cs b/src/wixext/FirewallErrors.cs
new file mode 100644
index 00000000..3fff8c8d
--- /dev/null
+++ b/src/wixext/FirewallErrors.cs
@@ -0,0 +1,42 @@
+// 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.
+
+namespace WixToolset.Firewall
+{
+    using System.Resources;
+    using WixToolset.Data;
+
+    public static class FirewallErrors
+    {
+        public static Message IllegalRemoteAddressWithScopeAttribute(SourceLineNumber sourceLineNumbers)
+        {
+            return Message(sourceLineNumbers, Ids.IllegalRemoteAddressWithScopeAttribute, "The RemoteAddress element cannot be specified because its parent FirewallException already specified the Scope attribute. To use RemoteAddress elements, omit the Scope attribute.");
+        }
+
+        public static Message IllegalEmptyRemoteAddress(SourceLineNumber sourceLineNumbers)
+        {
+            return Message(sourceLineNumbers, Ids.IllegalEmptyRemoteAddress, "The RemoteAddress element's inner text cannot be an empty string or completely whitespace.");
+        }
+
+        public static Message NoExceptionSpecified(SourceLineNumber sourceLineNumbers)
+        {
+            return Message(sourceLineNumbers, Ids.NoExceptionSpecified, "The FirewallException element doesn't identify the target of the firewall exception. To create an application exception, nest the FirewallException element under a File element or provide a value for the File or Program attributes. To create a port exception, provide a value for the Port attribute.");
+        }
+
+        private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
+        {
+            return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
+        }
+
+        private static Message Message(SourceLineNumber sourceLineNumber, Ids id, ResourceManager resourceManager, string resourceName, params object[] args)
+        {
+            return new Message(sourceLineNumber, MessageLevel.Error, (int)id, resourceManager, resourceName, args);
+        }
+
+        public enum Ids
+        {
+            IllegalRemoteAddressWithScopeAttribute = 6401,
+            IllegalEmptyRemoteAddress = 6402,
+            NoExceptionSpecified = 6403,
+        }
+    }
+}
diff --git a/src/wixext/FirewallExtensionData.cs b/src/wixext/FirewallExtensionData.cs
new file mode 100644
index 00000000..78939c4e
--- /dev/null
+++ b/src/wixext/FirewallExtensionData.cs
@@ -0,0 +1,24 @@
+// 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.
+
+namespace WixToolset.Firewall
+{
+    using WixToolset.Data;
+    using WixToolset.Extensibility;
+    using WixToolset.Firewall.Tuples;
+
+    public sealed class FirewallExtensionData : BaseExtensionData
+    {
+        public override string DefaultCulture => "en-US";
+
+        public override bool TryGetTupleDefinitionByName(string name, out IntermediateTupleDefinition tupleDefinition)
+        {
+            tupleDefinition = (name == FirewallTupleDefinitionNames.WixFirewallException) ? FirewallTupleDefinitions.WixFirewallException : null;
+            return tupleDefinition != null;
+        }
+
+        public override Intermediate GetLibrary(ITupleDefinitionCreator tupleDefinitions)
+        {
+            return Intermediate.Load(typeof(FirewallExtensionData).Assembly, "WixToolset.Firewall.firewall.wixlib", tupleDefinitions);
+        }
+    }
+}
diff --git a/src/wixext/FirewallExtensionFactory.cs b/src/wixext/FirewallExtensionFactory.cs
new file mode 100644
index 00000000..4594419d
--- /dev/null
+++ b/src/wixext/FirewallExtensionFactory.cs
@@ -0,0 +1,18 @@
+// 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.
+
+namespace WixToolset.Firewall
+{
+    using System;
+    using System.Collections.Generic;
+    using WixToolset.Extensibility;
+
+    public class FirewallExtensionFactory : BaseExtensionFactory
+    {
+        protected override IEnumerable<Type> ExtensionTypes => new[]
+        {
+            typeof(FirewallCompiler),
+            typeof(FirewallExtensionData),
+            typeof(FirewallWindowsInstallerBackendExtension),
+        };
+    }
+}
diff --git a/src/wixext/FirewallWindowsInstallerBackendExtension.cs b/src/wixext/FirewallWindowsInstallerBackendExtension.cs
new file mode 100644
index 00000000..a1c78f04
--- /dev/null
+++ b/src/wixext/FirewallWindowsInstallerBackendExtension.cs
@@ -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.
+
+namespace WixToolset.Firewall
+{
+    using System.Linq;
+    using System.Xml;
+    using WixToolset.Data.WindowsInstaller;
+    using WixToolset.Extensibility;
+
+    public class FirewallWindowsInstallerBackendExtension : BaseWindowsInstallerBackendExtension
+    {
+        private static readonly TableDefinition[] Tables = LoadTables();
+
+        protected override TableDefinition[] TableDefinitionsForTuples => Tables;
+
+        private static TableDefinition[] LoadTables()
+        {
+            using (var resourceStream = typeof(FirewallWindowsInstallerBackendExtension).Assembly.GetManifestResourceStream("WixToolset.Firewall.tables.xml"))
+            using (var reader = XmlReader.Create(resourceStream))
+            {
+                var tables = TableDefinitionCollection.Load(reader);
+                return tables.ToArray();
+            }
+        }
+    }
+}
diff --git a/src/wixext/Tuples/FirewallTupleDefinitions.cs b/src/wixext/Tuples/FirewallTupleDefinitions.cs
new file mode 100644
index 00000000..79fc28cf
--- /dev/null
+++ b/src/wixext/Tuples/FirewallTupleDefinitions.cs
@@ -0,0 +1,31 @@
+// 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.
+
+namespace WixToolset.Firewall.Tuples
+{
+    using WixToolset.Data;
+
+    public static class FirewallTupleDefinitionNames
+    {
+        public static string WixFirewallException { get; } = "WixFirewallException";
+    }
+
+    public static partial class FirewallTupleDefinitions
+    {
+        public static readonly IntermediateTupleDefinition WixFirewallException = new IntermediateTupleDefinition(
+            FirewallTupleDefinitionNames.WixFirewallException,
+            new[]
+            {
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.WixFirewallException), IntermediateFieldType.String),
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Name), IntermediateFieldType.String),
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.RemoteAddresses), IntermediateFieldType.String),
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Port), IntermediateFieldType.String),
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Protocol), IntermediateFieldType.Number),
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Program), IntermediateFieldType.String),
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Attributes), IntermediateFieldType.Number),
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Profile), IntermediateFieldType.Number),
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Component_), IntermediateFieldType.String),
+                new IntermediateFieldDefinition(nameof(WixFirewallExceptionTupleFields.Description), IntermediateFieldType.String),
+            },
+            typeof(WixFirewallExceptionTuple));
+    }
+}
diff --git a/src/wixext/Tuples/WixFirewallExceptionTuple.cs b/src/wixext/Tuples/WixFirewallExceptionTuple.cs
new file mode 100644
index 00000000..715a4b9b
--- /dev/null
+++ b/src/wixext/Tuples/WixFirewallExceptionTuple.cs
@@ -0,0 +1,93 @@
+// 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.
+
+namespace WixToolset.Firewall.Tuples
+{
+    using WixToolset.Data;
+
+    public enum WixFirewallExceptionTupleFields
+    {
+        WixFirewallException,
+        Name,
+        RemoteAddresses,
+        Port,
+        Protocol,
+        Program,
+        Attributes,
+        Profile,
+        Component_,
+        Description,
+    }
+
+    public class WixFirewallExceptionTuple : IntermediateTuple
+    {
+        public WixFirewallExceptionTuple() : base(FirewallTupleDefinitions.WixFirewallException, null, null)
+        {
+        }
+
+        public WixFirewallExceptionTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(FirewallTupleDefinitions.WixFirewallException, sourceLineNumber, id)
+        {
+        }
+
+        public IntermediateField this[WixFirewallExceptionTupleFields index] => this.Fields[(int)index];
+
+        public string WixFirewallException
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.WixFirewallException].AsString();
+            set => this.Set((int)WixFirewallExceptionTupleFields.WixFirewallException, value);
+        }
+
+        public string Name
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.Name].AsString();
+            set => this.Set((int)WixFirewallExceptionTupleFields.Name, value);
+        }
+
+        public string RemoteAddresses
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.RemoteAddresses].AsString();
+            set => this.Set((int)WixFirewallExceptionTupleFields.RemoteAddresses, value);
+        }
+
+        public string Port
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.Port].AsString();
+            set => this.Set((int)WixFirewallExceptionTupleFields.Port, value);
+        }
+
+        public int Protocol
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.Protocol].AsNumber();
+            set => this.Set((int)WixFirewallExceptionTupleFields.Protocol, value);
+        }
+
+        public string Program
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.Program].AsString();
+            set => this.Set((int)WixFirewallExceptionTupleFields.Program, value);
+        }
+
+        public int Attributes
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.Attributes].AsNumber();
+            set => this.Set((int)WixFirewallExceptionTupleFields.Attributes, value);
+        }
+
+        public int Profile
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.Profile].AsNumber();
+            set => this.Set((int)WixFirewallExceptionTupleFields.Profile, value);
+        }
+
+        public string Component_
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.Component_].AsString();
+            set => this.Set((int)WixFirewallExceptionTupleFields.Component_, value);
+        }
+
+        public string Description
+        {
+            get => this.Fields[(int)WixFirewallExceptionTupleFields.Description].AsString();
+            set => this.Set((int)WixFirewallExceptionTupleFields.Description, value);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/wixext/WixToolset.Firewall.wixext.csproj b/src/wixext/WixToolset.Firewall.wixext.csproj
new file mode 100644
index 00000000..2d89911c
--- /dev/null
+++ b/src/wixext/WixToolset.Firewall.wixext.csproj
@@ -0,0 +1,36 @@
+<?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 Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <RootNamespace>WixToolset.Firewall</RootNamespace>
+    <Description>WiX Toolset Firewallity Extension</Description>
+    <Title>WiX Toolset Firewall Extension</Title>
+    <IsTool>true</IsTool>
+    <ContentTargetFolders>build</ContentTargetFolders>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Content Include="$(MSBuildThisFileName).targets" />
+    <Content Include="firewall.xsd" PackagePath="tools" />
+    <EmbeddedResource Include="tables.xml" />
+    <EmbeddedResource Include="$(OutputPath)..\firewall.wixlib" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Data\README.md') " />
+    <PackageReference Include="WixToolset.Data" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Data\README.md') " PrivateAssets="all" />
+
+    <ProjectReference Include="$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Extensibility\README.md') " />
+    <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Extensibility\README.md') " PrivateAssets="all" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\wixlib\firewall.wixproj" ReferenceOutputAssembly="false" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Nerdbank.GitVersioning" Version="2.1.7" PrivateAssets="all" />
+  </ItemGroup>
+</Project>
diff --git a/src/wixext/WixToolset.Firewall.wixext.targets b/src/wixext/WixToolset.Firewall.wixext.targets
new file mode 100644
index 00000000..c717450f
--- /dev/null
+++ b/src/wixext/WixToolset.Firewall.wixext.targets
@@ -0,0 +1,11 @@
+<?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 xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
+  <PropertyGroup>
+    <WixToolsetFirewallWixextPath Condition=" '$(WixToolsetFirewallWixextPath)' == '' ">$(MSBuildThisFileDirectory)..\tools\WixToolset.Firewall.wixext.dll</WixToolsetFirewallWixextPath>
+  </PropertyGroup>
+  <ItemGroup>
+    <WixExtension Include="$(WixToolsetFirewallWixextPath)" />
+  </ItemGroup>
+</Project>
diff --git a/src/wixext/firewall.xsd b/src/wixext/firewall.xsd
new file mode 100644
index 00000000..d64aafef
--- /dev/null
+++ b/src/wixext/firewall.xsd
@@ -0,0 +1,211 @@
+<?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. -->
+
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+          xmlns:xse=" http://wixtoolset.org/schemas/XmlSchemaExtension"
+         xmlns:html="http://www.w3.org/1999/xhtml"
+    targetNamespace="http://wixtoolset.org/schemas/v4/wxs/firewall"
+              xmlns="http://wixtoolset.org/schemas/v4/wxs/firewall">
+    <xs:annotation>
+        <xs:documentation>
+            The source code schema for the WiX Toolset Firewall Extension.
+        </xs:documentation>
+    </xs:annotation>
+
+    <xs:import namespace="http://wixtoolset.org/schemas/v4/wxs" />
+
+    <xs:element name="FirewallException">
+        <xs:annotation>
+            <xs:documentation>
+                Registers an exception for a program or a specific port and protocol in the Windows Firewall
+                on Windows XP SP2, Windows Server 2003 SP1, and later. For more information about the Windows
+                Firewall, see <html:a href="http://msdn.microsoft.com/en-us/library/aa364679.aspx">
+                About Windows Firewall API</html:a>.
+            </xs:documentation>
+            <xs:appinfo>
+                <xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="Component" />
+                <xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="File" />
+            </xs:appinfo>
+        </xs:annotation>
+
+        <xs:complexType>
+            <xs:choice minOccurs="0" maxOccurs="unbounded">
+                <xs:annotation>
+                    <xs:documentation>
+                        Explicitly-listed remote addresses that this exception allows through the 
+                        firewall.
+                    </xs:documentation>
+                </xs:annotation>
+                <xs:element ref="RemoteAddress" />
+            </xs:choice>
+
+            <xs:attribute name="Id" type="xs:string" use="required">
+                <xs:annotation>
+                    <xs:documentation>
+                        Unique ID of this firewall exception.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+
+            <xs:attribute name="Name" type="xs:string" use="required">
+                <xs:annotation>
+                    <xs:documentation>
+                        Name of this firewall exception, visible to the user in the firewall 
+                        control panel.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+
+            <xs:attribute name="Scope">
+                <xs:annotation>
+                    <xs:documentation>
+                        The scope of this firewall exception, which indicates whether incoming
+                        connections can come from any computer including those on the Internet
+                        or only those on the local network subnet. To more precisely specify
+                        allowed remote address, specify a custom scope using RemoteAddress 
+                        child elements.
+                    </xs:documentation>
+                </xs:annotation>
+                <xs:simpleType>
+                    <xs:restriction base="xs:NMTOKEN">
+                        <xs:enumeration value="any" />
+                        <xs:enumeration value="localSubnet" />
+                    </xs:restriction>
+                </xs:simpleType>
+            </xs:attribute>
+
+            <xs:attribute name="Port" type="xs:string">
+                <xs:annotation>
+                    <xs:documentation>
+                        Port to allow through the firewall for this exception. 
+
+                        If you use Port and also File or Program in the same 
+                        FirewallException element, the exception will fail to install on 
+                        Windows XP and Windows Server 2003. IgnoreFailure="yes" can be used to
+                        ignore the resulting failure, but the exception will not be added.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+
+            <xs:attribute name="Protocol">
+                <xs:annotation>
+                    <xs:documentation>
+                        IP protocol used for this firewall exception. If Port is defined, 
+                        "tcp" is assumed if the protocol is not specified. 
+
+                        If you use Protocol and also File or Program in the same 
+                        FirewallException element, the exception will fail to install on 
+                        Windows XP and Windows Server 2003. IgnoreFailure="yes" can be used to
+                        ignore the resulting failure, but the exception will not be added.
+                    </xs:documentation>
+                </xs:annotation>
+                <xs:simpleType>
+                    <xs:restriction base="xs:NMTOKEN">
+                        <xs:enumeration value="tcp" />
+                        <xs:enumeration value="udp" />
+                    </xs:restriction>
+                </xs:simpleType>
+            </xs:attribute>
+
+            <xs:attribute name="File" type="xs:string">
+                <xs:annotation>
+                    <xs:documentation>
+                        Identifier of a file to be granted access to all incoming ports and 
+                        protocols. If you use File, you cannot also use Program.
+
+                        If you use File and also Port or Protocol in the same 
+                        FirewallException element, the exception will fail to install on 
+                        Windows XP and Windows Server 2003. IgnoreFailure="yes" can be used to
+                        ignore the resulting failure, but the exception will not be added.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+
+            <xs:attribute name="Program" type="xs:string">
+                <xs:annotation>
+                    <xs:documentation>
+                        Path to a target program to be granted access to all incoming ports and 
+                        protocols. Note that this is a formatted field, so you can use [#fileId] 
+                        syntax to refer to a file being installed. If you use Program, you cannot 
+                        also use File.
+
+                        If you use Program and also Port or Protocol in the same 
+                        FirewallException element, the exception will fail to install on 
+                        Windows XP and Windows Server 2003. IgnoreFailure="yes" can be used to
+                        ignore the resulting failure, but the exception will not be added.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+
+            <xs:attribute name="IgnoreFailure" type="YesNoType">
+                <xs:annotation>
+                    <xs:documentation>
+                        If "yes," failures to register this firewall exception will be silently 
+                        ignored. If "no" (the default), failures will cause rollback.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+
+            <xs:attribute name="Profile">
+              <xs:annotation>
+                <xs:documentation>
+                  Profile type for this firewall exception. Default is "all".
+                </xs:documentation>
+              </xs:annotation>
+              <xs:simpleType>
+                <xs:restriction base="xs:NMTOKEN">
+                  <xs:enumeration value="domain" />
+                  <xs:enumeration value="private" />
+                  <xs:enumeration value="public" />
+                  <xs:enumeration value="all" />
+                </xs:restriction>
+              </xs:simpleType>
+            </xs:attribute>
+            <xs:attribute name="Description" type="xs:string">
+                <xs:annotation>
+                    <xs:documentation>
+                      Description for this firewall rule displayed in Windows Firewall manager in 
+                      Windows Vista and later.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="RemoteAddress">
+        <xs:annotation>
+            <xs:documentation>
+                A remote address to which the port or program can listen. Address formats vary 
+                based on the version of Windows and Windows Firewall the program is being installed
+                on. For Windows XP SP2 and Windows Server 2003 SP1, see
+                <html:a href="http://msdn.microsoft.com/en-us/library/aa365270.aspx">
+                    RemoteAddresses Property</html:a>.
+                For Windows Vista and Windows Server 2008, see
+                <html:a href="http://msdn.microsoft.com/en-us/library/aa365366.aspx">
+                    RemoteAddresses Property</html:a>.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+            <xs:simpleContent>
+                <xs:extension base="xs:string">
+                    <xs:annotation>
+                        <xs:documentation>
+                            A remote address.
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:extension>
+            </xs:simpleContent>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:simpleType name="YesNoType">
+        <xs:annotation>
+            <xs:documentation>Values of this type will either be "yes" or "no".</xs:documentation>
+        </xs:annotation>
+        <xs:restriction base='xs:NMTOKEN'>
+            <xs:enumeration value="no"/>
+            <xs:enumeration value="yes"/>
+        </xs:restriction>
+    </xs:simpleType>
+</xs:schema>
diff --git a/src/wixext/tables.xml b/src/wixext/tables.xml
new file mode 100644
index 00000000..5b408b96
--- /dev/null
+++ b/src/wixext/tables.xml
@@ -0,0 +1,28 @@
+<?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. -->
+
+
+<tableDefinitions xmlns="http://wixtoolset.org/schemas/v4/wi/tables">
+    <tableDefinition name="WixFirewallException">
+        <columnDefinition name="WixFirewallException" type="string" length="72" primaryKey="yes" modularize="column"
+            category="identifier" description="The primary key, a non-localized token." />
+        <columnDefinition name="Name" type="localized" length="255" nullable="yes" modularize="property" 
+            category="formatted" description="Localizable display name." />
+        <columnDefinition name="RemoteAddresses" type="string" length="0" modularize="property" 
+            category="formatted" description="Remote address to accept incoming connections from." />
+        <columnDefinition name="Port" type="string" length="0" modularize="property" nullable="yes" 
+            category="formatted" minValue="1" description="Port number." />
+        <columnDefinition name="Protocol" type="number" length="1" nullable="yes" 
+            category="integer" minValue="6" maxValue="17" description="Protocol (6=TCP; 17=UDP)." />
+        <columnDefinition name="Program" type="string" length="255" nullable="yes" modularize="property"
+            category="formatted" description="Exception for a program (formatted path name)." />
+        <columnDefinition name="Attributes" type="number" length="4" nullable="yes" 
+            minValue="0" maxValue="65536" description="Vital=1" />
+        <columnDefinition name="Profile" type="number" length="4" nullable="no"
+            category="integer" minValue="1" maxValue="2147483647" description="Profile (1=domain; 2=private; 4=public; 2147483647=all)." />
+        <columnDefinition name="Component_" type="string" length="72" modularize="column"
+            keyTable="Component" keyColumn="1" category="identifier" description="Foreign key into the Component table referencing component that controls the firewall configuration."/>
+        <columnDefinition name="Description" type="string" length="255" nullable="yes"
+            category="formatted" description="Description displayed in Windows Firewall manager for this firewall rule."/>
+    </tableDefinition>
+</tableDefinitions>
diff --git a/src/wixlib/FirewallExtension.wxs b/src/wixlib/FirewallExtension.wxs
new file mode 100644
index 00000000..271469e4
--- /dev/null
+++ b/src/wixlib/FirewallExtension.wxs
@@ -0,0 +1,12 @@
+<?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. -->
+
+
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <?include caerr.wxi ?>
+    <Fragment>
+        <UI Id="WixFirewallErrors">
+            <Error Id="$(var.msierrFirewallCannotConnect)">!(loc.msierrFirewallCannotConnect)</Error>
+        </UI>
+    </Fragment>
+</Wix>
diff --git a/src/wixlib/FirewallExtension_Platform.wxi b/src/wixlib/FirewallExtension_Platform.wxi
new file mode 100644
index 00000000..263651d2
--- /dev/null
+++ b/src/wixlib/FirewallExtension_Platform.wxi
@@ -0,0 +1,41 @@
+<?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. -->
+
+<Include xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <?include caSuffix.wxi ?>
+    <Fragment>
+        <UIRef Id="WixFirewallErrors" />
+        <UI>
+            <ProgressText Action="WixSchedFirewallExceptionsInstall$(var.Suffix)">!(loc.WixSchedFirewallExceptionsInstall)</ProgressText>
+            <ProgressText Action="WixSchedFirewallExceptionsUninstall$(var.Suffix)">!(loc.WixSchedFirewallExceptionsUninstall)</ProgressText>
+            <ProgressText Action="WixRollbackFirewallExceptionsInstall$(var.DeferredSuffix)">!(loc.WixRollbackFirewallExceptionsInstall)</ProgressText>
+            <ProgressText Action="WixExecFirewallExceptionsInstall$(var.DeferredSuffix)">!(loc.WixExecFirewallExceptionsInstall)</ProgressText>
+            <ProgressText Action="WixRollbackFirewallExceptionsUninstall$(var.DeferredSuffix)">!(loc.WixRollbackFirewallExceptionsUninstall)</ProgressText>
+            <ProgressText Action="WixExecFirewallExceptionsUninstall$(var.DeferredSuffix)">!(loc.WixExecFirewallExceptionsUninstall)</ProgressText>
+        </UI>
+
+        <CustomAction Id="WixSchedFirewallExceptionsInstall$(var.Suffix)" BinaryKey="WixFirewallCA$(var.Suffix)" DllEntry="SchedFirewallExceptionsInstall" Execute="immediate" Return="check" SuppressModularization="yes" />
+        <CustomAction Id="WixSchedFirewallExceptionsUninstall$(var.Suffix)" BinaryKey="WixFirewallCA$(var.Suffix)" DllEntry="SchedFirewallExceptionsUninstall" Execute="immediate" Return="check" SuppressModularization="yes" />
+        <CustomAction Id="WixRollbackFirewallExceptionsInstall$(var.DeferredSuffix)" BinaryKey="WixFirewallCA$(var.Suffix)" DllEntry="ExecFirewallExceptions" Execute="rollback" Impersonate="no" Return="check" SuppressModularization="yes" />
+        <CustomAction Id="WixExecFirewallExceptionsInstall$(var.DeferredSuffix)" BinaryKey="WixFirewallCA$(var.Suffix)" DllEntry="ExecFirewallExceptions" Execute="deferred" Impersonate="no" Return="check" SuppressModularization="yes" />
+        <CustomAction Id="WixRollbackFirewallExceptionsUninstall$(var.DeferredSuffix)" BinaryKey="WixFirewallCA$(var.Suffix)" DllEntry="ExecFirewallExceptions" Execute="rollback" Impersonate="no" Return="check" SuppressModularization="yes" />
+        <CustomAction Id="WixExecFirewallExceptionsUninstall$(var.DeferredSuffix)" BinaryKey="WixFirewallCA$(var.Suffix)" DllEntry="ExecFirewallExceptions" Execute="deferred" Impersonate="no" Return="check" SuppressModularization="yes" />
+
+        <!--
+            We need the firewall on Windows XP SP2 or later.
+        -->
+        <InstallExecuteSequence>
+            <Custom Action="WixSchedFirewallExceptionsUninstall$(var.Suffix)" Before="RemoveFiles" Overridable="yes">
+                <![CDATA[ VersionNT >= 600 OR (VersionNT >= 501 AND ((MsiNTProductType = 1 AND ServicePackLevel >= 2) OR (MsiNTProductType > 1 AND ServicePackLevel >= 1))) ]]>
+            </Custom>
+            <Custom Action="WixSchedFirewallExceptionsInstall$(var.Suffix)" After="InstallFiles" Overridable="yes">
+                <![CDATA[ VersionNT >= 600 OR (VersionNT >= 501 AND ((MsiNTProductType = 1 AND ServicePackLevel >= 2) OR (MsiNTProductType > 1 AND ServicePackLevel >= 1))) ]]>
+            </Custom>
+        </InstallExecuteSequence>
+    </Fragment>
+
+    <!-- Firewall Custom Action DLL Definitions -->
+    <Fragment>
+        <Binary Id="WixFirewallCA$(var.Suffix)" SourceFile="!(bindpath.$(var.platform))fwca.dll" />
+    </Fragment>
+</Include>
diff --git a/src/wixlib/FirewallExtension_x86.wxs b/src/wixlib/FirewallExtension_x86.wxs
new file mode 100644
index 00000000..b43a4c5f
--- /dev/null
+++ b/src/wixlib/FirewallExtension_x86.wxs
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<!-- 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. -->
+
+
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <?define platform=x86 ?>
+    <?include FirewallExtension_Platform.wxi ?>
+</Wix>
diff --git a/src/wixlib/caSuffix.wxi b/src/wixlib/caSuffix.wxi
new file mode 100644
index 00000000..a56a2393
--- /dev/null
+++ b/src/wixlib/caSuffix.wxi
@@ -0,0 +1,28 @@
+<?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. -->
+
+<Include xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <?ifndef platform ?>
+        <?error Required value "platform" not defined in include caSuffix.wxi ?>
+    <?endif ?>
+
+    <?ifdef Suffix ?>
+        <?undef Suffix ?>
+        <?undef DeferredSuffix ?>
+    <?endif ?>
+
+    <?if $(var.platform)="x86" ?>
+        <?define Suffix="" ?>
+        <?define DeferredSuffix="" ?>
+    <?endif ?>
+
+    <?if $(var.platform)="x64" ?>
+        <?define Suffix="_x64" ?>
+        <?define DeferredSuffix="_64" ?>
+    <?endif ?>
+
+    <?if $(var.platform)="arm" ?>
+        <?define Suffix="_ARM" ?>
+        <?define DeferredSuffix="_ARM" ?>
+    <?endif ?>
+</Include>
diff --git a/src/wixlib/caerr.wxi b/src/wixlib/caerr.wxi
new file mode 100644
index 00000000..141942f2
--- /dev/null
+++ b/src/wixlib/caerr.wxi
@@ -0,0 +1,96 @@
+<Include>
+  <?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?>
+  <?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?>
+  <?define msierrDotNetRuntimeRequired = 27000?>
+  <?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?>
+</Include>
\ No newline at end of file
diff --git a/src/wixlib/en-us.wxl b/src/wixlib/en-us.wxl
new file mode 100644
index 00000000..7db8a9c2
--- /dev/null
+++ b/src/wixlib/en-us.wxl
@@ -0,0 +1,14 @@
+<?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. -->
+
+
+<WixLocalization Culture="en-us" xmlns="http://wixtoolset.org/schemas/v4/wxl">
+    <String Id="msierrFirewallCannotConnect" Overridable="yes">Cannot connect to Windows Firewall.  ([2]   [3]   [4]   [5])</String>
+    
+    <String Id="WixSchedFirewallExceptionsInstall" Overridable="yes">Configuring Windows Firewall</String>
+    <String Id="WixSchedFirewallExceptionsUninstall" Overridable="yes">Configuring Windows Firewall</String>
+    <String Id="WixRollbackFirewallExceptionsInstall" Overridable="yes">Rolling back Windows Firewall configuration</String>
+    <String Id="WixExecFirewallExceptionsInstall" Overridable="yes">Installing Windows Firewall configuration</String>
+    <String Id="WixRollbackFirewallExceptionsUninstall" Overridable="yes">Rolling back Windows Firewall configuration</String>
+    <String Id="WixExecFirewallExceptionsUninstall" Overridable="yes">Uninstalling Windows Firewall configuration</String>
+</WixLocalization>
diff --git a/src/wixlib/es-es.wxl b/src/wixlib/es-es.wxl
new file mode 100644
index 00000000..45d5fd0e
--- /dev/null
+++ b/src/wixlib/es-es.wxl
@@ -0,0 +1,13 @@
+<?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. -->
+
+<WixLocalization Culture="es-es" xmlns="http://wixtoolset.org/schemas/v4/wxl">
+  <String Id="msierrFirewallCannotConnect" Overridable="yes">No se puede conectar al Firewall de Windows.  ([2]   [3]   [4]   [5])</String>
+
+  <String Id="WixSchedFirewallExceptionsInstall" Overridable="yes">Configurando el Firewall de Windows</String>
+  <String Id="WixSchedFirewallExceptionsUninstall" Overridable="yes">Configurando el Firewall de Windows</String>
+  <String Id="WixRollbackFirewallExceptionsInstall" Overridable="yes">Regresando la configuración del Firewall de Windows</String>
+  <String Id="WixExecFirewallExceptionsInstall" Overridable="yes">Instalando la configuración del Firewall de Windows</String>
+  <String Id="WixRollbackFirewallExceptionsUninstall" Overridable="yes">Regresando la configuración del Firewall de Windows</String>
+  <String Id="WixExecFirewallExceptionsUninstall" Overridable="yes">Desinstalando la configuración del Firewall de Windows</String>
+</WixLocalization>
diff --git a/src/wixlib/firewall.wixproj b/src/wixlib/firewall.wixproj
new file mode 100644
index 00000000..fc700d61
--- /dev/null
+++ b/src/wixlib/firewall.wixproj
@@ -0,0 +1,44 @@
+<?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" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="15.0">
+  <Import Project="..\..\packages\WixToolset.Core.InternalPackage.4.0.64\build\WixToolset.Core.InternalPackage.props" Condition="Exists('..\..\packages\WixToolset.Core.InternalPackage.4.0.64\build\WixToolset.Core.InternalPackage.props')" />
+  <Import Project="..\FindLocalWix.props" />
+  <PropertyGroup>
+    <ProjectGuid>{1acffefd-505a-41a5-acbf-a02b7b473aa2}</ProjectGuid>
+    <OutputName>firewall</OutputName>
+    <OutputType>Library</OutputType>
+    <BindFiles>true</BindFiles>
+    <Pedantic>true</Pedantic>
+    <Cultures>en-us</Cultures>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="FirewallExtension.wxs" />
+    <Compile Include="FirewallExtension_x86.wxs" />
+    <EmbeddedResource Include="en-us.wxl" />
+    <EmbeddedResource Include="es-es.wxl" />
+    <EmbeddedResource Include="ja-jp.wxl" />
+    <EmbeddedResource Include="pl-pl.wxl" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\ca\fwca.vcxproj">
+      <Name>fwca</Name>
+      <Project>{F72D34CA-48DA-4DFD-91A9-A0C78BEF6981}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' AND Exists('$(WixTargetsPath)') " />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets') " />
+  <Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' ">
+    <Error Text="FG-WiX or WiX Toolset build tools (v3.11 or later) must be installed to build this project. To download FG-WiX, go to https://www.firegiant.com/downloads/. To download the WiX Toolset, go to http://wixtoolset.org/releases/." />
+  </Target>
+  <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\Nerdbank.GitVersioning.2.1.7\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Nerdbank.GitVersioning.2.1.7\build\Nerdbank.GitVersioning.targets'))" />
+    <Error Condition="!Exists('..\..\packages\WixToolset.Core.InternalPackage.4.0.64\build\WixToolset.Core.InternalPackage.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\WixToolset.Core.InternalPackage.4.0.64\build\WixToolset.Core.InternalPackage.props'))" />
+  </Target>
+  <Import Project="..\..\packages\Nerdbank.GitVersioning.2.1.7\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\packages\Nerdbank.GitVersioning.2.1.7\build\Nerdbank.GitVersioning.targets')" />
+</Project>
\ No newline at end of file
diff --git a/src/wixlib/ja-jp.wxl b/src/wixlib/ja-jp.wxl
new file mode 100644
index 00000000..b6c0b96e
--- /dev/null
+++ b/src/wixlib/ja-jp.wxl
@@ -0,0 +1,14 @@
+<?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. -->
+
+
+<WixLocalization Culture="ja-jp" xmlns="http://wixtoolset.org/schemas/v4/wxl">
+  <String Id="msierrFirewallCannotConnect" Overridable="yes">Windows ファイアウォールへ接続できません。  ([2]   [3]   [4]   [5])</String>
+
+  <String Id="WixSchedFirewallExceptionsInstall" Overridable="yes">Windows ファイアウォールを構成しています</String>
+  <String Id="WixSchedFirewallExceptionsUninstall" Overridable="yes">Windows ファイアウォールを構成しています</String>
+  <String Id="WixRollbackFirewallExceptionsInstall" Overridable="yes">Windows ファイアウォール構成をロールバックしています</String>
+  <String Id="WixExecFirewallExceptionsInstall" Overridable="yes">Windows ファイアウォール構成をインストールしています</String>
+  <String Id="WixRollbackFirewallExceptionsUninstall" Overridable="yes">Windows ファイアウォール構成をロールバックしています</String>
+  <String Id="WixExecFirewallExceptionsUninstall" Overridable="yes">Windows ファイアウォール構成をアンインストールしています</String>
+</WixLocalization>
diff --git a/src/wixlib/packages.config b/src/wixlib/packages.config
new file mode 100644
index 00000000..4250845c
--- /dev/null
+++ b/src/wixlib/packages.config
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Nerdbank.GitVersioning" version="2.1.7" developmentDependency="true" targetFramework="net40" />
+  <package id="WixToolset.Core.InternalPackage" version="4.0.64" targetFramework="net40" developmentDependency="true" />
+</packages>
\ No newline at end of file
diff --git a/src/wixlib/pl-pl.wxl b/src/wixlib/pl-pl.wxl
new file mode 100644
index 00000000..a685917c
--- /dev/null
+++ b/src/wixlib/pl-pl.wxl
@@ -0,0 +1,14 @@
+<?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. -->
+
+
+<WixLocalization Culture="pl-pl" xmlns="http://wixtoolset.org/schemas/v4/wxl">
+    <String Id="msierrFirewallCannotConnect" Overridable="yes">Nie udało się połączyć z Zaporą systemu Windows.  ([2]   [3]   [4]   [5])</String>
+    
+    <String Id="WixSchedFirewallExceptionsInstall" Overridable="yes">Dodawanie wyjątków do Zapory systemu Windows</String>
+    <String Id="WixSchedFirewallExceptionsUninstall" Overridable="yes">Usuwanie wyjątków z Zapory systemu Windows</String>
+    <String Id="WixRollbackFirewallExceptionsInstall" Overridable="yes">Cofanie zmian konfiguracji Zapory systemu Windows</String>
+    <String Id="WixExecFirewallExceptionsInstall" Overridable="yes">Konfigurowywanie Zapory systemu Windows</String>
+    <String Id="WixRollbackFirewallExceptionsUninstall" Overridable="yes">Cofanie zmian konfiguracji Zapory systemu Windows</String>
+    <String Id="WixExecFirewallExceptionsUninstall" Overridable="yes">Konfigurowywanie Zapory systemu Windows</String>
+</WixLocalization>
-- 
cgit v1.2.3-55-g6feb