aboutsummaryrefslogtreecommitdiff
path: root/src/dtf
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-03-31 11:56:14 -0700
committerRob Mensching <rob@firegiant.com>2022-03-31 18:01:06 -0700
commit47582b162368e8edf7a3b11c13b8e9dabc5f0a26 (patch)
tree2c4063eff325684bed39de0edacd7866a257ae02 /src/dtf
parent167296c42497c4e95f0d5d71168542d747655981 (diff)
downloadwix-47582b162368e8edf7a3b11c13b8e9dabc5f0a26.tar.gz
wix-47582b162368e8edf7a3b11c13b8e9dabc5f0a26.tar.bz2
wix-47582b162368e8edf7a3b11c13b8e9dabc5f0a26.zip
Provide managed CA and Embedded UI DTF libraries via NuGet
Lots of refactoring to bring the SFX tooling back into the 'dtf' layer since they are (in the end) tightly coupled to some DTF assemblies. Also refactored the DTF tests into their own folder and added a couple integration tests to build using the new CA/UI NuGet package. Closes wixtoolset/issues#6080
Diffstat (limited to 'src/dtf')
-rw-r--r--src/dtf/SfxCA/ClrHost.cpp262
-rw-r--r--src/dtf/SfxCA/EmbeddedUI.cpp281
-rw-r--r--src/dtf/SfxCA/EntryPoints.def140
-rw-r--r--src/dtf/SfxCA/EntryPoints.h162
-rw-r--r--src/dtf/SfxCA/Extract.cpp282
-rw-r--r--src/dtf/SfxCA/RemoteMsi.cpp629
-rw-r--r--src/dtf/SfxCA/RemoteMsiSession.h898
-rw-r--r--src/dtf/SfxCA/SfxCA.cpp363
-rw-r--r--src/dtf/SfxCA/SfxCA.rc10
-rw-r--r--src/dtf/SfxCA/SfxCA.vcxproj79
-rw-r--r--src/dtf/SfxCA/SfxCA.vcxproj.filters62
-rw-r--r--src/dtf/SfxCA/SfxUtil.cpp209
-rw-r--r--src/dtf/SfxCA/SfxUtil.h31
-rw-r--r--src/dtf/SfxCA/precomp.cpp3
-rw-r--r--src/dtf/SfxCA/precomp.h18
-rw-r--r--src/dtf/SfxCA/sfxca_t.proj7
-rw-r--r--src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.csproj17
-rw-r--r--src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.nuspec32
-rw-r--r--src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.targets (renamed from src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets)18
-rw-r--r--src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.v3.ncrunchproject5
-rw-r--r--src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj40
-rw-r--r--src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec18
-rw-r--r--src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props8
-rw-r--r--src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs710
-rw-r--r--src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.exe.manifest11
-rw-r--r--src/dtf/WixToolset.Dtf.MakeSfxCA/WixToolset.Dtf.MakeSfxCA.csproj19
-rw-r--r--src/dtf/WixToolset.Dtf.MakeSfxCA/app.config7
-rw-r--r--src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs36
-rw-r--r--src/dtf/dtf.cmd2
-rw-r--r--src/dtf/dtf.sln102
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs (renamed from src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj (renamed from src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj)6
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj (renamed from src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj)6
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs (renamed from src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs (renamed from src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs (renamed from src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs (renamed from src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj (renamed from src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj)4
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj)2
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj)8
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs)0
-rw-r--r--src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj (renamed from src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj)4
48 files changed, 4343 insertions, 148 deletions
diff --git a/src/dtf/SfxCA/ClrHost.cpp b/src/dtf/SfxCA/ClrHost.cpp
new file mode 100644
index 00000000..1988fb2a
--- /dev/null
+++ b/src/dtf/SfxCA/ClrHost.cpp
@@ -0,0 +1,262 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...);
6
7//---------------------------------------------------------------------
8// CLR HOSTING
9//---------------------------------------------------------------------
10
11/// <summary>
12/// Binds to the CLR after determining the appropriate version.
13/// </summary>
14/// <param name="hSession">Handle to the installer session,
15/// used just for logging.</param>
16/// <param name="version">Specific version of the CLR to load.
17/// If null, then the config file and/or primary assembly are
18/// used to determine the version.</param>
19/// <param name="szConfigFile">XML .config file which may contain
20/// a startup section to direct which version of the CLR to use.
21/// May be NULL.</param>
22/// <param name="szPrimaryAssembly">Assembly to be used to determine
23/// the version of the CLR in the absence of other configuration.
24/// May be NULL.</param>
25/// <param name="ppHost">Returned runtime host interface.</param>
26/// <returns>True if the CLR was loaded successfully, false if
27/// there was some error.</returns>
28/// <remarks>
29/// If szPrimaryAssembly is NULL and szConfigFile is also NULL or
30/// does not contain any version configuration, the CLR will not be loaded.
31/// </remarks>
32bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile,
33 const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost)
34{
35 typedef HRESULT (__stdcall *PGetRequestedRuntimeInfo)(LPCWSTR pExe, LPCWSTR pwszVersion,
36 LPCWSTR pConfigurationFile, DWORD startupFlags, DWORD runtimeInfoFlags,
37 LPWSTR pDirectory, DWORD dwDirectory, DWORD *dwDirectoryLength,
38 LPWSTR pVersion, DWORD cchBuffer, DWORD* dwlength);
39 typedef HRESULT (__stdcall *PCorBindToRuntimeEx)(LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor,
40 DWORD startupFlags, REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv);
41
42 HMODULE hmodMscoree = LoadLibrary(L"mscoree.dll");
43 if (hmodMscoree == NULL)
44 {
45 Log(hSession, L"Failed to load mscoree.dll (Error code %d). This custom action "
46 L"requires the .NET Framework to be installed.", GetLastError());
47 return false;
48 }
49 PGetRequestedRuntimeInfo pGetRequestedRuntimeInfo = (PGetRequestedRuntimeInfo)
50 GetProcAddress(hmodMscoree, "GetRequestedRuntimeInfo");
51 PCorBindToRuntimeEx pCorBindToRuntimeEx = (PCorBindToRuntimeEx)
52 GetProcAddress(hmodMscoree, "CorBindToRuntimeEx");
53 if (pGetRequestedRuntimeInfo == NULL || pCorBindToRuntimeEx == NULL)
54 {
55 Log(hSession, L"Failed to locate functions in mscoree.dll (Error code %d). This custom action "
56 L"requires the .NET Framework to be installed.", GetLastError());
57 FreeLibrary(hmodMscoree);
58 return false;
59 }
60
61 wchar_t szClrVersion[20];
62 HRESULT hr;
63
64 if (szVersion != NULL && szVersion[0] != L'\0')
65 {
66 wcsncpy_s(szClrVersion, 20, szVersion, 20);
67 }
68 else
69 {
70 wchar_t szVersionDir[MAX_PATH];
71 hr = pGetRequestedRuntimeInfo(szPrimaryAssembly, NULL,
72 szConfigFile, 0, 0, szVersionDir, MAX_PATH, NULL, szClrVersion, 20, NULL);
73 if (FAILED(hr))
74 {
75 Log(hSession, L"Failed to get requested CLR info. Error code 0x%x", hr);
76 Log(hSession, L"Ensure that the proper version of the .NET Framework is installed, or "
77 L"that there is a matching supportedRuntime element in CustomAction.config. "
78 L"If you are binding to .NET 4 or greater add "
79 L"useLegacyV2RuntimeActivationPolicy=true to the <startup> element.");
80 FreeLibrary(hmodMscoree);
81 return false;
82 }
83 }
84
85 Log(hSession, L"Binding to CLR version %s", szClrVersion);
86
87 ICorRuntimeHost* pHost;
88 hr = pCorBindToRuntimeEx(szClrVersion, NULL,
89 STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN,
90 CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void**) &pHost);
91 if (FAILED(hr))
92 {
93 Log(hSession, L"Failed to bind to the CLR. Error code 0x%X", hr);
94 FreeLibrary(hmodMscoree);
95 return false;
96 }
97 hr = pHost->Start();
98 if (FAILED(hr))
99 {
100 Log(hSession, L"Failed to start the CLR. Error code 0x%X", hr);
101 pHost->Release();
102 FreeLibrary(hmodMscoree);
103 return false;
104 }
105 *ppHost = pHost;
106 FreeLibrary(hmodMscoree);
107 return true;
108}
109
110/// <summary>
111/// Creates a new CLR application domain.
112/// </summary>
113/// <param name="hSession">Handle to the installer session,
114/// used just for logging</param>
115/// <param name="pHost">Interface to the runtime host where the
116/// app domain will be created.</param>
117/// <param name="szName">Name of the app domain to create.</param>
118/// <param name="szAppBase">Application base directory path, where
119/// the app domain will look first to load its assemblies.</param>
120/// <param name="szConfigFile">Optional XML .config file containing any
121/// configuration for thae app domain.</param>
122/// <param name="ppAppDomain">Returned app domain interface.</param>
123/// <returns>True if the app domain was created successfully, false if
124/// there was some error.</returns>
125bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost,
126 const wchar_t* szName, const wchar_t* szAppBase,
127 const wchar_t* szConfigFile, _AppDomain** ppAppDomain)
128{
129 IUnknown* punkAppDomainSetup = NULL;
130 IAppDomainSetup* pAppDomainSetup = NULL;
131 HRESULT hr = pHost->CreateDomainSetup(&punkAppDomainSetup);
132 if (SUCCEEDED(hr))
133 {
134 hr = punkAppDomainSetup->QueryInterface(__uuidof(IAppDomainSetup), (void**) &pAppDomainSetup);
135 punkAppDomainSetup->Release();
136 }
137 if (FAILED(hr))
138 {
139 Log(hSession, L"Failed to create app domain setup. Error code 0x%X", hr);
140 return false;
141 }
142
143 const wchar_t* szUrlPrefix = L"file:///";
144 size_t cchApplicationBase = wcslen(szUrlPrefix) + wcslen(szAppBase);
145 wchar_t* szApplicationBase = (wchar_t*) _alloca((cchApplicationBase + 1) * sizeof(wchar_t));
146 if (szApplicationBase == NULL) hr = E_OUTOFMEMORY;
147 else
148 {
149 StringCchCopy(szApplicationBase, cchApplicationBase + 1, szUrlPrefix);
150 StringCchCat(szApplicationBase, cchApplicationBase + 1, szAppBase);
151 BSTR bstrApplicationBase = SysAllocString(szApplicationBase);
152 if (bstrApplicationBase == NULL) hr = E_OUTOFMEMORY;
153 else
154 {
155 hr = pAppDomainSetup->put_ApplicationBase(bstrApplicationBase);
156 SysFreeString(bstrApplicationBase);
157 }
158 }
159
160 if (SUCCEEDED(hr) && szConfigFile != NULL)
161 {
162 BSTR bstrConfigFile = SysAllocString(szConfigFile);
163 if (bstrConfigFile == NULL) hr = E_OUTOFMEMORY;
164 else
165 {
166 hr = pAppDomainSetup->put_ConfigurationFile(bstrConfigFile);
167 SysFreeString(bstrConfigFile);
168 }
169 }
170
171 if (FAILED(hr))
172 {
173 Log(hSession, L"Failed to configure app domain setup. Error code 0x%X", hr);
174 pAppDomainSetup->Release();
175 return false;
176 }
177
178 IUnknown* punkAppDomain;
179 hr = pHost->CreateDomainEx(szName, pAppDomainSetup, NULL, &punkAppDomain);
180 pAppDomainSetup->Release();
181 if (SUCCEEDED(hr))
182 {
183 hr = punkAppDomain->QueryInterface(__uuidof(_AppDomain), (void**) ppAppDomain);
184 punkAppDomain->Release();
185 }
186
187 if (FAILED(hr))
188 {
189 Log(hSession, L"Failed to create app domain. Error code 0x%X", hr);
190 return false;
191 }
192
193 return true;
194}
195
196/// <summary>
197/// Locates a specific method in a specific class and assembly.
198/// </summary>
199/// <param name="hSession">Handle to the installer session,
200/// used just for logging</param>
201/// <param name="pAppDomain">Application domain in which to
202/// load assemblies.</param>
203/// <param name="szAssembly">Display name of the assembly
204/// containing the method.</param>
205/// <param name="szClass">Fully-qualified name of the class
206/// containing the method.</param>
207/// <param name="szMethod">Name of the method.</param>
208/// <param name="ppMethod">Returned method interface.</param>
209/// <returns>True if the method was located, otherwise false.</returns>
210/// <remarks>Only public static methods are searched. Method
211/// parameter types are not considered; if there are multiple
212/// matching methods with different parameters, an error results.</remarks>
213bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain,
214 const wchar_t* szAssembly, const wchar_t* szClass,
215 const wchar_t* szMethod, _MethodInfo** ppMethod)
216{
217 HRESULT hr;
218 _Assembly* pAssembly = NULL;
219 BSTR bstrAssemblyName = SysAllocString(szAssembly);
220 if (bstrAssemblyName == NULL) hr = E_OUTOFMEMORY;
221 else
222 {
223 hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly);
224 SysFreeString(bstrAssemblyName);
225 }
226 if (FAILED(hr))
227 {
228 Log(hSession, L"Failed to load assembly %s. Error code 0x%X", szAssembly, hr);
229 return false;
230 }
231
232 _Type* pType = NULL;
233 BSTR bstrClass = SysAllocString(szClass);
234 if (bstrClass == NULL) hr = E_OUTOFMEMORY;
235 else
236 {
237 hr = pAssembly->GetType_2(bstrClass, &pType);
238 SysFreeString(bstrClass);
239 }
240 pAssembly->Release();
241 if (FAILED(hr) || pType == NULL)
242 {
243 Log(hSession, L"Failed to load class %s. Error code 0x%X", szClass, hr);
244 return false;
245 }
246
247 BSTR bstrMethod = SysAllocString(szMethod);
248 if (bstrMethod == NULL) hr = E_OUTOFMEMORY;
249 else
250 {
251 hr = pType->GetMethod_2(bstrMethod,
252 (BindingFlags) (BindingFlags_Public | BindingFlags_Static), ppMethod);
253 SysFreeString(bstrMethod);
254 }
255 pType->Release();
256 if (FAILED(hr) || *ppMethod == NULL)
257 {
258 Log(hSession, L"Failed to get method %s. Error code 0x%X", szMethod, hr);
259 return false;
260 }
261 return true;
262}
diff --git a/src/dtf/SfxCA/EmbeddedUI.cpp b/src/dtf/SfxCA/EmbeddedUI.cpp
new file mode 100644
index 00000000..a49cdeec
--- /dev/null
+++ b/src/dtf/SfxCA/EmbeddedUI.cpp
@@ -0,0 +1,281 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "SfxUtil.h"
5
6// Globals for keeping track of things across UI messages.
7static const wchar_t* g_szWorkingDir;
8static ICorRuntimeHost* g_pClrHost;
9static _AppDomain* g_pAppDomain;
10static _MethodInfo* g_pProcessMessageMethod;
11static _MethodInfo* g_pShutdownMethod;
12
13// Reserve extra space for strings to be replaced at build time.
14#define NULLSPACE \
15L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
16L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
17L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
18L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
19
20// Prototypes for local functions.
21// See the function definitions for comments.
22
23bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession,
24 const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult);
25
26/// <summary>
27/// First entry-point for the UI DLL when loaded and called by MSI.
28/// Extracts the payload, hosts the CLR, and invokes the managed
29/// initialize method.
30/// </summary>
31/// <param name="hSession">Handle to the installer session,
32/// used for logging errors and to be passed on to the managed initialize method.</param>
33/// <param name="szResourcePath">Path the directory where resources from the MsiEmbeddedUI table
34/// have been extracted, and where additional payload from this package will be extracted.</param>
35/// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from
36/// the managed initialize method.</param>
37extern "C"
38UINT __stdcall InitializeEmbeddedUI(MSIHANDLE hSession, LPCWSTR szResourcePath, LPDWORD pdwInternalUILevel)
39{
40 // If the managed initialize method cannot be called, continue the installation in BASIC UI mode.
41 UINT uiResult = INSTALLUILEVEL_BASIC;
42
43 const wchar_t* szClassName = L"InitializeEmbeddedUI_FullClassName" NULLSPACE;
44
45 g_szWorkingDir = szResourcePath;
46
47 wchar_t szModule[MAX_PATH];
48 DWORD cchCopied = GetModuleFileName(g_hModule, szModule, MAX_PATH - 1);
49 if (cchCopied == 0)
50 {
51 Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
52 return uiResult;
53 }
54 else if (cchCopied == MAX_PATH - 1)
55 {
56 Log(hSession, L"Failed to get module path -- path is too long.");
57 return uiResult;
58 }
59
60 Log(hSession, L"Extracting embedded UI to temporary directory: %s", g_szWorkingDir);
61 int err = ExtractCabinet(szModule, g_szWorkingDir);
62 if (err != 0)
63 {
64 Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err);
65 Log(hSession, L"Ensure that no MsiEmbeddedUI.FileName values are the same as "
66 L"any file contained in the embedded UI package.");
67 return uiResult;
68 }
69
70 wchar_t szConfigFilePath[MAX_PATH + 20];
71 StringCchCopy(szConfigFilePath, MAX_PATH + 20, g_szWorkingDir);
72 StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\EmbeddedUI.config");
73
74 const wchar_t* szConfigFile = szConfigFilePath;
75 if (!PathFileExists(szConfigFilePath))
76 {
77 szConfigFile = NULL;
78 }
79
80 wchar_t szWIAssembly[MAX_PATH + 50];
81 StringCchCopy(szWIAssembly, MAX_PATH + 50, g_szWorkingDir);
82 StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll");
83
84 if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &g_pClrHost))
85 {
86 if (CreateAppDomain(hSession, g_pClrHost, L"EmbeddedUI", g_szWorkingDir,
87 szConfigFile, &g_pAppDomain))
88 {
89 const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller";
90 const wchar_t* szProxyClass = L"WixToolset.Dtf.WindowsInstaller.EmbeddedUIProxy";
91 const wchar_t* szInitMethod = L"Initialize";
92 const wchar_t* szProcessMessageMethod = L"ProcessMessage";
93 const wchar_t* szShutdownMethod = L"Shutdown";
94
95 if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
96 szProxyClass, szProcessMessageMethod, &g_pProcessMessageMethod) &&
97 GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
98 szProxyClass, szShutdownMethod, &g_pShutdownMethod))
99 {
100 _MethodInfo* pInitMethod;
101 if (GetMethod(hSession, g_pAppDomain, szMsiAssemblyName,
102 szProxyClass, szInitMethod, &pInitMethod))
103 {
104 bool invokeSuccess = InvokeInitializeMethod(pInitMethod, hSession, szClassName, pdwInternalUILevel, &uiResult);
105 pInitMethod->Release();
106 if (invokeSuccess)
107 {
108 if (uiResult == 0)
109 {
110 return ERROR_SUCCESS;
111 }
112 else if (uiResult == ERROR_INSTALL_USEREXIT)
113 {
114 // InitializeEmbeddedUI is not allowed to return ERROR_INSTALL_USEREXIT.
115 // So return success here and then IDCANCEL on the next progress message.
116 uiResult = 0;
117 *pdwInternalUILevel = INSTALLUILEVEL_NONE;
118 Log(hSession, L"Initialization canceled by user.");
119 }
120 }
121 }
122 }
123
124 g_pProcessMessageMethod->Release();
125 g_pProcessMessageMethod = NULL;
126 g_pShutdownMethod->Release();
127 g_pShutdownMethod = NULL;
128
129 g_pClrHost->UnloadDomain(g_pAppDomain);
130 g_pAppDomain->Release();
131 g_pAppDomain = NULL;
132 }
133 g_pClrHost->Stop();
134 g_pClrHost->Release();
135 g_pClrHost = NULL;
136 }
137
138 return uiResult;
139}
140
141/// <summary>
142/// Entry-point for UI progress messages received from the MSI engine during an active installation.
143/// Forwards the progress messages to the managed handler method and returns its result.
144/// </summary>
145extern "C"
146INT __stdcall EmbeddedUIHandler(UINT uiMessageType, MSIHANDLE hRecord)
147{
148 if (g_pProcessMessageMethod == NULL)
149 {
150 // Initialization was canceled.
151 return IDCANCEL;
152 }
153
154 VARIANT vResult;
155 VariantInit(&vResult);
156
157 VARIANT vNull;
158 vNull.vt = VT_EMPTY;
159
160 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 2);
161 VARIANT vMessageType;
162 vMessageType.vt = VT_I4;
163 vMessageType.lVal = (LONG) uiMessageType;
164 LONG index = 0;
165 HRESULT hr = SafeArrayPutElement(saArgs, &index, &vMessageType);
166 if (FAILED(hr)) goto LExit;
167 VARIANT vRecord;
168 vRecord.vt = VT_I4;
169 vRecord.lVal = (LONG) hRecord;
170 index = 1;
171 hr = SafeArrayPutElement(saArgs, &index, &vRecord);
172 if (FAILED(hr)) goto LExit;
173
174 hr = g_pProcessMessageMethod->Invoke_3(vNull, saArgs, &vResult);
175
176LExit:
177 SafeArrayDestroy(saArgs);
178 if (SUCCEEDED(hr))
179 {
180 return vResult.intVal;
181 }
182 else
183 {
184 return -1;
185 }
186}
187
188/// <summary>
189/// Entry-point for the UI shutdown message received from the MSI engine after installation has completed.
190/// Forwards the shutdown message to the managed shutdown method, then shuts down the CLR.
191/// </summary>
192extern "C"
193DWORD __stdcall ShutdownEmbeddedUI()
194{
195 if (g_pShutdownMethod != NULL)
196 {
197 VARIANT vNull;
198 vNull.vt = VT_EMPTY;
199 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
200 g_pShutdownMethod->Invoke_3(vNull, saArgs, NULL);
201 SafeArrayDestroy(saArgs);
202
203 g_pClrHost->UnloadDomain(g_pAppDomain);
204 g_pAppDomain->Release();
205 g_pClrHost->Stop();
206 g_pClrHost->Release();
207 }
208
209 return 0;
210}
211
212/// <summary>
213/// Loads and invokes the managed portion of the proxy.
214/// </summary>
215/// <param name="pInitMethod">Managed initialize method to be invoked.</param>
216/// <param name="hSession">Handle to the installer session,
217/// used for logging errors and to be passed on to the managed initialize method.</param>
218/// <param name="szClassName">Name of the UI class to be loaded.
219/// This must be of the form: AssemblyName!Namespace.Class</param>
220/// <param name="pdwInternalUILevel">MSI install UI level passed to and returned from
221/// the managed initialize method.</param>
222/// <param name="puiResult">Return value of the invoked initialize method.</param>
223/// <returns>True if the managed proxy was invoked successfully, or an
224/// error code if there was some error. Note the initialize method itself may
225/// return an error via puiResult while this method still returns true
226/// since the invocation was successful.</returns>
227bool InvokeInitializeMethod(_MethodInfo* pInitMethod, MSIHANDLE hSession, const wchar_t* szClassName, LPDWORD pdwInternalUILevel, UINT* puiResult)
228{
229 VARIANT vResult;
230 VariantInit(&vResult);
231
232 VARIANT vNull;
233 vNull.vt = VT_EMPTY;
234
235 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3);
236 VARIANT vSessionHandle;
237 vSessionHandle.vt = VT_I4;
238 vSessionHandle.lVal = (LONG) hSession;
239 LONG index = 0;
240 HRESULT hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle);
241 if (FAILED(hr)) goto LExit;
242 VARIANT vEntryPoint;
243 vEntryPoint.vt = VT_BSTR;
244 vEntryPoint.bstrVal = SysAllocString(szClassName);
245 if (vEntryPoint.bstrVal == NULL)
246 {
247 hr = E_OUTOFMEMORY;
248 goto LExit;
249 }
250 index = 1;
251 hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint);
252 if (FAILED(hr)) goto LExit;
253 VARIANT vUILevel;
254 vUILevel.vt = VT_I4;
255 vUILevel.ulVal = *pdwInternalUILevel;
256 index = 2;
257 hr = SafeArrayPutElement(saArgs, &index, &vUILevel);
258 if (FAILED(hr)) goto LExit;
259
260 hr = pInitMethod->Invoke_3(vNull, saArgs, &vResult);
261
262LExit:
263 SafeArrayDestroy(saArgs);
264 if (SUCCEEDED(hr))
265 {
266 *puiResult = (UINT) vResult.lVal;
267 if ((*puiResult & 0xFFFF) == 0)
268 {
269 // Due to interop limitations, the successful resulting UILevel is returned
270 // as the high-word of the return value instead of via a ref parameter.
271 *pdwInternalUILevel = *puiResult >> 16;
272 *puiResult = 0;
273 }
274 return true;
275 }
276 else
277 {
278 Log(hSession, L"Failed to invoke EmbeddedUI Initialize method. Error code 0x%X", hr);
279 return false;
280 }
281}
diff --git a/src/dtf/SfxCA/EntryPoints.def b/src/dtf/SfxCA/EntryPoints.def
new file mode 100644
index 00000000..dd28b920
--- /dev/null
+++ b/src/dtf/SfxCA/EntryPoints.def
@@ -0,0 +1,140 @@
1; Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3
4LIBRARY "SfxCA"
5
6EXPORTS
7
8CustomActionEntryPoint000________________________________________________=CustomActionEntryPoint000
9CustomActionEntryPoint001________________________________________________=CustomActionEntryPoint001
10CustomActionEntryPoint002________________________________________________=CustomActionEntryPoint002
11CustomActionEntryPoint003________________________________________________=CustomActionEntryPoint003
12CustomActionEntryPoint004________________________________________________=CustomActionEntryPoint004
13CustomActionEntryPoint005________________________________________________=CustomActionEntryPoint005
14CustomActionEntryPoint006________________________________________________=CustomActionEntryPoint006
15CustomActionEntryPoint007________________________________________________=CustomActionEntryPoint007
16CustomActionEntryPoint008________________________________________________=CustomActionEntryPoint008
17CustomActionEntryPoint009________________________________________________=CustomActionEntryPoint009
18CustomActionEntryPoint010________________________________________________=CustomActionEntryPoint010
19CustomActionEntryPoint011________________________________________________=CustomActionEntryPoint011
20CustomActionEntryPoint012________________________________________________=CustomActionEntryPoint012
21CustomActionEntryPoint013________________________________________________=CustomActionEntryPoint013
22CustomActionEntryPoint014________________________________________________=CustomActionEntryPoint014
23CustomActionEntryPoint015________________________________________________=CustomActionEntryPoint015
24CustomActionEntryPoint016________________________________________________=CustomActionEntryPoint016
25CustomActionEntryPoint017________________________________________________=CustomActionEntryPoint017
26CustomActionEntryPoint018________________________________________________=CustomActionEntryPoint018
27CustomActionEntryPoint019________________________________________________=CustomActionEntryPoint019
28CustomActionEntryPoint020________________________________________________=CustomActionEntryPoint020
29CustomActionEntryPoint021________________________________________________=CustomActionEntryPoint021
30CustomActionEntryPoint022________________________________________________=CustomActionEntryPoint022
31CustomActionEntryPoint023________________________________________________=CustomActionEntryPoint023
32CustomActionEntryPoint024________________________________________________=CustomActionEntryPoint024
33CustomActionEntryPoint025________________________________________________=CustomActionEntryPoint025
34CustomActionEntryPoint026________________________________________________=CustomActionEntryPoint026
35CustomActionEntryPoint027________________________________________________=CustomActionEntryPoint027
36CustomActionEntryPoint028________________________________________________=CustomActionEntryPoint028
37CustomActionEntryPoint029________________________________________________=CustomActionEntryPoint029
38CustomActionEntryPoint030________________________________________________=CustomActionEntryPoint030
39CustomActionEntryPoint031________________________________________________=CustomActionEntryPoint031
40CustomActionEntryPoint032________________________________________________=CustomActionEntryPoint032
41CustomActionEntryPoint033________________________________________________=CustomActionEntryPoint033
42CustomActionEntryPoint034________________________________________________=CustomActionEntryPoint034
43CustomActionEntryPoint035________________________________________________=CustomActionEntryPoint035
44CustomActionEntryPoint036________________________________________________=CustomActionEntryPoint036
45CustomActionEntryPoint037________________________________________________=CustomActionEntryPoint037
46CustomActionEntryPoint038________________________________________________=CustomActionEntryPoint038
47CustomActionEntryPoint039________________________________________________=CustomActionEntryPoint039
48CustomActionEntryPoint040________________________________________________=CustomActionEntryPoint040
49CustomActionEntryPoint041________________________________________________=CustomActionEntryPoint041
50CustomActionEntryPoint042________________________________________________=CustomActionEntryPoint042
51CustomActionEntryPoint043________________________________________________=CustomActionEntryPoint043
52CustomActionEntryPoint044________________________________________________=CustomActionEntryPoint044
53CustomActionEntryPoint045________________________________________________=CustomActionEntryPoint045
54CustomActionEntryPoint046________________________________________________=CustomActionEntryPoint046
55CustomActionEntryPoint047________________________________________________=CustomActionEntryPoint047
56CustomActionEntryPoint048________________________________________________=CustomActionEntryPoint048
57CustomActionEntryPoint049________________________________________________=CustomActionEntryPoint049
58CustomActionEntryPoint050________________________________________________=CustomActionEntryPoint050
59CustomActionEntryPoint051________________________________________________=CustomActionEntryPoint051
60CustomActionEntryPoint052________________________________________________=CustomActionEntryPoint052
61CustomActionEntryPoint053________________________________________________=CustomActionEntryPoint053
62CustomActionEntryPoint054________________________________________________=CustomActionEntryPoint054
63CustomActionEntryPoint055________________________________________________=CustomActionEntryPoint055
64CustomActionEntryPoint056________________________________________________=CustomActionEntryPoint056
65CustomActionEntryPoint057________________________________________________=CustomActionEntryPoint057
66CustomActionEntryPoint058________________________________________________=CustomActionEntryPoint058
67CustomActionEntryPoint059________________________________________________=CustomActionEntryPoint059
68CustomActionEntryPoint060________________________________________________=CustomActionEntryPoint060
69CustomActionEntryPoint061________________________________________________=CustomActionEntryPoint061
70CustomActionEntryPoint062________________________________________________=CustomActionEntryPoint062
71CustomActionEntryPoint063________________________________________________=CustomActionEntryPoint063
72CustomActionEntryPoint064________________________________________________=CustomActionEntryPoint064
73CustomActionEntryPoint065________________________________________________=CustomActionEntryPoint065
74CustomActionEntryPoint066________________________________________________=CustomActionEntryPoint066
75CustomActionEntryPoint067________________________________________________=CustomActionEntryPoint067
76CustomActionEntryPoint068________________________________________________=CustomActionEntryPoint068
77CustomActionEntryPoint069________________________________________________=CustomActionEntryPoint069
78CustomActionEntryPoint070________________________________________________=CustomActionEntryPoint070
79CustomActionEntryPoint071________________________________________________=CustomActionEntryPoint071
80CustomActionEntryPoint072________________________________________________=CustomActionEntryPoint072
81CustomActionEntryPoint073________________________________________________=CustomActionEntryPoint073
82CustomActionEntryPoint074________________________________________________=CustomActionEntryPoint074
83CustomActionEntryPoint075________________________________________________=CustomActionEntryPoint075
84CustomActionEntryPoint076________________________________________________=CustomActionEntryPoint076
85CustomActionEntryPoint077________________________________________________=CustomActionEntryPoint077
86CustomActionEntryPoint078________________________________________________=CustomActionEntryPoint078
87CustomActionEntryPoint079________________________________________________=CustomActionEntryPoint079
88CustomActionEntryPoint080________________________________________________=CustomActionEntryPoint080
89CustomActionEntryPoint081________________________________________________=CustomActionEntryPoint081
90CustomActionEntryPoint082________________________________________________=CustomActionEntryPoint082
91CustomActionEntryPoint083________________________________________________=CustomActionEntryPoint083
92CustomActionEntryPoint084________________________________________________=CustomActionEntryPoint084
93CustomActionEntryPoint085________________________________________________=CustomActionEntryPoint085
94CustomActionEntryPoint086________________________________________________=CustomActionEntryPoint086
95CustomActionEntryPoint087________________________________________________=CustomActionEntryPoint087
96CustomActionEntryPoint088________________________________________________=CustomActionEntryPoint088
97CustomActionEntryPoint089________________________________________________=CustomActionEntryPoint089
98CustomActionEntryPoint090________________________________________________=CustomActionEntryPoint090
99CustomActionEntryPoint091________________________________________________=CustomActionEntryPoint091
100CustomActionEntryPoint092________________________________________________=CustomActionEntryPoint092
101CustomActionEntryPoint093________________________________________________=CustomActionEntryPoint093
102CustomActionEntryPoint094________________________________________________=CustomActionEntryPoint094
103CustomActionEntryPoint095________________________________________________=CustomActionEntryPoint095
104CustomActionEntryPoint096________________________________________________=CustomActionEntryPoint096
105CustomActionEntryPoint097________________________________________________=CustomActionEntryPoint097
106CustomActionEntryPoint098________________________________________________=CustomActionEntryPoint098
107CustomActionEntryPoint099________________________________________________=CustomActionEntryPoint099
108CustomActionEntryPoint100________________________________________________=CustomActionEntryPoint100
109CustomActionEntryPoint101________________________________________________=CustomActionEntryPoint101
110CustomActionEntryPoint102________________________________________________=CustomActionEntryPoint102
111CustomActionEntryPoint103________________________________________________=CustomActionEntryPoint103
112CustomActionEntryPoint104________________________________________________=CustomActionEntryPoint104
113CustomActionEntryPoint105________________________________________________=CustomActionEntryPoint105
114CustomActionEntryPoint106________________________________________________=CustomActionEntryPoint106
115CustomActionEntryPoint107________________________________________________=CustomActionEntryPoint107
116CustomActionEntryPoint108________________________________________________=CustomActionEntryPoint108
117CustomActionEntryPoint109________________________________________________=CustomActionEntryPoint109
118CustomActionEntryPoint110________________________________________________=CustomActionEntryPoint110
119CustomActionEntryPoint111________________________________________________=CustomActionEntryPoint111
120CustomActionEntryPoint112________________________________________________=CustomActionEntryPoint112
121CustomActionEntryPoint113________________________________________________=CustomActionEntryPoint113
122CustomActionEntryPoint114________________________________________________=CustomActionEntryPoint114
123CustomActionEntryPoint115________________________________________________=CustomActionEntryPoint115
124CustomActionEntryPoint116________________________________________________=CustomActionEntryPoint116
125CustomActionEntryPoint117________________________________________________=CustomActionEntryPoint117
126CustomActionEntryPoint118________________________________________________=CustomActionEntryPoint118
127CustomActionEntryPoint119________________________________________________=CustomActionEntryPoint119
128CustomActionEntryPoint120________________________________________________=CustomActionEntryPoint120
129CustomActionEntryPoint121________________________________________________=CustomActionEntryPoint121
130CustomActionEntryPoint122________________________________________________=CustomActionEntryPoint122
131CustomActionEntryPoint123________________________________________________=CustomActionEntryPoint123
132CustomActionEntryPoint124________________________________________________=CustomActionEntryPoint124
133CustomActionEntryPoint125________________________________________________=CustomActionEntryPoint125
134CustomActionEntryPoint126________________________________________________=CustomActionEntryPoint126
135CustomActionEntryPoint127________________________________________________=CustomActionEntryPoint127
136
137zzzzInvokeManagedCustomActionOutOfProcW=InvokeManagedCustomActionOutOfProc
138zzzInitializeEmbeddedUI=InitializeEmbeddedUI
139zzzEmbeddedUIHandler=EmbeddedUIHandler
140zzzShutdownEmbeddedUI=ShutdownEmbeddedUI
diff --git a/src/dtf/SfxCA/EntryPoints.h b/src/dtf/SfxCA/EntryPoints.h
new file mode 100644
index 00000000..bd2fa970
--- /dev/null
+++ b/src/dtf/SfxCA/EntryPoints.h
@@ -0,0 +1,162 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3int InvokeCustomAction(MSIHANDLE hSession,
4 const wchar_t* szWorkingDir, const wchar_t* szEntryPoint);
5
6/// <summary>
7/// Macro for defining and exporting a custom action entrypoint.
8/// </summary>
9/// <param name="name">Name of the entrypoint as exported from
10/// the DLL.</param>
11/// <param name="method">Path to the managed custom action method,
12/// in the form: "AssemblyName!Namespace.Class.Method"</param>
13/// <remarks>
14/// To prevent the exported name from being decorated, add
15/// /EXPORT:name to the linker options for every entrypoint.
16/// </remarks>
17#define CUSTOMACTION_ENTRYPOINT(name,method) extern "C" int __stdcall \
18 name(MSIHANDLE hSession) { return InvokeCustomAction(hSession, NULL, method); }
19
20// TEMPLATE ENTRYPOINTS
21// To be edited by the MakeSfxCA tool.
22
23#define NULLSPACE \
24L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
25L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
26L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" \
27L"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
28
29#define TEMPLATE_CA_ENTRYPOINT(id,sid) CUSTOMACTION_ENTRYPOINT( \
30 CustomActionEntryPoint##id##, \
31 L"CustomActionEntryPoint" sid NULLSPACE)
32
33TEMPLATE_CA_ENTRYPOINT(000,L"000");
34TEMPLATE_CA_ENTRYPOINT(001,L"001");
35TEMPLATE_CA_ENTRYPOINT(002,L"002");
36TEMPLATE_CA_ENTRYPOINT(003,L"003");
37TEMPLATE_CA_ENTRYPOINT(004,L"004");
38TEMPLATE_CA_ENTRYPOINT(005,L"005");
39TEMPLATE_CA_ENTRYPOINT(006,L"006");
40TEMPLATE_CA_ENTRYPOINT(007,L"007");
41TEMPLATE_CA_ENTRYPOINT(008,L"008");
42TEMPLATE_CA_ENTRYPOINT(009,L"009");
43TEMPLATE_CA_ENTRYPOINT(010,L"010");
44TEMPLATE_CA_ENTRYPOINT(011,L"011");
45TEMPLATE_CA_ENTRYPOINT(012,L"012");
46TEMPLATE_CA_ENTRYPOINT(013,L"013");
47TEMPLATE_CA_ENTRYPOINT(014,L"014");
48TEMPLATE_CA_ENTRYPOINT(015,L"015");
49TEMPLATE_CA_ENTRYPOINT(016,L"016");
50TEMPLATE_CA_ENTRYPOINT(017,L"017");
51TEMPLATE_CA_ENTRYPOINT(018,L"018");
52TEMPLATE_CA_ENTRYPOINT(019,L"019");
53TEMPLATE_CA_ENTRYPOINT(020,L"020");
54TEMPLATE_CA_ENTRYPOINT(021,L"021");
55TEMPLATE_CA_ENTRYPOINT(022,L"022");
56TEMPLATE_CA_ENTRYPOINT(023,L"023");
57TEMPLATE_CA_ENTRYPOINT(024,L"024");
58TEMPLATE_CA_ENTRYPOINT(025,L"025");
59TEMPLATE_CA_ENTRYPOINT(026,L"026");
60TEMPLATE_CA_ENTRYPOINT(027,L"027");
61TEMPLATE_CA_ENTRYPOINT(028,L"028");
62TEMPLATE_CA_ENTRYPOINT(029,L"029");
63TEMPLATE_CA_ENTRYPOINT(030,L"030");
64TEMPLATE_CA_ENTRYPOINT(031,L"031");
65TEMPLATE_CA_ENTRYPOINT(032,L"032");
66TEMPLATE_CA_ENTRYPOINT(033,L"033");
67TEMPLATE_CA_ENTRYPOINT(034,L"034");
68TEMPLATE_CA_ENTRYPOINT(035,L"035");
69TEMPLATE_CA_ENTRYPOINT(036,L"036");
70TEMPLATE_CA_ENTRYPOINT(037,L"037");
71TEMPLATE_CA_ENTRYPOINT(038,L"038");
72TEMPLATE_CA_ENTRYPOINT(039,L"039");
73TEMPLATE_CA_ENTRYPOINT(040,L"040");
74TEMPLATE_CA_ENTRYPOINT(041,L"041");
75TEMPLATE_CA_ENTRYPOINT(042,L"042");
76TEMPLATE_CA_ENTRYPOINT(043,L"043");
77TEMPLATE_CA_ENTRYPOINT(044,L"044");
78TEMPLATE_CA_ENTRYPOINT(045,L"045");
79TEMPLATE_CA_ENTRYPOINT(046,L"046");
80TEMPLATE_CA_ENTRYPOINT(047,L"047");
81TEMPLATE_CA_ENTRYPOINT(048,L"048");
82TEMPLATE_CA_ENTRYPOINT(049,L"049");
83TEMPLATE_CA_ENTRYPOINT(050,L"050");
84TEMPLATE_CA_ENTRYPOINT(051,L"051");
85TEMPLATE_CA_ENTRYPOINT(052,L"052");
86TEMPLATE_CA_ENTRYPOINT(053,L"053");
87TEMPLATE_CA_ENTRYPOINT(054,L"054");
88TEMPLATE_CA_ENTRYPOINT(055,L"055");
89TEMPLATE_CA_ENTRYPOINT(056,L"056");
90TEMPLATE_CA_ENTRYPOINT(057,L"057");
91TEMPLATE_CA_ENTRYPOINT(058,L"058");
92TEMPLATE_CA_ENTRYPOINT(059,L"059");
93TEMPLATE_CA_ENTRYPOINT(060,L"060");
94TEMPLATE_CA_ENTRYPOINT(061,L"061");
95TEMPLATE_CA_ENTRYPOINT(062,L"062");
96TEMPLATE_CA_ENTRYPOINT(063,L"063");
97TEMPLATE_CA_ENTRYPOINT(064,L"064");
98TEMPLATE_CA_ENTRYPOINT(065,L"065");
99TEMPLATE_CA_ENTRYPOINT(066,L"066");
100TEMPLATE_CA_ENTRYPOINT(067,L"067");
101TEMPLATE_CA_ENTRYPOINT(068,L"068");
102TEMPLATE_CA_ENTRYPOINT(069,L"069");
103TEMPLATE_CA_ENTRYPOINT(070,L"070");
104TEMPLATE_CA_ENTRYPOINT(071,L"071");
105TEMPLATE_CA_ENTRYPOINT(072,L"072");
106TEMPLATE_CA_ENTRYPOINT(073,L"073");
107TEMPLATE_CA_ENTRYPOINT(074,L"074");
108TEMPLATE_CA_ENTRYPOINT(075,L"075");
109TEMPLATE_CA_ENTRYPOINT(076,L"076");
110TEMPLATE_CA_ENTRYPOINT(077,L"077");
111TEMPLATE_CA_ENTRYPOINT(078,L"078");
112TEMPLATE_CA_ENTRYPOINT(079,L"079");
113TEMPLATE_CA_ENTRYPOINT(080,L"080");
114TEMPLATE_CA_ENTRYPOINT(081,L"081");
115TEMPLATE_CA_ENTRYPOINT(082,L"082");
116TEMPLATE_CA_ENTRYPOINT(083,L"083");
117TEMPLATE_CA_ENTRYPOINT(084,L"084");
118TEMPLATE_CA_ENTRYPOINT(085,L"085");
119TEMPLATE_CA_ENTRYPOINT(086,L"086");
120TEMPLATE_CA_ENTRYPOINT(087,L"087");
121TEMPLATE_CA_ENTRYPOINT(088,L"088");
122TEMPLATE_CA_ENTRYPOINT(089,L"089");
123TEMPLATE_CA_ENTRYPOINT(090,L"090");
124TEMPLATE_CA_ENTRYPOINT(091,L"091");
125TEMPLATE_CA_ENTRYPOINT(092,L"092");
126TEMPLATE_CA_ENTRYPOINT(093,L"093");
127TEMPLATE_CA_ENTRYPOINT(094,L"094");
128TEMPLATE_CA_ENTRYPOINT(095,L"095");
129TEMPLATE_CA_ENTRYPOINT(096,L"096");
130TEMPLATE_CA_ENTRYPOINT(097,L"097");
131TEMPLATE_CA_ENTRYPOINT(098,L"098");
132TEMPLATE_CA_ENTRYPOINT(099,L"099");
133TEMPLATE_CA_ENTRYPOINT(100,L"100");
134TEMPLATE_CA_ENTRYPOINT(101,L"101");
135TEMPLATE_CA_ENTRYPOINT(102,L"102");
136TEMPLATE_CA_ENTRYPOINT(103,L"103");
137TEMPLATE_CA_ENTRYPOINT(104,L"104");
138TEMPLATE_CA_ENTRYPOINT(105,L"105");
139TEMPLATE_CA_ENTRYPOINT(106,L"106");
140TEMPLATE_CA_ENTRYPOINT(107,L"107");
141TEMPLATE_CA_ENTRYPOINT(108,L"108");
142TEMPLATE_CA_ENTRYPOINT(109,L"109");
143TEMPLATE_CA_ENTRYPOINT(110,L"110");
144TEMPLATE_CA_ENTRYPOINT(111,L"111");
145TEMPLATE_CA_ENTRYPOINT(112,L"112");
146TEMPLATE_CA_ENTRYPOINT(113,L"113");
147TEMPLATE_CA_ENTRYPOINT(114,L"114");
148TEMPLATE_CA_ENTRYPOINT(115,L"115");
149TEMPLATE_CA_ENTRYPOINT(116,L"116");
150TEMPLATE_CA_ENTRYPOINT(117,L"117");
151TEMPLATE_CA_ENTRYPOINT(118,L"118");
152TEMPLATE_CA_ENTRYPOINT(119,L"119");
153TEMPLATE_CA_ENTRYPOINT(120,L"120");
154TEMPLATE_CA_ENTRYPOINT(121,L"121");
155TEMPLATE_CA_ENTRYPOINT(122,L"122");
156TEMPLATE_CA_ENTRYPOINT(123,L"123");
157TEMPLATE_CA_ENTRYPOINT(124,L"124");
158TEMPLATE_CA_ENTRYPOINT(125,L"125");
159TEMPLATE_CA_ENTRYPOINT(126,L"126");
160TEMPLATE_CA_ENTRYPOINT(127,L"127");
161
162// Note: Keep in sync with EntryPoints.def
diff --git a/src/dtf/SfxCA/Extract.cpp b/src/dtf/SfxCA/Extract.cpp
new file mode 100644
index 00000000..171cf52f
--- /dev/null
+++ b/src/dtf/SfxCA/Extract.cpp
@@ -0,0 +1,282 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5//---------------------------------------------------------------------
6// CABINET EXTRACTION
7//---------------------------------------------------------------------
8
9// Globals make this code unsuited for multhreaded use,
10// but FDI doesn't provide any other way to pass context.
11
12// Handle to the FDI (cab extraction) engine. Need access to this in a callback.
13static HFDI g_hfdi;
14
15// FDI is not unicode-aware, so avoid passing these paths through the callbacks.
16static const wchar_t* g_szExtractDir;
17static const wchar_t* g_szCabFile;
18
19// Offset into the source file where the cabinet really starts.
20// Used to trick FDI into extracting from a concatenated cabinet.
21static int g_lCabOffset;
22
23// Use the secure CRT version of _wsopen if available.
24#ifdef __GOT_SECURE_LIB__
25#define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode)
26#else
27#define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode)
28#endif
29
30/// <summary>
31/// FDI callback to open a cabinet file.
32/// </summary>
33/// <param name="pszFile">Name of the file to be opened. This parameter
34/// is ignored since with our limited use this method is only ever called
35/// to open the main cabinet file.</param>
36/// <param name="oflag">Type of operations allowed.</param>
37/// <param name="pmode">Permission setting.</param>
38/// <returns>Integer file handle, or -1 if the file could not be opened.</returns>
39/// <remarks>
40/// To support reading from a cabinet that is concatenated onto
41/// another file, this function first searches for the offset of the cabinet,
42/// then saves that offset for use in recalculating later seeks.
43/// </remarks>
44static FNOPEN(CabOpen)
45{
46 UNREFERENCED_PARAMETER(pszFile);
47 int hf;
48 _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode);
49 if (hf != -1)
50 {
51 FDICABINETINFO cabInfo;
52 int length = _lseek(hf, 0, SEEK_END);
53 for(int offset = 0; offset < length; offset += 256)
54 {
55 if (_lseek(hf, offset, SEEK_SET) != offset) break;
56 if (FDIIsCabinet(g_hfdi, hf, &cabInfo))
57 {
58 g_lCabOffset = offset;
59 _lseek(hf, offset, SEEK_SET);
60 return hf;
61 }
62 }
63 _close(hf);
64 }
65 return -1;
66}
67
68/// <summary>
69/// FDI callback to seek within a file.
70/// </summary>
71/// <param name="hf">File handle.</param>
72/// <param name="dist">Seek distance</param>
73/// <param name="seektype">Whether to seek relative to the
74/// beginning, current position, or end of the file.</param>
75/// <returns>Resultant position within the cabinet.</returns>
76/// <remarks>
77/// To support reading from a cabinet that is concatenated onto
78/// another file, this function recalculates seeks based on the
79/// offset that was determined when the cabinet was opened.
80/// </remarks>
81static FNSEEK(CabSeek)
82{
83 if (seektype == SEEK_SET) dist += g_lCabOffset;
84 int pos = _lseek((int) hf, dist, seektype);
85 pos -= g_lCabOffset;
86 return pos;
87}
88
89/// <summary>
90/// Ensures a directory and its parent directory path exists.
91/// </summary>
92/// <param name="szDirPath">Directory path, not including file name.</param>
93/// <returns>0 if the directory exists or was successfully created, else nonzero.</returns>
94/// <remarks>
95/// This function modifies characters in szDirPath, but always restores them
96/// regardless of error condition.
97/// </remarks>
98static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath)
99{
100 int ret = 0;
101 if (!::CreateDirectoryW(szDirPath, NULL))
102 {
103 UINT err = ::GetLastError();
104 if (err != ERROR_ALREADY_EXISTS)
105 {
106 // Directory creation failed for some reason other than already existing.
107 // Try to create the parent directory first.
108 wchar_t* szLastSlash = NULL;
109 for (wchar_t* sz = szDirPath; *sz; sz++)
110 {
111 if (*sz == L'\\')
112 {
113 szLastSlash = sz;
114 }
115 }
116 if (szLastSlash)
117 {
118 // Temporarily take one directory off the path and recurse.
119 *szLastSlash = L'\0';
120 ret = EnsureDirectoryExists(szDirPath);
121 *szLastSlash = L'\\';
122
123 // Try to create the directory if all parents are created.
124 if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL))
125 {
126 err = ::GetLastError();
127 if (err != ERROR_ALREADY_EXISTS)
128 {
129 ret = -1;
130 }
131 }
132 }
133 else
134 {
135 ret = -1;
136 }
137 }
138 }
139 return ret;
140}
141
142/// <summary>
143/// Ensures a file's directory and its parent directory path exists.
144/// </summary>
145/// <param name="szDirPath">Path including file name.</param>
146/// <returns>0 if the file's directory exists or was successfully created, else nonzero.</returns>
147/// <remarks>
148/// This function modifies characters in szFilePath, but always restores them
149/// regardless of error condition.
150/// </remarks>
151static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath)
152{
153 int ret = 0;
154 wchar_t* szLastSlash = NULL;
155 for (wchar_t* sz = szFilePath; *sz; sz++)
156 {
157 if (*sz == L'\\')
158 {
159 szLastSlash = sz;
160 }
161 }
162 if (szLastSlash)
163 {
164 *szLastSlash = L'\0';
165 ret = EnsureDirectoryExists(szFilePath);
166 *szLastSlash = L'\\';
167 }
168 return ret;
169}
170
171/// <summary>
172/// FDI callback for handling files in the cabinet.
173/// </summary>
174/// <param name="fdint">Type of notification.</param>
175/// <param name="pfdin">Structure containing data about the notification.</param>
176/// <remarks>
177/// Refer to fdi.h for more comments on this notification callback.
178/// </remarks>
179static FNFDINOTIFY(CabNotification)
180{
181 // fdintCOPY_FILE:
182 // Called for each file that *starts* in the current cabinet, giving
183 // the client the opportunity to request that the file be copied or
184 // skipped.
185 // Entry:
186 // pfdin->psz1 = file name in cabinet
187 // pfdin->cb = uncompressed size of file
188 // pfdin->date = file date
189 // pfdin->time = file time
190 // pfdin->attribs = file attributes
191 // pfdin->iFolder = file's folder index
192 // Exit-Success:
193 // Return non-zero file handle for destination file; FDI writes
194 // data to this file use the PFNWRITE function supplied to FDICreate,
195 // and then calls fdintCLOSE_FILE_INFO to close the file and set
196 // the date, time, and attributes.
197 // Exit-Failure:
198 // Returns 0 => Skip file, do not copy
199 // Returns -1 => Abort FDICopy() call
200 if (fdint == fdintCOPY_FILE)
201 {
202 size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0);
203 size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile;
204 wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t));
205 if (szFilePath == NULL) return -1;
206 StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir);
207 StringCchCatW(szFilePath, cchFilePath + 1, L"\\");
208 MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1,
209 szFilePath + cchFilePath - cchFile, (int) cchFile + 1);
210 int hf = -1;
211 if (EnsureFileDirectoryExists(szFilePath) == 0)
212 {
213 _wsopen__s(hf, szFilePath,
214 _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL,
215 _SH_DENYWR, _S_IREAD | _S_IWRITE);
216 }
217 return hf;
218 }
219
220 // fdintCLOSE_FILE_INFO:
221 // Called after all of the data has been written to a target file.
222 // This function must close the file and set the file date, time,
223 // and attributes.
224 // Entry:
225 // pfdin->psz1 = file name in cabinet
226 // pfdin->hf = file handle
227 // pfdin->date = file date
228 // pfdin->time = file time
229 // pfdin->attribs = file attributes
230 // pfdin->iFolder = file's folder index
231 // pfdin->cb = Run After Extract (0 - don't run, 1 Run)
232 // Exit-Success:
233 // Returns TRUE
234 // Exit-Failure:
235 // Returns FALSE, or -1 to abort
236 else if (fdint == fdintCLOSE_FILE_INFO)
237 {
238 _close((int) pfdin->hf);
239 return TRUE;
240 }
241 return 0;
242}
243
244/// <summary>
245/// Extracts all contents of a cabinet file to a directory.
246/// </summary>
247/// <param name="szCabFile">Path to the cabinet file to be extracted.
248/// The cabinet may actually start at some offset within the file,
249/// as long as that offset is a multiple of 256.</param>
250/// <param name="szExtractDir">Directory where files are to be extracted.
251/// This directory must already exist, but should be empty.</param>
252/// <returns>0 if the cabinet was extracted successfully,
253/// or an error code if any error occurred.</returns>
254/// <remarks>
255/// The extraction will not overwrite any files in the destination
256/// directory; extraction will be interrupted and fail if any files
257/// with the same name already exist.
258/// </remarks>
259int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir)
260{
261 ERF erf;
262 // Most of the FDI callbacks can be handled by existing CRT I/O functions.
263 // For our functionality we only need to handle the open and seek callbacks.
264 HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen,
265 (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close,
266 CabSeek, cpu80386, &erf);
267 if (hfdi != NULL)
268 {
269 g_hfdi = hfdi;
270 g_szCabFile = szCabFile;
271 g_szExtractDir = szExtractDir;
272 char szEmpty[1] = {0};
273 if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL))
274 {
275 FDIDestroy(hfdi);
276 return 0;
277 }
278 FDIDestroy(hfdi);
279 }
280
281 return erf.erfOper;
282}
diff --git a/src/dtf/SfxCA/RemoteMsi.cpp b/src/dtf/SfxCA/RemoteMsi.cpp
new file mode 100644
index 00000000..ba59fdf7
--- /dev/null
+++ b/src/dtf/SfxCA/RemoteMsi.cpp
@@ -0,0 +1,629 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "RemoteMsiSession.h"
5
6
7//
8// Ensures that the request buffer is large enough to hold a request,
9// reallocating the buffer if necessary.
10// It will also reduce the buffer size if the previous allocation was very large.
11//
12static __success(return == 0) UINT EnsureBufSize(__deref_out_ecount(*pcchBuf) wchar_t** pszBuf, __deref_inout DWORD* pcchBuf, DWORD cchRequired)
13{
14 // It will also reduce the buffer size if the previous allocation was very large.
15 if (*pcchBuf < cchRequired || (LARGE_BUFFER_THRESHOLD/2 < *pcchBuf && cchRequired < *pcchBuf))
16 {
17 if (*pszBuf != NULL)
18 {
19 SecureZeroMemory(*pszBuf, *pcchBuf);
20 delete[] *pszBuf;
21 }
22
23 *pcchBuf = max(MIN_BUFFER_STRING_SIZE, cchRequired);
24 *pszBuf = new wchar_t[*pcchBuf];
25
26 if (*pszBuf == NULL)
27 {
28 return ERROR_OUTOFMEMORY;
29 }
30 }
31
32 return ERROR_SUCCESS;
33}
34
35typedef int (WINAPI *PMsiFunc_I_I)(int in1, __out int* out1);
36typedef int (WINAPI *PMsiFunc_II_I)(int in1, int in2, __out int* out1);
37typedef int (WINAPI *PMsiFunc_IS_I)(int in1, __in_z wchar_t* in2, __out int* out1);
38typedef int (WINAPI *PMsiFunc_ISI_I)(int in1, __in_z wchar_t* in2, int in3, __out int* out1);
39typedef int (WINAPI *PMsiFunc_ISII_I)(int in1, __in_z wchar_t* in2, int in3, int in4, __out int* out1);
40typedef int (WINAPI *PMsiFunc_IS_II)(int in1, __in_z wchar_t* in2, __out int* out1, __out int* out2);
41typedef MSIDBERROR (WINAPI *PMsiEFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
42typedef int (WINAPI *PMsiFunc_I_S)(int in1, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
43typedef int (WINAPI *PMsiFunc_II_S)(int in1, int in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
44typedef int (WINAPI *PMsiFunc_IS_S)(int in1, __in_z wchar_t* in2, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1);
45typedef int (WINAPI *PMsiFunc_ISII_SII)(int in1, __in_z wchar_t* in2, int in3, int in4, __out_ecount_full(*cchOut1) wchar_t* out1, __inout DWORD* cchOut1, __out int* out2, __out int* out3);
46
47UINT MsiFunc_I_I(PMsiFunc_I_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
48{
49 int in1 = pReq->fields[0].iValue;
50 int out1;
51 UINT ret = (UINT) func(in1, &out1);
52 if (ret == 0)
53 {
54 pResp->fields[1].vt = VT_I4;
55 pResp->fields[1].iValue = out1;
56 }
57 return ret;
58}
59
60UINT MsiFunc_II_I(PMsiFunc_II_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
61{
62 int in1 = pReq->fields[0].iValue;
63 int in2 = pReq->fields[1].iValue;
64 int out1;
65 UINT ret = (UINT) func(in1, in2, &out1);
66 if (ret == 0)
67 {
68 pResp->fields[1].vt = VT_I4;
69 pResp->fields[1].iValue = out1;
70 }
71 return ret;
72}
73
74UINT MsiFunc_IS_I(PMsiFunc_IS_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
75{
76 int in1 = pReq->fields[0].iValue;
77 wchar_t* in2 = pReq->fields[1].szValue;
78 int out1;
79 UINT ret = (UINT) func(in1, in2, &out1);
80 if (ret == 0)
81 {
82 pResp->fields[1].vt = VT_I4;
83 pResp->fields[1].iValue = out1;
84 }
85 return ret;
86}
87
88UINT MsiFunc_ISI_I(PMsiFunc_ISI_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
89{
90 int in1 = pReq->fields[0].iValue;
91 wchar_t* in2 = pReq->fields[1].szValue;
92 int in3 = pReq->fields[2].iValue;
93 int out1;
94 UINT ret = (UINT) func(in1, in2, in3, &out1);
95 if (ret == 0)
96 {
97 pResp->fields[1].vt = VT_I4;
98 pResp->fields[1].iValue = out1;
99 }
100 return ret;
101}
102
103UINT MsiFunc_ISII_I(PMsiFunc_ISII_I func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
104{
105 int in1 = pReq->fields[0].iValue;
106 wchar_t* in2 = pReq->fields[1].szValue;
107 int in3 = pReq->fields[2].iValue;
108 int in4 = pReq->fields[3].iValue;
109 int out1;
110 UINT ret = (UINT) func(in1, in2, in3, in4, &out1);
111 if (ret == 0)
112 {
113 pResp->fields[1].vt = VT_I4;
114 pResp->fields[1].iValue = out1;
115 }
116 return ret;
117}
118
119UINT MsiFunc_IS_II(PMsiFunc_IS_II func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp)
120{
121 int in1 = pReq->fields[0].iValue;
122 wchar_t* in2 = pReq->fields[1].szValue;
123 int out1, out2;
124 UINT ret = (UINT) func(in1, in2, &out1, &out2);
125 if (ret == 0)
126 {
127 pResp->fields[1].vt = VT_I4;
128 pResp->fields[1].iValue = out1;
129 pResp->fields[2].vt = VT_I4;
130 pResp->fields[2].iValue = out2;
131 }
132 return ret;
133}
134
135UINT MsiFunc_I_S(PMsiFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
136{
137 int in1 = pReq->fields[0].iValue;
138 szBuf[0] = L'\0';
139 DWORD cchValue = cchBuf;
140 UINT ret = (UINT) func(in1, szBuf, &cchValue);
141 if (ret == ERROR_MORE_DATA)
142 {
143 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
144 if (ret == 0)
145 {
146 ret = (UINT) func(in1, szBuf, &cchValue);
147 }
148 }
149 if (ret == 0)
150 {
151 pResp->fields[1].vt = VT_LPWSTR;
152 pResp->fields[1].szValue = szBuf;
153 }
154 return ret;
155}
156
157MSIDBERROR MsiEFunc_I_S(PMsiEFunc_I_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
158{
159 int in1 = pReq->fields[0].iValue;
160 szBuf[0] = L'\0';
161 DWORD cchValue = cchBuf;
162 MSIDBERROR ret = func(in1, szBuf, &cchValue);
163 if (ret == MSIDBERROR_MOREDATA)
164 {
165 if (0 == EnsureBufSize(&szBuf, &cchBuf, ++cchValue))
166 {
167 ret = func(in1, szBuf, &cchValue);
168 }
169 }
170 if (ret != MSIDBERROR_MOREDATA)
171 {
172 pResp->fields[1].vt = VT_LPWSTR;
173 pResp->fields[1].szValue = szBuf;
174 }
175 return ret;
176}
177
178UINT MsiFunc_II_S(PMsiFunc_II_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
179{
180 int in1 = pReq->fields[0].iValue;
181 int in2 = pReq->fields[1].iValue;
182 szBuf[0] = L'\0';
183 DWORD cchValue = cchBuf;
184 UINT ret = (UINT) func(in1, in2, szBuf, &cchValue);
185 if (ret == ERROR_MORE_DATA)
186 {
187 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
188 if (ret == 0)
189 {
190 ret = (UINT) func(in1, in2, szBuf, &cchValue);
191 }
192 }
193 if (ret == 0)
194 {
195 pResp->fields[1].vt = VT_LPWSTR;
196 pResp->fields[1].szValue = szBuf;
197 }
198 return ret;
199}
200
201UINT MsiFunc_IS_S(PMsiFunc_IS_S func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
202{
203 int in1 = pReq->fields[0].iValue;
204 wchar_t* in2 = pReq->fields[1].szValue;
205 szBuf[0] = L'\0';
206 DWORD cchValue = cchBuf;
207 UINT ret = (UINT) func(in1, in2, szBuf, &cchValue);
208 if (ret == ERROR_MORE_DATA)
209 {
210 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
211 if (ret == 0)
212 {
213 ret = (UINT) func(in1, in2, szBuf, &cchValue);
214 }
215 }
216 if (ret == 0)
217 {
218 pResp->fields[1].vt = VT_LPWSTR;
219 pResp->fields[1].szValue = szBuf;
220 }
221 return ret;
222}
223
224UINT MsiFunc_ISII_SII(PMsiFunc_ISII_SII func, const RemoteMsiSession::RequestData* pReq, RemoteMsiSession::RequestData* pResp, __deref_inout_ecount(cchBuf) wchar_t*& szBuf, __inout DWORD& cchBuf)
225{
226 int in1 = pReq->fields[0].iValue;
227 wchar_t* in2 = pReq->fields[1].szValue;
228 int in3 = pReq->fields[2].iValue;
229 int in4 = pReq->fields[3].iValue;
230 szBuf[0] = L'\0';
231 DWORD cchValue = cchBuf;
232 int out2, out3;
233 UINT ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3);
234 if (ret == ERROR_MORE_DATA)
235 {
236 ret = EnsureBufSize(&szBuf, &cchBuf, ++cchValue);
237 if (ret == 0)
238 {
239 ret = (UINT) func(in1, in2, in3, in4, szBuf, &cchValue, &out2, &out3);
240 }
241 }
242 if (ret == 0)
243 {
244 pResp->fields[1].vt = VT_LPWSTR;
245 pResp->fields[1].szValue = szBuf;
246 pResp->fields[2].vt = VT_I4;
247 pResp->fields[2].iValue = out2;
248 pResp->fields[3].vt = VT_I4;
249 pResp->fields[3].iValue = out3;
250 }
251 return ret;
252}
253
254void RemoteMsiSession::ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp)
255{
256 SecureZeroMemory(pResp, sizeof(RequestData));
257
258 UINT ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, 1024);
259
260 if (0 == ret)
261 {
262 switch (id)
263 {
264 case RemoteMsiSession::EndSession:
265 {
266 this->ExitCode = pReq->fields[0].iValue;
267 }
268 break;
269 case RemoteMsiSession::MsiCloseHandle:
270 {
271 MSIHANDLE h = (MSIHANDLE) pReq->fields[0].iValue;
272 ret = ::MsiCloseHandle(h);
273 }
274 break;
275 case RemoteMsiSession::MsiProcessMessage:
276 {
277 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
278 INSTALLMESSAGE eMessageType = (INSTALLMESSAGE) pReq->fields[1].iValue;
279 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue;
280 ret = ::MsiProcessMessage(hInstall, eMessageType, hRecord);
281 }
282 break;
283 case RemoteMsiSession::MsiGetProperty:
284 {
285 ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetProperty, pReq, pResp, m_pBufSend, m_cbBufSend);
286 }
287 break;
288 case RemoteMsiSession::MsiSetProperty:
289 {
290 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
291 const wchar_t* szName = pReq->fields[1].szValue;
292 const wchar_t* szValue = pReq->fields[2].szValue;
293 ret = ::MsiSetProperty(hInstall, szName, szValue);
294 }
295 break;
296 case RemoteMsiSession::MsiCreateRecord:
297 {
298 UINT cParams = pReq->fields[0].uiValue;
299 ret = ::MsiCreateRecord(cParams);
300 }
301 break;
302 case RemoteMsiSession::MsiRecordGetFieldCount:
303 {
304 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
305 ret = ::MsiRecordGetFieldCount(hRecord);
306 }
307 break;
308 case RemoteMsiSession::MsiRecordGetInteger:
309 {
310 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
311 UINT iField = pReq->fields[1].uiValue;
312 ret = ::MsiRecordGetInteger(hRecord, iField);
313 }
314 break;
315 case RemoteMsiSession::MsiRecordSetInteger:
316 {
317 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
318 UINT iField = pReq->fields[1].uiValue;
319 int iValue = pReq->fields[2].iValue;
320 ret = ::MsiRecordSetInteger(hRecord, iField, iValue);
321 }
322 break;
323 case RemoteMsiSession::MsiRecordGetString:
324 {
325 ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiRecordGetString, pReq, pResp, m_pBufSend, m_cbBufSend);
326 }
327 break;
328 case RemoteMsiSession::MsiRecordSetString:
329 {
330 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
331 UINT iField = pReq->fields[1].uiValue;
332 const wchar_t* szValue = pReq->fields[2].szValue;
333 ret = ::MsiRecordSetString(hRecord, iField, szValue);
334 }
335 break;
336 case RemoteMsiSession::MsiRecordClearData:
337 {
338 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
339 ret = ::MsiRecordClearData(hRecord);
340 }
341 break;
342 case RemoteMsiSession::MsiRecordIsNull:
343 {
344 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
345 UINT iField = pReq->fields[1].uiValue;
346 ret = ::MsiRecordIsNull(hRecord, iField);
347 }
348 break;
349 case RemoteMsiSession::MsiFormatRecord:
350 {
351 ret = MsiFunc_II_S((PMsiFunc_II_S) ::MsiFormatRecord, pReq, pResp, m_pBufSend, m_cbBufSend);
352 }
353 break;
354 case RemoteMsiSession::MsiGetActiveDatabase:
355 {
356 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
357 ret = (UINT) ::MsiGetActiveDatabase(hInstall);
358 }
359 break;
360 case RemoteMsiSession::MsiDatabaseOpenView:
361 {
362 ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseOpenView, pReq, pResp);
363 }
364 break;
365 case RemoteMsiSession::MsiViewExecute:
366 {
367 MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue;
368 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[1].iValue;
369 ret = ::MsiViewExecute(hView, hRecord);
370 }
371 break;
372 case RemoteMsiSession::MsiViewFetch:
373 {
374 ret = MsiFunc_I_I((PMsiFunc_I_I) ::MsiViewFetch, pReq, pResp);
375 }
376 break;
377 case RemoteMsiSession::MsiViewModify:
378 {
379 MSIHANDLE hView = (MSIHANDLE) pReq->fields[0].iValue;
380 MSIMODIFY eModifyMode = (MSIMODIFY) pReq->fields[1].iValue;
381 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[2].iValue;
382 ret = ::MsiViewModify(hView, eModifyMode, hRecord);
383 }
384 break;
385 case RemoteMsiSession::MsiViewGetError:
386 {
387 ret = MsiEFunc_I_S((PMsiEFunc_I_S) ::MsiViewGetError, pReq, pResp, m_pBufSend, m_cbBufSend);
388 }
389 break;
390 case RemoteMsiSession::MsiViewGetColumnInfo:
391 {
392 ret = MsiFunc_II_I((PMsiFunc_II_I) ::MsiViewGetColumnInfo, pReq, pResp);
393 }
394 break;
395 case RemoteMsiSession::MsiDatabaseGetPrimaryKeys:
396 {
397 ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiDatabaseGetPrimaryKeys, pReq, pResp);
398 }
399 break;
400 case RemoteMsiSession::MsiDatabaseIsTablePersistent:
401 {
402 MSIHANDLE hDb = (MSIHANDLE) pReq->fields[0].iValue;
403 const wchar_t* szTable = pReq->fields[1].szValue;
404 ret = ::MsiDatabaseIsTablePersistent(hDb, szTable);
405 }
406 break;
407 case RemoteMsiSession::MsiDoAction:
408 {
409 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
410 const wchar_t* szAction = pReq->fields[1].szValue;
411 ret = ::MsiDoAction(hInstall, szAction);
412 }
413 break;
414 case RemoteMsiSession::MsiEnumComponentCosts:
415 {
416 ret = MsiFunc_ISII_SII((PMsiFunc_ISII_SII) ::MsiEnumComponentCosts, pReq, pResp, m_pBufSend, m_cbBufSend);
417 }
418 break;
419 case RemoteMsiSession::MsiEvaluateCondition:
420 {
421 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
422 const wchar_t* szCondition = pReq->fields[1].szValue;
423 ret = ::MsiEvaluateCondition(hInstall, szCondition);
424 }
425 break;
426 case RemoteMsiSession::MsiGetComponentState:
427 {
428 ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetComponentState, pReq, pResp);
429 }
430 break;
431 case RemoteMsiSession::MsiGetFeatureCost:
432 {
433 ret = MsiFunc_ISII_I((PMsiFunc_ISII_I) ::MsiGetFeatureCost, pReq, pResp);
434 }
435 break;
436 case RemoteMsiSession::MsiGetFeatureState:
437 {
438 ret = MsiFunc_IS_II((PMsiFunc_IS_II) ::MsiGetFeatureState, pReq, pResp);
439 }
440 break;
441 case RemoteMsiSession::MsiGetFeatureValidStates:
442 {
443 ret = MsiFunc_IS_I((PMsiFunc_IS_I) ::MsiGetFeatureValidStates, pReq, pResp);
444 }
445 break;
446 case RemoteMsiSession::MsiGetLanguage:
447 {
448 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
449 ret = ::MsiGetLanguage(hInstall);
450 }
451 break;
452 case RemoteMsiSession::MsiGetLastErrorRecord:
453 {
454 ret = ::MsiGetLastErrorRecord();
455 }
456 break;
457 case RemoteMsiSession::MsiGetMode:
458 {
459 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
460 MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].iValue;
461 ret = ::MsiGetMode(hInstall, iRunMode);
462 }
463 break;
464 case RemoteMsiSession::MsiGetSourcePath:
465 {
466 ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetSourcePath, pReq, pResp, m_pBufSend, m_cbBufSend);
467 }
468 break;
469 case RemoteMsiSession::MsiGetSummaryInformation:
470 {
471 ret = MsiFunc_ISI_I((PMsiFunc_ISI_I) ::MsiGetSummaryInformation, pReq, pResp);
472 }
473 break;
474 case RemoteMsiSession::MsiGetTargetPath:
475 {
476 ret = MsiFunc_IS_S((PMsiFunc_IS_S) ::MsiGetTargetPath, pReq, pResp, m_pBufSend, m_cbBufSend);
477 }
478 break;
479 case RemoteMsiSession::MsiRecordDataSize:
480 {
481 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
482 UINT iField = pReq->fields[1].uiValue;
483 ret = ::MsiRecordDataSize(hRecord, iField);
484 }
485 break;
486 case RemoteMsiSession::MsiRecordReadStream:
487 {
488 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
489 UINT iField = pReq->fields[1].uiValue;
490 DWORD cbRead = (DWORD) pReq->fields[2].uiValue;
491 ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, (cbRead + 1) / 2);
492 if (ret == 0)
493 {
494 ret = ::MsiRecordReadStream(hRecord, iField, (char*) m_pBufSend, &cbRead);
495 if (ret == 0)
496 {
497 pResp->fields[1].vt = VT_STREAM;
498 pResp->fields[1].szValue = m_pBufSend;
499 pResp->fields[2].vt = VT_I4;
500 pResp->fields[2].uiValue = (UINT) cbRead;
501 }
502 }
503 }
504 break;
505 case RemoteMsiSession::MsiRecordSetStream:
506 {
507 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
508 UINT iField = pReq->fields[1].uiValue;
509 const wchar_t* szFilePath = pReq->fields[2].szValue;
510 ret = ::MsiRecordSetStream(hRecord, iField, szFilePath);
511 }
512 break;
513 case RemoteMsiSession::MsiSequence:
514 {
515 MSIHANDLE hRecord = (MSIHANDLE) pReq->fields[0].iValue;
516 const wchar_t* szTable = pReq->fields[1].szValue;
517 UINT iSequenceMode = pReq->fields[2].uiValue;
518 ret = ::MsiSequence(hRecord, szTable, iSequenceMode);
519 }
520 break;
521 case RemoteMsiSession::MsiSetComponentState:
522 {
523 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
524 const wchar_t* szComponent = pReq->fields[1].szValue;
525 INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue;
526 ret = ::MsiSetComponentState(hInstall, szComponent, iState);
527 }
528 break;
529 case RemoteMsiSession::MsiSetFeatureAttributes:
530 {
531 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
532 const wchar_t* szFeature = pReq->fields[1].szValue;
533 DWORD dwAttrs = (DWORD) pReq->fields[2].uiValue;
534 ret = ::MsiSetFeatureAttributes(hInstall, szFeature, dwAttrs);
535 }
536 break;
537 case RemoteMsiSession::MsiSetFeatureState:
538 {
539 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
540 const wchar_t* szFeature = pReq->fields[1].szValue;
541 INSTALLSTATE iState = (INSTALLSTATE) pReq->fields[2].iValue;
542 ret = ::MsiSetFeatureState(hInstall, szFeature, iState);
543 }
544 break;
545 case RemoteMsiSession::MsiSetInstallLevel:
546 {
547 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
548 int iInstallLevel = pReq->fields[1].iValue;
549 ret = ::MsiSetInstallLevel(hInstall, iInstallLevel);
550 }
551 break;
552 case RemoteMsiSession::MsiSetMode:
553 {
554 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
555 MSIRUNMODE iRunMode = (MSIRUNMODE) pReq->fields[1].uiValue;
556 BOOL fState = (BOOL) pReq->fields[2].iValue;
557 ret = ::MsiSetMode(hInstall, iRunMode, fState);
558 }
559 break;
560 case RemoteMsiSession::MsiSetTargetPath:
561 {
562 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
563 const wchar_t* szFolder = pReq->fields[1].szValue;
564 const wchar_t* szFolderPath = pReq->fields[2].szValue;
565 ret = ::MsiSetTargetPath(hInstall, szFolder, szFolderPath);
566 }
567 break;
568 case RemoteMsiSession::MsiSummaryInfoGetProperty:
569 {
570 MSIHANDLE hSummaryInfo = (MSIHANDLE) pReq->fields[0].iValue;
571 UINT uiProperty = pReq->fields[1].uiValue;
572 UINT uiDataType;
573 int iValue;
574 FILETIME ftValue;
575 m_pBufSend[0] = L'\0';
576 DWORD cchValue = m_cbBufSend;
577 ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue);
578 if (ret == ERROR_MORE_DATA)
579 {
580 ret = EnsureBufSize(&m_pBufSend, &m_cbBufSend, ++cchValue);
581 if (ret == 0)
582 {
583 ret = ::MsiSummaryInfoGetProperty(hSummaryInfo, uiProperty, &uiDataType, &iValue, &ftValue, m_pBufSend, &cchValue);
584 }
585 }
586 if (ret == 0)
587 {
588 pResp->fields[1].vt = VT_UI4;
589 pResp->fields[1].uiValue = uiDataType;
590
591 switch (uiDataType)
592 {
593 case VT_I2:
594 case VT_I4:
595 pResp->fields[2].vt = VT_I4;
596 pResp->fields[2].iValue = iValue;
597 break;
598 case VT_FILETIME:
599 pResp->fields[2].vt = VT_UI4;
600 pResp->fields[2].iValue = ftValue.dwHighDateTime;
601 pResp->fields[3].vt = VT_UI4;
602 pResp->fields[3].iValue = ftValue.dwLowDateTime;
603 break;
604 case VT_LPSTR:
605 pResp->fields[2].vt = VT_LPWSTR;
606 pResp->fields[2].szValue = m_pBufSend;
607 break;
608 }
609 }
610 }
611 break;
612 case RemoteMsiSession::MsiVerifyDiskSpace:
613 {
614 MSIHANDLE hInstall = (MSIHANDLE) pReq->fields[0].iValue;
615 ret = ::MsiVerifyDiskSpace(hInstall);
616 }
617 break;
618
619 default:
620 {
621 ret = ERROR_INVALID_FUNCTION;
622 }
623 break;
624 }
625 }
626
627 pResp->fields[0].vt = VT_UI4;
628 pResp->fields[0].uiValue = ret;
629}
diff --git a/src/dtf/SfxCA/RemoteMsiSession.h b/src/dtf/SfxCA/RemoteMsiSession.h
new file mode 100644
index 00000000..90c7c01f
--- /dev/null
+++ b/src/dtf/SfxCA/RemoteMsiSession.h
@@ -0,0 +1,898 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#define LARGE_BUFFER_THRESHOLD 65536 // bytes
4#define MIN_BUFFER_STRING_SIZE 1024 // wchar_ts
5
6///////////////////////////////////////////////////////////////////////////////////////
7// RemoteMsiSession //
8//////////////////////
9//
10// Allows accessing MSI APIs from another process using named pipes.
11//
12class RemoteMsiSession
13{
14public:
15
16 // This enumeration MUST stay in sync with the
17 // managed equivalent in RemotableNativeMethods.cs!
18 enum RequestId
19 {
20 EndSession = 0,
21 MsiCloseHandle,
22 MsiCreateRecord,
23 MsiDatabaseGetPrimaryKeys,
24 MsiDatabaseIsTablePersistent,
25 MsiDatabaseOpenView,
26 MsiDoAction,
27 MsiEnumComponentCosts,
28 MsiEvaluateCondition,
29 MsiFormatRecord,
30 MsiGetActiveDatabase,
31 MsiGetComponentState,
32 MsiGetFeatureCost,
33 MsiGetFeatureState,
34 MsiGetFeatureValidStates,
35 MsiGetLanguage,
36 MsiGetLastErrorRecord,
37 MsiGetMode,
38 MsiGetProperty,
39 MsiGetSourcePath,
40 MsiGetSummaryInformation,
41 MsiGetTargetPath,
42 MsiProcessMessage,
43 MsiRecordClearData,
44 MsiRecordDataSize,
45 MsiRecordGetFieldCount,
46 MsiRecordGetInteger,
47 MsiRecordGetString,
48 MsiRecordIsNull,
49 MsiRecordReadStream,
50 MsiRecordSetInteger,
51 MsiRecordSetStream,
52 MsiRecordSetString,
53 MsiSequence,
54 MsiSetComponentState,
55 MsiSetFeatureAttributes,
56 MsiSetFeatureState,
57 MsiSetInstallLevel,
58 MsiSetMode,
59 MsiSetProperty,
60 MsiSetTargetPath,
61 MsiSummaryInfoGetProperty,
62 MsiVerifyDiskSpace,
63 MsiViewExecute,
64 MsiViewFetch,
65 MsiViewGetError,
66 MsiViewGetColumnInfo,
67 MsiViewModify,
68 };
69
70 static const int MAX_REQUEST_FIELDS = 4;
71
72 // Used to pass data back and forth for remote API calls,
73 // including in & out params & return values.
74 // Only strings and ints are supported.
75 struct RequestData
76 {
77 struct
78 {
79 VARENUM vt;
80 union {
81 int iValue;
82 UINT uiValue;
83 DWORD cchValue;
84 LPWSTR szValue;
85 BYTE* sValue;
86 DWORD cbValue;
87 };
88 } fields[MAX_REQUEST_FIELDS];
89 };
90
91public:
92
93 // This value is set from the single data parameter in the EndSession request.
94 // It saves the exit code of the out-of-proc custom action.
95 int ExitCode;
96
97 /////////////////////////////////////////////////////////////////////////////////////
98 // RemoteMsiSession constructor
99 //
100 // Creates a new remote session instance, for use either by the server
101 // or client process.
102 //
103 // szName - Identifies the session instance being remoted. The server and
104 // the client must use the same name. The name should be unique
105 // enough to avoid conflicting with other instances on the system.
106 //
107 // fServer - True if the calling process is the server process, false if the
108 // calling process is the client process.
109 //
110 RemoteMsiSession(const wchar_t* szName, bool fServer=true)
111 : m_fServer(fServer),
112 m_szName(szName != NULL && szName[0] != L'\0' ? szName : L"RemoteMsiSession"),
113 m_szPipeName(NULL),
114 m_hPipe(NULL),
115 m_fConnecting(false),
116 m_fConnected(false),
117 m_hReceiveThread(NULL),
118 m_hReceiveStopEvent(NULL),
119 m_pBufReceive(NULL),
120 m_cbBufReceive(0),
121 m_pBufSend(NULL),
122 m_cbBufSend(0),
123 ExitCode(ERROR_INSTALL_FAILURE)
124 {
125 SecureZeroMemory(&m_overlapped, sizeof(OVERLAPPED));
126 m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
127 }
128
129 /////////////////////////////////////////////////////////////////////////////////////
130 // RemoteMsiSession destructor
131 //
132 // Closes any open handles and frees any allocated memory.
133 //
134 ~RemoteMsiSession()
135 {
136 WaitExitCode();
137 if (m_hPipe != NULL)
138 {
139 CloseHandle(m_hPipe);
140 m_hPipe = NULL;
141 }
142 if (m_overlapped.hEvent != NULL)
143 {
144 CloseHandle(m_overlapped.hEvent);
145 m_overlapped.hEvent = NULL;
146 }
147 if (m_szPipeName != NULL)
148 {
149 delete[] m_szPipeName;
150 m_szPipeName = NULL;
151 }
152 if (m_pBufReceive != NULL)
153 {
154 SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
155 delete[] m_pBufReceive;
156 m_pBufReceive = NULL;
157 }
158 if (m_pBufSend != NULL)
159 {
160 SecureZeroMemory(m_pBufSend, m_cbBufSend);
161 delete[] m_pBufSend;
162 m_pBufSend = NULL;
163 }
164 m_fConnecting = false;
165 m_fConnected = false;
166 }
167
168 /////////////////////////////////////////////////////////////////////////////////////
169 // RemoteMsiSession::WaitExitCode()
170 //
171 // Waits for the server processing thread to complete.
172 //
173 void WaitExitCode()
174 {
175 if (m_hReceiveThread != NULL)
176 {
177 SetEvent(m_hReceiveStopEvent);
178 WaitForSingleObject(m_hReceiveThread, INFINITE);
179 CloseHandle(m_hReceiveThread);
180 m_hReceiveThread = NULL;
181 }
182 }
183
184 /////////////////////////////////////////////////////////////////////////////////////
185 // RemoteMsiSession::Connect()
186 //
187 // Connects the inter-process communication channel.
188 // (Currently implemented as a named pipe.)
189 //
190 // This method must be called first by the server process, then by the client
191 // process. The method does not block; the server will asynchronously wait
192 // for the client process to make the connection.
193 //
194 // Returns: 0 on success, Win32 error code on failure.
195 //
196 virtual DWORD Connect()
197 {
198 const wchar_t* szPipePrefix = L"\\\\.\\pipe\\";
199 size_t cchPipeNameBuf = wcslen(szPipePrefix) + wcslen(m_szName) + 1;
200 m_szPipeName = new wchar_t[cchPipeNameBuf];
201
202 if (m_szPipeName == NULL)
203 {
204 return ERROR_OUTOFMEMORY;
205 }
206 else
207 {
208 wcscpy_s(m_szPipeName, cchPipeNameBuf, szPipePrefix);
209 wcscat_s(m_szPipeName, cchPipeNameBuf, m_szName);
210
211 if (m_fServer)
212 {
213 return this->ConnectPipeServer();
214 }
215 else
216 {
217 return this->ConnectPipeClient();
218 }
219 }
220 }
221
222 /////////////////////////////////////////////////////////////////////////////////////
223 // RemoteMsiSession::IsConnected()
224 //
225 // Checks if the server process and client process are currently connected.
226 //
227 virtual bool IsConnected() const
228 {
229 return m_fConnected;
230 }
231
232 /////////////////////////////////////////////////////////////////////////////////////
233 // RemoteMsiSession::ProcessRequests()
234 //
235 // For use by the service process. Watches for requests in the input buffer and calls
236 // the callback for each one.
237 //
238 // This method does not block; it spawns a separate thread to do the work.
239 //
240 // Returns: 0 on success, Win32 error code on failure.
241 //
242 virtual DWORD ProcessRequests()
243 {
244 return this->StartProcessingReqests();
245 }
246
247 /////////////////////////////////////////////////////////////////////////////////////
248 // RemoteMsiSession::SendRequest()
249 //
250 // For use by the client process. Sends a request to the server and
251 // synchronously waits on a response, up to the timeout value.
252 //
253 // id - ID code of the MSI API call being requested.
254 //
255 // pRequest - Pointer to a data structure containing request parameters.
256 //
257 // ppResponse - [OUT] Pointer to a location that receives the response parameters.
258 //
259 // Returns: 0 on success, Win32 error code on failure.
260 // Returns WAIT_TIMEOUT if no response was received in time.
261 //
262 virtual DWORD SendRequest(RequestId id, const RequestData* pRequest, RequestData** ppResponse)
263 {
264 if (m_fServer)
265 {
266 return ERROR_INVALID_OPERATION;
267 }
268
269 if (!m_fConnected)
270 {
271 *ppResponse = NULL;
272 return 0;
273 }
274
275 DWORD dwRet = this->SendRequest(id, pRequest);
276 if (dwRet != 0)
277 {
278 return dwRet;
279 }
280
281 if (id != EndSession)
282 {
283 static RequestData response;
284 if (ppResponse != NULL)
285 {
286 *ppResponse = &response;
287 }
288
289 return this->ReceiveResponse(id, &response);
290 }
291 else
292 {
293 CloseHandle(m_hPipe);
294 m_hPipe = NULL;
295 m_fConnected = false;
296 return 0;
297 }
298 }
299
300private:
301
302 //
303 // Do not allow assignment.
304 //
305 RemoteMsiSession& operator=(const RemoteMsiSession&);
306
307 //
308 // Called only by the server process.
309 // Create a new thread to handle receiving requests.
310 //
311 DWORD StartProcessingReqests()
312 {
313 if (!m_fServer || m_hReceiveStopEvent != NULL)
314 {
315 return ERROR_INVALID_OPERATION;
316 }
317
318 DWORD dwRet = 0;
319
320 m_hReceiveStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
321
322 if (m_hReceiveStopEvent == NULL)
323 {
324 dwRet = GetLastError();
325 }
326 else
327 {
328 if (m_hReceiveThread != NULL)
329 {
330 CloseHandle(m_hReceiveThread);
331 }
332
333 m_hReceiveThread = CreateThread(NULL, 0,
334 RemoteMsiSession::ProcessRequestsThreadStatic, this, 0, NULL);
335
336 if (m_hReceiveThread == NULL)
337 {
338 dwRet = GetLastError();
339 CloseHandle(m_hReceiveStopEvent);
340 m_hReceiveStopEvent = NULL;
341 }
342 }
343
344 return dwRet;
345 }
346
347 //
348 // Called only by the watcher process.
349 // First verify the connection is complete. Then continually read and parse messages,
350 // invoke the callback, and send the replies.
351 //
352 static DWORD WINAPI ProcessRequestsThreadStatic(void* pv)
353 {
354 return reinterpret_cast<RemoteMsiSession*>(pv)->ProcessRequestsThread();
355 }
356
357 DWORD ProcessRequestsThread()
358 {
359 DWORD dwRet;
360
361 dwRet = CompleteConnection();
362 if (dwRet != 0)
363 {
364 if (dwRet == ERROR_OPERATION_ABORTED) dwRet = 0;
365 }
366
367 while (m_fConnected)
368 {
369 RequestId id;
370 RequestData req;
371 dwRet = ReceiveRequest(&id, &req);
372 if (dwRet != 0)
373 {
374 if (dwRet == ERROR_OPERATION_ABORTED ||
375 dwRet == ERROR_BROKEN_PIPE || dwRet == ERROR_NO_DATA)
376 {
377 dwRet = 0;
378 }
379 }
380 else
381 {
382 RequestData resp;
383 ProcessRequest(id, &req, &resp);
384
385 if (id == EndSession)
386 {
387 break;
388 }
389
390 dwRet = SendResponse(id, &resp);
391 if (dwRet != 0 && dwRet != ERROR_BROKEN_PIPE && dwRet != ERROR_NO_DATA)
392 {
393 dwRet = 0;
394 }
395 }
396 }
397
398 CloseHandle(m_hReceiveStopEvent);
399 m_hReceiveStopEvent = NULL;
400 return dwRet;
401 }
402
403 //
404 // Called only by the server process's receive thread.
405 // Read one request into a RequestData object.
406 //
407 DWORD ReceiveRequest(RequestId* pId, RequestData* pReq)
408 {
409 DWORD dwRet = this->ReadPipe((BYTE*) pId, sizeof(RequestId));
410
411 if (dwRet == 0)
412 {
413 dwRet = this->ReadRequestData(pReq);
414 }
415
416 return dwRet;
417 }
418
419 //
420 // Called by the server process's receive thread or the client's request call
421 // to read the response. Read data from the pipe, allowing interruption by the
422 // stop event if on the server.
423 //
424 DWORD ReadPipe(__out_bcount(cbRead) BYTE* pBuf, DWORD cbRead)
425 {
426 DWORD dwRet = 0;
427 DWORD dwTotalBytesRead = 0;
428
429 while (dwRet == 0 && dwTotalBytesRead < cbRead)
430 {
431 DWORD dwBytesReadThisTime;
432 ResetEvent(m_overlapped.hEvent);
433 if (!ReadFile(m_hPipe, pBuf + dwTotalBytesRead, cbRead - dwTotalBytesRead, &dwBytesReadThisTime, &m_overlapped))
434 {
435 dwRet = GetLastError();
436 if (dwRet == ERROR_IO_PENDING)
437 {
438 if (m_fServer)
439 {
440 HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
441 dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
442 }
443 else
444 {
445 dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
446 }
447
448 if (dwRet == WAIT_OBJECT_0)
449 {
450 if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesReadThisTime, FALSE))
451 {
452 dwRet = GetLastError();
453 }
454 }
455 else if (dwRet == WAIT_FAILED)
456 {
457 dwRet = GetLastError();
458 }
459 else
460 {
461 dwRet = ERROR_OPERATION_ABORTED;
462 }
463 }
464 }
465
466 dwTotalBytesRead += dwBytesReadThisTime;
467 }
468
469 if (dwRet != 0)
470 {
471 if (m_fServer)
472 {
473 CancelIo(m_hPipe);
474 DisconnectNamedPipe(m_hPipe);
475 }
476 else
477 {
478 CloseHandle(m_hPipe);
479 m_hPipe = NULL;
480 }
481 m_fConnected = false;
482 }
483
484 return dwRet;
485 }
486
487 //
488 // Called only by the server process.
489 // Given a request, invoke the MSI API and return the response.
490 // This is implemented in RemoteMsi.cpp.
491 //
492 void ProcessRequest(RequestId id, const RequestData* pReq, RequestData* pResp);
493
494 //
495 // Called only by the client process.
496 // Send request data over the pipe.
497 //
498 DWORD SendRequest(RequestId id, const RequestData* pRequest)
499 {
500 DWORD dwRet = WriteRequestData(id, pRequest);
501
502 if (dwRet != 0)
503 {
504 m_fConnected = false;
505 CloseHandle(m_hPipe);
506 m_hPipe = NULL;
507 }
508
509 return dwRet;
510 }
511
512 //
513 // Called only by the server process.
514 // Just send a response over the pipe.
515 //
516 DWORD SendResponse(RequestId id, const RequestData* pResp)
517 {
518 DWORD dwRet = WriteRequestData(id, pResp);
519
520 if (dwRet != 0)
521 {
522 DisconnectNamedPipe(m_hPipe);
523 m_fConnected = false;
524 }
525
526 return dwRet;
527 }
528
529 //
530 // Called either by the client or server process.
531 // Writes data to the pipe for a request or response.
532 //
533 DWORD WriteRequestData(RequestId id, const RequestData* pReq)
534 {
535 DWORD dwRet = 0;
536
537 RequestData req = *pReq; // Make a copy because the const data can't be changed.
538
539 dwRet = this->WritePipe((const BYTE *)&id, sizeof(RequestId));
540 if (dwRet != 0)
541 {
542 return dwRet;
543 }
544
545 BYTE* sValues[MAX_REQUEST_FIELDS] = {0};
546 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
547 {
548 if (req.fields[i].vt == VT_LPWSTR)
549 {
550 sValues[i] = (BYTE*) req.fields[i].szValue;
551 req.fields[i].cchValue = (DWORD) wcslen(req.fields[i].szValue);
552 }
553 else if (req.fields[i].vt == VT_STREAM)
554 {
555 sValues[i] = req.fields[i].sValue;
556 req.fields[i].cbValue = (DWORD) req.fields[i + 1].uiValue;
557 }
558 }
559
560 dwRet = this->WritePipe((const BYTE *)&req, sizeof(RequestData));
561 if (dwRet != 0)
562 {
563 return dwRet;
564 }
565
566 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
567 {
568 if (sValues[i] != NULL)
569 {
570 DWORD cbValue;
571 if (req.fields[i].vt == VT_LPWSTR)
572 {
573 cbValue = (req.fields[i].cchValue + 1) * sizeof(WCHAR);
574 }
575 else
576 {
577 cbValue = req.fields[i].cbValue;
578 }
579
580 dwRet = this->WritePipe(const_cast<BYTE*> (sValues[i]), cbValue);
581 if (dwRet != 0)
582 {
583 break;
584 }
585 }
586 }
587
588 return dwRet;
589 }
590
591 //
592 // Called when writing a request or response. Writes data to
593 // the pipe, allowing interruption by the stop event if on the server.
594 //
595 DWORD WritePipe(const BYTE* pBuf, DWORD cbWrite)
596 {
597 DWORD dwRet = 0;
598 DWORD dwTotalBytesWritten = 0;
599
600 while (dwRet == 0 && dwTotalBytesWritten < cbWrite)
601 {
602 DWORD dwBytesWrittenThisTime;
603 ResetEvent(m_overlapped.hEvent);
604 if (!WriteFile(m_hPipe, pBuf + dwTotalBytesWritten, cbWrite - dwTotalBytesWritten, &dwBytesWrittenThisTime, &m_overlapped))
605 {
606 dwRet = GetLastError();
607 if (dwRet == ERROR_IO_PENDING)
608 {
609 if (m_fServer)
610 {
611 HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
612 dwRet = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
613 }
614 else
615 {
616 dwRet = WaitForSingleObject(m_overlapped.hEvent, INFINITE);
617 }
618
619 if (dwRet == WAIT_OBJECT_0)
620 {
621 if (!GetOverlappedResult(m_hPipe, &m_overlapped, &dwBytesWrittenThisTime, FALSE))
622 {
623 dwRet = GetLastError();
624 }
625 }
626 else if (dwRet == WAIT_FAILED)
627 {
628 dwRet = GetLastError();
629 }
630 else
631 {
632 dwRet = ERROR_OPERATION_ABORTED;
633 }
634 }
635 }
636
637 dwTotalBytesWritten += dwBytesWrittenThisTime;
638 }
639
640 return dwRet;
641 }
642
643 //
644 // Called either by the client or server process.
645 // Reads data from the pipe for a request or response.
646 //
647 DWORD ReadRequestData(RequestData* pReq)
648 {
649 DWORD dwRet = ReadPipe((BYTE*) pReq, sizeof(RequestData));
650
651 if (dwRet == 0)
652 {
653 DWORD cbData = 0;
654 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
655 {
656 if (pReq->fields[i].vt == VT_LPWSTR)
657 {
658 cbData += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
659 }
660 else if (pReq->fields[i].vt == VT_STREAM)
661 {
662 cbData += pReq->fields[i].cbValue;
663 }
664 }
665
666 if (cbData > 0)
667 {
668 if (!CheckRequestDataBuf(cbData))
669 {
670 return ERROR_OUTOFMEMORY;
671 }
672
673 dwRet = this->ReadPipe((BYTE*) m_pBufReceive, cbData);
674 if (dwRet == 0)
675 {
676 DWORD dwOffset = 0;
677 for (int i = 0; i < MAX_REQUEST_FIELDS; i++)
678 {
679 if (pReq->fields[i].vt == VT_LPWSTR)
680 {
681 LPWSTR szTemp = (LPWSTR) (m_pBufReceive + dwOffset);
682 dwOffset += (pReq->fields[i].cchValue + 1) * sizeof(WCHAR);
683 pReq->fields[i].szValue = szTemp;
684 }
685 else if (pReq->fields[i].vt == VT_STREAM)
686 {
687 BYTE* sTemp = m_pBufReceive + dwOffset;
688 dwOffset += pReq->fields[i].cbValue;
689 pReq->fields[i].sValue = sTemp;
690 }
691 }
692 }
693 }
694 }
695
696 return dwRet;
697 }
698
699 //
700 // Called only by the client process.
701 // Wait for a response on the pipe. If no response is received before the timeout,
702 // then give up and close the connection.
703 //
704 DWORD ReceiveResponse(RequestId id, RequestData* pResp)
705 {
706 RequestId responseId;
707 DWORD dwRet = ReadPipe((BYTE*) &responseId, sizeof(RequestId));
708 if (dwRet == 0 && responseId != id)
709 {
710 dwRet = ERROR_OPERATION_ABORTED;
711 }
712
713 if (dwRet == 0)
714 {
715 dwRet = this->ReadRequestData(pResp);
716 }
717
718 return dwRet;
719 }
720
721 //
722 // Called only by the server process's receive thread.
723 // Try to complete and verify an asynchronous connection operation.
724 //
725 DWORD CompleteConnection()
726 {
727 DWORD dwRet = 0;
728 if (m_fConnecting)
729 {
730 HANDLE hWaitHandles[] = { m_overlapped.hEvent, m_hReceiveStopEvent };
731 DWORD dwWaitRes = WaitForMultipleObjects(2, hWaitHandles, FALSE, INFINITE);
732
733 if (dwWaitRes == WAIT_OBJECT_0)
734 {
735 m_fConnecting = false;
736
737 DWORD dwUnused;
738 if (GetOverlappedResult(m_hPipe, &m_overlapped, &dwUnused, FALSE))
739 {
740 m_fConnected = true;
741 }
742 else
743 {
744 dwRet = GetLastError();
745 }
746 }
747 else if (dwWaitRes == WAIT_FAILED)
748 {
749 CancelIo(m_hPipe);
750 dwRet = GetLastError();
751 }
752 else
753 {
754 CancelIo(m_hPipe);
755 dwRet = ERROR_OPERATION_ABORTED;
756 }
757 }
758 return dwRet;
759 }
760
761 //
762 // Called only by the server process.
763 // Creates a named pipe instance and begins asynchronously waiting
764 // for a connection from the client process.
765 //
766 DWORD ConnectPipeServer()
767 {
768 DWORD dwRet = 0;
769 const int BUFSIZE = 1024; // Suggested pipe I/O buffer sizes
770 m_hPipe = CreateNamedPipe(
771 m_szPipeName,
772 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
773 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
774 1, BUFSIZE, BUFSIZE, 0, NULL);
775 if (m_hPipe == INVALID_HANDLE_VALUE)
776 {
777 m_hPipe = NULL;
778 dwRet = GetLastError();
779 }
780 else if (ConnectNamedPipe(m_hPipe, &m_overlapped))
781 {
782 m_fConnected = true;
783 }
784 else
785 {
786 dwRet = GetLastError();
787
788 if (dwRet == ERROR_PIPE_BUSY)
789 {
790 // All pipe instances are busy, so wait for a maximum of 20 seconds
791 dwRet = 0;
792 if (WaitNamedPipe(m_szPipeName, 20000))
793 {
794 m_fConnected = true;
795 }
796 else
797 {
798 dwRet = GetLastError();
799 }
800 }
801
802 if (dwRet == ERROR_IO_PENDING)
803 {
804 dwRet = 0;
805 m_fConnecting = true;
806 }
807 }
808 return dwRet;
809 }
810
811 //
812 // Called only by the client process.
813 // Attemps to open a connection to an existing named pipe instance
814 // which should have already been created by the server process.
815 //
816 DWORD ConnectPipeClient()
817 {
818 DWORD dwRet = 0;
819 m_hPipe = CreateFile(
820 m_szPipeName, GENERIC_READ | GENERIC_WRITE,
821 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
822 if (m_hPipe != INVALID_HANDLE_VALUE)
823 {
824 m_fConnected = true;
825 }
826 else
827 {
828 m_hPipe = NULL;
829 dwRet = GetLastError();
830 }
831 return dwRet;
832 }
833
834 //
835 // Ensures that the request buffer is large enough to hold a request,
836 // reallocating the buffer if necessary.
837 // It will also reduce the buffer size if the previous allocation was very large.
838 //
839 BOOL CheckRequestDataBuf(DWORD cbBuf)
840 {
841 if (m_cbBufReceive < cbBuf || (LARGE_BUFFER_THRESHOLD < m_cbBufReceive && cbBuf < m_cbBufReceive))
842 {
843 if (m_pBufReceive != NULL)
844 {
845 SecureZeroMemory(m_pBufReceive, m_cbBufReceive);
846 delete[] m_pBufReceive;
847 }
848 m_cbBufReceive = max(MIN_BUFFER_STRING_SIZE*2, cbBuf);
849 m_pBufReceive = new BYTE[m_cbBufReceive];
850 if (m_pBufReceive == NULL)
851 {
852 m_cbBufReceive = 0;
853 }
854 }
855 return m_pBufReceive != NULL;
856 }
857
858private:
859
860 // Name of this instance.
861 const wchar_t* m_szName;
862
863 // "\\.\pipe\name"
864 wchar_t* m_szPipeName;
865
866 // Handle to the pipe instance.
867 HANDLE m_hPipe;
868
869 // Handle to the thread that receives requests.
870 HANDLE m_hReceiveThread;
871
872 // Handle to the event used to signal the receive thread to exit.
873 HANDLE m_hReceiveStopEvent;
874
875 // All pipe I/O is done in overlapped mode to avoid unintentional blocking.
876 OVERLAPPED m_overlapped;
877
878 // Dynamically-resized buffer for receiving requests.
879 BYTE* m_pBufReceive;
880
881 // Current size of the receive request buffer.
882 DWORD m_cbBufReceive;
883
884 // Dynamically-resized buffer for sending requests.
885 wchar_t* m_pBufSend;
886
887 // Current size of the send request buffer.
888 DWORD m_cbBufSend;
889
890 // True if this is the server process, false if this is the client process.
891 const bool m_fServer;
892
893 // True if an asynchronous connection operation is currently in progress.
894 bool m_fConnecting;
895
896 // True if the pipe is currently connected.
897 bool m_fConnected;
898};
diff --git a/src/dtf/SfxCA/SfxCA.cpp b/src/dtf/SfxCA/SfxCA.cpp
new file mode 100644
index 00000000..06319f1e
--- /dev/null
+++ b/src/dtf/SfxCA/SfxCA.cpp
@@ -0,0 +1,363 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "EntryPoints.h"
5#include "SfxUtil.h"
6
7#define MANAGED_CAs_OUT_OF_PROC 1
8
9HMODULE g_hModule;
10bool g_fRunningOutOfProc = false;
11
12RemoteMsiSession* g_pRemote = NULL;
13
14// Prototypes for local functions.
15// See the function definitions for comments.
16
17bool InvokeManagedCustomAction(MSIHANDLE hSession,
18 _AppDomain* pAppDomain, const wchar_t* szEntryPoint, int* piResult);
19
20/// <summary>
21/// Entry-point for the CA DLL when re-launched as a separate process;
22/// connects the comm channel for remote MSI APIs, then invokes the
23/// managed custom action entry-point.
24/// </summary>
25/// <remarks>
26/// Do not change the parameters or calling-convention: RUNDLL32
27/// requires this exact signature.
28/// </remarks>
29extern "C"
30void __stdcall InvokeManagedCustomActionOutOfProc(
31 __in HWND hwnd, __in HINSTANCE hinst, __in_z wchar_t* szCmdLine, int nCmdShow)
32{
33 UNREFERENCED_PARAMETER(hwnd);
34 UNREFERENCED_PARAMETER(hinst);
35 UNREFERENCED_PARAMETER(nCmdShow);
36
37 g_fRunningOutOfProc = true;
38
39 const wchar_t* szSessionName = szCmdLine;
40 MSIHANDLE hSession;
41 const wchar_t* szEntryPoint;
42
43 int i;
44 for (i = 0; szCmdLine[i] && szCmdLine[i] != L' '; i++);
45 if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0';
46 hSession = _wtoi(szCmdLine + i);
47
48 for (; szCmdLine[i] && szCmdLine[i] != L' '; i++);
49 if (szCmdLine[i] != L'\0') szCmdLine[i++] = L'\0';
50 szEntryPoint = szCmdLine + i;
51
52 g_pRemote = new RemoteMsiSession(szSessionName, false);
53 g_pRemote->Connect();
54
55 int ret = InvokeCustomAction(hSession, NULL, szEntryPoint);
56
57 RemoteMsiSession::RequestData requestData;
58 SecureZeroMemory(&requestData, sizeof(RemoteMsiSession::RequestData));
59 requestData.fields[0].vt = VT_I4;
60 requestData.fields[0].iValue = ret;
61 g_pRemote->SendRequest(RemoteMsiSession::EndSession, &requestData, NULL);
62 delete g_pRemote;
63}
64
65/// <summary>
66/// Re-launch this CA DLL as a separate process, and setup a comm channel
67/// for remote MSI API calls back to this process.
68/// </summary>
69int InvokeOutOfProcManagedCustomAction(MSIHANDLE hSession, const wchar_t* szEntryPoint)
70{
71 wchar_t szSessionName[100] = {0};
72 swprintf_s(szSessionName, 100, L"SfxCA_%d", ::GetTickCount());
73
74 RemoteMsiSession remote(szSessionName, true);
75
76 DWORD ret = remote.Connect();
77 if (ret != 0)
78 {
79 Log(hSession, L"Failed to create communication pipe for new CA process. Error code: %d", ret);
80 return ERROR_INSTALL_FAILURE;
81 }
82
83 ret = remote.ProcessRequests();
84 if (ret != 0)
85 {
86 Log(hSession, L"Failed to open communication pipe for new CA process. Error code: %d", ret);
87 return ERROR_INSTALL_FAILURE;
88 }
89
90 wchar_t szModule[MAX_PATH] = {0};
91 GetModuleFileName(g_hModule, szModule, MAX_PATH);
92
93 const wchar_t* rundll32 = L"rundll32.exe";
94 wchar_t szRunDll32Path[MAX_PATH] = {0};
95 GetSystemDirectory(szRunDll32Path, MAX_PATH);
96 wcscat_s(szRunDll32Path, MAX_PATH, L"\\");
97 wcscat_s(szRunDll32Path, MAX_PATH, rundll32);
98
99 const wchar_t* entry = L"zzzzInvokeManagedCustomActionOutOfProc";
100 wchar_t szCommandLine[1024] = {0};
101 swprintf_s(szCommandLine, 1024, L"%s \"%s\",%s %s %d %s",
102 rundll32, szModule, entry, szSessionName, hSession, szEntryPoint);
103
104 STARTUPINFO si;
105 SecureZeroMemory(&si, sizeof(STARTUPINFO));
106 si.cb = sizeof(STARTUPINFO);
107
108 PROCESS_INFORMATION pi;
109 SecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
110
111 if (!CreateProcess(szRunDll32Path, szCommandLine, NULL, NULL, FALSE,
112 0, NULL, NULL, &si, &pi))
113 {
114 DWORD err = GetLastError();
115 Log(hSession, L"Failed to create new CA process via RUNDLL32. Error code: %d", err);
116 return ERROR_INSTALL_FAILURE;
117 }
118
119 DWORD dwWait = WaitForSingleObject(pi.hProcess, INFINITE);
120 if (dwWait != WAIT_OBJECT_0)
121 {
122 DWORD err = GetLastError();
123 Log(hSession, L"Failed to wait for CA process. Error code: %d", err);
124 return ERROR_INSTALL_FAILURE;
125 }
126
127 DWORD dwExitCode;
128 BOOL bRet = GetExitCodeProcess(pi.hProcess, &dwExitCode);
129 if (!bRet)
130 {
131 DWORD err = GetLastError();
132 Log(hSession, L"Failed to get exit code of CA process. Error code: %d", err);
133 return ERROR_INSTALL_FAILURE;
134 }
135 else if (dwExitCode != 0)
136 {
137 Log(hSession, L"RUNDLL32 returned error code: %d", dwExitCode);
138 return ERROR_INSTALL_FAILURE;
139 }
140
141 CloseHandle(pi.hThread);
142 CloseHandle(pi.hProcess);
143
144 remote.WaitExitCode();
145 return remote.ExitCode;
146}
147
148/// <summary>
149/// Entrypoint for the managed CA proxy (RemotableNativeMethods) to
150/// call MSI APIs remotely.
151/// </summary>
152void __stdcall MsiRemoteInvoke(RemoteMsiSession::RequestId id, RemoteMsiSession::RequestData* pRequest, RemoteMsiSession::RequestData** ppResponse)
153{
154 if (g_fRunningOutOfProc)
155 {
156 g_pRemote->SendRequest(id, pRequest, ppResponse);
157 }
158 else
159 {
160 *ppResponse = NULL;
161 }
162}
163
164/// <summary>
165/// Invokes a managed custom action from native code by
166/// extracting the package to a temporary working directory
167/// then hosting the CLR and locating and calling the entrypoint.
168/// </summary>
169/// <param name="hSession">Handle to the installation session.
170/// Passed to custom action entrypoints by the installer engine.</param>
171/// <param name="szWorkingDir">Directory containing the CA binaries
172/// and the CustomAction.config file defining the entrypoints.
173/// This may be NULL, in which case the current module must have
174/// a concatenated cabinet containing those files, which will be
175/// extracted to a temporary directory.</param>
176/// <param name="szEntryPoint">Name of the CA entrypoint to be invoked.
177/// This must be either an explicit &quot;AssemblyName!Namespace.Class.Method&quot;
178/// string, or a simple name that maps to a full entrypoint definition
179/// in CustomAction.config.</param>
180/// <returns>The value returned by the managed custom action method,
181/// or ERROR_INSTALL_FAILURE if the CA could not be invoked.</returns>
182int InvokeCustomAction(MSIHANDLE hSession,
183 const wchar_t* szWorkingDir, const wchar_t* szEntryPoint)
184{
185#ifdef MANAGED_CAs_OUT_OF_PROC
186 if (!g_fRunningOutOfProc && szWorkingDir == NULL)
187 {
188 return InvokeOutOfProcManagedCustomAction(hSession, szEntryPoint);
189 }
190#endif
191
192 wchar_t szTempDir[MAX_PATH];
193 bool fDeleteTemp = false;
194 if (szWorkingDir == NULL)
195 {
196 if (!ExtractToTempDirectory(hSession, g_hModule, szTempDir, MAX_PATH))
197 {
198 return ERROR_INSTALL_FAILURE;
199 }
200 szWorkingDir = szTempDir;
201 fDeleteTemp = true;
202 }
203
204 wchar_t szConfigFilePath[MAX_PATH + 20];
205 StringCchCopy(szConfigFilePath, MAX_PATH + 20, szWorkingDir);
206 StringCchCat(szConfigFilePath, MAX_PATH + 20, L"\\CustomAction.config");
207
208 const wchar_t* szConfigFile = szConfigFilePath;
209 if (!::PathFileExists(szConfigFilePath))
210 {
211 szConfigFile = NULL;
212 }
213
214 wchar_t szWIAssembly[MAX_PATH + 50];
215 StringCchCopy(szWIAssembly, MAX_PATH + 50, szWorkingDir);
216 StringCchCat(szWIAssembly, MAX_PATH + 50, L"\\WixToolset.Dtf.WindowsInstaller.dll");
217
218 int iResult = ERROR_INSTALL_FAILURE;
219 ICorRuntimeHost* pHost;
220 if (LoadCLR(hSession, NULL, szConfigFile, szWIAssembly, &pHost))
221 {
222 _AppDomain* pAppDomain;
223 if (CreateAppDomain(hSession, pHost, L"CustomAction", szWorkingDir,
224 szConfigFile, &pAppDomain))
225 {
226 if (!InvokeManagedCustomAction(hSession, pAppDomain, szEntryPoint, &iResult))
227 {
228 iResult = ERROR_INSTALL_FAILURE;
229 }
230 HRESULT hr = pHost->UnloadDomain(pAppDomain);
231 if (FAILED(hr))
232 {
233 Log(hSession, L"Failed to unload app domain. Error code 0x%X", hr);
234 }
235 pAppDomain->Release();
236 }
237
238 pHost->Stop();
239 pHost->Release();
240 }
241
242 if (fDeleteTemp)
243 {
244 DeleteDirectory(szTempDir);
245 }
246 return iResult;
247}
248
249/// <summary>
250/// Called by the system when the DLL is loaded.
251/// Saves the module handle for later use.
252/// </summary>
253BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, void* pReserved)
254{
255 UNREFERENCED_PARAMETER(pReserved);
256
257 switch (dwReason)
258 {
259 case DLL_PROCESS_ATTACH:
260 g_hModule = hModule;
261 break;
262 case DLL_THREAD_ATTACH:
263 case DLL_THREAD_DETACH:
264 case DLL_PROCESS_DETACH:
265 break;
266 }
267 return TRUE;
268}
269
270/// <summary>
271/// Loads and invokes the managed portion of the proxy.
272/// </summary>
273/// <param name="hSession">Handle to the installer session,
274/// used for logging errors and to be passed on to the custom action.</param>
275/// <param name="pAppDomain">AppDomain which has its application
276/// base set to the CA working directory.</param>
277/// <param name="szEntryPoint">Name of the CA entrypoint to be invoked.
278/// This must be either an explicit &quot;AssemblyName!Namespace.Class.Method&quot;
279/// string, or a simple name that maps to a full entrypoint definition
280/// in CustomAction.config.</param>
281/// <param name="piResult">Return value of the invoked custom
282/// action method.</param>
283/// <returns>True if the managed proxy was invoked successfully,
284/// false if there was some error. Note the custom action itself may
285/// return an error via piResult while this method still returns true
286/// since the invocation was successful.</returns>
287bool InvokeManagedCustomAction(MSIHANDLE hSession, _AppDomain* pAppDomain,
288 const wchar_t* szEntryPoint, int* piResult)
289{
290 VARIANT vResult;
291 ::VariantInit(&vResult);
292
293 const bool f64bit = (sizeof(void*) == sizeof(LONGLONG));
294 const wchar_t* szMsiAssemblyName = L"WixToolset.Dtf.WindowsInstaller";
295 const wchar_t* szMsiCAProxyClass = L"WixToolset.Dtf.WindowsInstaller.CustomActionProxy";
296 const wchar_t* szMsiCAInvokeMethod = (f64bit ? L"InvokeCustomAction64" : L"InvokeCustomAction32");
297
298 _MethodInfo* pCAInvokeMethod;
299 if (!GetMethod(hSession, pAppDomain, szMsiAssemblyName,
300 szMsiCAProxyClass, szMsiCAInvokeMethod, &pCAInvokeMethod))
301 {
302 return false;
303 }
304
305 HRESULT hr;
306 VARIANT vNull;
307 vNull.vt = VT_EMPTY;
308 SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT, 0, 3);
309 VARIANT vSessionHandle;
310 vSessionHandle.vt = VT_I4;
311 vSessionHandle.intVal = hSession;
312 LONG index = 0;
313 hr = SafeArrayPutElement(saArgs, &index, &vSessionHandle);
314 if (FAILED(hr)) goto LExit;
315 VARIANT vEntryPoint;
316 vEntryPoint.vt = VT_BSTR;
317 vEntryPoint.bstrVal = SysAllocString(szEntryPoint);
318 if (vEntryPoint.bstrVal == NULL)
319 {
320 hr = E_OUTOFMEMORY;
321 goto LExit;
322 }
323 index = 1;
324 hr = SafeArrayPutElement(saArgs, &index, &vEntryPoint);
325 if (FAILED(hr)) goto LExit;
326 VARIANT vRemotingFunctionPtr;
327#pragma warning(push)
328#pragma warning(disable:4127) // conditional expression is constant
329 if (f64bit)
330#pragma warning(pop)
331 {
332 vRemotingFunctionPtr.vt = VT_I8;
333 vRemotingFunctionPtr.llVal = (LONGLONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL);
334 }
335 else
336 {
337 vRemotingFunctionPtr.vt = VT_I4;
338#pragma warning(push)
339#pragma warning(disable:4302) // truncation
340#pragma warning(disable:4311) // pointer truncation
341 vRemotingFunctionPtr.lVal = (LONG) (g_fRunningOutOfProc ? MsiRemoteInvoke : NULL);
342#pragma warning(pop)
343 }
344 index = 2;
345 hr = SafeArrayPutElement(saArgs, &index, &vRemotingFunctionPtr);
346 if (FAILED(hr)) goto LExit;
347
348 hr = pCAInvokeMethod->Invoke_3(vNull, saArgs, &vResult);
349
350LExit:
351 SafeArrayDestroy(saArgs);
352 pCAInvokeMethod->Release();
353
354 if (FAILED(hr))
355 {
356 Log(hSession, L"Failed to invoke custom action method. Error code 0x%X", hr);
357 return false;
358 }
359
360 *piResult = vResult.intVal;
361 return true;
362}
363
diff --git a/src/dtf/SfxCA/SfxCA.rc b/src/dtf/SfxCA/SfxCA.rc
new file mode 100644
index 00000000..4d78194b
--- /dev/null
+++ b/src/dtf/SfxCA/SfxCA.rc
@@ -0,0 +1,10 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#define VER_DLL
4#define VER_LANG_NEUTRAL
5#define VER_ORIGINAL_FILENAME "SfxCA.dll"
6#define VER_INTERNAL_NAME "SfxCA"
7#define VER_FILE_DESCRIPTION "DTF Self-Extracting Custom Action"
8
9// Additional resources here
10
diff --git a/src/dtf/SfxCA/SfxCA.vcxproj b/src/dtf/SfxCA/SfxCA.vcxproj
new file mode 100644
index 00000000..96aa69e0
--- /dev/null
+++ b/src/dtf/SfxCA/SfxCA.vcxproj
@@ -0,0 +1,79 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <ItemGroup Label="ProjectConfigurations">
6 <ProjectConfiguration Include="Debug|ARM64">
7 <Configuration>Debug</Configuration>
8 <Platform>ARM64</Platform>
9 </ProjectConfiguration>
10 <ProjectConfiguration Include="Debug|Win32">
11 <Configuration>Debug</Configuration>
12 <Platform>Win32</Platform>
13 </ProjectConfiguration>
14 <ProjectConfiguration Include="Debug|x64">
15 <Configuration>Debug</Configuration>
16 <Platform>x64</Platform>
17 </ProjectConfiguration>
18 <ProjectConfiguration Include="Release|ARM64">
19 <Configuration>Release</Configuration>
20 <Platform>ARM64</Platform>
21 </ProjectConfiguration>
22 <ProjectConfiguration Include="Release|Win32">
23 <Configuration>Release</Configuration>
24 <Platform>Win32</Platform>
25 </ProjectConfiguration>
26 <ProjectConfiguration Include="Release|x64">
27 <Configuration>Release</Configuration>
28 <Platform>x64</Platform>
29 </ProjectConfiguration>
30 </ItemGroup>
31
32 <PropertyGroup Label="Globals">
33 <ProjectGuid>{55D5BA28-D427-4F53-80C2-FE9EF23C1553}</ProjectGuid>
34 <ConfigurationType>DynamicLibrary</ConfigurationType>
35 <CharacterSet>Unicode</CharacterSet>
36 <SignOutput>false</SignOutput>
37 <ProjectModuleDefinitionFile>EntryPoints.def</ProjectModuleDefinitionFile>
38 </PropertyGroup>
39
40 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
41 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
42
43 <PropertyGroup>
44 <ProjectAdditionalLinkLibraries>msi.lib;cabinet.lib;shlwapi.lib</ProjectAdditionalLinkLibraries>
45 </PropertyGroup>
46
47 <ItemGroup>
48 <ClCompile Include="ClrHost.cpp" />
49 <ClCompile Include="Extract.cpp" />
50 <ClCompile Include="precomp.cpp">
51 <PrecompiledHeader>Create</PrecompiledHeader>
52 </ClCompile>
53 <ClCompile Include="RemoteMsi.cpp" />
54 <ClCompile Include="SfxCA.cpp" />
55 <ClCompile Include="SfxUtil.cpp" />
56 <ClCompile Include="EmbeddedUI.cpp" />
57 </ItemGroup>
58 <ItemGroup>
59 <ClInclude Include="precomp.h" />
60 <ClInclude Include="EntryPoints.h" />
61 <ClInclude Include="RemoteMsiSession.h" />
62 <ClInclude Include="SfxUtil.h" />
63 </ItemGroup>
64
65 <ItemGroup>
66 <None Include="EntryPoints.def" />
67 </ItemGroup>
68
69 <ItemGroup>
70 <ResourceCompile Include="SfxCA.rc" />
71 </ItemGroup>
72
73 <ItemGroup>
74 <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
75 <PackageReference Include="GitInfo" PrivateAssets="All" />
76 </ItemGroup>
77
78 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
79</Project>
diff --git a/src/dtf/SfxCA/SfxCA.vcxproj.filters b/src/dtf/SfxCA/SfxCA.vcxproj.filters
new file mode 100644
index 00000000..a5ebf693
--- /dev/null
+++ b/src/dtf/SfxCA/SfxCA.vcxproj.filters
@@ -0,0 +1,62 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <ClCompile Include="ClrHost.cpp">
5 <Filter>Source Files</Filter>
6 </ClCompile>
7 <ClCompile Include="EmbeddedUI.cpp">
8 <Filter>Source Files</Filter>
9 </ClCompile>
10 <ClCompile Include="Extract.cpp">
11 <Filter>Source Files</Filter>
12 </ClCompile>
13 <ClCompile Include="RemoteMsi.cpp">
14 <Filter>Source Files</Filter>
15 </ClCompile>
16 <ClCompile Include="SfxCA.cpp">
17 <Filter>Source Files</Filter>
18 </ClCompile>
19 <ClCompile Include="SfxUtil.cpp">
20 <Filter>Source Files</Filter>
21 </ClCompile>
22 <ClCompile Include="precomp.cpp">
23 <Filter>Source Files</Filter>
24 </ClCompile>
25 </ItemGroup>
26 <ItemGroup>
27 <ClInclude Include="EntryPoints.h">
28 <Filter>Header Files</Filter>
29 </ClInclude>
30 <ClInclude Include="precomp.h">
31 <Filter>Header Files</Filter>
32 </ClInclude>
33 <ClInclude Include="RemoteMsiSession.h">
34 <Filter>Header Files</Filter>
35 </ClInclude>
36 <ClInclude Include="SfxUtil.h">
37 <Filter>Header Files</Filter>
38 </ClInclude>
39 </ItemGroup>
40 <ItemGroup>
41 <Filter Include="Resource Files">
42 <UniqueIdentifier>{81c92f68-18c2-4cd4-a588-5c3616860dd9}</UniqueIdentifier>
43 </Filter>
44 <Filter Include="Header Files">
45 <UniqueIdentifier>{6cdc30ee-e14d-4679-b92e-3e080535e53b}</UniqueIdentifier>
46 </Filter>
47 <Filter Include="Source Files">
48 <UniqueIdentifier>{1666a44e-4f2e-4f13-980e-d0c3dfa7cb6d}</UniqueIdentifier>
49 </Filter>
50 </ItemGroup>
51 <ItemGroup>
52 <ResourceCompile Include="SfxCA.rc">
53 <Filter>Resource Files</Filter>
54 </ResourceCompile>
55 </ItemGroup>
56 <ItemGroup>
57 <None Include="EntryPoints.def">
58 <Filter>Resource Files</Filter>
59 </None>
60 <None Include="packages.config" />
61 </ItemGroup>
62</Project> \ No newline at end of file
diff --git a/src/dtf/SfxCA/SfxUtil.cpp b/src/dtf/SfxCA/SfxUtil.cpp
new file mode 100644
index 00000000..1bf2c5b2
--- /dev/null
+++ b/src/dtf/SfxCA/SfxUtil.cpp
@@ -0,0 +1,209 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4#include "SfxUtil.h"
5
6/// <summary>
7/// Writes a formatted message to the MSI log.
8/// Does out-of-proc MSI calls if necessary.
9/// </summary>
10void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...)
11{
12 const int LOG_BUFSIZE = 4096;
13 wchar_t szBuf[LOG_BUFSIZE];
14 va_list args;
15 va_start(args, szMessage);
16 StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args);
17
18 if (!g_fRunningOutOfProc || NULL == g_pRemote)
19 {
20 MSIHANDLE hRec = MsiCreateRecord(1);
21 MsiRecordSetString(hRec, 0, L"SFXCA: [1]");
22 MsiRecordSetString(hRec, 1, szBuf);
23 MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec);
24 MsiCloseHandle(hRec);
25 }
26 else
27 {
28 // Logging is the only remote-MSI operation done from unmanaged code.
29 // It's not very convenient here because part of the infrastructure
30 // for remote MSI APIs is on the managed side.
31
32 RemoteMsiSession::RequestData req;
33 RemoteMsiSession::RequestData* pResp = NULL;
34 SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData));
35
36 req.fields[0].vt = VT_UI4;
37 req.fields[0].uiValue = 1;
38 g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp);
39 MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue;
40
41 req.fields[0].vt = VT_I4;
42 req.fields[0].iValue = (int) hRec;
43 req.fields[1].vt = VT_UI4;
44 req.fields[1].uiValue = 0;
45 req.fields[2].vt = VT_LPWSTR;
46 req.fields[2].szValue = L"SFXCA: [1]";
47 g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
48
49 req.fields[0].vt = VT_I4;
50 req.fields[0].iValue = (int) hRec;
51 req.fields[1].vt = VT_UI4;
52 req.fields[1].uiValue = 1;
53 req.fields[2].vt = VT_LPWSTR;
54 req.fields[2].szValue = szBuf;
55 g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
56
57 req.fields[0].vt = VT_I4;
58 req.fields[0].iValue = (int) hSession;
59 req.fields[1].vt = VT_I4;
60 req.fields[1].iValue = (int) INSTALLMESSAGE_INFO;
61 req.fields[2].vt = VT_I4;
62 req.fields[2].iValue = (int) hRec;
63 g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp);
64
65 req.fields[0].vt = VT_I4;
66 req.fields[0].iValue = (int) hRec;
67 req.fields[1].vt = VT_EMPTY;
68 req.fields[2].vt = VT_EMPTY;
69 g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp);
70 }
71}
72
73/// <summary>
74/// Deletes a directory, including all files and subdirectories.
75/// </summary>
76/// <param name="szDir">Path to the directory to delete,
77/// not including a trailing backslash.</param>
78/// <returns>True if the directory was successfully deleted, or false
79/// if the deletion failed (most likely because some files were locked).
80/// </returns>
81bool DeleteDirectory(const wchar_t* szDir)
82{
83 size_t cchDir = wcslen(szDir);
84 size_t cchPathBuf = cchDir + 3 + MAX_PATH;
85 wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t));
86 if (szPath == NULL) return false;
87 StringCchCopy(szPath, cchPathBuf, szDir);
88 StringCchCat(szPath, cchPathBuf, L"\\*");
89 WIN32_FIND_DATA fd;
90 HANDLE hSearch = FindFirstFile(szPath, &fd);
91 while (hSearch != INVALID_HANDLE_VALUE)
92 {
93 StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName);
94 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
95 {
96 if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0)
97 {
98 DeleteDirectory(szPath);
99 }
100 }
101 else
102 {
103 DeleteFile(szPath);
104 }
105 if (!FindNextFile(hSearch, &fd))
106 {
107 FindClose(hSearch);
108 hSearch = INVALID_HANDLE_VALUE;
109 }
110 }
111 return RemoveDirectory(szDir) != 0;
112}
113
114bool DirectoryExists(const wchar_t* szDir)
115{
116 if (szDir != NULL)
117 {
118 DWORD dwAttrs = GetFileAttributes(szDir);
119 if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
120 {
121 return true;
122 }
123 }
124 return false;
125}
126
127/// <summary>
128/// Extracts a cabinet that is concatenated to a module
129/// to a new temporary directory.
130/// </summary>
131/// <param name="hSession">Handle to the installer session,
132/// used just for logging.</param>
133/// <param name="hModule">Module that has the concatenated cabinet.</param>
134/// <param name="szTempDir">Buffer for returning the path of the
135/// created temp directory.</param>
136/// <param name="cchTempDirBuf">Size in characters of the buffer.
137/// <returns>True if the files were extracted, or false if the
138/// buffer was too small or the directory could not be created
139/// or the extraction failed for some other reason.</returns>
140__success(return != false)
141bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
142 __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf)
143{
144 wchar_t szModule[MAX_PATH];
145 DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1);
146 if (cchCopied == 0)
147 {
148 Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
149 return false;
150 }
151 else if (cchCopied == MAX_PATH - 1)
152 {
153 Log(hSession, L"Failed to get module path -- path is too long.");
154 return false;
155 }
156
157 if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1)
158 {
159 Log(hSession, L"Temp directory buffer is NULL or too small.");
160 return false;
161 }
162 StringCchCopy(szTempDir, cchTempDirBuf, szModule);
163 StringCchCat(szTempDir, cchTempDirBuf, L"-");
164
165 DWORD cchTempDir = (DWORD) wcslen(szTempDir);
166 for (int i = 0; DirectoryExists(szTempDir); i++)
167 {
168 swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
169 }
170
171 if (!CreateDirectory(szTempDir, NULL))
172 {
173 cchCopied = GetTempPath(cchTempDirBuf, szTempDir);
174 if (cchCopied == 0 || cchCopied >= cchTempDirBuf)
175 {
176 Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError());
177 return false;
178 }
179
180 wchar_t* szModuleName = wcsrchr(szModule, L'\\');
181 if (szModuleName == NULL) szModuleName = szModule;
182 else szModuleName = szModuleName + 1;
183 StringCchCat(szTempDir, cchTempDirBuf, szModuleName);
184 StringCchCat(szTempDir, cchTempDirBuf, L"-");
185
186 cchTempDir = (DWORD) wcslen(szTempDir);
187 for (int i = 0; DirectoryExists(szTempDir); i++)
188 {
189 swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
190 }
191
192 if (!CreateDirectory(szTempDir, NULL))
193 {
194 Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError());
195 return false;
196 }
197 }
198
199 Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir);
200 int err = ExtractCabinet(szModule, szTempDir);
201 if (err != 0)
202 {
203 Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err);
204 DeleteDirectory(szTempDir);
205 return false;
206 }
207 return true;
208}
209
diff --git a/src/dtf/SfxCA/SfxUtil.h b/src/dtf/SfxCA/SfxUtil.h
new file mode 100644
index 00000000..af12d8dd
--- /dev/null
+++ b/src/dtf/SfxCA/SfxUtil.h
@@ -0,0 +1,31 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "RemoteMsiSession.h"
4
5void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...);
6
7int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir);
8
9bool DeleteDirectory(const wchar_t* szDir);
10
11__success(return != false)
12bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
13 __out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf);
14
15bool LoadCLR(MSIHANDLE hSession, const wchar_t* szVersion, const wchar_t* szConfigFile,
16 const wchar_t* szPrimaryAssembly, ICorRuntimeHost** ppHost);
17
18bool CreateAppDomain(MSIHANDLE hSession, ICorRuntimeHost* pHost,
19 const wchar_t* szName, const wchar_t* szAppBase,
20 const wchar_t* szConfigFile, _AppDomain** ppAppDomain);
21
22bool GetMethod(MSIHANDLE hSession, _AppDomain* pAppDomain,
23 const wchar_t* szAssembly, const wchar_t* szClass,
24 const wchar_t* szMethod, _MethodInfo** ppCAMethod);
25
26extern HMODULE g_hModule;
27extern bool g_fRunningOutOfProc;
28
29extern RemoteMsiSession* g_pRemote;
30
31
diff --git a/src/dtf/SfxCA/precomp.cpp b/src/dtf/SfxCA/precomp.cpp
new file mode 100644
index 00000000..ce82c1d7
--- /dev/null
+++ b/src/dtf/SfxCA/precomp.cpp
@@ -0,0 +1,3 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h" \ No newline at end of file
diff --git a/src/dtf/SfxCA/precomp.h b/src/dtf/SfxCA/precomp.h
new file mode 100644
index 00000000..48d4f011
--- /dev/null
+++ b/src/dtf/SfxCA/precomp.h
@@ -0,0 +1,18 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <windows.h>
6#include <msiquery.h>
7#include <strsafe.h>
8#include <mscoree.h>
9#include <io.h>
10#include <fcntl.h>
11#include <share.h>
12#include <shlwapi.h>
13#include <sys/stat.h>
14#include <malloc.h>
15#include <fdi.h>
16#include <msiquery.h>
17#import <mscorlib.tlb> raw_interfaces_only rename("ReportEvent", "CorReportEvent")
18using namespace mscorlib;
diff --git a/src/dtf/SfxCA/sfxca_t.proj b/src/dtf/SfxCA/sfxca_t.proj
new file mode 100644
index 00000000..1e823be1
--- /dev/null
+++ b/src/dtf/SfxCA/sfxca_t.proj
@@ -0,0 +1,7 @@
1<Project Sdk="Microsoft.Build.Traversal">
2 <ItemGroup>
3 <ProjectReference Include="SfxCA.vcxproj" Properties="Platform=x86" />
4 <ProjectReference Include="SfxCA.vcxproj" Properties="Platform=x64" />
5 <ProjectReference Include="SfxCA.vcxproj" Properties="Platform=ARM64" />
6 </ItemGroup>
7</Project>
diff --git a/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.csproj b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.csproj
new file mode 100644
index 00000000..379aa194
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.csproj
@@ -0,0 +1,17 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFramework>net472</TargetFramework>
7 <IncludeBuildOutput>false</IncludeBuildOutput>
8 <Title>The WiX Toolset Managed CustomAction Framework.</Title>
9 <Description>The WiX Toolset lets developers create managed custom actions for the Windows Installer. This package contains the tools necessary to convert your project into a managed custom action.</Description>
10
11 <NuspecBasePath>$(OutputPath)</NuspecBasePath>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <ProjectReference Include="..\WixToolset.Dtf.MakeSfxCA\WixToolset.Dtf.MakeSfxCA.csproj" />
16 </ItemGroup>
17</Project>
diff --git a/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.nuspec b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.nuspec
new file mode 100644
index 00000000..4550e629
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.nuspec
@@ -0,0 +1,32 @@
1<?xml version="1.0" encoding="utf-8"?>
2<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
3 <metadata>
4 <id>$id$</id>
5 <version>$version$</version>
6 <title>$title$</title>
7 <description>$description$</description>
8 <authors>$authors$</authors>
9 <icon>wix-white-bg.png</icon>
10 <license type="expression">MS-RL</license>
11 <requireLicenseAcceptance>false</requireLicenseAcceptance>
12 <copyright>$copyright$</copyright>
13 <projectUrl>$projectUrl$</projectUrl>
14 <repository type="$repositorytype$" url="$repositoryurl$" commit="$repositorycommit$" />
15 <dependencies>
16 <dependency id="WixToolset.Dtf.WindowsInstaller" version="$version$" />
17 </dependencies>
18 </metadata>
19
20 <files>
21 <file src="$projectFolder$\$id$.targets" target="build" />
22 <file src="$projectFolder$\..\..\internal\images\wix-white-bg.png" />
23 <file src="net472\WixToolset.Dtf.MakeSfxCA.exe" target="tools" />
24 <file src="net472\WixToolset.Dtf.MakeSfxCA.exe.config" target="tools" />
25 <file src="net472\WixToolset.Dtf.Compression.dll" target="tools" />
26 <file src="net472\WixToolset.Dtf.Compression.Cab.dll" target="tools" />
27 <file src="net472\WixToolset.Dtf.Resources.dll" target="tools" />
28 <file src="x64\SfxCA.dll" target="tools\x64" />
29 <file src="x86\SfxCA.dll" target="tools\x86" />
30 <file src="ARM64\SfxCA.dll" target="tools\arm64" />
31 </files>
32</package>
diff --git a/src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.targets
index 4578c2d8..127bb29d 100644
--- a/src/dtf/WixToolset.Dtf.MSBuild/tools/wix.ca.targets
+++ b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.targets
@@ -1,8 +1,7 @@
1<?xml version="1.0" encoding="utf-8" ?> 1<?xml version="1.0" encoding="utf-8" ?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> 2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3 3
4 4<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 5
7 <Import Project="$(CustomBeforeWixCATargets)" Condition=" '$(CustomBeforeWixCATargets)' != '' and Exists('$(CustomBeforeWixCATargets)')" /> 6 <Import Project="$(CustomBeforeWixCATargets)" Condition=" '$(CustomBeforeWixCATargets)' != '' and Exists('$(CustomBeforeWixCATargets)')" />
8 7
@@ -11,13 +10,12 @@
11 10
12 <TargetCAFileName Condition=" '$(TargetCAFileName)' == '' ">$(TargetName).CA$(TargetExt)</TargetCAFileName> 11 <TargetCAFileName Condition=" '$(TargetCAFileName)' == '' ">$(TargetName).CA$(TargetExt)</TargetCAFileName>
13 12
14 <WixSdkPath Condition=" '$(WixSdkPath)' == '' ">$(MSBuildThisFileDirectory)</WixSdkPath> 13 <MakeSfxCAPath Condition=" '$(MakeSfxCAPath)' == '' ">$(MSBuildThisFileDirectory)..\tools\</MakeSfxCAPath>
15 <WixSdkX86Path Condition=" '$(WixSdkX86Path)' == '' ">$(WixSdkPath)x86\</WixSdkX86Path>
16 <WixSdkX64Path Condition=" '$(WixSdkX64Path)' == '' ">$(WixSdkPath)x64\</WixSdkX64Path>
17 14
18 <MakeSfxCA Condition=" '$(MakeSfxCA)' == '' ">$(WixSdkPath)MakeSfxCA.exe</MakeSfxCA> 15 <MakeSfxCA Condition=" '$(MakeSfxCA)' == '' ">$(MakeSfxCAPath)WixToolset.Dtf.MakeSfxCA.exe</MakeSfxCA>
19 <SfxCADll Condition=" '$(SfxCADll)' == '' and '$(Platform)' == 'x64' ">$(WixSdkX64Path)SfxCA.dll</SfxCADll> 16 <SfxCADll Condition=" '$(SfxCADll)' == '' and '$(Platform)' == 'ARM64' ">$(MakeSfxCAPath)arm64\SfxCA.dll</SfxCADll>
20 <SfxCADll Condition=" '$(SfxCADll)' == '' ">$(WixSdkX86Path)SfxCA.dll</SfxCADll> 17 <SfxCADll Condition=" '$(SfxCADll)' == '' and '$(Platform)' == 'x64' ">$(MakeSfxCAPath)x64\SfxCA.dll</SfxCADll>
18 <SfxCADll Condition=" '$(SfxCADll)' == '' ">$(MakeSfxCAPath)x86\SfxCA.dll</SfxCADll>
21 </PropertyGroup> 19 </PropertyGroup>
22 20
23 <!-- 21 <!--
@@ -74,7 +72,7 @@
74 <!-- Run the MakeSfxCA.exe CA packaging tool. --> 72 <!-- Run the MakeSfxCA.exe CA packaging tool. -->
75 <Exec Command='"$(MakeSfxCA)" "@(IntermediateCAPackage)" "$(SfxCADll)" "@(IntermediateCAAssembly)" "$(CustomActionContents)"' 73 <Exec Command='"$(MakeSfxCA)" "@(IntermediateCAPackage)" "$(SfxCADll)" "@(IntermediateCAAssembly)" "$(CustomActionContents)"'
76 WorkingDirectory="$(ProjectDir)" /> 74 WorkingDirectory="$(ProjectDir)" />
77 75
78 <!-- Add modules to be copied to output dir. --> 76 <!-- Add modules to be copied to output dir. -->
79 <ItemGroup> 77 <ItemGroup>
80 <AddModules Include="@(IntermediateCAPackage)" /> 78 <AddModules Include="@(IntermediateCAPackage)" />
diff --git a/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.v3.ncrunchproject b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.v3.ncrunchproject
new file mode 100644
index 00000000..cf22dfa9
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.CustomAction/WixToolset.Dtf.CustomAction.v3.ncrunchproject
@@ -0,0 +1,5 @@
1<ProjectConfiguration>
2 <Settings>
3 <HiddenComponentWarnings />
4 </Settings>
5</ProjectConfiguration> \ No newline at end of file
diff --git a/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj b/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj
deleted file mode 100644
index 1c81b861..00000000
--- a/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.csproj
+++ /dev/null
@@ -1,40 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5
6 <PropertyGroup>
7 <TargetFramework>netcoreapp3.1</TargetFramework>
8 <IncludeBuildOutput>false</IncludeBuildOutput>
9 <Description>WiX Toolset Dtf MSBuild integration</Description>
10 <NuspecFile>$(MSBuildThisFileName).nuspec</NuspecFile>
11 <NuspecBasePath>$(OutputPath)publish\WixToolset.Dtf.MSBuild\</NuspecBasePath>
12 <NuspecProperties>Id=$(MSBuildThisFileName);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description)</NuspecProperties>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <None Remove="build\WixToolset.Dtf.MSBuild.props" />
17 <None Remove="tools\wix.ca.targets" />
18 </ItemGroup>
19
20 <ItemGroup>
21 <Content Include="build\WixToolset.Dtf.MSBuild.props">
22 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
23 </Content>
24 <Content Include="tools\wix.ca.targets">
25 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
26 </Content>
27 </ItemGroup>
28
29 <PropertyGroup>
30 <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuspecVersion</GenerateNuspecDependsOn>
31 </PropertyGroup>
32
33 <Target Name="SetNuspecVersion">
34 <Error Text="Cannot pack $(MSBuildThisFileName) until all projects are published to: '$(NuspecBasePath)'. Run appveyor.cmd to publish projects properly." Condition=" !Exists('$(NuspecBasePath)') " />
35
36 <PropertyGroup>
37 <NuspecProperties>$(NuspecProperties);Version=$(Version);ProjectFolder=$(MSBuildThisFileDirectory)</NuspecProperties>
38 </PropertyGroup>
39 </Target>
40</Project>
diff --git a/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec b/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec
deleted file mode 100644
index 7f819cdb..00000000
--- a/src/dtf/WixToolset.Dtf.MSBuild/WixToolset.Dtf.MSBuild.nuspec
+++ /dev/null
@@ -1,18 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
3 <metadata>
4 <id>$id$</id>
5 <version>$version$</version>
6 <authors>$authors$</authors>
7 <owners>$authors$</owners>
8 <license type="expression">MS-RL</license>
9 <requireLicenseAcceptance>false</requireLicenseAcceptance>
10 <description>$description$</description>
11 <copyright>$copyright$</copyright>
12 </metadata>
13
14 <files>
15 <file src="build\**\*" target="build" />
16 <file src="tools\**\*" target="tools" />
17 </files>
18</package>
diff --git a/src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props b/src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props
deleted file mode 100644
index 06a98d6e..00000000
--- a/src/dtf/WixToolset.Dtf.MSBuild/build/WixToolset.Dtf.MSBuild.props
+++ /dev/null
@@ -1,8 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
5 <PropertyGroup>
6 <WixCATargetsPath Condition=" '$(WixCATargetsPath)' == '' ">$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\tools\wix.ca.targets'))</WixCATargetsPath>
7 </PropertyGroup>
8</Project>
diff --git a/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs b/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs
new file mode 100644
index 00000000..d701da20
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.cs
@@ -0,0 +1,710 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.MakeSfxCA
4{
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Security;
9 using System.Text;
10 using System.Reflection;
11 using WixToolset.Dtf.Compression;
12 using WixToolset.Dtf.Compression.Cab;
13 using WixToolset.Dtf.Resources;
14 using ResourceCollection = WixToolset.Dtf.Resources.ResourceCollection;
15
16 /// <summary>
17 /// Command-line tool for building self-extracting custom action packages.
18 /// Appends cabbed CA binaries to SfxCA.dll and fixes up the result's
19 /// entry-points and file version to look like the CA module.
20 /// </summary>
21 public static class MakeSfxCA
22 {
23 private const string REQUIRED_WI_ASSEMBLY = "WixToolset.Dtf.WindowsInstaller.dll";
24
25 private static TextWriter log;
26
27 /// <summary>
28 /// Prints usage text for the tool.
29 /// </summary>
30 /// <param name="w">Console text writer.</param>
31 public static void Usage(TextWriter w)
32 {
33 w.WriteLine("WiX Toolset custom action packager version {0}", Assembly.GetExecutingAssembly().GetName().Version);
34 w.WriteLine("Copyright (C) .NET Foundation and contributors. All rights reserved.");
35 w.WriteLine();
36 w.WriteLine("Usage: WixToolset.Dtf.MakeSfxCA <outputca.dll> SfxCA.dll <inputca.dll> [support files ...]");
37 w.WriteLine();
38 w.WriteLine("Makes a self-extracting managed MSI CA or UI DLL package.");
39 w.WriteLine("Support files must include " + MakeSfxCA.REQUIRED_WI_ASSEMBLY);
40 w.WriteLine("Support files optionally include CustomAction.config/EmbeddedUI.config");
41 }
42
43 /// <summary>
44 /// Runs the MakeSfxCA command-line tool.
45 /// </summary>
46 /// <param name="args">Command-line arguments.</param>
47 /// <returns>0 on success, nonzero on failure.</returns>
48 public static int Main(string[] args)
49 {
50 if (args.Length < 3)
51 {
52 Usage(Console.Out);
53 return 1;
54 }
55
56 var output = args[0];
57 var sfxDll = args[1];
58 var inputs = new string[args.Length - 2];
59 Array.Copy(args, 2, inputs, 0, inputs.Length);
60
61 try
62 {
63 Build(output, sfxDll, inputs, Console.Out);
64 return 0;
65 }
66 catch (ArgumentException ex)
67 {
68 Console.Error.WriteLine("Error: Invalid argument: " + ex.Message);
69 return 1;
70 }
71 catch (FileNotFoundException ex)
72 {
73 Console.Error.WriteLine("Error: Cannot find file: " + ex.Message);
74 return 1;
75 }
76 catch (Exception ex)
77 {
78 Console.Error.WriteLine("Error: Unexpected error: " + ex);
79 return 1;
80 }
81 }
82
83 /// <summary>
84 /// Packages up all the inputs to the output location.
85 /// </summary>
86 /// <exception cref="Exception">Various exceptions are thrown
87 /// if things go wrong.</exception>
88 public static void Build(string output, string sfxDll, IList<string> inputs, TextWriter log)
89 {
90 MakeSfxCA.log = log;
91
92 if (String.IsNullOrEmpty(output))
93 {
94 throw new ArgumentNullException("output");
95 }
96
97 if (String.IsNullOrEmpty(sfxDll))
98 {
99 throw new ArgumentNullException("sfxDll");
100 }
101
102 if (inputs == null || inputs.Count == 0)
103 {
104 throw new ArgumentNullException("inputs");
105 }
106
107 if (!File.Exists(sfxDll))
108 {
109 throw new FileNotFoundException(sfxDll);
110 }
111
112 var customActionAssembly = inputs[0];
113 if (!File.Exists(customActionAssembly))
114 {
115 throw new FileNotFoundException(customActionAssembly);
116 }
117
118 inputs = MakeSfxCA.SplitList(inputs);
119
120 var inputsMap = MakeSfxCA.GetPackFileMap(inputs);
121
122 var foundWIAssembly = false;
123 foreach (var input in inputsMap.Keys)
124 {
125 if (String.Compare(input, MakeSfxCA.REQUIRED_WI_ASSEMBLY,
126 StringComparison.OrdinalIgnoreCase) == 0)
127 {
128 foundWIAssembly = true;
129 }
130 }
131
132 if (!foundWIAssembly)
133 {
134 throw new ArgumentException(MakeSfxCA.REQUIRED_WI_ASSEMBLY +
135 " must be included in the list of support files. " +
136 "If using the MSBuild targets, make sure the assembly reference " +
137 "has the Private (Copy Local) flag set.");
138 }
139
140 MakeSfxCA.ResolveDependentAssemblies(inputsMap, Path.GetDirectoryName(customActionAssembly));
141
142 var entryPoints = MakeSfxCA.FindEntryPoints(customActionAssembly);
143 var uiClass = MakeSfxCA.FindEmbeddedUIClass(customActionAssembly);
144
145 if (entryPoints.Count == 0 && uiClass == null)
146 {
147 throw new ArgumentException(
148 "No CA or UI entry points found in module: " + customActionAssembly);
149 }
150 else if (entryPoints.Count > 0 && uiClass != null)
151 {
152 throw new NotSupportedException(
153 "CA and UI entry points cannot be in the same assembly: " + customActionAssembly);
154 }
155
156 var dir = Path.GetDirectoryName(output);
157 if (dir.Length > 0 && !Directory.Exists(dir))
158 {
159 Directory.CreateDirectory(dir);
160 }
161
162 using (Stream outputStream = File.Create(output))
163 {
164 MakeSfxCA.WriteEntryModule(sfxDll, outputStream, entryPoints, uiClass);
165 }
166
167 MakeSfxCA.CopyVersionResource(customActionAssembly, output);
168
169 MakeSfxCA.PackInputFiles(output, inputsMap);
170
171 log.WriteLine("MakeSfxCA finished: " + new FileInfo(output).FullName);
172 }
173
174 /// <summary>
175 /// Splits any list items delimited by semicolons into separate items.
176 /// </summary>
177 /// <param name="list">Read-only input list.</param>
178 /// <returns>New list with resulting split items.</returns>
179 private static IList<string> SplitList(IList<string> list)
180 {
181 var newList = new List<string>(list.Count);
182
183 foreach (var item in list)
184 {
185 if (!String.IsNullOrEmpty(item))
186 {
187 foreach (var splitItem in item.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
188 {
189 newList.Add(splitItem);
190 }
191 }
192 }
193
194 return newList;
195 }
196
197 /// <summary>
198 /// Sets up a reflection-only assembly-resolve-handler to handle loading dependent assemblies during reflection.
199 /// </summary>
200 /// <param name="inputFiles">List of input files which include non-GAC dependent assemblies.</param>
201 /// <param name="inputDir">Directory to auto-locate additional dependent assemblies.</param>
202 /// <remarks>
203 /// Also searches the assembly's directory for unspecified dependent assemblies, and adds them
204 /// to the list of input files if found.
205 /// </remarks>
206 private static void ResolveDependentAssemblies(IDictionary<string, string> inputFiles, string inputDir)
207 {
208 AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += delegate(object sender, ResolveEventArgs args)
209 {
210 AssemblyName resolveName = new AssemblyName(args.Name);
211 Assembly assembly = null;
212
213 // First, try to find the assembly in the list of input files.
214 foreach (var inputFile in inputFiles.Values)
215 {
216 var inputName = Path.GetFileNameWithoutExtension(inputFile);
217 var inputExtension = Path.GetExtension(inputFile);
218 if (String.Equals(inputName, resolveName.Name, StringComparison.OrdinalIgnoreCase) &&
219 (String.Equals(inputExtension, ".dll", StringComparison.OrdinalIgnoreCase) ||
220 String.Equals(inputExtension, ".exe", StringComparison.OrdinalIgnoreCase)))
221 {
222 assembly = MakeSfxCA.TryLoadDependentAssembly(inputFile);
223
224 if (assembly != null)
225 {
226 break;
227 }
228 }
229 }
230
231 // Second, try to find the assembly in the input directory.
232 if (assembly == null && inputDir != null)
233 {
234 string assemblyPath = null;
235 if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".dll"))
236 {
237 assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".dll";
238 }
239 else if (File.Exists(Path.Combine(inputDir, resolveName.Name) + ".exe"))
240 {
241 assemblyPath = Path.Combine(inputDir, resolveName.Name) + ".exe";
242 }
243
244 if (assemblyPath != null)
245 {
246 assembly = MakeSfxCA.TryLoadDependentAssembly(assemblyPath);
247
248 if (assembly != null)
249 {
250 // Add this detected dependency to the list of files to be packed.
251 inputFiles.Add(Path.GetFileName(assemblyPath), assemblyPath);
252 }
253 }
254 }
255
256 // Third, try to load the assembly from the GAC.
257 if (assembly == null)
258 {
259 try
260 {
261 assembly = Assembly.ReflectionOnlyLoad(args.Name);
262 }
263 catch (FileNotFoundException)
264 {
265 }
266 }
267
268 if (assembly != null)
269 {
270 if (String.Equals(assembly.GetName().ToString(), resolveName.ToString()))
271 {
272 log.WriteLine(" Loaded dependent assembly: " + assembly.Location);
273 return assembly;
274 }
275
276 log.WriteLine(" Warning: Loaded mismatched dependent assembly: " + assembly.Location);
277 log.WriteLine(" Loaded assembly : " + assembly.GetName());
278 log.WriteLine(" Reference assembly: " + resolveName);
279 }
280 else
281 {
282 log.WriteLine(" Error: Dependent assembly not supplied: " + resolveName);
283 }
284
285 return null;
286 };
287 }
288
289 /// <summary>
290 /// Attempts a reflection-only load of a dependent assembly, logging the error if the load fails.
291 /// </summary>
292 /// <param name="assemblyPath">Path of the assembly file to laod.</param>
293 /// <returns>Loaded assembly, or null if the load failed.</returns>
294 private static Assembly TryLoadDependentAssembly(string assemblyPath)
295 {
296 Assembly assembly = null;
297 try
298 {
299 assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
300 }
301 catch (IOException ex)
302 {
303 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
304 }
305 catch (BadImageFormatException ex)
306 {
307 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
308 }
309 catch (SecurityException ex)
310 {
311 log.WriteLine(" Error: Failed to load dependent assembly: {0}. {1}", assemblyPath, ex.Message);
312 }
313
314 return assembly;
315 }
316
317 /// <summary>
318 /// Searches the types in the input assembly for a type that implements IEmbeddedUI.
319 /// </summary>
320 /// <param name="module"></param>
321 /// <returns></returns>
322 private static string FindEmbeddedUIClass(string module)
323 {
324 log.WriteLine("Searching for an embedded UI class in {0}", Path.GetFileName(module));
325
326 string uiClass = null;
327
328 var assembly = Assembly.ReflectionOnlyLoadFrom(module);
329
330 foreach (var type in assembly.GetExportedTypes())
331 {
332 if (!type.IsAbstract)
333 {
334 foreach (var interfaceType in type.GetInterfaces())
335 {
336 if (interfaceType.FullName == "WixToolset.Dtf.WindowsInstaller.IEmbeddedUI")
337 {
338 if (uiClass == null)
339 {
340 uiClass = assembly.GetName().Name + "!" + type.FullName;
341 }
342 else
343 {
344 throw new ArgumentException("Multiple IEmbeddedUI implementations found.");
345 }
346 }
347 }
348 }
349 }
350
351 return uiClass;
352 }
353
354 /// <summary>
355 /// Reflects on an input CA module to locate custom action entry-points.
356 /// </summary>
357 /// <param name="module">Assembly module with CA entry-points.</param>
358 /// <returns>Mapping from entry-point names to assembly!class.method paths.</returns>
359 private static IDictionary<string, string> FindEntryPoints(string module)
360 {
361 log.WriteLine("Searching for custom action entry points " +
362 "in {0}", Path.GetFileName(module));
363
364 var entryPoints = new Dictionary<string, string>();
365
366 var assembly = Assembly.ReflectionOnlyLoadFrom(module);
367
368 foreach (var type in assembly.GetExportedTypes())
369 {
370 foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
371 {
372 var entryPointName = MakeSfxCA.GetEntryPoint(method);
373 if (entryPointName != null)
374 {
375 var entryPointPath = String.Format(
376 "{0}!{1}.{2}",
377 Path.GetFileNameWithoutExtension(module),
378 type.FullName,
379 method.Name);
380 entryPoints.Add(entryPointName, entryPointPath);
381
382 log.WriteLine(" {0}={1}", entryPointName, entryPointPath);
383 }
384 }
385 }
386
387 return entryPoints;
388 }
389
390 /// <summary>
391 /// Check for a CustomActionAttribute and return the entrypoint name for the method if it is a CA method.
392 /// </summary>
393 /// <param name="method">A public static method.</param>
394 /// <returns>Entrypoint name for the method as specified by the custom action attribute or just the method name,
395 /// or null if the method is not a custom action method.</returns>
396 private static string GetEntryPoint(MethodInfo method)
397 {
398 IList<CustomAttributeData> attributes;
399 try
400 {
401 attributes = CustomAttributeData.GetCustomAttributes(method);
402 }
403 catch (FileLoadException)
404 {
405 // Already logged load failures in the assembly-resolve-handler.
406 return null;
407 }
408
409 foreach (CustomAttributeData attribute in attributes)
410 {
411 if (attribute.ToString().StartsWith(
412 "[WixToolset.Dtf.WindowsInstaller.CustomActionAttribute(",
413 StringComparison.Ordinal))
414 {
415 string entryPointName = null;
416 foreach (var argument in attribute.ConstructorArguments)
417 {
418 // The entry point name is the first positional argument, if specified.
419 entryPointName = (string) argument.Value;
420 break;
421 }
422
423 if (String.IsNullOrEmpty(entryPointName))
424 {
425 entryPointName = method.Name;
426 }
427
428 return entryPointName;
429 }
430 }
431
432 return null;
433 }
434
435 /// <summary>
436 /// Counts the number of template entrypoints in SfxCA.dll.
437 /// </summary>
438 /// <remarks>
439 /// Depending on the requirements, SfxCA.dll might be built with
440 /// more entrypoints than the default.
441 /// </remarks>
442 private static int GetEntryPointSlotCount(byte[] fileBytes, string entryPointFormat)
443 {
444 for (var count = 0; ; count++)
445 {
446 var templateName = String.Format(entryPointFormat, count);
447 var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
448
449 var nameOffset = FindBytes(fileBytes, templateAsciiBytes);
450 if (nameOffset < 0)
451 {
452 return count;
453 }
454 }
455 }
456
457 /// <summary>
458 /// Writes a modified version of SfxCA.dll to the output stream,
459 /// with the template entry-points mapped to the CA entry-points.
460 /// </summary>
461 /// <remarks>
462 /// To avoid having to recompile SfxCA.dll for every different set of CAs,
463 /// this method looks for a preset number of template entry-points in the
464 /// binary file and overwrites their entrypoint name and string data with
465 /// CA-specific values.
466 /// </remarks>
467 private static void WriteEntryModule(
468 string sfxDll, Stream outputStream, IDictionary<string, string> entryPoints, string uiClass)
469 {
470 log.WriteLine("Modifying SfxCA.dll stub");
471
472 byte[] fileBytes;
473 using (var readStream = File.OpenRead(sfxDll))
474 {
475 fileBytes = new byte[(int) readStream.Length];
476 readStream.Read(fileBytes, 0, fileBytes.Length);
477 }
478
479 const string ENTRYPOINT_FORMAT = "CustomActionEntryPoint{0:d03}";
480 const int MAX_ENTRYPOINT_NAME = 72;
481 const int MAX_ENTRYPOINT_PATH = 160;
482 //var emptyBytes = new byte[0];
483
484 var slotCount = MakeSfxCA.GetEntryPointSlotCount(fileBytes, ENTRYPOINT_FORMAT);
485
486 if (slotCount == 0)
487 {
488 throw new ArgumentException("Invalid SfxCA.dll file.");
489 }
490
491 if (entryPoints.Count > slotCount)
492 {
493 throw new ArgumentException(String.Format(
494 "The custom action assembly has {0} entrypoints, which is more than the maximum ({1}). " +
495 "Refactor the custom actions or add more entrypoint slots in SfxCA\\EntryPoints.h.",
496 entryPoints.Count, slotCount));
497 }
498
499 var slotSort = new string[slotCount];
500 for (var i = 0; i < slotCount - entryPoints.Count; i++)
501 {
502 slotSort[i] = String.Empty;
503 }
504
505 entryPoints.Keys.CopyTo(slotSort, slotCount - entryPoints.Count);
506 Array.Sort<string>(slotSort, slotCount - entryPoints.Count, entryPoints.Count, StringComparer.Ordinal);
507
508 for (var i = 0; ; i++)
509 {
510 var templateName = String.Format(ENTRYPOINT_FORMAT, i);
511 var templateAsciiBytes = Encoding.ASCII.GetBytes(templateName);
512 var templateUniBytes = Encoding.Unicode.GetBytes(templateName);
513
514 var nameOffset = MakeSfxCA.FindBytes(fileBytes, templateAsciiBytes);
515 if (nameOffset < 0)
516 {
517 break;
518 }
519
520 var pathOffset = MakeSfxCA.FindBytes(fileBytes, templateUniBytes);
521 if (pathOffset < 0)
522 {
523 break;
524 }
525
526 var entryPointName = slotSort[i];
527 var entryPointPath = entryPointName.Length > 0 ?
528 entryPoints[entryPointName] : String.Empty;
529
530 if (entryPointName.Length > MAX_ENTRYPOINT_NAME)
531 {
532 throw new ArgumentException(String.Format(
533 "Entry point name exceeds limit of {0} characters: {1}",
534 MAX_ENTRYPOINT_NAME,
535 entryPointName));
536 }
537
538 if (entryPointPath.Length > MAX_ENTRYPOINT_PATH)
539 {
540 throw new ArgumentException(String.Format(
541 "Entry point path exceeds limit of {0} characters: {1}",
542 MAX_ENTRYPOINT_PATH,
543 entryPointPath));
544 }
545
546 var replaceNameBytes = Encoding.ASCII.GetBytes(entryPointName);
547 var replacePathBytes = Encoding.Unicode.GetBytes(entryPointPath);
548
549 MakeSfxCA.ReplaceBytes(fileBytes, nameOffset, MAX_ENTRYPOINT_NAME, replaceNameBytes);
550 MakeSfxCA.ReplaceBytes(fileBytes, pathOffset, MAX_ENTRYPOINT_PATH * 2, replacePathBytes);
551 }
552
553 if (entryPoints.Count == 0 && uiClass != null)
554 {
555 // Remove the zzz prefix from exported EmbeddedUI entry-points.
556 foreach (var export in new string[] { "InitializeEmbeddedUI", "EmbeddedUIHandler", "ShutdownEmbeddedUI" })
557 {
558 var exportNameBytes = Encoding.ASCII.GetBytes("zzz" + export);
559
560 var exportOffset = MakeSfxCA.FindBytes(fileBytes, exportNameBytes);
561 if (exportOffset < 0)
562 {
563 throw new ArgumentException("Input SfxCA.dll does not contain exported entry-point: " + export);
564 }
565
566 var replaceNameBytes = Encoding.ASCII.GetBytes(export);
567 MakeSfxCA.ReplaceBytes(fileBytes, exportOffset, exportNameBytes.Length, replaceNameBytes);
568 }
569
570 if (uiClass.Length > MAX_ENTRYPOINT_PATH)
571 {
572 throw new ArgumentException(String.Format(
573 "UI class full name exceeds limit of {0} characters: {1}",
574 MAX_ENTRYPOINT_PATH,
575 uiClass));
576 }
577
578 var templateBytes = Encoding.Unicode.GetBytes("InitializeEmbeddedUI_FullClassName");
579 var replaceBytes = Encoding.Unicode.GetBytes(uiClass);
580
581 // Fill in the embedded UI implementor class so the proxy knows which one to load.
582 var replaceOffset = MakeSfxCA.FindBytes(fileBytes, templateBytes);
583 if (replaceOffset >= 0)
584 {
585 MakeSfxCA.ReplaceBytes(fileBytes, replaceOffset, MAX_ENTRYPOINT_PATH * 2, replaceBytes);
586 }
587 }
588
589 outputStream.Write(fileBytes, 0, fileBytes.Length);
590 }
591
592 /// <summary>
593 /// Searches for a sub-array of bytes within a larger array of bytes.
594 /// </summary>
595 private static int FindBytes(byte[] source, byte[] find)
596 {
597 for (var i = 0; i < source.Length; i++)
598 {
599 int j;
600 for (j = 0; j < find.Length; j++)
601 {
602 if (source[i + j] != find[j])
603 {
604 break;
605 }
606 }
607
608 if (j == find.Length)
609 {
610 return i;
611 }
612 }
613
614 return -1;
615 }
616
617 /// <summary>
618 /// Replaces a range of bytes with new bytes, padding any extra part
619 /// of the range with zeroes.
620 /// </summary>
621 private static void ReplaceBytes(
622 byte[] source, int offset, int length, byte[] replace)
623 {
624 for (var i = 0; i < length; i++)
625 {
626 if (i < replace.Length)
627 {
628 source[offset + i] = replace[i];
629 }
630 else
631 {
632 source[offset + i] = 0;
633 }
634 }
635 }
636
637 /// <summary>
638 /// Print the name of one file as it is being packed into the cab.
639 /// </summary>
640 private static void PackProgress(object source, ArchiveProgressEventArgs e)
641 {
642 if (e.ProgressType == ArchiveProgressType.StartFile && log != null)
643 {
644 log.WriteLine(" {0}", e.CurrentFileName);
645 }
646 }
647
648 /// <summary>
649 /// Gets a mapping from filenames as they will be in the cab to filenames
650 /// as they are currently on disk.
651 /// </summary>
652 /// <remarks>
653 /// By default, all files will be placed in the root of the cab. But inputs may
654 /// optionally include an alternate inside-cab file path before an equals sign.
655 /// </remarks>
656 private static IDictionary<string, string> GetPackFileMap(IList<string> inputs)
657 {
658 var fileMap = new Dictionary<string, string>();
659 foreach (var inputFile in inputs)
660 {
661 if (inputFile.IndexOf('=') > 0)
662 {
663 var parse = inputFile.Split('=');
664 if (!fileMap.ContainsKey(parse[0]))
665 {
666 fileMap.Add(parse[0], parse[1]);
667 }
668 }
669 else
670 {
671 var fileName = Path.GetFileName(inputFile);
672 if (!fileMap.ContainsKey(fileName))
673 {
674 fileMap.Add(fileName, inputFile);
675 }
676 }
677 }
678 return fileMap;
679 }
680
681 /// <summary>
682 /// Packs the input files into a cab that is appended to the
683 /// output SfxCA.dll.
684 /// </summary>
685 private static void PackInputFiles(string outputFile, IDictionary<string, string> fileMap)
686 {
687 log.WriteLine("Packaging files");
688
689 var cabInfo = new CabInfo(outputFile);
690 cabInfo.PackFileSet(null, fileMap, CompressionLevel.Max, PackProgress);
691 }
692
693 /// <summary>
694 /// Copies the version resource information from the CA module to
695 /// the CA package. This gives the package the file version and
696 /// description of the CA module, instead of the version and
697 /// description of SfxCA.dll.
698 /// </summary>
699 private static void CopyVersionResource(string sourceFile, string destFile)
700 {
701 log.WriteLine("Copying file version info from {0} to {1}",
702 sourceFile, destFile);
703
704 var rc = new ResourceCollection();
705 rc.Find(sourceFile, ResourceType.Version);
706 rc.Load(sourceFile);
707 rc.Save(destFile);
708 }
709 }
710}
diff --git a/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.exe.manifest b/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.exe.manifest
new file mode 100644
index 00000000..5224db50
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MakeSfxCA/MakeSfxCA.exe.manifest
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3 <assemblyIdentity name="WixToolset.Dtf.MakeSfxCA" version="4.0.0.0" processorArchitecture="x86" type="win32"/>
4 <description>WiX Toolset Compiler</description>
5 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
6 <security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security>
7 </trustInfo>
8 <application xmlns="urn:schemas-microsoft-com:asm.v3">
9 <windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"><ws2:longPathAware>true</ws2:longPathAware></windowsSettings>
10 </application>
11</assembly>
diff --git a/src/dtf/WixToolset.Dtf.MakeSfxCA/WixToolset.Dtf.MakeSfxCA.csproj b/src/dtf/WixToolset.Dtf.MakeSfxCA/WixToolset.Dtf.MakeSfxCA.csproj
new file mode 100644
index 00000000..e62aaed3
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MakeSfxCA/WixToolset.Dtf.MakeSfxCA.csproj
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <OutputType>Exe</OutputType>
7 <TargetFrameworks>net472</TargetFrameworks>
8 <IsPackable>false</IsPackable>
9 <DebugType>embedded</DebugType>
10 <AppConfig>app.config</AppConfig>
11 <ApplicationManifest>MakeSfxCA.exe.manifest</ApplicationManifest>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <ProjectReference Include="..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj" />
16 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
17 <ProjectReference Include="..\WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj" />
18 </ItemGroup>
19</Project>
diff --git a/src/dtf/WixToolset.Dtf.MakeSfxCA/app.config b/src/dtf/WixToolset.Dtf.MakeSfxCA/app.config
new file mode 100644
index 00000000..29bbc006
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.MakeSfxCA/app.config
@@ -0,0 +1,7 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<configuration>
3 <runtime>
4 <loadFromRemoteSources enabled="true"/>
5 <AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" />
6 </runtime>
7</configuration>
diff --git a/src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs b/src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs
index b37d5311..8d46b54b 100644
--- a/src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs
+++ b/src/dtf/WixToolset.Dtf.Resources/ResourceCollection.cs
@@ -18,7 +18,7 @@ namespace WixToolset.Dtf.Resources
18 /// <remarks> 18 /// <remarks>
19 /// To use this class:<list type="number"> 19 /// To use this class:<list type="number">
20 /// <item>Create a new ResourceCollection</item> 20 /// <item>Create a new ResourceCollection</item>
21 /// <item>Locate resources for the collection by calling one of the <see cref="ResourceCollection.Find(string)"/> methods</item> 21 /// <item>Locate resources for the collection by calling one of the <see cref="ResourceCollection.Find(String)"/> methods</item>
22 /// <item>Load data of one or more <see cref="Resource"/>s from a file by calling the <see cref="Load"/> method of the 22 /// <item>Load data of one or more <see cref="Resource"/>s from a file by calling the <see cref="Load"/> method of the
23 /// Resource class, or load them all at once (more efficient) with the <see cref="Load"/> method of the ResourceCollection.</item> 23 /// Resource class, or load them all at once (more efficient) with the <see cref="Load"/> method of the ResourceCollection.</item>
24 /// <item>Read and/or edit data of the individual Resource objects using the methods on that class.</item> 24 /// <item>Read and/or edit data of the individual Resource objects using the methods on that class.</item>
@@ -28,7 +28,7 @@ namespace WixToolset.Dtf.Resources
28 /// </remarks> 28 /// </remarks>
29 public class ResourceCollection : ICollection<Resource> 29 public class ResourceCollection : ICollection<Resource>
30 { 30 {
31 private List<Resource> resources; 31 private readonly List<Resource> resources;
32 32
33 /// <summary> 33 /// <summary>
34 /// Creates a new, empty ResourceCollection. 34 /// Creates a new, empty ResourceCollection.
@@ -48,17 +48,17 @@ namespace WixToolset.Dtf.Resources
48 { 48 {
49 this.Clear(); 49 this.Clear();
50 50
51 IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); 51 var module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
52 if (module == IntPtr.Zero) 52 if (module == IntPtr.Zero)
53 { 53 {
54 int err = Marshal.GetLastWin32Error(); 54 var err = Marshal.GetLastWin32Error();
55 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to load resource file. Error code: {0}", err)); 55 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to load resource file. Error code: {0}", err));
56 } 56 }
57 try 57 try
58 { 58 {
59 if (!NativeMethods.EnumResourceTypes(module, new NativeMethods.EnumResTypesProc(this.EnumResTypes), IntPtr.Zero)) 59 if (!NativeMethods.EnumResourceTypes(module, new NativeMethods.EnumResTypesProc(this.EnumResTypes), IntPtr.Zero))
60 { 60 {
61 int err = Marshal.GetLastWin32Error(); 61 var err = Marshal.GetLastWin32Error();
62 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to enumerate resources. Error code: {0}", err)); 62 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to enumerate resources. Error code: {0}", err));
63 } 63 }
64 } 64 }
@@ -79,12 +79,12 @@ namespace WixToolset.Dtf.Resources
79 { 79 {
80 this.Clear(); 80 this.Clear();
81 81
82 IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); 82 var module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
83 try 83 try
84 { 84 {
85 if (!NativeMethods.EnumResourceNames(module, (string) type, new NativeMethods.EnumResNamesProc(this.EnumResNames), IntPtr.Zero)) 85 if (!NativeMethods.EnumResourceNames(module, (string) type, new NativeMethods.EnumResNamesProc(this.EnumResNames), IntPtr.Zero))
86 { 86 {
87 int err = Marshal.GetLastWin32Error(); 87 var err = Marshal.GetLastWin32Error();
88 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error. Error code: {0}", err)); 88 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error. Error code: {0}", err));
89 } 89 }
90 } 90 }
@@ -106,12 +106,12 @@ namespace WixToolset.Dtf.Resources
106 { 106 {
107 this.Clear(); 107 this.Clear();
108 108
109 IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); 109 var module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
110 try 110 try
111 { 111 {
112 if (!NativeMethods.EnumResourceLanguages(module, (string) type, name, new NativeMethods.EnumResLangsProc(this.EnumResLangs), IntPtr.Zero)) 112 if (!NativeMethods.EnumResourceLanguages(module, (string) type, name, new NativeMethods.EnumResLangsProc(this.EnumResLangs), IntPtr.Zero))
113 { 113 {
114 int err = Marshal.GetLastWin32Error(); 114 var err = Marshal.GetLastWin32Error();
115 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err)); 115 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err));
116 } 116 }
117 } 117 }
@@ -123,9 +123,9 @@ namespace WixToolset.Dtf.Resources
123 123
124 private bool EnumResTypes(IntPtr module, IntPtr type, IntPtr param) 124 private bool EnumResTypes(IntPtr module, IntPtr type, IntPtr param)
125 { 125 {
126 if (!NativeMethods.EnumResourceNames(module, type, new NativeMethods.EnumResNamesProc(EnumResNames), IntPtr.Zero)) 126 if (!NativeMethods.EnumResourceNames(module, type, new NativeMethods.EnumResNamesProc(this.EnumResNames), IntPtr.Zero))
127 { 127 {
128 int err = Marshal.GetLastWin32Error(); 128 var err = Marshal.GetLastWin32Error();
129 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error! Error code: {0}", err)); 129 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error! Error code: {0}", err));
130 } 130 }
131 return true; 131 return true;
@@ -133,9 +133,9 @@ namespace WixToolset.Dtf.Resources
133 133
134 private bool EnumResNames(IntPtr module, IntPtr type, IntPtr name, IntPtr param) 134 private bool EnumResNames(IntPtr module, IntPtr type, IntPtr name, IntPtr param)
135 { 135 {
136 if (!NativeMethods.EnumResourceLanguages(module, type, name, new NativeMethods.EnumResLangsProc(EnumResLangs), IntPtr.Zero)) 136 if (!NativeMethods.EnumResourceLanguages(module, type, name, new NativeMethods.EnumResLangsProc(this.EnumResLangs), IntPtr.Zero))
137 { 137 {
138 int err = Marshal.GetLastWin32Error(); 138 var err = Marshal.GetLastWin32Error();
139 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err)); 139 throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err));
140 } 140 }
141 return true; 141 return true;
@@ -179,10 +179,10 @@ namespace WixToolset.Dtf.Resources
179 /// <param name="file">The file from which resources are loaded.</param> 179 /// <param name="file">The file from which resources are loaded.</param>
180 public void Load(string file) 180 public void Load(string file)
181 { 181 {
182 IntPtr module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE); 182 var module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
183 try 183 try
184 { 184 {
185 foreach (Resource res in this) 185 foreach (var res in this)
186 { 186 {
187 res.Load(module); 187 res.Load(module);
188 } 188 }
@@ -199,17 +199,17 @@ namespace WixToolset.Dtf.Resources
199 /// <param name="file">The file to which resources are saved.</param> 199 /// <param name="file">The file to which resources are saved.</param>
200 public void Save(string file) 200 public void Save(string file)
201 { 201 {
202 IntPtr updateHandle = IntPtr.Zero; 202 var updateHandle = IntPtr.Zero;
203 try 203 try
204 { 204 {
205 updateHandle = NativeMethods.BeginUpdateResource(file, false); 205 updateHandle = NativeMethods.BeginUpdateResource(file, false);
206 foreach (Resource res in this) 206 foreach (var res in this)
207 { 207 {
208 res.Save(updateHandle); 208 res.Save(updateHandle);
209 } 209 }
210 if (!NativeMethods.EndUpdateResource(updateHandle, false)) 210 if (!NativeMethods.EndUpdateResource(updateHandle, false))
211 { 211 {
212 int err = Marshal.GetLastWin32Error(); 212 var err = Marshal.GetLastWin32Error();
213 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error {0}", err)); 213 throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error {0}", err));
214 } 214 }
215 updateHandle = IntPtr.Zero; 215 updateHandle = IntPtr.Zero;
diff --git a/src/dtf/dtf.cmd b/src/dtf/dtf.cmd
index dbc67c63..6b55ecfe 100644
--- a/src/dtf/dtf.cmd
+++ b/src/dtf/dtf.cmd
@@ -8,6 +8,8 @@
8 8
9@echo Building dtf %_C% 9@echo Building dtf %_C%
10 10
11msbuild -Restore SfxCA\sfxca_t.proj -p:Configuration=%_C% -nologo -m -warnaserror -bl:..\..\build\logs\dtf_sfxca.binlog || exit /b
12
11msbuild -Restore -t:Pack dtf.sln -p:Configuration=%_C% -nologo -m -warnaserror -bl:..\..\build\logs\dtf_build.binlog || exit /b 13msbuild -Restore -t:Pack dtf.sln -p:Configuration=%_C% -nologo -m -warnaserror -bl:..\..\build\logs\dtf_build.binlog || exit /b
12 14
13@popd 15@popd
diff --git a/src/dtf/dtf.sln b/src/dtf/dtf.sln
index fbd9452c..36592dcf 100644
--- a/src/dtf/dtf.sln
+++ b/src/dtf/dtf.sln
@@ -1,33 +1,39 @@
1 1
2Microsoft Visual Studio Solution File, Format Version 12.00 2Microsoft Visual Studio Solution File, Format Version 12.00
3# Visual Studio 15 3# Visual Studio Version 17
4VisualStudioVersion = 15.0.26730.8 4VisualStudioVersion = 17.1.32228.430
5MinimumVisualStudioVersion = 15.0.26124.0 5MinimumVisualStudioVersion = 15.0.26124.0
6Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression", "WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj", "{2D62850C-9F81-4BE9-BDF3-9379389C8F7B}" 6Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Compression", "WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj", "{2D62850C-9F81-4BE9-BDF3-9379389C8F7B}"
7EndProject 7EndProject
8Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression.Cab", "WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj", "{15895FD1-DD68-407B-8717-08F6DD14F02C}" 8Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Compression.Cab", "WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj", "{15895FD1-DD68-407B-8717-08F6DD14F02C}"
9EndProject 9EndProject
10Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Compression.Zip", "WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj", "{261F2857-B521-42A4-A3E0-B5165F225E50}" 10Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Compression.Zip", "WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj", "{261F2857-B521-42A4-A3E0-B5165F225E50}"
11EndProject 11EndProject
12Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.Resources", "WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj", "{44931ECB-8D6F-4C12-A872-64E261B6A98E}" 12Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.Resources", "WixToolset.Dtf.Resources\WixToolset.Dtf.Resources.csproj", "{44931ECB-8D6F-4C12-A872-64E261B6A98E}"
13EndProject 13EndProject
14Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller", "WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj", "{24121677-0ED0-41B5-833F-1B9A18E87BF4}" 14Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.WindowsInstaller", "WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj", "{24121677-0ED0-41B5-833F-1B9A18E87BF4}"
15EndProject 15EndProject
16Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller.Linq", "WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj", "{CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}" 16Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.WindowsInstaller.Linq", "WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj", "{CD7A37D8-9D8C-41BD-B78F-DB5E0C299D2E}"
17EndProject 17EndProject
18Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolset.Dtf.WindowsInstaller.Package", "WixToolset.Dtf.WindowsInstaller.Package\WixToolset.Dtf.WindowsInstaller.Package.csproj", "{1A9940A7-3E29-4428-B753-C4CC66058F1A}" 18Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.WindowsInstaller.Package", "WixToolset.Dtf.WindowsInstaller.Package\WixToolset.Dtf.WindowsInstaller.Package.csproj", "{1A9940A7-3E29-4428-B753-C4CC66058F1A}"
19EndProject 19EndProject
20Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression", "WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj", "{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}" 20Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression", "test\WixToolsetTests.Dtf.Compression\WixToolsetTests.Dtf.Compression.csproj", "{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}"
21EndProject 21EndProject
22Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Cab", "WixToolsetTests.Dtf.Compression.Cab\WixToolsetTests.Dtf.Compression.Cab.csproj", "{4544158C-2D63-4146-85FF-62169280144E}" 22Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Cab", "test\WixToolsetTests.Dtf.Compression.Cab\WixToolsetTests.Dtf.Compression.Cab.csproj", "{4544158C-2D63-4146-85FF-62169280144E}"
23EndProject 23EndProject
24Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Zip", "WixToolsetTests.Dtf.Compression.Zip\WixToolsetTests.Dtf.Compression.Zip.csproj", "{328799BB-7B03-4B28-8180-4132211FD07D}" 24Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.Compression.Zip", "test\WixToolsetTests.Dtf.Compression.Zip\WixToolsetTests.Dtf.Compression.Zip.csproj", "{328799BB-7B03-4B28-8180-4132211FD07D}"
25EndProject 25EndProject
26Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller", "WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj", "{16F5202F-9276-4166-975C-C9654BAF8012}" 26Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller", "test\WixToolsetTests.Dtf.WindowsInstaller\WixToolsetTests.Dtf.WindowsInstaller.csproj", "{16F5202F-9276-4166-975C-C9654BAF8012}"
27EndProject 27EndProject
28Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.CustomActions", "WixToolsetTests.Dtf.WindowsInstaller.CustomActions\WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj", "{137D376B-989F-4FEA-9A67-01D8D38CA0DE}" 28Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.CustomActions", "test\WixToolsetTests.Dtf.WindowsInstaller.CustomActions\WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj", "{137D376B-989F-4FEA-9A67-01D8D38CA0DE}"
29EndProject 29EndProject
30Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.Linq", "WixToolsetTests.Dtf.WindowsInstaller.Linq\WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj", "{4F55F9B8-D8B6-41EB-8796-221B4CD98324}" 30Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WixToolsetTests.Dtf.WindowsInstaller.Linq", "test\WixToolsetTests.Dtf.WindowsInstaller.Linq\WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj", "{4F55F9B8-D8B6-41EB-8796-221B4CD98324}"
31EndProject
32Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{222DA0A6-5E28-4D7A-A227-B818B0C55BAB}"
33EndProject
34Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.MakeSfxCA", "WixToolset.Dtf.MakeSfxCA\WixToolset.Dtf.MakeSfxCA.csproj", "{F8CA8E72-08BF-4A8A-AD32-C638616B72E2}"
35EndProject
36Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Dtf.CustomAction", "WixToolset.Dtf.CustomAction\WixToolset.Dtf.CustomAction.csproj", "{D6C0D94C-80A5-495C-B573-C7440A8594F5}"
31EndProject 37EndProject
32Global 38Global
33 GlobalSection(SolutionConfigurationPlatforms) = preSolution 39 GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -129,8 +135,8 @@ Global
129 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x64.Build.0 = Debug|Any CPU 135 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x64.Build.0 = Debug|Any CPU
130 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.ActiveCfg = Debug|Any CPU 136 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.ActiveCfg = Debug|Any CPU
131 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.Build.0 = Debug|Any CPU 137 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Debug|x86.Build.0 = Debug|Any CPU
132 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.ActiveCfg = Release|Any CPU 138 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.ActiveCfg = Debug|Any CPU
133 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.Build.0 = Release|Any CPU 139 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|Any CPU.Build.0 = Debug|Any CPU
134 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.ActiveCfg = Release|Any CPU 140 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.ActiveCfg = Release|Any CPU
135 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.Build.0 = Release|Any CPU 141 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x64.Build.0 = Release|Any CPU
136 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x86.ActiveCfg = Release|Any CPU 142 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}.Release|x86.ActiveCfg = Release|Any CPU
@@ -141,8 +147,8 @@ Global
141 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x64.Build.0 = Debug|Any CPU 147 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x64.Build.0 = Debug|Any CPU
142 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.ActiveCfg = Debug|Any CPU 148 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.ActiveCfg = Debug|Any CPU
143 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.Build.0 = Debug|Any CPU 149 {4544158C-2D63-4146-85FF-62169280144E}.Debug|x86.Build.0 = Debug|Any CPU
144 {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.ActiveCfg = Release|Any CPU 150 {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.ActiveCfg = Debug|Any CPU
145 {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.Build.0 = Release|Any CPU 151 {4544158C-2D63-4146-85FF-62169280144E}.Release|Any CPU.Build.0 = Debug|Any CPU
146 {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.ActiveCfg = Release|Any CPU 152 {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.ActiveCfg = Release|Any CPU
147 {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.Build.0 = Release|Any CPU 153 {4544158C-2D63-4146-85FF-62169280144E}.Release|x64.Build.0 = Release|Any CPU
148 {4544158C-2D63-4146-85FF-62169280144E}.Release|x86.ActiveCfg = Release|Any CPU 154 {4544158C-2D63-4146-85FF-62169280144E}.Release|x86.ActiveCfg = Release|Any CPU
@@ -153,8 +159,8 @@ Global
153 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x64.Build.0 = Debug|Any CPU 159 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x64.Build.0 = Debug|Any CPU
154 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.ActiveCfg = Debug|Any CPU 160 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.ActiveCfg = Debug|Any CPU
155 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.Build.0 = Debug|Any CPU 161 {328799BB-7B03-4B28-8180-4132211FD07D}.Debug|x86.Build.0 = Debug|Any CPU
156 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.ActiveCfg = Release|Any CPU 162 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.ActiveCfg = Debug|Any CPU
157 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.Build.0 = Release|Any CPU 163 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|Any CPU.Build.0 = Debug|Any CPU
158 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.ActiveCfg = Release|Any CPU 164 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.ActiveCfg = Release|Any CPU
159 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.Build.0 = Release|Any CPU 165 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x64.Build.0 = Release|Any CPU
160 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x86.ActiveCfg = Release|Any CPU 166 {328799BB-7B03-4B28-8180-4132211FD07D}.Release|x86.ActiveCfg = Release|Any CPU
@@ -165,8 +171,8 @@ Global
165 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x64.Build.0 = Debug|Any CPU 171 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x64.Build.0 = Debug|Any CPU
166 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.ActiveCfg = Debug|Any CPU 172 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.ActiveCfg = Debug|Any CPU
167 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.Build.0 = Debug|Any CPU 173 {16F5202F-9276-4166-975C-C9654BAF8012}.Debug|x86.Build.0 = Debug|Any CPU
168 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.ActiveCfg = Release|Any CPU 174 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.ActiveCfg = Debug|Any CPU
169 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.Build.0 = Release|Any CPU 175 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|Any CPU.Build.0 = Debug|Any CPU
170 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.ActiveCfg = Release|Any CPU 176 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.ActiveCfg = Release|Any CPU
171 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.Build.0 = Release|Any CPU 177 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x64.Build.0 = Release|Any CPU
172 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x86.ActiveCfg = Release|Any CPU 178 {16F5202F-9276-4166-975C-C9654BAF8012}.Release|x86.ActiveCfg = Release|Any CPU
@@ -177,8 +183,8 @@ Global
177 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x64.Build.0 = Debug|Any CPU 183 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x64.Build.0 = Debug|Any CPU
178 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.ActiveCfg = Debug|Any CPU 184 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.ActiveCfg = Debug|Any CPU
179 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.Build.0 = Debug|Any CPU 185 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Debug|x86.Build.0 = Debug|Any CPU
180 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.ActiveCfg = Release|Any CPU 186 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.ActiveCfg = Debug|Any CPU
181 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.Build.0 = Release|Any CPU 187 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|Any CPU.Build.0 = Debug|Any CPU
182 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.ActiveCfg = Release|Any CPU 188 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.ActiveCfg = Release|Any CPU
183 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.Build.0 = Release|Any CPU 189 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x64.Build.0 = Release|Any CPU
184 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x86.ActiveCfg = Release|Any CPU 190 {137D376B-989F-4FEA-9A67-01D8D38CA0DE}.Release|x86.ActiveCfg = Release|Any CPU
@@ -189,29 +195,47 @@ Global
189 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x64.Build.0 = Debug|Any CPU 195 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x64.Build.0 = Debug|Any CPU
190 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.ActiveCfg = Debug|Any CPU 196 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.ActiveCfg = Debug|Any CPU
191 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.Build.0 = Debug|Any CPU 197 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Debug|x86.Build.0 = Debug|Any CPU
192 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.ActiveCfg = Release|Any CPU 198 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.ActiveCfg = Debug|Any CPU
193 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.Build.0 = Release|Any CPU 199 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|Any CPU.Build.0 = Debug|Any CPU
194 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.ActiveCfg = Release|Any CPU 200 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.ActiveCfg = Release|Any CPU
195 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.Build.0 = Release|Any CPU 201 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x64.Build.0 = Release|Any CPU
196 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.ActiveCfg = Release|Any CPU 202 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.ActiveCfg = Release|Any CPU
197 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.Build.0 = Release|Any CPU 203 {4F55F9B8-D8B6-41EB-8796-221B4CD98324}.Release|x86.Build.0 = Release|Any CPU
198 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 204 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
199 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|Any CPU.Build.0 = Debug|Any CPU 205 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
200 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x64.ActiveCfg = Debug|Any CPU 206 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|x64.ActiveCfg = Debug|Any CPU
201 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x64.Build.0 = Debug|Any CPU 207 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|x64.Build.0 = Debug|Any CPU
202 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x86.ActiveCfg = Debug|Any CPU 208 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|x86.ActiveCfg = Debug|Any CPU
203 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Debug|x86.Build.0 = Debug|Any CPU 209 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Debug|x86.Build.0 = Debug|Any CPU
204 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|Any CPU.ActiveCfg = Release|Any CPU 210 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
205 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|Any CPU.Build.0 = Release|Any CPU 211 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|Any CPU.Build.0 = Release|Any CPU
206 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x64.ActiveCfg = Release|Any CPU 212 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|x64.ActiveCfg = Release|Any CPU
207 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x64.Build.0 = Release|Any CPU 213 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|x64.Build.0 = Release|Any CPU
208 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x86.ActiveCfg = Release|Any CPU 214 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|x86.ActiveCfg = Release|Any CPU
209 {E7A00377-A0B5-400F-8337-C0814AAC7153}.Release|x86.Build.0 = Release|Any CPU 215 {F8CA8E72-08BF-4A8A-AD32-C638616B72E2}.Release|x86.Build.0 = Release|Any CPU
216 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
217 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
218 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|x64.ActiveCfg = Debug|Any CPU
219 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|x64.Build.0 = Debug|Any CPU
220 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|x86.ActiveCfg = Debug|Any CPU
221 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Debug|x86.Build.0 = Debug|Any CPU
222 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
223 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|Any CPU.Build.0 = Release|Any CPU
224 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|x64.ActiveCfg = Release|Any CPU
225 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|x64.Build.0 = Release|Any CPU
226 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|x86.ActiveCfg = Release|Any CPU
227 {D6C0D94C-80A5-495C-B573-C7440A8594F5}.Release|x86.Build.0 = Release|Any CPU
210 EndGlobalSection 228 EndGlobalSection
211 GlobalSection(SolutionProperties) = preSolution 229 GlobalSection(SolutionProperties) = preSolution
212 HideSolutionNode = FALSE 230 HideSolutionNode = FALSE
213 EndGlobalSection 231 EndGlobalSection
214 GlobalSection(NestedProjects) = preSolution 232 GlobalSection(NestedProjects) = preSolution
233 {F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB}
234 {4544158C-2D63-4146-85FF-62169280144E} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB}
235 {328799BB-7B03-4B28-8180-4132211FD07D} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB}
236 {16F5202F-9276-4166-975C-C9654BAF8012} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB}
237 {137D376B-989F-4FEA-9A67-01D8D38CA0DE} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB}
238 {4F55F9B8-D8B6-41EB-8796-221B4CD98324} = {222DA0A6-5E28-4D7A-A227-B818B0C55BAB}
215 EndGlobalSection 239 EndGlobalSection
216 GlobalSection(ExtensibilityGlobals) = postSolution 240 GlobalSection(ExtensibilityGlobals) = postSolution
217 SolutionGuid = {BB57C98D-C0C2-4805-AED3-C19B47759DBD} 241 SolutionGuid = {BB57C98D-C0C2-4805-AED3-C19B47759DBD}
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs
index 981ecc69..981ecc69 100644
--- a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/CabTest.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj b/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj
index e751d405..636cedc6 100644
--- a/src/dtf/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj
+++ b/src/dtf/test/WixToolsetTests.Dtf.Compression.Cab/WixToolsetTests.Dtf.Compression.Cab.csproj
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> 2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="Current">
4 <PropertyGroup> 4 <PropertyGroup>
5 <ProjectGuid>{4544158C-2D63-4146-85FF-62169280144E}</ProjectGuid> 5 <ProjectGuid>{4544158C-2D63-4146-85FF-62169280144E}</ProjectGuid>
6 <OutputType>Library</OutputType> 6 <OutputType>Library</OutputType>
@@ -24,11 +24,11 @@
24 </ItemGroup> 24 </ItemGroup>
25 25
26 <ItemGroup> 26 <ItemGroup>
27 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj"> 27 <ProjectReference Include="..\..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj">
28 <Project>{45D81DAB-0559-4836-8106-CE9987FD4AB5}</Project> 28 <Project>{45D81DAB-0559-4836-8106-CE9987FD4AB5}</Project>
29 <Name>WixToolset.Dtf.Compression</Name> 29 <Name>WixToolset.Dtf.Compression</Name>
30 </ProjectReference> 30 </ProjectReference>
31 <ProjectReference Include="..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj"> 31 <ProjectReference Include="..\..\WixToolset.Dtf.Compression.Cab\WixToolset.Dtf.Compression.Cab.csproj">
32 <Project>{E56C0ED3-FA2F-4CA9-A1C0-2E796BB0BF80}</Project> 32 <Project>{E56C0ED3-FA2F-4CA9-A1C0-2E796BB0BF80}</Project>
33 <Name>WixToolset.Dtf.Compression.Cab</Name> 33 <Name>WixToolset.Dtf.Compression.Cab</Name>
34 </ProjectReference> 34 </ProjectReference>
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj b/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj
index 6ee102ae..d46776d8 100644
--- a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj
+++ b/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/WixToolsetTests.Dtf.Compression.Zip.csproj
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> 2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="Current">
4 <PropertyGroup> 4 <PropertyGroup>
5 <ProjectGuid>{328799BB-7B03-4B28-8180-4132211FD07D}</ProjectGuid> 5 <ProjectGuid>{328799BB-7B03-4B28-8180-4132211FD07D}</ProjectGuid>
6 <OutputType>Library</OutputType> 6 <OutputType>Library</OutputType>
@@ -22,11 +22,11 @@
22 </ItemGroup> 22 </ItemGroup>
23 23
24 <ItemGroup> 24 <ItemGroup>
25 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj"> 25 <ProjectReference Include="..\..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj">
26 <Project>{45D81DAB-0559-4836-8106-CE9987FD4AB5}</Project> 26 <Project>{45D81DAB-0559-4836-8106-CE9987FD4AB5}</Project>
27 <Name>WixToolset.Dtf.Compression</Name> 27 <Name>WixToolset.Dtf.Compression</Name>
28 </ProjectReference> 28 </ProjectReference>
29 <ProjectReference Include="..\WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj"> 29 <ProjectReference Include="..\..\WixToolset.Dtf.Compression.Zip\WixToolset.Dtf.Compression.Zip.csproj">
30 <Project>{E4C60A57-8AFE-4FF3-9058-ACAC6A069533}</Project> 30 <Project>{E4C60A57-8AFE-4FF3-9058-ACAC6A069533}</Project>
31 <Name>WixToolset.Dtf.Compression.Zip</Name> 31 <Name>WixToolset.Dtf.Compression.Zip</Name>
32 </ProjectReference> 32 </ProjectReference>
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs
index b264ad5b..b264ad5b 100644
--- a/src/dtf/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.Compression.Zip/ZipTest.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs
index e7a5373d..e7a5373d 100644
--- a/src/dtf/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.Compression/CompressionTestUtil.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs
index 2531f3bc..2531f3bc 100644
--- a/src/dtf/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.Compression/MisbehavingStreamContext.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs b/src/dtf/test/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs
index 98354d97..98354d97 100644
--- a/src/dtf/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.Compression/OptionStreamContext.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj b/src/dtf/test/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj
index 194628a7..628d36c5 100644
--- a/src/dtf/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj
+++ b/src/dtf/test/WixToolsetTests.Dtf.Compression/WixToolsetTests.Dtf.Compression.csproj
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> 2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="Current">
4 <PropertyGroup> 4 <PropertyGroup>
5 <ProjectGuid>{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}</ProjectGuid> 5 <ProjectGuid>{F045FFC1-05F9-4EA2-9F03-E1CBDB7BC4F9}</ProjectGuid>
6 <OutputType>Library</OutputType> 6 <OutputType>Library</OutputType>
@@ -25,7 +25,7 @@
25 </ItemGroup> 25 </ItemGroup>
26 26
27 <ItemGroup> 27 <ItemGroup>
28 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj"> 28 <ProjectReference Include="..\..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj">
29 <Project>{45D81DAB-0559-4836-8106-CE9987FD4AB5}</Project> 29 <Project>{45D81DAB-0559-4836-8106-CE9987FD4AB5}</Project>
30 <Name>WixToolset.Dtf.Compression</Name> 30 <Name>WixToolset.Dtf.Compression</Name>
31 </ProjectReference> 31 </ProjectReference>
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs
index bf843024..bf843024 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/CustomActionTest.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj
index 27e0b499..a2f45fde 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.CustomActions/WixToolsetTests.Dtf.WindowsInstaller.CustomActions.csproj
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> 2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="Current">
4 <PropertyGroup> 4 <PropertyGroup>
5 <ProjectGuid>{137D376B-989F-4FEA-9A67-01D8D38CA0DE}</ProjectGuid> 5 <ProjectGuid>{137D376B-989F-4FEA-9A67-01D8D38CA0DE}</ProjectGuid>
6 <OutputType>Library</OutputType> 6 <OutputType>Library</OutputType>
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs
index 7776a1c3..7776a1c3 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/LinqTest.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj
index a59e64d4..c34494b7 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller.Linq/WixToolsetTests.Dtf.WindowsInstaller.Linq.csproj
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> 2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="Current">
4 <PropertyGroup> 4 <PropertyGroup>
5 <ProjectGuid>{4F55F9B8-D8B6-41EB-8796-221B4CD98324}</ProjectGuid> 5 <ProjectGuid>{4F55F9B8-D8B6-41EB-8796-221B4CD98324}</ProjectGuid>
6 <OutputType>Library</OutputType> 6 <OutputType>Library</OutputType>
@@ -23,11 +23,11 @@
23 </ItemGroup> 23 </ItemGroup>
24 24
25 <ItemGroup> 25 <ItemGroup>
26 <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj"> 26 <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj">
27 <Project>{85225597-5121-4361-8332-4E3246D5BBF5}</Project> 27 <Project>{85225597-5121-4361-8332-4E3246D5BBF5}</Project>
28 <Name>WixToolset.Dtf.WindowsInstaller</Name> 28 <Name>WixToolset.Dtf.WindowsInstaller</Name>
29 </ProjectReference> 29 </ProjectReference>
30 <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj"> 30 <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller.Linq\WixToolset.Dtf.WindowsInstaller.Linq.csproj">
31 <Project>{7E66313B-C6D4-4729-8422-4D1474E0E6F7}</Project> 31 <Project>{7E66313B-C6D4-4729-8422-4D1474E0E6F7}</Project>
32 <Name>WixToolset.Dtf.WindowsInstaller.Linq</Name> 32 <Name>WixToolset.Dtf.WindowsInstaller.Linq</Name>
33 </ProjectReference> 33 </ProjectReference>
@@ -39,4 +39,4 @@
39 39
40 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> 40 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
41 <Target Name="Pack" DependsOnTargets="Build" /> 41 <Target Name="Pack" DependsOnTargets="Build" />
42</Project> 42</Project> \ No newline at end of file
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs
index b0fc00a8..b0fc00a8 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/EmbeddedExternalUI.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs
index 26c172c9..26c172c9 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/Schema.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs
index f994dfef..f994dfef 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTest.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs
index 3bdf5acd..3bdf5acd 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerTransactions.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs
index 644f1988..644f1988 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WindowsInstallerUtils.cs
diff --git a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj
index 0d2a50fb..eaa273ed 100644
--- a/src/dtf/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj
+++ b/src/dtf/test/WixToolsetTests.Dtf.WindowsInstaller/WixToolsetTests.Dtf.WindowsInstaller.csproj
@@ -1,6 +1,6 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> 2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 3<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="Current">
4 <PropertyGroup> 4 <PropertyGroup>
5 <ProjectGuid>{16F5202F-9276-4166-975C-C9654BAF8012}</ProjectGuid> 5 <ProjectGuid>{16F5202F-9276-4166-975C-C9654BAF8012}</ProjectGuid>
6 <OutputType>Library</OutputType> 6 <OutputType>Library</OutputType>
@@ -28,7 +28,7 @@
28 </ItemGroup> 28 </ItemGroup>
29 29
30 <ItemGroup> 30 <ItemGroup>
31 <ProjectReference Include="..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj"> 31 <ProjectReference Include="..\..\WixToolset.Dtf.WindowsInstaller\WixToolset.Dtf.WindowsInstaller.csproj">
32 <Project>{85225597-5121-4361-8332-4E3246D5BBF5}</Project> 32 <Project>{85225597-5121-4361-8332-4E3246D5BBF5}</Project>
33 <Name>WixToolset.Dtf.WindowsInstaller</Name> 33 <Name>WixToolset.Dtf.WindowsInstaller</Name>
34 </ProjectReference> 34 </ProjectReference>